题解:P11490 [BalticOI 2023] Staring Contest
前言
第一次做无题解的灰题,有点激动。
思路分析
首先从小数据开始思考。
当 \(n=2\) 时,可以询问 \(t_1=(1,2)\),然后返回 \(b_1=b_2=t_1\)。
当 \(n=3\) 时,可以询问 \(t_1=(1,2),t_2=(2,3)\),然后分讨一下:
-
当 \(t_1=t_2\) 时,可以确定 \(a_2=t_1\);
-
当 \(t_1<t_2\) 时,可以确定 \(a_1=t_1\);
-
当 \(t_1>t_2\) 时,可以确定 \(a_3=t_2\)。
不难发现,\(n=3\) 的做法具有很强的拓展性。
具体而言,可以每次维护一个待确定集合,然后用两次询问得到一个值,直到未确定的值的数量为 \(2\) 为止。总询问次数为 \(2n-1\)。实现这个算法有 \(20\) 分。
在上面的算法的基础上,我们可以进行优化,具体而言,我们很多次询问是冗余的,考虑每次处理 \(a_1,a_2,a_3\) 三个数时,询问 \(t_1=(1,2),t_2=(2,3),t_3=(1,3)\),通过三次询问可以确定两个数,总询问次数小于 \(\lfloor \frac{3n}{2} \rfloor\)。实现这个算法有 \(50\) 分。
其实上面的算法已经很接近正解了。
考虑进一步优化。注意到每次询问 \(t_1,t_2\) 时,得到的答案只有为 \(t_1=t_2\),需要再次询问,否则可以利用之前的询问继续递归处理。
也就是,每次已知 \(a,b,\min(a,b)\),希望利用一次询问确定一个值 \(c\),分类讨论:
下文为了方便,考虑令 \(t_1=\min(a,b),t_2=\min(b,c)\)。
-
当 \(t_1=t_2\) 时,可以确定 \(a=t_1\),递归处理 \(b,c,\min(b,c)\);
-
当 \(t_1<t_2\) 时,可以确定 \(b=t_1\),递归处理 \(a,c,\min(a,c)\);
-
当 \(t_1>t_2\) 时,可以确定 \(c=t_2\),选择一个新的 \(c\) 处理。
不难发现,1 和 3 操作都是一次询问确定一个值,而 2 操作,本质上不会劣于我们之前所说的询问次数为 \(\lfloor \frac{3n}{2} \rfloor\) 的算法。
所以总体的询问次数是玄学,提交发现被卡了。
但是考虑每次随机选择 \(c\),就过了。
不会证明询问次数的正确性,不知道有没有期望复杂度保证。
代码实现
#include<bits/stdc++.h>
using namespace std;
const int inf=-(1ll<<31);
int n,a,b,c,t1,t2,t3,ans[3005],id[3005],val[3005];
queue<int> v;
mt19937 rnd(time(0));
int ask(int x,int y){
int ans;
cout<<"? "<<id[x]<<' '<<id[y]<<endl;
cin>>ans;
return ans;
}
void solve(int a,int b,int t1){
while(!v.empty()){
c=v.front();
v.pop();
t2=ask(a,c);
if(t1==t2) return ans[id[a]]=t1,solve(b,c,ask(b,c));
else if(t2>t1) return ans[id[b]]=t1,solve(a,c,t2);
else ans[id[c]]=t2;
}
ans[id[a]]=ans[id[b]]=t1;
}
int main(){
cin>>n;
for(int i=1;i<=n;i++){
id[i]=i;
}
shuffle(id+1,id+1+n,rnd);
for(int i=3;i<=n;i++){
v.push(i);
}
solve(1,2,ask(1,2));
cout<<"! ";
for(int i=1;i<=n;i++){
cout<<ans[i]<<' ';
}
cout<<endl;
return 0;
}

浙公网安备 33010602011771号