交互类构造题

交互类构造题是一类偏思维的题目,其正解往往十分简单易懂,但是很难想到。
那么我们应该如何解决这样的一类问题呢?

做不出构造题?

事实:题很难构造。

它很难过!

它还有细节,

而另一个你不动脑帮忙(她最坏了)。

不可能完成的任务?

事实上,只要抓住关键点,就能极大幅度的简化和指引你的思考,当然还有可能不会

而这类题目的关键点往往集中于次数和一些特别的提示

以CF2031F为例:

题意:

交互,\(T\)组询问,每次给你一个数组长度\(n\)(保证为偶数,\(6\leq n\leq100\)),表示数据是一个\(1-n\)的重排数组(保证数据不自适应),

要求这个数组的两个中位数的下标。

你可以进行以下两个操作:

  • 询问(80次):\(?\space k\space x_1\space x_2\space ……\space x_k\),其中\(x\)数组不重复且在\([1,n]\),\(k\)为偶数且\(4\leq k\leq n\),将会返回这段区间的两个中位数的值
  • 输出:\(!\space i_1\space i_2\),表示答案的两个下标,顺序无所谓。

思路:

题目一看非常美妙有趣,这个询问次数很有说法

一般询问次数经常包含\(n,\log(n),\frac{n}{2}\)等等

注意到\(n\)只有\(100\),而询问有\(80\)次,\(log\)太小,\(n\)又太大,那大概率包含一个\(\frac{n}{2}\),那么还剩至少\(20\)次

注意到题目莫名其妙给个数据不自适应,这启发我们思考随机化。

接下来考虑具体做法:

首先注意中位数的性质:

若一串数的中位数为\(\frac{n}{2}\)和\(\frac{n}{2} + 1\),
那么\(1\) ~ \(\frac{n}{2}\)的个数和\(\frac{n}{2} + 1\) ~ \(n\)的个数显然恰好相等,
反之也成立。

如果我们找到一个\(1 \leq L \leq \frac{n}{2}\) 和一个 \(\frac{n}{2} + 1 \leq R \leq n\),

那么我们只需要枚举不重复的二元组,就能整出来最多4个数字,其中有2个是中位数,

直接暴力\(C_{4}^{2}\)枚举即可。

那么问题就转变为了求出符合条件的任意一组\((L, R)\),

暴力的话要进行最差\(n^2\)次的查询,显然不行

考虑随机化,注意到任意一组数它是符合条件的\((L, R)\)的概率接近于\(\frac{1}{2}\),那么我们只需要随机知道找到符合条件的\((L, R)\)即可,

这个过程的猜测次数超过\(20\)你就基本上\(100\)%猜出来了,总的猜测次数在合理范围内。

核心代码

int n;
pii query(int x, int y){
    cout << "? " << n - 2 << " ";
    rep(i, 1, n) if(i != x && i != y) cout << i << " ";
    cout << endl;
    int a, b; cin >> a >> b;
    cout << endl; 
    return {a, b};
}
pii query(vector<int> x){
    cout << "? ";
    cout << x.size() << " ";
    for(int i : x) cout << i << " ";
    cout << endl;
    int a, b; cin >> a >> b;
    cout << endl; 
    return {a, b};
}
void out(int x, int y){
    cout << "! " << x << " " << y << endl;
}
void solve(){
    cin >> n;
    int L = 1, R = 2; 
    bool f = 0;
    int a = n / 2, b = n / 2 + 1;
    while(1){
    	int i = rnd() % n + 1, j = rnd() % n + 1;
    	while(i == j) j = rnd() % n + 1;
    	pii ret = query(i, j);
    	if(ret.fir == a && ret.sec == b){
            L = i; R = j; f = 1;
        	break;
    	}
    }
    set<pii> s;
    rep(i, 1, n){
    	if(i == L || i == R) continue;
        int j = i; ++j;
        while(j == L || j == R) ++j;
        pii ret = query({L, R, i, j});
        if(ret.fir == a || ret.fir == b || ret.sec == a || ret.sec == b) s.insert({i, j});
        i = j;
    }
    if(s.size() == 1){
        pii x = (*s.begin());
        out(x.fir, x.sec);
    }else{
        vector<int> vc;
        for(pii x : s) vc.pb(x.fir), vc.pb(x.sec);
        rep(i, 0, 3){
            rep(j, i + 1, 3){
                pii ret = query({L, R, vc[i], vc[j]});
                if(ret.fir == a && ret.sec == b){
                    out(vc[i], vc[j]);
                    return ;
                }
            }
        }
    }
}
posted @ 2026-05-24 00:51  WangNoNo  阅读(33)  评论(0)    收藏  举报