如果我正确理解了这个问题,稍微思考一下就会让你相信,即使是动态编程也没有必要——解决方案完全是微不足道的。
这是我理解的问题:给你一个 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超过一次(执行两次等同于根本不执行),并且操作的顺序无关紧要。所以问题只是确定您执行的操作的哪个子集,这很容易确定。看[1]。如果它是 0,那么你知道你不会执行 S 1。否则,你知道你必须执行 S 1(因为这是唯一会翻转 a[1] 的操作),所以执行它 - 这会将所有位从 a[1] 切换到 a[N]。现在看 a[2](在这个操作之后)。看是1还是0,就知道会不会执行S 2或不。依此类推——您可以确定在线性时间内执行多少操作(以及执行哪些操作)。
编辑:用 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));
}