题目链接
题意
给出一段区间1-n,下面m行给出一端连续区间a-b的和,如果遇到冲突就忽略掉,问共几条指令冲突
思路
带权并查集,我们在维护并查集的同时维护一个数组value,在这个题中,给出的a-b的和可以看成是a-1节点到b的距离。我们把value数组定义为到祖先节点的距离,当出现的a-1和b祖先不同时,我们连接他们,并且更新value数组,如果相同,则通过比对a-1和b的权值差与给定的和是否相等,不等就更新答案。
框架已经搭好,下面重点放在init,find,unite三个函数的设计上
-
init(初始化)函数
初始化是最简单的,我们除了要设置fa数组外,再初始化value数组为0即可
int init(){ for(int i=0;i<maxn;i++){ fa[i]=i; va[i]=0; } }
-
find(查找)函数
普通的find函数就是递归加上路径压缩的优化。带权之后我们就需要更新value数组了。
在更新的前面要先理解路径压缩,路径压缩就是在每次find后,把这次查找所有涉及到的所有节点都直接连接到祖先节点上。如图在上图中,我们先链接12,再连接23,find函数查找1后,将1直接连到根节点3上了,当我们定义了value数组后,这张图是这样的
在连接好12后,我们的v1是1-2的距离,在连接好23后,v2是2-3的距离,而此时的v1还未更新,他并不是我们所定义的到根节点的距离,而只是到达他父节点的距离,所以在find函数中,我们应当更新v1,显然,1到根节点3的距离应为1的value加上父节点2的value,即v1+v2。具体到代码就是:
int find(int x){ if(fa[x]==x) return x; int root=find(fa[x]);//祖先节点 va[x]+=va[fa[x]];//更新一下va return fa[x]=root; //将x直接连接到祖先节点上 }
-
unite(连接)函数
这个是设计起来最麻烦的了,不考虑按轶合并的优化,普通的unite函数只需要把两个节点的祖先节点合并就可以了,那么加上value我们要怎么设计呢?
还是见图,我们连接1,3节点,那么首先找到根节点2,4.我们将2连接到4上,4的value按照定义为0,2本来的value是0,现在我们要更新它,让v2是2到4的距离。v1是1到2的距离,v3是3到4的距离,此外,题中还告诉了我们1到3的距离,那么1-2-4和1-3-4的路径长度应是相等的,也就是图中写的那个等式。代码实现如下
void unite(int x,int y,int v){ int fx=find(x),fy=find(y); fa[fx]=fy; va[fx]=v+va[y]-va[x]; }
至此,我们的三个函数已经设计完成了。
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=200005;
int fa[maxn];
int va[maxn];//距离祖先节点的距离
int n,m;
int init(){
for(int i=0;i<maxn;i++){
fa[i]=i;
va[i]=0;
}
}
int find(int x){
if(fa[x]==x)
return x;
int root=find(fa[x]);//祖先节点
va[x]+=va[fa[x]];//更新一下va
return fa[x]=root; //将x直接连接到祖先节点上
}
void unite(int x,int y,int v){
int fx=find(x),fy=find(y);
fa[fx]=fy;
va[fx]=v+va[y]-va[x];
}
int main(){
int n,m,ans,x,y,z;
while(cin>>n>>m){
ans=0;
init();
while(m--){
cin>>x>>y>>z;
x--;
if(find(x)==find(y)){
if(va[x]-va[y]!=z)
ans++;
}
else{
unite(x,y,z);
}
}
cout<<ans<<endl;
}
}