Kruskal重构树——学习笔记

Kruskal重构树

用于解决图中,有关两点间路径最大/小边权最小/大值的问题
如将
在这里插入图片描述
按边权从小到大建立 K r u s k a l Kruskal 重构树,我们就能得到这样的树
在这里插入图片描述
首先 K r u s k a l Kruskal 重构树只有 2 N 1 2N-1 个节点,只有 N N 2 N 1 2N-1 的点才 v a l val
而原图中任意两点 u , v u,v 间路径中最大边权的最小值可以在这颗树中找到,即 v a l [ l c a ( u , v ) ] val[lca(u, v)]
并且这颗树是一个大根堆,父节点的值大于或等于子节点的值

同理,如果我们按边权从大到小建立 K r u s k a l Kruskal 重构树,我们就能得到这样的树
在这里插入图片描述
原图中任意两点 u , v u,v 间路径中最小边权的最大值可以在这颗树中找到,即 v a l [ l c a ( u , v ) ] val[lca(u, v)]
并且这颗树是一个小根堆,父节点的值小于或等于子节点的值

模板

struct Edge {
    int u, v, w;
    bool operator < (const Edge &rhs) const {
        return w > rhs.w;
        //升序为(u, v)间多条路中最大边权最小值
        //降序为(u, v)间多条路中最小边权最大值
    }
} E[MAX];
vector<int> g[MAX];
int pre[MAX], val[MAX], cnt;//cnt为kruskal重构树的点数, 点数最多为2N - 1
int son[MAX], siz[MAX], top[MAX], fa[MAX], dep[MAX];
void dfs(int u, int par) {
    dep[u] = dep[fa[u] = par] + (siz[u] = 1);
    int max_son = -1;
    for (auto &v: g[u])
        if (v != par) {
            dfs(v, u);
            siz[u] += siz[v];
            if (max_son < siz[v])
                son[u] = v, max_son = siz[v];
        }
}
void dfs2(int u, int topf) {
    top[u] = topf;
    if (!son[u]) return;
    dfs2(son[u], topf);
    for (auto &v: g[u])
        if (v != fa[u] && v != son[u]) dfs2(v, v);
}
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;
}
int find(int x) { return x == pre[x] ? x : pre[x] = find(pre[x]); }
void exKruskal() {
    cnt = N; for (int i = 1; i < (N << 1); i++) pre[i] = i;
    sort(E + 1, E + 1 + M);
    for (int i = 1; i <= M; i++) {
        int u = find(E[i].u), v = find(E[i].v);
        if (u == v) continue;
        val[++cnt] = E[i].w;
        pre[u] = pre[v] = cnt;
        g[u].push_back(cnt), g[cnt].push_back(u);
        g[v].push_back(cnt), g[cnt].push_back(v);
        if (cnt == (N << 1) - 1) break;
    }
    //原图不连通的情况, 那形成的就是森林
    for (int i = 1; i <= cnt; i++)
        if (!siz[i]) {//未访问过
            int rt = find(i);//下树剖lca
            dfs(rt, 0); dfs2(rt, rt);
        }
}

P1967 货车运输

题目链接

题意

N N 个点 M M 条双向边的图,每条路有一个重量限制 w w
现在有 Q Q 次询问,每次询问 u u v v 最多能运多重的物品

题解

询问, u u v v 有多条路径,只要你运的物品比要走的路径中最小的重量限制小就行,现在我们要尽可能的多运,所以要让最小值最大
即查询 u u v v 的路径中最小边权的最大值
那就是一道 K r u s k a l Kruskal 重构树的裸题了
按边权从大到小建立 K r u s k a l Kruskal 重构树,这样 v a l [ l c a ( u , v ) ] val[lca(u, v)] 就是 u u v v 的路径中最小边权的最大值
注意这里图没有说一定连通,所以要判断一下是不是连通

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAX = 2e4 + 10;
const int MAX_M = 5e4 + 10;

int N, M, Q;

