题意
交互,告诉你一个不降的序列 长度为 ,你可以使用若干次下面的询问:
询问 ,返回 中的众数 以及它的出现次数 。如果有多个众数,返回最小的那个。
假设数列中有 个不同的数字( 不告诉你),你需要在 次询问中求出这个序列。
题解
没看过官方标算,不知道自己的做法和标算一不一样……
考虑 表示执行过之后就能求出 中的所有数字。我们先询问一遍 ,得到当前的众数 和它的出现次数 。
我们考虑一件事,如果已知 ,并且已知了一个 使得 ,那么我们就可以使用两次询问求出所有 的具体位置。分别询问 和 , 必然在其中一个区间成为众数,这样就可以知道 的开头位置或者结尾位置,连续的 个就都是 。然后扔掉这 个数,递归两半继续进行即可。
于是现在问题落到了如何找到 的任意 。我们先考虑 时怎么找,可以分别询问 是多少,直到找到了 。显然刚刚询问的这些数字都互不相同(否则 就不是众数了),这样我们顺便得到了一些其他数字的某个出现位置。
这给了我们一个启发,即能不能做到每个数字都恰好被询问一次?答案是肯定的。
假设当前在 中,如果当前众数 的位置已经被以前找到过了,那么就不需要询问;否则的话,假设我们知道了 的值(这些数字互不相同),那么我们就可以知道 在哪两个数字之间。假设是 和 ,那么我们从 开始,每隔 个数字问一次,直到问到 为止。显然问出的这些数字仍然和以前的所有数字互不相同(首先, 和外部的数字互不相同;其次, 内部的数字每隔 问一次也互不相同)。
综上,我们就用了 次询问找到了每种数字的某个出现位置,每次递归时需要一次询问,此外每个数字找到它出现的整个区间需要两次询问。总询问次数刚好是 。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
template<typename T> inline void chkmin(T &a, const T &b) { a = a < b ? a : b; }
template<typename T> inline void chkmax(T &a, const T &b) { a = a > b ? a : b; }const int MAXN = 200005;
int n, aa[MAXN], bb[MAXN];
map<int, int> mp;void ask(int l, int r, int &x, int &f) {printf("? %d %d\n", l, r);fflush(stdout);scanf("%d%d", &x, &f);
}void divide(int l, int r) {if (l > r) return;int x, f; ask(l, r, x, f);if (r - l + 1 == f) {for (int i = l; i <= r; i++) aa[i] = x;mp[x] = l;return;}auto p = mp.lower_bound(x);int pla = -1;if (p != mp.end() && p->first == x) pla = p->second;else {--p;for (int i = max(l, p->second + f);; i += f) {int a, b; ask(i, i, a, b);assert(!mp[a]);mp[a] = i;if (a == x) { pla = i; break; }}}int a, b, pl, pr; ask(max(l, pla - f + 1), pla, a, b);if (a == x) pl = pla - b + 1, pr = pla - b + f;else {ask(pla, min(pla + f - 1, r), a, b);pl = pla + b - f, pr = pla + b - 1;}for (int i = pl; i <= pr; i++) aa[i] = x;divide(l, pl - 1);divide(pr + 1, r);
}int main() {scanf("%d", &n);mp[0] = 0;divide(1, n);putchar('!');for (int i = 1; i <= n; i++)printf(" %d", aa[i]);fflush(stdout);return 0;
}