P5227 [AHOI2013] 连通图

发布时间 2023-11-04 15:26:32作者: 2020ljh

P5227 [AHOI2013] 连通图

(膜拜并感谢 @Genius_Z 给予本题解思路)

因为这一题是线段树合并板题,所以我们使用 LCT。

考虑最暴力的想法,维护一棵树和很多不在树上的边,每一次询问就暴力拆边,从那些没有被禁的边里面补到树上。

这个时候我们就会发现,每次 “补边” 的操作非常的消耗时间。

于是我们就可以想到利用一些 LCT 的性质优化一下。观察题目的本质,其实就是把每一个边按照询问的时间分成很多段 “启用区间” ,那我们 “补边” 的本质又是什么呢?

假设我们断了一条边 a,启用了另一条边 b,那么就说明当前的询问时间 超过了 a 目前的启用区间,而并没有超过 b 的启用区间。那么只要我使树上的边的启用区间右端点尽量的大,是不是就不需要补边了?

考虑对边额外标定一些边权 \(vl_i\),表示下一次被禁用的询问时间,也就是启用区间的右端点 +1,然后跑最大生成树,每次询问就断边,修改预处理出来的新边权,再加边即可。

至此,我们就还剩下一个问题:我不会维护虚子树的 size。

这其实也非常好解决,只需要在涉及虚实儿子更改的地方(access 操作、link 操作)统计一下虚儿子的 size 之和。当然还有重中之重,为了 cut 操作的方便,我们需要在其中多split一下,确保两个点是实儿子与父亲的关系,不然我们不知道到底是实儿子还是虚儿子。

现在,我们可以用 LCT 切了这道题霸气离场,然后回家偷偷补线段树分治的知识了。

代码 3K,不多
#include<bits/stdc++.h>
using namespace std;
const int N=4e5+10,inf=1e9+10;
int tg[N],s[N][2],f[N],st[N],top,n,m,k,vl[N],sz[N],xu[N],a[N],a1,a2;
void up(int x){
    sz[x]=sz[s[x][0]]+sz[s[x][1]]+xu[x]+(x<=n);
    if(x>n) vl[x]=x;
    else vl[x]=0;
    if(s[x][0]&&a[vl[s[x][0]]]<a[vl[x]]) vl[x]=vl[s[x][0]];
    if(s[x][1]&&a[vl[s[x][1]]]<a[vl[x]]) vl[x]=vl[s[x][1]];
}
void rev(int x){swap(s[x][0],s[x][1]),tg[x]^=1;}
void down(int x){if(tg[x]) rev(s[x][0]),rev(s[x][1]);tg[x]=0;}
int get(int x){return s[f[x]][1]==x;}
int nrt(int x){return s[f[x]][0]==x||s[f[x]][1]==x;}
void rot(int x){
    int y=f[x],z=f[y],o=get(x);
    if(nrt(y)) s[z][get(y)]=x;
    f[x]=z;
    s[y][o]=s[x][o^1],f[s[x][o^1]]=y;
    s[x][o^1]=y,f[y]=x;
    up(y),up(x);
}
void upd(int x){
    st[top=1]=x;
    while(nrt(x)) st[++top]=x=f[x];
    while(top) down(st[top--]);
}
void splay(int x){
    upd(x);
    for(int y;y=f[x],nrt(x);rot(x)) if(nrt(y)) rot(get(x)^get(y)?x:y);
    up(x);
}
void access(int x){for(int y=0;x;x=f[y=x]) splay(x),xu[x]+=sz[s[x][1]],s[x][1]=y,xu[x]-=sz[y],up(x);}
void make(int x){access(x),splay(x),rev(x);}
int find(int x){
    access(x),splay(x),down(x);
    while(s[x][0]) down(x=s[x][0]);
    splay(x);
    return x;
}
void split(int x,int y){make(x),access(y),splay(y);}
void link(int x,int y){
    make(y);
    f[y]=x,xu[x]+=sz[y],up(x); 
}
void cut(int x,int y){
    split(y,x);
    s[x][0]=f[y]=0,up(x);
}
struct node{
    int x,y,z,p;
}u,kk[N];
vector<node>d[N];
int nxt[2][N];
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++) scanf("%d%d",&u.x,&u.y),u.z=i+n,kk[i]=u;
    scanf("%d",&k);
    for(int i=1;i<=k;i++){
        int oo,uu,v;
        scanf("%d",&oo);
        for(int j=1;j<=oo;j++){
            scanf("%d",&uu),d[i].push_back(kk[uu]),v=kk[uu].z;
            if(nxt[0][v]) d[nxt[0][v]][nxt[1][v]].p=i;
            else a[v]=i;
            nxt[0][v]=i,nxt[1][v]=j-1;
        } 
    }
    for(int i=n+1;i<=n+m;i++){
        if(nxt[0][i]) d[nxt[0][i]][nxt[1][i]].p=inf;
        if(!a[i]) a[i]=inf;
    }
    for(int i=0;i<=n;i++) a[i]=inf;
    for(int i=1;i<=m;i++){
        node j=kk[i];
        make(j.x);
        if(find(j.y)==j.x){
            split(j.y,j.x);
            int pp=vl[j.x];
            if(a[pp]>=a[j.z]) continue;
            cut(pp,kk[pp-n].x),cut(pp,kk[pp-n].y);
        }
        link(j.z,j.x),link(j.z,j.y);
    }
    for(int i=1;i<=k;i++){
        for(node j:d[i]){
            make(j.x);
            if(find(j.y)==j.x){
                up(j.z);
                if(!f[j.z]&&!sz[j.z]) continue;
                cut(j.z,j.x),cut(j.z,j.y);
            }
        }
        access(1),splay(1);
        if(sz[1]==n) puts("Connected");
        else puts("Disconnected");
        for(node j:d[i]){
            make(j.x),a[j.z]=j.p;
            if(find(j.y)==j.x){
                split(j.y,j.x);
                int pp=vl[j.x];
                if(a[pp]>=j.p) continue;
                cut(pp,kk[pp-n].x),cut(pp,kk[pp-n].y);
            }
            link(j.z,j.x),link(j.z,j.y);
        }
    }
}