struct edge {
    int u, v, w;
    bool operator < (const edge &rhs) const {
        return w > rhs.w;
        //升序为(u, v)间多条路中最大边权最小值
        //降序为(u, v)间多条路中最小边权最大值
    }
} e[MAX_M];
vector<int> g[MAX];
int pre[MAX], val[MAX], cnt;//cnt为kruskal重构树的点数, 点数最多为2N - 1
//u, v为原图上的点, 则val[lca(u, v)]为u->v间多条路中...
//由于u, v可能在原图中不连通, 所以需要find(u)和find(v)判断一下是不是在一棵树中
int son[MAX], siz[MAX], top[MAX], fa[MAX], dep[MAX];
void dfs(int u, int par) {
    dep[u] = dep[fa[u] = par] + (siz[u] = 1);
    int max_son = -1;
    for (auto &v: g[u])
        if (v != par) {
            dfs(v, u);
            siz[u] += siz[v];
            if (max_son < siz[v])
                son[u] = v, max_son = siz[v];
        }
}
void dfs2(int u, int topf) {
    top[u] = topf;
    if (!son[u]) return;
    dfs2(son[u], topf);
    for (auto &v: g[u])
        if (v != fa[u] && v != son[u]) dfs2(v, v);
}
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;
}
int find(int x) { return x == pre[x] ? x : pre[x] = find(pre[x]); }
void exKruskal() {
    cnt = N; for (int i = 1; i < (N << 1); i++) pre[i] = i;
    sort(e + 1, e + 1 + M);
    for (int i = 1; i <= M; i++) {
        int u = find(e[i].u), v = find(e[i].v);
        if (u == v) continue;
        val[++cnt] = e[i].w;
        pre[u] = pre[v] = cnt;
        g[u].push_back(cnt), g[cnt].push_back(u);
        g[v].push_back(cnt), g[cnt].push_back(v);
        if (cnt == (N << 1) - 1) break;//最多2N-1个点
    }
    for (int i = 1; i <= cnt; i++)
        if (!siz[i]) {//未访问过
            int rt = find(i);//下树剖lca
            dfs(rt, 0); dfs2(rt, rt);
        }
}

int main() {

    scanf("%d%d", &N, &M);
    for (int i = 1; i <= M; i++)
        scanf("%d%d%d", &e[i].u, &e[i].v, &e[i].w);
    exKruskal();

    scanf("%d", &Q);
    while (Q--) {
        int u, v; scanf("%d%d", &u, &v);
        if (find(u) != find(v)) printf("-1\n");
        else printf("%d\n", val[lca(u, v)]);
    }

    return 0;
}

牛客练习赛62 E 水灾

题目链接

题意

N N 个点 M M 条双向边的连通图, Q Q 次询问,每次询问图中的 K i K_i 个互不相同的点,问最小的 x x 使得删去所有 x \leq x 的边后这 K i K_i 个点互不连通

题解

要想让两点不能连通,那么两点间的所有路径都不能连通
一条路能够通,那么必须所有边的边权都 > x >x ,那么要让他不连通,只需要这条路径中最小的边权 x \leq x 即可(即至少删掉一条边, 那就删掉权值最小的边),那么两点间有多条路径,要让这些路径中所有的最小边权都 x \leq x ,那就是让最大值 x \leq x
还有保证所有点都不连通,所以任意两个点之间的路径的最小边权的最大值都要 x \leq x
那么我们就可以按边权从大到小建立 K r u s k a l Kruskal 重构树
两两之间求 l c a lca 会超时,考虑到 K r u s k a l Kruskal 重构树是一个小根堆
我们只需要满足树上相邻的两点的 v a l > x val>x 即可,因为越远两点的 l c a lca 的深度越小,其 v a l val 越小
这里相邻可以将 K K 个点按 d f s dfs 序排序

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAX = 1e6 + 10;

int N, M, Q;
int a[MAX];

