NOI2001 食物链

NOI2001 食物链

地址:https://www.luogu.com.cn/problem/P2024

算法分类:并查集、加权

 

题目原文

食物链

Problem Description

动物王国中有三类动物 A,B,C,这三类动物的食物链构成了有趣的环形。A 吃 B,B 吃 C,C 吃 A。

现有 N (1 ≤ N ≤ 5 ∗ 10^4)个动物,以 1 - N 编号。每个动物都是 A,B,C 中的一种,但是我们并不知道它到底是哪一种。

有人用两种说法对这 N 个动物所构成的食物链关系进行描述:

  • 第一种说法是 1 X Y,表示 X 和 Y 是同类。

  • 第二种说法是2 X Y,表示 X 吃 Y 。

此人对 N 个动物,用上述两种说法,一句接一句地说出 K(1 ≤ K ≤ 10^5)句话,这 K 句话有的是真的,有的是假的。当一句话满足下列三条之一时,这句话就是假话,否则就是真话。

  • 当前的话与前面的某些真的话冲突,就是假话

  • 当前的话中 X 或 Y 比 N 大,就是假话

  • 当前的话表示 X 吃 X,就是假话

你的任务是根据给定的 N 和 K 句话,输出假话的总数。

Input

第一行两个整数,N,K,表示有 N 个动物,K 句话。

第二行开始每行一句话(按照题目要求,见样例)

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,B,C,我们可以用并查集表示和维护。

同时,为了表示他们三者的关系:A吃B,B吃C,C吃A。即A的猎物是B,A的天敌是C。那么,我们就可以在 区间表示自身及同类区间表示它对应的猎物区间表示它的天敌

那么,如果要判断u与v的是否为u吃v的关系,我们只需看u与v+N是否有相同祖先即可。需要注意的是,如果要表示u与v同类的关系,那么我们不仅要合并自身,还要合并他们的猎物和天敌

同理,判断或表示其他关系时也应比较或合并多项。

 

程序代码

 1 #include <iostream>
 2 #include <stdio.h>
 3  4 using namespace std;
 5 const int maxn=50005;
 6 int N, K;
 7 int e, u, v;
 8 int tot;
 9 int f[maxn*3];
10 inline int read(){ //读入优化
11 int x=0, f=1;
12 int c=getchar();
13 while(!isdigit(c)){
14 f=c=='-'?-1:1;
15 c=getchar();
16 }
17 while(isdigit(c)){
18 x=(x<<1)+(x<<3)+(c^48);
19 c=getchar();
20 }
21 return f*x;
22 }
23 inline int find(int x)
24 {
25 return x==f[x]?x:f[x]=find(f[x]);
26 }
27 int main()
28 {
29 //cin>>N>>K;
30 N=read();
31 K=read();
32 for(int i=0;i<=N*3;i++)f[i]=i;//初始化
33 while(K--)
34 {
35 //cin>>e>>u>>v;
36 e=read();
37 u=read();
38 v=read();
39 if(u>N||v>N){//不合法情况
40 tot++;
41 continue;
42 }
43 if(e==1)//uv同类
44 {
45 if(find(u+N)==find(v)||find(v)==find(u+2*N))//u吃v或v吃u
46 {
47 tot++;
48 continue;
49 }
50 f[find(u)]=f[find(v)];//自身同类
51 f[find(u+N)]=f[find(v+N)];//猎物同类
52 f[find(u+2*N)]=f[find(v+2*N)];//天敌同类
53 }
54 else//u吃v
55 {
56 if(u==v||find(u)==find(v)||find(u+2*N)==find(v)){//u与v同类或v吃u
57 tot++;
58 continue;
59 }
60 f[find(u)]=f[find(v+2*N)];//u是v的天敌
61 f[find(u+N)]=f[find(v)];//u的猎物是v
62 f[find(u+2*N)]=f[find(v+N)];//u的天敌是v的猎物
63 }
64 }
65 cout<<tot<<endl;
66 //system("pause");
67 return 0;
68 }
 

程序分析

使用了并查集,用于方便地表示与查询集合关系。不同的是,下标范围不是而是,目的是在表示当前各个集合关系的同时,还需表示各成员的其他关系(吃与被吃),理论上用3个数组分别表示各个关系也是可以的,但是会更加繁琐一些。另外,使用了读入优化。

 

心得

并查集真的很好用啊,并查集的加权我之前理解为就是为每一个节点加上它的值(表示管理的子节点数目等),这个题目的并查集用法有点难想QAQ,不过思路还是很好理解的。前几天学的Kruskal的最小生成树算法,使用并查集表示各点的连接状态,也是非常有效的。至于并查集的更深入的应用还有待进一步学习。另外,读入优化的加速效果还是挺感人的,之前写过一个题目1000MS980MS险过,可能就是读入优化帮的忙(不过还是不要擦边水过的好~)。

posted @ 2020-11-21 19:09  liacaca  阅读(139)  评论(0)    收藏  举报