并查集总结篇

1、模板题 poj1611the suspects

每个组内的人,同一个组内都是感染者,问与“0”号人有关的有多少人

#include <iostream>
#include<cstdio>
using namespace std;
const int MAXN = 1000100;
struct DS
{
    int f[MAXN];
    void init(int n)
    {
        for(int i=0;i<n;i++)
            f[i]=i;
    }
    int ff(int x)               ///int father (a);
    {
        ///查找父亲并压缩路径
        if(f[x]!=x)
            f[x]=ff(f[x]);
        return f[x];
    }
    void join(int a,int b)      ///void union (a, b);
    {
        f[ff(a)]=f[ff(b)];
    }
    int find(int a,int b)
    {
        return ff(a)==ff(b);
    }
}soul;

int main()
{
   // freopen("cin.txt","r",stdin);
    int n,m,a,b,c,t,num[100000];
    while(cin>>n>>m)
    ///n:人数    m:操作数
    {
        if(m==0&&n==0) break;
        soul.init(n);
        while(m--)
        {
            cin>>t;
            for(int i=1;i<=t;i++) cin>>num[i];
            for(int i=1;i<t;i++)
             soul.join(num[i],num[i+1]);
        }
        int q=soul.ff(0);
        int count=1;
        for(int i=1;i<n;i++)
         if(q==soul.ff(i)) count++;
        cout<<count<<endl;
    }
    return 0;
}

2、初级带权并查集
poj2492bug's life

题意:给出每对虫子的互相吸引关系,只有异性才会相互吸引,问虫子当中有没有同性恋==

解决方法:添加一个数组表示每个虫子的性别,注释加讲解

/* 
关于并查集,注意两个概念:按秩合并、路径压缩。
1、按秩合并
由于并查集一般是用比较高效的树形结构来表示的,按秩合并的目的就是防止产生退化的树(也就是类似链表的树),
用一个数组记录各个元素的高度(也有的记录各个元素的孩子的数目,具体看哪种能给解题带来方便),
然后在合并的时候把高度小的嫁接到高度大的上面,从而防止产生退化的树。
2、路径压缩
而另一个数组记录各个元素的祖先,这样就防止一步步地递归查找父亲从而损失的时间。因为并查集只要搞清楚各个元素所在的集合,
而区分不同的集合我们用的是代表元素(也就是树根),所以对于每个元素我们只需保存其祖先,从而区分不同的集合。
而我们这道题并没有使用纯正的并查集算法,而是对其进行了扩展,
我们并没有使用“1、按秩合并”(当然你可以用,那样就需要再开一个数组)
我们从“1、按秩合并”得到启示,保存“秩”的数组保存的是    元素相对于父节点的关系  ,我们岂不可以利用这种关系
(即相对于父节点的不同秩值来区分不同的集合),从而可以把两个集合合并成一个集合。
(注:此代码 relation=0 代表 和父节点同一性别)
*/


#include<stdio.h>
int father[2005];
int relation[2005];

int find_father(int i)
{
    int t;
    if(father[i]==i)
        return i;

    //计算相对于新的父节点(即根)的秩,relation[t]是老的父节点相对于新的父节点(即根)的秩,relation[i]是i元素相对于老的父节点的秩,
    //类似于物理里的相对运动,得到的r[i]就是相对于新的父节点(即根)的秩。而且这个递归调用不会超过两层
    t=father[i];    
    father[i]=find_father(father[i]);
    relation[i]=(relation[i]+relation[t]+1)%2;   //注意递归中把这棵树relation中的的值都更新一遍,这句的顺序 不能 和上一句 调换位置
    // relation[a]的改变是伴随着father[a]的改变而更新的(有father改变就有relation改变),要是father改变了,而relation未改变,此时的relation就记录了一个错误的值,
    //father未改变(即使实际的father已不是现在的值,但只要father未改变,relation的值就是“正确”的,认识到这点很重要。)
    return father[i];
}

void merge(int a,int b)
{
    int x,y;
    x=find_father(a);
    y=find_father(b);
    father[x]=y;
    relation[x]=(relation[b]-relation[a])%2;//relation[a]+relation[x]与relation[b]相对于新的父节点必须相差1个等级,因为他们不是gay
}                                            //x下边的节点不用改,因为查找的时候会自动更新

