bzoj2594/洛谷P4172 水管局长 kruskal+lct

题目分析

由于只有删边操作…众所周知,删边操作不如加边操作好处理,那么我们将时光倒流一下,逆着处理询问,这样删边操作都变成了加边操作…
然后很显然,要最小化两个点之间路径上的边权最大值,就可以弄出最小生成树然后搞。
所以加边的同时维护最小生成树…那就是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;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值