kruskal 重构树(讲解 + 例题)

kruskal重构树

如何建树

模仿 k r u s k a l kruskal kruskal,先将所有边排序。

依次遍历每一条边,如果这条边的两个节点( u , v u, v u,v)不在同一个连通块里面,

则新建一个 n o d e node node节点,更新 f a [ u ] = f a [ v ] = n o d e fa[u] = fa[v] = node fa[u]=fa[v]=node,同时有 f a [ n o d e ] = n o d e fa[node] = node fa[node]=node

建立 n o d e − > u , n o d e − > v node-> u, node-> v node>u,node>v的边,同时给 n o d e node node节点赋值上这条边的边权,

一些性质

① 是一个小/大根堆(由建树时边权的排序方式决定)。

L C A ( u , v ) LCA(u, v) LCA(u,v)的权值是从 u u u v v v的路径上的最大/最小边权的最小/最大值(同样由建树是边权的排序所决定)。

显然,对于每个新建的 n o d e node node节点,一定有权值是由深度更小到深度更大递减的(如果排序时按照升序排列),反之亦然,所以易证得 ① ② 成立。

#3732. Network

#include <bits/stdc++.h>

using namespace std;

const int N = 1e5 + 10;

int value[N], ff[N], nn, n, m, q;

int head[N], to[N], nex[N], cnt = 1;

int fa[N], son[N], sz[N], dep[N], top[N], tot;

struct Res {
  int u, v, w;

  void read() {
    scanf("%d %d %d", &u, &v, &w);
  }

  bool operator < (const Res &t) const {
    return w < t.w;
  }
}a[N];

void add(int x, int y) {
  to[cnt] = y;
  nex[cnt] = head[x];
  head[x] = cnt++;
}

int find(int rt) {
  return rt == ff[rt] ? rt : ff[rt] = find(ff[rt]);
}

void dfs1(int rt, int f) {
  fa[rt] = f, dep[rt] = dep[f] + 1, sz[rt] = 1;
  for (int i = head[rt]; i; i = nex[i]) {
    if (to[i] == f) {
      continue;
    }
    dfs1(to[i], rt);
    sz[rt] += sz[to[i]];
    if (sz[son[rt]] < sz[to[i]]) {
      son[rt] = to[i];
    }
  }
}

void dfs2(int rt, int tp) {
  top[rt] = tp;
  if (!son[rt]) {
    return ;
  }
  dfs2(son[rt], tp);
  for (int i = head[rt]; i; i = nex[i]) {
    if (to[i] == fa[rt] || to[i] == son[rt]) {
      continue;
    }
    dfs2(to[i], to[i]);
  }
}

int lca(int x, int y) {
  while (top[x] != top[y]) {
    if (dep[top[x]] < dep[top[y]]) {
      swap(x, y);
    }
    x = fa[top[x]];
  }
  return dep[x] < dep[y] ? x : y;
}

void kruskal() {
  for (int i = 1; i < N; i++) {
    ff[i] = i;
  }
  for (int i = 1; i <= m; i++) {
    int u = a[i].u, v = a[i].v;
    int fu = find(u), fv = find(v);
    if (fu != fv) {
      value[++n] = a[i].w;
      ff[n] = ff[fu] = ff[fv] = n;
      add(n, fv), add(n, fu);
    }
  }
  dfs1(n, 0), dfs2(n, n);
}

int main() {
  // freopen("in.txt", "r", stdin);
  // freopen("out.txt", "w", stdout);
  // ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
  scanf("%d %d %d", &nn, &m, &q), n = nn;
  for (int i = 1; i <= m; i++) {
    a[i].read();
  }
  sort(a + 1, a + 1 + m);
  kruskal();
  while (q--) {
    int u, v;
    scanf("%d %d", &u, &v);
    printf("%d\n", value[lca(u, v)]);
  }
  return 0;
}

#3551. [ONTAK2010]Peaks加强版

我们要求从一个点出发经过困难值小于等于 x x x的路径所能到达的山峰中第 k k k高的是什么。

考虑按照边权升序,建议 k r u s k a l kruskal kruskal重构树,然后倍增向上跳,找到困难值小于等于 x x x的深度最小的节点 u u u

那么我们只要在 u u u的子树中询问第 k k k大即可,所以可以用主席树来写,依照 d f s dfs dfs序,对每个节点建立一颗主席树,然后在主席树上查找第 k k k大即可。

#include <bits/stdc++.h>

using namespace std;

const int N = 5e5 + 10;

int head[N], to[N], nex[N], cnt = 1;

int n, m, q, nn, a[N], ff[N], value[N], h[N];

int fa[N][21], l[N], r[N], rk[N], tot;

int root[N], ls[N << 7], rs[N << 7], sum[N << 7], num;

