ABC180 E Traveling Salesman among Aerial Cities をPythonで解く【Atcoder】

問題はこちらです

atcoder.jp

この問題は典型的なbitDPで解けます。
以下の解説は都市を0-indexで扱うため、例えば都市1->都市0としています。
ここで、S := 訪れた都市の集合。2進数に変換したとき 1 の都市を訪れている と定義します。
例えば、S = 3_{10} = 0011_2のときは下から0番目と1番目が 1 なので、都市0と都市1を訪れたことになります。
この集合Sを使って、dpテーブルは以下のように定義できます。
dp[S][v] := 訪れた都市の集合が S で最後に訪れた都市が v であるときの最小コスト
遷移は集合 S に対し、まだ訪れていない都市 v と最後に訪れた都市 u を決めると、以下のようになります。
dp[S|(1<<v)][v] = min(dp[S|(1<<v)][v], dp[S][u]+都市 u から都市 v へのコスト)
S|(1<<v) はSに新たに下から v 番目を 1 にしたものを表します。
例えば、S = 4 = 0100_2のとき v = 1 とすると、4|(1<<1) は 0110_2 となり、新たに都市 1 を訪れたことになります。
この遷移を適切な v と u を選んで行うと、都市0 から全ての都市を1度通って各都市へ移動する最小コストが求められます。
最終的に、(都市0 から各都市へ移動する最小コスト + 最後に各都市から都市0へ戻るコスト)の最小値が答えになります。
わかりにくい解説になってしまいましたが、コードを見て少しでも理解していただけると嬉しいです。

N = int(input())
P = list()
for _ in range(N):
  x,y,z = map(int, input().split())
  P.append((x,y,z))
POW = pow(2,N)
dp = [[inf]*(N+1) for _ in range(POW)]
dp[1][0] = 0 # 都市1からスタート
for S in range(POW):
  for v in range(N): # 次訪れる点
    if (S>>v)&1==1: continue # v を既に訪れている場合
    for u in range(N): # 最後に訪れた点
      if (S>>u)&1==0 and S!=0: continue # u をまだ訪れていない場合(まだ訪れていない頂点からは遷移できない)
      dp[S|(1<<v)][v] = min(dp[S|(1<<v)][v], dp[S][u]+abs(P[v][0]-P[u][0])+abs(P[v][1]-P[u][1])+max(0,P[v][2]-P[u][2]))

ans = inf
for v in range(N):
  ans = min(ans, dp[POW-1][v]+abs(P[0][0]-P[v][0])+abs(P[0][1]-P[v][1])+max(0,P[0][2]-P[v][2]))
print(ans)