HDU3038 How Many Answers Are Wrong

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;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值