[集训队互测 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;
}