hdu 4677 并查集合并(两个相邻区间并查集的合并)+分块算法 好题

题意:点数n(n <= 30000), 边数(m <= 90000)的图,询问 q(1<=q<=30000)。

对于每个询问(l, r),去掉(l,r)区间以外的所有点和其相关联的边,问剩下来的图的联通块的个数。

思路:分块+并查集

这题很容易想到分块, 难点是并查集的合并。

对询问离线分块排序以后,我们对  左端点在相同块号内的询问 一起处理。这些询问的右断点是递增的,

左端点在某个块内,我们对每个询问分成2个区域分别进行并查集处理,区域1:左端点所在的块的区域(并查集lp)

(点的个数sqrt(n))。区域2:除区域1以外的其它询问点(并查集tf)。由于右断点是递增的,区域2很容易用一个并查集处理。区域1对于每个询问,都重新处理一遍并查集。最后关键就是两个并查集的合并,由于并查集不能进行删除操作,我们需要做的是   利用并查集tf里面的信息而不破坏它,并且复杂度要符合题意。我们可以在维护并查集lp的同时让这两个并查集合并,即在并查集f中把我当前需要的点搬到并查集lp里面,而没有必要把并查集tf全部搬过来,而且这样做复杂度也不符合要求,用一个vis数组就可以做到

在实际处理的时候   并查集lp能处理到的点是整个询问的区域内的点。

//#pragma comment(linker, "/STACK:102400000")
#include
#include
#include
#include
#include
#include
#include<set>
#include
#include
#include
#include
#define tree int o,int l,int r
#define lson o<<1,l,mid
#define rson o<<1|1,mid+1,r
#define lo o<<1
#define ro o<<1|1
#define pb push_back
#define mp make_pair
#define ULL unsigned long long
#define LL long long
#define inf 0x7fffffff
#define eps 1e-7
#define N 30009
/*
知识:
1.两个并查集的合并:用vis数组标记元素是否已经合并了!(复杂度与find函数一致,很小)
*/
using namespace std;
int m,n,T,t,bit,ns,tp;
struct node
{
    int l,r,id,b;
    bool operator<(const node a)const
    {
        return ba.r);
    }
} q[N];
vector<int>g[N];
int ans[N],tf[N],lf[N],vis[N];
void init()
{
    for(int i=0; i<=n; i++)
    {
        g[i].clear();
    }
    memset(vis,-1,sizeof(vis));
}
void initset(int f[],int l,int r)
{
    while(l<=r)
    {
        f[l]=l;
        l++;
    }
}
int find(int f[],int x)
{
    return f[x]==x?x:f[x]=find(f,f[x]);
}
int unionone(int f[],int u,int v)
{
    int tu=find(f,u);
    int tv=find(f,v);
    f[tu]=tv;
    return tu!=tv;
}
int findtwe(int x,int k)
{
    if(vis[x]!=k)
    {
        lf[x]=tf[x];
        vis[x]=k;
    }
    return lf[x]==x?x:lf[x]=findtwe(lf[x],k);
}
int uniontwe(int u,int v,int k)
{
    int tu=findtwe(u,k);
    int tv=findtwe(v,k);
    lf[tu]=tv;
    return tu!=tv;
}
int fenkuai(int k,int &last)
{
    tp+=q[k].r-last+1;//必须记住上一次得到的集合的个数,WA了多次
    initset(tf,last,q[k].r);
    for(int i=last; i<=q[k].r; i++)
    {
        for(int j=0; j)
        {
            int v=g[i][j];
            if(v>=(q[k].b*bit+1)&&v<=q[k].r)
                    tp-=unionone(tf,i,v);
        }
    }

    int ans=tp+q[k].b*bit-q[k].l+1;/WA
    initset(lf,q[k].l,q[k].b*bit);//q[k].b*bit不会大于n
    for(int i=q[k].l; i<=q[k].b*bit; i++)vis[i]=k;
    for(int i=q[k].l; i<=q[k].b*bit; i++)
    {
        for(int j=0; j)
        {
            int v=g[i][j];
            if(v>=q[k].l&&v<=q[k].r)
                ans-=uniontwe(i,v,k);
        }
    }
    last=q[k].r+1;
    return ans;
}
int bfans(int l,int r)
{
    int ans=r-l+1;
    initset(lf,l,r);
    for(int i=l+1; i<=r; i++)
    {
        for(int j=0; j)
        {
            int v=g[i][j];
            if(v>=l&&v<=r)
                ans-=unionone(lf,i,v);
        }
    }
    return ans;
}

void solve()
{
    for(int i=0; i<m;)
    {
        int b=q[i].b,last=(q[i].b)*bit+1;
        tp=0;
        while(ib)
        {
            if(q[i].r<(q[i].b)*bit+1)
            {
                ans[q[i].id]=bfans(q[i].l,q[i].r);
            }
            else
            {
                ans[q[i].id]=fenkuai(i,last);
            }
            i++;
        }
    }
}
int main()
{
#ifndef ONLINE_JUDGE
    freopen("ex.in","r",stdin);
#endif
    int ncase=0;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d",&n,&m);
        init();
        for (int i=1; i<=m; ++i )
        {
            int x,y;
            scanf("%d%d",&x,&y);
            g[x].push_back(y);
            g[y].push_back(x);
        }
        bit=sqrt(n+0.5);
        ns=(n+bit-1)/bit;
        scanf("%d",&m);
        for(int i=0; ii)
        {
            scanf("%d%d",&q[i].l,&q[i].r);
            q[i].id=i;q[i].b=(q[i].l+bit-1)/bit;
        }
        sort(q,q+m);
        solve();
        printf("Case #%d:\n",++ncase);
        for(int i=0; ii)
            printf("%d\n",ans[i]);
    }
    return 0;
}
View Code

 

转载于:https://www.cnblogs.com/sbaof/p/3346267.html

你可能感兴趣的:(hdu 4677 并查集合并(两个相邻区间并查集的合并)+分块算法 好题)