(ΦωΦ)<これのつづきですよー。目次はここ。
---
有名なこれをやろう。
「巡回セールスマン問題」
$n$ 個の街と、街と街を結ぶ $m$ 個の道があります。道を通るにはコストがかかります。
ある街から出発し、すべての街をちょうど一度通って帰ってくるルートのうち、コストの和が最小になるものを選んだとき、
そのコストの和の最小値を求めてください。Aizu Online Judge "Traveling Salesman Problem"
$n$ は $15$ 以下。それぞれの道のコストは $1{,}000$ 以下。
「どの街を既に訪ねたか」と「今どの街にいるか」、この2つの状態でやってみよう。
どの街を既に訪ねたかというのは、1: 訪ねた, 0: まだ という $n$ ビットの情報にするのが定石で、
集合に対する演算をビット操作を駆使してやる(蟻本初版pp.143-145)。
とりあえず、
$n$ 個の要素からなる集合 $S$ の全ての部分集合 $T$ | for(int T=0; T<1<<n; T++) |
集合 $S$ に $i$ 番目の要素を入れた(= $i$ 番の街を訪ねたことにする)集合 | S | 1<<i |
集合 $S$ に $i$ 番目の要素が入ってるかどうか調べる(= $i$ 番の街を既に訪ねたかどうか調べる) | if(S>>i & 1) |
これだけは覚えておいて。
(ΦωΦ)<メモ化再帰
#include <iostream>
#include <algorithm>
using namespace std;
const int inf = 987654321;
int n, m;
vector<vector<int>> G;
vector<vector<int>> dp;
int rec(int mask, int v) {
if(dp[mask][v] != -1) { return dp[mask][v]; }
if(mask==(1<<n)-1 && v==0) { return dp[mask][v] = 0; }
int res = inf;
for(int i=0; i<n; i++) {
if(mask>>i & 1) { continue; }
// v番の街からi番の街に行く
res = min(res, rec(mask|(1<<i), i) + G[v][i]);
}
return dp[mask][v] = res;
}
int main(void) {
scanf("%d%d", &n, &m);
G.assign(n, vector<int>(n, inf));
for(int i=0; i<m; i++) {
int p, q, c; scanf("%d%d%d", &p, &q, &c);
G[p][q] = c;
}
dp.assign(1<<n, vector<int>(n, -1));
int res = rec(0, 0);
if(res >= inf) { res = -1; }
printf("%d\n", res);
return 0;
}
(ΦωΦ)<forループ
#include <iostream>
#include <algorithm>
using namespace std;
const int inf = 987654321;
int main(void) {
int n, m; scanf("%d%d", &n, &m);
vector<vector<int>> G(n, vector<int>(n, inf)),
dp(1<<n, vector<int>(n, -1));
for(int i=0; i<m; i++) {
int p, q, c; scanf("%d%d%d", &p, &q, &c);
G[p][q] = c;
}
dp[0][0] = 0;
for(int mask=0; mask<1<<n; mask++) {
for(int v=0; v<n; v++) {
for(int i=0; i<n; i++) {
if(mask>>i & 1) { continue; }
int &a = dp[mask][v],
&b = dp[mask|(1<<i)][i];
if(a == -1) { a = inf; }
if(b == -1) { b = inf; }
b = min(b, a + G[v][i]);
}
}
}
int res = dp[(1<<n)-1][0];
if(res >= inf) { res = -1; }
printf("%d\n", res);
return 0;
}
(ΦωΦ)<つづく