Loading

「学习笔记」严格次小生成树(树链剖分)

之前写过倍增的做法
链接:严格次小生成树(倍增)
这里只是展示代码:

/*
  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;
}
posted @ 2022-09-25 14:51  yi_fan0305  阅读(40)  评论(1编辑  收藏  举报