【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;
}