牛客算法竞赛入门课第四节习题 并查集

食物链

https://ac.nowcoder.com/acm/problem/16884

食物链

题目描述

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

输入描述:

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

输出描述:

只有一个整数,表示假话的数目。

题解

完全合并

就是开一个3 * n 的数组,分别表示为三种的情况,

那么我们来看一下哪些情况是同类
(1)a吃c,b也吃c,则a和b同类;
(2)a吃c,c吃d,d吃b,则a和b同类;

我们就将a , b,和 a + n ,b +n 和 a+ n + n , b +n + n分别并起来

然后出现了a吃b我就将 a ,b + n ,和 a + n , b +n + n ,还有 a +n + n , b分别并起来,后面只要出现了冲突的地方就表示是假话,

code

#include<bits/stdc++.h>
using namespace std;
#define js ios::sync_with_stdio(false);cin.tie(0); cout.tie(0)
typedef long long ll; typedef unsigned long long ull; typedef long double ld;
const ll MOD = 1e9 + 7;
inline ll read() { ll s = 0, w = 1; char ch = getchar(); for (; !isdigit(ch); ch = getchar()) if (ch == '-') w = -1; for (; isdigit(ch); ch = getchar())    s = (s << 1) + (s << 3) + (ch ^ 48); return s * w; }
inline void write(ll x) { if (!x) { putchar('0'); return; } char F[40]; ll tmp = x > 0 ? x : -x; if (x < 0)putchar('-');  int cnt = 0;    while (tmp > 0) { F[cnt++] = tmp % 10 + '0';     tmp /= 10; }    while (cnt > 0)putchar(F[--cnt]); }
inline ll gcd(ll x, ll y) { return y ? gcd(y, x % y) : x; }
ll qpow(ll a, ll b) { ll ans = 1;   while (b) { if (b & 1)  ans *= a;       b >>= 1;      a *= a; }   return ans; }   ll qpow(ll a, ll b, ll mod) { ll ans = 1; while (b) { if (b & 1)(ans *= a) %= mod; b >>= 1; (a *= a) %= mod; }return ans % mod; }
inline int lowbit(int x) { return x & (-x); }

const int N = 1e7 + 7;
const int INF = 0x3f3f3f3f;

int fa[N];

int find(int x){
    return fa[x] == x ? x : fa[x] = find(fa[x]);
}

void merge(int x , int y){
    fa[find(x)] = find(y);
}

int main(void){
    int n , k ;
    cin>>n>>k;
    int ans = 0;
    for(int i = 1 ; i <= 3 * n ; ++i){
        fa[i] = i;
    }

    for(int i = 1 ; i <= k ; ++i){
        int d , x , y;
        cin>>d>>x>>y;
        if(x > n || y > n){
            ans++;
            continue;
        }
        if(d == 1){
            if(find(x) == find(y + n) || find(x) == find(y + n + n)){
                ans++;
                continue;
            }
            merge(x , y);
            merge(x + n , y + n);
            merge(x + n + n , y + n + n);
        }
        else{
            if(find(x) == find(y) || find(x) == find(y + n + n)){
                ans++;
                continue;
            }
            merge(x , y + n);
            merge(x + n , y + n + n);
            merge(x + n + n , y);
        }
    }
    cout<<ans<<endl;
    return 0;
}  

带权并查集

我们可以给他加上一个权值relation表示他和他的父节点的关系
struct node
{
    int par;                     //p[i].par表示节点i的父节点
    int relation;                   //p[i].relation表示节点i与其父节点(即p[i].par)的关系
}fa[N];

0表示和根节点是同一类

1表示被根节点吃

2表示吃根节点

初始话的时候relation就初始化为0,因为一开始fa[i]=i,此时相当于他的父节点就是他自己,即为同类

void init(int n)
{
    int i;
    for(i = 1;i <= n; ++i)
    {
        p[i].par = i;            //初始时集合编号就设置为自身
        p[i].relation = 0;        //因为p[i].par=i,即节点i的父亲节点就是自身,所以此时节点i与其父亲节点的关系为同类(即p[i].relation=0)
    }
}

接下来就是合并操作了
首先知道的是,在原来的并查集操作中,会将节点与节点的父节点的父节点连接起来,从而起到直接查询是否是同一个根节点的操作,那么这这里,我们进行合并的时候,就要意识到(x表示当前节点,xroot表示x的父节点,xxroot表示父节点的父节点)和父节点的关系的变化,比如xroot和xxroot为同类,而xroot和x是吃的关系,那么和xxroot的关系也应该是被吃的关系

