「学习笔记」严格次小生成树(树链剖分)
之前写过倍增的做法
链接:严格次小生成树(倍增)
这里只是展示代码:
/*
date: 2022.9.25
worked by yi_fan0305
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define ls p << 1
#define rs p << 1 | 1
const int N = 1e5 + 5;
const int M = 3e5 + 5;
const int INF = ~(1 << 31); // int范围的无穷大
const ll infll = ~(1ll << 63); // long long范围的无穷大
int n, m, cnt, id;
ll sum; // 最小生成树的路径和
int h[N], f[N], vis[M];
// h 存图用 f 并查集祖先 vis 标记边的使用情况
int dep[N], son[N], siz[N], fa[N];
// dep 深度 son 重儿子 siz 子树大小 fa 父节点
int dfn[N], top[N], l[N];
// dfn 位置 top 链顶 l 点权
ll ma[N << 2], mi[N << 2], val[N << 2];
// ma 最大值 mi 次大值 val 线段树每个位置的权值
struct edge {
int u, v, nxt;
ll w; // 边权
int operator < (const edge &a) const { // 重载运算符
return w < a.w;
}
} e[M << 1], g[M];
inline ll read() { // 快读
ll x = 0;
int fg = 0;
char ch = getchar();
while (ch < '0' || ch > '9') {
fg |= (ch == '-');
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 3) + (x << 1) + (ch ^ 48);
ch = getchar();
}
return fg ? ~x + 1 : x;
}
void add(int u, int v, ll w) {
e[++cnt].v = v;
e[cnt].w = w;
e[cnt].nxt = h[u];
h[u] = cnt;
}
int found(int x) {
return f[x] == x ? f[x] : f[x] = found(f[x]);
}
void kruskal() {
int cont = 0;
for (int i = 1; i <= m; ++i) {
int x = g[i].u, y = g[i].v;
ll w = g[i].w;
int fx = found(x), fy = found(y);
if (fx != fy) {
add(x, y, w);
add(y, x, w);
sum += w;
f[fx] = fy;
vis[i] = 1;
if (++cont == n - 1) {
break;
}
}
}
}
// 以下为树剖部分
/*-----------------------------------------------------------------------*/
void dfs(int u, int fat) {
dep[u] = dep[fat] + 1;
fa[u] = fat;
siz[u] = 1;
for (int i = h[u]; i; i = e[i].nxt) {
int v = e[i].v;
if (v == fat) {
continue;
}
l[v] = e[i].w; // 边权下落
dfs(v, u);
siz[u] += siz[v];
if(siz[v] > siz[son[u]]) {
son[u] = v;
}
}
}
void getpos(int u, int tp) {
top[u] = tp;
dfn[u] = ++id;
val[id] = l[u];
if (!son[u]) {
return ;
}
getpos(son[u], tp);
for (int i = h[u]; i; i = e[i].nxt) {
int v = e[i].v;
if (v == fa[u] || v == son[u]) {
continue;
}
getpos(v, v);
}
}
void pushup(int p) { // 合并左右区间获得最大值和次大值
ma[p] = max(ma[ls], ma[rs]);
if (ma[ls] == ma[rs]) {
mi[p] = max(mi[ls], mi[rs]);
}
else {
mi[p] = min(ma[ls], ma[rs]);
}
}
void build(int p, int l, int r) {
if (l == r) {
ma[p] = val[l];
return ;
}
int mid = (l + r) >> 1;
build(ls, l, mid);
build(rs, mid + 1, r);
pushup(p);
}
ll query(int p, int l, int r, int lr, int rr, ll key) { // 寻找小于 key 的最大值
if (lr <= l && r <= rr) {
if (key == ma[p]) {
return mi[p];
}
return ma[p];
}
int mid = (l + r) >> 1;
ll ans = 0;
if (lr <= mid) {
ans = max(ans, query(ls, l, mid, lr, rr, key));
}
if (rr > mid) {
ans = max(ans, query(rs, mid + 1, r, lr, rr, key));
}
return ans;
}
ll find(int x, int y, ll key) {
ll ans = 0;
while (top[x] != top[y]) {
if (dep[top[x]] < dep[top[y]]) {
swap(x, y);
}
ans = max(ans, query(1, 1, n, dfn[top[x]], dfn[x], key));
x = fa[top[x]];
}
if (dep[x] < dep[y]) {
swap(x, y);
}
ans = max(ans, query(1, 1, n, dfn[y] + 1, dfn[x], key));
return ans;
}
/*-----------------------------------------------------------------------*/
int main() {
n = read();
m = read();
for (int i = 1; i <= n; ++i) { // 并查集操作
f[i] = i;
}
for (int i = 1; i <= m; ++i) { // 读入,判断是否为自环
g[i].u = read();
g[i].v = read();
g[i].w = read();
if (g[i].u == g[i].v) { // 是自环,将值设为无穷大
g[i].w = ~(1 << 31);
}
}
sort(g + 1, g + m + 1); // kruskal排序
kruskal();
dfs(1, 0);
getpos(1, 1);
build(1, 1, n);
ll ans = infll;
for (int i = 1; i <= m; ++i) {
if (vis[i]) {
continue;
}
ans = min(ans, sum + g[i].w - find(g[i].u, g[i].v, g[i].w));
// 注意!!!这里是最初始的边,不是kruskal函数中新建的边
// 加入一条新边,删去一条最大边
}
printf("%lld\n", ans);
return 0;
}
朝气蓬勃 后生可畏