//kruskal重构树 + 树剖lca
struct edge {
    int u, v, w;
    bool operator < (const edge &rhs) const {
        return w > rhs.w;
    }
} e[MAX];
struct edge2 {
    int nxt, to;
} ee[MAX << 1];
int head[MAX], tot;
void add(int u, int v) { ee[++tot] = edge2{head[u], v}; head[u] = tot; }
int pre[MAX], val[MAX], cnt;
int son[MAX], siz[MAX], top[MAX], fa[MAX], dep[MAX], dfn[MAX], dfnt;
//树剖lca
void dfs(int u, int par) {
    dfn[u] = ++dfnt;//记录dfs序
    dep[u] = dep[fa[u] = par] + (siz[u] = 1);
    int max_son = -1;
    for (int i = head[u], v; i; i = ee[i].nxt)
        if ((v = ee[i].to) != par) {
            dfs(v, u);
            siz[u] += siz[v];
            if (max_son < siz[v])
                son[u] = v, max_son = siz[v];
        }
}
void dfs2(int u, int topf) {
    top[u] = topf;
    if (!son[u]) return;
    dfs2(son[u], topf);
    for (int i = head[u], v; i; i = ee[i].nxt)
        if ((v = ee[i].to) != fa[u] && v != son[u]) dfs2(v, v);
}
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;
}
//kruskal重构树
int find(int x) { return x == pre[x] ? x : pre[x] = find(pre[x]); }
void exKruskal() {
    cnt = N; for (int i = 1; i < (N << 1); i++) pre[i] = i;
    sort(e + 1, e + 1 + M);
    for (int i = 1; i <= M; i++) {
        int u = find(e[i].u), v = find(e[i].v);
        if (u == v) continue;
        val[++cnt] = e[i].w;
        pre[u] = pre[v] = cnt;
        add(u, cnt), add(cnt, u);
        add(v, cnt), add(cnt, v);
        if (cnt == (N << 1) - 1) break;
    }
    int rt = find(1);
    dfs(rt, 0); dfs2(rt, rt);
}

bool cmp(const int &x, const int &y) {
    return dfn[x] < dfn[y];
}

int main() {
    scanf("%d%d%d", &N, &M, &Q);
    for (int i = 1; i <= M; i++) scanf("%d%d%d", &e[i].u, &e[i].v, &e[i].w);
    exKruskal();

    int ans = 0;
    while (Q--) {
        int K; scanf("%d", &K);
        for (int i = 1; i <= K; i++) scanf("%d", &a[i]), a[i] ^= ans;
        sort(a + 1, a + 1 + K, cmp);
        ans = 0;
        for (int i = 2; i <= K; i++)
            ans = max(ans, val[lca(a[i], a[i - 1])]);
        printf("%d\n", ans);
    }

    return 0;
}

P4768 [NOI2018]归程

题目链接

题意

N N 个点 M M 条边的连通图,每条边有水位线 a a 和长度 l l ,若某天的最高水位线大于等于一条边的水位线,那么这条边车不能通过
现在 Q Q 次询问,给你当日的最高水位线 S S ,每次从点 v v 出发回到点 1 1 ,先乘车到达能够到达的点,剩下的路都步行(步行可以不考虑路的水位线),问最少走多长的路

题解

这里我们以以水位线为边权,
转换一下题意,其实就是找所有与 v v 连通的点里面,到点 1 1 的最短距离
这里连通是满足两点间存在某条路径中最小的边权 > S > S
也就是所有路径中最小边权的最大值 > S >S
那么按边权从大到小建立 K r u s k a l Kruskal 重构树
只需要找到出发点 v v 的祖先中 v a l > S val > S 的最大祖先节点
这样这个点及其子节点中所有的点与 v v 都是连通的
这样我们只需要在一开始跑一个最短路,记 f i f_i i i 及其子树中距离 1 1 最近的距离,然后跑一个树形 d p dp 即可

代码

#include <bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int MAX = 2e5 + 10;
const int MAX2 = 4e5 + 10;

int T;
int N, M, Q, K, S;

//最短路
struct edge {
    int nxt, to, w;
} e[MAX2 << 1];
int head[MAX], tot;
void add(int u, int v, int w) { e[++tot] = edge{head[u], v, w}; head[u] = tot; }
void init() { tot = 0; for (int i = 1; i <= N; i++) head[i] = 0; }
int dis[MAX], vis[MAX];
struct node {
    int now, d;
    bool operator < (const node &rhs) const {
        return d > rhs.d;
    }
};
priority_queue<node> q;
void dijkstra(int s) {
    while (!q.empty()) q.pop();
    for (int i = 1; i <= N; i++) dis[i] = INF, vis[i] = 0;
    dis[s] = 0;
    q.push(node{s, dis[s]});
    while (!q.empty()) {
        node p = q.top(); q.pop();
        int u = p.now;
        if (vis[u]) continue;
        vis[u] = 1;
        for (int i = head[u], v = e[i].to; i; i = e[i].nxt, v = e[i].to)
            if (dis[u] + e[i].w < dis[v]) {
                dis[v] = dis[u] + e[i].w;
                if (!vis[v]) q.push(node{v, dis[v]});
            }
    }
}

