$\newcommand{\O}{\mathrm{O}}$
最小シュタイナー木のコストを効率よく求めるアルゴリズム.
シュタイナー木とはターミナル点(要求点)の集合を含むような木(もとのグラフの部分グラフ)のことである. このうちコストが最小のものを最小シュタイナー木という.
岩田さんのスライド を参考にさせていただいた.
元論文は "The steiner problem in graphs" [Dreyfus Wagner 1971]
以下の実装では元論文とは異なり, Warshall Floyd を行わず, DP 遷移を Dijkstra 法を用いて更新することでターミナル数 $t$ に関して $\O(n 3^t + (n+m) 2^t \log n )$ の時間計算量で求めている(FPT). グラフは巨大だがターミナル数が小さいという場合にこの改良が効いてくる.
重みなし最小シュタイナー木問題の場合高速メビウス変換を用いて $O^{\ast}(2^t)$ に計算量が落ちるみたい(参考).
(関数)
add_edge$(u, v, cost)$ : 費用が $cost$ の辺 $(u,v)$ を追加する
solve$(terminal)$ : 頂点集合 $terminal$ をターミナル点の集合とするような最小シュタイナー木のコストを返す.
時間計算量: $\O (n 3^t + (n + m) 2^t \log n)$
template<typename T> class SteinerTree { private: struct edge { int to; T cost; }; int V; vector<vector<edge> > G; vector<vector<T> > dp; const T inf = numeric_limits<T>::max() / 10; using pti = pair<T, int>; public: SteinerTree(int node_size) : V(node_size), G(V){} void add_edge(int u, int v, T cost){ G[u].push_back((edge){v, cost}), G[v].push_back((edge){u, cost}); } T solve(vector<int>& terminal){ int t = (int)terminal.size(); if(t == 0) return (T)0; dp.resize((1 << t), vector<T>(V, inf)); for(int i = 0; i < t; i++){ dp[(1 << i)][terminal[i]] = 0; } for(int i = 1; i < (1 << t); i++){ for(int j = 0; j < V; j++){ for(int k = i; k > 0; k = (k - 1) & i){ dp[i][j] = min(dp[i][j], dp[k][j] + dp[i ^ k][j]); } } if(i == (1 << t) - 1) break; priority_queue<pti, vector<pti>, greater<pti> > que; for(int j = 0; j < V; j++){ que.push(make_pair(dp[i][j], j)); } while(!que.empty()){ pti p = que.top(); que.pop(); int v = p.second; if(dp[i][v] < p.first) continue; for(auto& e : G[v]){ if(dp[i][e.to] > dp[i][v] + e.cost){ dp[i][e.to] = dp[i][v] + e.cost; que.push(make_pair(dp[i][e.to], e.to)); } } } } return dp[(1 << t) - 1][terminal[0]]; } };
AOJ : Modern Announce Network 提出コード (t = 3 の場合のみ)