【UNR #6 E】神隐(交互)(二进制分组)
神隐
题目链接:UNR #6 E
题目大意
有一棵树,你每次询问可以给边(你不知道对于的两个点)选 0 或 1,然后它会把 0 的边删掉,把得到的每个连通块给你。
要你在不超过 logn+2 次得到每条边。
思路
考虑一个叫做二进制分组的东西,就是我们把读入的边自己编号。
然后每次问第 \(i\) 位是 \(0\) 和是 \(1\) 的分别问一次(就是把这些边标 \(1\) 其它标 \(0\))
那如果两个点恰好在其中的 \(\left\lceil\log_2 n\right\rceil\) 个连通块中,那就是树上的一条边。
然后这样是 \(2\log\) 级别的,考虑优化。
考虑如何才能区分两条边,那就是存在一次询问 A 在 B 不在,一次 B 在 A 不在。
然后我们考虑这么一个方法,就是我们把 \(0\sim 2^T\)(\(T\) 是询问次数,下同)里面 \(1\) 的个数为 \(T/2\) 的给找出来。
(其实你找任意个数都可以,但是我们要最大化,避免不能对应所有的边)
然后这个大小是 \(\binom{T}{T/2}\),然后我们会发现刚好大于 \(n-1\)。
那我们给每条边编一个上面的数的号,然后就也是二进制分组,但是每次只问 \(1\)。
然后由于你限制了 \(1\) 的个数,而且这些数的编号都是不同的,所以一定会有不同的地方,再加上 \(1\) 个数一样,所以一定是各自只要有一个位置自己是 \(1\) 别人是 \(0\)。
考虑怎么确定边。
发现如果一个点是叶子点,那它一定会在恰好 \(T/2\) 个询问中为单独一个点一个连通块。
然后考虑删掉这个叶子会怎样,那就把每个它所在的连通块大小减一,那如果减到了 \(1\),我们就可以找到新的叶子节点(其实就是它的父亲)。
所以如果我们要找边,我们删一个点之间,就把那个点集枚举,找到不是它,而且没有确定父亲的点给找出来,那它们的父亲就是你删的点。
代码
#include"tree.h"
#include<iostream>
#include<cassert>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
const int N = 131072 + 100;
vector <vector <int> > q[21];
int a[N], sz[21][N], pla[21][N];
queue <int> Q;
int num[N], fa[N];
bool in[N];
vector<pair<int, int> > solve(int n) {
vector <int> xl; int T = (n <= 2000) ? 14 : 20;
for (int i = 0; i < 1 << T; i++) {
int num = 0; for (int j = 0; j < T; j++) if ((i >> j) & 1) num++;
if (num == T / 2) xl.push_back(i);
}
for (int i = 0, now = 0; i < n - 1; i++)
a[i] = xl[now], now = (now + 1) % xl.size();
for (int i = 0; i < T; i++) {
vector <int> as;
for (int j = 0; j < n - 1; j++)
if ((a[j] >> i) & 1) as.push_back(1);
else as.push_back(0);
q[i] = query(as);
for (int j = 0; j < q[i].size(); j++) {
sz[i][j] = q[i][j].size();
for (int k = 0; k < sz[i][j]; k++) pla[i][q[i][j][k]] = j;
if (sz[i][j] == 1) num[q[i][j][0]]++;
}
}
for (int i = 0; i < n; i++) {
fa[i] = -1;
if (num[i] == T / 2) Q.push(i);
}
while (!Q.empty()) {
int now = Q.front(); Q.pop();
for (int i = 0; i < T; i++) { int x = pla[i][now];
if (sz[i][x] == 1) {
for (int j = 0; j < q[i][x].size(); j++) {
if (q[i][x][j] == now) continue;
if (in[q[i][x][j]]) continue;
in[q[i][x][j]] = 1; fa[q[i][x][j]] = now;
}
}
sz[i][x]--;
if (sz[i][x] == 1) {
for (int j = 0; j < q[i][x].size(); j++) {
if (q[i][x][j] == now) continue;
num[q[i][x][j]]++;
if (num[q[i][x][j]] == T / 2) Q.push(q[i][x][j]);
}
}
}
}
vector <pair<int, int> > re;
for (int i = 0; i < n; i++) if (fa[i] != -1) re.push_back(make_pair(i, fa[i]));
return re;
}