题意:
简单来说,给出n句话,判断谎言的个数,谎言就是与前面真话冲突的话。带权并查集的典型题。
对带权并查集的理解:
个人觉得带权并查集就是给出了结点的相对关系,已知相对关系的点就放到一个连通的块里,也就是并查集有同一个根节点。
这些相对关系又能转化到一个结点上,这个结点就是它们共有根节点,而转化到同一结点的过程就是路径压缩的过程。权值即数组r[ ]里面的值是该结点到其父节点的距离,只是在路径压缩中该结点前面的结点的父节点都变成了根节点。
以本题为例,下图为压缩路径的回溯过程,就是修改到父节点距离为到根节点的距离的过程(当然father数组的值也变为了根节点,r[ ]的意义依然是到父亲节点,只是父亲节点都变为了根节点。)最后一个图 1结点到根节点10的距离是9,手绘失误
思路:
利用前缀和的思想,[r~l]=[r,1]-[l-1,1]的值。我们用节点x代表x的前缀和,那么给出的【l,r】=4之间的和就是r结点比l-1结点大4,就是给出了相对关系,然后再利用矢量的思想进行相对关系转换。
代码:
#include<cstdio>
#include<string>
#include<cstring>
#include<iostream>
using namespace std;
const int maxn=2e5+10;
int father[maxn],r[maxn];
int ans;
void init(int n)
{
for(int i=0;i<maxn;i++)
{
father[i]=i;
r[i]=0;
}
}
//路径压缩
int findfather(int x)
{
if(x!=father[x])
{
int pre=father[x];
father[x]=findfather(father[x]);
r[x]=r[x]+r[pre];
}
return father[x];//要return father[x]而不是x
}
void combine(int x,int y,int s)
{
int fx=findfather(x);
int fy=findfather(y);
if(fx==fy)
{
if(s!=r[x]-r[y]) ans++;
}
else
{
father[fx]=fy;
r[fx]=s+r[y]-r[x];//合并转移
}
}
int main()
{
int n,m;
while(~scanf("%d%d",&n,&m))
{
init(n);
ans=0;
for(int i=1;i<=m;i++)
{
int a,b,s;
scanf("%d%d%d",&a,&b,&s);
combine(a-1,b,s);
}
printf("%d\n",ans);
}
return 0;
/*理解为b的前缀和比a-1的前缀和大s*/
}