hihocoder1629:Graph (分块+并查集)

题目传送门:http://hihocoder.com/problemset/problem/1629


题目大意:给出一幅n个点,m条边的无向图,然后给出q组询问。每组询问给定一个区间[L,R],问[L,R]中有多少点对可以相互到达。可以到达的要求是只能走[L,R]中的点。不超过5组数据,n,m<=50000,q<=100000。


题目分析:这题应该算是bzoj4537的弱化版吧。

所谓分块,就是在暴力的基础上优化,所以我们先考虑怎么暴力。可以枚举一个左端点L,然后将右端点R从左往右扫,并用一个并查集维护答案。合并两个大小分别为x和y的集合时,对答案的新贡献就是x*y。然后离线回答[L,R]的询问。

上述方法的缺点就是要枚举的L太多,时间是 O(m2α(n)) 的。假设我们统计了每个点的度数,现在将度数和为 m 的一些点分成一块,然后将每个块的末尾作为关键点(意思是对于块[a,b],满足[a,b-1]的度数和小于 m ,[a,b]的度数和大于等于 m ,然后b为关键点),只枚举这些关键点作为左端点。

当我们枚举第i个关键点为左端点的时候,同时处理所有左端点在(第i-1个关键点,第i个关键点],右端点在第i个关键点及之后的询问,将这些询问按右端点从小到大排序。假设当前的询问区间是[L,R],则我们先将[第i个关键点,R]的边加进并查集,这个部分可以用启发式合并+路径压缩。然后将[L,第i个关键点)的边用启发式合并加进并查集(不能用路径压缩,因为要撤销),并用一个栈记录操作。处理完询问后将栈里的操作还原。

如果某个询问没有跨越任何一个关键点,就说明区间内的边数小于 m ,直接用启发式合并+路径压缩的并查集处理即可。处理完了之后再将修改了的元素暴力初始化。

那么这样时间复杂度是多少呢?由于启发式合并+路径压缩的并查集,运行时间非常小,可以认为接近均摊 O(1) 。那么总时间就是 nm+qmlog(n) 。这里要注意一个小细节:因为我们是按点分块的,所以如果有度数为0的点,要用链表跳过这个点。否则如果有很多度数为0的点连在一起,每一次询问就都要扫这些无用的点,就不能保证时间为 mlog(n)

但其实如果将块的大小控制在 mlog(n) ,使得存在 mlog(n) 个块的话,就能将时间降为 (n+q)mlog(n) 。然而一件很神奇的事情是,我第一次写代码的时候,将块的大小打成了 mlog(n) ,居然2200ms就过了。而我后来发现了这个问题,将大小改成了 mlog(n) ,就TLE了QAQ……


CODE:

#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;

const int maxn=50100;

struct edge
{
    int obj;
    edge *Next;
} e[maxn<<1];
edge *head[maxn];
int cur;

int num[maxn];
int a[maxn];
int Prev[maxn];
int cnt1;

int fst[maxn];
int id[maxn];
int cnt2;

struct data
{
    int L,R,Time;
} ask[maxn<<1];
int ans[maxn<<1];

int fa[maxn];
int Size[maxn];
int tp;

int fa1[maxn];
int Size1[maxn];
int tp1=0;

int sak[maxn];
int tail;

int t,n,m,q;
int step;

void Add(int x,int y)
{
    cur++;
    e[cur].obj=y;
    e[cur].Next=head[x];
    head[x]=e+cur;
}

bool Comp1(data x,data y)
{
    return x.L<y.L;
}

bool Comp2(data x,data y)
{
    return x.R<y.R;
}

int Up(int x)
{
    if (x==fa[x]) return x;
    return (fa[x]=Up(fa[x]));
}

void Merge(int x,int y)
{
    x=Up(x);
    y=Up(y);
    if (x==y) return;
    if (Size[x]<Size[y]) swap(x,y);
    fa[y]=x;
    tp+=( Size[x]*Size[y] );
    Size[x]+=Size[y];
}

int Find(int x)
{
    if (x==fa[x]) return x;
    return Find(fa[x]);
}

void Push(int x,int y)
{
    x=Find(x);
    y=Find(y);
    if (x==y) return;
    if (Size[x]<Size[y]) swap(x,y);
    fa[y]=x;
    tp+=( Size[x]*Size[y] );
    Size[x]+=Size[y];
    sak[++tail]=y;
}

