Codeforces 268D (164D) Wall Bars

問題

1234からなる長さnの文字列で、
1, 2, 3, 4のうちどれか一つ以上が、

  • 文字列の先頭からh以内に現れ、
  • 直前の同じ数字から常にh以内にもう一度現れ
  • n - h番目以降に現れている

ようなものの数を求めよ。

制約条件

n≦1000
h≦min(30, n)

方針

1, 2, 3, 4が前回に出てきたときの位置を記憶するDP
hより離れてしまった文字はh + 1とかにしておけばよい。


dp[1の距離][2の距離][3の距離][4の距離]とするとTLEなので、
一つも距離h + 1以上のものがないときは、
(一つ目は1に決まっているので)3つの距離のみを覚えるとしたら通った。

ソースコード

const int mod = (int)1e9 + 9;
int n, h;
int dp[2][2][32][32][32];
int step[2][32][32][32][4][4];

void solve(int n, int h){
	int cur = 0, next = 1;
	int ans = 0;
	dp[cur][0][0][0][0] = 1;
	rep(i, n + 1){
		memset(dp[next], 0, sizeof(dp[next]));
		rep(j, 2) rep(a, h + 2) for(int b = a; b < h + 2; b++) for(int c = b; c < h + 2; c++){
			
			int &cnt = dp[cur][j][a][b][c];
			if(i >= n){
				(ans += cnt) %= mod;
				continue;
			}
			if(cnt == 0) continue;
			
			rep(k, 4) if(step[j][a][b][c][k][0] >= 0){
				int *p = step[j][a][b][c][k];
				dp[next][p[0]][p[1]][p[2]][p[3]] += cnt;
				if(dp[next][p[0]][p[1]][p[2]][p[3]] >= mod) dp[next][p[0]][p[1]][p[2]][p[3]] -= mod;
			}
		}
		swap(cur, next);
	}
	cout << ans << endl;
}

int main(){
	cin >> n >> h;
	rep(j, 2) rep(a, h + 2) for(int b = a; b < h + 2; b++) for(int c = b; c < h + 2; c++){
		vi v;
		v.pb(a + 1); v.pb(b + 1); v.pb(c + 1);
		if(j == 0) v.pb(1);
		else v.pb(h + 1);
		
		
		rep(k, 4) v[k] = min(v[k], h + 1);
		rep(k, 4){
			vi nv = v;
			int *p = step[j][a][b][c][k];
			if(v[k] <= h) nv[k] = 0;
			sort(all(nv));
			
			if(nv[0] >= h) p[0] = -1;
			else if(nv[3] < h){
				p[0] = 0;
				p[1] = nv[1];
				p[2] = nv[2];
				p[3] = nv[3];
			}
			else{
				p[0] = 1;
				p[1] = nv[0];
				p[2] = nv[1];
				p[3] = nv[2];
			}
		}
	}
	solve(n, h);
	return 0;
}