题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3038
题目大意:给出许多个区间的和,问你在前面给出的天台见都正确的情况下,当前条件是假的 的条数有多少个
题目分析:一般看到并查集的题目,首先要想到怎么去构造,构造的内容有俩个方面,
一个是树的根节点的合并,一个是单个树的状态压缩。
对于这题,比较经典,我们首先来分析第一个,单个树的状态压缩:
a->b->c->d 首先肯定有个这样的树 那么我们现在要实现find(a)的话就要使得结果达到这样一个效果,那就是 使得这个树只有俩层 ,a->d b->d c->d ,那么对于这个带权并查集怎么实现。我们想到了递归 ,用一个数组来保存当前节点到他的父节点的区间和,那么通常的这个递归算法就是先递归到底 然后再回溯逐层求解的过程,那么我们需要从a先递归到d(这也是并查集板子状态压缩的思路),但是最后我们的数组v[a]保存的就成了a到d的区间和 ,那么这就很简单了,用递归求出前面的区间和[b,d] 然后v[a] = v[a] +v[b] 即可
那么对于第二个 ,根节点的合并怎么处理:
首先 我们又俩颗树 要实现函数add(int a,int b)
那么我们传统的思路就是先找到a和b的根节点 ,即 调用find函数 ,这个函数同时也实现了路径压缩的功能,使得你的俩颗树变成了 a-->ra b-->rb 那么现在有个关系就是你要合并[a,b] 那么我们可以在这俩个图之间画一个循环出来 ,类似于大学物理里面的电势下降相等 即 ra->a ra->rb rb->b a->b 那么这个是合并之后的 ,大家可以自己在纸上画出一个循环的图 那么沿着箭头方向 下降的高度一致 则得到了这个结论:
s[a]+[a,b] ==s[r] + [rb,ra] 于是[rb,ra] 就是我们要唯一更新的那个值因为只有rb的状态变化。所以[rb,ra] = sa-sr+s(a,b)
对于这题,我们还要注意一个问题就是 ,题目给的是闭区间的区间和,那么我们在状态压缩的时候啊,就会使得边界的值发生重复 叠加的情况,那么我们这么优化下这个并查集让 父节点是区间的(左边界-1),子节点是右边界,那么我们在s做+=的时候就不会计算俩次了。(这个思路看上去就是根节点是较小的那个边界 子节点则越来越大)
//想法 :对于并查集 其实质就是一颗树 那么我们在求解的时候 用树的性质去维护就可以了
#include<iostream>
using namespace std;
#define maxn 200005
int pre[maxn];
int sn[maxn];//维护一个和 a------aa------aaa (根)
void init()
{
for(int i=0;i<=maxn-1;i++)
{
pre[i]=i;
sn[i]=0;
}
}
int find(int x)
{
if(x!=pre[x])
{
int father = pre[x];
pre[x] = find(pre[x]);//找到根节点 一路
sn[x] += sn[father];//
}
return pre[x];//
}
int main()
{
int n,m;
while(scanf("%d%d",&n,&m)!=EOF)
{
int ans=0;
init();
for(int i=1;i<=m;i++)
{
int a,b,s;
scanf("%d%d%d",&a,&b,&s);
a--;// [a,ra]
int aa=find(a);
int bb=find(b);
if(aa!=bb)//如果不是一个集合 合并
{
pre[aa]=bb;
sn[aa]= sn[b]-sn[a]+s;//俩个区间根的差 a----------ra //sn[a]
// s:( b)-------(---rb) //sn[b] sn[b]-sn[a]+s
}
else//同一个集合
{
if(sn[a]-sn[b]!=s)
ans++;
}
}
printf("%d\n",ans);
}
}