种类并查集 ----大锅烩

种类并查集的接触,对于我来说 还是比较有难度的,因为一开始思维不明确不清晰,所以上手难度较大,但是经过几天的做题和思考,有了新的感悟,主要帮助还是来自向量思维的建立,https://blog.csdn.net/qq_42505741/article/details/81104742(转载的大佬的一篇文章)

关于并查集就不多做介绍了,直接上题来体会种类并查集的奇特。

A Bug's Life  http://poj.org/problem?id=2492

Description

Background 
Professor Hopper is researching the sexual behavior of a rare species of bugs. He assumes that they feature two different genders and that they only interact with bugs of the opposite gender. In his experiment, individual bugs and their interactions were easy to identify, because numbers were printed on their backs. 
Problem 
Given a list of bug interactions, decide whether the experiment supports his assumption of two genders with no homosexual bugs or if it contains some bug interactions that falsify it.

Input

The first line of the input contains the number of scenarios. Each scenario starts with one line giving the number of bugs (at least one, and up to 2000) and the number of interactions (up to 1000000) separated by a single space. In the following lines, each interaction is given in the form of two distinct bug numbers separated by a single space. Bugs are numbered consecutively starting from one.

Output

The output for every scenario is a line containing "Scenario #i:", where i is the number of the scenario starting at 1, followed by one line saying either "No suspicious bugs found!" if the experiment is consistent with his assumption about the bugs' sexual behavior, or "Suspicious bugs found!" if Professor Hopper's assumption is definitely wrong.

Sample Input

2
3 3
1 2
2 3
1 3
4 2
1 2
3 4
Sample Output
Scenario #1:
Suspicious bugs found!

Scenario #2:
No suspicious bugs found!

Hint

Huge input,scanf is recommended.

这道题是作为种类并查集的基础入门题的存在,主要就是说给咱们n组关系,来判断其中有没有 gay虫

刚上来确实一头雾水,这是什么东西,没有向并查集这边思考,不理解它怎么来判断关系,在经过学习后,定义了一个rel数组,是来存放当前节点和它父节点的关系的,(不同的题目的关系不相同,gay题是来断定性别的)

ac代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#define maxn 2005
using namespace std;
int far[maxn],rel[maxn];
int n,k;
int flag;
int t,a,b;
int find(int x)
{
	if(x==far[x])
		return far[x];
	int tt=find(far[x]);
	rel[x]=(rel[far[x]]+rel[x])%2;
	far[x]=tt;
	return far[x];
}
void merge(int x,int y)
{
	int a=find(x);
	int b=find(y);
	if(a==b)
	{
		if(rel[x]==rel[y])
			flag=1;
		return ;
	}
	far[a]=b;
	rel[a]=(rel[x]-rel[y]+1+2)%2;
}
int main()
{
	int Case=1;
	cin>>t;
	while(t--)
	{
		scanf("%d%d",&n,&k);
		flag=0;
		for(int i=0;i<=n;i++)
		{
			far[i]=i;
			rel[i]=0;
		}
		for(int i=0;i<k;i++)
		{
			scanf("%d%d",&a,&b);
			if(flag)
				continue;
			merge(a,b);
		}
		printf("Scenario #%d:\n",Case++);
		if(flag)
			printf("Suspicious bugs found!\n");
		else
			printf("No suspicious bugs found!\n");
		printf("\n");
	}
	return 0;
}

上来就会注意到find函数和merge函数中多了两行核心代码。

rel[x]=(rel[far[x]]+rel[x])%2;

rel[a]=(rel[x]-rel[y]+1+2)%2;

他们是实时更新rel数组的,因为咱们只能确定当前节点和它父节点的关系,在经过关系递推后,rel中是这个节点和根节点之间的关系了,所以这时候的rel数组就应该改变了,可能不变(可能不变,偶然性)至于为啥都建立跟根节点的关系,是因为咱们的输入是任意的,就意味着任意两点之间都有直接的关系比较。

