题目分析
由于只有删边操作…众所周知,删边操作不如加边操作好处理,那么我们将时光倒流一下,逆着处理询问,这样删边操作都变成了加边操作…
然后很显然,要最小化两个点之间路径上的边权最大值,就可以弄出最小生成树然后搞。
所以加边的同时维护最小生成树…那就是lct了…
然后对于一条边,新建一个点,点权即该边边权。而原图上的点的权值为0。splay维护子树中最大权值点的编号。
那么对于一次加边操作,我们先用并查集维护该边连接的两点是否相连,不相连则直接加边。否则先找到两点之间的最大权边,比较该边与最大权边的权值,如果该边权值较小,则删去最大权边,加边。
思路还是很直观的,不过由于bzoj的数据丧心病狂,所以注意两点。
1.一定要用kurscal处理没有被删过的边,而不能直接用lct去搞(不过这也是废话,用lct去搞一定会T飞啊)
2.无论是用链式前向星还是map搞边哈希,绝对都会T飞,所以还是将边排序后二分查找比较好。
代码
#include<bits/stdc++.h>
using namespace std;
int read() {
int q=0;char ch=' ';
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
return q;
}
const int N=100005,M=1000005;
int n,m,Q,top;
struct edge{int u,v,w,bj;}e[M],q[N];
int operator < (edge x,edge y) {
if(x.u!=y.u) return x.u<y.u;
if(x.v!=y.v) return x.v<y.v;
return x.bj>y.bj;
}
int cmp(int x,int y) {return e[x].w<e[y].w;}
int ff[N],mx[N+M],son[N+M][2],f[N+M],rev[N+M],st[N+M],v[N+M],ans[N],id[M];
//ff:并查集 mx:子树中权值最大点的下标
int isroot(int x) {return son[f[x]][0]!=x&&son[f[x]][1]!=x;}
int is(int x) {return son[f[x]][1]==x;}
void pd(int x) {
if(!rev[x]) return;
if(son[x][0]) rev[son[x][0]]^=1;
if(son[x][1]) rev[son[x][1]]^=1;
swap(son[x][0],son[x][1]),rev[x]=0;
}
void up(int x) {
mx[x]=x;
if(son[x][0]&&v[mx[son[x][0]]]>v[mx[x]]) mx[x]=mx[son[x][0]];
if(son[x][1]&&v[mx[son[x][1]]]>v[mx[x]]) mx[x]=mx[son[x][1]];
}
void spin(int x) {
int fa=f[x],g=f[fa],t=is(x);
if(!isroot(fa)) son[g][is(fa)]=x;
f[x]=g,f[fa]=x,f[son[x][t^1]]=fa;
son[fa][t]=son[x][t^1],son[x][t^1]=fa;
up(fa),up(x);
}
void splay(int x) {
st[++top]=x;for(int i=x;!isroot(i);i=f[i]) st[++top]=f[i];
while(top) pd(st[top--]);
while(!isroot(x)) {
if(!isroot(f[x])) {
if(is(f[x])^is(x)) spin(x);
else spin(f[x]);
}
spin(x);
}
}
void acc(int x) {int y=0;while(x) splay(x),son[x][1]=y,up(x),y=x,x=f[x];}
void evert(int x) {acc(x),splay(x),rev[x]^=1;}
void link(int x,int y) {evert(x),f[x]=y;}
void split(int x,int y) {evert(y),acc(x),splay(x);}
void cut(int x,int y) {split(x,y),son[x][0]=f[y]=0,up(x);}
int query(int x,int y) {split(x,y);return mx[x];}//获取两点之间最大边编号
int find(int x) {return x==ff[x]?x:ff[x]=find(ff[x]);}
void kurscal() {
for(int i=1;i<=n;++i) ff[i]=i,mx[i]=i;
for(int i=n+1;i<=n+m;++i) mx[i]=i,v[i]=e[i-n].w;
int js=0;
for(int i=1;i<=m;++i) {
int k=id[i];
if(e[k].bj) continue;
int r1=find(e[k].u),r2=find(e[k].v);
if(r1!=r2) ff[r1]=r2,link(e[k].u,n+k),link(e[k].v,n+k),++js;
if(js==n-1) break;
}
}
void work() {
int cn=0;
for(int i=Q;i>=1;--i)
if(q[i].w==1) {
int kl=query(q[i].u,q[i].v);
ans[++cn]=v[kl];
}
else {
int r1=find(q[i].u),r2=find(q[i].v);
if(r1!=r2) ff[r1]=r2,link(q[i].u,q[i].bj+n),link(q[i].v,q[i].bj+n);
else {
int kl=query(q[i].u,q[i].v);
if(v[kl]>v[q[i].bj+n]) {
cut(e[kl-n].u,kl),cut(e[kl-n].v,kl);
link(q[i].u,q[i].bj+n),link(q[i].v,q[i].bj+n);
}
}
}
for(int i=cn;i>=1;--i) printf("%d\n",ans[i]);
}
int main()
{
n=read(),m=read(),Q=read();
for(int i=1;i<=m;++i) {
e[i].u=read(),e[i].v=read(),e[i].w=read();
if(e[i].u>e[i].v) swap(e[i].u,e[i].v);
}
sort(e+1,e+1+m);//将边排序以便于哈希查找
for(int i=1;i<=Q;++i) {
q[i].w=read(),q[i].u=read(),q[i].v=read();
if(q[i].w==2) {
if(q[i].u>q[i].v) swap(q[i].u,q[i].v);
int k=lower_bound(e+1,e+1+m,q[i])-e;
e[k].bj=1,q[i].bj=k;//找到删除的那条边的编号
}
}
for(int i=1;i<=m;++i) id[i]=i;
sort(id+1,id+1+m,cmp);//按照权值从小到大排序
kurscal(),work();
return 0;
}

本文介绍了一种使用最小生成树与链式前向星(LCT)算法处理动态图问题的方法,通过逆向处理边的增删操作,实现对图中两点间路径上边权的最大值进行高效查询。
502

被折叠的 条评论
为什么被折叠?



