[ARC070D] HonestOrUnkind

一、题目

点此看题

二、解法

我竟然做得起交互题了,而且还是 ARC F 级别的交互题!

首先拿到这题真是一点思路都没有,那么我不妨把所有 \((x,y)\) 都询问出来,如果结果是 ? 就连一条 \(x\rightarrow y\) 的有向边。那么考虑诚实的人的导出子图一定是一个完全图,并且没有连向外界的边。

那么我们称这个导出子图中的点是互证清白的,我们还可以利用的信息是两个数量 \(a,b\),当 \(a\leq b\) 时,不友好的人为了不让我们猜中,可以模仿一个 \(a\) 阶互证清白子图,那么我们一定无法区分;当 \(a>b\) 时,可以通过数量确定唯一的互证清白子图。

这说明了一个关键结论:有解的充要条件是 \(a>b\)

但是真正构造答案肯定不能去找完全图,考虑把询问拆分成 \(n+n\) 的形式,即用 \(n\) 次询问去寻找诚实的人,用 \(n\) 次询问向这个诚实的人问出所有信息。

主要矛盾在第一步,但是由于只有恰好 \(n\) 次操作我们询问的方式是很有限的。这里不妨直接线性扫了,然后及时排除掉不友好的人。我们维护一个可能诚实人的栈,每次对于一个新的人,用栈顶询问它的信息。

如果结果是 N,说明这两者之中必有一个不友好的人,把这两个人都排除掉,由于 \(a>b\) 最后一定会剩下诚实的人。然后考虑最后得到的栈中,不友好的人一定是栈的一个前缀,否则一定会被排除掉,那么取栈顶就是诚实的人。

时间复杂度 \(O(n)\),询问次数 \(\leq 2n\)

三、总结

当交互题没有任何思路时,不妨假设自己知道所有信息,然后看能用出什么效果。

用到的小技巧:建立图论模型思考问题、对询问次数进行合理的拆分、根据次数反推做法、确定关键点从而确定全局。

#include <cstdio>
const int M = 2005;
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,a,b,z[M],ans[M];char s[M];
void ask(int x,int y)
{
	printf("? %d %d\n",x,y);
	fflush(stdout);scanf("%s",s);
}
signed main()
{
	a=read();b=read();n=a+b;
	if(a<=b) {puts("Impossible");return 0;}
	for(int i=0;i<n;i++)
	{
		if(!m) {z[++m]=i;continue;}
		ask(z[m],i);
		if(s[0]=='N') m--;
		else z[++m]=i;
	}
	int x=z[m];
	for(int i=0;i<n;i++)
		ask(x,i),ans[i]=s[0]=='Y';
	printf("! ");
	for(int i=0;i<n;i++) printf("%d",ans[i]);
	return 0;
}
posted @ 2022-06-08 10:40  C202044zxy  阅读(74)  评论(0编辑  收藏  举报