kruskal 重构树
实现
- 将无向图的边按边权排序。
- 枚举排序后的边 \(e\),若 \(e\) 的两个端点 \(x, y\) 不连通,那么新建节点 \(p\),点 \(p\) 连接 \(x, y\) 所在连通块的根,将 \(p\) 的点权设为 \(e\) 的边权。
- 枚举结束后,得到的一棵树成为 kruskal 重构树。
性质
- 原图中的 \(n\) 个点都是树上的叶子节点。
- 树上共 \(2 \times n - 1\) 个点。
- 除开叶子,具有堆的性质。
- kruskal 重构树任意子树内的节点,在原图中可以不依赖子树外的点连通。
例题
P1967 [NOIP 2013 提高组] 货车运输
建出 kruskal 重构树,跳 LCA 即可。
代码:
#include <bits/stdc++.h>
#define int long long
#define pb emplace_back
using namespace std;
const int N = 1e5 + 5;
const int M = 5e4 + 5;
const int L = 31;
int n, m, Q, u, v, w, tot, lg[N << 1], val[N << 1], dep[N << 1], fat[N][L];
struct Edge {
int u, v, w;
} e[M];
vector<int> g[N << 1];
namespace USF {
int fa[N << 1];
inline void init() {
for(int i = 1 ; i < N << 1 ; ++ i)
fa[i] = i;
return ;
}
inline int find(int x) {
if(x != fa[x]) fa[x] = find(fa[x]);
return fa[x];
}
inline void merge(int x, int y) {
x = find(x), y = find(y);
if(x == y) return ;
fa[y] = x;
return ;
}
}
using namespace USF;
inline void build_kruskal() {
init();
sort(e + 1, e + 1 + m, [&](Edge a, Edge b) {
return a.w > b.w;
});
tot = n;
for(int i = 1 ; i <= m ; ++ i) {
u = e[i].u, v = e[i].v, w = e[i].w;
if(find(u) == find(v)) continue;
u=find(u),v = find(v);
g[u].pb(++ tot), g[tot].pb(u);
g[v].pb(tot), g[tot].pb(v);
//cout<<tot<<" "<<u<<"\n";
//cout<<tot<<" "<<v<<"\n";
merge(tot, u), merge(tot, v);
val[tot] = w;
}
//cout<<tot+1<<" "<<find(6)<<" "<<find(tot+1)<<"&\n";
for(int i = 1 ; i <= tot ; ++ i)
if(find(tot + 1) != find(i)) {
g[tot + 1].pb(find(i)), g[find(i)].pb(tot + 1);
//cout<<tot+1<<" "<<i<<"*\n";
merge(tot + 1, i);
}
}
inline void dfs(int x, int last) {
for(auto u : g[x])
if(u != last) {
fat[u][0] = x;
//cout<<u<<" "<<x<<" "<<fat[u][0]<<"\n";
dep[u] = dep[x] + 1;
dfs(u, x);
}
return ;
}
inline void build_lca() {
for(int i = 1 ; 1 << i <= tot + 1 ; ++ i)
for(int j = 1 ; j <= tot + 1 ; ++ j)
fat[j][i] = fat[fat[j][i - 1]][i - 1];
lg[1] = 0;
for(int i = 2 ; i < N << 1 ; ++ i)
lg[i] = lg[i >> 1] + 1;
return ;
}
inline int LCA(int x, int y) {
if(dep[x] < dep[y]) swap(x, y);
int dis = dep[x] - dep[y];
for(int i = lg[dis] ; ~ i ; -- i)
if(dis - (1 << i) >= 0) dis -= (1 << i), x = fat[x][i];
if(x == y) return x;
for(int i = lg[dep[x]] ; ~ i ; -- i)
if(fat[x][i] != fat[y][i]) x = fat[x][i], y = fat[y][i];
return fat[x][0];
}
signed main() {
ios_base :: sync_with_stdio(NULL);
cin.tie(nullptr);
cout.tie(nullptr);
cin >> n >> m;
for(int i = 1 ; i <= m ; ++ i) {
cin >> u >> v >> w;
e[i] = {u, v, w};
}
build_kruskal();
dfs(tot + 1, -1);
build_lca();
cin >> Q;
while(Q --) {
cin >> u >> v;
w = LCA(u, v);
//cout<<w<<"*\n";
if(w == tot + 1) cout << "-1\n";
else cout << val[w] << '\n';
}
return 0;
}
P4197 Peaks
对于路径权值的限制,用 kruskal 重构树解决;对于可达第 \(k\) 小,按深度建可持久化线段树即可。
代码:
#include <bits/stdc++.h>
#define int long long
#define pb push_back
using namespace std;
const int N = 2e5 + 5;
const int M = 5e5 + 5;
const int L = log2(N) + 5;
int n, m, Q, u, v, w, k, tot, times, a[N], lg[N], to[N], sz[N], val[N], dfn[N], dep[N], leaves[N], fa[N][L];
struct Node {
int u, v, w;
} e[M];
vector<int> g[N];
namespace UFS {
int f[N];
inline void init_set() {
for(int i = 1 ; i < N ; ++ i)
f[i] = i;
return ;
}
inline int find(int x) {
if(x != f[x]) f[x] = find(f[x]);
return f[x];
}
inline void merge(int x, int y) {
x = find(x), y = find(y);
if(x == y) return ;
f[y] = x;
return ;
}
}
using namespace UFS;
namespace SGT {
#define mid ((L + R) >> 1)
#define son p, L, R
#define lson t[p].ls, L, mid
#define rson t[p].rs, mid + 1, R
int cnt, rt[N];
struct sgt {
int ls, rs, sum;
} t[N << 7];
inline void psup(int p) {
t[p].sum = t[t[p].ls].sum + t[t[p].rs].sum;
return ;
}
inline void add(int x, int k, int v, int &p, int L = 0, int R = 1e9) {
p = ++ cnt;
t[p] = t[v];
if(L == R) {
t[p].sum += k;
return ;
}
if(x <= mid) add(x, k, t[v].ls, lson);
else add(x, k, t[v].rs, rson);
psup(p);
// if(L==0&&R==1e9)
// cout<<k<<" "<<v<<" "<<p<<" "<<t[v].sum<<" "<<t[p].sum<<'\n';
return ;
}
inline int query(int lpos, int rpos, int k, int L = 0, int R = 1e9) {
if(L == R) return L;
int res = t[t[rpos].ls].sum - t[t[lpos].ls].sum;
if(k <= res) return query(t[lpos].ls, t[rpos].ls, k, L, mid);
else return query(t[lpos].rs, t[rpos].rs, k - res, mid + 1, R);
}
#undef mid
#undef son
#undef lson
#undef rson
}
using namespace SGT;
inline void build_kruskal() {
init_set();
sort(e + 1, e + 1 + m, [&](Node a, Node b) {
return a.w < b.w;
});
tot = n;
for(int i = 1 ; i <= m ; ++ i) {
u = find(e[i].u), v = find(e[i].v), w = e[i].w;
if(u == v) continue;
val[++ tot] = w;
merge(tot, u), merge(tot, v);
g[tot].pb(u),g[tot].pb(v);
}
return ;
}
inline void dfs(int x, int last) {
// cerr << "vex:" << x << '\n';
sz[x] = 1;
dfn[x] = ++ times;
to[dfn[x]] = x;
leaves[x] = (g[x].size() == 0);
for(auto u : g[x])
if(u != last) {
fa[u][0] = x;
dep[u] = dep[x] + 1;
dfs(u, x);
sz[x] += sz[u];
leaves[x] += leaves[u];
}
return ;
}
inline void init_jump() {
for(int i = 2 ; i < N ; ++ i)
lg[i] = lg[i >> 1] + 1;
for(int i = 1 ; (1 << i) <= tot ; ++ i)
for(int j = 1 ; j <= tot ; ++ j)
fa[j][i] = fa[fa[j][i - 1]][i - 1];
return ;
}
inline int jump(int x, int k) {
for(int i = L-1 ; ~ i ; -- i)
if(fa[x][i]!=0 && val[fa[x][i]] <= k) x = fa[x][i];
return x;
}
signed main() {
ios_base :: sync_with_stdio(NULL);
cin.tie(nullptr);
cout.tie(nullptr);
fill(a + 1, a + N, 1e18);
cin >> n >> m >> Q;
for(int i = 1 ; i <= n ; ++ i)
cin >> a[i];
for(int i = 1 ; i <= m ; ++ i) {
cin >> u >> v >> w;
e[i] = {u, v, w};
}
build_kruskal();
dfs(tot, -1);
init_jump();
for(int i = 1 ; i <= tot ; ++ i)
{
if(to[i]<=n)
add(a[to[i]], 1, rt[i - 1], rt[i]);
else
add(a[to[i]], 0, rt[i-1], rt[i]);
}
// for(int i = 1 ; i <= tot ; ++ i)
// cerr << "leaves:" << leaves[i] << '\n';
while(Q --) {
cin >> u >> k >> w;
// cerr << "vex:" << u << '\n';
u = jump(u, k);
// cerr << "addvex:" << u << '\n';
// cout<<leaves[u]<<"*"<<'\n';
// cerr<<leaves[u]-w+1<<"*"<<'\n';
// cerr<<t[rt[dfn[u] - 1]].sum<<" "<<t[rt[dfn[u] + sz[u] - 1]].sum<<'\n';
if(leaves[u] < w) cout << "-1\n";
else cout << query(rt[dfn[u] - 1], rt[dfn[u] + sz[u] - 1], leaves[u]-w+1) << '\n';
}
return 0;
}
/*
10 11 4
1 1 1 1 1 1 1 1 1 1
1 4 4
2 5 3
9 8 2
7 8 10
7 1 4
6 7 1
6 4 8
2 1 5
10 8 10
3 4 7
3 4 6
1 5 2
1 5 6
1 5 8
8 9 2
*/
P4768 [NOI2018] 归程
同样是用 kruskal 重构树解决边权限制和强制在线。那么接下来只要考虑一件事:怎样在重构树上点权不超过 \(k\) 的点的子树里求到 \(1\) 号点的最短路。
在原图上跑最短路,由于 kruskal 重构树的单调性,我们只需要事先对于重构树做一遍 dp,而后倍增求答案即可。
代码:
#include <bits/stdc++.h>
#define int long long
#define pb push_back
using namespace std;
const int N = 1e6 + 5;
const int M = log2(N) + 5;
int T, n, m, Q, K, s, u, v, w1, w2, lst, lg[N], dis[N], val[N], dep[N], fa[N][M];
bool vis[N];
struct Vex {
int v, w;
};
struct Node {
int u, v, w;
} e[N];
vector<Vex> g[N];
priority_queue<pair<int, int>, vector<pair<int, int> >, greater<pair<int, int> > > q;
namespace kruskal {
int tot, f[N], ls[N], rs[N];
inline void init_set() {
for(int i = 1 ; i <= n << 1 ; ++ i)
f[i] = i;
return ;
}
inline int find(int x) {
if(x != f[x]) f[x] = find(f[x]);
return f[x];
}
inline void merge(int x, int y) {
x = find(x), y = find(y);
if(x == y) return ;
f[y] = x;
return ;
}
inline void build_kruskal() {
tot = n;
sort(e + 1, e + 1 + m, [&](Node a, Node b) {
return a.w > b.w;
});
for(int i = 1 ; i <= m ; ++ i) {
u = find(e[i].u), v = find(e[i].v);
if(u == v) continue;
val[++ tot] = e[i].w;
ls[tot] = u, rs[tot] = v;
merge(tot, u), merge(tot, v);
dis[tot] = min(dis[u], dis[v]);
// cerr << "root:" << u << ' ' << tot << ' ' << find(u) << '\n';
}
return ;
}
}
using namespace kruskal;
inline pair<int, int> get(int x, int y) {
return {(x + K * lst - 1) % n + 1, (y + K * lst) % (s + 1)};
}
inline void clear() {
lst = 0;
for(int i = 1 ; i <= n ; ++ i)
g[i].clear();
for(int i = 1 ; i <= tot ; ++ i) {
for(int j = 0 ; j <= lg[dep[i]] ; ++ j)
fa[i][j] = 0;
ls[i] = rs[i] = val[i] = dep[i] = 0;
}
return ;
}
inline void dijkstra() {
fill(dis + 1, dis + 1 + n * 2, 1e18);
fill(vis + 1, vis + 1 + n * 2, false);
dis[1] = 0;
q.push({0ll, 1ll});
while(! q.empty()) {
auto [_, u] = q.top();
q.pop();
if(vis[u]) continue;
vis[u] = true;
for(auto [x, w] : g[u])
if(dis[u] + w < dis[x]) {
dis[x] = dis[u] + w;
q.push({dis[x], x});
}
}
return ;
}
inline void dfs(int x) {
// cerr << "vex:" << x << '\n';
if(! x) return ;
fa[ls[x]][0] = fa[rs[x]][0] = x;
dep[ls[x]] = dep[rs[x]] = dep[x] + 1;
dfs(ls[x]), dfs(rs[x]);
return ;
}
inline void init_jump() {
for(int i = 2 ; i <= tot ; ++ i)
lg[i] = lg[i >> 1] + 1;
for(int i = 1 ; (1 << i) <= tot ; ++ i)
for(int j = 1 ; j <= tot ; ++ j)
fa[j][i] = fa[fa[j][i - 1]][i - 1];
return ;
}
inline int jump(int x, int k) {
if(val[fa[x][0]] < k) return x;
for(int i = lg[dep[x]] ; ~ i ; -- i)
if(dep[x] >= (1 << i) && val[fa[x][i]] > k) x = fa[x][i];
return x;
}
signed main() {
// freopen("xcy.in", "r", stdin);
// freopen("xcy.out", "w", stdout);
ios_base :: sync_with_stdio(NULL);
cin.tie(nullptr);
cout.tie(nullptr);
cin >> T;
while(T --) {
clear();
cin >> n >> m;
for(int i = 1 ; i <= m ; ++ i) {
cin >> u >> v >> w1 >> w2;
e[i] = {u, v, w2};
g[u].pb({v, w1}), g[v].pb({u, w1});
}
cin >> Q >> K >> s;
dijkstra();
init_set();
build_kruskal();
dfs(tot);
init_jump();
// for(int i = 1 ; i <= tot ; ++ i)
// cerr << "sons:" << ls[i] << ' ' << rs[i] << '\n';
// for(int i = 1 ; i <= tot ; ++ i)
// cout << "dis:" << dis[i] << '\n';
while(Q --) {
cin >> u >> w1;
auto [U, W] = get(u, w1);
U = jump(U, W);
cout << (lst = dis[U]) << '\n', lst = dis[U];
}
// cerr << val[tot] << '\n';
}
return 0;
}
/*
1
4 3
1 2 50 1
2 3 100 2
3 4 50 1
5 0 2
3 0
2 1
4 1
3 1
3 2
*/