【洛谷P5385】[Cnoi2019]须臾幻境

LCT+可持久化线段树

题目大意:

给定一张 $n$ 个点,$m$ 条边的无向图,每条边有编号。

多组询问,每次询问只保留编号为 $l\sim r$ 的边所能形成的连通块的个数。

强制在线。

题解:

我们按编号从小到大加边,用 LCT 维护当前的图。

对每条边 $i$,如果加入的时候形成了环,那么我们把这个环上加入时间最早的边 $k$ 删掉,并记录边 $i$ 替换掉的边 $h_i=k$。如果没替换边则 $h_i=0$。

考虑查询的时候,我们查询 $[l,r]$ 内 $h_i\in[0,l-1]$ 的 $i$ 的个数 $c$,然后 $n-c$ 即为答案。

我们考虑一条新加入的边,如果这条边原定要替换的边不存在,或者在 $l$ 之前,那么加入这条边的时候,势必会将两个连通块合并,所以贡献 $-1$。如果要替换的边在 $[l,r]$ 内,则之前的那条边已经将两个连通块连通,这条边就不计贡献。

用 LCT 预处理出 $h_i$,然后用可持久化线段树维护、查询即可。

注意自环要判掉。

时间复杂度 $O((n+m+q)\log n)$。

Code:

#include<iostream>
#include<algorithm>
#include<utility>
using namespace std;
const int N=3e5+6,inf=1e9;
int n,m,q,t;
pair<int,int>e[N];
namespace lct{
    int sz[N],fa[N],ch[N][2],val[N],mn[N];bool tag[N];
    inline bool ckr(int x,const int p=1){return ch[fa[x]][p]==x;}
    inline bool isroot(int x){return!(ckr(x)||ckr(x,0));}
    inline void flip(int x){swap(ch[x][0],ch[x][1]),tag[x]^=1;}
    inline void update(int x){
        sz[x]=sz[ch[x][0]]+sz[ch[x][1]]+1;
        mn[x]=min({mn[ch[x][0]],mn[ch[x][1]],val[x]});
    }
    inline void pushdown(int x){
        if(tag[x]){
            tag[x]=0;
            if(ch[x][0])flip(ch[x][0]);
            if(ch[x][1])flip(ch[x][1]);
        }
    }
    inline void rotate(int x){
        int y=fa[x],z=fa[y],k=ckr(x);
        if(!isroot(y))ch[z][ckr(y)]=x;
        fa[y]=x,fa[x]=z,fa[ch[x][!k]]=y;
        ch[y][k]=ch[x][!k],ch[x][!k]=y;
        update(y);
    }
    inline void splay(int x){
        static int sta[N],top;sta[top=1]=x;
        for(int y=x;!isroot(y);sta[++top]=y=fa[y]);
        while(top)pushdown(sta[top--]);
        for(;!isroot(x);rotate(x))
            if(!isroot(fa[x]))rotate((ckr(x)^ckr(fa[x]))?x:fa[x]);
        update(x);
    }
    inline void access(int x){for(int y=0;x;ch[x][1]=y,y=x,x=fa[x])splay(x);}
    inline void makeroot(int x){access(x),splay(x),flip(x);}
    inline void split(int x,int y){makeroot(x),access(y),splay(y);}
    inline int findroot(int x){access(x);for(splay(x);ch[x][0];x=ch[x][0]);splay(x);return x;}
    inline bool connect(int x,int y){return findroot(x)==findroot(y);}
    inline void link(int x,int y){makeroot(x),fa[x]=y;}
    inline void cut(int x,int y){split(x,y);if(ch[y][0]==x&&sz[y]==2)fa[x]=ch[y][0]=0,update(y);}
    int deledge(int x,int y){
        split(x,y);
        int id=mn[y];
        int a=e[id].first,b=e[id].second,d=n+id;
        cut(a,d),cut(b,d);
        return id;
    }
    void addedge(int x,int y,int id){
        int d=n+id;
        lct::sz[d]=1;
        lct::val[d]=lct::mn[d]=id;
        link(x,d),link(y,d);
    }
}
namespace sgt{
    int rt[N],ls[N*100],rs[N*100],sz[N*100],nod=0;
    void insert(int&o,int pre,int l,int r,const int&val){
        sz[o=++nod]=sz[pre]+1;
        if(l<r){
            const int mid=l+r>>1;
            if(val<=mid)
                insert(ls[o],ls[pre],l,mid,val),rs[o]=rs[pre];
            else
                insert(rs[o],rs[pre],mid+1,r,val),ls[o]=ls[pre];
        }
    }
    void add(int nw,int pre,int x){insert(rt[nw],rt[pre],0,m,x);}
    int query(int o,int pre,int l,int r,const int&L,const int&R){
        if(!o&&!pre)return 0;
        if(L<=l&&r<=R)return sz[o]-sz[pre];
        const int mid=l+r>>1;
        if(L<=mid&&mid<R)return query(ls[o],ls[pre],l,mid,L,R)+query(rs[o],rs[pre],mid+1,r,L,R);
        if(L<=mid)return query(ls[o],ls[pre],l,mid,L,R);return query(rs[o],rs[pre],mid+1,r,L,R);
    }
    int ask(int l,int r,int L,int R){return query(rt[r],rt[l-1],0,m,L,R);}
}
int main(){
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    cin>>n>>m>>q>>t;
    lct::mn[0]=lct::val[0]=inf;
    for(int i=1;i<=n;++i)lct::mn[i]=lct::val[i]=inf,lct::sz[i]=1;
    for(int i=1;i<=m;++i){
        int x,y;
        cin>>x>>y;
        e[i]=make_pair(x,y);
        if(x!=y&&lct::connect(x,y)){
            int pr=lct::deledge(x,y);
            sgt::add(i,i-1,pr);
        }else
            if(x!=y)
                sgt::add(i,i-1,0);
            else
                sgt::rt[i]=sgt::rt[i-1];
        if(x!=y)
        lct::addedge(x,y,i);
    }
    int ans=0;
    while(q--){
        int l,r;
        cin>>l>>r;
        if(t)
            l=(l+ans)%m+1,r=(r+ans)%m+1;
        if(l>r)swap(l,r);
        ans=n-sgt::ask(l,r,0,l-1);
        cout<<ans<<'\n';
    }
    return 0;
}