Kruskal 算法
将所有边按照权值排序,按照权值从低到高遍历每条边,维护一个并查集,两个节点属于一个集合当且仅当当前这两个集合在同一个联通块当中。
对于每条边 ((u,v))
- 如果 (u,v) 属于同一个连通块,那么跳过;
- 否则加入这条边,将 (u,v) 所对应的两个连通块连接到一起。
复杂度:整体复杂度 (O(malpha+m log m))。
练习
Kruskal 重构树
首先,跑一遍 Dijkstra
,得到 (1) 号节点到所有节点的最短路。
找到节点 (m) ,取其最短路的最小值。
考虑如何计算从一个节点 (v) 经过权值不超过 (a) 的节点集合。
重新考虑
Kruskal
的过程,在合并并查集过程中,对于每个合并的过程新建一个节点,将两个子树的根节点的父亲同时设置为这个新节点,此时这个节点的子树就可以代表这个所有经过不超过某个权值的边的连通块。考虑对于一个节点 (u) 和一个权值 (a),当沿着其父亲节点不断向上走是,对应边权值逐渐变大,即恰好会有一个节点 (m) 其对应的子树就是从这个节点出发经过不大于 (a) 的边能到达的所有点,只需取里面最短路的最小值。
练习
CODEFORCES 722C Destroying Array
倒序操作。
代码
#include<bits/stdc++.h>
#define N 100100
#define ll long long int
using namespace std;
bool sign[N];
int n;
ll f[N],del[N],num[N],c[N],ans[N];
inline int FIND(int);
int main()
{
memset(c,0,sizeof(c));
memset(num,0,sizeof(num));
memset(f,0,sizeof(f));
memset(del,0,sizeof(del));
memset(ans,0,sizeof(ans));
memset(sign,false,sizeof(sign));
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%lld",&num[i]);
for(int i=1;i<=n;i++)
scanf("%lld",&del[i]);
for(int i=1;i<=n;i++)
f[i]=i;
for(int i=n,x;i>1;i--)
{
x=del[i];
sign[x]=true;
ll res=0;
if(sign[x-1])
res+=c[FIND(x-1)],f[FIND(x-1)]=x;
if(sign[x+1])
res+=c[FIND(x+1)],f[FIND(x+1)]=x;
res+=num[x];
c[x]=res;
ans[i]=max(ans[i+1],res);
}
for(int i=2;i<=n;i++)
printf("%lld
",ans[i]);
puts("0");
return 0;
}
inline int FIND(int x)
{
return f[x]==x?x:f[x]=FIND(f[x]);
}
写于 2021年7月6日 在 焦作一中 集训中