Official

D - 点検スケジュール / Inspection Schedule Editorial by physics0523


点検区間として両端が整数であるものだけを考えれば十分です。(両端が非整数なる点検区間は両端が整数となる点検区間に適切に移動できます)

座標を \(1\) ずつ進めながら、以下の動的計画法を考えます。

  • \(dp[S][l] = \{\) 既に点検区間と接触したイベント会場の集合が \(S\) であり、あと距離 \(l\) 以内に点検区間を開始しなければならないという状態が取れるなら \(1\) 、取れないなら \(0\) \(\}\)

点検を行わないなら \(dp[S][l-1]\) へ、行うなら \(S\) を (\(S'\) に)更新した上で \(dp[S'][K+M-1]\) に遷移するイメージです。
ところが、これでは実行時間制限に間に合いません。
競技プログラミングにおいて、 DP の value (値) が \(0/1\) であるとき、何らかの key (添え字) を取り除ける場合が往々にしてあります。

今回は、以下のように DP を変更できます。

  • \(dp[S] = \{\) 既に点検区間と接触したイベント会場の集合が \(S\) である場合の、次の点検区間の開始点までの距離の最大値 \(\}\)

直感的には、 value をある種の「余命」として、その最大値を維持していく感じです。
詳細な遷移は読者への課題とします。

こうすることで、 DP を高速化することができました。
実装の際、点検区間が道路の区域 \([0,T]\) をはみ出せないことに注意してください。

本解法の時間計算量は \(O(2^NT)\) です。

実装例 (C++):

#include<bits/stdc++.h>

using namespace std;
using pi=pair<int,int>;

bool hit(pi l,pi r){
  if(l.second<=r.first){return false;}
  if(r.second<=l.first){return false;}
  return true;
}

int main(){
  int T,N,K,M;
  cin >> T >> N >> K >> M;
  vector<pi> seg(N);
  for(auto &nx : seg){
    cin >> nx.first >> nx.second;
  }
  vector<int> dp(1<<N,-1e9);
  dp[0]=M;
  for(int pos=0;pos<T;pos++){
    vector<int> ndp(1<<N,-1e9);
    int fl=0;
    pi cur={pos,pos+K};
    for(int i=0;i<N;i++){
      if(hit(cur,seg[i])){fl|=(1<<i);}
    }
    for(int i=0;i<(1<<N);i++){
      if(dp[i]>0){
        ndp[i]=max(ndp[i],dp[i]-1);
      }
      if(dp[i]>=0 && pos+K<=T){
        ndp[i|fl]=max(ndp[i|fl],K+M-1);
      }
    }
    dp=ndp;
  }
  
  int ans=1e9;
  for(int i=0;i<(1<<N);i++){
    if(dp[i]>=0){
      ans=min(ans,__builtin_popcount(i));
    }
  }
  cout << ans << "\n";
  return 0;
}

posted:
last update: