斯坦纳树
1.算法分析
算法描述:
在一个图中,有若干个关键点,将这几个关键点连在一起的最小花费,就是斯坦纳树问题
不同的题目会有不同的限制,比如求 斯坦纳树 ,比如求 斯坦纳森林(需要对斯坦纳树在进行一次状压)
1.1 斯坦纳树
状态表示: f [ i ] [ s t a t e ] f[i][state] f[i][state]表示以i为根的,关键点状态为state的最小花费
状态转移:
-
枚举连通状态的子集:
权值在边上: 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}
-
枚举树上边进行松弛: 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] !&