题目传送门

题目描述

交互题,给定一棵包含 \(n\) 个节点和 \(n-1\) 条边的树,我们定义 \(Dist ( u , v )\)\(u \neq v\) )为从节点 \(u\) 到节点 \(v\) 的路径上所有边权的最大公约数。现在你至多可以询问交互库 \(12\) 次,每次可询问一个点集,而交互库会回答这个点集中任意两个点的 \(Dist\) 最大值。请通过询问求出最大的 \(Dist\) 所对应的两个路径端点。

思路

乍一看这题什么鬼啊,还要求 \(\gcd\)。但由于往一个集合中不断加入点,它们的 \(\gcd\) 一定是单调不升的。所以我们很容易发现,不管是交互库给出的答案,还是我们要求的最大 \(Dist\),一定是原树上的一条边。现在我们只需要一个包含所有点的序列,并且相邻两条边的端点在序列里一定相邻。那这个东西是什么呢?答案是欧拉序!

依旧是OI Wiki的解释。
欧拉序:对一棵树进行 DFS,无论是第一次访问还是回溯,每次到达一个结点时都将编号记录下来,可以得到一个长度为 2n-1 的序列,这个序列被称作这棵树的欧拉序列。

不难发现,欧拉序中相邻两个点一定有一条直接边相连。
那我们的答案就呼之欲出了!我们要在欧拉序上二分!

具体写法

首先,我们需要“浪费”一次询问以便确定最大 \(Dist\) 的值。
接着,我们进行二分。每次询问序列左边的点集,如果等于最大 \(Dist\),那么说明这条边的对应端点在左边;否则就在右边。套一个板子上去二分就可。

Code

#include<bits/stdc++.h>
using namespace std;
vector<int> g[1005];
int tim=0,ou[2005],d[1005],flag[1005],n;
void dfs(int u,int father){
	ou[++tim]=u;
	for(auto v:g[u])
		if(v!=father){
			dfs(v,u);
			ou[++tim]=u;
		}
	return;
}
int check(int l,int r){
	int in,len=0;
	for(int i=1;i<=n;i++) flag[i]=0;
	for(int i=l;i<=r;i++)
		if(flag[ou[i]]==0) flag[ou[i]]=1,d[++len]=ou[i];
	printf("? %d",len);
	for(int i=1;i<=len;i++) printf(" %d",d[i]);
	printf("\n");
	fflush(stdout);
	scanf("%d",&in);
	return in;
}
int main(){
	int sin;
	scanf("%d",&n);
	for(int i=1;i<n;i++){
		int u,v;
		scanf("%d%d",&u,&v);
		g[u].push_back(v),g[v].push_back(u);
	}
	dfs(1,0);
	int l=1,r=tim;
	sin=check(l,r);
	while(true){
		if(l+1==r) break;
		int mid=(l+r)/2;
		if(check(l,mid)==sin) r=mid;
		else l=mid;
	}
	printf("! %d %d\n",ou[l],ou[r]);
	return 0;
}

注意:交互题要记得刷新缓冲区。