题目描述:
动物王国中有三类动物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),输出假话的总数。
Input
第一行是两个整数N和K,以一个空格分隔。
以下K行每行是三个正整数 D,X,Y,两数之间用一个空格隔开,其中D表示说法的种类。
若D=1,则表示X和Y是同类。
若D=2,则表示X吃Y。
Output
只有一个整数,表示假话的数目。
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
思路分析:这道题一开始的想法是用带权并查集,后来发现至少需要三组数组,每次还要分很多情况,太麻烦了,于是看大佬的博客,发现并查集居然还能这样用!之前一直是想着假设一开始的是A类,然后后来者根据情况对应之。思维局限。完全不需要确定谁具体是A还是BC呀,只需要知道他们之间的关系就行
大佬思路如下:
我们不关心小动物具体是ABC,只关心他们之间的关系,所以每个动物都有ABC类三种可能,三种可能表示的对应关系是一样的,我们判断关系的时候只需要假设都是A类即可。
开三倍空间,前50000表示A类,中间50000表示B类,后50000表示C类。跨类别同祖先表示捕食关系,同类别同祖先表示同类关系。如此一来,下标就能表示捕食关系,比如如果A中有1,B中有2,那么就可以表示1吃2。单纯下标表示捕食关系是不够的,比如123456,既能表示1吃2也能表示2吃1。
所以再用并查集的父子关系来表示捕食关系,被捕食者祖先是捕食者。这样的话123456就成了153153,A中的1和B中的2都返回1,所以A中的1和B中的2等价,仅表示1吃2
这道题的归并是有逻辑先后的,不能压缩路径!
如果x,y同类关系的话,就将ABC类里x,y都合并。
例子:
A | A | A | B | B | B | C | C | C |
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
初始状态
A | A | A | B | B | B | C | C | C |
1 | 2 | 2n+2 | 4 | 5 | 2 | 7 | 8 | n+2 |
输入 2吃3
A | A | A | B | B | B | C | C | C |
1 | 2n+1 | 2n+2 | 4 | 1 | 2 | 7 | n+1 | n+2 |
输入 1吃2
此时,A1的祖先是1,B3的祖先是,2 -> 2n+1 -> 7,7在C类,而1在A类,所以3吃1
#include <stdlib.h>
#include <stdio.h>
#include <iostream>
#include <queue>
#include <algorithm>
#include <string.h>
using namespace std;
int tree[150050];
int find(int x){
if(tree[x]==x)return x;
else return find(tree[x]);
}
int same(int a,int b){
return find(a)==find(b);
}
void unite(int a,int b){
int ra=find(a);
int rb=find(b);
if(ra!=rb){
tree[rb]=ra;
}
}
int main(){
int n,k,t,x,y,ans=0;
scanf("%d%d",&n,&k);
for(int i=1;i<=3*n;i++){
tree[i]=i;
}
while(k--){
scanf("%d%d%d",&t,&x,&y);
if(x<0||y<0||x>n||y>n){
ans ++;
continue;
}
if(t==1){
if(same(x,y+n)||same(y,x+n)){
ans++;
}
else {
unite(x,y);
unite(x+n,y+n);
unite(x+n*2,y+2*n);
}
}
if(t==2){
if(same(y,x+n)||same(x,y)){
ans ++;
}
else {
unite(x,y+n);
unite(x+n,y+2*n);
unite(x+2*n,y);
}
}
// printf("%d|%d|%d|%d|%d|%d|%d|%d|%d\n",tree[1],tree[2],tree[3],tree[n+1],tree[n+2],tree[n+3],tree[2*n+1],tree[2*n+2],tree[2*n+3]);
// printf("%d*\n",ans);
}
printf("%d\n",ans);
}
解法二:
带权并查集
本题目只需要知道相对关系,并不需要明确小动物具体是ABC哪一类。而带权并查集的权值可以表示与父节点的相对关系,所以可以用带权并查集解这道题。
tree[y]=x,表示y和x有关系,同一个根节点表示在同一个已知关系里,也就是说在同一条食物链中。value为0表示某节点与父节点同类关系,1表示吃父节点,2表示被父节点吃。我们只需要知道每个节点和父节点的关系,在路径压缩的时候层层向上修改即可
权值更新和判断可以拟几个例子归纳出来
#include <stdlib.h>
#include <stdio.h>
#include <iostream>
#include <queue>
#include <algorithm>
#include <string.h>
using namespace std;
int n,k,d,x,y,ans;
int tree[50010];
int value[50010];
int find(int x){
int tmp=x;
while(tmp!=tree[tmp]){
tmp=tree[tmp];
value[x]=(value[x]+value[tmp])%3;
}
return tree[x]=tmp;
}
int main()
{
scanf("%d%d",&n,&k);
ans =0;
memset(value,0,sizeof(value));
for(int i=0;i<=n;i++){
tree[i]=i;
}
while(k--){
scanf("%d%d%d",&d,&x,&y);
if(x>n||x<=0||y>n||y<=0){
ans ++;
continue;
}
int rx=find(x);
int ry=find(y);
if(d==1){
if(rx!=ry){
tree[ry]=rx;
value[ry]=(value[x]-value[y]+3)%3;
}
else {
if((value[x]-value[y]+3)%3!=0){
ans ++;
}
}
}
if(d==2){
if(rx!=ry){
tree[ry]=rx;
value[ry]=(value[x]-value[y]-1+3)%3;
}
else {
if((value[x]-value[y]+3)%3!=1){
ans ++;
}
}
}
}
printf("%d\n",ans);
return 0;
}
参考博客:
https://blog.csdn.net/shadowcw/article/details/52159262
https://blog.csdn.net/qq_41890797/article/details/81540847