题目链接:https://codeforces.com/problemset/problem/1353/E
题意:
- 给你一个长度为n的01串s, 和一个正整数k。
- 操作:每次操作将串中的0变成1,或者将串中的1变成0
- 问:至少需要操作几次,才能使得传中所有相邻1下标之差等于k?(如果只有一个1或者没有1 也满足)
思路:
- 原问题:至少操作几次,才能使得1 ~ n(整个串)所有相邻’1‘下标之差等于k。
- 子问题:至少操作几次,才能使得1 ~ i 的子串所有相邻’1‘下标之差等于k。
- dp[i][0]表示将第i位变成’0‘,使得1~i的子串满足条件,dp[i][1]表示将第i位变成’1‘,使得1~i的子串满足条件。
- sum[i]前缀和,表示1到i 有多少个‘1’。
- 将第i位变成‘0‘,不会影响1 ~ i子串的合法性,只需要继承上一个状态较小值就好了,即min(dp[i-1][0],dp[i-1][1])。
- 将第i位变成’1‘,两种合法情况。
- 上一个’1‘的位置在第i - k位,那么就是i - k + 1到i - 1的‘1’都要改成‘0’,并且将第i-k位也改成’1‘,即sum[i-1]-sum[i-k+1] + dp[i - k][1]
- i之前没有’1‘,那么就是1到i-1的’1‘都要改成’0‘,即sum[i-1]
- 转移方程:
- dp[i][0] = min(dp[i -1][0], dp[i - 1][1]) + (a[i] == '1');//如果当前是’1‘,就要修改为’0‘,修改次数+1
- dp[i][1] = min(sum[i-1], dp[i-k][1] + sum[i-1] - sum[i-k]) + (a[i] == '0);//如果当前是’1‘,就要修改成‘1’,修改次数+1
- 最后答案就是dp[n][0]和dp[n][1]之中较小者。
ac代码:
#include <bits/stdc++.h>
#define LL __int128
#define ll long long
#define sc(a) scanf("%d", &a)
#define sc2(a, b) scanf("%d%d", &a, &b)
#define sc3(a, b, c) scanf("%d%d%d", &a, &b, &c)
#define scl(a) scanf("%lld", &a)
#define scl2(a, b) scanf("%lld%lld", &a, &b)
#define ss(a) scanf("%s", a)
#define mem(a, b) memset(a, b, sizeof(a))
#define ios ios::sync_with_stdio(false);cin.tie(0);cout.tie(0)
#define PII pair<int, int>
using namespace std;
const int maxn = 1e6 + 5;
char a[maxn];
int dp[maxn][2], sum[maxn];
int main(){
int t, n, k;
sc(t);
while(t --){
sc2(n, k);
ss(a + 1);
for(int i = 1; i <= n; i ++){
dp[i][0] = dp[i][1] = 0;
sum[i] = sum[i - 1] + a[i] - '0';
}
for(int i = 1; i <= n; i ++){
dp[i][0] = min(dp[i - 1][0], dp[i - 1][1]) + (a[i] == '1');
dp[i][1] = min(sum[i - 1], dp[max(i - k, 0)][1] + sum[i - 1] - sum[max(i - k, 0)]) + (a[i] == '0');
//当i-k<0时,表示i之前就不存在‘1’了,可以当他是0,对答案没有影响.
}
printf("%d\n",min(dp[n][0], dp[n][1]));
}
return 0;
}
/*
6
9 2
010001010
9 3
111100000
7 4
1111111
10 3
1001110101
1 1
1
1 1
0
1
2
5
4
0
0
*/