人,只有自己站起来,这个世界才能属于他。|

园龄:粉丝:关注:

题解:P11490 [BalticOI 2023] Staring Contest

前言

第一次做无题解的灰题,有点激动。

思路分析

首先从小数据开始思考。

n=2 时,可以询问 t1=(1,2),然后返回 b1=b2=t1

n=3 时,可以询问 t1=(1,2),t2=(2,3),然后分讨一下:

  • t1=t2 时,可以确定 a2=t1

  • t1<t2 时,可以确定 a1=t1

  • t1>t2 时,可以确定 a3=t2

不难发现,n=3 的做法具有很强的拓展性。

具体而言,可以每次维护一个待确定集合,然后用两次询问得到一个值,直到未确定的值的数量为 2 为止。总询问次数为 2n1。实现这个算法有 20 分。

在上面的算法的基础上,我们可以进行优化,具体而言,我们很多次询问是冗余的,考虑每次处理 a1,a2,a3 三个数时,询问 t1=(1,2),t2=(2,3),t3=(1,3),通过三次询问可以确定两个数,总询问次数小于 3n2。实现这个算法有 50 分。

其实上面的算法已经很接近正解了。

考虑进一步优化。注意到每次询问 t1,t2 时,得到的答案只有为 t1=t2,需要再次询问,否则可以利用之前的询问继续递归处理。

也就是,每次已知 a,b,min(a,b),希望利用一次询问确定一个值 c,分类讨论:

下文为了方便,考虑令 t1=min(a,b),t2=min(b,c)

  1. t1=t2 时,可以确定 a=t1,递归处理 b,c,min(b,c)

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

  3. t1>t2 时,可以确定 c=t2,选择一个新的 c 处理。

不难发现,1 和 3 操作都是一次询问确定一个值,而 2 操作,本质上不会劣于我们之前所说的询问次数为 3n2 的算法。

所以总体的询问次数是玄学,提交发现被卡了。

但是考虑每次随机选择 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;
}

本文作者:Kenma

本文链接:https://www.cnblogs.com/Kenma/p/18689778

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   _Kenma  阅读(10)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起