P8710
提供一种拓扑排序+并查集的解法。
简化题意:
给定两种操作:
-
将两个点纳入一个连通块。
-
将这个点所在的连通块的值
+t 。
简述思路:
第一种操作自然让人想到并查集,重点看第二种操作。
我们想要对一整块序列都进行加和操作,回想我们之前学到过的数据结构,对于区间的操作下,可以通过维护懒标记来简化操作。
同理,我们大概也能把所需要加的值放在一个点上,等到必要的时候下放。
我们知道,并查集是一种树形结构,对于每一个并查集的合并,实际上是对根节点的嫁接,所以我们可以把懒标记先放在根节点上。
什么时候下放懒标记呢?最后结算答案的时候当然要下放。
那么中间操作的时候什么时候下放呢?我们可以思考合并的过程。前文已经提到,并查集的合并,是一个根节点接到另一个根节点上,那么新成为的那个根节点,下放标记的时候,显然不能传递到新增的那些节点上。所以我们要在合并之前,将即将成为新根节点的节点下放。
为了便于操作,我们直接把并查集的结构直接存成一个自根节点向下的有向图,然后,我们借助并查集将环结构略去,这样就成了一个 DAG。
发现这个性质之后,我们在最后直接拓扑排序下放懒标记即可。
//2023/9/23
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e6+10;
int num,ans;
int fa[MAXN];
int find(int x)
{
if(fa[x]==x) return x;
else return fa[x]=find(fa[x]);
}
struct linkstar{
int from,to,next;
}edge[2*MAXN];
struct node{
int mark,val;
node(){mark=val=0;}
}point[MAXN];
int escnt,n,m;
int head[MAXN],du[MAXN];
void add(int from,int to)
{
edge[++escnt].from=from;
edge[escnt].to=to;
edge[escnt].next=head[from];
head[from]=escnt;
}
void pushdown(int x)//下放懒标记
{
for (int i=head[x];i!=-1;i=edge[i].next){
int y=edge[i].to;
point[y].mark+=point[x].mark;
point[y].val+=point[x].mark;
}
point[x].mark=0;
}
void toposort()//拓扑排序下放
{
queue<int> que;
for (int i=1;i<=n;i++){
if(du[i]==0) que.push(i);
}
while(!que.empty()){
int x=que.front();
que.pop();
for (int i=head[x];i!=-1;i=edge[i].next){
int y=edge[i].to;
du[y]--;
point[y].mark+=point[x].mark;
point[y].val+=point[x].mark;
if(du[y]==0) que.push(y);
}
point[x].mark=0;
}
}
int main()
{
std::ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
memset(head,-1,sizeof(head));
cin>>n>>m;
int opt,p,t;
for (int i=1;i<=n;i++) fa[i]=i;
for (int i=1;i<=m;i++){
cin>>opt>>p>>t;
if(opt==1){
t=find(t),p=find(p);
if(t==p) continue;//取消环的影响
if(t<p) swap(t,p);
fa[t]=p;
du[t]++;
pushdown(p);//下放新根节点的懒标记
add(p,t);//直接建立树形结构,便于拓扑排序
}
if(opt==2){
p=find(p);//维护当前块的懒标记
point[p].mark+=t;
point[p].val+=t;
}
}
toposort();
for (int i=1;i<=n;i++) cout<<point[i].val<<" ";
return 0;
}