题解 [UNR #6] 神隐

传送门

以前只知道有这么个套路但不理解为什么要这么做,一直以为是个人类智慧构造
更详细的解释
考虑我们要还原的信息是树的形态
考虑对于任意两条相邻的边 \(e_1=(u, v), e_2=(u, w)\),能得到具体连边方式的条件是:

  1. 存在一组询问使 \(e_1\in E, e_2\notin E\)
  2. 存在一组询问使 \(e_2\in E, e_1\notin E\)

少一个则无法确定第三个点与确定的两个点之间的连边关系
而由于我们不知道边之间的相邻关系,所以需要得到所有 \(O(n^2)\) 组边对之间的这个信息
考虑一个构造:为每条边赋一个恰有 10 个为 1 位的 20 位二进制权值
进行 20 次询问,每次询问时询问所有权值的这一位为 1 的边
发现一定有且仅有相邻的点对有 10 次出现在同一连通块里
于是有了一个 \(O(n^2\log n)\) 的做法

然后优化上面的做法:
不能枚举点对的话,需要换一种方法找点之间的位置关系
考虑还原树形态的一种常用方法:剥叶子
叶子是在恰好 10 次询问中处于孤立连通块中的点
递归这一过程可以得到原树的拓扑序

为什么不能直接得到连边关系?
又发现对于任意一个叶子,在 10 次非孤立询问中均与之连通的点有且仅有一个,即为其父亲 然后问题转为了对树上联通块快速取交 * 关于树上联通块快速取交: 结论是交的顶部(深度最小的点)一定是其中一个集合的顶部,如果交唯一,一定是这些顶部深度最大的那个点 证明是容易的。

对于本题,每个点的具体深度并不可知,但知道所有有祖先关系的点间的相对深度,足以完成上述过程
复杂度 \(O(n\log^2 n)\),因为我用的 set

点击查看代码
#include "tree.h"
#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define N 200010
#define fir first
#define sec second
#define pb push_back

queue<int> q[2];
int cnt[2010][2010];
vector<int> to[N], mask;
vector<set<int>> gra[25];
vector<vector<int>> com[25];
vector<pair<int, int>> top[25], ans;
vector<vector<int>> query(vector<int> vec);
int tim[N], bel[N][21], flr[N], dep[N], now, rot;

vector<pair<int, int>> force_solve(int n) {
	int lim=1<<14;
	for (int s=0; s<lim; ++s) if (__builtin_popcount(s)==7) mask.pb(s);
	for (int i=0; i<14; ++i) {
		vector<int> tem;
		for (int j=0; j<n-1; ++j) tem.pb(mask[j]&(1<<i)?1:0);
		com[i]=query(tem);
	}
	for (int i=0; i<14; ++i)
		for (auto& it:com[i])
			for (auto& s:it)
				for (auto& t:it)
					++cnt[s][t];
	for (int i=0; i<n; ++i)
		for (int j=i+1; j<n; ++j)
			if (cnt[i][j]==7)
				ans.pb({i, j});
	return ans;
}

vector<pair<int, int>> solve(int n) {
	if (n<=2000) return force_solve(n);
	int lim=1<<20;
	for (int s=0; s<lim; ++s) if (__builtin_popcount(s)==10) mask.pb(s);
	for (int i=0; i<20; ++i) {
		vector<int> tem;
		for (int j=0; j<n-1; ++j) tem.pb(mask[j]&(1<<i)?1:0);
		com[i]=query(tem);
		for (int j=0; j<com[i].size(); ++j) {
			set<int> s;
			for (auto& t:com[i][j]) s.insert(t), bel[t][i]=j;
			gra[i].pb(s);
		}
	}
	for (int i=0; i<20; ++i)	
		for (auto& it:com[i]) if (it.size()==1)
			if (++tim[it[0]]==10)
				q[now].push(it[0]);
	int cnt;
	for (lim=1,cnt=0; q[now].size(); ++lim,now^=1) {
		// cout<<"lim: "<<lim<<endl;
		if (cnt==n-1 && q[now].size()==1) {flr[q[now].front()]=lim; break;}
		if (cnt==n-2 && q[now].size()==2) {
			flr[q[now].front()]=lim; q[now].pop();
			flr[q[now].front()]=++lim; q[now].pop();
			break;
		}
		while (q[now].size()) {
			int u=q[now].front(); q[now].pop();
			// cout<<"u: "<<u<<endl;
			flr[u]=lim; ++cnt;
			for (int i=0; i<20; ++i) {
				gra[i][bel[u][i]].erase(u);
				if (gra[i][bel[u][i]].size()==1 && ++tim[*gra[i][bel[u][i]].begin()]==10)
					q[now^1].push(*gra[i][bel[u][i]].begin());
			}
		}
	}
	// cout<<"flr: "; for (int i=0; i<n; ++i) cout<<flr[i]<<' '; cout<<endl;
	// cout<<"tim: "; for (int i=0; i<n; ++i) cout<<tim[i]<<' '; cout<<endl;
	for (int i=0; i<n; ++i) if ((dep[i]=lim-flr[i]+1)==1) rot=i;
	for (int i=0; i<20; ++i) {
		for (auto& it:com[i]) {
			int mdep=INF, mini;
			for (auto& t:it) if (dep[t]<mdep) mdep=dep[t], mini=t;
			top[i].pb({mdep, mini});
			// cout<<"("<<mdep<<','<<mini<<") ";
		} //cout<<endl;
	}
	// cout<<"rot: "<<rot<<endl;
	// cout<<"dep: "; for (int i=0; i<n; ++i) cout<<dep[i]<<' '; cout<<endl;
	for (int i=0; i<n; ++i) if (i!=rot) {
		// cout<<"i: "<<i<<endl;
		int mdep=0, maxi;
		for (int j=0; j<20; ++j) if (com[j][bel[i][j]].size()>1) {
			if (top[j][bel[i][j]].fir>mdep && top[j][bel[i][j]].fir<dep[i])
				mdep=top[j][bel[i][j]].fir, maxi=top[j][bel[i][j]].sec; //, cout<<1<<endl;
		}
		ans.pb({i, maxi});
	}
	// cout<<"ans: "; for (auto& it:ans) cout<<"("<<it.fir<<','<<it.sec<<") "; cout<<endl;
	return ans;
}
posted @ 2022-08-09 16:44  Administrator-09  阅读(4)  评论(0编辑  收藏  举报