并查集小练

本文精选并查集算法的经典应用案例,涵盖STL结合、立方体堆叠、食物链模拟等多个典型问题,并提供详细题解及代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.题目:[Noi2015]程序自动分析(STL+并查集)

题解:

先把该连的连起来,然后在判断不该相等的相不相等。但是这样有个问题(不然就是普及-了喂)i和j的范围1e9,但是n只有1e6,我们知道总点数最多只有2*n个,所以这个时候需要离散啦,这里运用了一些STL

代码:

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
struct ask{int x,y,e,p1,p2;}que[1000005];
int fa[2000005],a[2000005];
int find(int x)
{
	if (fa[x]!=x) fa[x]=find(fa[x]);
	return fa[x];
}
int main()
{
	int T;
	scanf("%d",&T);
	while (T--)
	{
		int n,i,cnt=0;
		bool fff=false;
		scanf("%d",&n);
		for (i=1;i<=n*2;i++) fa[i]=i;
		for (i=1;i<=n;i++)
		{
			scanf("%d%d%d",&que[i].x,&que[i].y,&que[i].e);
			a[++cnt]=que[i].x; a[++cnt]=que[i].y;
		}
		sort(a+1,a+cnt+1);  
		int tot=unique(a+1,a+cnt+1)-a-1;
		for (i=1;i<=n;i++)
		{
			que[i].p1=upper_bound(a+1,a+cnt+1,que[i].x)-a-1;//在a这个升序数组中查找 最小的 比que[i].x这个数大的位置,起到离散化的作用 
			que[i].p2=upper_bound(a+1,a+cnt+1,que[i].y)-a-1;
		}	
		for (i=1;i<=n;i++)
		  if (que[i].e)
		  {
			int a=find(que[i].p1),b=find(que[i].p2);
			if (a!=b) fa[a]=b;
		  }
		for (i=1;i<=n;i++) 
		  if (!que[i].e && find(que[i].p1)==find(que[i].p2))
		  {
		  	printf("NO\n"); fff=true; break;
		  }
		if (fff) continue;  
		printf("YES\n");
	}
}

2.题目:Cube Stacking

题意:有n个立方体,初始时每个立方体都在独立的一个栈里,支持两个操作:

1.Move a b 把包含立方体a的栈整体移动到包含立方体b的栈顶   2.Count a,询问立方体a下方有多少个立方体。N<=30000

题解:这也是一道很有意思的题目了

主要就是三个数组的设置

f[k]立方体k所属栈的栈底元素;cnt[k]为立方体k到所属栈底的元素个数;num[k]为k所属栈的总数量 

代码:

#include <cstdio>
#include <iostream>
#include <cstring>
#define N 30005
using namespace std;
int f[N],cnt[N],num[N];
//f[k]立方体k所属栈的栈底元素;cnt[k]为立方体k到所属栈底的元素个数;
//num[k]为k所属栈的总数量 
int find(int x)
{
	if (f[x]!=x)
	{
		int fa=f[x];
		f[x]=find(fa);
	    cnt[x]+=cnt[fa]; 
	}
    return f[x];
}
void merge(int x,int y)
{
	int a=find(x),b=find(y);
	f[a]=b;
	cnt[a]=num[b];
	num[b]+=num[a];
}
int main()
{
	int n,i;
	for (i=1;i<=30000;i++)
	  f[i]=i,num[i]=1;
	scanf("%d",&n);
	while (n--)
	{
		char c;int x,y;
		while (c!='M' && c!='C') c=getchar();
	    if (c=='M')
	    {
	    	scanf("%d%d",&x,&y);
	    	merge(x,y);
		}
		else
		{
			scanf("%d",&x);
			int ff=find(x);
			printf("%d\n",cnt[x]);
		}
		if (n) c=getchar();
	}
}

3.题目:食物链

题解:

我们可以开3*n的数组,设x为本体,x+n为食物,x+2*n是天敌,对于1操作来说,x不能是y的食物,也不能是天敌,同理y也是;对于2操作来说,x不能是y的同类和食物。

代码:

#include <cstdio>
#define N 150005
using namespace std;
int fa[N];
int find(int x)
{
	if (fa[x]!=x) fa[x]=find(fa[x]);
	return fa[x];
}
void uni(int x,int y)
{
	int a=find(x),b=find(y);
	fa[a]=b;
}
int main()
{
	int n,k,i;
	scanf("%d%d",&n,&k);
	int ff=0;
	for (i=1;i<=n*3;i++) fa[i]=i;
	for (i=1;i<=k;i++)
	{
		int id,x,y;
		scanf("%d%d%d",&id,&x,&y);
		if ((x==y && id==2) || x>n || y>n){ff++; continue;}
		if (id==1)
		{
			if (find(x)==find(y+n) || find(x)==find(y+2*n) || find(x+n)==find(y) || find(x+n*2)==find(y)) {ff++; continue;}
			uni(x,y);
			uni(x+n,y+n);
			uni(x+2*n,y+2*n);
		}
		else
		{
		    if (find(x)==find(y) || find(x)==find(y+n) || find(x+2*n)==find(y)) {ff++; continue;}
		    uni(x,y+2*n);
			uni(x+n,y);
		    uni(x+2*n,y+n);
		}
	}
	printf("%d",ff);  
}

4.题目: Find them, Catch them

题解:同食物链

