食物链(权值并查集)

Description

动物王国中有三类动物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),输出假话的总数。

Input

第一行是两个整数N和K,以一个空格分隔。
以下K行每行是三个正整数 D,X,Y,两数之间用一个空格隔开,其中D表示说法的种类。
若D=1,则表示X和Y是同类。
若D=2,则表示X吃Y。

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

solution1

在这里插入图片描述
我们把它们之间的关系转化成模3向量,A吃B, B吃C,C吃A,就是 A B = B C = C A = 1 \vec{AB}=\vec{BC}=\vec{CA}=1 A C = 2 \vec{AC}=2 就表示C吃A。

三类关系,假设x的父亲是y,y->x:

  • 0 同类
  • 1 y吃x
  • 2 x吃y

然后我们看各个情况的传递关系:(图中的含义大家对应着翻译过来就很清晰了)
在这里插入图片描述
下图分别对应code1中的46行和57行。
在这里插入图片描述

code1

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5;
int f[N]; //f[i]存点i的父节点编号
int r[N]; //r[i]存点i与父节点的关系,0,1,2
int findd(int x)
{
    if (f[x] == x)
        return x;
    int t = f[x];
    f[x] = findd(f[x]); //每次压缩路径前把原来的父子结点对应的关系r更新
    r[x] = (r[x] + r[t]) % 3;
    return f[x];
}
void mergee(int d, int x, int y)
{
    int xx = findd(x), yy = findd(y);
    f[yy] = xx;
    r[yy] = (r[x] + (d - 1) - r[y] + 3) % 3;
}
int main()
{
    ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
    int n, k;
    cin >> n >> k;
    for (int i = 0; i <= n; i++)
    {
        f[i] = i;
    }
    int ans = 0;
    while (k--)
    {
        int d, x, y;
        cin >> d >> x >> y;
        if (x > n || y > n || (d == 2 && x == y))
        {
            ans++;
            continue;
        }
        if (d == 1) // x,y是同类
        {
            if (findd(x) != findd(y)) // 如果x和y之间还没有任何关系,直接按照关系d合并
            {
                mergee(d, x, y);
            }
            else if (r[x] != r[y]) // 如果x和y之前已经存在某种关系,这里应该是r[x]+0!=r[y],对应上面的传递关系
            {
                ans++;
            }
        }
        else // d==2 x吃y
        {
            if (findd(x) != findd(y))
            {
                mergee(d, x, y);
            }
            else if ((r[x] + 1) % 3 != r[y]) // 对应上述传递关系
            {
                ans++;
            }
        }
    }
    cout << ans << endl;
    return 0;
}

solution2

三倍并查集方法。上面solution1的方法中结点 i i 与父节点的关系有同类、吃与被吃三种,但是solution2的这个方法点之间的关系是通过不同群系之间的编号来确定的,边连接的两个点之间并没有某种确定的关系。
我们假设三个群系 G 0 G_0 G 1 G_1 G 2 G_2 ,每个群系中都有相同的N个动物,分别编号1~n、n+1~2n、2n+1~3n。
G i G_i 来说, G ( i 1 + 3 ) % 3 G_{(i-1+3)\%3} 狩猎 G i G_i G ( i + 1 ) % 3 G_{(i+1)\%3} G i G_i 狩猎,简单说就是 G 0 G_0 G 1 G_1 G 1 G_1 G 2 G_2 G 2 G_2 G 0 G_0
同类、吃、被吃这三种关系是通过不同群系之间的编号来确定的,只要它们合并到一个集合中,那么就可以通过编号来判断它们之间的关系。 点与点之间的连线并没有什么实际意义。
在这里插入图片描述
比如样例,上图是路径压缩后的并查集, x y x\rightarrow y 表示 f [ y ] = x f[y]=x ,A吃B,B吃C,C吃A。
现在有三个集合 {A1,C3,B2},{B1,A3,C2},{C1,A2,B3}。比如第一个集合就可以通过A吃B、B吃C、C吃A来判断:A1吃B2,B2吃C3,C3吃A1。同理其他两个集合。

code2

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5;
int f[N * 3]; // A:1~n B:n+1~2n C:2n+1~3n
int findd(int x)
{
    return f[x] == x ? x : f[x] = findd(f[x]);
}
int main()
{
    ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
    int n, k;
    cin >> n >> k;
    for (int i = 1; i <= n * 3; i++)
    {
        f[i] = i;
    }
    int ans = 0;
    int t = 1;
    while (k--)
    {
        int d, x, y;
        cin >> d >> x >> y;
        if (x > n || y > n || (d == 2 && x == y))
        {
            ans++;
            continue;
        }
        if (d == 1)
        {
        	// 判断x和y是否同类的方法是 x不吃y,y不吃x
            if (findd(x) == findd(y + n) || findd(x + n) == findd(y))
            {
                ans++;
            }
            else
            {
            	// 合并
                f[findd(y)] = findd(x);
                f[findd(y + n)] = findd(x + n);
                f[findd(y + n + n)] = findd(x + n + n);
            }
        }
        else
        {
        	// 若x、y是同类,或者y吃x,则是假话
            if (findd(x) == findd(y) || findd(x + n) == findd(y))
            {
                ans++;
            }
            else
            {
            	// 合并
                f[findd(y + n)] = findd(x);
                f[findd(y + n + n)] = findd(x + n);
                f[findd(y)] = findd(x + n + n);
            }
        }
    }
    cout << ans << endl;
    return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_44169557/article/details/107707077