[集训队互测 2023] R9T2 通道建设

为什么 QOJ 上其他人都爆标还原了整颗树,而只有我傻傻改标算。

是不是做这道题的除了我都有脑子。

感觉像是完全对着硬 idea 出的,所以正常人做题想法根标方向完全不一样,但是涉及到的技巧都还是挺有用的哈!


题意大概是有一颗 \(2n\) 个点的树,你得知了前 \(n\) 个点构成的虚树形态,然后你能进行两类询问:

  • 给出两个集合 \(A,B\) 和一个点 \(P\),询问在以 \(A\) 中节点的每一个点作为根时,\(\text{LCA}(B)\) 是否是 \(P\)。需要满足询问次数 \(O(n)\)\(\sum |A|,\sum |B|\) 都在 \(O(n\log n)\) 级别。

  • 询问两点距离。询问次数限制 \(2n\)

然后你需要构造一组大小为 \(n\) 匹配 \((a_i,b_i)\) 满足不存在 \(i\neq j\) 使得 \(\text{dis}(a_i,b_i)<\text{dis}(a_i,b_j)<\text{dis}(a_j,b_j)\)


如果你是正常人,你会先想给定树形态怎么做。发现如果你取的点对距离均不超过 \(2\),那么限制条件总会满足。容易在树上构造一组距离都不超过 \(2\) 的匹配方案。

如果你是正常人,那么你会考虑如何直接还原树形态。

如果你是正常人且强如 zhouhuanyi,那么你就可以想出一种基于问出深度对每一层分治的做法

你发现这样题目中的限制条件一点意义都没有,所以这并不是出题人的意图。

题目中“匹配”的定义明显就是稳定婚姻问题:左部点更偏好距离大的点,右部点更偏好距离小的点,然后不能有匹配“私奔”。

由稳定婚姻解的存在性,这意味着你可以任取左右部的点集而都是有解的。

这样你直接取原来的 \(n\) 个点作为左部点,你的任务变成了求出左右部点两两间的 \(\text{dis}\)

这个问题比确定所有点对的距离简单。这是因为你只需要知道后 \(n\) 个点在前 \(n\) 个点虚树上的位置而不关心后 \(n\) 个点的具体形态。你得到一个点是挂在两个点间还是一个点上后,你直接问出它到这两个点或这一个点的距离。然后你就可以确定一开始的虚树上每两个点的距离。(具体地,总有一个点在这两个点的路径上,所以将所有的到这两个点的距离之和取个最小值就行了)。

接下来你只需要解决定位节点问题就行了。我们考虑树上定位的一个经典想法:点分治然后判断其在哪个子树方向内。

具体地,先考虑如何只定位一个点。对于当前分治中心,我们先判断当前点是不是挂在分治中心上,这个可以通过判断分治中心所有邻接点以当前点为根的 \(\text{LCA}\) 是不是分治中心来判断。

然后我们进行二分邻接点集合,对于当前二分判断的集合 \(V\),我们可以通过以当前点为根这些点的 \(\text{LCA}\) 是不是 \(u\) 来判断当前点是否在 \(V\) 中点的子树内(或者也有可能在指向这颗子树的边上,这个可以二分完后再用一次询问判断一下)。

然后注意到我们只用做后 \(n\) 个点在前 \(n\) 个点虚树上的定位问题,所以我们直接并行这个二分过程,也就是整体二分。

毛咕咕下复杂度,首先第二类询问只需要至多 \(2n\) 次就行了。注意到整体二分的结构相当于遍历所有邻接点建出的线段树,第一类询问次数就是线段树总节点数,不超过度数之和是线性的。\(\sum |B|\) 就是线段树区间长度之和多挂了一个 \(\log\),也可以接受。

问题在于 \(\sum |A|\),每个需要定位点的贡献相当于每个分治中心二分都有一个 \(\log\),所以是两个 \(\log\) 的炸了怎么办?

注意到这样一个问题,全局平衡二叉树的思想是将树剖和线段树/平衡树的结构合在一起,从而消掉一个 \(\log\)。通过带权分割建立线段树/平衡树,将下面挂的轻子树重量大的放在离根尽量近的地方。

这道题也可以同理,我们二分的时候按子树大小不均匀划分集合就可以少一只 \(\log\) 了。

标算给了一种更加简单的实现,就是建虚点多叉转二叉然后在新树上点分治,这样现在的虚点就隐含了原树上对每一个分治中心分治的信息。这样甚至免去了建整体二分的分治结构。

