带权并查集|How many answer are we wrong

HDU-3038

题面

题目的意思大概是,连续给出多条信息,每条信息代表一段区间和。

比如,1 4 5 代表[1,4]的和为5,也可以表示为(0,5]的区间和为5。然后,要求我们判断出无效的(也就是错误的)信息数目。

例如,已知(0,4]区间和为5,(2,4]的区间和为2,此时我们可以计算出(0,2]的区间和为3。假如现在给了一条信息为:(0,2]的区间和为4,那么我们判断这条信息为错误信息,错误信息数目要加1.

根据已知信息,计算某段区间和

简单分析完题面的意思,现在来观察在什么情况下,能够根据信息计算出某段区间的和(例如,上面已知(0,4]和(2,4]可以推出(0,2])。

通过上面的例子,如果我们可以得到一个结论:假如有区间(x,y],要想计算其区间和,我们必须先知道(x,z]和(y,z]的区间和是多少。(z是某个端点)

使用并查集

区间(x,z]和(y,z]有共同点,就是右侧端点都是z。假如使用并查集,可以理解为x与y都属于以z为root的集合。

也就是当输入的信息x,y,v中x与y都属于同一个集合时,我们可以计算的到(x-1,y]的区间和,同时我们又被告知(x-1,y]的区间和为v。此时,我们就可以比较v是否等于计算出来的区间和,若不是,则错误信息数目加1.

使用sum数组保存权值

首先定义数组sum,sum[i]表示由节点i-1到其根节点的区间和,即(i-1,root]的区间和,在下面我们理解为i到root的正向距离。可以推出(x-1,y]的区间和,即x到y的正向距离为sum[x]-sum[y]

在union中维护sum

当两个集合进行合并时,例如x与y进行合并时(默认将x接在y上),集合x中的所有元素到根的距离都会发生变化(即sum[i]需要更新)。

在union方法中,我们只维护sum[x]的值,至于原来集合x中其他元素的sum值,在find方法中维护。

以输入信息为a b v 为例。假设a的根为x,b的根为y,合并集合x与y时,有如下的公式:
因为sum[x]+sum[a] = v + sum[b](画个简单的图理解)
所以有sum[x] = v + sum[b] - sum[a]

故在合并集合x与集合y时,更新sum[x] = v + sum[b] - sum[a]
实际上,集合x中其他元素的sum值也是要更新的,但我们选择在find中更新。

在find中维护sum

接着上面的例子,现在要在find方法中维护集合x其他元素的sum值,也就是要使sum[a] += sum[x],可以这样写find方法:

private static int find(int x) {
		if (parent[x] == x)
			return x;

		int fa = parent[x]; // 压缩前的根节点
		parent[x] = find(fa);// 压缩
		sum[x] += sum[fa];// 更新权值

		return parent[x];
	}

需要注意的地方大概就是这些了,下面是本题的实现。

Code

package DisjoinSet;

import java.util.Scanner;

public class 带权并查集模板 {
	static int parent[];
	static int rank[];
	static int sum[];
	static int n;
	static int count;

	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		while (sc.hasNext()) {
			n = sc.nextInt();
			int m = sc.nextInt();
			init();
			// m 组询问
			for (int i = 0; i < m; i++) {
				int x = sc.nextInt();
				x--;// 维护左开右闭的区间呢
				int y = sc.nextInt();
				int v = sc.nextInt();
				union(x, y, v);
			}
			System.out.println(count);
		}
	}

	private static void init() {
		parent = new int[n + 1];
		for (int i = 0; i < parent.length; i++) {
			parent[i] = i;
		}
		// rank = new int[n + 1];
		sum = new int[n + 1];
		count = 0;
	}

	private static int find(int x) {
		if (parent[x] == x)
			return x;

		int fa = parent[x]; // 压缩前的根节点
		parent[x] = find(fa);// 压缩
		sum[x] += sum[fa];// 更新权值

		return parent[x];
	}

	private static int union(int x, int y, int v) {
		int rootX = find(x);
		int rootY = find(y);
		if (rootX == rootY) {// x与y有共同的基准,故可以计算出x到y的距离
			int distance = distance(x, y);
			if (distance != v)
				count++;
			return -1;
		}

		parent[rootX] = rootY;
		sum[rootX] = v + sum[y] - sum[x];// 更新sum[rootX]
		return 1;
	}

	// 根据x与y共同的基准,得到x到y的距离
	// 注意其本质是计算向量
	private static int distance(int x, int y) {
		int distance = sum[x] - sum[y];
		return distance;
	}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值