带权并查集 (HDU-3038 , POJ-1182 , etc.)

题目

How Many Answers Are Wrong
链接https://vjudge.net/problem/HDU-3038#author=hpreon

给出一个区间的长度 N,及 M 个子区间和, 形如:x y z, 表示
子区间 [x, y] 的和为 z
如果一个“子区间和”与前面的“子区间和”冲突,即为错误(而且这个“子区间和”将在接下来的判断中被忽略)。
求总错误个数。

有多组数据。
[input]
每组数据第一行两个整数N M (1 <= N <= 200000, 1 <= M <= 40000),
接下来每行有 M 对关系 x y z;
注:可以认为 z 为32位整型。
[Output]
错误个数。

思路

考虑为每个区间和信息构建一条带权边,形成一个图: 边 a->b 的值代表区间 (a,b] 的和。

解释:

  • 为什么用这样边的形式建图?
    因为可以发现这样区间的信息和并查集有相似的特性,都是随着信息增多而分出的块越来越少(都合并了),这样利用带权值并查集进行路径压缩就可以提高效率了。
  • 为什么区间和是左开右闭
    因为我们想达到的目的是,从点 x 走到点 y ,路径权值即为区间和。若是采用全开或全闭区间,会发现路径包含多个点的时候无法刚好覆盖整个 定义出来的区间 ,而半开半闭刚好就可以。

操作:

  • 其实操作起来并不复杂,只是在普通并查集路径压缩那里多维护一下边权 s[] 数组即可。
  • 注:s[x] 代表左开右闭区间 ( x,f[x] ] 的和。
  • 更多具体的东西看看下面的图解或者最后的代码(里面也有注释)吧。

复杂度:

就是线性复杂度吧?

图解:
在这里插入图片描述

代码

//https://vjudge.net/problem/HDU-3038
#include <cstdio>

//设当前节点为 x 
//f: 指向的父节点 (f>x) 
//s: 区间 (x,f] 的和 (左开右闭)
int f[200005],s[200005];
int n,m,a,b,fa,fb,z,ans;

int Fa(int x)
{
    
    
	if (f[x]!=x) {
    
    
		int tmp=f[x];
		f[x]=Fa(tmp);	//先压缩好父节点的路径,维护好其 s[] 值后才能计算当前节点的 s[] 值
		s[x]+=s[tmp];
	}
	return f[x];
}

void init() {
    
    
	ans=0;
	for (int i=0; i<=n; i++) {
    
    	//注意从 0 开始,因为开区间,会用到 0
		f[i]=i;
		s[i]=0;
	}
}

int main()
{
    
    
	while (scanf("%d%d",&n,&m)!=EOF) {
    
    
		
		init();
	
		while (m--) {
    
    
			scanf("%d%d%d",&a,&b,&z);
			a--;
			fa=Fa(a);
			fb=Fa(b);
			
			if (fa==fb) {
    
    
				ans+=(s[a]-s[b]!=z);	//判断是否失败
			}
			else {
    
    
				f[fa]=fb;
				s[fa]=z+s[b]-s[a];	//向量加减得到
			}
		}
		
		printf("%d\n",ans);
	}
	return 0;
}

补充题

可以看看 kuangbin 并查集题单。我的该题单做题记录

食物链 POJ - 1182

//https://vjudge.net/problem/POJ-1182
#include <cstdio>
#define N 50005

int f[N];	//并查集 
int g[N];	//与父亲的关系 (0:x=f 1:x>f 2:x<f) 
int ans,n,k,D,a,b,fa,fb,p;

int Fa(int x)
{
    
    
	if (f[x]!=x) {
    
    
		int tmp=f[x];
		f[x]=Fa(tmp);
		g[x]=(g[x]+g[tmp])%3;
	}
	return f[x];
}

void init()
{
    
    
	ans=0;
	for (int i=1; i<=n; i++) {
    
    
		f[i]=i;
		g[i]=0;
	}
}

int main()
{
    
    
	scanf("%d%d",&n,&k);
	init();
	while (k--) {
    
    
		scanf("%d%d%d",&D,&a,&b);
		
		if (a>n || b>n) {
    
    
			ans++;
			continue;
		}
		
		p=(D==2);	//a->b = p
		
		fa=Fa(a);
		fb=Fa(b);
		if (fa==fb) {
    
    
			ans+=((g[a]-g[b]+3)%3!=p);
		}
		else {
    
    
			f[fa]=fb;
			g[fa]=(p+g[b]-g[a]+3)%3;
		}
	}
	printf("%d\n",ans);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/jackypigpig/article/details/113348863