种类并查集的关系不会很多,三四种就到顶了,这道题只有两种,0/1,0代表当前点与其父节点同性 1代表异性

输入的两个 已知为情侣,要我们根据前边的输入来判断是否有gay,即输入的两个数的性别相同。

find函数的操作就是一直压缩,根据思考能得到  rel[x]=(rel[far[x]]+rel[x])%2;

merge函数就涉及了两种情况,即find(a)==find(b),a b的根节点相同,通过rel[a]和rel[b]的异同就可以直接判断

若find(a)!=find(b),需要在两棵树之间建立关系,所以 far[pa]=pb;  这个时候,重点就来了,咱们应该怎么寻找这棵树的关系呢,

这时候 奆佬的思维打开了我的脑洞:rootb -> roota = rootb -> b + b -> a +a -> roota 即rel[roota] = rel[b] +s - rel[a], 规定a->b rel[b].

也可以根据自己的数学经验总结出规律,相信大佬们会的

POJ Find them, Catch them

没找到链接,直接上原题

Description

The police office in Tadu City decides to say ends to the chaos, as launch actions to root up the TWO gangs in the city, Gang Dragon and Gang Snake. However, the police first needs to identify which gang a criminal belongs to. The present question is, given two criminals; do they belong to a same clan? You must give your judgment based on incomplete information. (Since the gangsters are always acting secretly.) 

Assume N (N <= 10^5) criminals are currently in Tadu City, numbered from 1 to N. And of course, at least one of them belongs to Gang Dragon, and the same for Gang Snake. You will be given M (M <= 10^5) messages in sequence, which are in the following two kinds: 

1. D [a] [b] 
where [a] and [b] are the numbers of two criminals, and they belong to different gangs. 

2. A [a] [b] 
where [a] and [b] are the numbers of two criminals. This requires you to decide whether a and b belong to a same gang.

Input

The first line of the input contains a single integer T (1 <= T <= 20), the number of test cases. Then T cases follow. Each test case begins with a line with two integers N and M, followed by M lines each containing one message as described above.

Output

For each message "A [a] [b]" in each case, your program should give the judgment based on the information got before. The answers might be one of "In the same gang.", "In different gangs." and "Not sure yet."

题意:有一群人属于两个队伍每行给出两个人说明他们不属于同一个队问给出两个人他们属于一个队还是不同的队或是不确定
思路:并查集把给出的人分成几个集合每个集合之间的人的关系不确定,对同一个集合保存和本人不为同一队的人本着敌人的敌人便是朋友的原则用并查集同一集合为同一队不同集合为不同队

ac代码:

#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstdlib>
#include<stack>
#include<queue>
#include<cmath>
#define maxn 101000
using namespace std;
int far[maxn],rel[maxn];
int flag1,flag2;
int find(int x)
{
	if(x==far[x])
		return far[x];
	int tt=find(far[x]);
	rel[x]=(rel[far[x]]+rel[x])%2;
	far[x]=tt;
	return far[x];
}
void merge(int x,int y)
{
	int a=find(x);
	int b=find(y);
	if(a==b)
		return ;
	far[a]=b;
	rel[a]=(rel[x]-rel[y]+1)%2;
}
void judge(int x,int y)
{
	int px=find(x);
	int py=find(y);
	if(px==py)
	{
		flag1=1;
		if(rel[x]!=rel[y])
			flag2=1;
		return ;
	}
}
int main()
{
	int t;
	while(scanf("%d",&t)!=EOF)
	{
		while(t--)
		{
			int n,m;
			scanf("%d%d",&n,&m);
			for(int i=1;i<=n;i++)
			{
				far[i]=i;
				rel[i]=0;
			}
			for(int i=0;i<m;i++)
			{
				char s;
				int a,b;
				flag1=flag2=0;
				cin>>s;
				if(s=='D')
				{
					scanf("%d%d",&a,&b);
					merge(a,b);
				}
				else
				{
					scanf("%d%d",&a,&b);
					judge(a,b);
					if(flag1&&flag2)
						cout<<"In different gangs."<<endl;
					else if(flag1&&!flag2)
						cout<<"In the same gang."<<endl;
					else 
						cout<<"Not sure yet."<<endl;
				}
			}
		}
	}
	return 0;
}

 这道题 跟gay虫的没有多大区别,就不细分析了(: /\ ;)

