斯坦那樹學習筆記
阿新 • • 發佈:2021-07-07
引子
最小斯坦納樹
大概意思就是一個圖給出 \(k(k \le 10)\) 個關鍵點,要求選出若干條邊使得這 \(k\) 個關鍵點連通,求邊權和的最小值
\(Analysis\)
發現 \(k\) 很小,考慮狀壓 \(dp\)
為得到最優解,我們需要考慮以每個點為根的形態
設 \(f_{i,S}\) 表示以 \(i\) 為根關鍵點的狀態為 \(S\) 時的最優解
那麼兩個轉移
一、 \(f_{i,S} = \min(f_{i,S},f_{i,S0}+f_{i,S1})\)
表示用 \(i\) 這個根將兩個不相交的狀態直接合並在一起
二、 \(f_{i,S} = \min(f_{i,S},f_{k,S}+e(k,i))\)
表示用之前的一個以 \(k\) 為根的狀態連一條邊到 \(i\) 變為以 \(i\) 為根
我們發現第二種轉移無法確定最佳的 \(i,k\) 的順序,即 \(dp\) 用後效性
這是考慮用最短路演算法轉移即可
\(Code\)
#include<cstdio> #include<queue> #include<cstring> #include<iostream> using namespace std; const int N = 105, INF = 0x3f3f3f3f; int n, m, k, g[N][N], key[N], h[N], f[N][1045], vis[N]; struct edge{ int to, nxt, w; }e[N * 10]; inline void add(int u, int v, int w) { static int tot = 0; e[++tot] = edge{v, h[u], w}, h[u] = tot; } queue<int> Q; inline void spfa(int s) { while (!Q.empty()) { int now = Q.front(); Q.pop(); for(int i = h[now]; i; i = e[i].nxt) if (f[e[i].to][s] > f[now][s] + e[i].w) { f[e[i].to][s] = f[now][s] + e[i].w; if (!vis[e[i].to]) vis[e[i].to] = 1, Q.push(e[i].to); } vis[now] = 0; } } inline void Stenir_Tree() { for(int s = 1; s < (1 << k); s++) { memset(vis, 0, sizeof vis); for(int i = 1; i <= n; i++) { for(int t = (s - 1) & s; t; t = (t - 1) & s) f[i][s] = min(f[i][s], f[i][t] + f[i][s ^ t]); if (f[i][s] != INF) vis[i] = 1, Q.push(i); } spfa(s); } } int main() { memset(g, INF, sizeof g), memset(f, INF, sizeof f); scanf("%d%d%d", &n, &m, &k); for(int i = 1, x, y, z; i <= m; i++) scanf("%d%d%d", &x, &y, &z), g[x][y] = g[y][x] = min(g[x][y], z); for(int i = 1; i <= n; i++) for(int j = i + 1; j <= n; j++) if (g[i][j] != INF) add(i, j, g[i][j]), add(j, i, g[i][j]); for(int i = 0; i < k; i++) scanf("%d", &key[i]), f[key[i]][1 << i] = 0; Stenir_Tree(); int ans = INF; for(int i = 1; i <= n; i++) ans = min(ans, f[i][(1 << k) - 1]); printf("%d\n", ans); }