//kruskal重构树和树形dp
struct Edge {
    int u, v, w;
    bool operator < (const Edge &rhs) const {
        return w > rhs.w;
    }
} E[MAX2];
vector<int> g[MAX2];
void add(int u, int v) { g[u].push_back(v); }
int pre[MAX2], val[MAX2], cnt;
int anc[MAX2][20], f[MAX2];
void dfs(int u, int fa) {//树形dp
    f[u] = u <= N ? dis[u] : INF;//只有1-N的点才有距离
    anc[u][0] = fa;
    for (int i = 1; i <= 19; i++) anc[u][i] = anc[anc[u][i - 1]][i - 1];
    for (auto &v: g[u])
        if (v != fa) {
            dfs(v, u);
            f[u] = min(f[u], f[v]);//记录子树中最短距离
        }
    g[u].clear();//多组数据, 清空kruskal重构树的边
}
int getPoint(int u, int p) {
    for (int i = 19; i >= 0; i--)
        if (val[anc[u][i]] > p) u = anc[u][i];
    return u;
}
int find(int x) { return x == pre[x] ? x : pre[x] = find(pre[x]); }
void exKruskal() {
    cnt = N; for (int i = 1; i < (N << 1); i++) pre[i] = i, val[i] = 0;
    sort(E + 1, E + 1 + M);
    for (int i = 1; i <= M; i++) {
        int u = find(E[i].u), v = find(E[i].v);
        if (u == v) continue;
        val[++cnt] = E[i].w;
        pre[u] = pre[v] = cnt;
        add(u, cnt), add(cnt, u);
        add(v, cnt), add(cnt, v);
        if (cnt == (N << 1) - 1) break;
    }
    int rt = find(1);
    dfs(rt, 0);
}

int main() {

    scanf("%d", &T);
    while (T--) {
        scanf("%d%d", &N, &M);
        init();
        for (int i = 1; i <= M; i++) {
            int u, v, l, w; scanf("%d%d%d%d", &u, &v, &l, &w);
            E[i] = Edge{u, v, w};
            add(u, v, l); add(v, u, l);
        }
        dijkstra(1);//先跑最短路
        exKruskal();
        int ans = 0;
        scanf("%d%d%d", &Q, &K, &S);
        while (Q--) {
            int v0, p0; scanf("%d%d", &v0, &p0);
            int v = (v0 + K * ans - 1) % N + 1, p = (p0 + K * ans) % (S + 1);
            printf("%d\n", ans = f[getPoint(v, p)]);
        }
    }

    return 0;
}

P4197 Peaks

题目链接

题意

N N 座山峰,每座山峰有高度 h i h_i ,有些山峰间有双向道路相连,每条路有困难值。
现在有 Q Q 组询问,每次询问从点 v v 开始只经过困难值小于等于 x x 的路径所能到达的山峰中第 k k 高的山峰,若无解输出 1 -1

题解

这里困难值就是路的边权,那么
在一个图中,对于点 u u 来说,在 u u v v 的所有路径中,只要有一条路径中最大边权 x \leq x ,那么我们就可以通过这条路到达 v v
所以我们这里就是找所有路径中最大边权的最小值
因此按边权从小到大建立 K r u s k a l Kruskal 重构树
这样我们只需要在 v v 的祖先节点中找到 v a l x val\leq x 的最大祖先即可
然后再来看找高度第 K K
我们可以用主席树来完成这个操作
这里我们按 d f s dfs 序建树,并记录刚到一个点的时间 s t a r t start 和搜完所有子树的时间 e n d end ,这样我们要查一个点 u u 及其子节点的信息的时候,只需要找 s t a r t start e n d end 之间的点即可

代码

#include <bits/stdc++.h>
#define mid (l+r)/2
using namespace std;
typedef long long ll;
const int MAX = 2e5 + 10;
const int MAX_N = MAX * 30;
const int MAX_M = 5e5 + 10;

int N, M, Q;
int h[MAX];

