zoukankan      html  css  js  c++  java
  • 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
    ",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
    ",ans);
        }
        return 0;
    }
    
    
  • 相关阅读:
    document.load 和document.ready 两个事件的区别
    undefined和null的区别
    点击返回上一个界面事件
    APP上点击事件时 取消点击的阴影
    MUI控制安卓手机自带的返回键 禁止返回
    css过滤镜实现颜色渐变
    CSS3 Media Queries模板:max-width和min-width
    什么是响应式网页设计?
    实用的CSS3-渐变背景色
    Oracle EBS 报表日期格式问题
  • 原文地址:https://www.cnblogs.com/valk3/p/13284965.html
Copyright © 2011-2022 走看看