AcWing 240. 食物链 基础数据结构之并查集

题目描述

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

题目分析

首先,查看输入,k行询问,每次说明两只动物x,y的关系。
其次,查看一句话如何才能是假话

①当前的话中X或Y比N大,就是假话,这个判断起来最简单,只需要if(x>n || y>n) ans++;即可。
②当前的话和之前真话冲突或者出现x吃x就是假话,其实出现了x吃x就是和真话冲突。
故我们可以将可以确定正确关系的所有动物放在一个集合当中,初始的时候我们只知道每个动物不吃自己,所以每个动物各成集合。
其次在一个集合当中,我们需要维护当前节点到此集合根节点的距离d[i],因为只有三种动物,所以每个动物所属的种类可以用kind[i] = d[i]%3表示一个确定的集合当中动物的状态关系,可以令kind = 1吃0,2吃1,0吃2,所以当kind[i]%3 == kind[j]%3时i和j为同一物种,kind[i]%3 == kind[j]%3时表示i吃j。

既然是对集合的合并和查询,当然就要用到并查集,只是需要维护一个距离参数而已。

当询问的一句话的两个物种不在同一个集合的时候,说明两个动物关系未定,当前的话就是真话,没有发生冲突,根据当前的话合并两个集合。
有关距离的更新

if(x和y同类)
	(d[sy]+d[y])%3 = d[x]%3
	即d[sy] = (d[x]%3+d[y]%3+3)%3
if(x吃y)
	d[x]%3 = (d[sy] +d[y]+1)%3 
	d[sy] = (d[x]%3-d[y]%3-1+3)
	 

当询问的两个物种在同一个集合的时候,有可能和真话发生冲突。

非冲突状态
	同类:d[x] %3 == d[y]%3
	x吃y: d[x]%3 == (d[y]+1)%3

C++代码实现

#include 
#include 

using namespace std;

const int N = 5e4+10;
int n, k;
int p[N], d[N];//p是并查集的集合,d[i]是i到根节点的距离

//带有路径压缩的并查集查找
int find(int x) {
     
    if(x != p[x]) {
     
        int t = p[x];
        p[x] = find(p[x]);
        d[x] += d[t];//将整个路径的距离加在一起就是当前节点到根节点的距离
    }
    return p[x];
}

int main() {
     
    cin>>n>>k;
    //初始化集合
    for(int i = 1; i < n; ++i) {
     
        p[i] = i;
    }
    int ans= 0;
    while(k--) {
     
        int D, x, y;
        scanf("%d%d%d", &D, &x, &y);
        if(x > n || y > n) {
     
            ans++;
            continue;
        }
        int sx = find(x);
        int sy = find(y);
        //只有在同一个集合才有可能冲突
        if(sx == sy) {
     
            //int dif = (d[x]%3 - d[y]%3 + 3)%3;
            if(D == 1) {
     
                if(d[x]%3 != d[y]%3) {
     
                    ans++;
                }
            }
            else {
     
                if(d[x]%3!= (d[y]+1)%3) {
     
                    ans++;
                }
            }
        }
        else{
     //不会冲突,合并两个集合
            p[sy] = sx;
            //1-->0, 2-->1, 3-->2
            //(d[y]+d[sy])%3 = d[x]%3
            //d[sy] = d[x]-d[y];
            if(D == 1) {
     
                d[sy] = (d[x]%3-d[y]%3+3)%3;
            }
            else{
     
                //(d[y]+d[sy])%3-d[x]%3 = 1
                d[sy] = (d[x]%3-d[y]%3-1+3)%3;
            }        
        }
    }
    cout<<ans<<endl;
    return 0;
}

你可能感兴趣的:(基础算法模板题,数据结构,c++)