最小生成树板子及小结

生成树的小总结

2.1 Kruskal算法求最小生成树

对边排序加一个并查集然后加进来就好了,复杂度是mlogm,对边少的图非常友好。

#include<bits/stdc++.h>
using namespace std;
const int maxn = 510;
int root[maxn];
int find(int x) {
    if (root[x] == x) return x;
    else return root[x] = find(root[x]);
}
struct edge {
    int from, to, dis;
    bool operator<(const edge& rhs)const {
        return dis < rhs.dis;
    }
}edge[100200];
int n, m;
int kruskal() {
    int res = 0, cnt = 0;
    for (int i = 1; i <= n; i++) root[i] = i;
    for (int i = 1; i <= m; i++) {
        int a = edge[i].from, b = edge[i].to, c = edge[i].dis;
        if (find(a) != find(b)) {
            root[find(a)] = root[find(b)];
            cnt++;
            res += c;
        }
        if (cnt == n - 1) break;
    }
    if (cnt == n - 1) return res;
    else return -1;
}
int main() {
    cin >> n >> m;
    for (int i = 1; i <= m; i++) {
        int u, v, w;
        cin>>edge[i].from >> edge[i].to >> edge[i].dis;
    }
    sort(edge + 1, edge + 1 + m);
    int ans = kruskal();
    if (ans == -1) cout << "impossble" << endl;
    else cout << ans << endl;
    return 0;
}

2.2 prim算法求最小生成树

复杂度O(n^2) 适用于边多点少的情况

#include<bits/stdc++.h>
using namespace std;
const int N = 510, INF = 0x3f3f3f3f;
int n, m;
int g[N][N], dist[N];
bool st[N];
int prim() {
	memset(dist, INF, sizeof dist);
	int res = 0;
	for (int i = 0; i < n; i++) {
		int t = -1;
		for (int j = 1; j <= n; j++)
			if (!st[j] && (t == -1 || dist[t] > dist[j]))
				t = j;     
		if (i && dist[t] == INF) return INF;
		if (i) res += dist[t];
		st[t] = true;
		for (int j = 1; j <= n; j++) dist[j] =min(dist[j], g[t][j]);
	}
	return res;
}
int main() {
	cin >> n >> m;
	int u, v, w;
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= n; j++)
			if (i == j) g[i][j] = 0;
			else g[i][j] = INF;
	while (m--) {
		cin >> u >> v >> w;
		g[u][v] = g[v][u] = min(g[u][v], w);
	}
	int t = prim();
	if (t == INF) puts("impossible");
	else cout << t << endl;
}

2.2 求次小生成树(非严格)

判断次小生成树的与最小生成树的不同只有将最小生成树的一条边隐藏,生成的树与最小生成树进行判断就好了,复杂度O(mn);

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e4 + 10;
typedef long long ll;
ll cnt, n, m, tot, ans, mst[maxn], sum;
struct edge {
	ll a, b, c;
}e[maxn];
bool cmp(edge a1, edge a2) { return a1.c < a2.c; }
int root[maxn];
int find(int x) { return x == root[x] ? x : root[x] = find(root[x]); }
void init() {
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= m; i++)scanf("%lld%lld%lld", &e[i].a, &e[i].b, &e[i].c);
}
void kruskal() {
	sort(e + 1, e + 1 + m, cmp);
	for (int i = 1; i <= n; i++) root[i] = i;
	cnt = ans = 0;
	for (int i = 1; i <= m && cnt < n - 1; i++) {
		if (find(e[i].a) != find(e[i].b)) {
			root[find(e[i].a)] = root[find(e[i].b)];
			mst[++cnt] = i;
			ans += e[i].c;
		}
	}
	for (int i = 1; i < n; i++) {
		sum = cnt = 0;
		for (int j = 0; j <= n; j++) root[j] = j;
		for (int j = 1; j <= m && cnt < n - 1; j++) {
			if (j == mst[i]) continue;
			if (find(e[j].a) != find(e[j].b)) {
				root[find(e[j].a)] = root[find(e[j].b)];
				sum += e[j].c;
				cnt++;
			}
		}
		if (cnt != n - 1) continue;
		if (sum == ans) { cout << "No" << endl; return; }
	}
	cout << ans << endl;
}//这个是判断是否存在唯一最小生成树
int main() {
	int t;
	cin >> t;
	while (t--) {
		init();
		kruskal();
	}
	return 0;
}

连小宝宝都看得懂这个暴力做法,但是当n=1e5的时候,m=3e5的时候,复杂度就起飞了。那其实还是可以接着优化的。
前缀知识用 lca优化倍增,下面是lca的模板,复杂度O(mlogn)

