hihocoder1629:Graph (分块+并查集)

来源:互联网 发布:姚明41分比赛数据 编辑:程序博客网 时间:2024/06/07 07:31

题目传送门: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;}
原创粉丝点击