最小生成树深入学习

简称MST。有两个方法,kruskal(稀疏图)与prim(稠密图)。现给出prim的代码:

int a[N][N], d[N], n, m, ans; //a是邻接矩阵
bool v[N];
inline void prim() {
	memset(d, 0x3f, sizeof(d));
	memset(v, false, sizeof(v));
	d[1] = 0;
	for(int i = 1; i < n; ++i) {
		int x = 0;
		for(int j = 1; j <= n; ++j) if(!v[j] && (x == 0 || d[j] < d[x])) x = j;
		v[x] = true;
		for(int y = 1; y <= n; ++y) if(!v[y]) d[y] = min(d[y], a[x][y]);
	}
	ans = 0;
	for(int i = 1; i <= n; ++i) ans+= d[i];
}

与图论类似,经常会使用二分。

有一个结论:即使MST不唯一,其边权集合也会是固定不变的。(可以解决MST计数)给大家看个代码吧,bzoj1016:

#include <cstdio>
#include <cstring>
#include <map>
#include <algorithm>
#define mo 31011
using namespace std;
struct EDGE {int a, b, c;}e[1005];
inline bool mycmp(EDGE x, EDGE y) {return x.c < y.c;}
struct node {int d, num;}a[105];
int n, m, ans1, fa[105], cnt, score; bool v[1005];
inline int read() {
    int x = 0; char c = getchar();
    while(c < '0' || c > '9') c = getchar();
    while(c >= '0' && c <= '9') {x = x * 10 + c - 48; c = getchar();}
    return x;
}
inline int getfa(int x) {
    int r = x; while(r != fa[r]) r = fa[r];
    while(x != fa[x]) {int temp = x; x = fa[x]; fa[temp] = r;} return r;
}
inline void mst() {
    ans1 = cnt = score = 0; for(int i = 1; i <= n; ++i) fa[i] = i;
    for(int i = 1; i <= m; ++i) {
        if(!v[i]) continue;
        int x = e[i].a, y = e[i].b, w = e[i].c;
        int fx = getfa(x), fy = getfa(y);
        if(fx != fy) {fa[fx] = fy; if(a[cnt].d == w) a[cnt].num++; else {a[++cnt].d = w; a[cnt].num = 1;} ans1+= w;}
    }
    if(score != n - 1) score = -1;
    return ;
}
inline void mst1() {
    score = 0; int ss = 0; for(int i = 1; i <= n; ++i) fa[i] = i;
    for(int i = 1; i <= m; ++i) {
        if(!v[i]) continue;
        int x = e[i].a, y = e[i].b, w = e[i].c;
        int fx = getfa(x), fy = getfa(y);
        if(fx != fy) {fa[fx] = fy; score++; ss+= w;}
    }
    if(score != n - 1 || ss != ans1) score = -1;
    return ;
}
int main() {
    n = read(); m = read();
    for(int i = 1; i <= m; ++i) {e[i].a = read(); e[i].b = read(); e[i].c = read();}
    sort(e+1, e+m+1, mycmp); map<int, int>MAP; map<int, int>MAP1;
    for(int i = 1; i <= m; ++i) {if(e[i].c != e[i - 1].c) MAP[e[i].c] = i, MAP1[e[i].c] = 0; MAP1[e[i].c]++;}
    memset(v, true, sizeof(v)); mst(); int ans = 1;
    for(int i = 1; i <= cnt; ++i) {
        int d = a[i].d, num = a[i].num, sum = 0, tot = MAP1[d], st = MAP[d];
        for(int j = 0; j <= (1<<tot) - 1; ++j) {
            int k = j, tmp = 0; while(k != 0) {++tmp; k-= k & (-k);}
            if(tmp != num) continue;
            for(int j1 = 0; j1 < tot; ++j1) {if(((1<<j1) & j) == 0) {v[st + j1] = false;}}
            mst1(); if(score != -1) ++sum;
            for(int j1 = 0; j1 < tot; ++j1) {if(((1<<j1) & j) == 0) v[st + j1] = true;}
        }
        ans*= sum; ans%= mo;
    }
    printf("%d", ans);
    return 0;
}


看几道例题。

(一)给定一棵n个节点的树,要求增加若干条边,把它扩充为完全图,并满足图中的最小生成树仍是这棵树并且唯一。求增加的边权总和最小是多少。(n ≤ 6000)

给n-1条边排序,仿照kruskal进行遍历。我们按照流程,合并x所在的集合与y所在的集合 这两个集合(并查集)。可知合并完后必为一棵树(一个无向连通图,又无环,当然是树)。但是呢,我们要形成完全图,则这两个集合中的任意两个元素间都要连边(除了x与y),于是u到x的路径,边u<-->v,边x<-->y,v到y的路径就成环了。为了保证边x<-->y是唯一的连接两个集合且在MST中的边,而加的边又要尽量小,那就全都赋为边权+1。则答案增加了(num(x所在集合) * num(y所在集合) - 1) * ((x<-->y的边权) + 1)。

(二)给一无向图,求最小生成树。(1号节点度数不超过S,S给定)点数 ≤ 30。

先不去看1号节点,则图会变成几个连通块。dfs划分出。设一共blank块连通块,若blank > S,无解。

对于每个连通块,求出MST,然后选出一节点p与1号相连(这是能连的边里边权最小的一条边)。

那么现在我们得到了1号点度数为blank的最小生成树。不过呢,既然题目用S去约束,那么我们可以尝试着增加度数,即把一些与1号节点无关的边,替换成一些1号节点发出的边(使代价尽量小)。

遍历1发出的所有边(1,x),它的边权是val。若(1,x)尚不在MST中,那么我们找1~x这条路径上权值最大的边,它的边权记为w吧(这里和上一道题有点类似,也是构成了简单环,这样我们就可以在这个环中把代价较大的删掉了)。找到使w-val最大的那个点,把它更新。(打擂台,贪心思想)

循环的做S - blank次。若有一次发现最大的w - val居然≤0了,break。


由此我们可以看出,不停利用:每一个小的简单环中具有对称性,把最大边弃掉的思想是十分重要的。


(三)最短路径生成树问题

一个无向图,生成树之后要满足,任意一点与1号间的距离与在图中时候的距离都一样(即单源最短路dis值等于生成树中从根节点(当然把1号作为根节点咯)到自己的距离(直接就能dfs出来))。问有多少种生成方法?

对于树中能组成父子的节点对x,y(x是父亲),必须要满足dis[y] = dis[x] + val(x->y)。所以我们枚举这样的点对,他们就一定可以构成满足条件的生成树的一条树枝。

然后乘法原理,得出答案。(怎么乘我也不知道!!!但是由于这样的点集都是有序的,个人感觉可以treedp一下)

最后,祝大家情人节快乐,也预祝新年快乐!

有对象的长长久久~ 还没有的会尽快遇到陪你一辈子的人~ 

谢谢大家支持

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值