CF1353E 【K-periodic Garland】

题目大意

给你一个长度为 \(n\)\(01\) 字符串,要求让这个字符串的每个 \(1\) 字符之间的距离恰好都为 \(k\) ,问至少要修改几个字符。

思路

显然这是道 dp 题。

  • \(dp_{i ,0}\) 表示到第 \(i\) 个字符为止,只让第 \(i\) 位为 \(1\),其余 \(i-1\) 位均位 \(0\) 时需要的操作次数。

  • \(dp_{i,1}\) 表示到第 \(i\) 个字符为止,让第 \(i\) 位为 \(1\),且保证在这之前合法所需要的最少操作次数。

那显然,只要维护一个前缀和 \(sum_i\) ,统计到第 \(i-1\) 个字符位置 \(1\) 的数量,那么:

\[dp_{i,0}=\begin{cases}sum_{i-1}\ \ \ \ \ \ (s_i=1)\\sum_{i-1}+1\ \ \ \ \ \ (s_i=0)\end{cases} \]

Ps: \(s_i\) 表示字符串的第 \(i\) 位所表示的字符。

同时,我们也很容易推出另外一个转移式:

\[\mathop{dp_{i,1}}\limits_{i-k\ge1}=\begin{cases}\min(dp_{i-k,1},dp_{i-k,0}) + sum_{i-1}-sum_{i-k} \ \ \ \ \ \ (s_i=1)\\\min(dp_{i-k,1},dp_{i-k,0}) + sum_{i-1}-sum_{i-k}+1 \ \ \ \ \ \ (s_i=0)\end{cases} \]

这很好理解,只要保证第 \(i-k\) 个位置为 \(1\) 并将 \((i-k+1)\sim (i-1)\) 中的所有 \(1\) 都设为 \(0\) 即可,这步同样可以用前缀和维护。

最后计算答案的时候,将 \(dp\) 数组从后往前扫一遍,每次在 \(dp_{i,0}\;,\;dp_{i,1}\) 中取 \(\min\) 。在扫的同时维护一个 \(num\) 表示从 \((i+1) \sim n\) 这段中 \(1\) 的数量,统计答案的时候要加上这个 \(num\),意味着吧 \((i+1) \sim n\) 中所有的 \(1\) 设为 \(0\),防止出现不合法的情况。

时间复杂度:\(\text{O}(n)\)

Code

#include<bits/stdc++.h>

using namespace std;

int read()
{
	int ans=0,f=1;
	char c=getchar();
	while(c>'9'||c<'0'){if(c=='-')f=-1;c=getchar();}
	while(c>='0'&&c<='9'){ans=ans*10+c-'0';c=getchar();}
	return ans*f;
}

const int N=1e6+5,inf=0x7f7f7f7f;
int t,n,k,dp[N][2],sum[N],ans;
char s[N]; 

int main()
{
	memset(dp,0x7f,sizeof(dp));
    // 将 dp 数组初始化,将所有可能先设为不合法
	t=read();
	while(t--)
	{
		n=read();k=read();
		scanf("%s",s+1);
		for(int i=1;i<=n;++i)
        // 统计前缀和
			sum[i]=sum[i-1]+(s[i]-'0');
        ans=sum[n];  // 这里的意思是把答案初始化为:把所有的 1 都变成 0 所需要的操作次数
		for(int i=1;i<=n;++i)
		{
			dp[i][0]=sum[i-1];
            // dp[i][0] 的转移
			if(i-k>=1)
				dp[i][1]=min(dp[i-k][1]+sum[i-1]-sum[i-k],dp[i-k][0]+sum[i-1]-sum[i-k]);
            // dp[i][1] 的转移
			if(s[i]=='0')
			{
				if(dp[i][0]!=inf)
					dp[i][0]++;
				if(dp[i][1]!=inf)
					dp[i][1]++;
			}
				
		}
		int num=0;
        // 统计答案的时候维护一个 num,防止出现不合法情况
		for(int i=n;i>=1;--i)
		{
			ans=min(ans,min(dp[i][1]+num,dp[i][0]+num));
			if(s[i]=='1')
				num++;
		}
			
		for(int i=1;i<=n;++i)
        // 这是这题的一个坑点,直接 memset 会 T,必须手动清空
		{
			sum[i]=0;
			dp[i][0]=inf;
			dp[i][1]=inf;
		} 
		printf("%d\n",ans);
	}
	return 0;
} 

posted @ 2020-08-23 19:57  Blackbird137  阅读(74)  评论(0编辑  收藏  举报