uoj#751-[UNR #6]神隐【交互】
正题
题目链接:https://uoj.ac/problem/751
题目大意
有一棵\(n\)个点的树,你每次可以选择一个边集,交互库会返回你所有联通块,要求这棵树。
\(n\leq 2000\),操作次数不超过\(14\)。
或
\(n\leq 131072\),操作次数不超过\(20\)。
解题思路
一个神奇的做法,设操作次数为\(T\),我们发现有\(C_T^{\frac{T}{2}}\geq n-1\),我们可以给所有边一个独特的编号\(a_i\)满足其是一个\(T\)位二进制数且其中恰好有\(\frac{T}{2}\)个\(1\)。
这样对于一个叶子来说它就恰好在\(\frac{T}{2}\)次询问中是一个单独的连通块,然后我们每次暴力找出叶子删掉即可。
至于一个叶子的父节点是谁,我们在一个连通块删除的只剩下一个点\(x\)时,我们考虑\(x\)的儿子,那么在过这个连通块的节点肯定都在\(x\)的子树中,此时取出还没有父节点的点,它们的父节点设为\(x\)即可。
时间复杂度:\(O(n\log n)\)
code
#include"tree.h"
#include<algorithm>
#include<vector>
#include<queue>
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;
}