struct Res {
  int u, v, w;

  bool operator < (const Res &t) const {
    return w < t.w;
  }
}edge[N];

void add(int x, int y) {
  to[cnt] = y;
  nex[cnt] = head[x];
  head[x] = cnt++;
}

void update(int &rt, int pre, int l, int r, int x, int v) {
  rt = ++num;
  ls[rt] = ls[pre], rs[rt] = rs[pre], sum[rt] = sum[pre] + v;
  if (l == r) {
    return ;
  }
  int mid = l + r >> 1;
  if (x <= mid) {
    update(ls[rt], ls[pre], l, mid, x, v);
  }
  else {
    update(rs[rt], rs[pre], mid + 1, r, x, v);
  }
}

int query(int L, int R, int l, int r, int k) {
  if (l == r) {
    return l;
  }
  int res = sum[ls[R]] - sum[ls[L]], mid = l + r >> 1;
  if (res >= k) {
    return query(ls[L], ls[R], l, mid, k);
  }
  else {
    return query(rs[L], rs[R], mid + 1, r, k - res);
  }
}

int find(int rt) {
  return rt == ff[rt] ? rt : ff[rt] = find(ff[rt]);
}

void dfs(int rt, int f) {
  fa[rt][0] = f, l[rt] = ++tot, rk[tot] = rt;
  for (int i = 1; i <= 20; i++) {
    fa[rt][i] = fa[fa[rt][i - 1]][i - 1];
  }
  for (int i = head[rt]; i; i = nex[i]) {
    if (to[i] == f) {
      continue;
    }
    dfs(to[i], rt);
  }
  r[rt] = tot;
}

void kruskal() {
  for (int i = 1; i < N; i++) {
    ff[i] = i;
  }
  sort(edge + 1, edge + 1 + m);
  for (int i = 1, cur = 1; i <= m && cur < n; i++) {
    int u = find(edge[i].u), v = find(edge[i].v);
    if (u != v) {
      cur++, nn++;
      ff[u] = nn, ff[v] = nn;
      value[nn] = edge[i].w;
      add(nn, u), add(nn, v);
      if (u <= n) {
        value[u] = edge[i].w;
      }
      if (v <= n) {
        value[v] = edge[i].w;
      }
    }
  }
  dfs(nn, 0);
}

int main() {
  // freopen("in.txt", "r", stdin);
  // freopen("out.txt", "w", stdout);
  // ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
  scanf("%d %d %d", &n, &m, &q);
  for (int i = 1; i <= n; i++) {
    scanf("%d", &h[i]);
    a[i] = h[i];
  }
  nn = n;
  for (int i = 1; i <= m; i++) {
    scanf("%d %d %d", &edge[i].u, &edge[i].v, &edge[i].w);
  }
  kruskal();
  int maxn = n;
  sort(a + 1, a + 1 + maxn);
  maxn = unique(a + 1, a + 1 + maxn) - (a + 1);
  for (int i = 1; i <= n; i++) {
    h[i] = lower_bound(a + 1, a + 1 + maxn, h[i]) - a; 
  }
  for (int i = 1; i <= nn; i++) {
    root[i] = root[i - 1];
    if (rk[i] <= n) {
      update(root[i], root[i], 1, maxn, h[rk[i]], 1);
    }
  }
  for (int i = 1, u, x, k, last_ans = 0, res; i <= q; i++) {
    scanf("%d %d %d", &u, &x, &k);
    if (last_ans != -1) {
      u ^= last_ans, x ^= last_ans, k ^= last_ans;
    }
    for (int j = 20; j >= 0; j--) {
      if (fa[u][j] && value[fa[u][j]] <= x) {
        u = fa[u][j];
      }
    }
    res = sum[root[r[u]]] - sum[root[l[u] - 1]];
    last_ans = res < k ? -1 : a[query(root[l[u] - 1], root[r[u]], 1, maxn, res - k + 1)];
    printf("%d\n", last_ans);
  }
  return 0;
}

P4768 [NOI2018] 归程

给定一个 n n n个点, m m m条边的无向联通图,边的描述为 [ u , v , l , a ] [u, v, l, a] [u,v,l,a],表示 u u u v v v连有一条长度为 l l l,海拔为 a a a的边,

Q Q Q个询问,每次给出一个出发点 u u u和一个海拔限制高度 p p p,并且在出发点有一辆车,这辆车可以通过海拔大于 p p p的边,

问,从 u − > 1 u->1 u>1的最短步行长度是什么多少。

设从 u u u坐车出发可到的点集为 S S S,我们的任务就是找到一个点 v , v ∈ S v, v \in S v,vS d i s ( v , 1 ) dis(v, 1) dis(v,1) d i s ( x , 1 ) , x ∈ S dis(x, 1),x \in S dis(x,1),xS中最的小。

