并查集题目

一、食物链:A吃B,B吃C,C吃A,构成循环的关系。
思路:用1表示吃根节点的,2被根节点吃,3同类,于是有%3==1,吃根节点;%3==2被根节点吃
%3==0同类,这三种表示关系。即用每个点到根节点的距离来表明他们之间的关系。
 

#include<iostream>
using namespace std;
const int N=50005;
int fa[N],d[N];
int find(int x)
{
    if(x!=fa[x])
    {
        int t=find(fa[x]);
        d[x]+=d[fa[x]];//d每个点到根节点的距离
        fa[x]=t;
    }
    return fa[x];
}
int main()
{
    int n,k;
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++)
        fa[i]=i;
    int cnt=0;
    while(k--)
    {
        int t,x,y;
        scanf("%d%d%d",&t,&x,&y);
        if(x>n||y>n) cnt++;
        else
        {
            int px=find(x),py=find(y);
            if(t==1)
            {
                if(px==py&&(d[x]-d[y])%3) cnt++;//在同一个集合下判断满不满足同类
                else if(px!=py)
                {
                    fa[px]=py;
                    d[px]=d[y]-d[x];//d[x]+d[px]=d[y]
                }
            }
            else
            {
                if(px==py&&(d[x]-d[y]-1)%3)//x吃y,则x的等级比y大1,即x到根节点的距离比y小1
                    cnt++;
                else if(px!=py)
                {
                    fa[px]=py;
                    d[px]=d[y]-d[x]+1;//d[y]+1=d[x]+d[px]
                }
            }
        }
    }
    printf("%d\n",cnt);
    return 0;
}

1250. 格子游戏
思路:当形成环的时候就结束判断。怎么用并查集来判断有没有形成环呢?当两个点已经在同一个
集合里,如果再给这两个点连一条线就会形成环,所以判断给出的点是否已经在同一个集合里了就
可,即根结点是否一样。

#include<iostream>
using namespace std;
const int N=40010;
int fa[N];
int n,m;
int get(int x,int y)
{
	return (x-1)*n+y;//将二维数组转换为一维存储
}
int find(int x)
{
	if(x!=fa[x])
		fa[x]=find(fa[x]);
	return fa[x];
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n*n;i++)//转换为一维,有n*n个格子
		fa[i]=i;
	int res=0;
	for(int i=1;i<=m;i++)
	{
		int x,y;
		char ch;
		cin>>x>>y>>ch;
		int a,b;
		a=get(x,y);
		if(ch=='D')
			b=get(x+1,y);
		else
			b=get(x,y+1);
		int pa=find(a),pb=find(b);
		if(pa==pb)//已经在同一个集合里的了,在次相连就会形成环
		{
			res=i;
			break;
		}
		else
			fa[pa]=pb;
	}
	if(!res)
		cout<<"draw"<<endl;
	else
		cout<<res<<endl;
	return 0;
}

搭配购买
思路:把要在一起搭配购买的连接起来在同一个集合,把这个集合的总价钱和总价值存在根节点,最后就成了0/1背包问题(因为对于每个集合要么就买下要么就不要)找出每个集合的根节点,有多少个集合就有多少个背包。

#include<iostream>
using namespace std;
const int N=5e4+10;
int fa[N],pr[N],val[N];
int f[N];
int find(int x)
{
	if(x!=fa[x])
		fa[x]=find(fa[x]);
	return fa[x];
}
int main()
{
	int n,m,w;
	cin>>n>>m>>w;
	for(int i=1;i<=n;i++)
		fa[i]=i;
	for(int i=1;i<=n;i++)
		cin>>pr[i]>>val[i];
	for(int i=1;i<=m;i++)
	{
		int a,b;
		cin>>a>>b;
		int pa=find(a),pb=find(b);
		if(pa!=pb)
		{
			fa[pa]=pb;
			pr[pb]+=pr[pa];//更新整个集合的根节点的值,记录整个集合的总价值
			val[pb]+=val[pa];
		}
	}
	//0/1背包
	for(int i=1;i<=n;i++)
	{
		if(fa[i]==i)
		{
			for(int j=w;j>=pr[i];j--)
				f[j]=max(f[j],f[j-pr[i]]+val[i]);
		}
	}
	cout<<f[w]<<endl;
	return 0;
}

237. 程序自动分析
思路:先判断所有相等的情况,即把相等的放在同一个集合里,再判断不等的情况是否与前面判断出来的矛盾。这里需要把每组的条件进行离散化处理,因为最多只有n组,所以数据最多只有2*n个,所以用离散化把大范围的数据缩小来表示。

离散化:这道题用的是不保序的离散化方式,即不用保证离散化后每个值的相对位置保持不变,
 

