kurarrr's memooo

主に競プロ備忘録

Typical DP Contest F - 準急

問題概要

ある路線には駅 1 から駅 N までの N 個の駅がある。
準急は、駅 1 に止まり、{駅 2, ..., 駅 N-1} の部分集合に止まり、駅 N に止まる。
連続する K個以上の駅に止まらないような組み合わせは何通り考えられるか、mod 109+7で求めよ。
2≤K≤N≤106

解法

駅に止まる→1 止まらない→0とすると、1がK個以上連続しないN個のbit列を考えれば良い。
1がK個以上連続しない条件を満たすためには、 i個目に1を置こうとした時、最後に0を置いたのがi-K+2,..,i-1のどこかであり、 i個目に0を置こうとした時、最後に0を置いたのがi-K+1,i-K+2,..,i-1のどこかであれば良い。
すると、dp[i]:=(最後に0を置いたのがi番目であるようなビット列)と考えれば重複なく数え上げられ、漸化式も立てられることがわかる。
この時、1番目、N番目には1を置かなければならないのでdp[1]=dp[N]=0。 また0番目には0があるとして、dp[0]=1とする。
漸化式はdp[i] = sum(i-K+1<=j<=i-1)(dp[j]) (i != 1,N) となる。これを累積和で処理するとO(N)。
i=N+1まで計算すれば、dp[N+1]はN+1に0を置く場合の数で、dp[N]=0としているからN番目には1が置かれていることになる。
よってdp[N+1]は答えと一致する。

メモ

最初O(N2)のdpを考えたがオーダーを落とせなかった。
意外とO(N)の解法を最初から考えた方がよかったかもしれない。

コード

#include<stdio.h>
#include<iostream>
#include<string>
#include<vector>
#include<map>
#include<set>
#include<list>
#include<queue>
#include<deque>
#include<algorithm>
#include<utility>
#include<memory>
#include<cmath>
#include<stack>
#include<tuple>
#include<numeric>
#include<cassert>

#define ALL(g) (g).begin(),(g).end()
#define REP(i, x, n) for(int i = x; i < n; i++)
#define rep(i,n) REP(i,0,n)
#define EXIST(s,e) ((s).find(e)!=(s).end())
#define pb push_back
#define DEBUG false

using namespace std;

using ll = long long;
using P = pair<int,int>;
using Pl = pair<int,ll>;
using Pd = pair<double,double>;
using T = tuple<double,double,int>;

const int mod=1e9+7,INF=1<<30;
const double EPS=1e-12,PI=3.1415926535897932384626;
const ll LINF=1LL<<60;
const int MAX_N = 1000006, MAX_C = 102;
const ll lmod = 1e9+7;

ll sum[MAX_N],dp[MAX_N];

int main(){
  int N,K; cin >> N >> K ;
  sum[1] = dp[0] = 1LL;
  dp[1] = dp[N] = 0LL;
  // sum[i] := [0,i)のdpの和
  REP(i,1,N+2){
    if(i!=1 && i!=N) dp[i] = (sum[i] - sum[max(0,i-K)] + lmod) % lmod;
    sum[i+1] = (sum[i] + dp[i] + lmod) % lmod;
  }
  cout << dp[N+1] << endl;
  return 0;
}