私が質問を正しく理解していれば、少し考えただけで、動的プログラミングでさえ必要ないことがわかるでしょう。解決策はまったく簡単です。
これは私が理解している質問です: 0 と 1 の配列 a[1]..a[M] が与えられ、S kという形式の操作が許可されます。ここで、S kは N 個の要素 a を反転することを意味します。 [k]、a[k+1]、...a[k+N-1]。これは明らかに 1≤k≤M-N+1 に対してのみ定義されています。
これらの一連の操作 S kを実行することにより、すべて 0 またはすべて 1 の状態に到達する必要があります。両方を別々に解き、小さい方の数を取ります。したがって、それらをすべて 0 にしたいとします (他の場合、すべて 1 は対称です)。
重要な考え方は、操作 S kを 2 回以上実行したくない(2 回実行することは、まったく実行しないことと同じです) ということと、操作の順序は重要ではないということです。したがって、問題は、実行する操作のサブセットを決定することだけであり、これは簡単に決定できます。[1] を見てください。0 の場合、 S 1を実行しないことがわかります。それ以外の場合は、S 1を実行する必要があることがわかっているため(これは a[1] を反転する唯一の操作であるため)、実行します — これにより、すべてのビットが a[1] から a[N] に切り替わります。次に a[2] を見てください (この操作の後)。1か0かでS2をするかどうかがわかるか否か。などなど — 実行する操作の数 (およびどれ) を線形時間で決定できます。
編集: C++ タグがあるため、疑似コードを C++ コードに置き換えました。醜いコードで申し訳ありません。「コンテスト モード」のときは、コンテストの習慣に戻ります。:-)
#include <iostream>
using namespace std;
const int INF = 20000000;
#define FOR(i,n) for(int i=0,_n=n; i<_n; ++i)
int flips(int a[], int M, int N, int want) {
int s[M]; FOR(i,M) s[i] = 0;
int sum=0, ans=0;
FOR(i,M) {
s[i] = (a[i]+sum)%2 != want;
sum += s[i] - (i>=N-1?s[i-N+1]:0);
ans += s[i];
if(i>M-N and s[i]!=0) return INF;
}
return ans;
}
int main() {
int M = 6, N = 3;
int a[] = {1, 0, 0, 1, 0, 0};
printf("Need %d flips to 0 and and %d flips to 1\n",
flips(a, M, N, 0), flips(a, M, N, 1));
}