动物王国中有三类动物 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; }
时间: 2025-06-17 09:15:18 浏览: 17
<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。
- **合并操作**:调整合并后的根节点距离,使得后续查询能正确反映当前说法的关系。
此方法高效处理大规模数据,时间复杂度接近线性,适用于题目给定的数据范围。
阅读全文
相关推荐














