最小度限制生成树

设G=(V, E, ω)是连通的无向图,v0 ∈V是特别指定的一个顶点,k为给定的一个正整数。如果T是G的一个生成树且dT(v0)=k,则称T为G的k度限制生成树。G中权值和最小的k度限制生成树称为G的最小k度限制生成树

明确几个概念
T为图G的一个生成树,T+a-b记作(+a,-b),如果T+a-b仍然是一个生成树,则称(+a,-b)是T的一个可行交换。

T为图G的一个生成树,由T进行一次可行交换得到的新的生成树所组成的集合,称为T的邻集,记为N(T)。

定 理

定理:设T是图G的最小k度限制树,E0是G中与v0有关联的边的集合,
E1=E0\E(T),
E1即是和V0相连,但是不在T中的边的集合
E2=E(T)\E0,
E2即是T中不和V0相连的边的集合
A={(+a,-b)| a∈E1,b∈E2}
设ω(a’)-ω(b’)=min{ω(a)-ω(b)| (+a,-b)∈A},则T + a’-b’是G的一个最小k+1度限制生成树。

即最小k+1度限制生成树属于最小k度限制生成树的邻集。


假设我们已经得到了最小p度限制生成树,
如何通过它来求最小p+1度限制生成树呢?

由定理可知最小p+1度限制生成树属于最小p度生成树的邻集,因此它可以通过枚举最小p度生成树上的一次可行交换求得。

为了使v0的度增加,枚举的可行交换中必须有一条边与v0关联。

如图,假设我们已经得到了v0点度为2时的最小生成树,现在要求v0度为3时的最小生成树。
这里写图片描述
我们枚举于V0关联且不在树上边,分别添加到树上,例如:
这里写图片描述
为了使V0的度增加,图中两条边红色的边是不能删除的

删去边的权值越大,所得到的生成树的权值和就越小,因此,需要找到环上可删除的权值最大的边并将其删除。
这里写图片描述
简单的枚举 但 时间复杂度非常高!!!

造成时间复杂度高的主要原因是有大量的重复计算

用动态规划可以避免大量重复计算

设最小p度限制生成树为T,T是无根树,为了简便,我们把v0作为该树的根。
定义Father(v)为T中v的父结点,Father(v0)无意义。
设Best(v)为路径v0->v上与v0无关联且权值最大的边。

Best(v)的状态转移方程为
Best(v)=max(Best(Father(v)),ω(Father(v),v))

边界条件为
Best[v0]=-∞,Best[v’]=-∞|(v0,v’)∈E(T)。
因为每次寻找的是最大边,所以-∞不会被考虑

状态总共|V|个,而状态转移的时间复杂度为O(1),因而总的时间复杂度是O(V),即通过最小p度限制生成树求最小p+1度限制生成树的时间复杂度是O(V)。

具体实现:从V0开始做一遍搜索即可。
问题:最先求几度的最小度限制生成树呢?即p从多少开始求?

因为求最小k度限制生成树,当 k < DG(v0) 时,问题并不总是有解的。如图。
这里写图片描述
k≤2,不存在k度限制生成树

将v0从图中删去,图中将会出现3个连通分量
这里写图片描述
而这3个连通分量必须通过v0来连接,k<3无解

所以如果将v0去掉,图会被分成m个连通支,那么就先求最小m度限制生成树。

如何求最小m度限制生成树

1)我们可以先删去v0,对各个连通分量求最小生成树。

2)再在每个连通分量中找最小边和v0相连。

3)得到最小m度限制生成树。

时间复杂度分析

求最小m度生成树:O(VlogV+E);

最多k次从p度到p+1度的递推:k*O(V);

总复杂度:O(VlogV+E+kV);

模板题,poj1639

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <map>
#include <algorithm>
using namespace std;
const int INF = ~0u>>2;
const int N = 50;
int father[N];
int g[N][N];
bool flag[N][N];
map<string, int> num;
int n, k, cnt, ans;

struct Node{
    int x, y, v;
    friend bool operator < (Node n1, Node n2){
        return n1.v < n2.v;
    }
}a[1010];
Node dp[N];
//dp(v)为路径v0—v上与v0无关联且权值最大的边; 
int find(int x){
    if (x != father[x]){
        father[x] = find(father[x]);
    }
    return father[x];
}

void kruskal(){  
    int i;
    for (i = 1; i <= n; i++){
        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;
        father[x] = y;
        ans += a[i].v;
    }
}


void dfs(int x, int pre){  //dfs求1到某节点路程上的最大值
    int i;
    for (i = 2; i <= cnt; i++){
        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;
    memset(flag, false, sizeof(flag));
    memset(g, -1, sizeof(g));
    num["Park"] = 1;
    for (int i = 0; i < N; i++){
        father[i] = i;
    }
}
int main(){
#ifndef ONLINE_JUDGE
    freopen("1.txt", "r", stdin);
#endif
    int i, j, v;
    string s;
    scanf("%d", &n);
    init();
    for (i = 1; i <= n; i++){
        cin >> s;
        if (num.find(s) == num.end()){
            num[s] = ++cnt;
        }
        a[i].x = num[s];
        cin >> s;
        if (num.find(s) == num.end()){
            num[s] = ++cnt;
        }
        a[i].y = 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(v, g[a[i].x][a[i].y]);
        }
    }
    scanf("%d", &k);
    int set[N], Min[N];
    for (i = 0; i < N; i++){
        Min[i] = INF;
    }
    sort(a+1, a+n+1);
    kruskal();
    for (i = 2; i <= cnt; i++){  //寻找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; i <= cnt; i++){  //把1跟这些连通块连接起来
        if (Min[i] != INF){
            m++;
            flag[1][set[i]] = flag[set[i]][1] = true;
            ans += g[1][set[i]];
        }
    }
    for (i = m+1; i <= k; i++){
        memset(dp, -1, sizeof(dp));
        dp[1].v = -INF;
        for (j = 2; j <= cnt; j++){
            if (flag[1][j]){
                dp[j].v = -INF;
            }
        }
        dfs(1, -1);
        int tmp, Min = INF;
        for (j = 2; j <= cnt; j++){
            if (g[1][j] != -1){
                if (Min > g[1][j] - dp[j].v){ 
            //找到一条dp到连通块中某个点的边,替换原来连通块中的边
                    Min = g[1][j]-dp[j].v;
                    tmp = j;
                }
            }
        }
        int x = dp[tmp].x, y = dp[tmp].y;
        flag[1][tmp] = flag[tmp][1] = true;
        flag[x][y] = flag[y][x] = false;
        ans += Min;
    }
    cout << "Total miles driven: " << ans << endl;
    return 0; 
}

本文参考国家集训队论文,

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值