20230410 训练记录:最小瓶颈路 / lca

初识最小瓶颈路其实是上海那道著名的铜牌题,其次就是 P1396 营救

P1967 [NOIP2013 提高组] 货车运输 / 最小瓶颈路#

O(mlogm+(n+q)logn) 最大生成树(森林)两点间最小边权,直接在倍增 lca 向上爬的时候更新答案。

问题反过来,最小生成树上的瓶颈(最大边权)也是一样的,双倍经验捏:LOJ #136. 最小瓶颈路

为了避免重复访问,dfs 的时候不记父亲节点(如果要记的话,需要判 parent[u] == p)。

展开代码
void dfs(int u) {
    for (int i = h[u]; i; i = graph[i].t) {
        if (int v = graph[i].f; !dep[v]) {
            dep[v] = dep[u] + 1, f[v][0] = u, w[v][0] = graph[i].w;
            dfs(v);
        }
    }
}
// ...
for (int i = 1; i <= n; i++) if (!dep[i]) {
    dep[i] = 1;
    dfs(i);
    f[i][0] = i; // 之前因为这里无脑设成 f[u][0] = p 而一直出错
    w[i][0] = INT_MAX;
}

哇呜呜,怎么还有加强版,学!

LOJ#137. 最小瓶颈路(加强版)/ Kruskal 重构树#

使用 Kruskal 重构树来求解。 感觉已经看过太多太多次克鲁斯卡尔重构树了,但是都没有认真学过。

最小瓶颈路
= 最小生成树两点间简单路径上的最大值
= Kruskal 重构树上两点之间的 lca 的权值。

建立 Kruskal 重构树就是在做 Kruskal 的过程中,原本要合并 uv,现在改为新建一个点 t,连边 utvt 的点权设为 u,v 的边权。

这方法对最大生成树两点间最小边权也适用。

void Kruskal() {
    sort(edge + 1, edge + 1 + m, cmp);
    for (int i = 1; i <= n; ++i) ff[i] = i;
    for (int i = 1; i <= m; ++i) {
        int fu = find(edge[i].u), fv = find(edge[i].v);
        if (fu != fv) {
            val[++cnt] = edge[i].dis;
            ff[cnt] = ff[fu] = ff[fv] = cnt;
            add(fu, cnt), add(cnt, fu);
            add(fv, cnt), add(cnt, fv);
        }
    }
}

倍增可以得 75pts,树剖可以得 90pts

image

image

100pts(Tarjan / RMQ 求 lca)#

满分得配合 Tarjan / RMQ 求 lca,这俩都是离线算法,不过看起来貌似 RMQ 更厉害一点

草,怎么还要复习稀疏表,咋写的来着。

fi,j 表示 [i,i+2j) 的答案,只要有 xopx=x 就可以用。递推就是倍增:

fi,j=fi,j1opfi+2j1,j1

反正就是 [i,i+2j1)[i+jj1,i+2×2j1)=[i,i+2j)

查询的时候就拆成 [l,l+2t)[r2t+1,r]

结论是:记 posuu 在欧拉序中第一次出现的位置,则

poslca(u,v)=mink=uv{posk}

意为从 u 走到 v 经过欧拉序最小的节点就是 lca(u,v)

现在,终于可以来看看这道题了。

Life is a game#

先学了一个单词 threshold(/ˈθreʃˌhoʊld/): 门槛,瓶颈。

题意#

n 个点 m 条边,点权表示这个点拥有的金币(只能获得一次),边权表示前往联通的点至少需要有 w 金币。
多组询问:从 x 点出生,初始拥有 k 个金币,最多可以得到的金币数为多少?
n,m,q105;ai104;k,w109

实际的边权为 wiau,wiav,随后在 Kruskal 重构树上不断向上爬,直到 wx,i>k,此时 ax+k 即为所求。

展开代码
#include <bits/stdc++.h>

using ll = long long;

const int N = 200010, M = N * 2;

int n, m, q;
ll a[N];

int val[N], t = 0, f[N][20];
ll w[N][20];

struct edge {
    int f, w, t;
    bool operator< (const edge &_) const {
        return w < _.w;
    }
} mst[N];

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

void Kruskal() {
    std::sort(mst + 1, mst + m + 1);
    for (int i = 1; i <= n * 2; i++)
        p[i] = i;

    t = n;
    for (int i = 1, edgs = 0; i <= m; i++) {
        int u = find(mst[i].f);
        int v = find(mst[i].t);
        int w = mst[i].w;

        if (u != v) {
            a[++t] = a[u] + a[v];
            p[u] = p[v] = f[u][0] = f[v][0] = t;
            ::w[u][0] = w - a[u];
            ::w[v][0] = w - a[v];
            edgs += 1;
        }

        if (edgs == n - 1)
            break;
    }
}

int main() {
    scanf("%d%d%d", &n, &m, &q);
    for (int i = 1; i <= n; i++) scanf("%lld", a + i);

    for (int i = 1; i <= m; i++) {
        int u, v, w;
        scanf("%d%d%d", &u, &v, &w);
        mst[i] = {u, w, v};
    }

    Kruskal();

    a[0] = a[t];
    int lg = std::__lg(t) + 1;
    for (int j = 1; j <= lg; j++) {
        for (int i = 1; i <= t; i++) {
            f[i][j] = f[f[i][j - 1]][j - 1];
            w[i][j] = std::max(w[i][j - 1], w[f[i][j - 1]][j - 1]);
        }
    }

    while (q --) {
        int x, k;
        scanf("%d%d", &x, &k);

        for (int i = lg; ~i; i--) {
            if (w[x][i] <= k)
                x = f[x][i];
        }

        printf("%lld\n", a[x] + k);
    }

    return 0;
}

总结#

今天一直在学习树上问题,希望在天梯赛之前能够磨练自己的数据结构和树论水平,等天梯赛选上西安之后再复习数论。慢慢地要改变之前的学习方式了,得开始背一背板子,定期做做复习工作。

加油喵。

作者:patricky

出处:https://www.cnblogs.com/patricky/p/train-20230410.html

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   PatrickyTau  阅读(45)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示
more_horiz
keyboard_arrow_up light_mode palette
选择主题
menu