Codechef MARCH14 GERALD07-莫队+并查集

传送门
这道题也是bzoj3514离线版
题意:
给你n个点,m条边,询问k个区间[L,R],求只保留[L,R]间的边,有多少个联通块。n,m,k<=200000

Solution:
既然是离线那么显然可以搞事[滑稽],询问区间有什么暴力方法呢?莫队!因为在图上所以每次操作不是 O1 ,感觉似乎可以用并查集的样子呢,算一下复杂度为 O(nnα) 也是可以接受的。但是并查集怎么做呢?一开始一直在考虑删边的问题,但是那就需要可持久化并查集,复杂度就不会特别优秀而且正确性不敢保证,这时候我们就要想办法避免掉删边的操作:我们回顾一下莫队算法的思想,按照左端点所在的块为第一关键字,右端点为第二关键字排序。这保证了左端点所在的块中加的边数是不固定的,后面的块中加的边数一定是递增的,那么我们就可以操作了:枚举每一块,求出左端点在这一块内的操作是那些,按照右端点排序,先用一个并查集 f1 维护不在当前枚举的这一块的边,然后用另一个并查集 f2 每次暴力维护左端点到当前块末端的边,对于每一块 f1 Onα 的,对于所有操作 f2 O(knα) 的,保证了复杂度,具体如何维护见代码。

#include<cstdio>
#include<iostream>
#include<cmath>
#include<algorithm>
using namespace std;
int fa[200010],f[200010],st[200010],cnt;
int n,m,qu,T,kn,nans;
struct Q{
    int l,r,pos,id;
}q[200010];
struct edg{
    int u,v;
}e[200010];
int pos[200010];
int ans[200010];
bool cmp(Q a,Q b)
{
    if (pos[a.l]==pos[b.l]) return a.r<b.r;
    else return a.l<b.l;
}
int find(int x)
{
    if (f[x]!=x) f[x]=find(f[x]);
    return f[x];
}
int find2(int x)
{
    if (fa[x]!=x) fa[x]=find2(fa[x]);
    return fa[x];
}
void baoli(int x)
{
    int u,v;
    ans[q[x].id]=nans;
    for (int i=q[x].l;i<=q[x].r;i++)
    {
        u=find2(e[i].u);v=find2(e[i].v);
        if (u!=v)
        {
            ans[q[x].id]--;
            st[++cnt]=u;
            fa[u]=v;
        }
    }
    for (int i=1;i<=cnt;i++) fa[st[i]]=st[i];
}
int main()
{
    scanf("%d",&T);
    while (T--)
    {
        scanf("%d%d%d",&n,&m,&qu);
        kn=sqrt(m);
        int sj=(m-1)/kn+1;
        for (int i=1;i<=n;i++) fa[i]=i;
        for (int i=1;i<=m;i++)
            pos[i]=(i-1)/kn+1;
        for (int i=1;i<=m;i++)
            scanf("%d%d",&e[i].u,&e[i].v);
        for (int i=1;i<=qu;i++)
            scanf("%d%d",&q[i].l,&q[i].r),q[i].id=i;
        sort(q+1,q+1+qu,cmp);
        int L,R,last=0,p,nr;

        for (int i=1;i<=sj;i++)
        {
            L=last+1;R=last;p=kn*i;nr=p;
            nans=n;
            while (pos[q[R+1].l]==i&&R<qu) R++;
            if (L>R) continue;
            for (int ii=1;ii<=n;ii++) f[ii]=ii;
            for (int nw=L;nw<=R;nw++)
            {
                if (q[nw].r<=p)
                {
                    baoli(nw);
                    continue;
                }
                int u,v;
                for (int j=nr+1;j<=q[nw].r;j++)
                {
                    u=find(e[j].u);
                    v=find(e[j].v);
                    if (u!=v)
                    {
                        nans--;
                        f[u]=v;
                    }
                }
                nr=q[nw].r;
                cnt=0;
                ans[q[nw].id]=nans;
                for (int j=q[nw].l;j<=p;j++)
                {
                    u=find(e[j].u);v=find(e[j].v);//注意这里是find
                    if (u!=v&&find2(u)!=find2(v))
                    {
                        ans[q[nw].id]--;
                        st[++cnt]=find2(u);
                        fa[find2(u)]=find2(v);
                    }
                }
                for (int ii=1;ii<=cnt;ii++) fa[st[ii]]=st[ii];
            }
            last=R; 
        }
        for (int i=1;i<=qu;i++)
            printf("%d\n",ans[i]);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值