poj 1182 食物链的一种解法(详解),非向量法

食物链
Time Limit: 1000MS   Memory Limit: 10000K
Total Submissions: 54761   Accepted: 16056

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
解题思路:

先分析题目,该题除了考察常规的并查集知识之外,还添加了关于捕食关系的处理。这样的话就不能直接套用并查集模板了,需要思考清楚,集合和捕食关系之间的联系。

     由于所有动物都属于A,B,C三类中的一种,将每只动物对应A,B,C,这样就能得到i-A,i-B,i-C的元素,动物共有N只,则三个集合共用3N个元素。得到并查集集合之后,可以识别出需要维护的信息:
  • i-x表示 i属于种类x。
  • 并查集里的每一个组表示组内所有元素代表的情况都同时发生或不发生。

     弄清楚集合之后,就要考虑其间的关系。其间的关系为所列举出来的两种,一种为同类,一种为捕食关系。
  • x和y为同类关系,合并x-A和y-A、x-B和y-B、x-C和y-C。
  • x和y为捕食关系,合并x-A和y-B、x-B和y-C、x-C和y-A。

这里解释一下为什么要这样做,首先,三个集合代表三种可能,因为不知道动物属于哪一个集合,所以一开始就设置三个集合,代表三种不同的可能性。其次,元素之间的关系是同类或者捕食,就可以将所对应的关系放入一个集合,如果接下来出现的元素关系违反原来元素间的关系,那么必定就是违法的。

举个例子,如果x与y是同类,那么不可能再有x吃y或者是y吃x的关系出现。
同理,如果x吃y的话,那么不可能出现x与y是同类或者y吃x的情况。

最后,需要在合并之前判断合并是否会产生矛盾,且判断编号是否越界。
小提示:该题目每次只需要测试一组数据。

源代码:

#include <iostream>
#include <cstdio>

using namespace std;

const int MAX_N = 100000; //最大数量

int parent[MAX_N];  //父亲
int rank[MAX_N];    //树的高度

//并查集初始化
void init(int n){
    for(int i = 0; i < n; ++i){
        parent[i] = i;
        rank[i] = 0;
    }
}

//查找根节点
int find(int x){
    if(parent[x] == x){
        return x;
    } else {    //递归查找至根节点,并将由该点到根节点上的所有节点的parent置为根节点
        return parent[x] = find(parent[x]);
    }
}

//合并x和y所属集合
void unite(int x, int y){
    x = find(x);
    y = find(y);
    if(x == y){
        return;
    }

    if(rank[x] < rank[y]){  //x的高度小于y的,则以y为根节点
        parent[x] = y;
    } else {
        parent[y] = x;
        if(rank[x] == rank[y]){ //如果高度相同,x的高度加一
            rank[x]++;
        }
    }
}

//判断x和y是否为一个并查集
bool same(int x, int y){
    return find(x) == find(y);
}

int N,K;    //N只动物,K组数据
int T[MAX_N];   //信息类型
int X[MAX_N];   //X数组
int Y[MAX_N];   //Y数组

void solve(){   //解决函数
    //初始化并查集
    //这里的3N范围为A[0,N),B[N,2N),C[2N,3N)
    init(N * 3);

    int ans = 0;
    for(int i = 0; i < K; ++i){
        int t = T[i];
        int x = X[i] - 1;   //范围变为[0,N-1]
        int y = Y[i] - 1;   //范围变为[0,N-1]

        if(x < 0 || y < 0 || x >= N || y >= N){
            ++ans;
            continue;
        }

        if(t == 1){ //x,y属于同一种类
            //如果出现了x与y不是同一种类,也就是说x吃y,或者x被y吃的情况,则非法
            if(same(x, (int)(y + N)) || same(x, y + 2 * N)){
                ++ans;
            } else {    //否则,将x,y归为同一种类,三个集合都归类
                unite(x, y);
                unite(x + N, y + N);
                unite(x + 2 * N, y + 2 * N);
            }
        } else {    //x吃y
            //如果出现了x与y是同一类或者y吃x的情况,则非法
            if(same(x, y) || same(x, y + 2 * N)){
                ++ans;
                continue;
            } else {    //否则,将x,y的吃的关系,归为一类,三个集合都这样归类。这样,如果后续出现x,y同种类或者是y吃x,则非法
                unite(x, y + N);
                unite(x + N, y + 2 * N);
                unite(x + 2 * N, y);
            }
        }
    }
    printf("%d\n",ans);
}

int main()
{
    scanf("%d%d",&N,&K);
    for(int i = 0; i < K; i++){
        scanf("%d%d%d",&T[i],&X[i],&Y[i]);
    }
    solve();
    return 0;
}









你可能感兴趣的:(详解,并查集,非向量法)