hdu3038(带权并查集)

题目传送门

另一位大佬的链接https://www.cnblogs.com/liyinggang/p/5327055.html

以下是我看了这篇文章的不同的感悟



题意:

给出n个数字,每个数字带有一个权值(正数),给出m个数据,每输入一组数据,判断此数据是否与前面冲突,如果冲突,错误数据+1,最后输出错误数据的个数。


思路:

并查集思想,father[x]还是为父节点,但是多了一个sum[x],这个sum[x]数组代表着x到father[x]的权值,节点的父节点一定小于等于子节点(此文章要表示的意思)
1.当输入的a与b是相同的父节点是,判断节点是否冲突。
2.为不同父节点时,将他们合并(合并在下面)

find函数(路径压缩且返回父节点的父节点的…父节点)

int find(int x){
	if(x != father[x]){
		int temp = father[x];
		father[x] = find(father[x]);
		sum[x] += sum[temp];
	}
	return father[x];
}

例如:find(1) :father[1] = 2,father[2] = 3,father[3] = 3;从1找到2再找到3,当x=3递归返回3,进入x=2的find函数,得:sum[2] = sum[2]+sum[3],再进入1的find函数得:sum[1] = sum[1] + sum[2],本来1的父节点是2,2的父节点是3,此操作直接将1的父节点变成了3,不仅节点与节点连接,值也与相应的节点的值连接了。


样例
10 5
1 10 100
7 10 28
1 3 32
4 6 41
6 6 1

有10个数字,给出5个数据

1 10 100

sum[1~10] = 100


7 10 100

先判断此数据是否与前面所有以保存的数据冲突
这里可知不冲突,所以
sum[7~10] = 28
sum[1~6] = 100 - 28


1 3 32

与上例一样
sum[1~3] = 32


4 6 41

因为已知sum[1~3] = 32
还已知sum[7~10] = 100 - 28(sum[1-10] - sum[1-6])
所以此数据与前面的不符,ans++,直接跳过这个数据


6 6 1

不冲突 sum[6~6] = 1
所以输出1


合并代码

void unionset(int a,int b,int u,int v,int w){
	if(u > v){
		val[u] = val[b] - w -val[a];
		father[u] = v;
	}
	else{
		val[v] = val[a] + w - val[b];
		father[v] = u;
	}
}

- 代码解释(u为a的父节点,v为b的父节点)

在这里插入图片描述

已知10的父节点是1,和7的父节点是6,10-1的值和7-6的值都知道,他们是不同的父节点,所以进行合并操作,合并时判断u和v的大小,如果u大于v,则将u合并在v上,此时u的父节点变成了v(因为本文规定父节点总是小于等于子节点)。
所以看路线图,w + 30 + y应该等于100所以 val[u] = val[b] - w -val[a](val就是sum,这里不记得改了)
在这里插入图片描述
这种情况为val[v] = val[a] + w - val[b](看向量的指向)

注意!!:图片里的a没有-1,因为[a,b] = [v,b]-[u,a-1],我这样画是为了方便理解。


全部代码

#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<algorithm> 
using namespace std;

const int maxn = 200010;
int father[maxn];
int val[maxn];
int sign = 0;

int find(int x){
	if(x != father[x]){
		int temp = father[x];
		father[x] = find(father[x]);
		val[x] += val[temp];
	}
	return father[x];
}

void unionset(int a,int b,int u,int v,int w){
	if(u > v){
		val[u] = val[b] - w -val[a];
		father[u] = v;
	}
	else{
		val[v] = val[a] + w - val[b];
		father[v] = u;
	}
}

int main()
{
	int n,m,a,b,w;
	while(scanf("%d%d",&n,&m) != EOF){
		//init
		for(int i=0;i<=n+1;i++) father[i] = i;
		sign = 0;
		memset(val,0,sizeof(val));
			
		while(m--){
			scanf("%d%d%d",&a,&b,&w);
			//a-- ro b++ 都可以通过
			a--;
			//b++;
			int u = find(a);
			int v = find(b);
			
			if(u == v){
				if(val[b] != val[a] + w) sign++;
			} 
			else{
				unionset(a,b,u,v,w);
			}
			
			
		}
		printf("%d\n",sign);
	}
	
	return 0;
}
  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值