#include<bits/stdc++.h>
using namespace std;
const int maxn = 5e5 + 100;
vector<int>g[maxn];int n, m, s;
int depth[maxn], dp[maxn][22];
void dfs(int u,int p, int d) {
	dp[u][0] = p;
	depth[u] = d;
	int len = g[u].size();
	for (int i = 0; i < len; i++) {
		int v = g[u][i];
		if (v == p) continue;
		dfs(v, u, d + 1);
	}
}
void init() {
	for (int i = 1; i <= 20; i++) 
		for (int j = 1; j <= n; j++) 
			dp[j][i] = dp[dp[j][i - 1]][i - 1];
}
int lca(int x, int y) {
	if (depth[x] < depth[y]) swap(x, y);
	for (int i = log2(depth[x] - depth[y]); i >= 0; i--) {
		if ((1 << i) <= depth[x] - depth[y]) x = dp[x][i];
	}
	if (x == y) return x;
	for (int i = log2(depth[x]); i >= 0; i--) {
		if (dp[x][i] != dp[y][i]) {
			x = dp[x][i];
			y = dp[y][i];
		}
	}
	assert(x != y && dp[x][0] == dp[y][0]);
	return dp[x][0];
}
int main() {
	scanf("%d%d%d", &n, &m, &s);
	for (int i = 1; i < n; i++) {
		int a, b;
		scanf("%d%d", &a, &b);
		g[a].push_back(b);
		g[b].push_back(a);
	}
	dfs(s, s, 0);
	init();
	while (m--) {
		int a, b;
		scanf("%d%d", &a, &b);
		printf("%d\n", lca(a, b));
	}
	return 0;
}

这个是优化过后的非严格的生成树的板子,复杂度大概在O(mlogn)这里的亚子

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 3e5 + 100;
const ll inf = 2147483647000000;
ll root[maxn], head[maxn], depth[maxn], fa[maxn][22], maxf[maxn][22];
ll n, m, sum, cnt1, cnt;
int find(int x) { return x == root[x] ? x : root[x] = find(root[x]); }
struct edge { ll a, b, c; bool in; }e[maxn];
struct edge1 { ll  v, w, next; }g[maxn];
void add(int u, int v, ll w) { g[++cnt1].next = head[u]; g[cnt1].v = v; g[cnt1].w = w; head[u] = cnt1; }
bool cmp(edge a, edge b) { return a.c < b.c; }
void kruskal() {
	sort(e + 1, e + 1 + m, cmp);
	for (int i = 1; i <= n; i++) root[i] = i;
	for (int i = 1; i <= m && cnt < n - 1; i++) {
		if (find(e[i].a) != find(e[i].b)) {
			root[find(e[i].a)] = root[find(e[i].b)];
			sum += e[i].c;
			e[i].in = 1; cnt++;
			add(e[i].a, e[i].b, e[i].c); add(e[i].b, e[i].a, e[i].c);
		}
	}
}
int lca(int x, int y) {
	if (depth[x] < depth[y]) swap(x, y);
	for (int i = log2(depth[x] - depth[y]); i >= 0; i--) {
		if ((1 << i) <= depth[x] - depth[y]) x = fa[x][i];
	}
	if (x == y) return x;
	for (int i = log2(depth[x]); i >= 0; i--) {
		if (fa[x][i] != fa[y][i]) {
			x = fa[x][i];
			y = fa[y][i];
		}
	}
	return fa[x][0];
}
void dfs(int t, int f, ll w) {
	depth[t] = depth[f] + 1;
	fa[t][0] = f;
	maxf[t][0] = w;
    //直接用dfs按深度直接就可以对这条边支进行倍增按深度进行区间合并找最大值和次大值
	for (int i = 1; (1 << i) <= depth[t]; i++) {
		fa[t][i] = fa[fa[t][i - 1]][i - 1];
		maxf[t][i] = max(maxf[t][i - 1], maxf[fa[t][i - 1]][i - 1]);//第一个maxf是从t到2^i-1的区间最大值
        //第二个maxf是2^i-1到2^i的值,然后合并
	}
	for (int i = head[t]; i; i = g[i].next) {
		if (g[i].v == f) continue;
		dfs(g[i].v, t, g[i].w);
	}
}
ll work(int x, int y) {
	ll ans = -inf;
	for (int i = 18; i >= 0; i--) {
		if (depth[fa[x][i]] >= depth[y]) {
			 ans = max(ans, maxf[x][i]);
			x = fa[x][i];
		}
	}
	return ans;
}
int main() {
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= m; i++) scanf("%lld%lld%lld", &e[i].a, &e[i].b, &e[i].c);
	kruskal();//将能构成最小生成树的边都打上标记
	dfs(1, 0, 0);//再把所有能建成最小生成树的边都按1这个节点建树,然后提前处理好这颗树里的lca和最大次大值
	ll ans = inf;
	for (int i = 1; i <= m; i++) {
		if (e[i].in) continue;
		int u = e[i].a, v = e[i].b;//因为每次加边进去会形成一个环,形成的环其实就是u到v之前的那条链
		int L = lca(u, v);//而这条链想要快速求出区间最大和次大,就要和lca联系起来
		ll maxu = work(u, L);//按lca将这条链分成两条,分别是(lca,u)和(lca,v)合并求最大次大就ok了
		ll maxv = work(v, L);
		ans = min(ans, sum - max(maxv, maxu) + e[i].c);
	}
	printf("%lld\n", ans);
	return 0;
}

