POJ - 1182 食物链 (kuangbin - 并查集)

这篇博客介绍了一个关于动物食物链的问题,其中动物分为A、B、C三类,形成环状食物链。题目要求根据输入的动物关系判断真假话的数量。解题方法采用了加权并查集,通过扩展元素范围到3N,分别表示同类、天敌和猎物,并利用并查集维护环形关系。博客重点在于理解并查集的应用以及如何处理环形食物链的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述

  动物王国中有三类动物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。

输出格式

  只有一个整数,表示假话的数目。

输入输出样例
输入

100 7
1 101 1
2 1 2
2 2 3
2 3 3
1 1 3
2 3 1
1 5 5

输出

3

题目链接

分析:

  这道题属于加权并查集下的分支:种类并查集,可目前我对于这方面的知识还不太熟悉,所以用了有点暴力的方法解决这道题。我将1-n个元素扩大为1-3n个元素,使用[1,3n]个并查集(每一个并查集中的所有元素都具有同一种特性,不同并查集中不存在相同元素)来维护3n元素彼此的关系。
  在这里x元素,x+n元素,x+2n元素三者的关系被定义为:
    x∈[1, n]–x元素的同类
    x∈[n+1, 2n]–x元素的天敌
    x∈[2n+1, 3n]–x元素的猎物
  通过x+n元素来确定x元素目前已知的天敌,也可以通过x+2n元素来确定x元素目前的猎物,通过x元素本身来确定x的同类,
  则判断真假要有以下几个原则:
    1.两个同类元素的天敌集合是同一个集合,猎物集合也是同一个集合
    2.天敌的天敌是猎物
    3.猎物的猎物是天敌
  对于一句真话,当x元素,y元素是同类时,将他们两者的天敌集合(x+n元素与y+n元素所在集合)和猎物集合(x+2n元素与y+2n元素所在集合)以及自身所在的集合分别合并。当x元素是y元素的天敌时,将x元素所在集合与y元素的天敌集合合并,将y元素所在集合和x元素的猎物集合合并,将x元素的天敌集合和y元素的猎物集合合并

这道题需要注意的地方:

  这道题是一道十分明显的并查集的题目,这点不难发现 但是容易进入一个误区,就是以动物的种类单独分并查集,这道题核心是维护A吃B,B吃C,C吃A这个奇妙的环形关系,普通的单倍并查集显然维护不了这个关系,但是我们可以开多倍并查集来维护这个关系。