代码:

#include <cstdio>
#include <iostream>
#include <cstring>
#define N 400005
using namespace std;
int fa[N];
int find(int x)
{
	if (fa[x]!=x) fa[x]=find(fa[x]);
	return fa[x];
}
int main()
{
	int T;
	scanf("%d",&T);
	while (T--)
	{
		int n,m,i,x,y;
		scanf("%d%d",&n,&m);
		for (i=1;i<=n*2;i++) fa[i]=i;
		for (i=1;i<=m;i++)
	    {
	    	char ch;ch=getchar();
	    	while (ch!='A' && ch!='D') ch=getchar();
	    	scanf("%d%d",&x,&y);
	    	int a=find(x),b=find(y);
			if (ch=='A')
	    	{
	    		if (a!=b && find(x+n)!=b && find(y+n)!=a) printf("Not sure yet.\n");
	    	    else if (a!=b) printf("In different gangs.\n");
	    	    else printf("In the same gang.\n");
			}
			else
			{
				int c=find(x+n),d=find(y+n);
				fa[c]=b;
				fa[d]=a;
			}
		}
	}  
}


5.题目:关押罪犯

题解:

将边从大到小排序,把这些边连的两个点分到两个集合中,然后运用+n啥的合并,如果搜到一条边发现x和y已经属于一个集合,那无法避免了,因为只有迁就了这条边,更大的边才不会被当做答案。

代码:

#include <cstdio>
#include <algorithm>
using namespace std;
struct hh
{
	int x,y,z;
}a[100005];
int fa[40005];
int cmp(hh a,hh b){return a.z>b.z;}
int find(int x)
{
	if(fa[x]!=x) fa[x]=find(fa[x]);
	return fa[x]; 
}
int main()
{
	int n,i,m;
	scanf("%d%d",&n,&m);
	for (i=1;i<=m;i++)
	  scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].z);
	for (i=1;i<=n*2;i++)  
	  fa[i]=i;
	int ll=n-1;
	sort(a+1,a+m+1,cmp);  
    for (i=1;i<=m;i++)
    {
    	int x1=find(a[i].x),x2=find(a[i].y);
    	if (x1==x2) 
		{
			printf("%d",a[i].z);
			return 0;
		}
		fa[x2]=find(a[i].x+n);
		fa[x1]=find(a[i].y+n);
	}
    printf("0");
}


总结:

并查集常见题目:

1.  kruskal最小生成树

2.  食物链,团伙:每个格子占2-3个块,x+ny+n代表敌人,x+2ny+2n代表食物

  经典代码

for (i=1;i<=n*3;i++)
           fa[i]=i;
if (find(a)==find(b+n)||find(b)==find(a+n)||find(a+2*n)==find(b)||find(a)==find(b+n*2))
                     {
                              ans++;continue;
                     }
  uni(a,b);
  uni(a+n,b+n);
  uni(a+2*n,b+2*n);

3.      银河英雄+摞盒子:before[]x之前的个数,cnt[]存这一列的个数

经典代码

 if (fa[x]!=x)
{
           int fat=fa[x];
           fa[x]=find(fa[x]);
           before[x]+=before[fat];
}
      return fa[x];
if (x1!=x2)
                 {
                          fa[x1]=x2;
                          before[x1]+=cnt[x2];
                          cnt[x2]+=cnt[x1];
                           }

4.   打击犯罪:倒悬并查集,倒着开始连,每一个连完后就判断一下有几个father

5.这个农场一共有被用M条双向道路连接的N个谷仓(1<=N,M<=200000)。为了关闭整个农场,FJ 计划每一次关闭掉一个谷仓。当一个谷仓被关闭了,所有的连接到这个谷仓的道路都会被关闭,而且再也不能够被使用。

FJ现在正感兴趣于知道在每一个时间(这里的“时间”指在每一次关闭谷仓之后的时间)时他的农场是否是“全连通的”——也就是说从任意的一个开着的谷仓开始,能够到达另外的一个谷仓。注意自从某一个时间之后,可能整个农场都开始不会是“全连通的”。

【输入格式】

输入的第一行是N和M。下面的M行每行都描述了一条连接两个谷仓的双向路径的两个端点(输入的点保证在1...N的范围内),最后的N行是一个1...N的排列,描述每0个谷仓被关闭的顺序。

【输出顺序】

输出一共有N行,每行可以是“YES”或者“NO”。第一行表示一开始时整个农场是否是“全连通的”,然后第i+1行表示在第i次的关闭谷仓之后整个农场是否是“全连通的”。

【样例输入】

4 3

1 2

2 3

3 4

3

4

1

2

【样例输出】

YES

NO

YES

YES

100%数据(1<=N,M<=200000)

倒悬并查集,由于不用知道每个联通块具体的个数,只需要统计联通块的个数,设tt,连一次少一块嘛

经典代码:

for (i=1;i<=n;i++) fa[i]=i;
    int tt=n;
    for(i=n;i>=1;i--)
      {
          d[i]=1;
          c[a[i]]=true;
          for (j=front[a[i]];j;j=edge[j].next)
            if(c[edge[j].to])
                   {
                           intx1=find(edge[j].to),x2=find(a[i]);
                           if(x1!=x2)
                           {
                                    fa[x2]=x1;tt--;
                           }
                   }
          if (tt!=i) d[i]=0;
          }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值