//离散化和主席树
struct Hash {
    int b[MAX], tot;
    void init() { tot = 0; }
    void insert(int x) { b[++tot] = x; }
    void build() {
        sort(b + 1, b + 1 + tot);
        tot = unique(b + 1, b + 1 + tot) - (b + 1);
    }
    int pos(int x) { return lower_bound(b + 1, b + 1 + tot, x) - b; }
} ha;
int rt[MAX], tot;
int lc[MAX_N], rc[MAX_N], num[MAX_N];
void update(int &now, int pre, int l, int r, int p) {
    now = ++tot;
    num[now] = num[pre] + 1, lc[now] = lc[pre], rc[now] = rc[pre];
    if (l == r) return;
    if (p <= mid) update(lc[now], lc[pre], l, mid, p);
    else update(rc[now], rc[pre], mid + 1, r, p);
}
int query(int now, int pre, int k) {
    if (num[now] - num[pre] < k) return -1;//如果之间的数少于k个,返回-1
    k = num[now] - num[pre] - k + 1;//这里主席树写的是第k小,转换一下变成第k大
    int l = 1, r = ha.tot;
    while (l < r) {
        int sum = num[lc[now]] - num[lc[pre]];
        if (k <= sum) {
            now = lc[now], pre = lc[pre];
            r = mid;
        }
        else {
            now = rc[now], pre = rc[pre];
            l = mid + 1;
            k -= sum;
        }
    }
    return ha.b[l];
}

//kruskal重构树
struct Edge {
    int u, v, w;
    bool operator < (const Edge &rhs) const { return w < rhs.w; }
} E[MAX_M];
vector<int> g[MAX];
int pre[MAX], val[MAX], cnt;
int anc[MAX][20], dep[MAX], dfn[MAX], nodeOf[MAX], dfnt;
int st[MAX], ed[MAX];
void dfs(int u, int fa) {
    nodeOf[dfn[u] = ++dfnt] = u, anc[u][0] = fa, dep[u] = dep[fa] + 1;
    for (int i = 1; i <= 19; i++) anc[u][i] = anc[anc[u][i - 1]][i - 1];
    st[u] = dfnt;//开始的dfs序
    for (auto &v: g[u])
        if (v != fa) dfs(v, u);
    ed[u] = dfnt;//结束的dfs序
}
int getPoint(int u, int p) {//找到u的祖先节点中满足val <= p的最大val所在的点
    for (int i = 19; i >= 0; i--)
        if (dep[u] > (1 << i) && val[anc[u][i]] <= p) u = anc[u][i];
    return u;
}
int find(int x) { return x == pre[x] ? x : pre[x] = find(pre[x]); }
void exKruskal() {
    cnt = N; for (int i = 1; i < (N << 1); i++) pre[i] = i;
    sort(E + 1, E + 1 + M);
    for (int i = 1; i <= M; i++) {
        int u = find(E[i].u), v = find(E[i].v);
        if (u == v) continue;
        val[++cnt] = E[i].w;
        pre[u] = pre[v] = cnt;
        g[u].push_back(cnt), g[cnt].push_back(u);
        g[v].push_back(cnt), g[cnt].push_back(v);
        if (cnt == (N << 1) - 1) break;
    }
    for (int i = 1; i <= cnt; i++)
        if (!dep[i]) {
            int t = find(i);
            dfs(t, 0);
        }
}

int ask(int u, int p, int k) {
    int point = getPoint(u, p);
    return query(rt[ed[point]], rt[st[point] - 1], k);
}

int main() {

    scanf("%d%d%d", &N, &M, &Q);
    for (int i = 1; i <= N; i++) scanf("%d", &h[i]), ha.insert(h[i]);
    ha.build();
    for (int i = 1; i <= M; i++) {
        int u, v, w; scanf("%d%d%d", &u, &v, &w);
        E[i] = Edge{u, v, w};
    }
    exKruskal();
    for (int i = 1; i <= dfnt; i++) {
        rt[i] = rt[i - 1];
        if (nodeOf[i] <= N)
            update(rt[i], rt[i - 1], 1, ha.tot, ha.pos(h[nodeOf[i]]));
    }
    while (Q--) {
        int v, x, k; scanf("%d%d%d", &v, &x, &k);
        printf("%d\n", ask(v, x, k));
    }


    return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_44282912/article/details/105821573