最小生成树板子及小结

生成树的小总结

2.1 Kruskal算法求最小生成树

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

#include
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
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
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
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
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
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;
}

每天一遍
最小生成树板子及小结_第1张图片

你可能感兴趣的:(笔记,图论,图论,树结构,c++,kruskal)