最小度限制生成树

黑书+论文+各种资料。终于理解了一点。。。

最小度限制生成树就是给一个图,让求它的最小生成树。找的的最小生成树满足并且点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;

}

 

 

 

 

 

 

posted @ 2012-07-03 21:50  AC_Von  阅读(1797)  评论(0编辑  收藏  举报