int main()
{
    int T,m,n,i,j,a,b,flag;
    scanf("%d",&T);
    for(i=1;i<=T;++i)
    {
        flag=0;
        scanf("%d%d",&n,&m);
        for(j=1;j<=n;++j)       //初始化
        {
            father[j]=j;
            relation[j]=1;
        }
        for(j=1;j<=m;++j)
        {
            scanf("%d%d",&a,&b);
            if(find_father(a)==find_father(b))
            {
            //    if(relation[a]!=(relation[b]+1)%2)
                if(relation[a]==relation[b])            //说明是同性
                    flag=1;
            }
            else
                merge(a,b);
        }
        if(flag)
            printf("Scenario #%d:\nSuspicious bugs found!\n\n",i);
        else
            printf("Scenario #%d:\nNo suspicious bugs found!\n\n",i);
    }
    return 0;
}

3、带权并查集经典题:poj1182食物链

我说不明白==参考:http://blog.csdn.net/c0de4fun/article/details/7318642

4、并查集水题 挨着就算相交,给定某个线段,询问这一团的有多少个

HDU 1558 Segment set 并查集

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int pre[1010],sum[1010];
struct point{
	double x,y;
};
struct EDGE{
	point a,b;
} edge[1010];
int E;//边数
int Find(int x){
	return x==pre[x]? x:pre[x]=Find(pre[x]);
}
void Merge(int a,int b){
	int x=Find(a),y=Find(b);
	if(x!=y){
		pre[y]=x;
		sum[x]+=sum[y];
	}
}
double xmult(point a,point b,point c){//大于零代表a,b,c左转
	return (b.x-a.x)*(c.y-a.y)-(b.y-a.y)*(c.x-a.x);
}
bool OnSegment(point a,point b,point c){		//a,b,c共线时有效
	return c.x>=min(a.x,b.x)&&c.x<=max(a.x,b.x)&&c.y>=min(a.y,b.y)&&c.y<=max(a.y,b.y);
}
bool Cross(point a,point b,point c,point d){//判断ab 与cd是否相交
	double d1,d2,d3,d4;
	d1=xmult(c,d,a);
	d2=xmult(c,d,b);
	d3=xmult(a,b,c);
	d4=xmult(a,b,d);
	if(d1*d2<0&&d3*d4<0)	return 1;
	else	if(d1==0&&OnSegment(c,d,a))	return 1;
	else	if(d2==0&&OnSegment(c,d,b))	return 1;
	else	if(d3==0&&OnSegment(a,b,c))	return 1;
	else	if(d4==0&&OnSegment(a,b,d))	return 1;
	return 0;
}
int main()
{
	int i,j,k,T,n;
	char s[10];
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d",&n);E=0;
		for(i=1;i<=n;i++)	pre[i]=i,sum[i]=1;
		for(i=1;i<=n;i++)
		{
			scanf("%s",s);
			if(s[0]=='P'){
				E++;
				scanf("%lf%lf%lf%lf",&edge[E].a.x,&edge[E].a.y,&edge[E].b.x,&edge[E].b.y);
				for(j=1;j<E;j++)
					if(Find(E)!=Find(j)&&Cross(edge[E].a,edge[E].b,edge[j].a,edge[j].b))	Merge(E,j);
			}
			else	if(s[0]=='Q'){
				scanf("%d",&k);
				printf("%d\n",sum[Find(k)]);
			}
		}
		if(T)	printf("\n");
	}
	return 0;
}
5、并查集浇灌农田:

HDU 1198 Farm Irrigation 并查集

给出每种小单元的上下左右联通情况,求最后整个农田分几块?把每种田地的四个边转化成数组形式,相邻的相连就用并查集

#include <iostream>
#include<cstdio>
#include<cstring>
using namespace std;
bool type[15][4]={{1,0,0,1},{1,1,0,0},{0,0,1,1},{0,1,1,0},
{1,0,1,0},{0,1,0,1},{1,1,0,1},{1,0,1,1},{0,1,1,1},{1,1,1,0},{1,1,1,1}};
int f[300000],n,m;
char c;
int num[100][100];
void init(int n){
    for(int i=1;i<=n;i++) f[i]=i;
}
int find(int x)
{
    if(x==f[x]) return x;
    int tmp=f[x]; f[x]=find(f[x]);
    return f[x];
}
int main()
{
   //freopen("cin.txt","r",stdin);
    while(~scanf("%d%d",&m,&n))
    {
        if(m==-1&&n==-1) break;
        init(n*m);
        for(int i=1;i<=m;i++)
        {
            for(int j=1;j<=n;j++)
            {
              cin>>c;
              num[i][j]=c-65;
              //cout<<num[i][j]<<" ";
            }
           // cout<<endl;
        }
        int count=n*m;
        for(int i=1;i<=m;i++)
            for(int j=1;j<n;j++)
            {
                if(type[num[i][j]][1]&&type[num[i][j+1]][3])
                {
                    int fx=find(i*n-n+j),fy=find(i*n-n+j+1);
                    if(fx!=fy) {f[fx]=fy;count--;}
                }
            }
        for(int i=1;i<=n;i++)
            for(int j=1;j<m;j++)
            {
                if(type[num[j][i]][2]&&type[num[j+1][i]][0])//就是这里 这里这里~~
                {
                    int fx=find(j*n-n+i),fy=find(j*n+i);
                    if(fx!=fy) {f[fx]=fy;count--;}
                }
            }


        cout<<count<<endl;
    }
    return 0;
}

