并查集 之 食物链

题目

有三种动物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和k,输出假话的总数;

输入格式

第一行是两个整数n和k,用空格分开;
以下k行每行是3个正整数D,X,Y,两数之间用空格分开,其中D表示说法的种类;
若D = 1,表示X和Y是同类;
若D = 2,表示X吃Y;

输出格式

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

数据范围

1 <= N <= 50000,
0 <= K <= 100,000

输入样例

100 7
1 101 1 
2 1 2
2 2 3 
2 3 3 
1 1 3 
2 3 1 
1 5 5

输出样例

3

算法思路

并查集讲解:

1,并查集的作用:快速将两个集合合并;询问两个元素是否在一个集合中;
2,并查集原理:用树的形式(不一定是二叉树)来维护所有集合,即所有集合用树来表示;
树根的编号就是整个集合的编号;
用一个父数组来维护每个节点的父节点(p[ x ] 即表示节点 x 的父节点;
3,并查集的相关问题:
1)如何判断树根? if ( p[ x ] == x ) 即为树根;
2)如何求某节点所在集合的编号(即树根节点编号)?

int find(int x){ //返回x的祖宗节点  + 路径压缩(让每个节点都直接指向根节点)
	if(p[x] != x){
		p[x] = find(p[x]);
	}
	return p[x];
}

3)如何合并两个集合?
若px是x所在集合编号(即x所在集合的根节点编号),py是y所在集合编号;合并两个集合,即让其中一个的根节点的父节点指向另一个集合的根节点;
p[ px ] = py (让x节点的根节点指向y节点的根节点,即x节点的根节点的父节点为y节点的根节点)

关于本题:

1,用并查集维护每个集合,初始化时,每个动物单独为一个集合;用每个节点到根节点的距离来表示它与根节点之间的关系;
(距离为1,可以吃根节点;距离为2,可以吃距离为1的;距离为3的可以吃距离为2的;)
2,因为只有3种动物,所以可以每个节点与根节点的关系可以用对3取模结果来表示,即:
取模余1,吃根,被2吃;
取模余2,吃1,被0吃;
取模余0,吃2,被1吃;(取模余0即与根同类)
3,题目中的说法的种类只有2种:
若D = 1,表示X和Y是同类;
若D = 2,表示X吃Y;
所以我们可以维护两个函数,分别对应这两种说法;

代码

import java.io.*;
public class Main{
    static final int N = 50000;
    static final int K = 100000;
    static int n;  //动物个数
    static int k;  //描述句子数
    static int[] p; //存储每个节点的父节点
    static int[] d; //存储每个节点距离父节点的距离
    public static void main(String[] args) throws IOException{
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String[] s = br.readLine().split(" ");
        n = Integer.parseInt(s[0]);
        k = Integer.parseInt(s[1]);
        p = new int[N];  //直接开辟到数据范围最大(在满足题目要求的前提下更方便)
        d = new int[N];
        init(n);  //初始化
        int cnt = 0; //最终要输出的假话数量
        while(k-- > 0){
            String[] desc = br.readLine().split(" ");
            if(Integer.parseInt(desc[1]) > n || Integer.parseInt(desc[2]) > n ){  //假话满足的条件之二:当前的话中X或Y比n大,就是假话;
                cnt++;
            }
            else{
                if(desc[0].equals("1")){ // D = 1,表示X和Y是同类
                    if(!similar(Integer.parseInt(desc[1]), Integer.parseInt(desc[2]))){
                        cnt++;
                    }
                }
                else{ // D = 2,表示X吃Y
                    if(!hunt(Integer.parseInt(desc[1]), Integer.parseInt(desc[2]))){
                        cnt++;
                    }
                }
            }
        }
        System.out.print(cnt);
    }
    //初始化函数 - 距离数组d不需要初始化,因为全局变量默认为0;
    public static void init(int n){
        for(int i = 0; i < n; i++){
        	//每个节点最开始只有自己,所以其父节点就是自己
            p[i] = i;
        }
    }
    //获取某节点x的祖宗节点
    public static int get(int x){
        if(p[x] != x){ //若当前节点不是根节点时,
            int t = get(p[x]); //让x的父节点指向其父节点的祖宗节点
            d[x] += d[p[x]]; 
            p[x] = t;
        }
        return p[x];
    }
    //因为说法的种类有两种:X,Y是同类;X吃Y,所以维护两个函数,分别表示这两种情况
    //判断X和Y是同类
    public static boolean similar(int x, int y){
        int px = get(x);  //先得到x的根节点
        int py = get(y);
        if(px == py){ //x,y在同一个集合里
            //则判断二者对3(因为总共有3种动物)取余的结果是否相等
            if((d[x] - d[y]) % 3 == 0 ){
                return true;
            }
            else{
                return false;
            }
            
        }
        //x,y不在同一个集合
        p[px] = py; //x的根节点的父节点指向y的根节点
        d[px] = d[y] - d[x]; //更新x到新的根节点的距离
        return true;
    }
    //判断X吃Y
    public static boolean hunt(int x, int y){
        int px = get(x);
        int py = get(y);
        if(px == py){ //x,y在同一个集合里
            //则判断二者对3取余的结果是否相差1
            if((d[x] - 1 - d[y]) % 3 == 0 ){
                return true;
            }
            else{
                return false;
            }
        }
        //x,y不在同一个集合
        p[px] = py;
        d[px] = d[y] - d[x] + 1;
        return true;
    }
}

