E. K-periodic Garland DP | 贪心

E. K-periodic Garland

题意

给出一个长度为 n 的 01 串,现在规定一个串如果相邻两个 1 的位置相隔为 k ,那么这个串就是好串,现在你可以将某个位置的字符翻转,问最少需要多少次可以把这个串变成一个好串?

思路

本来是练习DP的,但是想着想着跑偏了。

好串格式应该是0000001000100010001000000

前面全都是0,中间出现周期,最后也全是0,我们只要求出一个区间[l,r],这个区间的开头和结尾都是 1 ,并且合法即可。

那么我们就可以枚举区间的开头,只要可以O(logn)的求出当前开头以哪个下标结尾最小就可以。

但是想了很久想不到。

看了题解有两种解法:DP和算是思维吧。

DP

dp[i][0]表示前 i 位合法,并且第 i 位为 0

dp[i][1]表示前 i 位合法,并且第 i 位为 1

转移方程如下:

当前 i - 1 项合法时,第 i 项为 0 ,那么前 i 项必定合法

当第 i 项为 1 时,如果要合法,那么前 i-k 项肯定要合法并且第 i-k 项为 1 ,而且\((i-k,i)\)全都是0 ,或者第 i 项为第一个 1 ,代价就是把前 i-1 项中的 1 全部变为0。

代码

#include<bits/stdc++.h>
#define pb push_back
const int N=1e6+10;
const int inf=0x3f3f3f3f;
typedef long long ll;
typedef unsigned long long ull;
using namespace std;

char str[N];
int dp[N][2],pre[N];
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        int n,m;
        scanf("%d%d%s",&n,&m,str+1);
        for(int i=1; i<=n; i++)
        {
            pre[i]=pre[i-1]+(str[i]=='1');
            dp[i][0]=min(dp[i-1][0],dp[i-1][1])+(str[i]=='1');
            dp[i][1]=pre[i-1]+(str[i]=='0');
            if(i-m>0)
                dp[i][1]=min(dp[i][1],dp[i-m][1]+pre[i-1]-pre[i-m]+(str[i]=='0'));
        }
        printf("%d\n",min(dp[n][0],dp[n][1]));
    }
    return 0;
}

思维

我们可以知道好串的两段都有若干个 0,中间部分是周期性的串。

所以我们先可以把所有的 1 变成 0,然后在中间找一个子串使其成周期性,并修改总代价。

每个周期只有一个 1 ,而且他们的位置随着第一个 1 的位置固定而固定,所以我们可以枚举 1 的下标。

每次枚举的时候定义一个变量 cnt ,表示本次要减少的代价

\(str[i]=='1'\) 时,那么本来这个位置不用改变,所以我们要减去把它变为 0 的代价,否则 这个位置应该变成 1 ,加上变为 1 的代价。

每更新一个周期就更新 cnt 和 ans ,其实这样有点类似于最大子段和。

cnt == 0 的时候就是前面遍历过的周期都维持 0 。

(其实这个思想在一道树形DP题中遇到过,当时还是看的kuangbin的博客)

代码

#include<bits/stdc++.h>
#define pb push_back
const int N=1e6+10;
const int inf=0x3f3f3f3f;
typedef long long ll;
typedef unsigned long long ull;
using namespace std;

char str[N];
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        int n,m;
        scanf("%d%d%s",&n,&m,str+1);
        int sum=0,ans=inf;
        for(int i=1;i<=n;i++)
            sum+=(str[i]=='1');
        for(int i=1;i<=m;i++)
        {
            int cnt=0;
            for(int j=i;j<=n;j+=m)
            {
                if(str[j]=='1') cnt--;
                else cnt++;
                cnt=min(cnt,0);
                ans=min(sum+cnt,ans);
            }
        }
        printf("%d\n",ans);
    }
    return 0;
}

posted @ 2020-07-11 18:35  Valk3  阅读(102)  评论(0编辑  收藏  举报