【BZOJ】2594: [Wc2006]水管局长数据加强版(Link-Cut Tree维护动态最小生成树)
【参考】★t14t41t(感谢!)
【题意】给定带正边权简单无向图,每次操作删除一条边或者询问两点间最小的 [ 简单路径上边的最大值 ]。n,q<=10^5,m<=10^6。
【题解】题目询问的是两点间的最小瓶颈路,答案一定在原图的最小生成树上,于是本题就变成了动态维护删边最小生成树。
然而Link-Cut Tree维护最小生成树时并不支持删边操作,所以要离线处理,先删掉该删掉的边,再求最小生成树,把所有操作倒过来用LCT维护。
一、设原图中有n个点,m条边,把每条边视为一个点,编号为$n+1,n+2,⋯,n+m$,权值为其边权,初始化边权最大值为自己的边权。把原图中每个点的权值设成0。
因为点权固定,故LCT只需要维护权值最大的点编号;
二、求最小生成树的时候,每添一条边时,设这条边连接x和y,这条边编号为k。如果x和y未连接,直接把x和k连起来,y和k也连起来。
否则,查询这条边所连的两个结点路径上权值最大的边,若当前最大边的边权比新加入的还大,则断开当前最大边,把新边加入LCT中。
三、若查询x到y路径上的最大边权,只需要把x置为根,对y进行access操作,和splay操作,返回y所在实链上权值最大的点即可。LCT上权值不为0的点一定是原图中的边。
本题需要注意:
1.将所有未删的边建成最小生成树的时候不能用Link-Cut Tree,复杂度是O(m log n)但是常数太大。必须用kruscal算法。
2.尽量不用map,将所有边按编号排序后二分查找对应边。
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; bool isdigit(char c){return c>='0'&&c<='9';} int read(){ int s=0;char c; while(!isdigit(c=getchar())); do{s=s*10+c-'0';}while(isdigit(c=getchar())); return s; } const int maxn=100010,maxM=1100010; int A[maxM],B[maxM],z[maxM],n,m,q,kind[maxn],qx[maxn],qy[maxn],qz[maxn]; int mx[maxM],num[maxM],ans[maxn],t[maxM][2],mxi[maxM],f[maxM],b[maxM]; bool g[maxM],c[maxM]; struct cyc{ int x,y,id; bool operator <(const cyc &a)const{ return x<a.x||(x==a.x&&y<a.y); } }p[maxM]; int max(int x,int y){return x<y?y:x;} void up(int x){ if(num[x]>mx[t[x][0]]&&num[x]>mx[t[x][1]]){mx[x]=num[x];mxi[x]=x;return;} if(mx[t[x][0]]<mx[t[x][1]]){ mx[x]=mx[t[x][1]];mxi[x]=mxi[t[x][1]]; } else mx[x]=mx[t[x][0]],mxi[x]=mxi[t[x][0]];//mxi } void down(int x){ if(g[x]){ swap(t[x][0],t[x][1]); if(t[x][0])g[t[x][0]]^=1; if(t[x][1])g[t[x][1]]^=1; g[x]=0; } } bool isroot(int x){return !x||(t[f[x]][0]!=x&&t[f[x]][1]!=x);} void rotate(int x){ int y=f[x],k=x==t[y][1]; t[y][k]=t[x][!k];f[t[x][!k]]=y;// if(!isroot(y))t[f[y]][t[f[y]][1]==y]=x;f[x]=f[y];f[y]=x; t[x][!k]=y;up(y);up(x); } void splay(int x){ int y=x,tot=0; while(!isroot(y))b[++tot]=y,y=f[y]; b[++tot]=y; for(int i=tot;i>=1;i--)down(b[i]); while(!isroot(x)){ if(isroot(f[x]))return void(rotate(x)); int X=x==t[f[x]][1],Y=f[x]==t[f[f[x]]][1]; if(X^Y)rotate(x),rotate(x); else rotate(f[x]),rotate(x); } } void access(int x){ int y=0; while(x){ splay(x); t[x][1]=y; up(x); y=x;x=f[x]; } } void reverse(int x){access(x);splay(x);g[x]^=1;} void link(int x,int y){reverse(x);f[x]=y;} void cut(int x,int y){reverse(x);access(y);splay(y);t[y][0]=f[x]=0;} int root(int x){access(x);splay(x);while(t[x][0])down(x),x=t[x][0];return x;} void Link(int x){link(A[x],x);link(B[x],x);} void Cut(int x){cut(A[x],x);cut(B[x],x);} void solve(int x){ reverse(A[x]);access(B[x]);splay(B[x]); if(mx[B[x]]>num[x]){ Cut(mxi[B[x]]);Link(x); } } namespace kruscal{ int fa[maxn],cnt=0; struct edge{int u,v,w,id;}e[maxM]; int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);} bool cmp(edge a,edge b){return a.w<b.w;} void solve(){ for(int i=1;i<=n;i++)fa[i]=i; for(int i=n+1;i<=n+m;i++)if(c[i]){e[++cnt]=(edge){A[i],B[i],z[i],i};}; sort(e+1,e+cnt+1,cmp);int now=0; for(int i=1;i<=cnt;i++)if(find(e[i].u)!=find(e[i].v)){ fa[e[i].u]=e[i].v; Link(e[i].id); if((++now)==n-1)break; } } } int main(){ n=read();m=read();q=read(); for(int i=1;i<=m;i++){ A[n+i]=read();B[n+i]=read();z[n+i]=read(); p[i]=(cyc){A[n+i],B[n+i],n+i};//focus on index c[n+i]=1; } sort(p+1,p+m+1); for(int i=1;i<=q;i++){ kind[i]=read();qx[i]=read(),qy[i]=read(); if(kind[i]==2)qz[i]=p[lower_bound(p+1,p+m+1,(cyc){qx[i],qy[i],0})-p].id,c[qz[i]]=0; } for(int i=1;i<=n;i++)mx[i]=num[i]=0; for(int i=n+1;i<=n+m;i++)mx[i]=num[i]=z[i]; for(int i=1;i<=n+m;i++)mxi[i]=i; kruscal::solve();//1.complexity 2.link two tree point will lead to RE. int cnt=0; for(int i=q;i>=1;i--){ if(kind[i]==2){ solve(qz[i]); } else{ reverse(qx[i]); access(qy[i]); splay(qy[i]); ans[++cnt]=mx[qy[i]]; } } for(int i=cnt;i>=1;i--)printf("%d\n",ans[i]); return 0; }
【BZOJ】3514: Codechef MARCH14 GERALD07加强版
【题意】N个点M条边的无向图,询问保留图中编号在[l,r]的边的时候图中的联通块个数。
【算法】LCT+主席树
【题解】LCT维护从1到m加边的生成树,每条边加入如果形成环则替换掉环中最早加入的边,即c[x]表示边x替换掉的边的编号。
这样边x能连接两个不同连通块当且仅当c[x]<L,因为这样仅凭区间内的点不可能构成环(大概理解为x~c[x]就是最短的构成环的区间)
初始n个连通块,每连两个就会减少一个,所以答案=n-贡献,贡献只需要统计区间内满足c[x]<L的点数,这用主席树维护就可以了。
主席树和LCT可以分开写,比较方便。
【BZOJ】3669:NOI2014 魔法森林
【题意】给定一个无向图,每条边有两个权值ai和bi,从1走到N,设路径上a权的最大值为A,b权的最大值为B,求A+B的最小值。
【算法】LCT
【题解】如果只有一个权值就是最小瓶颈路,最小生成树就是答案。
两个权值怎么办?直接枚举其中一个啊。
按a权排序后,一条一条加边并查询1~n的答案+当前a权就可以了。