[CodeChef]GERALD07/[JZOJ4739]Ztxz16学图论

该博客详细介绍了如何应用莫队算法和并查集解决CodeChef上的GERALD07问题,这是一个涉及无向图联通块查询的竞赛题目。博主分析了使用莫队算法的思路,并提及了类似[JZOJ4663]Seq的方法。此外,还讨论了在线解法,即利用LCT(线段树)维护最大生成树,以达到更高的时间复杂度优化。最后,给出了代码实现部分。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目大意

一个 n 个点,m条边的无向图。有 q 对询问,每次询问如果只保留编号在[li,ri]的边,图中有多少个联通块。

1n,m,q2×105


题目分析

考虑使用莫队算法。求联通块个数的经典思路是并查集,但是并查集仅支持添加操作,所以我们要使用类似[JZOJ4663]Seq的方法,在处理同一个块的询问时,左指针位于块尾。使用一个并查集维护右指针右移的影响,每次处理询问的时候再使用另一个并查集维护左指针左移的影响(这个并查集“套”在前一个并查集上面),记录改变的位置,处理完询问之后暴力还原。思路是十分简单的,如果是第一次见这种姿势不太明白,可以去看看我上面说的那道题。
时间复杂度 O((q+m)mα(n))
其实本题可以在线。以编号为键值,使用 LCT 维护最大生成树,接着使用可持久化线段树搞一搞可以做到 O((q+m)log2n) 。但是由于本蒟蒻不会这个做法,于是在这里也只是口胡一下。如果想了解可以戳这里看官方题解。
Update(2016/09/03)
在线做法是以编号为键值,然后使用我们按照编号从小到大加边,使用 LCT 来维护各个连通块包含 1 i的最大生成树。
查询区间 [l,r] 的时候,我们可以先查询包含 [1,r] 的所有边的连通块个数,由于我们曾经维护了 [1,r] 的最大生成树,这个是很容易维护的。那么我们只需要看一下这棵最大生成树里面有多少条编号在 [1,l) 的边(主席树维护),原本连通块个数加上这些边的条数便是答案。


代码实现

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cctype>
#include <cmath>

using namespace std;

int read()
{
    int x=0,f=1;
    char ch=getchar();
    while (!isdigit(ch)) f=ch=='-'?-1:f,ch=getchar();
    while (isdigit(ch)) x=x*10+ch-'0',ch=getchar();
    return x*f;
}

const int N=200050;
const int M=200050;
const int Q=200050;

int fa[N],Fa[N],cg[N];
int n,m,q,bs,T;
bool mark[N];
int ans[Q];

struct D
{
    int l,r,id;
}qy[Q];

struct Edge
{
    int x,y;

    Edge (int x0=0,int y0=0){x=x0,y=y0;}
}es[M];

bool operator<(D x,D y){return (x.l-1)/bs<(y.l-1)/bs||(x.l-1)/bs==(y.l-1)/bs&&x.r<y.r;}

int lcur,rcur,now;

int getfather(int son){return fa[son]==son?son:fa[son]=getfather(fa[son]);}
int GetFather(int son){return Fa[son]==son?son:Fa[son]=GetFather(Fa[son]);}

void add(int x,bool tp)
{
    if (lcur>rcur) return;
    int u=es[x].x,v=es[x].y,fx=getfather(u),fy=getfather(v);
    if (tp)
    {
        if (!mark[fx]) mark[fx]=1,cg[++cg[0]]=fx;
        if (!mark[fy]) mark[fy]=1,cg[++cg[0]]=fy;
        int Fx=GetFather(fx),Fy=GetFather(fy);
        if (Fx!=Fy) now--,Fa[Fy]=Fx;
    }
    else if (fx!=fy) now--,fa[fy]=fx;
}

void recover(){for (;cg[0];cg[0]--) mark[Fa[cg[cg[0]]]=cg[cg[0]]]=0;}

void solve()
{
    bs=trunc(sqrt(m))+1,sort(qy+1,qy+1+q);
    for (int i=1,tmp,tail;i<=q;i++)
    {
        if (i==1||(qy[i].l-1)/bs!=(qy[i-1].l-1)/bs)
        {
            for (int j=1;j<=n;j++) Fa[j]=fa[j]=j;
            now=n,lcur=tail=min(((qy[i].l-1)/bs+1)*bs,m),rcur=0;
        }
        while (rcur<qy[i].r) add(++rcur,0);
        tmp=now;
        while (lcur>qy[i].l) add(--lcur,1);
        ans[qy[i].id]=now,now=tmp,recover(),lcur=tail;
    }
}

int main()
{
    freopen("gerald07.in","r",stdin),freopen("gerald07.out","w",stdout);
    for (T=read();T;T--)
    {
        n=read(),m=read(),q=read();
        for (int i=1,u,v;i<=m;i++) u=read(),v=read(),es[i]=Edge(u,v);
        for (int i=1;i<=q;i++) qy[i].l=read(),qy[i].r=read(),qy[i].id=i;
        solve();
        for (int i=1;i<=q;i++) printf("%d\n",ans[i]);
    }
    fclose(stdin),fclose(stdout);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值