#include<iostream>
#include<algorithm>
#include<unordered_map>
using namespace std;
const int N=2e5+10;//n组,可能有2*n个不同的数
int fa[N];
int n,t,cnt;
unordered_map<int,int> S;
struct type
{
	int x,y,e;
}query[N];
int get(int x)//离散化,不保序
{
	if(S.count(x)==0) S[x]=++cnt;
	return S[x];
}
int find(int x)
{
	if(x!=fa[x])
		fa[x]=find(fa[x]);
	return fa[x];
}
int main()
{
	scanf("%d",&t);
	while(t--)
	{
		S.clear();
		cnt=0;
		scanf("%d",&n);
		int x,y,e;
		for(int i=0;i<n;i++)
		{
			scanf("%d%d%d",&x,&y,&e);
			query[i]={get(x),get(y),e};//将离散化后的x,y的值存入
		}
		for(int i=1;i<=cnt;i++)//最后映射成cnt个点
			fa[i]=i;
		//先处理相等的情况
		for(int i=0;i<n;i++)
		{
			if(query[i].e==1)
			{
				int px=find(query[i].x),py=find(query[i].y);
				fa[px]=py;
			}
		}
		//判断不相等情况下的两个元素是否在同一集合中
		int flag=0;
		for(int i=0;i<n;i++)
		{
			if(query[i].e==0)
			{
				int px=find(query[i].x),py=find(query[i].y);
				if(px==py)
				{
					flag=1;
					break;
				}
			}
		}
		if(flag)
			printf("NO\n");
		else
			printf("YES\n");
	}
	return 0;
}

239. 奇偶游戏(带边权并查集,维护与根节点的相对关系)
思路:先抽象化,利用前缀和的思想将[l,r]区间里1的个数的奇偶数用S[r]-S[L-1]来理解,当S[r]-S[l-1]为偶数时,说明S[r]与S[l-1]是同类的(同为奇或偶),用0来标识,否则是不同类的。于是可将每个数放入到同一个集合,通过维护每个点与根节点的关系来判断这两个点的关系。当d[x],d[y]与父节点d[fa]是同类时,如果d[x]+d[y]为偶,即d[x]^d[y]=0,没有说谎,否则在说谎。同理,当d[x],d[y]与父节点d[fa]是异类时,如果d[x]+d[y]为奇数,即d[x]^d[y]==1,说明没有在说谎。这道题妙处在于把区间抽象出两个点,用这两个点的类型来判别。

#include<iostream>
#include<string>
#include<unordered_map>
using namespace std;
const int N=1e4+10;
int fa[N],d[N];
int n,m;
unordered_map<int,int> S;
int get(int x)
{
	if(S.count(x)==0) S[x]=++n;
	return S[x];
}
int find(int x)
{
	if(x!=fa[x])
	{
		int t=find(fa[x]);
		d[x]^=d[fa[x]];//意义等同于d[x]+=d[fa[x]
		fa[x]=t;
	}
	return fa[x];
}
int main()
{
	cin>>n>>m;
	int res=m;
	n=0;
	for(int i=0;i<N;i++)
		fa[i]=i;
	for(int i=0;i<m;i++)
	{
		int x,y;
		string str;
		cin>>x>>y>>str;
		x=get(x-1),y=get(y);
		int px=find(x),py=find(y);//Sy-Sx-1
		int flag=0;//标记偶数,即Sy,Sx-1是同类
		if(str=="odd") flag=1;//标记奇数,即Sy,Sx-1是不同类
		if(px==py)
		{
			if(d[x]^d[y]!=flag)
			{
				res=i;
				break;
			}
		}
		else
		{
			fa[px]=py;
			d[px]=d[x]^d[y]^flag;//(d[px]+d[x])^d[y]^flag
		}
	}
	cout<<res<<endl;
	return 0;
}

238. 银河英雄传说
思路:用一个size数组来维护每个集合的元素个数,同时d[x]来维护每个点到根节点的距离。于是
当一个结点x以结点y为父节点时i,d[x]=size[y]即x到根节y的距离等于以y为根结点的集合元素
个数,然后s[y]+=s[x];再查找根节点的时候要维护每个点到根节点的距离,这样每次再查找的
时候就可以保证每个点的d[x]一定是更新到了与根节点的距离。

#include<iostream>
using namespace std;
const int N=3e4+10;
int fa[N],d[N],s[N];
int find(int x)
{
	if(x!=fa[x])//在查找的时候会自动更新每个点到根结点的距离
	{
		int t=find(fa[x]);
		d[x]+=d[fa[x]];//更新每个点到根节点的距离
		fa[x]=t;
	}
	return fa[x];
}
int main()
{
	int t,m;
	cin>>t;
	for(int i=0;i<N;i++)
	{
		fa[i]=i;
		s[i]=1;//没合并之前每个结点都是自成一个集合,每个集合的大小是1
	}
	while(t--)
	{
		char ch;
		int x,y;
		cin>>ch>>x>>y;
		int px=find(x),py=find(y);
		if(ch=='M')
		{
			fa[px]=py;
			d[px]=s[py];//更新px到新的根节点的距离
			s[py]+=s[px];
		}
		else
		{
			if(px!=py)
				cout<<-1<<endl;
			else
				cout<<max(0,abs(d[x]-d[y])-1)<<endl;
		}
	}
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值