题面
题目的意思大概是,连续给出多条信息,每条信息代表一段区间和。
比如,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;
}
}