void Clear()
{
    while (tail)
    {
        int x=sak[tail];
        Size[ fa[x] ]-=Size[x];
        tp-=( Size[x]*Size[ fa[x] ] );
        fa[x]=x;
        tail--;
    }
}

int Up1(int x)
{
    if (x==fa1[x]) return x;
    return (fa1[x]=Up1(fa1[x]));//一开始这里的Up1打成了Up!!!
}

void Merge1(int x,int y)
{
    x=Up1(x);
    y=Up1(y);
    if (x==y) return;
    if (Size1[x]<Size1[y]) swap(x,y);
    fa1[y]=x;
    tp1+=( Size1[x]*Size1[y] );
    Size1[x]+=Size1[y];
}

int main()
{
    freopen("1629.in","r",stdin);
    freopen("1629.out","w",stdout);

    scanf("%d",&t);
    while (t--)
    {
        scanf("%d%d%d",&n,&m,&q);
        cur=-1;
        for (int i=1; i<=n; i++) head[i]=NULL,num[i]=id[i]=Prev[i]=0;
        for (int i=1; i<=m; i++)
        {
            int x,y;
            scanf("%d%d",&x,&y);
            Add(x,y);
            Add(y,x);
            num[x]++;
            num[y]++;
        }

        cnt1=0;
        for (int i=1; i<=n; i++) if (num[i]) a[++cnt1]=i,Prev[i]=i;
        for (int i=1; i<=n; i++) if (!Prev[i]) Prev[i]=Prev[i-1];

        cnt2=0;
        step=(int)floor( sqrt( (double)(2*m)*(double)(log(n)/0.3) )+1e-5 );
        int now=0;
        for (int i=1; i<=n; i++)
        {
            now+=num[i];
            if (now>=step)
            {
                fst[++cnt2]=i;
                id[i]=cnt2;
                now=0;
            }
        }
        if (fst[cnt2]<n) fst[++cnt2]=n,id[n]=cnt2;
        for (int i=n; i>=1; i--) if (!id[i]) id[i]=id[i+1];

        for (int i=1; i<=n; i++) fa1[i]=i,Size1[i]=1;
        for (int i=1; i<=q; i++) scanf("%d%d",&ask[i].L,&ask[i].R),ask[i].Time=i;
        sort(ask+1,ask+q+1,Comp1);
        int last=1;
        for (int i=1; i<=cnt2; i++)
        {
            int x=fst[i],y=last;
            while ( y<=q && id[ ask[y].L ]<=i ) y++;
            if (last==y) continue;

            sort(ask+last,ask+y,Comp2);
            for (int j=1; j<=n; j++) fa[j]=j,Size[j]=1;
            tp=0;
            int ta=x-1;
            for (int j=last; j<y; j++) if (ask[j].R>=x)
            {
                while (ta<ask[j].R)
                {
                    ta++;
                    for (edge *p=head[ta]; p; p=p->Next)
                    {
                        int to=p->obj;
                        if ( x<=to && to<=ask[j].R ) Merge(ta,to);
                    }
                }

                tail=0;
                for (int k=Prev[x-1]; k>=ask[j].L; k=Prev[k-1])
                    for (edge *p=head[k]; p; p=p->Next)
                    {
                        int to=p->obj;
                        if ( ask[j].L<=to && to<=ask[j].R ) Push(k,to);
                    }
                ans[ ask[j].Time ]=tp;
                Clear();
            }
            else //当区间没有跨越任何一个关键点的时候,暴力计算答案!!!
            {
                for (int k=ask[j].L; k<=ask[j].R; k++)
                    for (edge *p=head[k]; p; p=p->Next)
                    {
                        int to=p->obj;
                        if ( ask[j].L<=to && to<=ask[j].R ) Merge1(k,to);
                    }
                ans[ ask[j].Time ]=tp1;
                for (int k=ask[j].L; k<=ask[j].R; k++)
                {
                    fa1[k]=k,Size1[k]=1;
                    for (edge *p=head[k]; p; p=p->Next)
                    {
                        int to=p->obj;
                        fa1[to]=to,Size1[to]=1;
                    }
                }
                tp1=0;
            }
            last=y;
        }

        for (int i=1; i<=q; i++) printf("%d\n",ans[i]);
    }

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值