HDU 3038 How Many Answers Are Wrong(带权并查集)

题目链接

题目大意

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

解题思路

  当一段子区间和与前面发生冲突是,说明可以从之前的信息中得到这个子区间的值并且两者不一样。那么问题就是怎么从所给信息中确定一个子区间的值了。如果我们可以得到两点之间的值,说明它们有一个共同的公共点,所以就能想到用并查集来解决。比方说我们知道了[1,3]的值和[1,10]的值,那么3和10有一个公共点1,我们就可以根据1来推出[4,10]的值(为什么不是[3,10]?因为都是闭区间,在纸上画画就知道了),假如之后又出现了[4,10]的值且和我们算出来的不一样,那肯定就冲突了。
  那么如何维护这个并查集呢?我们设sum[x]表示x到根结点的值,那么对于两个点a和b以及他们之间的距离d,我们把a的父节点挂在b的父节点下面,使它们在一个集合之中,因为a到b的距离是d,所以a到b的距离加上b到b的父节点的距离就应该和a到a的父节点的距离加上a的父节点到b的父节点的距离相同。所以我们只要更新一下a的父节点到b的父节点的距离,然后在路径压缩的过程中更新每个点到其父节点的距离就好了。

代码

const int maxn = 2e5+10;
int p[maxn], sum[maxn];
int find(int x) {
    if (p[x]!=x) {
        int tmp = p[x];
        p[x] = find(p[x]);
        sum[x] += sum[tmp];
    }
    return p[x];
}
int main(void) {
    int n, m;
    while(~scanf("%d%d",&n,&m)) {
        for (int i = 0; i<=n; ++i) {
            p[i] = i; sum[i] = 0;
        }
        int cnt = 0;
        for (int i = 0,a,b,c; i<m; ++i) {
            scanf("%d%d%d",&a,&b,&c);
            --a;
            int fa = find(a), fb = find(b);
            if (fa==fb && sum[a]-sum[b]!=c) ++cnt;
            else if (fa!=fb) {
                p[fa] = fb;
                sum[fa] = sum[b]-sum[a]+c;
            }
        }
        printf("%d\n",cnt);
    }
    return 0;
}
posted @ 2020-06-04 16:13  shuitiangong  阅读(173)  评论(0编辑  收藏  举报