题解: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)\)

  1. \(t_1=t_2\) 时,可以确定 \(a=t_1\),递归处理 \(b,c,\min(b,c)\)

  2. \(t_1<t_2\) 时,可以确定 \(b=t_1\),递归处理 \(a,c,\min(a,c)\)

  3. \(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;
}
posted @ 2025-01-24 16:46  _Kenma  阅读(40)  评论(0)    收藏  举报