競技プログラミングの鉄則を読んでみて1

柴犬の4年前の写真です。9歳になったばかりのことで、きりっとした顔をしています。
概要
今読んでいる本は、著者 米田優峻氏の「競技プログラミングの鉄則 アルゴリズムと思考力を高める77の技術」です。
カラー刷りで図解も多く、解説も適切で私にとって理解が進む本です。応用問題も多く考えさせられます。
その中で、p143の応用問題「問題 B23」にちょっとてこずり、理解するのに2日程要しましたので自分の理解を記録することにします。
問題は巡回セールスマン問題と呼ばれるものでその内容は次の通りです。
二次元平面上にN(<=15)個の都市があり、それぞれ1からNまで番号が付けられています。
都市 i の座標は(Xi,Yi)です。
ある都から出発し、すべての都市を一度ずつ巡った後、出発した都市に戻ります。
この最短時間を求める。
解答
模範解答は次のようになっています。
#include <iostream>
#include <cmath>
#include <algorithm>
using namespace std;
int N, X[19], Y[19];
double dp[1 << 16][19];
int main() {
// 入力
cin >> N;
for (int i = 0; i < N; i++) cin >> X[i] >> Y[i];
// 配列 dp の初期化
for (int i = 0; i < (1 << N); i++) {
for (int j = 0; j < N; j++) dp[i][j] = 1e9;
}
// 動的計画法(dp[通った都市][今いる都市] となっている)
dp[0][0] = 0;
for (int i = 0; i < (1 << N); i++) {
for (int j = 0; j < N; j++) {
if (dp[i][j] >= 1e9) continue;
// 都市 j から都市 k に移動したい!
for (int k = 0; k < N; k++) {
// 既に都市 k を通っていた場合
if ((i / (1 << k)) % 2 == 1) continue;
// 状態遷移
double DIST = sqrt(1.0 * (X[j] - X[k]) * (X[j] - X[k]) + 1.0 * (Y[j] - Y[k]) * (Y[j] - Y[k]));
dp[i + (1 << k)][k] = min(dp[i + (1 << k)][k], dp[i][j] + DIST);
}
}
}
// 答えを出力
printf("%.12lf\n", dp[(1 << N) - 1][0]);
return 0;
}
分からなかったところ
分からないところ1
int i が i = 0 int j が j = 0 int k が k = 0 のとき dp[1][0] が作られるものの意味。
分からないところ2
dp[(1 << N) - 1][0] が答えになること。
理解したところ
分からないところ1

i の値が 1101101111111 の場合、次に選択できる都市の番号(k)は 10000000 か10000000000に限られます。
最終、都市の番号1は選択済み(i の一桁目が1のため)で決して戻ることはなく、都市の番号10000000か10000000000が最後の到達する都市です。
dp[1111111111111 ][10000000] か dp[1111111111111 ][10000000000] で終了します。
上のことに対して、i の値が 1101101111110 の場合、次に選択できる都市の番号は 0、10000000、10000000000に限られます。

最後、都市の番号1(k=0)で終わるためには、都市の番号10000000か10000000000を通過した後都市の番号1 に行くことです。このとき i の値は 1111111111110 となります。
dp[1111111111110][10000000] か dp[1111111111110 ][10000000000] です。
都市の番号1(k=0)に来た時 i の値が 1111111111111( dp[1111111111111][0])となります。
分からないところ2
dp[(1 << N) – 1][0] が答えになることも理解できました。
解決したので、先に読み進めることにします。