POJ 1182【食物链】(边带权/扩展域 并查集)

题目链接 http://poj.org/problem?id=1182

第一种 扩展域

对于几个编号为x的动物  我们用x表示它的同类域,x+n表示它的捕食域,x+2*n表示它的天地域。

我们用并查集来维护动物之间的关系,也即维护这三种域之间的关系

当给定一个指令 1 x y 时,说明x和y是同类,什么情况下这个话是错误的呢,当

x捕食y,或者y捕食x的时候这句话是错的,所以此时我们就查询 x与y+n  y与x+n是否有共同祖先

1 如果有,说明它俩在同一个集合中,那它俩就不可能同类,这句话就是错误的

2 如果没有,那么这句话就是对的,那么我们就合并 x与y,x+n与y+n,x+2*n与y+2*n,把它们并到一个集合中。

当给定一个指令 2 x y时,说明x捕食y,什么情况下这个话是错误的呢,当x和y时同类,或者y捕食x,那么这句话就是错误的。

此时我们就查询 x与y,x与y+n是否是否有共同祖先

如果有,说明它俩在同一个集合中,这句话就是错误的

如果没有,那么这句话就是对的,那么我们就合并 x+n与y,x与y+2*n,把它们并到一个集合中。

注意食物链是一个环,x捕食y也说明了 y的捕食对象是x的天敌,因此此时我们再合并 y+n与x+2*n

#include<bits/stdc++.h>
using namespace std;
const int M = 5e4 + 5;
int fa[3 * M];
int n, k,cnt; 
int get(int x)
{
    if (fa[x] == x) return x;
    return fa[x] = get(fa[x]);
}
bool same(int x, int y)
{
    return get(x) == get(y); 
}
void merge(int x, int y)
{
    fa[get(x)] = get(y);
}
int main(){
    cin >> n >> k;
    for (int i = 0; i <= 3 * n; i++)
    {
        fa[i] = i; // i表示同类域 i+n表示捕食域 i+2*n表示天敌域
    }
    while (k--)
    {
        int op, x, y;
        scanf("%d%d%d", &op, &x, &y);
        if (x > n || y > n || x < 1 || y < 1) {
            cnt++;
            continue;
        }
        if (op == 1)
        {
            if (same(x, y + n) || same(x + n, y) )
                cnt++;
            else
            {
                merge(x, y);
                merge(x + n, y + n);
                merge(x + 2 * n, y + 2 * n);
            }
        }
        else {
            if (same(x, y) || same(x, y + n))
                cnt++;
            else
            {
                merge(x + n, y);
                merge(x, y + 2*n);
                merge(x + 2 * n, y + n);
            }
        }
    }
    cout << cnt << endl;
    return 0;
}
扩展域

第二种 边带权

我们给编号为x的动物一个权值d[x] ,这个权值为它和节点之间的关系

d[x]=0说明它和父节点同类,=1说明它捕食父节点,=2说明它被父节点捕食。

我们在路径压缩的过程同时更新权值d[x],x的父节点之前是fa[x],压缩后父节点变成了根节点,所以父节点变了,权值也应当发生改变

 

d[x]=(d[x]+d[fa[x]])%3

 

对于给的指令 d x y,如果x和y有共同根节点,说明他们在一个集合,那从在这个集合中,x与y已经有了共同根节点,且深度都为1,x与y的关系可以表示为d[x]+d[fa[x]->y] 

d[fa[x]->y]就是 3-d[y] ,所以x与y的关系可以表示为

(d[x]+3-d[y])%3

如果(d[x]+3-d[y])%3 != d-1,(d为1,表示x和y同类,那么d-1就是0,这里不矛盾,d为2也是一样) 既x和y不满足所给关系,那这句话就是错的

如果x和y没有共同根节点,那么它俩的关系还不清楚,所以就合并它俩所在的集合,p和q分别表示x和y的根节点

我们让fa[p]=q;

此时我们更改了p的父节点,那它的权值d[p]也要发生相应改变,那这是一种怎样的传递关系呢?我们假设d[p]是我们要求的值,关系如下

(d[x]+d[p](此时p的父节点已经为q,这里d[p]就是我们要求的)+d[q->y])%3=d-1
d[q->y]=3-d[y](q是y的父节点)
所以可以得到d[p] = (d[y] - d[x] + 3 + d - 1) % 3;

这样就行啦。

#include<bits/stdc++.h>
using namespace std;
const int M = 5e4 + 5;
int fa[M], d[M]; //0表示与父节点同类 1表示吃父节点 2表示被吃
int n, k, cnt;
int get(int x)
{
    if (fa[x] == x) return x;
    int root = get(fa[x]);
    d[x] = (d[x] + d[fa[x]]) % 3;
    return fa[x] = root;
}
void merge(int x, int y)
{
    fa[get(x)] = get(y);
}
int main(){
    cin >> n >> k;
    for (int i = 0; i <= n; i++)
    {
        fa[i] = i; 
    }
    while (k--)
    {
        int op, x, y;
        scanf("%d%d%d", &op, &x, &y);
        if (x > n || y > n || x < 1 || y < 1) {
            cnt++;
            continue;
        }
        int p = get(x), q = get(y);
        if (p == q)
        {
            if ((d[x] + 3 - d[y]) % 3 != op - 1)
                cnt++;
            continue;
        }
        fa[p] = q;
        d[p] = (d[y] - d[x] + 3 + op - 1) % 3;
    }
    cout << cnt;
    return 0;
}
边带权

 

posted @ 2019-03-01 21:17  TLE自动机  阅读(239)  评论(0编辑  收藏  举报