6、

hdu1272小希的迷宫【并查集基础】

题意:问连接两条边是否有环,这个并查集的题用到了find函数的返回值,两个即将连接的点返回值如果相同,那么就说明要成环了!话说是不是好多图论题也可以这么搞呢?

#include <iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int a[100005],x,y,count,maxn,edge,point;
bool vis[100005],mark;
int find(int x)
{
    if(x!=a[x]) a[x]=find(a[x]);
    return a[x];
}
void addto(int x,int y)
{
    x=find(x),y=find(y);
    if(x==y) {
        mark=0;
        return ;
    }
    a[y]=a[x];
    return ;
}
void cal()
{
    for(int i=1;i<=maxn;i++)
    {
        if(vis[i])
        {
            if(a[i]==i) count++;
           // point++;
           if(count>1) mark=0;
        }
    }
}

int main()
{
   // freopen("cin.txt","r",stdin);
    while(~scanf("%d%d",&x,&y))
    {
        if(x==-1&&y==-1) break;
        if(x==0&&y==0)
        {
            printf("Yes\n");
            continue;
        }
        memset(vis,0,sizeof(vis));
        for(int i=1;i<=100000;i++) a[i]=i;
        mark=1;
        count=0;
        maxn=0;
       // point=0;
        addto(x,y);
        vis[x]=1;
        vis[y]=1;
        if(maxn<x) maxn=x;
        if(maxn<y) maxn=y;
         while(~scanf("%d%d",&x,&y))
        {
            if(x==0&&y==0) break;///
            addto(x,y);
            vis[x]=1;
            vis[y]=1;
            if(maxn<x) maxn=x;
            if(maxn<y) maxn=y;
        }
        cal();
        if(mark==1) printf("Yes\n");
        else printf("No\n");
    }
    return 0;
}

7、

hdu3461Code Lock【并查集+快速幂】

这个题的题意晦涩难懂,总的做法就是每次将两个集合合并(原来两个点就在一个集合内的不算)合并一次,一个最开始是等于长度的变量--,最后对这个变量^26取模(快速幂)

#include <iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define maxn 10000005
#define mod 1000000007
int f[maxn],n,m,l,r,count;
bool vis[maxn];
int find(int x)
{
    if(x!=f[x]) f[x]=find(f[x]);
    return f[x];
}
void addto(int x,int y)
{
    x=find(x),y=find(y);
    if(x==y) return ;
    f[x]=y;
    count++;
}
long long multi(int x)
{
    long long ans=1,tmp=26;
    while(x)
    {
        if(x&1) ans=(ans*tmp)%mod;
        tmp=(tmp*tmp)%mod;
        x>>=1;
    }
    return ans;
}
int main()
{
   // freopen("cin.txt","r",stdin);
    while(~scanf("%d%d",&n,&m))
    {
        for(int i=1;i<=n+1;i++) f[i]=i;
        count=0;
        while(m--)
        {
            scanf("%d%d",&l,&r);
            addto(l,r+1);
            vis[l]=1,vis[r+1]=1;
        }
        /*for(int i=1;i<=n+1;i++)
        {
            if(vis[i]&&f[i]==i) count++;
        }*/
        printf("%lld\n",multi(n-count)%mod);
    }
    return 0;
}

8、

poj3177Redundant Paths【构造双连通分量:并查集缩点 模板】

第六个题刚刚说完图论,这就来了==本题意在求出加入多少边可以构成双连通分量,而 构造双连通分量的加边数=(原图的叶节点数+1)/2,用并查集缩点,枚举每个桥,[团]++,=1说明是叶子节点

#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#include <stack>

using namespace std;

