食物链

题目描述 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 Description
第一行是两个整数N和K,以一个空格分隔。   

以下K行每行是三个正整数D,X,Y,两数之间用一个空格隔开,其中 D 表示说法的种类。   

若D=1,则表示X和Y是同类。   

若D=2,则表示X吃Y。

输出描述 Output Description
只有一个整数,表示假话的数目。

样例输入 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

#include<iostream>
#include<cstdio>
#include<cstdlib>
using namespace std;
int n,m,fa[50010],va[50010]={0};
int find(int x){
    if(fa[x]!=x){
        int tmp=fa[x];//记录原始的父结点号,如果不记录,则到计算va时父结点已经变了
        fa[x]=find(fa[x]);
        va[x]=(va[x]+va[tmp])%3;//根据自己到原始父结点的路径值和原始父结点到祖宗的路径值调整
    }
    return fa[x];
}
void myunion(int &a,int &b,int x){
    va[fa[a]]=3+x-va[a];//这句要先做
    fa[fa[a]]=b;//这句要晚做,否则a的祖宗已经换人了
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=0;i<=n;i++){
        fa[i]=i; 
    }
    int ans=0,t,x,y;
    for(int i=1;i<=m;i++){  
        scanf("%d%d%d",&t,&x,&y);
        if(x>n||y>n){ans++;continue;}
        if(t==1){
            if(x!=y){
                if(find(x)==find(y)){
                    if(va[x]!=va[y])ans++;
                }else myunion(x,y,0);
            }
        }else{
            if(x==y){ans++;continue;}
            if(find(x)==find(y)){
                if(va[x]!=(va[y]+1)%3)ans++;
            }else myunion(x,y,1);
        }   
    }
    cout<<ans<<endl;    
    return 0;
}

我一直囿于A吃B,则A和B处于不同的集合,然后B吃C,又处于两个不同的集合,总的来说,一共有三个集合,搞来搞去,弄得很复杂,随便怎么搞,只能通过3到4个点,后来看材料终于明白了,其实所有能判别有谁吃谁的关系的点都是一根藤上的蚂蚱,只是子结点到父结点不一定是同类,也可以吃或被吃。

这样想了以后,很快就搞定了。

相互有关系的点就在同一个集合中,没关系的点分别在自己的集合中;
假设同一个集合中的A和B,当前的状态为:
1、若A和B同类,则fa[A]=B,va[A] =0;
2、若A 吃 B,则fa[A]=B,va[A] =1;
3、若A被 B 吃,则fa[A]=B,va[A] =2;
{以上第3条不是手动完成的,是在路径压缩的时候产生的}
这样设计是很巧妙的,因为A到B是1,B到C是2,即A吃B,B被C吃,
则可以断言A和C是同类((1+2)%3==0),这里只要模3即可路径和压缩。
再举例, A到B是2(fa[A]=B,va[A]=2)即A被B吃,
B到C是2(fa[B]=C,va[B]=2),B被C吃,
则可以断言A吃C((2+2)%3==1,fa[A]=C,va[A]=1),因为是这3个点循环的。
注意:处于同一个集合的不同点的关系不需要自己调整,在路径压缩时会自动调整。
若要判断两个点A和B是否同类不互吃,
首先看是否同宗(即是否在同一个集合) ,
如果是同宗,则看到祖宗的距离和是否相同,
若到祖宗的距离和相同则为同类,不做任何操作;
若到祖宗的距离和不相同则为不是同类,ans++;
如果不是同宗,说明在两个不同的集合,进行集合合并:
即x所在集合要接到y的下面,因为是x和y是同类,
则首先要完成va[fa[A]]=3-va[A],
因为A到祖宗然后到B的路径和必须是0或3(因为是同类) ;
再完成fa[A]=B,如果搞错次序,A的祖宗就变了,
再调整原来A的祖宗的值就晚了 。
若要判断A是否吃B,先看是否同宗,
如果同宗,则经过尝试,只要A的路径值和(B的路径值+1)模3相等即可,
如果不等,则ans++;
如果不同宗,则进行集合合并,首先要完成va[fa[A]]=4-va[A],
这里是吃的关系,所以要大1,
再完成fa[A]=B,如果搞错次序,A的祖宗就变了,
再调整原来A的祖宗的值就晚了 。

Problem:1182 - 食物链,NOI2001 
Begin Time:4th/Mar/2012 1:00 p.m. 
End Time:4th/Mar/2012 6:47 p.m. 
Cost Time:两天多,看的别人的解题报告AC的 
Reference:http://apps.hi.baidu.com/share/detail/16059767 
测试数据: 
http://poj.org/showmessage?message_id=93058 
输出: 
上方有 
教训: 
    WA一次,没搞清楚先更新父节点relation还是更新当前节点relation的关系!!! 
    (在最后那条犯错误了!) 
思路: 
老子决心要写一个,关于这道题的,最详细的解题报告。 
本题思路是带权并查集,我们从最开始讲起。 

Part I  - 权值(relation)的确定。 
我们根据题意,森林中有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不是随便选的 


