CF2032D 题解

CF2032D 题解

题意

有一张特殊结构的树图。

通过交互来确定每个节点的父节点,即这棵树的结构。

具体还是直接上CF看吧,在题解里面太详尽地描述有点浪费时间了。

分析

可以发现 \(1\) 这个节点是比较特殊的一个节点,并且题目保证 \(1\) 一定有一个儿子。

可以从 \(1\) 入手,发现可以通过先确定 \(1\) 这条链上面的所有节点,然后从小到大枚举所有这条链之外的节点,通过询问他们是否与最小的不在链上的,且已经确定了父节点的点之间的路径,可以确定当前的节点是否是询问的节点的儿子,如果不是,那么之后一一定不会再有节点是被询问节点的儿子了,这一点可以通过反证法证明。

但是发现这种写法好像实在无法满足 \(2n-6\) 次询问的严苛限制,因为确定 \(1\) 所在的链就要花上 \(n-2\) 的代价。

于是瞟了一眼题解。。。

但是只看了 Hint ,就突然醒悟了。

可以发现第一层节点的编号一定是连续且递增的,这个结论同样也可以通过反证法证明。

那么我们可以用很少的代价先确定第一层所有的节点。

之后直接从小到大询问,发现如果询问失败,那么这个节点实际上可以直接丢掉了。。。

Solution 1

那么就可以用一个 set 来维护询问的过程,没用的节点就直接删掉就可以了。

还有一个优化,就是当 set 里面只有一个节点的时候,这个唯一的节点一定是当前所需要确定的节点的父亲。

否则它的父亲只能是 \(0\) ,但这显然是和我们推出来的关于第一层的结论是矛盾的。

这是我差不多自己想出来的玄学做法。

Solution 2

其实还会发现一个性质,就是当前需要确定的节点一旦确定下来,那么它也一定是被插到了 set 的最尾部,所以大可不必用 set 来维护,

在序列上跑一个双指针就可以了。

即:\(L\) 代表现在所询问到的节点,\(R\) 代表现在需要确定的节点。

这个是std的做法,好聪明。

Code

1

#include<bits/stdc++.h>
#define int long long
using namespace std;
int res;
inline int ask(int x,int y)
{
	cout<<"? "<<x<<' '<<y<<endl;
	fflush(stdout);
	cin>>res;
	return res;
}
int n,T; 
signed main()
{
	cin>>T;
	while(T--)
	{
		cin>>n;
		int res;
		vector<int> fa(n+1,0); 
		set<int> node,del;
		int now=2;
		for(;now<=n-1;++now)
		{
			if(!ask(1,now))break;
			node.insert(now),fa[now]=0;
		}
		fa[now]=1,node.insert(now);
		now++; 
		for(;now<=n-1;++now)
		{
			for(auto x:node)
			{
				if(node.size()==1)
				{
					fa[now]=x;
					del.insert(x);
					node.insert(now);
					break;
				}
				else
				{
					int ans=ask(now,x);
					if(ans==0)
					{
						fa[now]=x;
						del.insert(x);
						node.insert(now);
						break;
					}
					else
						del.insert(x); 
				}
			}
			for(auto tmp:del)node.erase(tmp);
			del.clear();
		}
		cout<<"!";
		for(int i=1;i<=n-1;++i)cout<<" "<<fa[i];
		cout<<endl;
	}
	return 0;
}

2

#include<bits/stdc++.h>
using namespace std;
int T,n;
int main()
{
	cin>>T;
	auto query=[&](int x,int y)
	{
		int ans;
		cout<<"? "<<x<<' '<<y<<endl; 
		cin>>ans;
		return ans;
	};
	while(T--)
	{
		cin>>n;
		vector<int> fa(n+1,0);
		int l=1,r=2;
		while(query(l,r)) r++;
		fa[r]=l,l++,r++;
		while(r<=n-1)
		{
			while(query(l,r))l++;
			fa[r]=l,l++,r++;
		}
		cout<<"!";
		for(int i=1;i<n;++i)cout<<' '<<fa[i];
		cout<<endl;
	}
	return 0;
}

Tip

有一个值得注意的小地方是,交互题直接用endl可以在换行的同时起到刷新缓冲区的效果。

posted @ 2024-11-05 19:23  Hanggoash  阅读(4)  评论(0编辑  收藏  举报
动态线条
动态线条end