???+note "[CF 1867 E2. Salyg1n and Array (hard version)](https://codeforces.com/contest/1867/problem/E2)"
有一個長度為 $n$ 的陣列 $a_1,\ldots ,a_n$,目標是輸出 $a_1\oplus \ldots \oplus a_n$。給 $k$,你可以做以下查詢至多 57 次 :
- $\text{query}(i):$ 問 $a_i \oplus a_{i + 1} \oplus \ldots \oplus a_{i + k - 1}$ 是多少,問了之後,這個區間內的元素就會被 reverse
$n,k\le 2500,n,k$ 皆為偶數
??? note "思路"
若 n % k == 0,那我們可以每次從左到右依序詢問長度為 k 的區間即可。若 n % k != 0,代表我們左到右依序詢問長度為 k 的區間後會剩下一段,我們的目標就是將這段的值算出來。可以發現若我們以剩下區間的一半做為 query 的結尾 query 一次,在以 n 作為結尾 query 一次,中間多餘的貢獻會剛好消除
??? note "code"
```cpp linenums="1"
#include
#define int long long
using namespace std;
int query(int i) {
cout << "? " << i << endl;
int res;
cin >> res;
return res;
}
void solve() {
int n, k;
cin >> n >> k;
int ans = 0, now = 1;
while (now + k - 1 <= n) {
int res = query(now);
ans ^= res;
now += k;
}
if (now < n) {
int tmp = (n - now + 1) / 2;
int res1 = query(now + tmp - k);
ans ^= res1;
int res2 = query(now + tmp + tmp - k);
ans ^= res2;
}
cout << "! " << ans << '\n';
}
signed main() {
int t = 1;
cin >> t;
while (t--) {
solve();
}
}
```
???+note "[CF 1918 E. ace5 and Task Order](https://www.luogu.com.cn/problem/CF1918E)"
有一個長度為 $n$ 的 $1\sim n$ 的 permutation 以及一個介於 $1$ 和 $n$ 之間的正整數$x$。目標是要去確定這個 permutation。可以通過 `? i` 進行詢問,返回值有三種:
- `<`:表示 $a_i`:表示 $a_i>x$,該次詢問後,$x\leftarrow x+1$
- `=`:表示 $a_i=x$
$n\leq 2000,$ 詢問次數 $\le 40n$
??? note "思路"
首先,對於一個有 $O(n^2)$ 次查詢的問題,解答很明顯:對於每個位置 i,持續查詢直到返回等於,然後通過大小關係直接對這 n 個數進行排序,從而獲得答案。
既然排序可以得到答案,我們考慮那些 $O(n \log n)$ 的排序算法,比如借鑒快速排序的思路。
但我們發現在無法確定 x 是多少個情況下,我們什麼都很難做。所以首先我們必須去能有權力控制 x,而最好的辦法就是知道 1 還有 n 在哪裡,一般題目在分析時也很常從最小和最大開始找。
具體來講,我們只要掃一遍 1 ~ n,對於每個位置,如果詢問返回 < 就繼續查直到不是 < 為止,否則直接不管。顯然這樣最多查詢 O(n) 次(因為 x 最多增大 n 次),並且在 x = 1 的地方一定可以取到 x = 1。我們只要看 x 在哪裡取到最小值即可。找 x = n 同理。
再來,我們借鑑快速排序的想法,我们需要對值域進行分治。在控制 x 的值不變的情況下,我們需要查詢若干個位置的答案,從而將這些位置分成兩組:「大於 x」 和 「小於 x 」的兩組(等於 x 的位置可以直接得到答案)。遞迴下去就可以得到答案。複雜度 O(n log n)。
> 參考:
??? note "code"
```cpp linenums="1"
#include
using namespace std;
int p1, pn;
int ans[2005];
int query(int x) {
cout << "? " << x << endl;
cout.flush();
string s;
cin >> s;
if (s[0] == '=') return 0;
if (s[0] == '<') return -1;
if (s[0] == '>') return 1;
return 0114507537;
}
int cur;
void solve(int l, int r, vector& v) {
// v 是值在 [l,r] 中的下标集合
if (l > r) return;
if (l == r) {
ans[v[0]] = l;
return;
}
int mid = (l + r) / 2;
while (cur > mid) {
query(p1);
cur--;
}
while (cur < mid) {
query(pn);
cur++;
}
vector vl, vr;
vl.clear();
vr.clear();
// 分成 [l,mid-1] 和 [mid+1,r]
for (int x : v) {
int tmp = query(x);
if (tmp == 0) ans[x] = mid;
if (tmp == -1) {
vl.push_back(x);
query(pn);
}
if (tmp == 1) {
vr.push_back(x);
query(p1);
}
}
solve(l, mid - 1, vl);
solve(mid + 1, r, vr);
}
int main() {
int T;
cin >> T;
while (T--) {
int n;
cin >> n;
// find 1&n
p1 = pn = 1;
int md = 0x3f3f3f3f, d = 0;
for (int i = 1; i <= n; i++) {
int tmp = query(i);
d += tmp;
while (tmp == -1) {
tmp = query(i);
d += tmp;
}
if (d < md) p1 = i, md = d;
}
md = -0x3f3f3f3f, d = 0;
for (int i = 1; i <= n; i++) {
int tmp = query(i);
d += tmp;
while (tmp == 1) {
tmp = query(i);
d += tmp;
}
if (d > md) pn = i, md = d;
}
ans[p1] = 1, ans[pn] = n;
vector tmp;
tmp.clear();
for (int i = 1; i <= n; i++)
if (i != p1 && i != pn) tmp.push_back(i);
int val = query(pn);
while (val == 1) val = query(pn);
cur = n;
solve(2, n - 1, tmp);
cout << "! ";
for (int i = 1; i <= n; i++) cout << ans[i] << ' ';
cout << endl;
cout.flush();
}
return 0;
// quod erat demonstrandum
}
```