动物王国中有三类动物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
解题报告 :
经典的种类并查集,有三种关系。这种题只要找到那几个关系的式子,其他的就比较好写了,一开始自己写了一个,但一直错,也不知道哪里的问题,最后看了大佬们的博客,倒推回去,发现是merge函数中应该是用原来的节点u,v来推导w[t2],而不是根节点t1,t2来推导。这种推导关系一定要注意弄清楚。
我的代码的关系可能会比较乱,如果没看懂,可以到这个博客看看: https://blog.csdn.net/j_sure/article/details/25917915
接下来是我自己的代码的思路:
首先是getf函数:v为当前节点,f为v的父节点,a为求得的结果。经过递归后,f的父节点已经变成根节点,其权值w也变成与根节点的关系,因此我们可以用v和f两点,来求v点的权值(即a)。以下为关系表:
v | f | a |
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
0 | 2 | 2 |
2 | 0 | 2 |
1 | 1 | 2 |
1 | 2 | 0 |
2 | 1 | 0 |
2 | 2 | 1 |
由这个关系表我们可以推得一个式子:w[a]=(w[v]+w[f])%3这个式子就是getf中权值变化的式子。
接下来是merge函数中的表达式:
merge函数中是合并两个不在同一集合的两个点,而由题目已知这两个点有两种关系,所以我们可以分开讨论。
设两个节点u,v,他们的根节点是t1,t2。合并之后是f[t2]=t1,因此我们要求w[t2]的关系。
d=1时:
思路1:把关系写出来,找适合的式子。最后发现这样一个规律。w[t2]=(w[u]+w[v]*2)%3
t1 | u(v) | t2 |
0 | 0 | 0 |
0 | 1 | 2 |
1 | 0 | 1 |
0 | 2 | 1 |
2 | 0 | 2 |
1 | 1 | 0 |
1 | 2 | 2 |
2 | 1 | 1 |
2 | 2 | 0 |
思路2:因为u、v关系相同,所以我们可以把u、v看作同一个点,然后我们把v当作中间节点,t1当作父节点,t2当作v的子节点,按照getf中的求法来求。
首先我们先把v当作t2的父节点,此时w[t2]=(3-w[v])%3(写一下两个节点的关系表,就可以很明显的看出来),然后按照getf中寻找父节点的关系来求出t2与t1的关系(即把w[t2]当作w[f]带入)。
d=2时:
这个关系的找法和d=1时的思路二时一样的,只不过多了一层中间节点而已。当然找节点的顺序不同,得出来的表达式也就会略有区别。
最后是判断给的关系是不是合理的:已知输入是 t1,t2则t1是吃t2的,若t2的父节点是t1,则w[t2]=1。t1和t2都与根节点直接相连,因此我们可以把t1当作根节点,原来根节点当作中间节点,来连接起t1与t2。此时中间节点的对与t1的权值为:(3-w[t1])%3。w[t1]为原来t1的权值。再根据getf中的式子我们可以得出最后t2的权值表达式为:(w[t2]+(3-w[t1])%3)%3。因此只要判断这个式子和1是否相等即可。
ac代码:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<queue>
#include<stack>
#include<string.h>
using namespace std;
int n,k;
int f[50009];
int w[50009];//0同类,1被父节点吃,2吃父节点
void init()
{for(int i=0;i<=n;i++){f[i]=i;w[i]=0; }
}
int getf(int v)
{if(f[v]!=v){ int temp=f[v];f[v]=getf(f[v]);w[v]=(w[v]+w[temp])%3;} return f[v];
}
void merge(int u,int v,int d)
{int t1,t2;t1=getf(u);t2=getf(v);if(t1!=t2){if(d==1){w[t2]=(w[u]+w[v]*2)%3;//(w[u]+(3-w[v])%3)%3 这种写法也对,两者是找的思路不同}else if (d==2){int temp=w[v];w[t2]=((3-w[v])%3+(w[u]+1)%3)%3;//注意这里是u,v不是t1、t2。是找原来节点而不是根节点的关系 //w[t2]=(w[u]+(1+(3-w[v])%3)%3)%3;这样写也可以,两种方法思路一样,不过节点找的顺序不同}f[t2]=t1; }
}
int join(int u,int v)
{int t1,t2;t1=getf(u);t2=getf(v);if(t1==t2)return 0;else return 1;
}
int main()
{int ans=0;scanf("%d%d",&n,&k);init();for(int i=0;i<k;i++){int d,x,y;scanf("%d%d%d",&d,&x,&y);if(x>n||y>n)ans++;else if(d==1){if(!join(x,y)){if(w[x]!=w[y])ans++;}else merge(x,y,1);}else{if(x==y)ans++;else if(!join(x,y)){if((w[y]+(3-w[x])%3)%3!=1)ans++;}else{merge(x,y,2); }}}printf("%d\n",ans);return 0;
}