斯坦纳树

斯坦纳树

1.算法分析

算法描述:

在一个图中,有若干个关键点,将这几个关键点连在一起的最小花费,就是斯坦纳树问题
不同的题目会有不同的限制,比如求 斯坦纳树 ,比如求 斯坦纳森林(需要对斯坦纳树在进行一次状压)

1.1 斯坦纳树

状态表示: f [ i ] [ s t a t e ] f[i][state] f[i][state]表示以i为根的,关键点状态为state的最小花费

状态转移:

  1. 枚举连通状态的子集:

    权值在边上: f [ i ] [ s t a t e ] = m i n ( { f [ i ] [ s u b s e t 1 ] + f [ i ] [ s u b s e t 2 ] } ) f[i][state] = min(\{f[i][subset1] + f[i][subset2]\}) f[i][state]=min({ f[i][subset1]+f[i][subset2]})

    权值在点上: f [ i ] [ s t a t e ] = m i n ( { f [ i ] [ s u b s e t 1 ] + f [ i , s u b s e t 2 ] − w i } f[i][state] = min(\{f[i][subset1] + f[i,subset2] - w_i\} f[i][state]=min({ f[i][subset1]+f[i,subset2]wi}

  2. 枚举树上边进行松弛: f [ i ] [ s t a t e ] = m i n ( f [ i ] [ s t a t e ] , f [ j ] [ s t a t e ] + d i s [ i ] [ j ] ) f[i][state] = min(f[i][state], f[j][state] + dis[i][j]) f[i][state]=min(f[i][state],f[j][state]+dis[i][j])

入口:

f [ a l l ] = 0 x 3 f f[all] = 0x3f f[all]=0x3f

f [ i ] [ 1 < < i ] = 0 ( i 为 关 键 点 ) f[i][1<<i] = 0(i为关键点) f[i][1<<i]=0(i)

f [ i ] [ 0 ] = 0 ( i 为 所 有 的 点 ) f[i][0] = 0(i为所有的点) f[i][0]=0(i)

出口: m i n { f [ i ] [ 111...111 ] } min\{f[i][111...111]\} min{ f[i][111...111]}

时间复杂度分析

O(n * 3k + c * E * 2k)
n为点数,E为边数,k为关键点数,c为spfa常数,前一部分为枚举子集的复杂度,后一部分为松弛边的复杂度

1.2 斯坦纳森林

在求完斯坦纳树后多维护一个数组 f 2 [ S ] f2[S] f2[S]表示连通状态为S时的最小代价。

状态转移:

​ 枚举根: f 2 [ S ] = m i n ( { f 2 [ S ] , f [ i ] [ S ] } ) ; f2[S] = min(\{f2[S], f[i][S]\}); f2[S]=min({ f2[S],f[i][S]});

​ 枚举子集: f 2 [ S ] = m i n ( { f 2 [ S ] , f 2 [ s u b s e t 1 ] + f 2 [ s u b s e t 2 ] } ) ; f2[S] = min(\{f2[S], f2[subset1] + f2[subset2]\}); f2[S]=min({ f2[S],f2[subset1]+f2[subset2]});

出口: f 2 [ 11111...11 ] f2[11111...11] f2[11111...11]

2. 算法模板

2.1 斯坦纳树

#include <bits/stdc++.h>

using namespace std;

int const N = 110, K = 10, M = 510 * 2;

int n, m, k, f[N][1 << K];
int e[M], ne[M], h[N], idx, w[M];
queue <int> q; 
bool st[N];

void add(int a, int b, int c) {
   
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}

// spfa枚举树上边进行松弛
void spfa(int S) {
   
    while(q.size()) {
   
        int t = q.front();
        q.pop();
        st[t] = 0;
        for(int i = h[t]; ~i; i = ne[i]) {
   
            int j = e[i];
            if(f[j][S] > f[t][S] + w[i]) {
     // 当前状态下的点的转移
                f[j][S] = f[t][S] + w[i];
                if(!st[j]) {
   
                    st[j] = 1;
                    q.push(j);
                }
            }
        }
    }
}

int main() {
   
    memset(h, -1, sizeof h);
    cin >> n >> m >> k;  // 点数、边数、关键点数
    for(int i = 1; i <= m; ++i) {
   
        int a, b, c;
        cin >> a >> b >> c;
        add(a, b, c), add(b, a, c);
    }
    
    // 初始化
    memset(f, 0x3f, sizeof(f));
    for(int i = 1; i <= k; ++i) {
     // 读入关键点
        int u;
        cin >> u;
        f[u][1 << (i - 1)] = 0;
    }
    for(int i = 1; i <= n; ++i) f[i][0] = 0;  // 初始化

    for(int S = 0; S < (1 << k); ++S) {
     // 枚举状态
        memset(st, 0, sizeof st);  // 初始化spfa的st数组
        for(int i = 1; i <= n; ++i) {
     // 枚举每个点
            for(int T = S & (S - 1); T; T = S & (T - 1))  // 枚举连通状态的子集
                f[i][S] = min(f[i][S], f[i][T] + f[i][T ^ S]);  // 状压dp转移
            if (f[i][S] !&
  • 4
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值