题目
T(T<=10)组样例,每次给定一棵n(2<=n<=1e3)节点的树,
并预先选好树上两个不同的点u和v,每次你可以给出一个点集S,
以S的大小和S内的点的方式输入,输出会返回一个dis(i,u)+dis(i,v)最小的点i,并返回dis(i,u)+dis(i,v)的值
最多11次询问,要求最终猜出最后的不同的两个点u和v
思路来源
官方题解
题解
交互,最烦人的地方,就是没办法debug,
自己造的样例怎么试怎么过,然而就怎么交怎么wa
首先,如果可以询问12次,这是很好想的,也足以通过EasyVersion
先1次全局询问,就能询问到u到v路径上的点w和距离disw,
以w为树根dfs一次,w就是u和v的lca,考虑到w树下的深度只有[1,1000],
二分深度去找u、v中的远端点,不妨记为u,
每次判断该深度是否存在点x满足dis(x,u)+dis(x,v)==disw,
即把这个深度x内的所有点都拿出来询问一遍,
如果有一个点在u到v的路径上,说明x<=dep[u],此时距离最小的点的距离是disw
存在就说明还可以往更远的深度探索,
找到最远的深度,也就找到了u,需要10次询问,
再对u重新dfs一次,再问1次距离为dis的点即得v,共12次
考虑怎么优化成11次,就是一个小trick,
在对w进行dfs时,发现远端点u的距离一定,
考虑将二分界限设置到即可,
由于最坏情况是,缩小了一半范围,故减少了一次二分
代码
#include<bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define pb push_back
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
#define sci(a) scanf("%d",&(a))
const int N=1e3+10;
typedef pair<int,int> P;
vector<int>e[N],dep[N],now;
char s[20];
int t,n,u,v,dis,mx;
P tmp,las;
P ask(vector<int> &x){
printf("? %d",(int)x.size());
for(int v:x){
printf(" %d",v);
}
printf("\n");
fflush(stdout);
P ans;
scanf("%d%d",&ans.fi,&ans.se);
return ans;
}
void dfs(int u,int fa,int y){
dep[y].pb(u);
mx=max(mx,y);
for(int v:e[u]){
if(v==fa)continue;
dfs(v,u,y+1);
}
}
int main(){
sci(t);
while(t--){
sci(n);
now.clear();
rep(i,0,n){
dep[i].clear();
e[i].clear();
if(i)now.pb(i);
}
rep(i,2,n){
sci(u),sci(v);
e[u].pb(v);
e[v].pb(u);
}
tmp=ask(now);
u=tmp.fi,dis=tmp.se;
mx=0;
dfs(u,-1,0);
int l=(dis+1)/2,r=min(dis,mx);//dis/2向上取整 绝了 减少了一次二分 找远端节点
//printf("u:%d dis:%d l:%d r:%d\n",u,dis,l,r);
while(l<=r){//找到最大的深度满足==dis的节点
int mid=(l+r)/2;
tmp=ask(dep[mid]);
if(tmp.se==dis)l=mid+1,u=tmp.fi;
else r=mid-1;
}
rep(i,0,n){
dep[i].clear();
}
dfs(u,-1,0);
tmp=ask(dep[dis]);
v=tmp.fi;
printf("! %d %d\n",u,v);
fflush(stdout);
scanf("%s",s);
if(strcmp(s,"Correct")==0){
continue;
}
}
return 0;
}
/*
8
8
1 2
1 3
1 5
2 4
2 8
3 6
5 7
*/