种类并查集的接触,对于我来说 还是比较有难度的,因为一开始思维不明确不清晰,所以上手难度较大,但是经过几天的做题和思考,有了新的感悟,主要帮助还是来自向量思维的建立,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],就可以啦!!
自己也是初学种类并查集,若有错误,请多指教!!!