JZOJ4739. Ztxz16学图论

题目大意

给定 N 个点,M条无向边, Q 个询问,每个询问给定(L,R),问连上第 LR 条边后,图中有多少联通块(询问之间互不影响)。

Data Constraint
N,M,Q200000

题解

有一种用LCT维护最小生成树的方法,然而我并不会。
考虑莫队算法。并查集的插入是非常简单的,但是删除就很难维护。观察莫队算法的性质,对于左端点在同一块的询问,它们的右端点必然是单调的,所以就可以维护右边的并查集,剩下的左边那部分我们就暴力查询,最后再恢复并查集。
直接恢复显然会T。考虑多开一个临时并查集 tp ,记右边维护的并查集为 fa
对于当前的一条边 (x,y) ,我们先在 fa 中找到它们的祖先 fx,fy ,然后在 tp 中找到 fx,fy 的祖先 ffx,ffy ,修改 tp ,最后在记录 tp 哪些位置发生修改,暴力赋为原值即可。这样就避免了对 fa 的修改。
时间复杂度: O(mm)

SRC

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std ;

#define N 200000 + 10
struct Edge {
    int x , y ;
} E[N] ;
struct Query {
    int L , R , h ;
} Q[N] ;

bool vis[N] ;
int fa[N] , nowfa[N] , S[N] ;
int Bel[N] , ans[N] ;
int n , m , Qn , Size ;
int Cnt ;

bool cmp( Query a , Query b ) { return Bel[a.L] < Bel[b.L] || ( Bel[a.L] == Bel[b.L] && a.R < b.R ) ; }

int Get( int x ) { return fa[x] == x ? x : fa[x] = Get(fa[x]) ; }
int GetNow( int x ) { return nowfa[x] == x ? x : nowfa[x] = GetNow(nowfa[x]) ; }

int main() {
    scanf( "%d%d%d" , &n , &m , &Qn ) ;
    Size = sqrt(m) ;
    for (int i = 1 ; i <= m ; i ++ ) {
        scanf( "%d%d" , &E[i].x , &E[i].y ) ;
        Bel[i] = i / Size + (i % Size != 0) ;
    }
    for (int i = 1 ; i <= Qn ; Q[i].h = i , i ++ ) scanf( "%d%d" , &Q[i].L , &Q[i].R ) ;
    sort( Q + 1 , Q + Qn + 1 , cmp ) ;
    int L = 0 , R = 0 ;
    for (int i = 1 ; i <= Qn ; i ++ ) {
        if ( Bel[Q[i].L] != Bel[L] ) {
            Cnt = n ;
            int st = Size * Bel[Q[i].L] + 1 ;
            for (int k = 1 ; k <= n ; k ++ ) fa[k] = nowfa[k] = k ;
            for (int k = st ; k <= Q[i].R ; k ++ ) {
                int fx = Get(E[k].x) ;
                int fy = Get(E[k].y) ;
                if ( fx == fy ) continue ;
                Cnt -- ;
                fa[fx] = fy ;
            }
        } else {
            int st = max( Size * Bel[Q[i].L] + 1 , R ) ;
            for (int k = st ; k <= Q[i].R ; k ++ ) {
                int fx = Get(E[k].x) ;
                int fy = Get(E[k].y) ;
                if ( fx == fy ) continue ;
                Cnt -- ;
                fa[fx] = fy ;
            }
        }
        S[0] = 0 ;
        int ori = Cnt ;
        int ed = min( Q[i].R , Size * Bel[Q[i].L] ) ;
        for (int k = Q[i].L ; k <= ed ; k ++ ) {
            int fx = Get(E[k].x) ;
            int fy = Get(E[k].y) ;
            if ( fx == fy ) continue ;
            int ffx = GetNow(fx) ;
            int ffy = GetNow(fy) ;
            if ( ffx == ffy ) continue ;
            Cnt -- ;
            nowfa[ffx] = ffy ;
            if ( !vis[ffx] ) vis[ffx] = 1 , S[++S[0]] = ffx ;
        }
        while ( S[0] ) {
            nowfa[S[S[0]]] = S[S[0]] ;
            vis[S[S[0]]] = 0 ;
            S[0] -- ;
        }
        ans[Q[i].h] = Cnt ;
        Cnt = ori ;
        L = Q[i].L , R = Q[i].R ;
    }
    for (int i = 1 ; i <= Qn ; i ++ ) printf( "%d\n" , ans[i] ) ;
    return 0 ;
}

以上.

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值