AcWing 240. 食物链(带权并查集)

题目链接 :点击查看

题目描述 :

动物王国中有三类动物 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 行每行是三个正整数 D,X,Y,两数之间用一个空格隔开,其中 D 表示说法的种类。

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

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

输出

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

输入输出样例 :

输入

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

输出

3

题目分析 :

本题同样用并查集来做,而且用到的是维护与根节点距离的并查集。由题意可知,对于编号x与编号y的动物,一共有3种关系,1 . x 吃 y 2 . x被y 吃  3.x 与 y是同类。在并查集的树形节点中,我们可以这样抽象出 x 与 y 的关系 : 假定y为根节点,如果x与y的距离为1(即相连中间没有节点) 则表示x吃y ,如果x与y的距离为2(中间相隔一个节点,两段相连),则表示x被y吃,如果x与y的距离为3,表示x与y是同类。向下递推,如果x与y的距离为4,可以看成x距y的同类的距离为1,x吃y的同类,也就是x吃y……我们可以引申为这样的规律,如果x与y的距离mod 3 == 1,则x吃y,如果x与y的距离mod 3 == 2,表示x被y吃,如果x与y的距离mod 3 == 0,表示x与y是同类。类比于前面我们做过的题,find函数通过路径压缩可以求得某个点(比如说x)的根节点,在find函数中用一个中间遍历t记录递归中的p[x](要是跟之前相同直接用p[x] = find(p[x]),在回溯过程p[x]始终等于根节点),同时,我们定义了一个数组d来记录每个点到父节点的距离。在递归回溯过程中,由于递归后的语句回溯过程会发生自内向外遍历,所以我们用d[x] += d[p[x]]可以从根节点的下一个点开始,依次加和每个点距离其父节点的距离,最后d[x]的值即为点x到其根节点的距离。这样,我们可以完成对于带权并查集的构建。在对假话进行判断时,条件二很容易判断,即判断输入的两个数(编号)x与y是否大于n即可,若是有一个大于n则这句话就是假话。而一、三条件判定则相对较麻烦,尤其是判断是否与前面的话冲突,这里我们用px 与 py分别算出x,y分别属于的集合编号(px = find(x), py = find(b)),如果px == py则他们属于同一个集合,如果描述的是他们是同一类(t == 1),则求(d[y] - d[x]) % 3的值,如果是0则表明确实是同一类,其他值则不是。为啥呢?我们由上面的分析可知,三个距离为一循环,每个相应循化中的值,性质是一样的。如果如果描述的是x吃y的关系(t == 2),则求(d[y] - d[x]) %3 是否等于1,若y与x相距一个单位,则x吃y,这个可以参考我们上述的递推过程。若是px != py那该怎么办?这就表明x 与 y之前没有任何语句建立过他们俩的关系,因此我们可以认为当前这句话就是对的,要按照这句话来构建x与y的关系。如果t == 1表明x与y同类,首先我们要合并两者所在的区间,p[px] = p[py],集合合并后以根节点py为最终的祖宗元素,此时之前x集合的根节点px距离根节点py的距离未知,而d[x]还是x距离px的距离,但根据二者同类可知,(d[px] + d[x]  -d[y])mod 3 是否 == 0,即d[px] = d[y] - d[x]。同理要是t == 2,(d[px] + d[x] - d[y]) mod 3 是否 == 1 ,转换为  d[px] = d[y] + 1 - d[x]。上述就是判断假话的全过程,用res记录假话数量,进行m次即可。详见如下代码。

代码 :

#include<iostream>
#include<cstdio>
using namespace std;
const int N = 5e5 + 7;
int n, m;
int p[N], d[N];//p数组记录每个节点的父节点(根节点的父节点就是其本身) d数组记录的是每个节点到其父节点的距离(初始时每个点的父节点就是其本身,所以初始化为0) 
int find(int x) {
	if (p[x] != x) {
		int t = find(p[x]);//先用临时变量来记录x的祖宗节点 
		d[x] += d[p[x]];//d[x]来记录x到其祖宗节点的距离,上述递归回溯时,会将x与父节点,x的父节点与x的父节点的父节点等等等各段的距离相加,最后总和就是x 
		p[x] = t;      //递归后的语句是由内到外开始执行的,若此句放于递归语句前,则无法计算x到祖宗元素的距离 
	}   //这里是路径压缩,x节点最终指向根节点 
	return p[x];
} 
int main() {
	cin >> n >> m;
	for (int i = 1; i <= n; i ++ ) p[i] = i;//初始化集合 
	int res = 0;//res记录假话 
	while (m -- ) {
	
	   int t, x, y;
	   cin >> t >> x >> y;
	   if (x > n || y > n) res ++ ;//不符合条件二 
	   else {
	     	int px = find(x), py = find(y);//px,py分别记录x与y的祖宗节点 即所在的集合编号 
	    	if (t == 1) {//t等于1时,表示x与y是同类 
		    	if (px == py && (d[x] - d[y]) % 3) res ++ ;//d[x] 与 d[y] 都是 x,y到根节点之间的距离 
			    else if (px != py) {//不在同一集合中,这就暗示了之前并没有语句表明x与y之间的关系,此时这句话就是对的,然后我们按照x与y是同类来建立关系 
				   p[px] = py;//集合合并 
				   d[px] = d[y] - d[x];//求px到其父节点py的距离  
			    }
		    }
	     	else {//t == 2的情况,表示x吃y,即判断是否(d[x]- d[y]) % 3 == 1 即判断是否 (d[x]- d[y] - 1) % 3 == 0 
			    if (px == py && (d[x] - d[y] -1) % 3 ) res ++ ;
			    else if (px != py) {//不在同一集合中,两者之前并无关系,也就是这句话表示的两者关系就是对的 
				   p[px] = py;
				   d[px] = d[y] + 1 - d[x]; //d数组中值改变由此开始 
			    }
		    }
	    }
    }
	cout << res;
	return 0;
}

------------------------------------------------------------------------------

在此我们给出带权并查集的相关模板

维护到祖宗节点距离的并查集:

    int p[N], d[N];
    //p[]存储每个点的祖宗节点, d[x]存储x到p[x]的距离

    // 返回x的祖宗节点
    int find(int x)
    {
        if (p[x] != x)
        {
            int u = find(p[x]);
            d[x] += d[p[x]];
            p[x] = u;
        }
        return p[x];
    }

    // 初始化,假定节点编号是1~n
    for (int i = 1; i <= n; i ++ )
    {
        p[i] = i;
        d[i] = 0;
    }

    // 合并a和b所在的两个集合:
    p[find(a)] = find(b);
    d[find(a)] = distance; // 根据具体问题,初始化find(a)的偏移量

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

在森林中麋了鹿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值