中国配達人問題
Chinese Postman Problem | Aizu Online Judge
// // オイラー路とは、グラフ(グラフ理論)の全ての辺を1度だけ通る路のこと。 // 全ての辺を1度だけ通る閉路は、オイラー閉路という。 // グラフGの辺をすべて通るようなオイラー閉路を持つグラフのことをオイラーグラフという。 // またGの辺をすべて通るような、閉路でないオイラー路を持つグラフのことを準オイラーグラフ // という。 // // オイラーグラフと準オイラーグラフは、一筆書き可能である。 // 連結グラフ G に対して次が成り立つ。 // // * Gがオイラーグラフ ⇔ Gの全ての頂点の次数が偶数 // * Gが準オイラーグラフ ⇔ Gの頂点のうち、次数が奇数であるものがちょうど2つ // class chinise_postman_problem { public: void solve(void) { int V, E; cin>>V>>E; vector<vector<pair<int,int>>> tree(V); REP(i, E) { int s, t, d; cin>>s>>t>>d; tree[s].emplace_back(t,d); tree[t].emplace_back(s,d); } // 全辺のコスト // 少なくとも一度は全ての辺を移動するのでそれにかかるコスト ll total = 0; // // オイラー閉路がある場合はそれで終了。 // 出て行く辺の数が奇数の頂点(奇数点)を巡る最小コストを加えればよい。 // vector<int> odds; REP(v, V) { for (const auto &e : tree[v]) total += e.second; // 奇点を集める if (tree[v].size() % 2 == 1) odds.push_back(v); } // 無効グラフなので重複分割る total /= 2; int n = odds.size(); const int inf = (1<<30); // 奇数点から別の奇数点へ移動するときのコスト vector<vector<int>> weight(n,vector<int>(n,inf)); // 出て行く辺が奇数の頂点を巡回 // O(V*E*log(V)) REP(u, n) { int s = odds[u]; vector<int> dist(V, inf); dist[s] = 0; priority_queue<pair<int,int>> que; // dijkstra que.emplace(s, 0); while (!que.empty()) { int v, d; tie(v, d) = que.top(); que.pop(); if ( dist[v] < d ) continue; for (const auto &e : tree[v]) { int to, cost; tie(to, cost) = e; if ( dist[v] + cost < dist[to] ) { dist[to] = dist[v] + cost; que.emplace(to, dist[to]); } } } REP(v, n) weight[u][v] = dist[odds[v]]; } int N = (1<<n); // グラフにいくつか辺(パス)を追加してオイラー閉路を完成させる // どのパスを追加するのかに dp を使って最小値を計算する vector<int> best(N, inf); best[0] = 0; // O(2^V*V^2) for (int S = 0; S < N; ++S) { for (int v = 0; v < n; ++v) { if ( (S & (1<<v)) ) continue; for (int u = v+1; u < n; ++u) { if ( (S & (1<<u)) ) continue; best[S|(1<<v)|(1<<u)] = min(best[S|(1<<v)|(1<<u)], best[S]+weight[u][v]); } } } cout<<total+best[N-1]<<endl; } };