#include "passageconstruction.h"
#include <queue>
#include <vector>
#include <cassert>
#include <numeric>
#include <algorithm>
#define fi first
#define se second
using namespace std;
typedef pair<int,int> pii;
typedef vector<int> vi;
typedef vector<pii> vpii;
const int N=10003,M=5003;
int n,cnt;
int lc[N],rc[N],fa[N],nd[N];
vi vec[N];
void rebuild(int u,int fs){
	int las=u;
	for(int v:vec[u]){
		if(v==fs) continue;
		rebuild(v,u);
		int p=++cnt;
		nd[p]=u;
		fa[lc[p]=v]=p;
		fa[rc[las]=p]=las;
		las=p;
	}
}
bool del[N];
int sz[N],sn[N];
void dfs(int u){
	sz[u]=1;sn[u]=0;
	if(lc[u]&&!del[lc[u]]){
		dfs(lc[u]);sz[u]+=sz[lc[u]];
		if(sz[lc[u]]>sz[sn[u]]) sn[u]=lc[u];
	}
	if(rc[u]&&!del[rc[u]]){
		dfs(rc[u]);sz[u]+=sz[rc[u]];
		if(sz[rc[u]]>sz[sn[u]]) sn[u]=rc[u];
	}
}
vector<int> lis[N];
void proc(int rt,vi cur){
	if(cur.empty()) return;
	int x=rt;
	dfs(rt);
	while(sz[sn[x]]*2>sz[rt]) x=sn[x];
	del[x]=1;
	if(x<=n){
		vi qvec=vec[x];
		if(qvec.size()==1lu) qvec.emplace_back(x);
		if(!cur.empty()){
			vi RES=QueryLCA(cur,qvec,x),out;
			for(int i=0;i<(int)cur.size();++i)
				if(RES[i]) lis[x].emplace_back(cur[i]);
				else out.emplace_back(cur[i]);
			cur.swap(out);
		}
	}
	if(lc[x]){
		vi onin,out,in;
		if(!cur.empty()){
			vi RES=QueryLCA(cur,{nd[x],lc[x]},nd[x]);
			for(int i=0;i<(int)cur.size();++i)
				if(RES[i]) out.emplace_back(cur[i]);
				else onin.emplace_back(cur[i]);
			cur.swap(out);
		}
		if(del[lc[x]]){
			for(int p:onin) lis[x].emplace_back(p);
		}
		else{
			vi in;
			if(!onin.empty()){
				vi RES=QueryLCA(onin,{nd[x],lc[x]},lc[x]);
				for(int i=0;i<(int)onin.size();++i)
					if(RES[i]) in.emplace_back(onin[i]);
					else lis[x].emplace_back(onin[i]);
			}
			proc(lc[x],in);
		}
	}
	if(rc[x]&&!del[rc[x]]){
		vi chain,in,out;
		for(int i=rc[x];i&&!del[i];i=rc[i])
			chain.emplace_back(lc[i]);
		if(chain.size()==1lu) chain.emplace_back(nd[x]);
		vi RES=QueryLCA(cur,chain,nd[x]);
		for(int i=0;i<(int)cur.size();++i)
			if(RES[i]) out.emplace_back(cur[i]);
			else in.emplace_back(cur[i]);
		cur.swap(out);
		proc(rc[x],in);
	}
	if(x!=rt) proc(rt,cur);
}
int d[M][M],p[M][M],q[M];
int mat[N];
int len;
int dl[N],dr[N];
vpii adj[N];
void dfs(int u,int fa,int *dep){
	for(auto [v,w]:adj[u]){
		if(v==fa) continue;
		dep[v]=dep[u]+w;
		dfs(v,u,dep);
	}
}
vpii ConstructPassages(int _N,const vpii &_E){
	n=_N;
	if(n==1) return {{1,2}};
	for(auto [u,v]:_E){
		vec[u].emplace_back(v);
		vec[v].emplace_back(u);
	}
	vi init;
	for(int i=1;i<=n;++i) nd[i]=i,init.emplace_back(i+n);
	cnt=n;
	rebuild(1,0);
	proc(1,init);
	for(int i=n+1;i<=cnt;++i){
		int len=1;
		int u=nd[i],v=lc[i];
		for(int x:lis[i]){
			dl[x]=GetDistance(u,x);
			dr[x]=GetDistance(v,x);
			if(dl[x]==1||dr[x]==1) len=dl[x]+dr[x];
		}
		adj[v].emplace_back(u,len);
		adj[u].emplace_back(v,len);
	}
	for(int i=1;i<=n;++i)
		for(int x:lis[i]){
			d[x-n][i]=GetDistance(i,x);
			dfs(i,0,d[x-n]);
		}
	for(int i=n+1;i<=cnt;++i){
		int u=nd[i],v=lc[i];
		for(int x:lis[i]){
			d[x-n][u]=dl[x];dfs(u,v,d[x-n]);
			d[x-n][v]=dr[x];dfs(v,u,d[x-n]);
		}
	}
	queue<int> que;
	for(int i=1;i<=n;++i){
		iota(p[i]+1,p[i]+n+1,1);
		sort(p[i]+1,p[i]+n+1,[&](int x,int y){return d[i][x]>d[i][y];});
		q[i]=1;mat[i]=0;que.emplace(i);
	}
	int cnt=0;
	while(!que.empty()){
		int u=que.front();que.pop();
		int v=p[u][q[u]];
		if(!mat[v]||d[u][v]<d[mat[v]][v]||(d[u][v]==d[mat[v]][v]&&u<mat[v])){
			if(mat[v]&&++q[mat[v]]<=n) que.emplace(mat[v]);
			mat[v]=u;
		}
		else if(++q[u]<=n) que.emplace(u);
	}
	vpii res;
	for(int i=1;i<=n;++i) res.emplace_back(mat[i]+n,i);
	return res;
}
posted @ 2024-03-28 22:47  yyyyxh  阅读(50)  评论(0编辑  收藏  举报