CF1698D

发布时间 2023-04-24 19:50:08作者: untitled0

题面

观察题面:\(n\le10^4\),询问次数 \(\le15\),因此考虑二分。

\(x\) 表示没有被交换过的那个元素。

考虑当询问区间 \([l,r]\) 时,得到的序列满足什么条件,才能确定 \(x\) 在这个区间内。

考虑一个在交换前的序列的 \([l,r]\) 区间内的元素 \(y\),由题意 \(a_y=y\)。设和 \(y\) 交换的元素为 \(z\),则 \(z\) 和区间 \([l,r]\) 的关系只有两种情况:

  • \(z\notin[l,r]\)。此时这个区间会“进来”一个外面的元素,“出去”一个里面的元素。
  • \(z\in[l,r]\)。此时这两个元素还在区间内。

因此,如果一个区间内的元素全部被交换过,则在评测机回答的序列中,值在 \(\bold[l,r]\) 之间的元素个数一定是偶数。
(可以理解为:要么出去,要么成对留下)

那么我们只要查出值在 \([l,r]\) 之间的元素个数,如果为奇,说明 \(x\) 在区间 \([l,r]\) 中;为偶则不在。

二分法缩小询问范围,\(l=r\) 时结束循环。

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
constexpr int N = 1e4 + 5;
int main() {
    int t, n; 
    cin >> t;
    while(t--) {
        cin >> n;
        int l = 1, r = n;
        do {
            int mid = (l + r) / 2;
            cout << "? " << l << ' ' << mid << endl;
            int cnt = 0, x;
            for(int i = l; i <= mid; ++i) {
                cin >> x;
                if(x >= l && x <= mid) ++cnt;
            }
            if(cnt & 1) r = mid;
            else l = mid + 1;
        } while(r > l);
        cout << "! " << l << endl;
    }
    return 0;
}