题目链接:https://uoj.ac/problem/751
有一棵 n n n个点的树,你每次可以选择一个边集,交互库会返回你所有联通块,要求这棵树。
n
≤
2000
n\leq 2000
n≤2000,操作次数不超过
14
14
14。
或
n
≤
131072
n\leq 131072
n≤131072,操作次数不超过
20
20
20。
一个神奇的做法,设操作次数为 T T T,我们发现有 C T T 2 ≥ n − 1 C_T^{\frac{T}{2}}\geq n-1 CT2T≥n−1,我们可以给所有边一个独特的编号 a i a_i ai满足其是一个 T T T位二进制数且其中恰好有 T 2 \frac{T}{2} 2T个 1 1 1。
这样对于一个叶子来说它就恰好在 T 2 \frac{T}{2} 2T次询问中是一个单独的连通块,然后我们每次暴力找出叶子删掉即可。
至于一个叶子的父节点是谁,我们在一个连通块删除的只剩下一个点 x x x时,我们考虑 x x x的儿子,那么在过这个连通块的节点肯定都在 x x x的子树中,此时取出还没有父节点的点,它们的父节点设为 x x x即可。
时间复杂度: O ( n log n ) O(n\log n) O(nlogn)
#include"tree.h"
#include
#include
#include
using namespace std;
const int N=1<<17,M=20;
int T,cnt,a[N],g[N],ans[N],fa[N];
int r[1<<M],c[M][N],col[M][N];
vector<vector<int> >ret[M];bool v[N];
vector<pair<int,int> > e;queue<int> qe;
vector<pair<int,int> > solve(int n){
T=(n<=2000)?14:20;int cnt=0,MS=1<<T;
for(int i=0;i<MS;i++)
if(__builtin_popcount(i)==T/2){
a[cnt]=i;r[i]=cnt++;
if(cnt==n-1)break;
}
vector<int> q;q.resize(n-1);
for(int i=0;i<T;i++){
for(int j=0;j<n-1;j++)q[j]=((a[j]>>i)&1);
ret[i]=query(q);
for(int j=0;j<ret[i].size();j++){
for(int k=0;k<ret[i][j].size();k++)
col[i][ret[i][j][k]]=j;
if(ret[i][j].size()==1)g[ret[i][j][0]]++,ans[ret[i][j][0]]|=(1<<i);
c[i][j]=ret[i][j].size();
}
}
for(int i=0;i<n;i++)
if(g[i]==T/2)qe.push(i),v[i]=1;
int x;
while(!qe.empty()){
x=qe.front();qe.pop();v[x]=1;
for(int i=0;i<T;i++){
int z=col[i][x];c[i][z]--;
if(c[i][z]==1){
int y=0,rt=-1;
for(int j=0;j<ret[i][z].size();j++){
y=ret[i][z][j];
if(!v[y]){
g[y]++;ans[y]|=(1<<i);
if(g[y]==T/2)qe.push(y);
rt=y;break;
}
}
if(rt==-1)continue;
for(int j=0;j<ret[i][z].size();j++){
if(ret[i][z][j]==y||fa[ret[i][z][j]])continue;
fa[ret[i][z][j]]=y+1;
}
}
}
}
MS--;e.resize(n-1);
for(int i=0;i<n;i++)
if(i!=x)
e[r[MS^ans[i]]]=make_pair(fa[i]-1,i);
return e;
}