[eJOI 2022] Where Is the Root?
不难得出所有叶子节点的 LCA 即为根节点,分两种情况讨论:
根节点为叶子节点:直接二分询问叶子节点集合,如果返回 YES 则根节点在集合中。
根节点不为叶子节点:二分询问非叶子节点集合时带上所有叶子节点即可。
但是如果我们先查询一遍所有叶子节点再二分碰见菊花图时会超过限制次数。
考虑合并上述过程,将节点按度数排序过后序列上二分即可,每次询问 mid 及左侧的所有节点。
为什么这样是正确的?
如果我们得到的返回值为 NO,那根肯定不在 mid 及左侧,继续二分右边即可。
如果我们得到的返回值为 YES,假设 mid 是叶子,我们此时就相当于在二分叶节点集合了,假设 mid 不是叶子,因为按度数排了序,所以已经带上了所有叶子,也是正确的。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define N 505
#define all(v) v.begin(),v.end()
#define assign(v,l,r) v.begin()+l,v.begin()+r
int n,l,r;
bool check(vector<int> v,string res=""){cout<<"? "<<v.size()<<" ";for(auto x:v) cout<<x<<" ";return cout<<"\n",cout.flush(),cin>>res,res=="YES";
}
int main(){ios::sync_with_stdio(false);cin.tie(nullptr),cout.tie(nullptr);cin>>n;vector<int> d(n+1,0),v(n,0);for(int i=1,x,y;i<n;i++) cin>>x>>y,d[x]++,d[y]++;iota(all(v),1),l=1,r=n;sort(all(v),[&](int a,int b){return d[a]<d[b];});while(l<r){int mid=(l+r)>>1,res=0;if(mid==1) res=check({v[0],v[2]});else res=check(vector<int>(assign(v,0,mid)));res?r=mid:l=mid+1;}return cout<<"! "<<v[l-1]<<"\n",0;
}