一本通:秘密的牛奶运输

次小生成树经典做法总结

定义

严格次小生成树:生成树权值之和的第二小的数值的树

非严格次小生成树:生成树权值之和的数值第二小的的树

Sol1:(既可以求严格最小生成树,也可以求非严格)

先做一遍最小生成树,枚举每一条非树边,加上这条边,便形成了一个环,再去掉环上权值最大的一条树边,得到了一颗疑似次小生成树,再取个极值。

复杂度:O(mlogm+n^2+m)

实现细节:

去掉环上权值最大的一条树边:建立最小生成树,从每个点依次dfs处理出从该点到其他点的路径上权值的最大值

原理

设T是G的一个最小生成树,对于非树边a,树边b,(+a,-b)后若仍是一个生成树,称为一个可行变换

由T进行一次可行变换得到的新生成树集合称为T的邻集

定理

次小生成树一定在最小生成树的邻集中

Sol2:每次去掉一个树边,再做一遍Kruskal算法,再求最小的生成树

Code:

#include<bits/stdc++.h>
#define LL long long
#define x first
#define y second
using namespace std;
typedef pair<int, int> PII;
const int N = 2010,M=40010,INF=0x3f3f3f3f;
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int c) {
	ne[idx] = h[a], e[idx] = b, w[idx] = c, h[a] = idx++;
}
int n, m;
struct Edge {
	int a, b, c;
	bool operator < (const Edge& e) { return c < e.c; }
}ee[M];
int cnt;
int p[N];
int dist[N][N];
void dfs(int u, int p, int maxw,int id) {
	for (int i = h[u]; ~i; i = ne[i]) {
		int k = e[i];
		if (p == k)continue;
		dist[id][k] = max(maxw, w[i]);
		dfs(k, u, dist[id][k],id);
	}
}
int find(int x) {
	if (p[x] != x)p[x] = find(p[x]);
	return p[x];
}
bool st[M];
int main() {
	freopen("in.in", "r", stdin);
	memset(h, -1, sizeof h);
	cin >> n >> m;
	for (int i = 1; i <= n; i++)p[i] = i;
	for (int i = 1; i <= m; i++) {
		int a, b, c;
		cin >> a >> b >> c;
		ee[cnt++] = { a,b,c };
	}
	sort(ee, ee + cnt);
	LL tot = 0;
	for (int i = 0; i < cnt; i++) {
		int a = ee[i].a, b = ee[i].b, w = ee[i].c;
		int pa = find(a), pb = find(b);
		if (pa != pb) {
			st[i] = true;
			p[pa] = pb;
			add(a, b, w), add(b, a, w);
			tot += w;
		}
	}
	for(int i=1;i<=n;i++)
	dfs(i,-1,0,i);
	int res = INF;
	for(int i=0;i<cnt;i++)
		if (!st[i]) {
			int a = ee[i].a, b = ee[i].b, w = ee[i].c;
			if(w>dist[a][b])
			res = min(res, w - dist[a][b]);
		}
	cout << res + tot;
}