The Third Letter
这是一个并查集的题目。
题目要求我们维护多个人之间关系的传递(譬如说已知 A 在 B 前面 ,B 在 C 前面 ,则应该能计算出 在 前面 )。
考虑并查集:已知 和 处于一个集合, 和 处于一个集合,则 和 处于一个集合。这是不是很像题目要求我们维护的关系?因此。我想到使用并查集解决这个问题。
题目要维护人们之间的距离,因此原先的 fa
数组应当作修改。fa
数组的定义如下:
struct node{size_t p;long long int dis;};
vector<node> fa;
其中,p
仍然表示父节点,而 dis
表示子结点在父节点前面的距离(若在后面则为负数)。
find
函数除了要找到树根,还要计算当前节点在树根前面的距离。
node find(size_t x){
if (x==fa[x].p) return {x,0}; //自己就是树根,与树根的距离就是0
else{
node res=find(fa[x].p);
return fa[x]={res.p,res.dis+fa[x].dis};
/*树根就是 res.p;
当前节点到树根的距离 = 父节点与根的距离 res.dis + 当前节点与父节点的距离 fa[x].dis。
同时路径压缩,fa[x] 接记录与根的距离。*/
}
}
最后,就是最重要的合并函数了。我选择将查询函数与合并函数放到一块写。若 x
和 y
不在一个集合内(关系无法判断),则合并然后返回 true
;否则,根据并查集维护的两点之间的距离与给定条件比对。
bool merge(size_t x,size_t y,long long int dis){
node fax=find(x),fay=find(y);
if (fax.p!=fay.p){
fa[fax.p]={fay.p,-fax.dis+fay.dis+dis};
return true;
}else return fax.dis==fay.dis+dis;
}
最后,不开 long long 见祖宗(某人因此被 hack 了)
完整代码:
#include<bits/extc++.h>
using namespace std;
namespace pbds=__gnu_pbds;
using ui=unsigned int;
class dsds{
struct node{size_t p;long long int dis;};
vector<node> fa;
node find(size_t x){
if (x==fa[x].p) return {x,0};
else{
node res=find(fa[x].p);
return fa[x]={res.p,res.dis+fa[x].dis};
}
}
public:
//构造函数,将数组大小初始化为 n ,循环赋值父节点为自己,dis 初始化为 0
dsds(size_t n):fa(n){for (size_t i=0;i<n;i++) fa[i].p=i,fa[i].dis=0;}
bool merge(size_t x,size_t y,long long int dis){
node fax=find(x),fay=find(y);
if (fax.p!=fay.p){
fa[fax.p]={fay.p,-fax.dis+fay.dis+dis};
return true;
}else return fax.dis==fay.dis+dis;
}
};
int main(void){
ios::sync_with_stdio(false),cin.tie(nullptr),cout.tie(nullptr);
size_t T;
cin>>T;
while (T--){
size_t n,m;bool flag=true; //是否全部符合的标记
cin>>n>>m;
dsds ds(n);
while (m--){
size_t a,b;long long int d;
cin>>a>>b>>d;
if (!ds.merge(--a,--b,d)) flag=false; //个人习惯从 0 存储因此 -1;当不符合(返回 false)时才标记 flag,因此要作非运算
}
cout<<(flag?"YES":"NO")<<'\n';
}
return 0;
}