P6192 【模板】最小斯坦納樹 題解

下蛋爷發表於2024-08-30

Description

給定一個包含 \(n\) 個結點和 \(m\) 條帶權邊的無向連通圖 \(G=(V,E)\)

再給定包含 \(k\) 個結點的點集 \(S\),選出 \(G\) 的子圖 \(G'=(V',E')\),使得:

  1. \(S\subseteq V'\)

  2. \(G'\) 為連通圖;

  3. \(E'\) 中所有邊的權值和最小。

你只需要求出 \(E'\) 中所有邊的權值和。

\(1\leq n\leq 100,\ \ 1\leq m\leq 500,\ \ 1\leq k\leq 10,\ \ 1\leq u,v\leq n,\ \ 1\leq w\leq 10^6\)

Solution

求出的圖一定是樹,因為如果有環刪掉環邊一定更優。

假設已經求出了這個樹,那麼對於樹上的每個點 \(u\),可以記錄 \(S\) 表示 \(u\) 的子樹出現的關鍵點集合。利用這個集合可以在圖上模擬選樹的兒子的過程。

\(f_{i,S}\) 表示圖上與 \(i\) 連通的樹包含 \(S\) 集合對應的關鍵點的最小邊權和,可以得到兩種轉移:

  1. \(i\) 在樹上的兒子有 \(\geq 2\) 個或者 \(i\) 為關鍵點,則兒子的集合一定小於 \(S\) 且互不相交,所以可以直接列舉兒子的集合然後求並就是答案。
  2. \(i\) 在樹上只有一個兒子且 \(i\) 不為關鍵點,轉移一定形如 \(f_{i,S}\leftarrow f_{j,S}+w\),跑 dijkstra 即可。

對於第一種轉移實現上有一些細節,如果每次是先列舉 \(S\)\(i\) 再列舉 \(i\) 的鄰域的狀態合併,這樣單次就是 \(O(3^{|S|})\)。正確做法是直接讓 \(f_{i,S}\leftarrow f_{i,S-T}+f_{i,T}\),因為 \(<S\) 的答案已經確定。這樣做轉移一的總時間複雜度就是 \(O(3^kn)\) 了。

容易發現這麼 dp 不會出現不合法的情況,並且最優解一定會被算到。

時間複雜度:\(O(3^kn+2^km\log m)\)

Code

#include <bits/stdc++.h>

// #define int int64_t

const int kMaxN = 105, kMaxS = (1 << 10);

int n, m, k;
int f[kMaxN][kMaxS], id[kMaxN];
std::vector<std::pair<int, int>> G[kMaxN];

void dijkstra(int s) {
  static bool vis[kMaxN];
  std::priority_queue<std::pair<int, int>> q;
  for (int i = 1; i <= n; ++i)
    q.emplace(-f[i][s], i), vis[i] = 0;
  for (; !q.empty();) {
    int u = q.top().second; q.pop();
    if (vis[u]) continue;
    vis[u] = 1;
    for (auto [v, w] : G[u]) {
      if (f[v][s] > f[u][s] + w) {
        f[v][s] = f[u][s] + w;
        q.emplace(-f[v][s], v);
      }
    }
  }
}

void solve() {
  memset(f, 0x3f, sizeof(f));
  for (int i = 1; i <= n; ++i) f[i][id[i] ? (1 << (id[i] - 1)) : 0] = 0;
  for (int s = 0; s < (1 << k); ++s) {
    for (int i = 1; i <= n; ++i) {
      for (int t = s; t; t = (t - 1) & s)
        f[i][s] = std::min(f[i][s], f[i][s ^ t] + f[i][t]);
    }
    dijkstra(s);
  }
}

void dickdreamer() {
  std::cin >> n >> m >> k;
  for (int i = 1; i <= m; ++i) {
    int u, v, w;
    std::cin >> u >> v >> w;
    G[u].emplace_back(v, w), G[v].emplace_back(u, w);
  }
  for (int i = 1; i <= k; ++i) {
    int x;
    std::cin >> x;
    id[x] = i;
  }
  solve();
  int ans = 1e9;
  for (int i = 1; i <= n; ++i) ans = std::min(ans, f[i][(1 << k) - 1]);
  std::cout << ans << '\n';
}

int32_t main() {
#ifdef ORZXKR
  freopen("in.txt", "r", stdin);
  freopen("out.txt", "w", stdout);
#endif
  std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
  int T = 1;
  // std::cin >> T;
  while (T--) dickdreamer();
  // std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
  return 0;
}

相關文章