闲话
赛场就做了这道和 A,喜提 \(625\) 大分。
带权并查集练手题,有点像银河英雄传说。
题目大意
存在一个长度为 \(N\) 的数列 \(X\),给定 \(Q\) 个三元组 \((a_i,b_i,d_i)\),定义一个好集合为集合 \(S\subseteq\{1,2,\dots,Q\}\),使得所有 \(x\in S\) 满足 \(X_{a_i}-X_{b_i}=d_i\)。
初始 \(S\) 为空集,以从 \(1\) 到 \(Q\) 执行以下操作:设当前操作的数为 \(i\),如果 \(S\cup\{i\}\) 为好集合,则以 \(S\cup\{i\}\) 代替 \(S\)。
以升序打印出 \(S\) 中的元素。
题目分析
既然题目让我们从 \(1\) 到 \(Q\) 执行操作,那我们也从 \(1\) 到 \(Q\) 考虑。
对于这个操作我们只需判断是否与以前的操作构成数列的相对关系矛盾即可。
如果没有规定 \(X_{a_i}\) 与 \(X_{b_i}\) 有什么关系的话,直接添加 \(i\) 即可。
如果规定过 \(X_{a_i}\) 与 \(X_{b_i}\) 的关系,则判断这两个 \(X_{a_i}-X_{b_i}\) 是否相同,相同则添加。
这个关系是逐步添加却不会消失的,也会有传递性。
于是我就想到了并查集。
我们还要维护这两个值的差,于是我们可以使用带权并查集。
对于有关系的数就是合并在一个集合里,我们维护父节点与每个节点的差(也可维护相反数,本质相同)即可。
没有关系就不在一个集合里,进行合并操作。
有关系我们就判断差是否相等,相等就输出即可,因为这个关系的添加与否是等效的(不添加原来的操作也能判断关系),所以我们不进行合并操作也可以。
使用路径压缩的查询
设 \(dis_i\) 为 \(i\) 节点的父节点与其的差。
如下图,\(fa_3=2\),\(fa_2=1\),路径压缩压缩前我们可知
\(X_3+dis_{3old}=X_2\),\(X_2+dis_{2old}=X_1\),压缩后的 \(dis_{3new}=X_1-X_3\) 移项代入化简可得 \(dis_{3new}=dis_{2old}+dis_{3old}\)。
查询时该节点 \(i\) 的父节点 \(fa_i\) 如果是它自己就直接返回自己,否则先查询 \(fa_{i}\)(得到 \(dis_{fa_i}\) 为 \(fa_i\) 与祖先的距离,否则 \(dis_{fa_i}\) 为 \(fa_i\) 与父节点的距离,可能出错)。查询完用上式累加到 \(dis_i\) 上即可。
合并
我们只需考虑不同集合的合并(因为相同的没必要合并)。
这里合并使 \(fa_{fa_x}\) 指向 \(fa_y\)。
我们需要满足 \(X_{x}-X_{y}=w\),但并查集的操作时在祖先节点上的操作,需要进行一定的转化。
由上式和 \(X_{x}+dis_{y}=X_{fa_{x}}\) 与 \(X_{y}+dis_{y}=X_{fa_{y}}\),移项代入得 \((X_{fa_{x}}-dis_{x})-(X_{fa_{y}}-dis_{y})=d_i\),又因合并后 \(dis_{fa_x}+X_{fa_x}=X_{fa_y}\),则 \(dis_{fa_x}=w+dis_y-dis_x\)。
对其赋值即可。
代码中的 check
是来检测是否可以添加 \(i\) 的,在上面已经讲过。
代码如下。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int MAXN=2e5+10;
int a,b,d,fa[MAXN],N,Q;
ll dis[MAXN];
int read(){
int f=1,x=0;
char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
return f*x;
}
int sget(int x){
if(fa[x]==x)return x;
int root=sget(fa[x]);
dis[x]+=dis[fa[x]];
return fa[x]=root;
}
void merge(int x,int y,int w){
int fx=sget(x),fy=sget(y);
if(fx!=fy){
fa[fx]=fy;
dis[fx]=w+dis[y]-dis[x];
//X_x-X_y=w
//dis[x]+dis[fx]-(dis[y]+dis[fy])=w
}
}
bool check(){
int faa=sget(a),fab=sget(b);
if(faa!=fab||dis[b]+d==dis[a])return true;
else return false;
}
int main(){
N=read();Q=read();
for(int i=1;i<=N;++i){
fa[i]=i;
}
for(int i=1;i<=Q;++i){
a=read();b=read();d=read();
if(check()){
merge(a,b,d);
printf("%d ",i);
}
}
return 0;
}
标签:ch,题解,ABC328F,合并,fa,操作,节点,dis
From: https://www.cnblogs.com/LiJoQiao/p/17827780.html