「APIO2020」交换城市 题解
作者:互联网
参考:APIO2020交换城市 - syksykCCC's blog
Statement
Solve
首先,我们简化题意:
给定一个 \(n\) 个点,\(m\) 条边的无向图,求图上 交换两点 所走过的边中 权值最大的最小是多少
这里,我们定义交换:从点 \(x,y\) 分别出发,到达对方,要求路径不相交,但允许在路径上反复横跳
强制在线,\(2\leq n\leq 10^5,n-1\leq m\leq 2e5\)
显然,两个点能够交换的条件是(至少满足一个即可):
- 存在度数 \(\geq3\) 的点
- 形成环
Subtask 1
图是链或者环
那这个就很好搞嘛,
如果是链,那么显然无解
如果是环,那么唯一的交换路径就是两个点同时 顺时针/逆时针 把环遍历一遍
那么,此时的答案就是所有边权的最大值
Subtask 2
菊花图
先判无解。
我们可以手玩一下,发现只需要讨论是否有点在根上即可
- \(x==rt\ ||\ y==rt\) ,答案就是 \(\min(w(x,y),忽略 (x,y) 的最小边权)\)
- \(x\oplus rt\ \&\&\ y\oplus rt\) ,答案就是 \(\min\{w(x,rt),w(y,rt),忽略前二者的最小边权\}\)
其中 \(\oplus\) 表示异或
Subtask 3
\(Q≤5,N≤1000,M≤2000\)
考虑 \(dp\) ,设 \(dp[i][j]\) 表示 从 \(x\) 走到了 \(i\) ,从 \(y\) 走到了 \(j\) 的最大边权最小值,那么,
\[dp[i][j]\leftarrow \min(dp[i][j],\min(\max(w(i,k),dp[k][j]),\max(w(j,h),dp[i][h]))) \]特殊的是, \(dp[i][i]=inf\)
Subtask 4
\(Q\leq 5\)
注意到询问比较少,我们考虑二分答案
发现现在的问题变成了只能走 边权 \(\leq lim\) 的边,\(x,y\) 交换的最大边权的最小
发现这样的表述很像 瓶颈路 ,我们可以想到抛弃二分,直接上 \(\text{Kruksal}\) 重构树
特殊的地方在于,一般的瓶颈路的目标是到达即可,而这里还要求到达的路径不能是一条链
我们可以在做最小生成树合并的时候动态维护一个点集是否是一条链
设 \(nch[u]\) (not a chain) 表示以 \(u\) 为代表元的连通块是否脱离的链的形态
考虑现在我们合并边 \((u,v)\)
-
\(deg[u]++,deg[v]++\) ,若
- \(nch[find(u/v)]=1\)
- \(deg[u/v]\geq 3\)
- \(find(u)==find(v)\)(原本已经连通了,再加一条就构成环)。
三者其一满足,\(nch[fath]=1\)
-
若 \(find(x)==find(y) \ \&\&\ nch[find(x)]=1\),即 \(x,y\) 联通且不是链,那么答案即为 \(w(u,v)\)
那么,时间复杂度 \(O(m\log m+qm\alpha(n))\)
Subtask 5
树
那么,不存在环,唯一可能的交换在于利用 \(deg\geq 3\) 的节点
预处理每个点到某个 \(deg\geq3\) 的最大边权的最小值即可
Subtask 6
考虑正解,发现 Subtask 4 的复杂度瓶颈在于判断是否形成链
考虑对 \(\text{Kruskal}\) 重构树进行一些改变,让在重构树上节点 \(u\) 的子树满足不形成链,即 \(\forall x,y\in u的子树 ,lca(x,y)=u ,s.t.,x\ y\) 可以交换且最大边权的最小值为 \(u\) 的点权
显然,改造后的重构树会是一颗多叉树
在这里,我们用一个 \(vector\) ,\(id[u]\) 记录以 \(u\) 为代表的连通块有什么点
如果 \(nch[u]\) 变成了 \(1\) ,即不形成链,满足了我们构建重构树的要求,那么这个时候,我们就可以在 \(\text{Kruskal}\) 树上连边了。
具体的,假设我们现在加入 \((u,v)\)
-
\(find(u)==find(v)\ \&\&\ nch[find(u)]=0\) ,原本已经连通了,再加一条就构成环。
这个时候,\(nch\) 变成 \(1\) ,我们把 \(id[find(u)]\) 中所有点向重构树连边
-
$find(u)\neq find(v) $ 但两者中有至少一个不是链,那么合并后肯定也不是链,直接连重构树
-
剩下的情况就是不连通且都是链的情况,但还需要看这两条链是怎么接的
- 首位相接,仍然是链:把 \(id[v]\) 给 \(id[u]\) 即可
- 产生三岔路:直接连边
注意不要忘了并查集的合并 (\(f[fv]=fu\))
这样的复杂度是多少呢?采用启发式合并的思想,\(O(m\log m+m\log n)\)
小结一下重构树的适用范围:对连通块有特殊限制的连通性问题,在某个时刻的连通块内查询的问题。
Code
#include<bits/stdc++.h>
using namespace std;
const int S = 2e5+5;
struct Edge{
int u,v,w;
bool operator<(const Edge &rhs)const{
return w<rhs.w;
}
}edge[S];
int rt[S],f[S],st[S],ed[S];
int dep[S],fa[S][20];
vector<int>Edge[S],id[S];
bool nch[S];
int n,m;
int find(int x){return f[x]==x?x:f[x]=find(f[x]);}
void dfs(int u){
rt[u]=fa[u][0]?rt[fa[u][0]]:u;
for(int i=1;i<=19;++i)
fa[u][i]=fa[fa[u][i-1]][i-1];
for(auto v:Edge[u])
dep[v]=dep[fa[v][0]=u]+1,
dfs(v);
}
int lca(int x,int y){
if(rt[x]!=rt[y])return -1;
if(dep[x]<dep[y])swap(x,y);
for(int i=19;~i;--i)
if(dep[fa[x][i]]>=dep[y])x=fa[x][i];
if(x==y)return x;
for(int i=19;~i;--i)
if(fa[x][i]!=fa[y][i])
x=fa[x][i],y=fa[y][i];
return fa[x][0];
}
void init(int N,int M,vector<int>U,vector<int>V,vector<int>W){
n=N,m=M;
for(int i=0;i<m;++i)
edge[i+1]={U[i]+1,V[i]+1,W[i]};
sort(edge+1,edge+1+m);
for(int i=1;i<=n<<1;++i)f[i]=i;
for(int i=1;i<=n;++i)st[i]=ed[i]=rt[i]=i,id[i].push_back(i);
//rt[i] 记录 i 表示的连通块在重构树上的父亲
//st[i] ed[i] 记录链首尾
for(int i=1;i<=m;++i){
int u=edge[i].u,v=edge[i].v,fu=find(u),fv=find(v),fath=i+n;
if(id[fu].size()<id[fv].size())swap(fu,fv),swap(u,v);
if(fu==fv){
if(!nch[fu]){
for(auto j:id[fu])Edge[fath].push_back(j);
id[fu].clear(),nch[fu]=true,rt[fu]=fath;
}
}else{
if(nch[fu]||nch[fv]){
if(nch[fu])Edge[fath].push_back(rt[fu]);
else for(auto j:id[fu])Edge[fath].push_back(j);
if(nch[fv])Edge[fath].push_back(rt[fv]);
else for(auto j:id[fv])Edge[fath].push_back(j);
id[fu].clear(),id[fv].clear();
nch[fu]=true,rt[fu]=fath;
}else{
if((u==st[fu]||u==ed[fu])&&(v==st[fv]||v==ed[fv])){
st[fu]=u^st[fu]^ed[fu],ed[fu]=v^st[fv]^ed[fv];
for(auto j:id[fv])id[fu].push_back(j);
id[fv].clear();
}else{
for(auto j:id[fu])Edge[fath].push_back(j);
for(auto j:id[fv])Edge[fath].push_back(j);
id[fu].clear(),id[fv].clear();
nch[fu]=true,rt[fu]=fath;
}
}
f[fv]=fu;
}
}
for(int i=1;i<=n+m;++i)
if(!dep[i])dfs(i);
//这里一定要逆序扫
}
int getMinimumFuelCapacity(int X,int Y){
int l=lca(X+1,Y+1);
if(l==-1)return -1;
return edge[l-n].w;
}
//int N,M,q,x,y;
//vector<int>U,V,W;
//
//int main(){
// scanf("%d%d",&N,&M);
// U.resize(M+1),V.resize(M+1),W.resize(M+1);
// for(int i=0;i<M;++i)scanf("%d%d%d",&U[i],&V[i],&W[i]);
// init(N,M,U,V,W);
// scanf("%d",&q);
// while(q--){
// scanf("%d%d",&x,&y);
// printf("%d\n",getMinimumFuelCapacity(x,y));
// }
// return 0;
//}
标签:rt,int,题解,边权,交换,nch,fa,APIO2020,find 来源: https://www.cnblogs.com/wyb-sen/p/15193742.html