zoukankan      html  css  js  c++  java
  • POJ1182--食物链(经典并查集)并查集看不出来系列2

    食物链
    Time Limit: 1000MS   Memory Limit: 10000K
    Total Submissions: 65906   Accepted: 19437

    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

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

    Output

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

    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

    Source

     
    并查集看不出来系列2
    转载自:http://www.cnblogs.com/dongsheng/archive/2013/06/12/3133188.html
    这题算是对并查集应用的一个总结吧!
    参考一另篇博客http://blog.csdn.net/c0de4fun/article/details/7318642,

    大体思想:
    1、如果两个物种有联系,不管是吃,被吃还是同类,它们之间应该是有一条径路可达的,
    也就是它们在一个合集中。
    2、如果a,b有关系,b,c有关系,那么a,c之间的关系式可以通过两者的关系推出来的。

    OK,下面围绕着上面的两个思想来逐一拆分。
    首先就是怎么把有关系的物种放到同一个合集中去,这就要需用到并查集了。每一次入输d,x,y,
    也就是相当于x,y之间有一条权为d的径路。先忽略这个权值,直接斟酌路径,那并查集的路径建立就
    不用我说了。一个parent数组,parent[i]表现从parent[i]到i有一条径路。OK,那不同的物食圈就构
    成了一个连通区域。每个连通区域都有一个根点节。

    下面斟酌怎么处理这个权
    先说点数学的货色,任何一种偏序关系都足满自反、对称、传递。
    自反:自己跟自己满足偏序关系。
    对称:a,b的偏序关系为r,则b,a的偏序关系为~r.表现求反。
    传递:a,b的偏序关系为r1,b,c的偏序关系为r2,a,c的偏序关系为r1+r2.

    为了便利,用一个relation数组来维护这个权值。relation[i]表现的是i在所的连通区域的
    根点节到i的关系。先略忽这个关系数组的维护过程,把团体的思绪理清晰。如果有两个物种加进来,
    就有两种情况,要么它们在同一个连通集里头。要么不在同一个连通集里头。

    一、两者在同一个连通集里头:
    1、新加的关系表明x,y是同类,那么它们两个分别到连通区域根点节的关系应该是一样的,
    要不就矛盾了。(记为case1)
    2、如果新加的关系表明x,y不是同类,那么在当前参加y,x相对根节点的关系和x本来相对根点节的
    关系应该是不变的,否则就矛盾了。(记为case2)

    二、两者在不同的连通集里头:就直接连接两个连通集就能够了。(记为case3)

    路径压缩处理:
    由于后来物种会越来越多,我们不希望食物链拉的很长,所以会尽可能的让全部的点节都直接和根节点
    相连。这样整个连通的图就有点呈现出星形。

    怎么维护关系数组:
    数组里头的每个元素的取值要么是0(同类),要么是1(父吃子),要么是2(子吃父)。至于为什么要
    这么设置(因为题目中1表示同类,而我们定义0表示同类,相对都应该减一,所以题目中的2表示父吃子在我们这里
    应该是2-1=1,,1表示父吃子),参考一另篇博客http://blog.csdn.net/c0de4fun/article/details/7318642,
    这里是不能随便定义的。设前面的数据我已经处理好了,现在要处理d,x,y.为了叙说的便利,
    记relation[x]为x根->x.那么在现就有三种情况:
    case1:(同一个集合且同类)
    这种情况x根与y根雷同。如果x根->x与y根->y不同,表明x,y不是同类,与d=1矛盾。
    case2:(同一个集合但不同类)
    这种情况x根与y根雷同。如果参加y之后,(x根->x) = (x根(即y根)->y + y->x),如果新求出来
    的关系与本身已有的x根->x的关系不同,则矛盾。
    case3:(在不同集合中)
    这种情况x根与y根不同。由于这里添加的是x到y的一条有向边。将y根的父点节设置为x根,更新y根父点
    节到x根的关系,即x根->y根=x根->x+x->y+y->y根,由于这里都是有向边,所以更新关系的时候注意关
    系的方向。这里需要注意,我们只更新了两个根之间的关系,x根与原来的y所在的连通区域里头的节点
    的关系都没有更新,这就是为什么要在一开始判断之前就要调用Find函数,更新每个点节到其根点节的
    关系。

    初始条件:
    有了这个递推,就好办了。初始条件parent就是并查集一般的初始条件,父点节于等自己。由于初始的
    时候父节点是自己,当然自己跟自己的关系肯定是同类咯,也就是relation[i]=0

    代码:

     1 #include <cstdio>
     2 #include <iostream>
     3 
     4 using namespace std;
     5 
     6 const int N = 50005;
     7 int father[N];
     8 int relation[N];//根点节到点节的关系
     9 
    10 void init(int n)
    11 {
    12     for(int i = 0; i <= n; ++i)
    13     {
    14         father[i]= i;
    15         relation[i] = 0;
    16     }
    17 }
    18 //更新的步调,先将当前点节与其根点节相连,然后更新其与根点节的关系
    19 //当前节点x与根节点r的关系更新的方法:
    20 //    (x与其父点节的关系+其父点节的关系与根点节的关系)%3
    21 //所以在更新节点x的数据之前需要更新其父节点的数据,这是find为什么搞成递归函数的原因
    22 //其更新的次序是从根节点开始往下,始终到当前点节x的父点节。
    23 int find(int x)
    24 {
    25     if(x != father[x])//不是根点节
    26     {
    27         int temp = father[x];
    28         //将当前点节的父点节设置为根点节
    29         father[x] = find(temp);
    30         //更新当前点节与根点节的关系,由x->x父和x父->父根的关系失掉x->父根的关系
    31         //所以在这之前必须更新其父点节与根点节的关系
    32         relation[x] = (relation[x] + relation[temp]) % 3;
    33     }
    34     return father[x];
    35 }
    36 
    37 int main()
    38 {
    39     int n, m, x, y, d, fx, fy, cnt;
    40 
    41     while(~scanf("%d %d", &n, &m))//POJ上只要需一次入输,所以不要需while循环
    42     {
    43         cnt = 0;
    44         init(n);
    45         for(int i = 0; i < m; ++i)
    46         {
    47             scanf("%d %d %d", &d, &x, &y);
    48             if(x > n || y > n)
    49             {
    50                 ++cnt;
    51                 continue;
    52             }
    53             if(d == 2 && x == y)
    54             {
    55                 ++cnt;
    56                 continue;
    57             }
    58             fx = find(x);
    59             fy = find(y);
    60             if(fx == fy)//属于同一个子集
    61             {
    62                 //如果x、y是同类,那么他们相对根点节的关系应该是一样的
    63                 if(d == 1 && relation[x] != relation[y])
    64                     ++cnt;
    65                 //如果不是同类,加入x与y的关系之后,x相对根点节的关系(x根->y,y->x(即3-(d-1)=2).即x根->x)应该是不变的
    66                 //这里d=2表示x - y = 2-1=1;而y->x=3-(x->y)=3-1=2;
    67                 if(d == 2 && relation[x] != (relation[y] + 2)%3)
    68                     ++cnt;
    69             }
    70             else//合并两个连通区域
    71             {
    72                 father[fy] = fx;//y根的父点节更新成x根
    73                 //(d-1)为x与y的关系,3-relation[y]是y与y的根点节的关系,注意方向,relation[x]是其根点节与x的关系
    74                 //x根->x,x->y,y->y根:即x根->y根
    75                 relation[fy] = (relation[x] + (d-1) + (3-relation[y])) % 3;//注意这里只更新的是fy相对于根的关系
    76             }
    77         }
    78         printf("%d
    ", cnt);
    79     }
    80     return 0;
    81 }
  • 相关阅读:
    web前端开发最佳实践--(笔记之JavaScript最佳实践)
    web前端开发最佳实践--(笔记一)
    HTML5及CSS3--freeCodeCamp(笔记一)
    javascript系列--函数(一)
    HTML5本地存储
    分享一些好用的设计网站
    .net面试问题总结
    ife_task10--弹性盒子
    WPF--搭建一个简单的demo
    信息技术文集
  • 原文地址:https://www.cnblogs.com/liuzhanshan/p/6094539.html
Copyright © 2011-2022 走看看