并查集-区间问题

来源:互联网 发布:在淘宝怎么设置优惠券 编辑:程序博客网 时间:2024/06/06 14:27

 题目链接: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 200005int 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);}}




原创粉丝点击