BZOJ3514 GERALD07加强版

本文介绍了一种使用树状数据结构和主席树优化无向图连通性查询的高级算法,通过维护最大生成树和边的替换关系,实现高效查询特定区间内连通块数量,适用于大规模图数据。

Link

Difficulty

算法难度7,思维难度7,代码难度7

Description

给定一个无向图,有 n n n个点, m m m条边,边从 1 1 1 m m m标号

q q q次询问,每次询问 [ l , r ] [l,r] [l,r]内的边组成的图的连通块个数

1 ≤ n , m , q ≤ 2 × 1 0 5 1\le n,m,q \le 2\times 10^5 1n,m,q2×105

Solution

这题听说可以树套树,但是会TLE

以下都是神仙做法:

我们维护按照编号加边,维护边的最大生成树,边的权值就是编号

每次如果没有形成环就直接加,如果有环就替换掉最小的那条边,可以LCT维护

我们记第 i i i条边替换的边的序号为 n t r i ntr_i ntri,特别的,自环的 n t r = m ntr=m ntr=m,直接加的边的 n t r = 0 ntr=0 ntr=0

查询的时候直接查 [ l , r ] [l,r] [l,r] n t r i &lt; l ntr_i&lt;l ntri<l的个数,答案就是 n n n减去这个个数,可以主席树维护

接下来我们尝试证明这个结论的正确性:

我们考虑如果一条位于 [ l , r ] [l,r] [l,r]内的边的 n t r ntr ntr取不同的值时,它对答案的贡献

  1. n t r i = 0 ntr_i=0 ntri=0时,也就是可以直接加上这条边,那么连通块个数会-1

  2. 0 &lt; n t r i &lt; l 0&lt;ntr_i&lt;l 0<ntri<l时,也就是它替换掉了前面出现过的一条边,并且这条边的编号 &lt; l &lt;l <l

    你可能会说:“它没有贡献啊,它明明替换了一条边,怎么会有贡献呢?”

    但是你会发现它替换的边的编号 &lt; l &lt;l <l,那么这时这条边已经不存在了

    也就是说现在这条边就可以替代它的作用了,这时它就可以有1的贡献了

  3. l ≤ n t r i ≤ r l\le ntr_i\le r lntrir时,也就是说它替换了一条前面出现的边,并且这条边的编号 ≥ l \ge l l

    和前面同理,只不过这时它替换的边是存在的,那么现在这条边就没有贡献了

  4. n t r i = m ntr_i=m ntri=m时,也就是说这条边是一个自环,那么它一定不会有贡献

这样我们就求得了 [ l , r ] [l,r] [l,r]内的边对答案的贡献了,就可以计算答案了

时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn),本人在bzoj上跑了32s,写起来应该没什么常数问题

#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<iostream>
#include<cstring>
#define LL long long
using namespace std;
inline int read(){
    int x=0,f=1;char ch=' ';
    while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0' && ch<='9')x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
    return f==1?x:-x;
}
const int N=4e5+5;
namespace LCT{
    int ch[N][2],f[N],rev[N],mn[N],id[N];
    inline bool get(int x){return ch[f[x]][1]==x;}
    inline void pushup(int x){mn[x]=min(id[x],min(mn[ch[x][0]],mn[ch[x][1]]));}
    inline bool nroot(int x){return ch[f[x]][0]==x || ch[f[x]][1]==x;}
    inline void reverse(int x){rev[x]^=1;swap(ch[x][0],ch[x][1]);}
    inline void pushdown(int x){
        if(rev[x]){
            if(ch[x][0])reverse(ch[x][0]);
            if(ch[x][1])reverse(ch[x][1]);
            rev[x]=0;
        }
    }
    inline void pd(int x){if(nroot(x))pd(f[x]);pushdown(x);}
    inline void rotate(int x){
        int y=f[x],z=f[y],w=get(x);
        f[x]=z;if(nroot(y))ch[z][get(y)]=x;
        ch[y][w]=ch[x][w^1];f[ch[y][w]]=y;
        ch[x][w^1]=y;f[y]=x;
        pushup(y);pushup(x);
    }
    inline void Splay(int x){
        pd(x);
        for(int fa=f[x];nroot(x);rotate(x),fa=f[x])
            if(nroot(fa))
                rotate((get(fa)==get(x))?fa:x);
    }
    inline void access(int x){for(int y=0;x;x=f[y=x])Splay(x),ch[x][1]=y,pushup(x);}
    inline void makeroot(int x){access(x);Splay(x);reverse(x);}
    inline int findroot(int x){access(x);Splay(x);while(ch[x][0])x=ch[x][0];return x;}
    inline void split(int x,int y){makeroot(x);access(y);Splay(y);}
    inline void link(int x,int y){split(x,y);f[x]=y;}
    inline void cut(int x,int y){split(x,y);f[x]=ch[y][0]=0;}
};
using namespace LCT;
int n,m,q;
int X[N],Y[N],ntr[N];
int sum[N*30],ls[N*30],rs[N*30],sz,root[N];
inline void insert(int &now,int pre,int l,int r,int pos){
    now=++sz;
    sum[now]=sum[pre]+1;
    ls[now]=ls[pre];
    rs[now]=rs[pre];
    if(l==r)return;
    int mid=(l+r)>>1;
    if(pos<=mid)insert(ls[now],ls[pre],l,mid,pos);
    else insert(rs[now],rs[pre],mid+1,r,pos);
}
inline int query(int now,int pre,int l,int r,int pos){
    if(l==r)return sum[now]-sum[pre];
    int mid=(l+r)>>1;
    if(pos<=mid)return query(ls[now],ls[pre],l,mid,pos);
    else return query(rs[now],rs[pre],mid+1,r,pos)+sum[ls[now]]-sum[ls[pre]];
}
int main(){
    memset(mn,0x3f,sizeof mn);
    memset(id,0x3f,sizeof id);
    n=read();m=read();q=read();int type=read();
    for(int i=1;i<=m;++i){
        X[i]=read();
        Y[i]=read();
        mn[i+n]=id[i+n]=i;
        if(X[i]==Y[i]){
            ntr[i]=m;
            insert(root[i],root[i-1],0,m,ntr[i]);
            continue;
        }
        int fx=findroot(X[i]),fy=findroot(Y[i]);
        if(fx!=fy){
            link(i+n,X[i]);
            link(i+n,Y[i]);
            ntr[i]=0;
        }
        else{
            split(X[i],Y[i]);
            int id=mn[Y[i]];
            cut(id+n,X[id]);
            cut(id+n,Y[id]);
            link(i+n,X[i]);
            link(i+n,Y[i]);
            ntr[i]=id;
        }
        insert(root[i],root[i-1],0,m,ntr[i]);
    }
    int lastans=0;
    for(int i=1;i<=q;++i){
        int l=read()^lastans,r=read()^lastans;
        printf("%d\n",lastans=n-query(root[r],root[l-1],0,m,l-1));
        if(!type)lastans=0;
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值