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;
}
posted @ 2022-08-12 13:37  QuantAsk  阅读(46)  评论(0编辑  收藏  举报