http://codeforces.com/problemset/problem/883/D
これは考察系のDP。
場合分けでtrivialな場合は別で処理します。
非自明(Pが2つ以上ある)の時、まず二分探索して、距離Kすすめるとき*をすべて覆えるか?という問題を解くことを考えます。
dp(i):=i番目のPを使った時、*をカバーできる範囲が[0, l)だとして、lの最大値とします。
するとi+1番目のPがi番目の時点でカバーできているのなら、区間は必ず右に伸ばせばいいです。
カバーできてないとすると、[dp(i),P(i+1)の区間をカバーするのはi+1番目のP、またはi+2番目のPとなります。
なぜなら、i+k番目(k>=3)のPでその区間をカバーしたとすると、i+k-1番目のPは右に伸ばす場合が、dp(i+k)の候補になりえます。しかしi+k-2以下は右に伸ばすのも左に伸ばすのも自由になり、i+1番目のPでカバーしても問題ありません。
なのでdp(i)を更新するためにはdp(i-1)とdp(i-2)の値のみだけが必要で、dp(N)で求められます。
全部でO(NlogN)となり十分間に合います。
O(N)個参照しないといけないと見せて実はO(1)で済む系DPですね。
http://codeforces.com/contest/853/problem/D(解いてないけど)
とかもこれと同じ系統です。
int N, M; string str; int minv, maxv; int P[1000010]; int S[1000010]; int dp[1000010]; bool exist(int l, int r) { return ((r > l) && (S[r] - S[l])); } bool C(int m) { rep(i, 0, M + 1) dp[i] = 0; rep(i, 1, M + 1) { int e = P[i - 1] + 1; if(!exist(dp[i - 1], e - m - 1)) MAX(dp[i], e); if(!exist(dp[i - 1], e)) MAX(dp[i], e + m); if(i != 1) { int f = P[i - 2] + 1; if(!exist(dp[i - 2], e - m - 1)) MAX(dp[i], f + m); } } // debug(m, vi(dp, dp + M + 1)); return dp[M] > maxv; } void solve() { cin >> N >> str; M = 0; rep(i, 0, N) { if(str[i] == 'P') P[M++] = i; S[i + 1] = S[i] + (str[i] == '*'); } minv = inf; maxv = -inf; rep(i, 0, N) { if(str[i] != '*') continue; MIN(minv, i); MAX(maxv, i); } if(M == 1) { int p = P[0]; int lc = S[p], rc = S[N] - S[p]; if(lc > rc) { cout << lc << " " << p - minv << "\n"; } else if(lc < rc) { cout << rc << " " << maxv - p << "\n"; } else { //lc == rc cout << lc << " " << min(maxv - p, p - minv) << "\n"; } return; } C(2); int lv = 0, rv = N; while(rv - lv > 1) { //minimize int m = (lv + rv) / 2; if(C(m)) rv = m; else lv = m; } cout << S[N] << " " << rv << "\n"; }