代码如下:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <string>
#include <cstring>
#include <map>
#include <queue>
#define maxn 150010
using namesp
动物王国中有三类动物 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 句话有的是真的,有的是假的。 当一句话满足下列三条之一时,这句话就是假话,否则就是真话。 当前的话与前面的某些真的话冲突,就是假话; 当前的话中 X 或 Y 比 N 大,就是假话; 当前的话表示 X 吃 X ,就是假话。 你的任务是根据给定的 N 和 K 句话,输出假话的总数。 输入格式 第一行是两个整数 N 和 K ,以一个空格分隔。 以下 K 行每行是三个正整数 D,X,Y ,两数之间用一个空格隔开,其中 D 表示说法的种类。 若 D=1 ,则表示 X 和 Y 是同类。 若 D=2 ,则表示 X 吃 Y 。 输出格式 只有一个整数,表示假话的数目。 数据范围 1≤N≤50000 , 0≤K≤100000 输入样例: 100 7 1 101 1 2 1 2 2 2 3 2 3 3 1 1 3 2 3 1 1 5 5 输出样例: 3 难度:中等 时/空限制:1s / 64MB 总通过数:67053 总尝试数:147208 来源: 《算法竞赛进阶指南》模板题NOI2001POJ1182kuangbin专题《信息学奥赛一本通》语言及算法基础篇 算法标签 #include<iostream> using namespace std; int p[100010],d[100010]; int find(int x){ if(x!=p[x]){ int u=find(p[x]); d[x]+=d[p[x]]; p[x]=u; } return p[x]; } int main(){ int n,m; scanf("%d%d",&n,&m); for(int i=1;i<-n;i++){ p[i]=i; d[i]=0; } int sum=0; while(m--){ int k,x,y; scanf("%d%d%d",&k,&x,&y); if(k==2&&x==y){ sum++; continue; } if(x>n||y>n){ sum++; continue; } if(k==1){ int px=find(x),py=find(y); if(px==py&&(d[x]-d[y])%3){ sum++; continue; } else if(px!=py){ d[px]=d[y]-d[x]; p[px]=py; } } else { int px=find(x),py=find(y); if(px==py&&d[x]%(d[x]-d[y]-1)%3){ sum++; continue; } if(px!=py){ d[px]=d[y]-d[x]+1; p[px]=py; } } } printf("%d",sum); return 0; }
最新发布
03-12
<think>哦,这个问题看起来有点挑战性。我需要仔细理解题目要求。题目是关于动物王国的食物链关系,A吃B,B吃C,C吃A。然后给出一些说法,判断哪些是假话。假话的条件有三个:与之前正确的冲突、X或Y超过N、或者自己吃自己。我们的任务是统计假话的总数。 首先,我得想怎么用数据结构来处理这些关系。可能用的是并查集,因为涉及到同类或者捕食关系的合并和查询。并查集不仅可以维护集合关系,还能处理节点之间的某种相对关系。比如,可以记录每个节点到根节点的距离,然后通过模3运算来判断它们的关系类型(同类、捕食、被捕食)。 那具体来说,怎么用并查集处理这三种类型呢?假设每个动物属于A、B、C中的一个,但具体是什么类型并不重要,重要的是它们之间的关系。比如,如果X和Y是同类,那么它们的模3余数应该相同。如果X吃Y,那么X的余数应该比Y大1(模3后)。 然后,处理每个说法的时候,需要检查是否满足条件。例如,当处理说法1 X Y(X和Y同类),需要检查当前的X和Y是否已经在同一个集合里。如果已经在一个集合里,那么它们的d[x]和d[y]的差是否模3等于0。如果不等于,则说明矛盾,这句话是假话。如果不在同一个集合,那么需要合并它们的集合,并调整d数组的值。 同样,处理说法2 X Y(X吃Y)时,要检查是否满足X的余数比Y大1。这时候,可能需要计算两者的d的差是否等于1模3。或者,可能需要考虑其他方式,比如(d[x] - d[y] -1) %3 ==0? 这里的模3运算可能有点复杂,需要仔细处理。比如,假设每个节点的d表示到父节点的距离,那么到根节点的总距离可以决定它的类型。比如,余0是同类,余1是被吃,余2是吃别人?或者反过来? 比如,假设X的根是R。那么d[X]表示X到R的距离。如果X和Y是同类,那么d[X] ≡ d[Y] mod3。如果X吃Y,那么d[X] ≡ d[Y] +1 mod3。或者说,当X吃Y时,两者的关系是X的余数比Y大1。比如,如果Y的余数是0,那么X的余数是1;如果Y是1,X是2;Y是2,X是0?这时候可能需要取模后的差为1。 或者,可能另一种方式:X吃Y的条件是 (d[x] -d[y] -1) mod3 ==0。或者是否等于1?这可能需要仔细推导。 举个例子,假设X和Y的根相同。如果X和Y同类,那么d[x] ≡d[y] mod3。如果X吃Y,那么d[x] ≡d[y] +1 mod3。此时,(d[x] -d[y]) mod3 ==1。所以,当处理说法2时,如果X和Y已经在一个集合中,需要判断是否满足这个条件。否则,合并两个集合时,需要调整d数组的值,使得X和Y的差满足这一条件。 那并查集的路径压缩如何处理?比如,在find函数中,当递归找到根时,需要同时更新当前节点到根的距离。这可以通过在find函数中进行路径压缩,同时维护d数组的正确性。 现在看代码部分。给出的代码存在一些问题,比如,在初始化循环中,条件写成了i<=-n,这显然是错误的。应该是i<=n。这可能是一个笔误或者代码错误。例如,原题的正确代码中,初始化的循环应该是for(int i=1; i<=n; i++),而这里的代码写成了i<-n,这会导致循环不执行,从而导致并查集初始化失败。这会使得所有后续的find操作出现问题,因为p数组没有被正确初始化,从而导致错误。 另外,在处理第二种说法时的条件判断也存在疑问。比如,代码中的这一行: if(px==py && d[x]%(d[x]-d[y]-1)%3) 这可能有问题。正确的判断应该是检查(d[x] -d[y] -1)%3是否等于0。但原代码中的写法可能是想表达这个条件是否为真,但表达式可能不正确。例如,正确的条件应该是( (d[x] -d[y] -1) %3 ) !=0。如果这样,当这个条件成立时,说明当前的陈述是假的。因此,在代码中的判断可能需要修正。 比如,正确的判断应该是:当当前说法是X吃Y时,如果X和Y已经属于同一集合,那么需要验证是否 (d[x] -d[y] -1) %3 ==0。如果余数不等于0,那么这句话是假的,应该sum++。否则,是真的。 因此,在代码中的判断条件可能有错误。原代码中的条件可能写反了或者不正确,比如: 原代码在处理k==2时,条件写的是: if(px==py &&d[x]%(d[x]-d[y]-1)%3) 这可能原本是想写(d[x] -d[y] -1)%3 !=0,但表达式写错了。例如,正确的应该是 (d[x] -d[y] -1) %3 !=0,这时候sum++。但是原代码中的表达式可能无法正确计算这个条件。 比如,原式中的d[x]%(d[x]-d[y]-1)的值可能不是我们需要的,而且后面的%3是否正确? 这可能是一个错误点。例如,正确的判断应该是: 如果px == py,并且 (d[x] - d[y] -1) %3 !=0,那么是假话。 所以,正确的条件应该是: if (px == py && (d[x] - d[y] - 1) % 3 != 0) { sum++ } 或者,另一种写法可能是将表达式转换为: if (px == py && ( (d[x] - d[y]) %3 ) !=1 ) 因为X吃Y的条件是d[x] -d[y] ≡1 mod3。所以,如果当前的说法是X吃Y,那么在同一个集合中的情况下,必须满足两者的差模3等于1。否则,是假话。 所以,当处理k==2时,正确的条件应该是:如果X和Y在同一个集合,并且(d[x] -d[y])%3 !=1,那么是假话。这时候sum++。 那么,原代码中的条件写的是: d[x] % (d[x] -d[y] -1) %3。这可能存在计算错误。例如,假设d[x]-d[y]-1等于0,那么模运算会出错。或者,这可能是一个完全错误的表达式。比如,作者可能想写的是(d[x] -d[y] -1)%3 !=0,但代码中的写法错误。 此外,合并集合时的处理是否正确?比如,当k==1时,合并X和Y,此时需要保证d[x] + d[px] ≡d[y] mod3。或者,合并时,假设px是x的根,py是y的根。此时,合并px到py,并且调整d[px]的值,使得x和y的关系满足同类。也就是,d[x] + d[px] ≡d[y] mod3 → d[px] = d[y] -d[x]。这样,当合并之后,x的父节点是py,路径压缩时会自动更新d[x]的值? 所以,在合并时,当k==1时,即X和Y同类,此时需要让px和py合并,并且调整d[px]的值。例如,当前的x的根是px,y的根是py。合并时,将px的父设为py,并设置d[px] = (d[y] -d[x] ) %3。这样,在后续的find操作中,路径压缩时会自动更新各个节点的d值。 同样,当处理k==2时,即X吃Y,此时需要保证d[x] ≡d[y] +1 mod3。合并时,将px的父设为py,并设置d[px] = (d[y] -d[x] +1 ) mod3。或者,可能不需要mod3,因为之后的计算会自动处理模运算。 现在,回到原代码中的处理: 在k==1的情况下,合并时的处理: d[px] = d[y] -d[x]; 而在k==2的情况下: d[px] =d[y] -d[x] +1; 这可能正确,因为当合并两个集合时,需要让合并后的关系满足当前的条件。例如,对于k==1,合并之后,x和y的同类关系必须满足d[x] + d[px] ≡d[y] mod3。因此,d[px]应该等于d[y] -d[x],这样合并后,x到根的总距离是d[x] +d[px] =d[y],与y到根的距离d[y]相等,模3后相等,满足同类关系。 同样,对于k==2的情况,X吃Y,合并后应该满足d[x] +d[px] ≡d[y] +1 mod3。所以,d[px] =d[y] -d[x] +1。这样,x到根的总距离是d[x] +d[px] =d[y] +1,而y到根的距离是d[y]。此时,差为1,模3等于1,满足X吃Y的条件。 这可能就是代码中的逻辑。但是,代码中的模运算处理是否正确?例如,在合并时,是否需要模3?或者,d数组存储的是相对于父节点的距离,而路径压缩时,会通过递归处理将总距离模3? 假设在find函数中,路径压缩时会累加各个父节点的d值,并且在合并时,d数组的值可能不需要模3,因为在后续的find过程中,路径压缩时会自动处理总距离的模运算。例如,当合并时设置d[px]的值为d[y] -d[x],这可能导致该值为负数或者超过3,但在后续的find操作中,路径压缩时,d[x]会被计算为总距离,然后模3来判断类型。因此,在合并时,d数组的值可能不需要模3,只要保持正确的相对关系即可。 那么,回到原代码中的错误。例如,初始化的循环条件错误: for(int i=1;i<-n;i++){ 这里明显是写错了,应该是i<=n。这会导致初始化循环不执行,因为当n是正数时,i初始化为1,i <= -n 显然不成立。所以,这会导致p数组未被正确初始化,所有元素的父节点可能为0或者初始值,从而导致后续的find函数出错。这可能是一个笔误或者代码输入错误,导致整个程序的错误。 此外,在处理k==2的条件时,原代码中的判断条件可能有误。比如: if(px==py &&d[x]%(d[x]-d[y]-1)%3) 这个表达式可能存在计算错误。例如,当d[x]-d[y]-1等于0,那么模运算会导致除以零的错误。此外,原意可能是判断(d[x] -d[y] -1)%3是否不等于0。正确的条件应该是: ( (d[x] -d[y] -1) %3 ) !=0 但原代码中的写法是d[x]取模于(d[x]-d[y]-1)的结果,然后这个结果再取模3。这可能完全不是想要的条件。例如,假设d[x]=4,d[y]=2,则d[x]-d[y]-1=1。那么d[x]%1=0,0%3=0,条件不成立。这显然不符合正确的判断条件。这说明原代码中的条件写法是错误的,导致逻辑错误。 综上,原代码存在两个主要错误:初始化循环条件错误,导致p数组未被正确初始化;以及k==2时的条件判断错误,导致无法正确检测假话。 因此,正确的代码应该修正这两个错误。例如,初始化循环应改为: for(int i=1; i<=n; i++) 处理k==2时的条件应改为: if (px == py && (d[x] -d[y] -1) %3 !=0 ) 或者,用另一种方式表达。比如,如果当前的说法是X吃Y,那么在同一集合中的情况下,必须满足(d[x] -d[y])%3 ==1。否则,是假话。所以条件可以写成: if (px == py && ( (d[x] -d[y]) %3 ) !=1 ) 这样可能更直观。 因此,在代码中,当处理k==2时,正确的条件判断应为: if(px == py && ( (d[x] - d[y]) %3 ) !=1 ) 这样,如果当前X和Y在同一个集合中,并且它们的d差模3不等于1,则说明这句话是假的。 总结,正确的代码应该进行以下修正: 1. 初始化循环的条件改为i<=n。 2. 在处理k==2的情况下,条件判断修正为检查(d[x]-d[y])%3是否等于1。如果不等于,sum++。 此外,在合并操作时,当px和py不同根时,需要调整d[px]的值。例如,k==1时,d[px] =d[y] -d[x]。而k==2时,d[px] =d[y] -d[x] +1。这部分处理是否正确? 假设合并时,x的根是px,y的根是py。当合并到py时,x的路径会被更新。例如,当合并px到py时,新的父节点是py。此时,根据x和y的关系,必须调整px的d值,使得x到py的总距离满足相应的条件。 例如,对于k==1的情况,合并后,x和y必须是同类。所以,d[x] +d[px] ≡d[y] mod3 → d[px] ≡d[y] -d[x] mod3。这可能不需要取模,因为后续的find操作会自动处理。 所以,在代码中的处理是正确的,即k==1时,d[px] =d[y] -d[x]。k==2时,d[px] =d[y] -d[x] +1. 这样,在路径压缩的过程中,d数组的值会被正确累加,而模3的运算会在判断的时候处理。 现在,回到原题的正确解法,正确的代码应该包括这些修正。 现在,我需要根据上述分析,编写正确的解题思路和代码。</think>### 问题分析 题目中的三类动物构成环形食物链,需要处理两种说法(同类和捕食)并判断真假。通过并查集维护每个动物到根节点的相对关系,利用模3运算判断关系类型: - **同类**:两节点到根的距离之差模3等于0。 - **捕食**:当前节点到根的距离比对方大1模3。 ### 解题思路 1. **并查集初始化**:每个动物的父节点初始化为自身,距离初始化为0。 2. **处理每条语句**: - 检查当前语句是否越界或自食,直接判假。 - 查找两个节点的根,若根相同: - **同类**:检查距离差是否模3为0。 - **捕食**:检查当前节点距离是否比对方大1模3。 - 若根不同,合并两个集合,并调整根节点间的距离以满足当前说法。 ### 代码实现 ```cpp #include <iostream> using namespace std; const int MAXN = 50010; int p[MAXN], d[MAXN]; // d[x]存储x到父节点的距离(在路径压缩后会变成到根的距离的差值) int find(int x) { if (p[x] != x) { int root = find(p[x]); // 递归找到根 d[x] += d[p[x]]; // 累加路径上的距离 p[x] = root; // 路径压缩 } return p[x]; } int main() { int n, k; scanf("%d%d", &n, &k); for (int i = 1; i <= n; i++) { p[i] = i; d[i] = 0; } int falseCount = 0; while (k--) { int op, x, y; scanf("%d%d%d", &op, &x, &y); if (x > n || y > n) { // 越界 falseCount++; continue; } if (op == 2 && x == y) { // 自食 falseCount++; continue; } int px = find(x), py = find(y); if (op == 1) { // X和Y同类 if (px == py) { if ((d[x] - d[y]) % 3 != 0) // 同类但模3不等 falseCount++; } else { p[px] = py; d[px] = d[y] - d[x]; // 合并后保证d[x]+d[px] = d[y] } } else { // X吃Y if (px == py) { if ((d[x] - d[y] - 1) % 3 != 0) // X的模应比Y大1 falseCount++; } else { p[px] = py; d[px] = d[y] - d[x] + 1; // 合并后保证d[x]+d[px] = d[y] +1 } } } printf("%d\n", falseCount); return 0; } ``` ### 关键点解释 - **并查集路径压缩**:在查找根节点时同步更新距离,确保每个节点直接指向根并维护正确的相对距离。 - **模3运算**:利用距离差的模3结果判断同类或捕食关系: - 同类:差值为0。 - 捕食:差值为1。 - **合并操作**:调整合并后的根节点距离,使得后续查询能正确反映当前说法的关系。 此方法高效处理大规模数据,时间复杂度接近线性,适用于题目给定的数据范围。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值