AcWing 240. 食物链
1.题目描述
动物王国中有三类动物 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
2.题目算法与思路
算法:带权并查集
先回顾一下普通并查集
int find(int x) // 并查集
{
if (p[x] != x) p[x] = find(p[x]);//不断递归父节点,直到p[x]==x就是找到根节点
return p[x];//返回x的根节点
}
再来看看这题要用到的并查集
int find(int x)//食物链需要的带权并查集
{
if(x!=p[x]) //和普通版一样
{
int type=find(p[x]);//记录当前找到的父节点
d[x]+=d[p[x]];//更新x的权值,我们下面说
p[x]=type;//和普通版一样
}
return p[x];//返回x的根节点
}
让我们先来理解一下d数组的含义
d[i]的正确理解,应是第 i 个节点到其父节点距离。find()函数进行了路径压缩,当查询某个节点 i 时,如果 i 的父节点不为根节点的话,就会进行递归调用,将 i 节点沿途路径上所有节点均指向父节点,此时的 d[i] 存放的是 i 到父节点,也就是根节点的距离。
下面为了更好的理解函数调用过程以及d[i]的变化,我将插图分享给大家
牢记A 吃 B,B 吃 C,C 吃 A是一个环形
借用一位大佬的图片
了解之后我们直接上AC代码
#include<bits/stdc++.h>
using namespace std;
const int N=100005;
int p[N];//并查集数组
int d[N];//记录x到根节点的距离
int n,m;
int find(int x)
{
if(x!=p[x])
{
int type=find(p[x]);
d[x]+=d[p[x]];//记录x到根节点的距离
p[x]=type;
}
return p[x];
}
int main()
{
for(int i=0;i<=100005;i++) p[i]=i;//初始化并查集数组
cin>>n>>m;
int res=0;//谎言数
for(int i=1;i<=m;i++)
{
int a,b,c;
cin>>a>>b>>c;
if(b>n || c>n) //如果动物序号比总序号大则一定是假的
{
res++;
}
else
{
int pa=find(b);
int pb=find(c);//求出b,c的根节点
if(a==1)
{
if(pa==pb && (d[c]-d[b])%3) res++;//如果b,c是同类
else if(pa!=pb)
{
p[pa]=pb;
/*因为合并b和c所在集合多出了一段长度
这块长度是find(b)到find(c)的距离
所以求多出来的这块部分的长度
当x和y是同类时,有这样的特性
(d[b]+d[find[b]]-d[c])%3==0
这里的d[b]是还未合并时,b到find[b]的距离
*/
d[pa]=d[c]-d[b];
}
}
else
{
/*
当b和c在一个集合中时,由题目可知,b捕食c
此时有
b到根节点的距离-c到根节点的距离=1+3k k为任意
实数
∴当(d[b]-d[c]-1-3k)%3 ==0 时可确认
x捕食y
反之当(d[x]-d[y]-1-3k)%3 !=0
x不可能捕食y
*/
if(pa==pb && (d[b]-d[c]-1)%3) res++;
else if(pa!=pb)
{
p[pa]=pb;
/*
设find(x)到find(y)的距离为d([find(x)])
此时有d[b]+d([find(b)])-d[c]=3k+1
∴d[find(b)]=-d[b]+d[c]+1+3k
*/
d[pa]=d[c]-d[b]+1;
}
}
}
}
printf("%d",res);//输出谎言数
return 0;
}
// 100 7
// 1 101 1
// 2 1 2
// 2 2 3
// 2 3 3
// 1 1 3
// 2 3 1
// 1 5 5
// 3