Kruskal 重构树学习笔记

做了 NOI2018 归程 学到的东西。

前置知识

  • Kruskal 算法
  • 一些数据结构基础

概念

Kruskal 重构树是一个基于 Kruskal 算法演变而来的图论算法,其用于解决一般图上诸如“只能经过边权小于等于某个值”相关问题。

Kruskal 算法的过程是将所有边进行排序,然后从小到大选择边,构建生成树。

Kruskal 生成树则是对于一条边 $(x,y,w)$ 来说,新建一个点 $p$ ,在新图上将 $p$ 作为 $x,y$ 的父亲,并且将 $p$ 的点权设为 $w$,在这个过程中使用并查集维护连通性。

image-20220313174004363

新的树有如下几个性质:

  1. 新的树满足二叉堆的性质
  2. 如果从大到小加边,整棵树是一个大根堆
  3. 任意两点之间的边权最大值是树上两点的 LCA
  4. 一个节点能走到的节点一定在它的子树中(会结合题目讲解)

给出建树的参考 Code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int find(int x) {
return x == f[x] ? x : f[x] = find(f[x]);
}

void Kruskal() {
tot = n;
for (int i = 1;i <= m;++i) {
int fx = find(e[i].u),fy = find(e[i].v);
if (fx != fy) {
p[++tot] = e[i].w;
f[fx] = f[fy] = f[tot] = tot;
G[fx].push_back(tot);
G[tot].push_back(fx);
G[fy].push_back(tot);
G[tot].push_back(fy);
}
}
}

例题

  1. CFgym103446H

给一张无向图,最初你在点 $x$ 并且有 $k$ 点社交牛逼值,你能从点 $i$ 获取 $a_i$ 点社交牛逼值,但是每个点只能获取一次。对于第 $i$ 条边有个限制 $w_i$,你至少要有 $w_i$ 点社交牛逼值才能牛逼到通过这条道路。

有 $q$ 组询问,每次询问给出 $x,k$,请求出你能获取的最大社交牛逼值。

$n,m,q \leq 10^5$

建出 Kruskal 重构树,那么这个题就等价于从点 $x$ 开始跳,然后每次向上跳时,检查从当前点的子树和是否大于等于要跳到的点的最大值,如果是那就向上跳。

Code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cctype>
#include <vector>
#include <cmath>
using namespace std;
#define ll long long
#define FO(x) {freopen(#x".in","r",stdin);freopen(#x".out","w",stdout);}
#define pii pair<int,int>
#define mp make_pair

char buf[1 << 20], *p1, *p2;
#define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 20, stdin), p1 == p2)?EOF: *p1++)
template <typename T> inline void read(T &t) {
int v = getchar();T f = 1;t = 0;
while (!isdigit(v)) {if (v == '-')f = -1;v = getchar();}
while (isdigit(v)) {t = t * 10 + v - 48;v = getchar();}
t *= f;
}
template <typename T,typename... Args> inline void read(T &t,Args&... args) {
read(t);read(args...);
}

const int N = 4e5 + 10;

vector <int> G[N];

int n,m,q,a[N],p[N],tot;

struct edge {
int u,v,w;
friend inline bool operator < (const edge &a,const edge &b) {
return a.w < b.w;
}
}e[N];

int f[N];

int find(int x) {
return x == f[x] ? x : f[x] = find(f[x]);
}

void Kruskal() {
tot = n;
for (int i = 1;i <= m;++i) {
int fx = find(e[i].u),fy = find(e[i].v);
if (fx != fy) {
p[++tot] = e[i].w;
f[fx] = f[fy] = f[tot] = tot;
G[fx].push_back(tot);
G[tot].push_back(fx);
G[fy].push_back(tot);
G[tot].push_back(fy);
}
}
p[0] = 0x3f3f3f3f;
}

int dep[N],siz[N],fa[30][N],val[30][N];

void dfs(int x,int fat) {
dep[x] = dep[fat] + 1;
for (auto y : G[x]) {
if (y != fat) {
dfs(y,x);
siz[x] += siz[y];
}
}
fa[0][x] = fat;
val[0][x] = p[fat] - siz[x];
}

void solve(int x,int k) {
for (int i = 20;i >= 0;--i) {
if (val[i][x] <= k && fa[i][x]) {
x = fa[i][x];
}
}
printf("%d\n",k + siz[x]);
}

signed main() {
read(n,m,q);
for (int i = 1;i <= n;++i) read(siz[i]),f[i] = i;
for (int i = 1;i <= m;++i) {
read(e[i].u,e[i].v,e[i].w);
}
sort(e+1,e+1+m);
Kruskal();
dfs(tot,0);
for (int i = 1;i <= 20;++i) {
for (int x = 1;x <= tot;++x) {
fa[i][x] = fa[i-1][fa[i-1][x]];
val[i][x] = max(val[i-1][x],val[i-1][fa[i-1][x]]);
}
}
//for (int i = 1;i <= tot;++i) printf("%d %d\n",siz[i],p[i]);
for (int i = 1;i <= q;++i) {
int x,k;read(x,k);
solve(x,k);
}
return 0;
}
  1. NOI2018 归程

