#include #include using namespace std; using namespace atcoder; #define rep(i, n) REP(i, 0, n) #define REP(i, s, e) for (int i = (s); i < (int)(e); i++) #define repr(i, n) REPR(i, n, 0) #define REPR(i, s, e) for (int i = (int)(s - 1); i >= (int)(e); i--) #define all(r) r.begin(), r.end() #define rall(r) r.rbegin(), r.rend() typedef long long ll; typedef vector vi; typedef vector vl; template T chmax(T& a, const U& b) { if (a >= b) return false; a = b; return true; } template T chmin(T& a, const U& b) { if (a <= b) return false; a = b; return true; } void yes_no(bool f, string yes = "Yes", string no = "No") { cout << (f ? yes : no) << "\n"; } const long long INF = 1e18; // オーバーフローしない十分大きな値 /** * @brief Dreyfus-Wagner法によるシュタイナー木の最小コスト計算 * * @param V 頂点数 * @param terminals ターミナルとなる頂点のリスト (0-indexed) * @param d グラフの隣接行列表現 (d[u][v]: 辺のコスト, 辺がない場合はINF, d[i][i] = 0) * @return long long シュタイナー木の最小コスト */ long long steiner_tree(int V, const vector& terminals, const vector>& d) { int K = terminals.size(); if (K == 0) return 0; if (K == 1) return 0; // 1. 全点対最短経路をWarshall-Floyd法で求める vector> dist = d; for (int k = 0; k < V; ++k) { for (int i = 0; i < V; ++i) { if (dist[i][k] == INF) continue; for (int j = 0; j < V; ++j) { if (dist[k][j] == INF) continue; dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]); } } } // 2. DPテーブルの初期化 // dp[S][v] : ターミナルの部分集合 S を含み、頂点 v で接続されるシュタイナー木の最小コスト vector> dp(1 << K, vector(V, INF)); // サイズが 1 の部分集合の初期化 for (int i = 0; i < K; ++i) { for (int v = 0; v < V; ++v) { dp[1 << i][v] = dist[terminals[i]][v]; } } // 3. DPの更新 (Sの要素数が少ない順に計算される) for (int S = 1; S < (1 << K); ++S) { // S の要素数が 1 の場合はすでに計算済みのためスキップ if (!(S & (S - 1))) continue; // a. 部分木同士の結合 // S の真部分集合 T をビット演算で高速に列挙するテクニック for (int T = (S - 1) & S; T > 0; T = (T - 1) & S) { for (int v = 0; v < V; ++v) { if (dp[T][v] != INF && dp[S ^ T][v] != INF) { dp[S][v] = min(dp[S][v], dp[T][v] + dp[S ^ T][v]); } } } // b. 他の頂点を経由する場合の緩和 (最短経路を用いて拡張) for (int v = 0; v < V; ++v) { if (dp[S][v] == INF) continue; for (int u = 0; u < V; ++u) { if (dist[v][u] == INF) continue; dp[S][u] = min(dp[S][u], dp[S][v] + dist[v][u]); } } } // 4. 全てのターミナルを含む最小コストを取得 // どの頂点 v を起点としてもよいので、その中での最小値を探す long long ans = INF; for (int v = 0; v < V; ++v) { ans = min(ans, dp[(1 << K) - 1][v]); } return ans; } void solve() { int n, m, t; cin >> n >> m >> t; vector d(n, vl(n, INF)); using T = tuple; vector es; rep(i, m) { int a, b, c; cin >> a >> b >> c; --a; --b; d[a][b] = c; d[b][a] = c; es.emplace_back(c, a, b); } rep(i, n) d[i][i] = 0; vector v(t); rep(i, t) cin >> v[i], --v[i]; if (t < 15) { ll ans = steiner_tree(n, v, d); cout << ans << "\n"; } else { ll ans = INF; sort(all(es)); vi a; rep(i, n) { bool f = true; rep(j, t) if (v[j] == i) f = false; if (f) a.emplace_back(i); } int sz = a.size(); rep(ng, 1 << sz) { vi use = v; rep(i, sz) if ((ng & (1 << i)) == 0) use.emplace_back(a[i]); vi rev(n, -1); int tot = use.size(); rep(i, tot) rev[use[i]] = i; dsu uf(n); ll tmp = 0; for (auto&& [w, x, y] : es) { if (uf.same(x, y)) continue; if (rev[x] == -1 || rev[y] == -1) continue; uf.merge(x, y); tmp += w; } if (uf.size(v[0]) == tot) chmin(ans, tmp); } cout << ans << "\n"; } } int main() { cin.tie(0); ios::sync_with_stdio(false); int t = 1; // multi-testcase // cin >> t; rep(ti, t) solve(); return 0; }