赞
踩
简称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一下)
最后,祝大家情人节快乐,也预祝新年快乐!
有对象的长长久久~ 还没有的会尽快遇到陪你一辈子的人~
谢谢大家支持
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。