题目
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 并查集题单。我的该题单做题记录
//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;
}