题目大意:
有n个人坐在zjnu体育馆里面,然后给出m个他们之间的距离, A B X, 代表B的座位比A多X. 然后求出这m个关系之间有多少个错误,所谓错误就是当前这个关系与之前的有冲突。
分析:
首先我们假设所有节点均在不同行的0位置,即rank[]初始化需为零,同时p[]需要初始化为-1,表示各个节点之间不存在联系,相互独立;
接下来,我们获取信息:A B X;那么我们需要检查 :
1. 如果A B的father相同,那么说明前面的一组(A B X)直接或者间接地更新了A B间的距离,我们便需要检查已经存在距离,和最新输入的X是否相违背;
2. 如果A B的father不相同,那么是否能直接把B以及与B同在一个集合的元素加入A所在的集合,而不发生冲突呢?
这里的冲突指的是:假设节点1是根节点,已知节点2距离节点1 200m,现在读取新的输入:1 3 200 。我们发现,节点2,3到达节点1 的距离相同,同一个位置上出现了两个节点,这显然与题意相违背,即冲突。
【问题】假设我们发现A属于ra集合,B属于rb集合,如何合并两个独立的集合ra,rb?
\[merge\left\{ {A,B|A \in ra,B \in rb} \right\}\]
事实上将集合rb并入集合ra是容易的,但是我们需要更新集合rb中的rank[],因为根元素变化了。
现在集合rb中的根元素的rank值不再是0了(自己到自己的距离为0),而是变成了该根元素到达ra集合中的根元素的距离。
假设输入为 A B X,rb为B所在集合的根元素,那么该根元素的更新公式为:
\[rank\left[ {rb} \right] = \left( {rank\left[ A \right] + rank\left[ B \right] + X + 300} \right)\% 300\]【问题】那么rb集合中的剩下的元素如何更新?何时更新?
我们可以在下一次的find操作中更新集合rb中的距离值,这样的话,通过路径压缩的方法,我们可以实现:
集合rb中的元素的father指向集合ra中的根元素,集合rb中的元素的rank表示距离集合ra中的根元素的距离。
更新公式:(x为当前节点,p[x]表示x的父节点)
\[rank\left[ x \right] = \left( {rank\left[ x \right] + rank\left[ {p\left[ x \right]} \right]} \right)\% 300\]
题解:
#include<iostream> #include<cstdio> using namespace std; #define MOD 300 #define maxn 50001 int p[maxn]; int rank[maxn]; void init(int n){ memset(p,-1,sizeof(p)); memset(rank,0,sizeof(rank)); } int find(int x){ if(p[x]==-1) return x; else{ int tmp=p[x]; p[x]=find(p[x]); rank[x]=(rank[x]+rank[tmp])%MOD; return p[x]; } } bool judge(int a,int b){ return find(a)==find(b); } bool Union(int a,int b,int x){ int ra=find(a); int rb=find(b); if(ra==rb){ if((rank[b]-rank[a]+MOD)%MOD!=x) return false; return true; } rank[rb]=(rank[a]-rank[b]+x+MOD)%MOD; p[rb]=ra; return true; } int main(){ int n,m; while(scanf("%d %d",&n,&m)!=EOF){ int a,b,x; int cnt=0; init(n); while(m--){ scanf("%d %d %d",&a,&b,&x); if(!Union(a,b,x)) cnt++; } printf("%d\n",cnt); } }