题目描述
交互题,给定一棵包含 \(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;
}
注意:交互题要记得刷新缓冲区。
浙公网安备 33010602011771号