const int N=5006;
vector<int>G[N];
struct bridge
{
    int u,v;
}bg[2*N];
int vis[N],low[N],dfn[N],Time;
int fa[N],deg[N];
int n,m,cnt;
void init()
{
    for(int i=0;i<n;i++) G[i].clear();
    memset(dfn,0,sizeof(dfn));
    memset(low,0,sizeof(low));
    memset(vis,0,sizeof(vis));
    memset(deg,0,sizeof(deg));
    for(int i=1;i<=n;i++) fa[i]=i;
    cnt=Time=0;
}
int findset(int x)
{
    if(x!=fa[x])
        fa[x]=findset(fa[x]);
    return fa[x];
}
void Tarjan(int u,int father)
{
    low[u] = dfn[u] = ++Time;
    vis[u] = 1;
    for(int i=0;i<G[u].size();i++)
    {
        int v = G[u][i];
        if(v == father)
            continue;
        if(!vis[v])
        {
            Tarjan(v,u);
            low[u] = min(low[u],low[v]);
            if(low[v] > dfn[u])        //u->v为桥
                bg[cnt].u = u,bg[cnt++].v = v;
            else   //否则,u,v同属一个连通分量,合并
            {
                int fx = findset(u);
                int fy = findset(v);
                if(fx != fy)
                    fa[fx] = fy;
            }
        }
        else
            low[u] = min(low[u],dfn[v]);
    }
}

int main()
{
   // freopen("cin.txt","r",stdin);
    while(~scanf("%d%d", &n, &m))
    {
        init();
        for(int i=0;i<m;i++)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            G[u].push_back(v);
            G[v].push_back(u);
        }
        Tarjan(1,-1);
        for(int i=0;i<cnt;i++)
        {
            int fx=findset(bg[i].u);
            int fy=findset(bg[i].v);
            deg[fx]++;
            deg[fy]++;
        }
        int leaf=0;
        for(int i=1;i<=n;i++)
            if(deg[i]==1)
                leaf++;
        printf("%d\n",(leaf+1)/2);
    }
    return 0;

}
9、

hdu5606 bestcoder#68 div2tree【并查集】

有一个树(<span style=""><span class="katex" style="font-size:1.21em; font-family:KaTeX_Main; line-height:1.2; white-space:nowrap"><span class="katex-mathml" style="position:absolute; padding:0px; border:0px; height:1px; width:1px; overflow:hidden">n</span><span class="katex-html" style="display:inline-block"><span class="strut" style="display:inline-block; height:0.43056em"></span><span class="strut bottom" style="display:inline-block; height:0.43056em; vertical-align:0em"></span><span class="base textstyle uncramped" style="display:inline-block"><span class="mord mathit" style="font-family:KaTeX_Math;font-style:italic">n</span></span></span></span></span>个点, <span style=""><span class="katex" style="font-size:1.21em; font-family:KaTeX_Main; line-height:1.2; white-space:nowrap"><span class="katex-mathml" style="position:absolute; padding:0px; border:0px; height:1px; width:1px; overflow:hidden">n-1</span><span class="katex-html" style="display:inline-block"><span class="strut" style="display:inline-block; height:0.64444em"></span><span class="strut bottom" style="display:inline-block; height:0.72777em; vertical-align:-0.08333em"></span><span class="base textstyle uncramped" style="display:inline-block"><span class="mord mathit" style="font-family:KaTeX_Math;font-style:italic">n</span><span class="mbin" style="margin-left:0.22222em">−</span><span class="mord" style="margin-left:0.22222em">1</span></span></span></span></span>条边的联通图),点标号从<span style=""><span class="katex" style="font-size:1.21em; font-family:KaTeX_Main; line-height:1.2; white-space:nowrap"><span class="katex-mathml" style="position:absolute; padding:0px; border:0px; height:1px; width:1px; overflow:hidden">1</span><span class="katex-html" style="display:inline-block"><span class="strut" style="display:inline-block; height:0.64444em"></span><span class="strut bottom" style="display:inline-block; height:0.64444em; vertical-align:0em"></span><span class="base textstyle uncramped" style="display:inline-block"><span class="mord" style="">1</span></span></span></span></span>~<span style=""><span class="katex" style="font-size:1.21em; font-family:KaTeX_Main; line-height:1.2; white-space:nowrap"><span class="katex-mathml" style="position:absolute; padding:0px; border:0px; height:1px; width:1px; overflow:hidden">n</span><span class="katex-html" style="display:inline-block"><span class="strut" style="display:inline-block; height:0.43056em"></span><span class="strut bottom" style="display:inline-block; height:0.43056em; vertical-align:0em"></span><span class="base textstyle uncramped" style="display:inline-block"><span class="mord mathit" style="font-family:KaTeX_Math;font-style:italic">n</span></span></span></span></span>,树的边权是<span style=""><span class="katex" style="font-size:1.21em; font-family:KaTeX_Main; line-height:1.2; white-space:nowrap"><span class="katex-mathml" style="position:absolute; padding:0px; border:0px; height:1px; width:1px; overflow:hidden">0</span><span class="katex-html" style="display:inline-block"><span class="strut" style="display:inline-block; height:0.64444em"></span><span class="strut bottom" style="display:inline-block; height:0.64444em; vertical-align:0em"></span><span class="base textstyle uncramped" style="display:inline-block"><span class="mord" style="">0</span></span></span></span></span>或<span style=""><span class="katex" style="font-size:1.21em; font-family:KaTeX_Main; line-height:1.2; white-space:nowrap"><span class="katex-mathml" style="position:absolute; padding:0px; border:0px; height:1px; width:1px; overflow:hidden">1</span><span class="katex-html" style="display:inline-block"><span class="strut" style="display:inline-block; height:0.64444em"></span><span class="strut bottom" style="display:inline-block; height:0.64444em; vertical-align:0em"></span><span class="base textstyle uncramped" style="display:inline-block"><span class="mord" style="">1</span></span></span></span></span>.求离每个点最近的点个数(包括自己).
距离是0的。两个团连在一起,维护以某点为根节点的点的个数

