【UNR #6】神隐

【UNR #6】神隐

by AmanoKumiko

Description

这是一道交互题

有一个\(n\)个点的树

你可以进行规定次数的询问,每次选上一些边

交互库会将选中边形成的若干联通块返回给你(只知道每个联通块有哪些点)

你需要回答每条边是什么

Data Constraint

设询问次数上限为\(T\)

\(1\le n\le 2000\),则\(T=14\)

\(2000<n\le 131072\),则\(T=20\)

Solution

首先这个次数很紧,是\(O(1)+\log_2n\)级别的

所以要往二进制方向想

然后对于任意两条相邻的边

必须满足存在询问第一条出现,第二条不出现

同时满足存在询问第二条出现,第一条不出现

否则无法确定其形态

然后我们把所有\([0,2^T-1]\)\(popcount\)\(\frac{T}{2}\)的数找出来,给每条边依次重标号

(这是怎么想到的!!!

可以证明满足上面的条件

接着思考怎么求出每条边

考虑一个这样的过程:

不断找出当前所有的叶子并删除

在本题,若一个点是叶子,则其会在恰好\(\frac{T}{2}\)次询问中单独一个点是一个联通块

在不断删除的过程中,若当前这个联通块只有一个点了

那么它一定就是深度最小的那个

这时给所有还没有父亲的节点连向它就可以了

Code

#include<bits/stdc++.h>
#include"tree.h"
using namespace std;
#define F(i,a,b) for(int i=a;i<=b;i++)
#define Fd(i,a,b) for(int i=a;i>=b;i--)
#define N 200010
#define M 21

bool vis[N];
int num[N],be[N][M],sz[M][N],s[N],fa[N];
vector<vector<int>>q[M];

int count(int x){return x?(x&1)+count(x>>1):0;}

vector<pair<int,int>>solve(int n){
	int T=(n<=2000?14:20);
	vector<int>a;
	F(i,0,(1<<T)-1)if(count(i)==T/2)a.push_back(i);
	int t=0;
	F(i,0,n-2)num[i]=a[t],t=(t+1)%a.size();
	F(i,0,T-1){
		vector<int>ask;
		ask.resize(n-1);
		F(j,0,n-2)ask[j]=((1<<i)&num[j])?1:0;
		q[i]=query(ask);
		F(j,0,q[i].size()-1){
			sz[i][j]=q[i][j].size();
			for(auto d:q[i][j]){
				be[d][i]=j;
				if(q[i][j].size()==1)s[d]++;
			}
		}
	}
	queue<int>leaf;
	vector<pair<int,int>>res;
	memset(fa,-1,sizeof(fa));
	F(i,0,n-1)if(s[i]==T/2)leaf.push(i);
	while(!leaf.empty()){
		int u=leaf.front();leaf.pop();
		F(i,0,T-1){
			if(sz[i][be[u][i]]==1){
				for(auto v:q[i][be[u][i]])if(v!=u){
					if(!vis[v])vis[v]=1,fa[v]=u;
				}
			}
			sz[i][be[u][i]]--;
			if(sz[i][be[u][i]]==1){
				for(auto v:q[i][be[u][i]])if(v!=u){
					s[v]++;
 					if(s[v]==T/2)leaf.push(v);
				}
			}
		}
	}
	F(i,0,n-1)if(fa[i]!=-1)res.push_back(make_pair(i,fa[i]));
	return res;
}
posted @ 2022-08-08 15:54  冰雾  阅读(76)  评论(0编辑  收藏  举报