CF1592D Hemose in ICPC ? 题解

题目传送门

题目大意

这是一道交互题。给定一棵树,有边权。已知这棵树的形态但不知道边权。现在设 \(\operatorname{Dist}\left(u,v\right)\) 为点 \(u\)\(v\) 的简单路径上所有边权的最大公约数。你现在可以给出一个点集,你可以得到 \(\operatorname{Dist}\left(u,v\right)_{max}\) 当然 \(u,v\) 是这个点集里面的点。你要求棵树中边权最大的边的两个端点,最多询问 \(12\) 次,树的节点数 \(n \le10^3\)

题目解析

显然我们最多询问 \(\log n\) 次,不难想到是二分。
难点在于用什么二分和怎么写 check 函数。
check 函数其实很好写。我们发现如果给出的点任意两两之间都能通过经过点集之内的点互相到达,那么输出的就是这些点之间的最大边权。然后我们也可以通过输出所有的点来得到树上最大的边的边权是多少,这样就可以检测这些点之中是否有最大边了。
因此我们要构造一个恰当的序列再二分才可以,不难发现在这棵树上的欧拉序上二分即可,这样需要询问 \(\log n +2\) 次 。
代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#define I inline
#define db double
#define U unsigned
#define R register
#define ll long long
#define RI register int
#define ull unsigned long long
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
#define Me(a,b) memset(a,b,sizeof(a))
#define EPS (1e-7)
#define INF (0x7fffffff)
#define LL_INF (0x7fffffffffffffff)
#define maxn 1039
//#define debug
using namespace std;
#define Type int
I Type read(){
	Type sum=0; int flag=0; char c=getchar();
	while((c<'0'||c>'9')&&c!='-') c=getchar(); if(c=='-') c=getchar(),flag=1;
	while('0'<=c&&c<='9'){ sum=(sum<<1)+(sum<<3)+(c^48); c=getchar(); }
	if(flag) return -sum; return sum;
}
int n,u,v,maxx;
int head[maxn],nex[maxn<<1],to[maxn<<1],kkk;
#define add(x,y) nex[++kkk]=head[x]; head[x]=kkk; to[kkk]=y;
int a[maxn<<1],len;
void geta(int x,int pre){
	a[++len]=x;
	for(RI i=head[x];i;i=nex[i]) if(to[i]!=pre){
		geta(to[i],x); a[++len]=x;
	} return;
}
int t[maxn],num,l,r,mid;
int check(int x){
	Me(t,0); num=0; RI i; for(i=l;i<=mid;i++) t[a[i]]++;
	for(i=1;i<=n;i++) if(t[i]) num++; cout.flush(); cout<<"? "<<num<<' ';
	for(i=1;i<=n;i++) if(t[i]) cout<<i<<' ';
	cout<<endl; cin>>num; return num==maxx;
}
int main(){
    //freopen(".in","r",stdin);
    //freopen(".out","w",stdout);
	n=read(); RI i; for(i=1;i<n;i++){ cin>>u>>v; add(u,v) add(v,u) }
	cout.flush(); cout<<"? "<<n<<' '; for(i=1;i<=n;i++) cout<<i<<' ';
	cout<<endl; cin>>maxx; geta(1,-1); //for(i=1;i<=len;i++) printf("%d ",a[i]);
	l=1; r=len; while(l+1<r){ mid=(l+r)>>1; if(check(mid)) r=mid; else l=mid; }
	cout.flush(); cout<<"! "<<min(a[l],a[r])<<' '<<max(a[l],a[r])<<endl;
	return 0;
}

posted @ 2021-10-06 18:27  jiangtaizhe001  阅读(62)  评论(0编辑  收藏  举报