即我们的关系的更新,这里我们需要用到向量的思维。

在合并操作之后,如果两个节点的根节点为同一个,并且他们们和根节点的关系相同(和根节点为同类或者都被根节点吃或者都吃根节点)那么他们也是同类

那么如果节点a吃根节点而根节点吃b,那么b就吃a,或者a和根节点为同类,根节点吃b,那么a也吃b

那么我们就把这2个元素之间的关系量转化为一个偏移量

x->y 偏移量0时 x和y同类 (即同吃或同被吃或同类

x->y 偏移量1时 x被y吃 (即x吃根 根吃y

x->y 偏移量2时 x吃y(即x和根为同类,根吃y

code

#include<bits/stdc++.h>
using namespace std;
#define js ios::sync_with_stdio(false);cin.tie(0); cout.tie(0)
typedef long long ll; typedef unsigned long long ull; typedef long double ld;
const ll MOD = 1e9 + 7;
inline ll read() { ll s = 0, w = 1; char ch = getchar(); for (; !isdigit(ch); ch = getchar()) if (ch == '-') w = -1; for (; isdigit(ch); ch = getchar())    s = (s << 1) + (s << 3) + (ch ^ 48); return s * w; }
inline void write(ll x) { if (!x) { putchar('0'); return; } char F[40]; ll tmp = x > 0 ? x : -x; if (x < 0)putchar('-');  int cnt = 0;    while (tmp > 0) { F[cnt++] = tmp % 10 + '0';     tmp /= 10; }    while (cnt > 0)putchar(F[--cnt]); }
inline ll gcd(ll x, ll y) { return y ? gcd(y, x % y) : x; }
ll qpow(ll a, ll b) { ll ans = 1;   while (b) { if (b & 1)  ans *= a;       b >>= 1;      a *= a; }   return ans; }   ll qpow(ll a, ll b, ll mod) { ll ans = 1; while (b) { if (b & 1)(ans *= a) %= mod; b >>= 1; (a *= a) %= mod; }return ans % mod; }
inline int lowbit(int x) { return x & (-x); }

#define N 50010

struct node
{
    int pre;
    int relation;
};
node p[N];

int find(int x) //查找根结点
{
    int temp;
    if(x == p[x].pre)
        return x;
    temp = p[x].pre; //路径压缩
    p[x].pre = find(temp);
    p[x].relation = (p[x].relation + p[temp].relation) % 3; //关系域更新
    return p[x].pre; //根结点
}

int main()
{
    int n, k;
    int ope, a, b;
    int root1, root2;
    int sum = 0; //假话数量
    scanf("%d%d", &n, &k);
    for(int i = 1; i <= n; ++i) //初始化
    {
        p[i].pre = i;
        p[i].relation = 0;
    }
    for(int i = 1; i <= k; ++i)
    {
        scanf("%d%d%d", &ope, &a, &b);
        if(a > n || b > n) //条件2
        {
            sum++;
            continue;
        }
        if(ope == 2 && a == b) //条件3
        {
            sum++;
            continue;
        }
        root1 = find(a);
        root2 = find(b);
        if(root1 != root2) // 合并
        {
            p[root2].pre = root1;
            p[root2].relation = (3 + (ope - 1) +p[a].relation - p[b].relation) % 3;
        }
        else //验证是否和题目给出的关系一致
        {
            if(ope == 1 && p[a].relation != p[b].relation)
            {
                sum++;
                continue;
            }
            if(ope == 2 && ((3 - p[a].relation + p[b].relation) % 3 != ope - 1))
            {
                sum++;
                continue;}
        }
    }
    printf("%d\n", sum);
    return 0;
}
全部评论

相关推荐

铁锈不腻玩家:下面那个袁先生删了,问他怎么回事,头像都换不明白
点赞 评论 收藏
分享
11-06 10:58
已编辑
门头沟学院 嵌入式工程师
双非25想找富婆不想打工:哦,这该死的伦敦腔,我敢打赌,你简直是个天才,如果我有offer的话,我一定用offer狠狠的打在你的脸上
点赞 评论 收藏
分享
评论
4
收藏
分享
牛客网
牛客企业服务