【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;
}
View Code

 

【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权就可以了。

posted @ 2018-04-23 20:27  ONION_CYC  阅读(101)  评论(0编辑  收藏