① 预处理出每个点到点 1 1 1的最短路径出来,

② 我们按照海拔高度降序建立一颗 k r u s k a l kruskal kruskal重构树,

③ 从 u u u号点往上跳,找到可坐车到达的深度最小的节点 r t rt rt,显然从 u u u可坐车到达的点集就是 r t rt rt所在的这颗子树,

④ 由于我们查找的是最小值,所以只需在 d f s dfs dfs的过程中,不断向上更新整颗子树的最小值即可。

⑤ 直接输出我们找到的 r t rt rt所代表的答案。

#include <bits/stdc++.h>

using namespace std;

const int N = 1e6 + 10;

int head[N], to[N], nex[N], cnt = 1;

int head1[N], to1[N], nex1[N], value1[N], cnt1 = 1;

int vis[N], dis[N], ff[N], value[N], fa[N][21], ans[N], nn, n, m, Q, K, S;

struct Edge {
  int u, v, w;
  
  bool operator < (const Edge &t) const {
    return w > t.w;
  }
}edge[N];

struct Node {
  int u, w;
  
  bool operator < (const Node &t) const {
    return w > t.w;
  }
};

void add1(int x, int y, int w) {
  to1[cnt1] = y;
  nex1[cnt1] = head1[x];
  value1[cnt1] = w;
  head1[x] = cnt1++;
}

void add(int x, int y) {
  to[cnt] = y;
  nex[cnt] = head[x];
  head[x] = cnt++;
}

priority_queue<Node> q;

void Dijkstra() {
  while (q.size()) {
    q.pop();
  }
  q.push({1, 0});
  memset(vis, 0, sizeof vis), memset(dis, 0x3f, sizeof dis);
  dis[1] = 0;
  while (q.size()) {
    int rt = q.top().u;
    q.pop();
    if (vis[rt]) {
      continue;
    }
    vis[rt] = 1;
    for (int i = head1[rt]; i; i = nex1[i]) {
      if (dis[to1[i]] > dis[rt] + value1[i]) {
        dis[to1[i]] = dis[rt] + value1[i];
        q.push({to1[i], dis[to1[i]]});
      }
    }
  }
}

int find(int rt) {
  return ff[rt] == rt ? rt : ff[rt] = find(ff[rt]);
}

void dfs(int rt, int f) {
  fa[rt][0] = f, ans[rt] = rt <= n ? dis[rt] : 0x3f3f3f3f;
  for (int i = 1; i <= 20; i++) {
    fa[rt][i] = fa[fa[rt][i - 1]][i - 1];
  }
  for (int i = head[rt]; i; i = nex[i]) {
    if (to[i] == f) {
      continue;
    }
    dfs(to[i], rt);
    ans[rt] = min(ans[rt], ans[to[i]]);
  }
}

void kruskal() {
  for (int i = 1; i < N; i++) {
    ff[i] = i, head[i] = 0;
  }
  cnt = 1;
  sort(edge + 1, edge + 1 + m);
  for (int i = 1, cur = 1; i <= m && cur < n; i++) {
    int u = find(edge[i].u), v = find(edge[i].v);
    if (u != v) {
      cur++, nn++;
      ff[u] = nn, ff[v] = nn;
      value[nn] = edge[i].w;
      add(nn, u), add(nn, v);
      if (u <= n) {
        value[u] = edge[i].w;
      }
      if (v <= n) {
        value[v] = edge[i].w;
      }
    }
  }
  dfs(nn, 0);
}

int main() {
  // freopen("in.txt", "r", stdin);
  // freopen("out.txt", "w", stdout);
  // ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
  int T;
  scanf("%d", &T);
  while (T--) {
    scanf("%d %d", &n, &m);
    nn = n;
    memset(head1, 0, sizeof head1), cnt1 = 1;
    for (int i = 1, u, v, l, a; i <= m; i++) {
      scanf("%d %d %d %d", &u, &v, &l, &a);
      add1(u, v, l), add1(v, u, l);
      edge[i] = {u, v, a};
    }
    Dijkstra();
    kruskal();
    scanf("%d %d %d", &Q, &K, &S);
    for (int i = 1, v, p, last_ans = 0; i <= Q; i++) {
      scanf("%d %d", &v, &p);
      v = (v + 1ll * K * last_ans - 1) % n + 1, p = (p + 1ll * K * last_ans) % (S + 1);
      for (int j = 20; j >= 0; j--) {
        if (fa[v][j] && value[fa[v][j]] > p) {
          v = fa[v][j];
        }
      }
      last_ans = ans[v];
      printf("%d\n", last_ans);
    }
  }
  return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值