poj1182

这道题用到一部分技巧:
这个家伙写的挺详细的:
http://blog.csdn.net/c0de4fun/article/details/7318642/

//这题分析一下:
我们根据题意,森林中有3种动物。A吃B,B吃C,C吃A。
我们还要使用并查集,那么,我们就以动物之间的关系来作为并查集每个节点的
权值。
注意,我们不知道所给的动物(题目说了,输入只给编号)所属的种类。
所以,我们可以用动物之间“相对”的关系来确定一个并查集。
0 - 这个节点与它的父节点是同类
1 - 这个节点被它的父节点吃
2 - 这个节点吃它的父节点。

注意,这个0,1,2所代表的意义不是随便制定的,我们看题目中的要求。
说话的时候,第一个数字(下文中,设为d)指定了后面两种动物的关系:
1 - X与Y同类
2 - X吃Y

我们注意到,当 d = 1的时候,( d - 1 ) = 0,也就是我们制定的意义
            当 d = 2的时候,( d - 1 ) = 1,代表Y被X吃,也是我们指定的意义。
所以,这个0,1,2不是随便选的

1.路径压缩:
压缩后儿子->爷爷 变成 儿子父亲,
所以对应的num[]的关系也要变
( 儿子relation + 父亲relation ) % 3 = 儿子对爷爷的relation

2.集合的关系:
当两个不同的集合出现的时候,就是将这两个集合合并
//别人的写的
(2) 集合间关系的确定
在初始化的时候,我们看到,每个集合都是一个元素,就是他本身。
这时候,每个集合都是自洽的(集合中每个元素都不违反题目的规定)
注意,我们使用并查集的目的就是尽量的把路径压缩,使之高度尽量矮
假设我们已经有一个集合
set1 = {1,2,7,10}
set2 = {11,4,8,13},每个编号所属的物种见上文
set3 = {12,5,4,9}
现在有一句话
2 13 2
这是一句真话,X = 13,Y = 2
我们要把这两个集合合并成一个集合。
直接
int a = findParent(ani[X]);
int b = findParent(ani[Y]);
ani[b].parent = a;
就是把Y所在集合的根节点的父亲设置成X所在集合的根节点。
但是,但是!!!!
Y所在集合的根结点与X所在集合的根节点的关系!!!要怎么确定呢?
我们设X,Y集合都是路径压缩过的,高度只有2层
我们先给出计算的公式
ani[b].relation = ( 3 - ani[Y].relation + ( d - 1 ) + ani[X].relation) % 3;
这个公式,是分三部分,这么推出来的
第一部分,好理解的一部分:
( d - 1 ) :这是X和Y之间的relation,X是Y的父节点时,Y的relation就是这个
3 - ani[Y].relation = 根据Y与根节点的关系,逆推根节点与Y的关系
这部分也是穷举法推出来的,我们举例:
j
子 父相对于子的relation(即假如子是父的父节点,那么父的relation应该是什么,因为父现在是根节点,所以父.relation = 0,我们只能根据父的子节点反推子跟父节点的关系)
0 ( 3 - 0 ) % 3 = 0
1(父吃子) ( 3 - 1 ) % 3 = 2 //父吃子
2(子吃父) ( 3 - 2 ) % 3 = 1 //子吃父,一样的哦亲
——————————————————————————————————————————————————————
我们的过程是这样的:
把ani[Y],先连接到ani[X]上,再把ani[Y]的根节点移动到ani[X]上,最后,把ani[Y]的根节点移动到ani[X]的根节点上,这样算relation的
还记得么,如果我们有一个集合,压缩路径的时候父子关系是这么确定的
ani[爷爷].relation = ( ani[父亲].relation + ani[儿子].relation ) % 3
我们已知道,( d - 1 )就是X与Y的relation了
而 (3 - ani[Y].relation)就是 以Y为根节点时,他的父亲的relation
那么
我们假设把Y接到X上,也就说,现在X是Y的父亲,Y原来的根节点现在是Y的儿子
Y的relation + ani[Y]根节点相对于ani[Y]的relation
( ( d - 1 ) + ( 3 - ani[Y].relation) ) % 3
就是ani[Y]的父亲节点与ani[X]的relation了!

    那么,不难得到,ani[Y]的根节点与ani[X]根节点的关系是:
    ( ( d - 1 ) + ( 3 - ani[Y].relation) + ani[X].relation ) % 3 ->应用了同余定理
    注意,这个当所有集合都是初始化状态的时候也适用哦
    还是以最开头我们给的三个集合(分别代表三个物种)为例
    2 1 6
    带入公式
    ani[6].relation = ( ( 2 - 1 ) + ( 3 - 0 ) + 0 ) % 3 = 1
    也就是,6被1吃

最后同一个集合就判断关系,不同就并:

代码:

#include <iostream>
#include <cstdio>

using namespace std;

const int maxn = 50005;

int pa[maxn], num[maxn];//num 秩
//这里的秩表示儿子对应的父亲的关系,0,1,2

void makeset(int n)
{
    for (int i = 1; i <= n; i++)
    {
        pa[i] = i;
        num[i] = 0;
    }
}

int findset(int x)
{
    if (pa[x] != x)
    {
        int root = pa[x];
        pa[x] = findset(pa[x]);//路径压缩
        num[x] = (num[x] + num[root]) % 3;
        //( 儿子relation + 父亲relation ) % 3 = 儿子对爷爷的relation
    }
    return pa[x];
}

void Union(int x, int y, int a, int b, int d)
{
    pa[b] = a;
    num[b] = ((3 - num[y]) + d - 1 + num[x]) % 3;
      //( d - 1 ) :这是X和Y之间的relation,X是Y的父节点时,Y的relation就是这个
      //3 - ani[Y].relation = 根据Y与根节点的关系,逆推根节点与Y的关系
      //ani[x].relation :就是x对应父节点的关系,这样就可以求出b对a的关系
}

int ans = 0;

int main()
{
    int n, m;
    scanf("%d%d", &n, &m);
    makeset(n);
    for (int i = 1; i <= m; i++)
    {
        int d, x, y, a, b;
        scanf("%d%d%d", &d, &x, &y);
        if (x > n || y > n)
        {
            ans++;
            continue;
        }
        if (d == 2 && x == y)
        {
            ans++;
            continue;
        }
        a = findset(x);
        b = findset(y);
        if (a != b)
        {
            Union(x, y, a, b, d);
        }
        else
        {
            switch (d)
            {
            case 1:
                if (num[x] != num[y])
                    ans++;
                break;
            case 2:
                if ((num[y] + 3 - num[x]) % 3 != 1)
                    ans++;
                break;
            }
            //判断关系。
        }
    }
    printf("%d\n", ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值