kruskal 重构树
\(kruskal\) 重构树
算法
其实这个算法就是把并查集之间的合并放到树上体现出来了,并没有什么难的地方。
我们对于正常的 \(kruskal\) 的并查集的过程,只是在需要合并的时候,建一个新点,新点的权值是原来边的权值,然后对于原来的两个联通块所代表的点连边。
下面大概说一下重构树的性质:
- 这棵树是一棵二叉树,且具有堆的性质。
- 树上除了叶子节点是原来的点,其余的点都是新建的点,且都有权值。
- 两个点 \(u\) 和 \(v\) 的 \(lca\) 的点权就对应着它们最小生成树上的路径上的最大值(瓶颈)。
然后好像就没什么了,一般这种题可能都比较套路。
例题
这里以NOI2018 归程为例题。
这个应该算是比较套路的题目吧。
水位线的限制让我很显然的就想到了重构树,所以直接建一棵出来就可以了,本题的树是一棵小根堆。
然后假设我们找到了满足题目水位线限制的最小节点,那么这个节点的子树都是可以坐车互相到达的,那么我们只要找到这里面的一个点,使其到达一号点的距离最短就行了。
所以大体流程就已经出来了,我们需要跑一个最短路,还需要建出重构树,还需要倍增找到满足条件的深度最小的点,然后这道题就做完了。
作为正规比赛里第一道卡 \(spfa\) 的题,我们还是老老实实的写 Dij 吧。(可能优化过的 \(spfa\) 也可以过)
然后放个本人的代码:
// by longdie
#include <bits/stdc++.h>
#define cl(x) memset(x, 0, sizeof(x))
using namespace std; // 最短路 + 并查集 + 倍增
const int N = 2e5 + 5, inf = 0x3f3f3f3f;
inline int read(int s = 0, int f = 1, char ch = getchar()) {
while(!isdigit(ch)) { if(ch == '-') f = -1; ch = getchar(); }
while(isdigit(ch)) { s = s*10 + ch - '0', ch = getchar(); }
return s * f;
}
vector<int> g[N<<2];
int n, m, head[N], cnt, dis[N], vis[N], fa[N<<2], tot, val[N<<2], f[N<<2][21], Min[N<<2], dep[N<<2], mx[N<<2][21];
struct edge1 { int to, next, w; } e[N << 2];
inline void add1(int x, int y, int z) {
e[++cnt] = (edge1){y, head[x], z}, head[x] = cnt;
}
struct edge2 { int x, y, z; } a[N << 2];
struct node {
int id, val;
node() {}
node(int a, int b) { id = a, val = b; }
friend bool operator < (const node &a, const node &b) { return a.val > b.val; }
}; priority_queue<node> q;
void Dij() {
memset(dis, 0x3f, sizeof(dis)), memset(vis, 0, sizeof(vis));
dis[1] = 0, q.push(node(1, 0));
while(q.size()) {
node t = q.top(); q.pop(); int u = t.id;
if(vis[u]) continue;
vis[u] = 1;
for(register int i = head[u], v; i; i = e[i].next) {
v = e[i].to;
if(dis[v] > dis[u] + e[i].w) {
dis[v] = dis[u] + e[i].w;
q.push(node(v, dis[v]));
}
}
}
}
inline bool cmp(edge2 a, edge2 b) { return a.z > b.z; }
inline int get_fa(int x) {
return x == fa[x] ? x : fa[x] = get_fa(fa[x]);
}
inline void merge(int x, int y, int z) {
int fx = get_fa(x), fy = get_fa(y);
++tot, val[tot] = z;
g[tot].push_back(fx), g[tot].push_back(fy);
fa[fx] = tot, fa[fy] = tot;
}
void dfs0(int u) {
if(u <= n) Min[u] = dis[u];
else Min[u] = inf;
for(register int i = 0; i < g[u].size(); ++i) {
int v = g[u][i];
dep[v] = dep[u] + 1, f[v][0] = u, mx[v][0] = val[u];
for(register int j = 1; j <= 19; ++j) {
f[v][j] = f[f[v][j-1]][j-1];
mx[v][j] = min(mx[v][j-1], mx[f[v][j-1]][j-1]);
}
dfs0(v);
Min[u] = min(Min[u], Min[v]);
}
}
int query(int u, int v) {
for(register int i = 19; i >= 0; --i) {
if(mx[u][i] > v) u = f[u][i];
}
return Min[u];
}
void init() {
cl(head), cl(val), tot = cnt = 0, cl(f), cl(Min), cl(mx);
for(register int i = 1; i <= n * 4; ++i) g[i].clear();
}
signed main() {
int T = read();
while(T--) {
n = read(), m = read();
init(), tot = n;
for(register int i = 1, x, y, z, w; i <= m; ++i) {
x = read(), y = read(), z = read(), w = read();
add1(x, y, z), add1(y, x, z);
a[i] = (edge2){x, y, w};
}
Dij();
sort(a + 1, a + m + 1, cmp);
for(register int i = 1; i <= n << 2; ++i) fa[i] = i;
for(register int i = 1; i <= m; ++i) {
int x = a[i].x, y = a[i].y;
if(get_fa(x) == get_fa(y)) continue;
merge(x, y, a[i].z);
}
dep[tot] = 1, dfs0(tot);
int Q = read(), K = read(), S = read(), ans = 0;
while(Q--) {
int v = read(), p = read();
v = (v + K * ans - 1) % n + 1;
p = (p + K * ans) % (S + 1);
ans = query(v, p);
printf("%d\n", ans);
}
}
return 0;
}