黑书+论文+各种资料。终于理解了一点。。。
最小度限制生成树就是给一个图,让求它的最小生成树。找的的最小生成树满足并且点vo的度最大为k。
算法流程如下:
1.将该点(以下用v0表示)从图中删除,将得到m个连通分量。
2.对每个连通分量求最小生成树,假设m个。
3.从每个连通分量中找与v0关联的权值最小的边,与v0相连接,这样将得到v0的最小m度生成树
4.如果 k < m 那么这种树是不存在的。
5.如果 k >=m ,那么考虑构建 m+1度 最小生成树 ,将与v0关联的且不在当前的树中的边
6.如果将其加入树中 ,必然会存在一个环,那么删掉该环中与v0不关联的权值最大边,将得到加入该边后的最小生成树,且是m+1的。
7.枚举上述 6 的边找树权值最小,那么即是m+1度限制的最小生成树。如果 m + 1 度最小生成树的值大于 m 度最小生成树的话直接输出当前 m 度的值即可。
8.重复5.6.7,直到k 度最小生成树出现。
由于从度为m扩展到m+1时存在大量的重复计算,可以用动态规划优化。
以下引用自汪汀的论文
由最小m度限制生成树,得到最小m+1度限制生成树,对于和V0相邻的点v,则可以知道一定会有一个环出现,只要找到这个环上的最大权边,用边(V0, v)替换掉,就可以得到一个m+1度限制生成树,枚举所有和V0相邻点v,找到替换后增加权值最小的一次替换,就可以求得m+1度限制生成树。。如果每添加一条边,都需要对环上的边一一枚
举,时间复杂度将比较高,这里,动态规划就有了用武之地。设Best(v)为路径v0—v上与v0无关联且权值最大的边。定义father(v)为v的父结点,动态转移方程:Best(v)=max(Best(father(v)),ω(father(v),v)),边界条件为Best[v0]=-∞,Best[v’]=-∞| (v0,v’)∈E(T)。
模板 POJ 1639:
#include <iostream> #include <cstdio> #include <cmath> #include <vector> #include <cstring> #include <algorithm> #include <string> #include <set> #include <ctime> #include <queue> #include <map> #define CL(arr, val) memset(arr, val, sizeof(arr)) #define REP(i, n) for((i) = 0; (i) < (n); ++(i)) #define FOR(i, l, h) for((i) = (l); (i) <= (h); ++(i)) #define FORD(i, h, l) for((i) = (h); (i) >= (l); --(i)) #define L(x) (x) << 1 #define R(x) (x) << 1 | 1 #define MID(l, r) (l + r) >> 1 #define Min(x, y) x < y ? x : y #define Max(x, y) x < y ? y : x #define E(x) (1 << (x)) const double eps = 1e-8; typedef long long LL; using namespace std; const int inf = ~0u>>2; const int N = 33; int parent[N]; int g[N][N]; bool flag[N][N]; map<string, int> NUM; int n, k, cnt, ans; struct node { int x; int y; int v; } a[1<<10]; struct edge { int x; int y; int v; } dp[N]; bool cmp(node a, node b) { return a.v < b.v; } int find(int x) { //并查集查找 int k, j, r; r = x; while(r != parent[r]) r = parent[r]; k = x; while(k != r) { j = parent[k]; parent[k] = r; k = j; } return r; } int get_num(string s) { //求编号 if(NUM.find(s) == NUM.end()) { NUM[s] = ++cnt; } return NUM[s]; } void kruskal() { //。。。 int i; FOR(i, 1, n) { if(a[i].x == 1 || a[i].y == 1) continue; int x = find(a[i].x); int y = find(a[i].y); if(x == y) continue; flag[a[i].x][a[i].y] = flag[a[i].y][a[i].x] = true; parent[y] = x; ans += a[i].v; } //printf("%d\n", ans); } void dfs(int x, int pre) { //dfs求1到某节点路程上的最大值 int i; FOR(i, 2, cnt) { if(i != pre && flag[x][i]) { if(dp[i].v == -1) { if(dp[x].v > g[x][i]) dp[i] = dp[x]; else { dp[i].v = g[x][i]; dp[i].x = x; //记录这条边 dp[i].y = i; } } dfs(i, x); } } } void init() { ans = 0; cnt = 1; CL(flag, false); CL(g, -1); NUM["Park"] = 1; for(int i = 0; i < N; ++i) parent[i] = i; } int main() { //freopen("data.in", "r", stdin); int i, j, v; string s; scanf("%d", &n); init(); for(i = 1; i <= n; ++i) { cin >> s; a[i].x = get_num(s); cin >> s; a[i].y = get_num(s); scanf("%d", &v); a[i].v = v; if(g[a[i].x][a[i].y] == -1) g[a[i].x][a[i].y] = g[a[i].y][a[i].x] = v; else g[a[i].x][a[i].y] = g[a[i].y][a[i].x] = min(g[a[i].x][a[i].y], v); } scanf("%d", &k); int set[N], Min[N]; REP(i, N) Min[i] = inf; sort(a + 1, a + n + 1, cmp); kruskal(); FOR(i, 2, cnt) { //找到1到其他连通块的最小值 if(g[1][i] != -1) { int x = find(i); if(Min[x] > g[1][i]) { Min[x] = g[1][i]; set[x] = i; } } } int m = 0; FOR(i, 1, cnt) { //把1跟这些连通块连接起来 if(Min[i] != inf) { m++; flag[1][set[i]] = flag[set[i]][1] = true; ans += g[1][set[i]]; } } //printf("%d\n", ans); for(i = m + 1; i <= k; ++i) { //从度为m+1一直枚举到最大为k,找ans的最小值 CL(dp, -1); dp[1].v = -inf; //dp初始化 for(j = 2; j <= cnt; ++j) { if(flag[1][j]) dp[j].v = -inf; } dfs(1, -1); int tmp, mi = inf; for(j = 2; j <= cnt; ++j) { if(g[1][j] != -1) { if(mi > g[1][j] - dp[j].v) { //找到一条dp到连通块中某个点的边,替换原来连通块中的边(前提是新找的这条边比原来连通块中那条边要大) mi = g[1][j] - dp[j].v; tmp = j; } } } if(mi >= 0) break; //如果不存在这样的边,直接退出 int x = dp[tmp].x, y = dp[tmp].y; flag[1][tmp] = flag[tmp][1] = true; //加上新找的边 flag[x][y] = flag[y][x] = false; //删掉被替换掉的那条边 ans += mi; } printf("Total miles driven: %d\n", ans); return 0; }