题目
1-300列,无限行
N个人,每人坐在一列
M个叙述,表示A所在的列在B所在的列的前x列
问有多少种说法是假的
即若A在B的前x1列,A又在B的前x2列,则假
思路来源
https://blog.csdn.net/shuangde800/article/details/7983965
https://blog.csdn.net/xiaolonggezte/article/details/53025150
题解
学了并查集这么久竟然没入带权并查集
可以说是很菜了
有个等价的食物链的题 可以用三个集合的合并关系来搞
但思路不如这个简单
每棵树上维护这个节点到根节点的距离,即相距列数
A所在的树与B所在的树合并的时候,
不妨记A所在树树根root1,B所在树树根root2
令par[root2]=root1,即把root2挂在root1上
只改root2这个根节点
在root1这棵新树里,即以root1为0列
A排在pos[A]列,B比A后x列即(pos[A]+x)列
而root2比B前pos[B]列,则root2排在(pos[A]+x-pos[B])列
只改根,查询叶子结点的时候
把这条链的父亲节点的父亲,
都压缩为根,
利用回溯时,从根到叶,
搞链的前缀和,
相当于令该节点一步连根
2020年3月19日补充:
图片来源于Lazines_by的博客,此时要确定的是fa[a]和fa[b]的相对大小,
即fa[b]=0时,b比fa[b]大d[y],而a比b小z,fa[a]比a大d[x],
显然fa[a]相对于fa[b]的位次为-d[x]+z+d[y],
未路径压缩时,是相对于自己的根的相对rank,路径压缩时是相对于新根的相对rank
代码
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn=5e4+10;
int n,m;
int a,b,x,ans;
int par[maxn],pos[maxn];
void init()
{
for(int i=1;i<maxn;++i)
{
par[i]=i;
pos[i]=0;
}
ans=0;
}
int find(int x)
{
if(par[x]==x)return x;
int fa=par[x];//直接基类
par[x]=find(par[x]);//路径压缩
pos[x]+=pos[fa];//树上前缀和 两链变一链 直接连树根
return par[x];//返回树根
}
bool unite(int x,int y,int v)//y比x大v
{
int px=find(x),py=find(y);
if(px==py)
{
if(pos[x]+v!=pos[y])return 0;
return 1;
}
//把y所在树的树根挂在x所在树的树根上 只改树根py
par[py]=px;//rank[py]=rank[y现在]-rank[y之前]==(rank[x]+v)-rank[y]
pos[py]=pos[x]+v-pos[y];//注意rank可能有负 与将负旋为0的根是等价的
return 1;
}
int main()
{
while(~scanf("%d%d",&n,&m))
{
init();
while(m--)
{
scanf("%d%d%d",&a,&b,&x);
if(!unite(a,b,x))ans++;
}
printf("%d\n",ans);
}
return 0;
}