给定带边权的无根树,定义dis(u,v)
等于u到v简单路径上所有边的GCD,每次可以询问一个点集,会返回点集中所有点对的dis最大值,最后输出这棵树中,使得dis(u,v)
最大的u,v
。
(2<=n<=1e3 ,最多询问12次)
- 首先GCD具有只减不增的性质,最后答案就等价于输出边权最大的边的两个顶点。每次询问也是返回的最大边权。
- 看到询问次数,12,点只有1000个,说明主体算法肯定是二分了。
- 我们首先可以询问所有的点,返回的一定是最大边权W。
- 我们想到,能不能把所有的边分成两部分,左边右边各询问一次,看看最大边权W在哪边,这两部分相当于把树分成了两个连通块,所有的边仍然是有序的。
- 我们可以用边的dfs序,在这个序列上,边始终是按访问顺序排列,可以进行二分。每次二分相当于把1个连通块分成了两个。
#include<bits/stdc++.h>
using namespace std;
//#pragma GCC optimize(2)
#define ull unsigned long long
#define ll long long
#define pii pair<int, int>
const int maxn = 1e3 + 10;
const ll mod = 998244353;
const ll inf = (ll)4e16+5;
const int INF = 1e9 + 7;
const double pi = acos(-1.0);
ll inv(ll b){
if(b==1)return 1;return(mod-mod/b)*inv(mod%b)%mod;}
inline ll read()
{
ll x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){
x=x*10+ch-'0';ch=getchar();}
return x*f;
}
//给定带点权的树
int a[maxn];
int clk,n;
vector<int> g[maxn];
pii edg[maxn];//对边求dfs序 然后二分
void dfs(int rt,int fa)
{
for(int i:g[rt])
{
if(i==fa) continue;
edg[++clk]={
rt,i};//边的dfs序
dfs(i,rt);
}
}
inline int ask(int l,int r)//[l,r]这些边当中的最大边权
{
set<int> p;
for(int i=l;i<=r;i++)
{
p.insert(edg[i].first);
p.insert(edg[i].second);
}
printf("? %d",p.size());
for(int i:p) printf(" %d",i);
puts("");
fflush(stdout);
int ret;
scanf("%d",&ret);
return ret;
}
int main()
{
scanf("%d",&n);
for(int i=1,u,v;i<n;i++)
{
scanf("%d %d",&u,&v);
g[u].push_back(v);
g[v].push_back(u);
}
dfs(1,0);
int l=1,r=n-1;
int target=ask(l,r);//最大边权
int ans;
while(l <= r)
{
int mid=l+r>>1;
if(ask(l,mid) == target)
{
ans=mid;
r=mid-1;
}
else l=mid+1;
}
printf("! %d %d\n",edg[ans].first,edg[ans].second);
fflush(stdout);
return 0;
}