#include <iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int n,t,a[100005],num[100005],cnt;
int fnd(int x)
{
    if(x!=a[x]) a[x]=fnd(a[x]);
    return a[x];
}
void addto(int x,int y)
{
    x=fnd(x),y=fnd(y);
    if(x==y) {
        return ;
    }
    a[y]=a[x];
    num[x]+=num[y];
    return ;
}
int main()
{
  //  freopen("cin.txt","r",stdin);
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d",&n);
        cnt=0;
        for(int i=1;i<=n;i++) {num[i]=1;a[i]=i;}
        for(int i=1;i<n;i++)
        {
            int u,v,q;
            scanf("%d%d%d",&u,&v,&q);
            if(q==0)  addto(u,v);
        }
        //cout<<"44544"<<endl;
        cnt=0;
        for(int i=1;i<=n;i++)
        {
            if(a[i]==i&&(num[i]&1)) cnt^=num[i];
        }
        printf("%d\n",cnt);
    }
    return 0;
}

10、

hdu3018Ant Trip【欧拉道路数量 并查集】

题意:给出一个图,问几笔画才能经过所有边 连通图有一个性质:其需要画的笔数=度数为奇数的点数除以2,那么由于给出的图并没有说明是否是连通图,我们需要用并查集来维护连通图,并且忽略单点的“子图” 所以说就和第8题一样了

#include <iostream>
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
int a[100005],deg[100005],odd[100005];
bool used[100005];
int fnd(int x)
{
    if(x!=a[x]) a[x]=fnd(a[x]);
    return a[x];
}
void addto(int x,int y)
{
    x=fnd(x),y=fnd(y);
    if(x==y) {
        return ;
    }
    a[y]=a[x];
    return ;
}
int main()
{
   // freopen("cin.txt","r",stdin);
    int n,m,x,y,cnt;
    while(~scanf("%d%d",&n,&m))
    {
        for(int i=1;i<=n;i++) {a[i]=i;deg[i]=0;used[i]=false;odd[i]=0;}
        for(int i=0;i<m;i++)
        {
            scanf("%d%d",&x,&y);
            deg[x]++;deg[y]++;
            x=fnd(x);y=fnd(y);
            if(x!=y)addto(x,y);
        }
        vector<int>v;
        for(int i=1;i<=n;i++)
        {
            int f=fnd(i);
            if(!used[f])
            {
                v.push_back(f);
                used[f]=1;//!!!
            }
            if(deg[i]&1)
            odd[f]++;
        }
        cnt=0;
        for(int i=0;i<v.size();i++)
        {
            int k=v[i];
            if(deg[k]==0) continue;
            if(odd[k]==0) cnt++;
            else cnt+=odd[k]/2;
        }
        printf("%d\n",cnt);
    }
    return 0;
}



本来还有两个题的,离线版的lca和左偏堆,

hdu2586How far away ?【LCA tarjan求最短距离】

hdu1512 Monkey King【左偏堆、并查集】

和并查集的关系不大,就没写


你可能感兴趣的:(ACM,并查集)