Official

E - 冒険者の旅路 / The Adventurer's Journey Editorial by physics0523


まず、各都市間の最短距離 \(d(u,v)\)ワーシャルフロイド法 などで予め求められます。

既に訪れている都市をもう一度目的地とする意味はないので、最適な行動は以下のように構成できます。

  • 現在いる都市 \(u\) から最短距離でまだ訪れていない都市 \(v\) へと移動することを繰り返す。

訪れる都市の順番を最適に指定することで、最適解を得ることができます。
そこで、以下の 巡回セールスマン問題型の bitDP を考えます。

\(dp[\) 既に通った都市の集合 \(S\ ][\) 現在いる都市 \(k\ ]=\{\) 残存する最大 HP \(\}\)

  • \(dp[\{1\}][1]=F+R_1\) から始めます。
  • \(dp[S][u]\) から次の都市 \(v\) に到着する時点で HP が \(0\) 以上とすることができれば、すなわち \(dp[S][u]-d(u,v) \ge 0\) であれば、 \(dp[S \cup \{v\}][v]\)\(dp[S][u]-d(u,v)+R_v\) を遷移させます。

この DP 中に直接移動できない都市間の遷移が入ることがありますが、その間に新たに訪れる都市があったとしても回復が遅れて損をするだけで得をすることはないので、問題ありません。(損した場合は、適切に訪れる順番が指定される DP の遷移の道筋によってカバーされます。)

時間計算量は \(O(N^3 + 2^N N^2)\) です。

実装例 (C++):

#include<bits/stdc++.h>

using namespace std;

int main(){
  int N,M,F;
  cin >> N >> M >> F;
  vector<int> R(N);
  for(auto &nx : R){cin >> nx;}
  vector<vector<int>> d(N,vector<int>(N,1e9));
  for(int i=0;i<N;i++){d[i][i]=0;}
  for(int i=0;i<M;i++){
    int U,V,W;
    cin >> U >> V >> W;
    U--; V--;
    d[U][V]=min(d[U][V],W);
    d[V][U]=min(d[V][U],W);
  }

  for(int k=0;k<N;k++){
    for(int i=0;i<N;i++){
      for(int j=0;j<N;j++){
        d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
      }
    }
  }

  int res=-1;
  vector<vector<int>> dp(1<<N,vector<int>(N,-1));
  dp[1][0]=F+R[0];
  for(int i=0;i<(1<<N);i++){
    for(int j=0;j<N;j++){
      if(dp[i][j]<0){continue;}
      if(j==N-1){
        res=max(res,dp[i][j]);
      }
      for(int k=0;k<N;k++){
        if((i&(1<<k))==0){
          if(dp[i][j]-d[j][k]>=0){
            dp[i|(1<<k)][k]=max(dp[i|(1<<k)][k],dp[i][j]-d[j][k]+R[k]);
          }
        }
      }
    }
  }
  cout << res << "\n";
  return 0;
}

posted:
last update: