HDU3038 How Many Answers Are Wrong
(带权并查集经典例题)
题目大意:
一共有n个数,接下来的m行,每行给出一组a,b,s,代表从第a到第b个数的和为s,但m组数据中会有错误的信息,要求你找出共有多少组错误的信息(并且该题默认当前的信息若不与之前的信息冲突的话即为正确的,若与之前的信息冲突,则认为该信息是错误的,并忽略该条信息)
样例:
Sample Input
10 5
1 10 100
7 10 28
1 3 32
4 6 41
6 6 1
Sample Output
1
思路:这是我第一次做带权并查集的题目,众所周知并查集是一种数据结构,其形态是一棵树,当我们使用路径压缩后,树的形态便成为了以一个节点为根连接其余所有节点的一棵树。该题每个数都有自己的权值,我们可以将该权值转化看为节点之间的距离,且该权值为与该号节点连的后面那条边的权值。如第一个与第五个数之间所有数的和为x;那么我们将其转化为第一号节点与第六号节点之间的距离(如图所示)
因此每次我们读入a,b,s时,需将b+1,原因可参照上图。额外开一个数组dis,代表该点到其父亲节点的距离,初始时每个点的父亲都是他们自己,即dis[i]=0。
在读入数据时查询a,b两点的祖先,若祖先不同,则说明二者不在同一个集合中(也可理解为二者不在同一棵树上),我们便将二者合并,设a对应树的祖先为fx,b对应树的祖先为fy,将fy合并至fx树上(新树以fx为根节点)(当然也可以将fx合并至fy上),可以发现合并时我们只需求出新树中fy至根节点fx的距离,fy的子节点在find函数路径压缩过程中会被求出来,可以看出这些数呈线性排列,因此通过画图易得出dis[fy]的表达式:
若祖先相同,则说明二者已经在同一棵树中,其距离应该是dis[b]-dis[a](题目的数据默认b大于a),因此我们便判断给出的s是否等于dis[b]-dis[a],若不相等,则说明该组信息是错误的,令ans+1即可。
代码实现:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
using namespace std;
#define ll long long
#define clear(a) memset(a,0,sizeof a)
#define maxn 200010
int n,m,ans;
int fa[maxn],dis[maxn];
int findx(int x){
if(x==fa[x])return x;
int fx=findx(fa[x]);
dis[x]+=dis[fa[x]];
//注意:此处不可写为dis[x]+=dis[fx];
//因为该语句是在回溯后处理的,fx此时已经为x的祖先了;
//我们每次需将dis[x](x到其父亲的距离)加上dis[fa[x]](x父亲到x爷爷的距离)以此来处理出x到其祖先的距离
fa[x]=fx;
//处理完dis数组后再将x的父亲更新为祖先(路径压缩)
return fx;
}
int main(){
while(scanf("%d%d",&n,&m)!=EOF){
clear(dis);//初始时该点到父亲节点(自己)距离为0
ans=0;
for(int i=1;i<=n+1;i++)fa[i]=i;
//n+1是因为我们虚拟出的节点必然比数字编号大一
for(int i=1;i<=m;i++){
int a,b,s;
scanf("%d%d%d",&a,&b,&s);
b++;
int fx=findx(a);
int fy=findx(b);
if(fx!=fy){//祖先不同则将两棵树合并
fa[fy]=fx;
dis[fy]=dis[a]+s-dis[b];
//处理fy到新祖先的距离
}
else if(s!=dis[b]-dis[a])
ans++;//矛盾则说明该句话是错的
}
printf("%d\n",ans);
}
return 0;
}