题意:你被给予了一个n个灯的花圈,灯的状态由长度为n的字符串组成,字符串s的第i个字母为'0',表示这个灯关闭,为'1',表示这个灯开着。你被给予了一个正整数k。
每一个操作,你可以选择一个灯泡,并且改变它的状态。
一个周期为k的花圈如果是合法的,意味着每一对相邻的开着的灯泡的距离为k,考虑k = 3的情况,"00010010"是合法的。
分析:题目中要求每一对开着的灯泡的距离为k,但是这个花圈的左右两边可以有超过k个灯泡是关闭,只要中间连续的开的灯泡是合法的即可,比如"00010010"是合法的,只要它出现的连续的开着的灯泡是距离为k的。如何用最少的dp状态覆盖整个问题呢?比如出现前面多余k的关闭的灯泡的情况,
所以我们要定义两个状态来组合这些情况。
f[i][0]:前i项合法,第i位为0的最少操作次数。//这个状态能覆盖左右两端多余k个关闭的灯泡的状态
//因此它可以从f[i - 1][0]转移过来,右端是多个0,也可以从f[i - 1][1]转移过来,第i个位置的灯泡可以关闭
它的状态转移方程为f[i][0] = min(f[i - 1][0],f[i - 1][1]) + (s[i] == 1)
f[i][1]:前i - 1项合法,第i位为1的最少操作次数。//如何考虑这个状态覆盖的情况
前i项合法,第i位为1的操作次数,它可以覆盖左边全是关闭的灯的状态,从sum[i - 1]转移过来,即前面灯泡都要关闭,也可以从f[i - k][1]转移过来,
再从f[i - k][1]转移过来的时候,中间的灯泡是需要关闭的,即sum[i - 1] - sum[i - k]。
//仔细思考下,f[i][0]从f[i - 1][0]转移的时候,是可以覆盖右边很多个关闭灯泡的情况的
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1000005;
const int INF = 0x3f3f3f3f;
char g[N];
int a[N], sum[N];
int f[N][2];
int main()
{
int t;
scanf("%d", &t);
while (t--)
{
int n, k;
scanf("%d%d", &n, &k);
scanf("%s", g + 1);
for (int i = 1; i <= n; ++i)
{
if (g[i] == '1') a[i] = 1;
else a[i] = 0;
}
for (int i = 1; i <= n; ++i)
{
sum[i] = sum[i - 1] + a[i];
}
for (int i = 1; i <= n; ++i)
{
int p = max(0, i - k);
f[i][0] = min(f[i - 1][0], f[i - 1][1]) + (a[i] == 1);
f[i][1] = min(sum[i - 1], f[p][1] + sum[i - 1] - sum[p]) + (a[i] == 0);
}
printf("%d
", min(f[n][0], f[n][1]));
for (int i = 0; i <= n; ++i) f[i][0] = f[i][1] = 0;
}
return 0;
}