Codeforces Round #642 (Div. 3) E. K-periodic Garland(思维+前缀和/dp)

前言

花了一天多搞懂一道题目,最后的顿悟真的太舒服了


题目链接
Codeforces Round #642 (Div. 3) E. K-periodic Garland(思维+前缀和/dp)_第1张图片
1.题目大意:给出一个长度为n的字符串,现在问最少修改多少次使得所有连续(顺序)出现的1的间隔均为k

2.如果两个连续出现的1间隔为k,那么中间的字符都是0,也就是说对于所有间隔为k的字符,仅有一组能出现1,其余必须全部为0。那么我们首先将这个字符串分为k组,从下标0开始每k个字符取出为第一组,…,从下标k-1开始每k个字符取出为第二组。也就是相当于每个下标对k取模,余数相同的分为一组

3.考虑上面的k组,显然只有其中一组能出现1,那么其它的都设置为0,这一步需要的次数为整个字符串1的个数减去当前组1的个数

4.但是还没结束,一组字符串中如果能出现1,这里的1必须是连续的1,即形如"000011110000","1111100000"这样的才是合法的,下面有两种方法:

方法一:前缀和

对于一组这样的字符串"001011101000010000"

首先我们对字符取前缀和(统计每个前缀中1的个数),那么考虑已经生成了最终的序列,连续1的区间为[L,R],那么:

  • L之前的字符都为0,即L之前的1都需要变为0,即sum[L-1]
  • R之后的字符都为0,即R之后的1都需要变为0,即sum[n]-sum[R]
  • 区间[L,R]之间的0都需要变为1,即(R-L+1)-(sum[R]-sum[L-1])

最后的答案就是min{ sum[L-1]+sum[n]-sum[R]+(R-L+1)-(sum[R]-sum[L-1]) }

整理一下,即min{ sum[n]-2*sum[R]+R+[2*sum[L-1]-(L-1)] }

那么需要枚举所有的区间吗?显然不是,对于一个区间我们只需固定右端点R,那么L取的是[1,R]之间的[2*sum[L-1]-(L-1)]的最小值

方法二:dp

其实我补题时第一眼也想到了dp,因为我们只需考虑第一个为1和最后一个为1,然后这段区间内在保证合法性的情况下某个数既可以变为0又可以变为1,那么我们从左向右递推,并设置两个状态,具体见下面代码:

int dp[2][maxn];

int solve(){
    int len=res.size();
    int i=1,j=len-1;
    memset(dp,0,sizeof dp);
    while(i<=len-1 && !res[i]) i++;
    while(j>=1 && !res[j]) j--;
    for(int k=i;k<=j;k++){
        if(res[k]){
            dp[1][k]=min(dp[1][k-1],dp[0][k-1]);
            dp[0][k]=dp[0][k-1]+1;
        }else{
            dp[0][k]=dp[0][k-1];
            dp[1][k]=min(dp[1][k-1]+1,dp[0][k-1]+1);
        }
    }
    return min(dp[0][j],dp[1][j]);
}

遗憾的是不知道为什么这个O(n)的dp超时了!

无奈之下去参考了Kanoon博主的文章,实际上一维的dp即可,但是也要像前面前缀和那样思考:

dp[i] 表示当前位置为 1,之前的字符串合法至少需要改变的字符个数
一开始需要判断当前字符是否为 1:dp[i] = (s[i] == ‘1’)

当前位置为 1,之前的字符串合法有两种情况:

  • 前一个位置为 1,前一个位置之前的字符串合法至少需要改变的字符个数:dp[i - 1]
  • 前一个位置为 0,前一个位置之前的字符串合法至少需要改变的字符个数:pref - cur

所以:dp[i] += min(dp[i - 1], pref - cur)

同时需要保证当前位置之后的字符串合法,即将当前位置之后的 1 都变为 0:all - pref

所以:ans = min(ans, dp[i] + all -pref)

代码

#include <set>
#include <map>
#include <stack>
#include <queue>
#include <math.h>
#include <cstdio>
#include <string>
#include <cstring>
#include <sstream>
#include <iostream>
#include <algorithm>
#include <unordered_map>
using namespace std;
#define lowbit(x) (x&(-x))
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> P;
const double eps=1e-8;
const double pi=acos(-1.0);
const int inf=0x3f3f3f3f;
const ll INF=1e18;
const int Mod=1e9+7;
const int maxn=1e6+10;

string s;
vector<int> res;
int a[maxn],d[maxn],sum[maxn];
int dp[maxn];

/*int dp[2][maxn];

int solve(){
    int len=res.size();
    int i=1,j=len-1;
    memset(dp,0,sizeof dp);
    while(i<=len-1 && !res[i]) i++;
    while(j>=1 && !res[j]) j--;
    //cout<


int cal(int cnt){  //dp
    memset(dp,0,sizeof dp);
    int ans=cnt,n=res.size(),pre=0;
    for(int i=0;i<n;i++){
        int cur=res[i];
        pre+=cur;
        dp[i]=1-cur;
        if(i>0) dp[i]+=min(dp[i-1],pre-cur);
        ans=min(ans,dp[i]+cnt-pre);
    }
    return ans;
}

int f(int cnt){  //前缀和
    int len=res.size()-1;
    for(int i=1;i<=len;i++){
        sum[i]=sum[i-1]+res[i];
    }
    int ans=sum[len],pre=0;
    //sum[l-1]+sum[len]-sum[r]+(r-l+1)-(sum[r]-sum[l-1])
    //上面等价于:sum[len]-2*sum[r]+r+[2*sum[l-1]-(l-1)]
    for(int r=1;r<=len;r++){
        ans=min(ans,sum[len]-2*sum[r]+r+pre);
        pre=min(pre,2*sum[r]-r);
    }
    return ans;
}

int main(){
    //freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    int t,n,k;
    cin>>t;
    while(t--){
        cin>>n>>k;
        cin>>s;
        int sum=0;
        for(int i=0;i<k;i++){
            a[i+1]=0;
            res.clear();
            res.push_back(-1);  //为了使区间下标1开始设置无关的数第一个加入
            for(int j=i;j<n;j+=k){
                res.push_back(s[j]-'0');
                if(s[j]=='1') a[i+1]++,sum++;
            }
            //d[i+1]=cal(a[i+1]);
            d[i+1]=f(a[i+1]);
        }
        int ans=sum;
        for(int i=1;i<=k;i++){
            ans=min(ans,sum-a[i]+d[i]);
        }
        cout<<ans<<"\n";
    }
    return 0;
}

你可能感兴趣的:(Codeforces)