【洛谷6765】[APIO2020] 交换城市(Kruskal生成树)

点此看题面

  • 给定一张\(n\)个点\(m\)条边的无向图。
  • \(q\)次询问,每次给定两个点。让你找到一种走法,在两点不相遇的前提下交换两点位置,使得所经边权的最大值最小。
  • \(n\le10^5,m,q\le2\times10^5\),强制在线

一开始想直接根据推出的性质大力分类讨论。

写了\(150\)多行之后发现有一个地方完全写错了,一怒之下直接弃掉了,果然现在根本写不来细节题。。。

后来发现\(Kruskal\)重构树又好想又好写,于是果断选择了\(Kruskal\)重构树。

至于什么是\(Kruskal\)重构树,可见这篇博客:Kruskal重构树补记

暴力二分答案

首先,考虑暴力二分答案\(x\),那么就相当于每次保留边权小于等于\(x\)的边验证在这张新图上是否能完成交换。

容易发现,除非这张图是一条链,否则必然存在一种可行的方案。(具体证明自己画画图就好了,应该是比较显然的)

但如果每次询问都二分一次答案显然不可行,需要用一些更高级的东西。

\(Kruskal\)重构树

考虑\(Kruskal\)重构树上倍增本质上就是一个二分答案的过程。

我们只要记录一下树上每个点的子树在原图上是不是一条链。

回忆\(Kruskal\)重构树的基本性质:从\(x\)出发只经过边权大于等于/小于等于\(v\)的边所能到达的点集,就是\(x\)深度最小点权大于等于\(v\)/小于等于\(v\)的祖先子树内所有的叶节点。

因此我们只要找到询问两点\(LCA\)深度最大的子树不是一条链的点,它的点权便是答案。

那么现在的问题就是如何判断一个点子树在原图上是不是一条链。

每加入一条边,我们分类讨论:

  • 如果它的两个端点已经连通,那么加上这个点之后必然不可能是链。在一般的\(Kruskal\)重构树上我们不会对应这种无用边建点,但此题中我们需要给它建一个点,并标记这个点子树内不是一条链。
  • 如果它的两个端点没有联通,那么仅当它的两个端点所在的连通块原本都是一条链,且这两个端点都是各自所在链的首/尾,新的连通块才是一条链。为了处理这个判断,对于每条链还要记录一下它的首尾。

建出树之后,询问只要倍增跳就可以解掉了。

代码:\(O((m+q)logn)\)

#include "swap.h"
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define M 200000
#define LN 20
#define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y)
using namespace std;
int n,m;struct line {int x,y,v;I bool operator < (Con line& o) Con {return v<o.v;}}s[M+5];
int rt,X[N+M+5],Y[N+M+5],ee,lnk[N+M+5];struct edge {int to,nxt;}e[2*M+5];
int fa[N+M+5];I int getfa(CI x) {return fa[x]?fa[x]=getfa(fa[x]):x;}//并查集维护连通性
int D[N+M+5],T[N+M+5],V[N+M+5],f[N+M+5][LN+5];I void dfs(CI x)
{
	RI i;for(i=1;i<=LN;++i) f[x][i]=f[f[x][i-1]][i-1];//预处理倍增数组
	for(i=lnk[x];i;i=e[i].nxt) D[e[i].to]=D[f[e[i].to][0]=x]+1,dfs(e[i].to);
}
I int LCA(RI x,RI y)//倍增LCA
{
	RI i;D[x]<D[y]&&(swap(x,y),0);
	for(i=0;D[x]^D[y];++i) (D[x]^D[y])>>i&1&&(x=f[x][i]);if(x==y) return x;
	for(i=LN;~i;--i) f[x][i]^f[y][i]&&(x=f[x][i],y=f[y][i]);return f[x][0];
}
I int Jump(RI x)//倍增上跳,找到深度最大的子树不是一条链的点
{
	for(RI i=LN;~i;--i) !T[f[x][i]]&&(x=f[x][i]);return T[x]?x:f[x][0];
}
void init(int _n,int _m,vector<int> _x,vector<int> _y,vector<int> _v)//初始化
{
	RI i;for(n=_n,m=_m,i=1;i<=m;++i) s[i].x=_x[i-1]+1,s[i].y=_y[i-1]+1,s[i].v=_v[i-1];
	RI o,x,y,A,B;for(i=1;i<=n;++i) X[i]=Y[i]=i;for(sort(s+1,s+m+1),i=1;i<=m;++i)//Kruskal
	{
		V[o=n+i]=s[i].v,x=getfa(s[i].x),y=getfa(s[i].y);
		if(x==y) {T[o]=1,add(o,x),fa[x]=o;continue;}//如果已连通,新建一个点,标记不是链
		if(add(o,x),add(o,y),fa[x]=fa[y]=o,T[o]=T[x]|T[y]) continue;//如果两个端点中有至少一个所在连通块不是一条链
		s[i].x==X[x]&&(X[o]=Y[x]),s[i].x==Y[x]&&(X[o]=X[x]),
		s[i].y==X[y]&&(Y[o]=Y[y]),s[i].y==Y[y]&&(Y[o]=X[y]),(!X[o]||!Y[o])&&(T[o]=1);//如果有至少一个不是链的首尾
	}dfs(n+m),T[0]=1;
}
int getMinimumFuelCapacity(int x,int y) {RI t=Jump(LCA(x+1,y+1));return t?V[t]:-1;}//询问
posted @ 2020-12-23 18:31  TheLostWeak  阅读(24)  评论(0编辑  收藏