反复看,多思考,多理解,但不要陷入一道题里,要引申开来

并查集是一种用于解决集合合并与查询问题的数据结构。在食物链问题中,我们可以使用并查集来判断给定的K句话中有多少句是假话。 首先,我们需要创建一个并查集,其中每个动物都是一个节点。初始时,每个节点都是独立的集合。 然后,我们按照给定的K句话进行处理。对于每一句话,我们需要判断它是真话还是假话。 如果是第一种说法"1 X Y",表示X和Y是同类。我们可以通过将X和Y所在的集合合并来实现。即将X所在的集合的根节点指向Y所在的集合的根节点,或者将Y所在的集合的根节点指向X所在的集合的根节点。 如果是第二种说法"2 X Y",表示X吃Y。我们需要判断X和Y是否属于同一类。如果它们属于同一类,那么这句话就是假话。否则,这句话是真话。 最后,我们统计假话的总数即可。 以下是一个示例代码,演示了如何使用并查集解决食物链问题: ```python class UnionFind: def __init__(self, n): self.parent = list(range(n)) self.rank = [0] * n def find(self, x): if self.parent[x] != x: self.parent[x] = self.find(self.parent[x]) return self.parent[x] def union(self, x, y): root_x = self.find(x) root_y = self.find(y) if root_x == root_y: return if self.rank[root_x] < self.rank[root_y]: self.parent[root_x] = root_y elif self.rank[root_x] > self.rank[root_y]: self.parent[root_y] = root_x else: self.parent[root_y] = root_x self.rank[root_x] += 1 def count_false_statements(N, K, statements): uf = UnionFind(N+1) false_count = 0 for statement in statements: type, X, Y = statement if X > N or Y > N: false_count += 1 continue if type == 1: if uf.find(X) == uf.find(Y+N) or uf.find(X+N) == uf.find(Y): false_count += 1 else: uf.union(X, Y) uf.union(X+N, Y+N) uf.union(X+2*N, Y+2*N) elif type == 2: if uf.find(X) == uf.find(Y) or uf.find(X+N) == uf.find(Y): false_count += 1 else: uf.union(X, Y+N) uf.union(X+N, Y+2*N) uf.union(X+2*N, Y) return false_count N = 5 K = 7 statements = [(1, 1, 2), (2, 1, 3), (1, 2, 3), (2, 2, 4), (1, 4, 1), (1, 4, 3), (2, 4, 3)] false_count = count_false_statements(N, K, statements) print(false_count) # 输出:3 ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值