建立以海拔为关键字的 Kruskal 重构树(海拔为降序),那么能到达的点在 Kruskal 重构树的一个子树中。

那么就很清晰了,先用 Dijkstra 预处理单源最短路,再构建 Kruskal 重构树,然后 dfs 维护每个点为根的子树中距1号点的最小距离。处理询问时树上倍增即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cctype>
#include <vector>
#include <cmath>
#include <queue>
using namespace std;
#define int long long
#define FO(x) {freopen(#x".in","r",stdin);freopen(#x".out","w",stdout);}
#define pii pair<int,int>
#define mp make_pair

char buf[1 << 20], *p1, *p2;
#define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 20, stdin), p1 == p2)?EOF: *p1++)
template <typename T> inline void read(T &t) {
int v = getchar();T f = 1;t = 0;
while (!isdigit(v)) {if (v == '-')f = -1;v = getchar();}
while (isdigit(v)) {t = t * 10 + v - 48;v = getchar();}
t *= f;
}
template <typename T,typename... Args> inline void read(T &t,Args&... args) {
read(t);read(args...);
}

const int N = 1e6 + 10;
const int K = 26;

int n,m,q,k,s,dis[N],F[N],poi[N],val[N],tot,fat[N][K],f[N][K],dep[N];
bool vis[N];
vector <int> G[N];
vector <pii> G2[N];

struct edg {
int u,v,l,a;
friend inline bool operator < (const edg &A,const edg &B) {
return A.a > B.a;
}
}edge[N];

struct node {
int pos,d;
friend inline bool operator < (const node &a,const node &b) {
return a.d > b.d;
}
};

int find(int x) {return x == F[x] ? x : F[x] = find(F[x]);}

void clear() {
tot = n;
for (int i = 1;i <= (n << 1);++i) G[i].clear(),G2[i].clear(),F[i] = i;
}

void Dijkstra(int s) {
priority_queue <node> q;
memset(vis,0,sizeof vis);
memset(dis,0x3f,sizeof dis);
memset(val,0x3f,sizeof val);
q.push((node){s,dis[s] = 0});

while (!q.empty()) {
node p = q.top();q.pop();
int x = p.pos;
if (vis[x]) continue;
vis[x] = 1;
for (auto e : G2[x]) {
int y = e.first,w = e.second;
if (dis[y] > dis[x] + w) {
dis[y] = dis[x] + w;
q.push((node){y,dis[y]});
}
}
}
for (int i = 1;i <= n;++i) val[i] = dis[i];
}

void Kruskal() {
int cnt = 0;
for (int i = 1;i <= m;++i) {
int u = edge[i].u,v = edge[i].v,w = edge[i].a;
//printf("%d %d %d %d\n",u,v,find(u),find(v));
u = find(u),v = find(v);
if (u != v) {
++tot;poi[tot] = w;
//poi[u] = poi[v] = w;
F[u] = F[v] = tot;
val[tot] = 0x3f3f3f3f;
G[tot].push_back(u);
G[u].push_back(tot);
G[tot].push_back(v);
G[v].push_back(tot);
if (++cnt == (n - 1)) break;
}
}
}

void dfs(int x,int fa) {
fat[x][0] = fa;
dep[x] = dep[fa] + 1;
//printf("%d %d\n",fa,x);
for (int j = 1;j <= 20;++j) {
fat[x][j] = fat[fat[x][j-1]][j-1];
}
for (auto y : G[x]) {
if (y != fa) {
dfs(y,x);
val[x] = min(val[x],val[y]);
}
}
}

int query(int x,int y) {
for (int i = 20;i >= 0;--i) {
if (fat[x][i]) {
//printf("%d %d %d %d %d %d %d\n",x,dep[x],y,i,fat[x][i],poi[fat[x][i]],val[fat[x][i]]);
if (poi[fat[x][i]] > y) {
x = fat[x][i];
}
}
}
//printf("%d\n",x);
return val[x];
}

void solve() {
read(n,m);
clear();
for (int i = 1;i <= m;++i) {
read(edge[i].u,edge[i].v,edge[i].l,edge[i].a);
G2[edge[i].u].push_back(mp(edge[i].v,edge[i].l));
G2[edge[i].v].push_back(mp(edge[i].u,edge[i].l));
}
sort(edge + 1,edge + 1 + m);
Dijkstra(1);
//puts("here");
Kruskal();
//puts("here");
dfs(tot,0);
//puts("here");
read(q,k,s);
int lastans = 0;
for (int i = 1;i <= q;++i) {
int x,p;read(x,p);
x = (x + k * lastans - 1) % n + 1;
p = (p + k * lastans) % (s + 1);
printf("%lld\n",lastans = query(x,p));
}

}

signed main() {
int T;read(T);
while (T--) solve();
return 0;
}
作者

Doubeecat

发布于

2022-03-13

更新于

2025-09-18

许可协议