2.3 求最小生成树的个数

如果两颗最小生成树中至少有一条边不同,则这两个最小生成树就是不同的。
对所有的最小生成树来说,每种边权的数量都是一致的

2.4 求次小生成树(严格)

和前面不严格的写法一样,只不过加了一个次大的数组

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 3e5 + 100;
const ll inf = 2147483647000000;
ll root[maxn], head[maxn], depth[maxn],fa[maxn][22],maxf[maxn][22],minf[maxn][22];
ll n, m, sum,cnt1,cnt;
int find(int x) { return x == root[x] ? x : root[x] = find(root[x]); }
struct edge { ll a, b, c; bool in; }e[maxn];
struct edge1 { ll  v, w, next; }g[maxn];
void add(int u, int v, ll w) { g[++cnt1].next = head[u]; g[cnt1].v = v; g[cnt1].w = w; head[u] = cnt1; }
bool cmp(edge a, edge b) { return a.c < b.c; }
void kruskal() {
	sort(e + 1, e + 1 + m, cmp);
	for (int i = 1; i <= n; i++) root[i] = i;
	for (int i = 1; i <= m && cnt < n - 1; i++) {
		if (find(e[i].a) != find(e[i].b)) {
			root[find(e[i].a)] = root[find(e[i].b)];
			sum += e[i].c;
			e[i].in = 1; cnt++;
			add(e[i].a, e[i].b, e[i].c); add(e[i].b, e[i].a, e[i].c);
		}
	}
}
int lca(int x, int y) {
	if (depth[x] < depth[y]) swap(x, y);
	for (int i = log2(depth[x] - depth[y]); i >= 0; i--) {
		if ((1 << i) <= depth[x] - depth[y]) x = fa[x][i];
	}
	if (x == y) return x;
	for (int i = log2(depth[x]); i >= 0; i--) {
		if (fa[x][i] != fa[y][i]) {
			x = fa[x][i];
			y = fa[y][i];
		}
	}
	return fa[x][0];
}
void dfs(int t, int f, ll w) {
	depth[t] = depth[f] + 1;
	fa[t][0] = f;
	maxf[t][0] = w;
	minf[t][0] = -inf;
	for (int i = 1; (1 << i) <= depth[t]; i++) {
		fa[t][i] = fa[fa[t][i - 1]][i - 1];
		maxf[t][i] = max(maxf[t][i - 1], maxf[fa[t][i - 1]][i - 1]);
		minf[t][i] = max(minf[t][i - 1], minf[fa[t][i - 1]][i - 1]);
		if (maxf[t][i - 1] > maxf[fa[t][i - 1]][i - 1]) minf[t][i] = max(minf[t][i], maxf[fa[t][i - 1]][i - 1]);
		else if (maxf[t][i - 1] < maxf[fa[t][i - 1]][i - 1]) minf[t][i] = max(minf[t][i], maxf[t][i - 1]);
	}
	for (int i = head[t]; i; i = g[i].next) {
		if (g[i].v == f) continue;
		dfs(g[i].v, t, g[i].w);
	}
}
ll work(int x, int y, ll maxx) {
	ll ans = -inf;
	for (int i = 18; i >= 0; i--) {
		if (depth[fa[x][i]] >= depth[y]) {
			if (maxx != maxf[x][i]) ans = max(ans, maxf[x][i]);
			else ans = max(ans, minf[x][i]);
			x = fa[x][i];
		}
	}
	return ans;
}
int main() {
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= m; i++) scanf("%lld%lld%lld", &e[i].a, &e[i].b, &e[i].c);
	kruskal();
	dfs(1, 0, 0);
	ll ans = inf;
	for (int i = 1; i <= m; i++) {
		if (e[i].in) continue;
		int u = e[i].a, v = e[i].b;
		int L = lca(u, v);
		ll maxu = work(u, L, e[i].c);
		ll maxv = work(v, L, e[i].c);
		ans = min(ans, sum - max(maxv, maxu) + e[i].c);		
	}
	printf("%lld\n", ans);
	return 0;
}

每天一遍

  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值