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