公式

E - 散歩とバリケード / A Walk and Barricades 解説 by physics0523


バリケードの条件を整理しましょう。
まず、 \(X_i=0\) なるバリケードが存在すれば座標 \(0\) から動けないため、答えは \(0\) です。以降、このようなケースを取り除いて考えます。

\(\sum |D_i| \le 5 \times 10^4\) という制約があるので、高橋君が動ける座標は \([-50000,50000]\) の範囲内であることがわかります。
このことから、この範囲外のバリケードは存在しないものとして考えて構いません。
また、重要なのは座標 \(0\) に正・負それぞれで最も近いバリケードです。それらが高橋君の動ける座標の範囲を支配します。

以下の動的計画法 (DP) を考えます。
\(dp[\) 高橋君の座標 \(x] = \{\) その時点で座標 \(x\) に高橋君が存在できるか \(\}\)
この DP を回すことができればこの問題に正解できます。
高橋君の存在しうる座標の範囲の大きさを \(R=10^5+1\) としたとき、時間計算量 \(O(NR)\) の DP は存在することがわかります。
このままでは実行時間制限に間に合いません。(部分和問題を含んでいるため、ここから劇的に高速化することも難しそうです。)
どうすればよいでしょうか。

DP が持つものがブール値なので、 bitset と呼ばれるデータ構造でこの DP を \(w\) 倍に高速化できます。( \(w\) はワードサイズと呼ばれる定数で、現代のコンピュータでは \(w=64\) 程度の定数であるとみなすことができます)
bitset といっても、ビット演算ができる長い bit 列であると考えればよいです。

今回の DP では、 DP テーブルを bitset として保持し、遷移を以下のようにかければよいです。

  • \(D_i > 0\) のとき、元の DP テーブルと \(D_i\) だけ左シフトした DP テーブルとの OR を取る。
    • \(dp[i+D_i]\)\(dp[i]\) を遷移させる行為に対応する。
  • \(D_i < 0\) のとき、元の DP テーブルと \(-D_i\) だけ右シフトした DP テーブルとの OR を取る。
    • \(dp[i-D_i]\)\(dp[i]\) を遷移させる行為に対応する。
  • その後、高橋君が存在できる範囲を表現した bitset との AND を取る。
    • \(dp\) の範囲外に出てしまった遷移を取り除く。

時間計算量 \(O(NR/w)\) であれば額面で \(3 \times 10^8\) 程度であり、間に合うことがわかります。

実装例 (C++):

#include<bits/stdc++.h>

using namespace std;

int main(){
  int N,M;
  cin >> N >> M;
  vector<int> D(N);
  for(auto &nx : D){cin >> nx;}
  int low=-50005,high=50005;
  int ofs=50016;
  bool x0=false;
  for(int i=0;i<M;i++){
    int X;
    cin >> X;
    if(X==0){x0=true;}
    if(X<0){low=max(low,X+1);}
    if(X>0){high=min(high,X-1);}
  }
  if(x0){
    cout << "0\n";
    return 0;
  }

  bitset<100032> bs,mask;
  for(int i=0;i<100032;i++){
    int act=i-ofs;
    if(low<=act && act<=high){mask[i]=1;}
  }
  bs[ofs]=1;

  for(int i=0;i<N;i++){
    if(D[i]>0){bs|=(bs<<D[i]);}
    else{bs|=(bs>>(-D[i]));}
    bs&=mask;
  }
  for(int i=100031;i>=0;i--){
    if(bs[i]){
      cout << i-ofs << "\n";
      return 0;
    }
  }
  return 0;
}

投稿日時:
最終更新: