【BZOJ2594】[WC2006] 水管局长数据加强版(重拾LCT)

点此看题面

大致题意: 定义一条路径的值为这条路径上权值最大值。现给定一张无向图,两种操作:询问两点间路径的最小值;删去一条边。

前言

\(Jan\ 29th\)刷题计划(1/6),算法标签:LCT。

这道题应该还算比较基础的\(LCT\)题吧。

好久没写\(LCT\)了,没想到\(LCT\)一遍就写对了,却挂在了其他地方......

大致思路

考虑如果没有删边操作,那就相当于求出原图的最小生成树,然后每次询问树上路径最大值。

但如果要在删边的情况下维护最小生成树,是非常麻烦的。

于是我们想到,可以离线,倒着处理,就变成了加边操作。

那么每次只要找到树上路径最大值,然后将其与新边的权值比较,如果新边权值较小,就将原先的边断开,再连上新边。

所以需要\(LCT\)来实现这里的连边、断边操作。

大致思路就是这么简单,可实现起来略有些复杂,可详见代码。

代码

#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 1000000
#define Pr pair<int,int>
#define mp make_pair
#define max(x,y) ((x)>(y)?(x):(y))
#define swap(x,y) (x^=y^=x^=y) 
using namespace std;
int n,m,k,Qt,g[N+5],St[N+5];struct Query {int op,x,y;}q[N+5];
struct edge
{
	int x,y,v,p;I edge(CI a=0,CI b=0,CI c=0,CI f=0):x(min(a,b)),y(max(a,b)),v(c),p(f){}//加边时强制x<y
}s[M+5],del[N+5];
I bool cmp_xy(Con edge& x,Con edge& y) {return x.x^y.x?x.x<y.x:x.y<y.y;}//按端点排序
I bool cmp_v(Con edge& x,Con edge& y) {return x.v<y.v;}//按边权排序
I bool cmp_p(Con edge& x,Con edge& y) {return abs(x.p)<abs(y.p);}//按读入顺序排序
class FastIO
{
	private:
		#define FS 100000
		#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
		#define pc(c) (C==E&&(clear(),0),*C++=c)
		#define tn (x<<3)+(x<<1)
		#define D isdigit(c=tc())
		int T;char c,*A,*B,*C,*E,FI[FS],FO[FS],S[FS];
	public:
		I FastIO() {A=B=FI,C=FO,E=FO+FS;}
		Tp I void read(Ty& x) {x=0;W(!D);W(x=tn+(c&15),D);}
		Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
		Tp I void write(Ty x) {W(S[++T]=x%10+48,x/=10);W(T) pc(S[T--]);}
		Tp I void writeln(Con Ty& x) {write(x),pc('\n');}
		I void clear() {fwrite(FO,1,C-FO,stdout),C=FO;}
}F;
class LinkCutTree//LCT
{
	private:
		#define SZ N+M
		#define PU(x)\
		(\
			O[x].Mx=O[x].V,O[x].Mp=x,\
			O[x].Mx<O[O[x].S[0]].Mx&&(O[x].Mx=O[O[x].S[0]].Mx,O[x].Mp=O[O[x].S[0]].Mp),\
			O[x].Mx<O[O[x].S[1]].Mx&&(O[x].Mx=O[O[x].S[1]].Mx,O[x].Mp=O[O[x].S[1]].Mp)\
		)
		#define PD(x) (O[x].R&&(Re(O[x].S[0]),Re(O[x].S[1]),O[x].R=0))
		#define Re(x) (x)&&(O[x].R^=1,swap(O[x].S[0],O[x].S[1]))
		#define IR(x) (O[O[x].F].S[0]^(x)&&O[O[x].F].S[1]^(x))
		#define Wh(x) (O[O[x].F].S[1]==(x))
		#define Co(x,y,d) (O[O[x].F=y].S[d]=x)
		#define MR(x) (Ac(x),S(x),Re(x))
		#define Sp(x,y) (MR(x),Ac(y),S(y))
		int St[SZ+5];struct node {int V,Mx,Mp,R,F,S[2];}O[SZ+5];
		I void Ro(int x)
		{
			RI f=O[x].F,p=O[f].F,d=Wh(x);!IR(f)&&(O[p].S[Wh(f)]=x),
			O[x].F=p,Co(O[x].S[d^1],f,d),Co(f,x,d^1),PU(f),PU(x);
		}
		I void S(int x)
		{
			RI f=x,T=0;W(St[++T]=f,!IR(f)) f=O[f].F;W(T) PD(St[T]),--T;
			W(!IR(x)) f=O[x].F,!IR(f)&&(Ro(Wh(x)^Wh(f)?x:f),0),Ro(x);
		}
		I void Ac(int x) {for(RI y=0;x;x=O[y=x].F) S(x),O[x].S[1]=y,PU(x);}
		I int FR(int x) {Ac(x),S(x);W(O[x].S[0]) PD(x),x=O[x].S[0];return S(x),x;}
		I void Link(CI x,CI y) {MR(x),FR(y)^x&&(O[x].F=y);}
		I void Cut(CI x,CI y) {MR(x),FR(y)==x&&O[y].F==x&&!O[y].S[0]&&(O[x].S[1]=O[y].F=0,PU(x));}
	public:
		I void Init() {for(RI i=1;i<=n;++i) O[i].V=-1;for(RI i=1;i<=m;++i) O[n+i].V=s[i].v;}//初始化点权
		I void Add(CI x,CI y,CI id) {Link(x,n+id),Link(n+id,y);}//初始连边
		I void Try(CI id)//加边操作
		{
			RI x=s[id].x,y=s[id].y,v=s[id].v;if(Sp(x,y),O[y].Mx<s[id].v) return;//如果路径上原最大值比新边权值小,不操作
			RI t=O[y].Mp;Cut(s[t-n].x,t),Cut(t,s[t-n].y),Link(x,n+id),Link(n+id,y);//断掉原先的边,连上新边
		}
		I int Qry(CI x,CI y) {return Sp(x,y),O[y].Mx;}//求树上路径最大值
}S;
namespace MST//求出初始状态下的最小生成树
{
	int fa[N+5];I int getfa(CI x) {return fa[x]?fa[x]=getfa(fa[x]):x;}//并查集
	I void Work()
	{
		RI i,fx,fy;for(i=1;i<=m;++i) s[i].p>0&&//s[i].p<0表示这条边被删掉过
			(fx=getfa(s[i].x))^(fy=getfa(s[i].y))&&(S.Add(s[i].x,s[i].y,s[i].p),fa[fx]=fy);//最小生成树
	}
}
int main()
{
	RI i,j,x,y,v,T=0;using namespace MST;
	for(F.read(n,m,Qt),i=1;i<=m;++i) F.read(x,y,v),s[i]=edge(x,y,v,i);S.Init();//读入图,初始化LCT
	for(i=1;i<=Qt;++i) F.read(q[i].op,q[i].x,q[i].y),//读入询问
		q[i].op==2&&(del[++k]=edge(q[i].x,q[i].y,0,i),0);//对于删边操作,存储下被删的边
	for(sort(s+1,s+m+1,cmp_xy),sort(del+1,del+k+1,cmp_xy),i=j=1;i<=m;++i)//将两组边都按端点排序
		s[i].x==del[j].x&&s[i].y==del[j].y&&(g[del[j++].p]=s[i].p,s[i].p*=-1);//将删去的边对应到图上,并用s[i].p*=-1表示这条边被删过
	sort(s+1,s+m+1,cmp_v),Work(),sort(s+1,s+m+1,cmp_p);//将边按权值排序后求最小生成树,然后按读入顺序排序方便之后加边
	for(i=Qt;i;--i) q[i].op==1?St[++T]=S.Qry(q[i].x,q[i].y):(S.Try(g[i]),0);//倒着处理询问,用栈存储答案
	W(T) F.writeln(St[T--]);return F.clear(),0;
}
posted @ 2020-01-29 11:14  TheLostWeak  阅读(...)  评论(...编辑  收藏