直接进入难度大一些的三个关系的食物链吧

POJ1182 食物链

Description
动物王国中有三类动物A,B,C,这三类动物的食物链构成了有趣的环形。A吃B, B吃C,C吃A。现有N个动物,以1-N编号。每个动物都是A,B,C中的一种,但是我们并不知道它到底是哪一种。 有人用两种说法对这N个动物所构成的食物链关系进行描述:
第一种说法是"1 X Y",表示X和Y是同类。 
第二种说法是"2 X Y",表示X吃Y。 
此人对N个动物,用上述两种说法,一句接一句地说出K句话,这K句话有的是真的,有的是假的。当一句话满足下列三条之一时,这句话就是假话,否则就是真话。
1) 当前的话与前面的某些真的话冲突,就是假话; 
2) 当前的话中X或Y比N大,就是假话; 
3) 当前的话表示X吃X,就是假话。 
你的任务是根据给定的N(1 <= N <= 50,000)和K句话(0 <= K <= 100,000),输出假话的总数。

输入:
第一行是两个整数N和K,以一个空格分隔。 
以下K行每行是三个正整数 D,X,Y,两数之间用一个空格隔开,其中D表示说法的种类。 
若D=1,则表示X和Y是同类。 
若D=2,则表示X吃Y。

输出:
只有一个整数,表示假话的数目。
 
Sample Input 
100 7
1 101 1 
2 1 2
2 2 3 
2 3 3 
1 1 3 
2 3 1 
1 5 5
 
Sample Output 
3

首先要明确rel数组存放的是什么,rel[x]=0.即x和far[x]是同类,rel[x]=1.即far[x]吃x,rel[x]=2.即x吃far[x]

关系细思考有点恶心,还是将就着做题吧。

ac代码:

#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstdlib>
#include<stack>
#include<queue>
#include<cmath>
#define maxn 50005
using namespace std;
int n,k,ans,d,x,y;
int far[maxn],rel[maxn];
int find(int x)
{
	if(far[x]!=x)
	{
		int pa=find(far[x]);
		rel[x]=(rel[x]+rel[far[x]])%3;
		far[x]=pa;
	}
	return far[x];
}
void merge(int d,int a,int b)
{
	int pa=find(a);
	int pb=find(b);
	if(pa==pb)
	{
		if((rel[b]-rel[a]+3)%3!=d)
			++ans;
		return ;
	}
	far[pb]=pa;
	rel[pb]=(rel[x]-rel[y]+d+3)%3;
	
}
int main()
{
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;i++)
	{
		far[i]=i;
		rel[i]=0;
	}
	ans=0;
	while(k--)
	{
		scanf("%d%d%d",&d,&x,&y);
		if(x>n||y>n||(d==2&&x==y))
		{
			++ans;
			continue;
		}
		merge(d-1,x,y);
	}
	printf("%d\n",ans);
	return 0;
}

这个是在输入 时就给定了二者的关系,根据前边的输入来判定后来的正确与否。

同样,在find函数里,经过rel数组的递推,会得到公式   rel[x]=(rel[x]+rel[far[x]])%3;

在merge有find(x)==find(y)的,可以直接比较他们的关系是否符合已知的规律

如果find(x)!=find(y),就需要 far[pa]=pb;  再根据pb -> pa = pb - > b + b -> a + a - > pa,得 rel[pa]=rel[b]  -s -rel[a],就可以啦!!

自己也是初学种类并查集,若有错误,请多指教!!!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值