【P2024】食物链 (扩展域并查集)

https://www.luogu.org/problem/show?pid=2024

题目描述

动物王国中有三类动物 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,表示有 N 个动物,K 句话。

第二行开始每行一句话(按照题目要求,见样例)

输出

一行,一个整数,表示假话的总数。

输入样例

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 ≤ 5 ∗ 10^4

1 ≤ K ≤ 10^5
对7句话的分析 100 7
1 101 1  假话
2 1 2   真话
2 2 3   真话
2 3 3   假话
1 1 3   假话
2 3 1   真话
1 5 5   真话

思路:

总体思想就是把并查集的空间开3倍当作扩展域,然后存3大类东西,同类域,吃域,被吃域。然后用并差距维护即可
存3倍扩展域的解释:
x1=find(x); 同类
x2=find(x+n); 吃域
x3=find(x+n*2); 被吃域
y1=find(y); 同类
y2=find(y+n); 吃域
y3=find(y+n*2); 被吃域
有了这样的分区域之后:
对于第二种谎话判断不提,输入的时候随手一分析就能看出
对于第一种谎话,如果y吃x或x吃y 因为同类不能吃所以为假话 否则x与y便是同类,即同类域,吃域,被吃域都相同
对于第三种谎话,如果x,y为同类或y吃x所以为假话,否则x的同类等于y被吃域的x,x的吃域等于y的同类,x的被吃域等于y所吃的z(因为就只有3类动物)
扩展域并查集写起来很顺手,只要你分析合理,虽然扩展域并查集提高了对空间的要求(每一个N把一种信息存到一个区块内分开储存),但是仍然是非常优秀的并查集(连merge都不需要专门想一想,只需要将扩展域的关系联系起来即可2333)

代码如下:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ri register int
typedef long long LL;
using namespace std;
const int sz = 5000000;
inline void rd(int &x){
    x=0;bool f=0;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-') f=1;c=getchar();}
    while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
    if(f) x*=-1;
}
inline void we(int x){
    if(x<0) putchar('-'),x*=-1;
    if(x/10) we(x/10);
    putchar(x%10+'0');
}
int fa[sz],ans;
int n,k,d,x,y;
int find(int x){
    return x==fa[x]?x:x=find(fa[x]);
}
int main()
{
    rd(n),rd(k);
    for(ri i=1;i<=k*3;++i) fa[i]=i;
    for(ri i=1;i<=k;++i)
    {
        rd(d),rd(x),rd(y);
        if(x>n||y>n){
            ans++;
            continue;
        }
        int x1=find(x),x2=find(x+n),x3=find(x+n*2);
        int y1=find(y),y2=find(y+n),y3=find(y+n*2);
        if(d==1)
        {
            if(x1==y2||x2==y1) ans++;
            else
            fa[x1]=y1,fa[x2]=y2,fa[x3]=y3;
        }
        if(d==2)
        {
            if(x1==y1||x1==y2) ans++;
            else
            fa[x1]=y3,fa[x2]=y1,fa[x3]=y2;
        }
    }
    we(ans);
    return 0;
}

另一种版本的扩展域并查集

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int sz = 100010;
int ans,n,k,t,x,y;
int fa[sz<<2],rak[sz<<2];
int find(int x){
    return fa[x]==x?x:fa[x]=find(fa[x]);
}
void merge(int x,int y){
    x = find(x);
    y = find(y);
    if(rak[x] > rak[y])
        swap(x,y);
    if(rak[x] == rak[y])
        rak[y] ++;
    fa[x] = y;
}
bool same(int x,int y){
    return find(x) == find(y);
}
bool can(int x)
{
    return (x >= 1 && x <= n);
}
int main()
{
    scanf("%d%d",&n,&t);
    for(int i=1;i<=3*n+1;i++) fa[i] = i;
    for(int i = 1;i <= t;i ++)
    {
        scanf("%d%d%d",&k,&x,&y);
        if(!can(x)||!can(y)){ans ++;continue;}
        if(k == 1)
        {
            if(same(x,y + n) || same(y + 2 * n,x))
            {
                ans ++;
                continue;
            }
            else
            {
                merge(x,y);
                merge(x + n,y + n);
                merge(x + 2 * n,y + 2 * n);
            }
        }
        if(k == 2)
        {
            if(same(x,y) || same(x,y + 2 * n))
            {
                ans ++;
                continue;
            }
            else
            {
                merge(x,y + n);
                merge(x + n,y + 2 * n);
                merge(x + 2 * n,y);
            }
        }
    }
    printf("%d\n",ans);
    return 0;
}

其实这也是道加权并查集的好题,真是好题好题

发布了75 篇原创文章 · 获赞 80 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/qq_36693514/article/details/78318885
今日推荐