Skip to content

Commit c41ccde

Browse files
committed
atcoder B23 - Traveling Salesman Problem bitDP
1 parent 378256e commit c41ccde

6 files changed

Lines changed: 855 additions & 0 deletions

File tree

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
// 以下は、**Go 1.20.6** を用いた巡回セールスマン問題(TSP)の解法です。
2+
// ビットDPを用いて、全都市を一度ずつ訪問し出発地点に戻る最短距離を求めます。
3+
4+
// ---
5+
6+
// ## ✅ Go実装(main.go)
7+
8+
// ```go
9+
package main
10+
11+
import (
12+
"bufio"
13+
"fmt"
14+
"math"
15+
"os"
16+
"strconv"
17+
"strings"
18+
)
19+
20+
// Point 座標を表す構造体
21+
type Point struct {
22+
x int
23+
y int
24+
}
25+
26+
// computeDistances は各都市間のユークリッド距離を前計算して返します。
27+
//
28+
// 引数:
29+
// coords []Point: 都市の座標
30+
//
31+
// 戻り値:
32+
// [][]float64: dist[i][j] := 都市iから都市jへの距離
33+
func computeDistances(coords []Point) [][]float64 {
34+
N := len(coords)
35+
dist := make([][]float64, N)
36+
for i := 0; i < N; i++ {
37+
dist[i] = make([]float64, N)
38+
for j := 0; j < N; j++ {
39+
dx := float64(coords[i].x - coords[j].x)
40+
dy := float64(coords[i].y - coords[j].y)
41+
dist[i][j] = math.Hypot(dx, dy)
42+
}
43+
}
44+
return dist
45+
}
46+
47+
// solveTSP はビットDPを使って巡回セールスマン問題の最短距離を返します。
48+
//
49+
// 引数:
50+
// N int: 都市の数 (2 <= N <= 15)
51+
// coords []Point: 都市の座標
52+
//
53+
// 戻り値:
54+
// float64: 最短経路の長さ(誤差1e-3未満)
55+
func solveTSP(N int, coords []Point) float64 {
56+
const INF = 1e18
57+
dist := computeDistances(coords)
58+
59+
dp := make([][]float64, 1<<N)
60+
for i := range dp {
61+
dp[i] = make([]float64, N)
62+
for j := range dp[i] {
63+
dp[i][j] = INF
64+
}
65+
}
66+
dp[1][0] = 0.0 // 都市0のみ訪問している状態
67+
68+
for s := 1; s < (1 << N); s++ {
69+
for u := 0; u < N; u++ {
70+
if (s>>u)&1 == 0 {
71+
continue
72+
}
73+
for v := 0; v < N; v++ {
74+
if (s>>v)&1 != 0 {
75+
continue
76+
}
77+
ns := s | (1 << v)
78+
if dp[ns][v] > dp[s][u]+dist[u][v] {
79+
dp[ns][v] = dp[s][u] + dist[u][v]
80+
}
81+
}
82+
}
83+
}
84+
85+
// 全都市訪問後、都市0へ戻る
86+
res := INF
87+
full := (1 << N) - 1
88+
for u := 1; u < N; u++ {
89+
if cost := dp[full][u] + dist[u][0]; cost < res {
90+
res = cost
91+
}
92+
}
93+
return res
94+
}
95+
96+
// main は標準入力を読み取り、TSPの最短距離を出力します。
97+
func main() {
98+
scanner := bufio.NewScanner(os.Stdin)
99+
scanner.Scan()
100+
N, _ := strconv.Atoi(scanner.Text())
101+
102+
coords := make([]Point, N)
103+
for i := 0; i < N; i++ {
104+
scanner.Scan()
105+
fields := strings.Fields(scanner.Text())
106+
x, _ := strconv.Atoi(fields[0])
107+
y, _ := strconv.Atoi(fields[1])
108+
coords[i] = Point{x, y}
109+
}
110+
111+
result := solveTSP(N, coords)
112+
fmt.Printf("%.12f\n", result)
113+
}
114+
115+
// ## ✅ 実行方法
116+
117+
// ```bash
118+
// go run main.go < input.txt
119+
// ```
120+
121+
// ### 入力例(input.txt):
122+
123+
// ```
124+
// 4
125+
// 0 0
126+
// 0 1
127+
// 1 0
128+
// 1 1
129+
// ```
130+
131+
// ### 出力:
132+
133+
// ```
134+
// 4.000000000000
135+
// ```
136+
137+
// ---
138+
139+
// ## 📊 処理量とメモリの見積もり
140+
141+
// | 指標 | 値 |
142+
// | -------- | ------------------------------- |
143+
// | 時間計算量 | `O(N^2 * 2^N)`(N=15で約50万回) |
144+
// | 空間使用量 | 約 `N * 2^N * 8 Byte` ≒ 最大 4 MiB |
145+
// | 浮動小数誤差対策 | `math.Hypot` と `%.12f` で安全な出力 |
146+
147+
// ---
148+
149+
// ## ✅ この実装の特徴
150+
151+
// * `math.Hypot(dx, dy)` による安全で精度の高い距離計算
152+
// * `[]float64` による省メモリなDPテーブル
153+
// * 都市数 N=15 まで十分高速に対応
154+
// * 誤差 1e-3 未満の出力精度を保証(`fmt.Printf("%.12f")`)
155+
156+
// ---
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// 以下は、Node.js (18.16.1) 環境で動作する、\*\*巡回セールスマン問題(TSP)\*\*をビットDPで解く実装です。
2+
// 距離は **ユークリッド距離**で計算し、すべての都市を訪れて元の都市に戻る最短距離を求めます。
3+
4+
// ```javascript
5+
// Node.js (v18.16.1) における TSP の解法(ビットDP)
6+
// 実行時間制限: 10秒、メモリ制限: 1024MiB
7+
8+
const fs = require('fs');
9+
10+
/**
11+
* 都市巡回セールスマン問題を解く関数
12+
* @param {number} N - 都市の数
13+
* @param {[number, number][]} coords - 都市の座標配列 (X, Y)
14+
* @returns {number} - 最短距離(絶対/相対誤差10^-3未満で正解)
15+
*/
16+
function solveTSP(N, coords) {
17+
const dist = Array.from({ length: N }, () => Array(N).fill(0));
18+
19+
// 距離を前計算
20+
for (let i = 0; i < N; i++) {
21+
for (let j = 0; j < N; j++) {
22+
const dx = coords[i][0] - coords[j][0];
23+
const dy = coords[i][1] - coords[j][1];
24+
dist[i][j] = Math.hypot(dx, dy); // √(dx² + dy²)
25+
}
26+
}
27+
28+
const INF = Infinity;
29+
const dp = Array.from({ length: 1 << N }, () => Array(N).fill(INF));
30+
31+
// 初期位置を全都市として開始(0都市からスタート)
32+
dp[1][0] = 0;
33+
34+
for (let s = 1; s < (1 << N); s++) {
35+
for (let u = 0; u < N; u++) {
36+
if (!(s & (1 << u))) continue;
37+
for (let v = 0; v < N; v++) {
38+
if (s & (1 << v)) continue;
39+
const ns = s | (1 << v);
40+
dp[ns][v] = Math.min(dp[ns][v], dp[s][u] + dist[u][v]);
41+
}
42+
}
43+
}
44+
45+
// 最後に戻る距離を加える
46+
let res = INF;
47+
for (let u = 1; u < N; u++) {
48+
res = Math.min(res, dp[(1 << N) - 1][u] + dist[u][0]);
49+
}
50+
51+
return res;
52+
}
53+
54+
// 入力処理
55+
(function main() {
56+
const input = fs.readFileSync('/dev/stdin', 'utf8').trim().split('\n');
57+
const N = parseInt(input[0], 10);
58+
const coords = input.slice(1).map(line => line.split(' ').map(Number));
59+
60+
const result = solveTSP(N, coords);
61+
62+
// 絶対誤差・相対誤差が 1e-3 未満になるように出力(12桁精度)
63+
console.log(result.toFixed(12));
64+
})();
65+
66+
// ### ✅ 解説
67+
68+
// * **計算量**: `O(N^2 * 2^N)`(N=15 でも問題なし)
69+
// * **距離の計算**: `Math.hypot(dx, dy)` を使うと精度良くユークリッド距離を計算できます。
70+
// * **メモリ使用量**: 約 `N * 2^N * 8 byte`(最大約 4 MiB 程度)
71+
// * **誤差対策**: `toFixed(12)` で小数第12位まで出力
72+
73+
// ### ✅ 実行例(入力: `input.txt` を指定)
74+
75+
// ```bash
76+
// $ node main.js < input.txt
77+
// 4.000000000000
78+
// ```
79+
80+
// 他の改善(例えば枝刈りやメモ化DFS)もありますが、今回の制約ならこのビットDPが最適です。必要があれば経路復元付きの実装も可能です。
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
<!-- 以下は、**PHP 8.2.8** を用いた「巡回セールスマン問題(TSP)」の解法です。
2+
ビットDP(状態:訪問済み都市集合×現在地)により、**全都市を訪問して出発地に戻る最短距離**を求めます。
3+
4+
---
5+
6+
## ✅ 実装(PHP 8.2.8、型・コメント付き)
7+
8+
<?php
9+
10+
declare(strict_types=1);
11+
12+
/**
13+
* 距離行列を計算する
14+
*
15+
* @param array<int, array{int, int}> $coords 各都市の座標 [(x1, y1), ..., (xN, yN)]
16+
* @return array<int, array<int, float>> 距離行列 dist[i][j] = 都市iからjへの距離
17+
*/
18+
function computeDistances(array $coords): array {
19+
$N = count($coords);
20+
$dist = array_fill(0, $N, array_fill(0, $N, 0.0));
21+
for ($i = 0; $i < $N; $i++) {
22+
for ($j = 0; $j < $N; $j++) {
23+
$dx = $coords[$i][0] - $coords[$j][0];
24+
$dy = $coords[$i][1] - $coords[$j][1];
25+
$dist[$i][$j] = hypot($dx, $dy);
26+
}
27+
}
28+
return $dist;
29+
}
30+
31+
/**
32+
* 巡回セールスマン問題を解く
33+
*
34+
* @param int $N 都市の数 (2 <= N <= 15)
35+
* @param array<int, array{int, int}> $coords 各都市の座標
36+
* @return float 最短距離(誤差1e-3以内)
37+
*/
38+
function solveTSP(int $N, array $coords): float {
39+
$dist = computeDistances($coords);
40+
$INF = INF;
41+
42+
// dp[s][u] := 訪問済み集合s, 現在地u のときの最短距離
43+
$dp = array_fill(0, 1 << $N, array_fill(0, $N, $INF));
44+
$dp[1][0] = 0.0;
45+
46+
for ($s = 1; $s < (1 << $N); $s++) {
47+
for ($u = 0; $u < $N; $u++) {
48+
if (!(($s >> $u) & 1)) continue;
49+
for ($v = 0; $v < $N; $v++) {
50+
if (($s >> $v) & 1) continue;
51+
$ns = $s | (1 << $v);
52+
$dp[$ns][$v] = min($dp[$ns][$v], $dp[$s][$u] + $dist[$u][$v]);
53+
}
54+
}
55+
}
56+
57+
$res = $INF;
58+
for ($u = 1; $u < $N; $u++) {
59+
$res = min($res, $dp[(1 << $N) - 1][$u] + $dist[$u][0]);
60+
}
61+
62+
return $res;
63+
}
64+
65+
/**
66+
* 標準入力から座標情報を読み取り、solveTSP を呼び出すメイン関数
67+
*/
68+
function main(): void {
69+
[$N] = sscanf(trim(fgets(STDIN)), "%d");
70+
$coords = [];
71+
72+
for ($i = 0; $i < $N; $i++) {
73+
[$x, $y] = sscanf(trim(fgets(STDIN)), "%d %d");
74+
$coords[] = [$x, $y];
75+
}
76+
77+
$result = solveTSP($N, $coords);
78+
79+
// 小数点以下12桁で出力(誤差1e-3対策)
80+
printf("%.12f\n", $result);
81+
}
82+
83+
main();
84+
85+
// ## 🧪 実行方法
86+
87+
// ```bash
88+
// php main.php < input.txt
89+
// ```
90+
91+
// 例: `input.txt`
92+
93+
// ```
94+
// 4
95+
// 0 0
96+
// 0 1
97+
// 1 0
98+
// 1 1
99+
// ```
100+
101+
// 出力:
102+
103+
// ```
104+
// 4.000000000000
105+
// ```
106+
107+
// ---
108+
109+
// ## 📊 計算量と制約への対応
110+
111+
// | 項目 | 内容 |
112+
// | -------- | ------------------------------ |
113+
// | 時間計算量 | `O(N^2 * 2^N)`(N≦15で約50万) |
114+
// | 空間使用量 | `N * 2^N * float` ≒ 約4MiB |
115+
// | 精度 | `hypot` + `printf("%.12f")` 使用 |
116+
// | PHPバージョン | 8.2.8(型指定、INF、hypot利用可) |
117+
118+
// ---
119+
120+
// ## ✅ 特徴
121+
122+
// * 型コメント・戻り値明示(`array{int, int}` 型タプル風)
123+
// * 安全で高速な `hypot()` による距離計算
124+
// * 浮動小数誤差に強い出力形式(12桁)
125+
// * 計算量・メモリ制限に完全準拠(N ≦ 15)
126+
127+
// ---
128+
129+
// ご希望があれば、**経路復元付き実装** や **部分メモ化付きDFS版** なども提供可能です。お気軽にどうぞ!

0 commit comments

Comments
 (0)