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

前言

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


题目链接
在这里插入图片描述
1.题目大意:给出一个长度为 n n n的字符串,现在问最少修改多少次使得所有连续(顺序)出现的 1 1 1的间隔均为 k k k

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

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

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

方法一:前缀和

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

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

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

最后的答案就是 m i n { s u m [ L − 1 ] + s u m [ n ] − s u m [ R ] + ( R − L + 1 ) − ( s u m [ R ] − s u m [ L − 1 ] ) } min\{ sum[L-1]+sum[n]-sum[R]+(R-L+1)-(sum[R]-sum[L-1]) \} min{sum[L1]+sum[n]sum[R]+(RL+1)(sum[R]sum[L1])}

整理一下,即 m i n { s u m [ n ] − 2 ∗ s u m [ R ] + R + [ 2 ∗ s u m [ L − 1 ] − ( L − 1 ) ] } min\{ sum[n]-2*sum[R]+R+[2*sum[L-1]-(L-1)] \} min{sum[n]2sum[R]+R+[2sum[L1](L1)]}

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

方法二: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即可,但是也要像前面前缀和那样思考:

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

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

  • 前一个位置为 1,前一个位置之前的字符串合法至少需要改变的字符个数: d p [ i − 1 ] dp[i - 1] dp[i1]
  • 前一个位置为 0,前一个位置之前的字符串合法至少需要改变的字符个数: p r e f − c u r pref - cur prefcur

所以: d p [ i ] + = m i n ( d p [ i − 1 ] , p r e f − c u r ) dp[i] += min(dp[i - 1], pref - cur) dp[i]+=min(dp[i1],prefcur)

同时需要保证当前位置之后的字符串合法,即将当前位置之后的 1 都变为 0: a l l − p r e f all - pref allpref

所以: a n s = m i n ( a n s , d p [ i ] + a l l − p r e f ) ans = min(ans, dp[i] + all -pref) ans=min(ans,dp[i]+allpref)

代码
#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<<i<<" "<<j<<endl;
    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]);
}*/


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;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值