Part II - 路径压缩,以及节点间关系确定 
确定了权值之后,我们要确定有关的操作。 
我们把所有的动物全初始化。 
struct Animal 
{ 
    int num; //该节点(node)的编号 
    int parent; //该node的父亲 
    int relation; //该node与父节点的关系,0同类,1被父节点吃,2吃父节点 
}; Animal ani[50010]; 
    初始化为 
    For i = 0 to N do 
        ani[i].num = i; 
        ani[i].parent = i; 
        ani[i].relation = 0 ; //自己和自己是同类 
    End For 

    (1)路径压缩时的节点算法 
    我们设A,B,C动物集合如下:(为了以后便于举例) 
    A = { 1 , 2 , 3 ,4 ,5 } 
    B = { 6 , 7 , 8 ,9 ,10} 
    C = { 11, 12, 13,14,15} 
    假如我们已经有了一个集合,分别有3个元素 
    SET1 = {1,2},我们规定集合中第一个元素为并查集的“代表” 
    假如现在有语句: 
    2 2 6 
    这是一句真话 
    2是6的父亲 
     ani[6].parent = 2; 
     ani[6].relation = 1; 
    那么,6和1的关系如何呢? 
     ani[2].parent = 1; 
     ani[2].relation = 0; 
    我们可以发现6与2的关系是 1. 
    通过穷举我们可以发现 
    ani[now].parent = ani[ani[now].parent].parent; 
    ani[now].relation = ( ani[now].relation + ani[now.parent].relation ) % 3; 
    这个路径压缩算法是正确的 
    关于这个路径压缩算法,还有一点需要注意的地方,我们一会再谈 
    注意,根据当前节点的relation和当前节点父节点的relation推出 
    当前节点与其父节点的父节点的relation这个公式十分重要!! 
    它推不出来下面都理解不了!!自己用穷举法推一下: 
    好吧,为了方便伸手党,我给出穷举过程 
            i      j 
    爷爷  父亲  儿子  儿子与爷爷 
           0      0       (i + j)%3 = 0 
           0      1       (i + j)%3 = 1 
           0      2       (i + j)%3 = 2 
           1      0       (i + j)%3 = 1 
           1      1       (i + j)%3 = 2 
           1      2       (i + j)%3 = 0 
           2      0       (i + j)%3 = 2 
           2      1       (i + j)%3 = 0 
           2      2       (i + j)%3 = 1 
    嗯,这样可以看到,( 儿子relation + 父亲relation ) % 3 = 儿子对爷爷的relation 
    这就是路径压缩的节点算法 
    (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吃 
Part III - 算法正确性的证明 
    首先,两个自洽的集合,合并以后仍然是自洽的 
    这个不难想吧,数学上有个什么对称性定理跟他很像的。 
    如果理解不了,就这么想!! 
    当set1和set2合并之后,set2的根节点得到了自己关于set1根节点的 
    正确relation值,变成了set1根节点的儿子,那么 
    set2的所有儿子只要用 
    ( ani[X].relation + ani[Y].relation ) % 3就能得到自己正确的relation值了 
    所以说,针对不在同一集合的两个元素的话,除非违背了(2)和(3),否则永远是真的 
    (无论这句话说的是什么,我们都可以根据所给X,Y推出两个子节点之间应有的关系,这个关系一确定,所有儿子的关系都可以确定) 

    其实所有的不同集合到最后都会被合并成一个集合的。 
    我们只要在一个集合中找那些假话就可以了。 
    首先,如何判断 
    1 X Y是不是假话。//此时 d = 1 
    if ( X 和 Y 不在同一集合) 
        Union(x,y,xroot,yroot,d) 
    else 
        if x.relation != y.relation  ->假话 
    其次,如何判断 
    2 X Y是不是假话 //此时d = 2 
    if ( X 和 Y 不在同一集合) 
        Union(x,y,xroot,yroot,d) 
    else 
        (ani[y].relation + 3 - ani[x].relation ) % 3 != 1 ->假话 
    这个公式是这么来的: 
    3 - ani[x].relation得到了根节点关于x的relation 
    ani[y] + 3 - ani[x].relation得到了y关于x的relation 
    所以,只要y关于x的relation不是1,就是y不被x吃的话,这句话肯定是假话! 

    (2)路径压缩要特别注意的一点(错在这里,要检讨自己) 
        路径压缩的时候,记得要 
        先findParent,再给当前节点的relation赋值。 
        否则有可能因为当前节点的父节点的relation不正确而导致错的稀里哗啦。 
        例子: 
        set1 = {1,2,7,10} 
        set2 = {3,4,8,11} 
        set3 = {12,5,14,9} 
        Union(1,3,1,3,1) 
        Union(3,12,3,12,2) 
        1 5 1 
        算5的relation 
        如果不先更新parent的relation,算出来应该是 
        ( 3 - 0 + 0 + 1 ) % 3 = 1,5被1吃,显然不对 
        这里面,+ 0的那个0是指根节点 12 的relation(未更新,这里的0是指12与11的relation) 
        如果更新完了的话,应该是 
        ( 3 - 0 + 2 + 1 ) % 3 = 0 ,5与1是同一物种,对了 
        这里面的 2 是更新节点12的relation(12与1的relation) 
后记: 
    关于这道题,我在网上搜索了许多解题报告,但是都闪烁其词,大概大家都不想 
    把自己辛辛苦苦推出来的公式写到网上供别人学习来节省时间吧。 
    我觉得这么做不好,对初学者容易产生不良影响,ACM如果只是一个小众化的圈子,那 
    岂不是太没意思了。 
    于是我就把我自己总结的这道题的经验放了出来,希望可以帮得到大家 
    自己总结的,对错也不知道,但是起码是“自洽”的,^ ^ 
    感谢那篇博文的博主,也感谢gzm,lqy两位学长的指导。 
    c0de4fun 

*/
using namespace std;
const int c0de4fun = 50010;//动物个数的最大值
///指明父节点与自己的关系,0同类,1被吃,2吃父
const int SAME = 0;
const int ENEMY = 1;
const int FOOD = 2;
struct Animal
{
int parent;
int num;
int relation;
};
Animal ani[c0de4fun];
long ans;
int findParent(Animal* node)
{
///Wrong Answer 因为这个函数写错了
///这个函数得是“自洽的”
///就是说,得保证每个元素的父亲的relation是对的
///再算自己的relation
///因为自己的relation和父亲的relation有关
///这就是为什么要先findParent再relation更新的原因
int tmp;
if( node->parent == node->num )
return node->parent;
tmp = node->parent;

ifdef DBG

printf("Animal %d s Parent is %d\n",node->num,node->parent);  

endif

// node->relation = ( ani[node->parent].relation + node->relation ) % 3;
node->parent = findParent(&ani[node->parent]);
node->relation = ( ani[tmp].relation + node->relation ) % 3;
return node->parent;
}
void Union(int x,int y,int a,int b,int d)
{
ani[b].parent = a;
///rootY.parent = rootX.parent;
ani[b].relation =( (3 - ani[y].relation) + (d - 1) + ani[x].relation) % 3;
}

void init_Animal(int n)
{
for(int i = 1 ; i <= n ; i++)
{
ani[i].num = i;
ani[i].parent = i;
ani[i].relation = SAME;
}
}
int main(int argc,char* argv[])
{
int N,K;
int d,X,Y;

ifdef INPUT

freopen("b:\\acm\\poj1182\\input.txt","r",stdin);  

endif

scanf("%d%d",&N,&K);  
init_Animal(N);  
for(int i = 0 ; i < K ; i++)  
{  
    scanf("%d%d%d",&d,&X,&Y);  
    if( X > N || Y > N)  
        ans++;  
    else  
    {  
        if(d == 2 && X == Y)  
            ans++;  
        else  
        {  
            int a = findParent(&ani[X]);  
            int b = findParent(&ani[Y]);  
            if ( a != b )  
            {  
                ///x,y不在同一集合中  
                Union(X,Y,a,b,d);  
            }  
            else  
            {  
                switch(d)  
                {  
                    case 1:  
                        if(ani[X].relation != ani[Y].relation)  
                            ans++;  
                        break;  
                    case 2:  
                        if(((ani[Y].relation + 3 - ani[X].relation) % 3 ) != 1)  
                            ans++;  
                        break;  
                }  
            }  
        }  
    }  
}  
printf("%d\n",ans);  
return 0;  

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
根据引用和引用的描述,动物王国中有三类动物A、B、C,它们的食物链构成了一个有趣的环形。具体来说,A吃B,B吃C,C吃A。现在有N个动物,每个动物都是A、B、C中的一种,但我们不知道它们到底属于哪一类。有人用两种说法对这N个动物之间的食物链关系进行描述,一种是"1 X Y",***根据以下三个条件,我们可以判断一句话是否为假话: 1)当前的话与前面的某些真话冲突; 2)当前的话中的X或Y比N大; 3)当前的话表示X吃X。 通过判断这些条件,我们可以计算出假话的总数。 在解决这个问题时,可以使用并查集来高效地维护动物之间的关系,并快速判断是否产生了矛盾。具体而言,对于每一只动物i,我们创建3个元素i-A、i-B、i-C,并用这3*N个元素构建并查集。这个并查集可以维护以下信息: 1)i-x表示"i属于种类x"; 2)并查集中的每一个组表示组内所有元素代表的情况都同时发生或不发生。 例如,如果i-A和j-B在同一个组里,就表示如果i属于种类A,那么j一定属于种类B;如果j属于种类B,那么i一定属于种类A。因此,对于每一条信息,我们只需要按照以下操作进行: 1)第一种说法,即x和y属于同一种类——合并x-A和y-A、x-B和y-B、x-C和y-C; 2)第二种说法,即x吃y——合并x-A和y-B、x-B和y-C、x-C和y-A。 在执行合并之前,需要先判断合并是否会产生矛盾。例如,在第一种说法的情况下,需要检查x-A和y-B、y-C是否在同一组中等信息。 另外,初始化时每个元素的祖先都是自己,这样可以避免出现相同的关系。 以上是解决这个问题的一种方法,具体的实现细节可能需要根据实际情况进行调整。希望对你有所帮助!

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值