互動題

CF 1867 E2. Salyg1n and Array (hard version)

有一個長度為 \(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\) 皆為偶數

思路

若 n % k == 0,那我們可以每次從左到右依序詢問長度為 k 的區間即可。若 n % k != 0,代表我們左到右依序詢問長度為 k 的區間後會剩下一段,我們的目標就是將這段的值算出來。可以發現若我們以剩下區間的一半做為 query 的結尾 query 一次,在以 n 作為結尾 query 一次,中間多餘的貢獻會剛好消除

Image title

Image title

code
#include <bits/stdc++.h>
#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();
    }
} 
CF 1918 E. ace5 and Task Order

有一個長度為 \(n\)\(1\sim n\) 的 permutation 以及一個介於 \(1\)\(n\) 之間的正整數\(x\)。目標是要去確定這個 permutation。可以通過 ? i 進行詢問,返回值有三種:

  • <:表示 \(a_i<x\),該次詢問後,\(x\leftarrow x-1\)

  • >:表示 \(a_i>x\),該次詢問後,\(x\leftarrow x+1\)

  • =:表示 \(a_i=x\)

\(n\leq 2000,\) 詢問次數 \(\le 40n\)

思路

首先,對於一個有 \(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)。

參考: https://www.luogu.com.cn/blog/wosile/solution-cf1918e

code
#include <bits/stdc++.h>
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<int>& 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<int> 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<int> 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
}