20230410 训练记录:最小瓶颈路 / lca
初识最小瓶颈路其实是上海那道著名的铜牌题,其次就是 P1396 营救。
P1967 [NOIP2013 提高组] 货车运输 / 最小瓶颈路#
问题反过来,最小生成树上的瓶颈(最大边权)也是一样的,双倍经验捏: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 重构树上两点之间的的权值。
建立 Kruskal 重构树就是在做 Kruskal 的过程中,原本要合并
这方法对最大生成树两点间最小边权也适用。
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);
}
}
}
倍增可以得
100pts(Tarjan / RMQ 求 lca)#
满分得配合 Tarjan / RMQ 求 ,不过看起来貌似 RMQ 更厉害一点。
草,怎么还要复习稀疏表,咋写的来着。
用
表示 的答案,只要有 就可以用。递推就是倍增: 反正就是
。 查询的时候就拆成
。
结论是:记
意为从
现在,终于可以来看看这道题了。
Life is a game#
先学了一个单词 threshold(/ˈθreʃˌhoʊld/): 门槛,瓶颈。
题意#
个点 条边,点权表示这个点拥有的金币(只能获得一次),边权表示前往联通的点至少需要有 金币。
多组询问:从点出生,初始拥有 个金币,最多可以得到的金币数为多少?
实际的边权为
展开代码
#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 国际」许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现