Codeforces 660C

题目链接:http://codeforces.com/problemset/problem/660/C

题意:

给你一个长度为 $n$ 的 $01$ 串 $a$,记 $f(a)$ 表示其中最长的一段连续 $1$ 的长度。

现在你最多可以将串中的 $k$ 个 $0$ 变成 $1$,求操作后的 $f(a)$。

题解:

(说实话这道题不看tag我不一定能想得出来……)

首先考虑二分枚举答案,对于一个假定的 $f(a)=x$,我们需要判断能不能满足:

用 $dp[i]$ 表示 $a[i-x+1], cdots, a[i]$ 的这一段上 $1$ 的数目,那么只要存在一个 $dp[i] + k ge x$ 就代表这个答案 $f(a)=x$ 是可行的。

另外,考虑到二分的边界问题比较难处理,故在区间被缩得比较小的时候可以改用暴力枚举。

同时,考虑到还有输出的问题,可以在让二分的check(mid)函数不是单纯返回 $0,1$,还可以再返回一个数代表答案子串的位置。

AC代码:

#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> P;
#define mp(x,y) make_pair(x,y)
#define fi first
#define se second
const int maxn=3e5+10;
int n,k;
int a[maxn];
int f[maxn];
P check(int x)
{
    int mx=0, p=x;
    for(int i=1;i<=x;i++) f[i]=f[i-1]+a[i], mx=max(mx,f[i]);

    for(int i=x+1;i<=n;i++)
    {
        f[i]=f[i-1]-a[i-x]+a[i];
        if(f[i]>mx) mx=f[i], p=i;
    }
    return mp(mx+k>=x,p);
}
int main()
{
    cin>>n>>k;
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);

    int l=k, r=n, ans, pos;
    while(l<=r)
    {
        if(r-l<=5)
        {
            for(int mid=r;mid>=l;mid--)
            {
                P res=check(mid);
                if(!res.fi) continue;
                ans=mid, pos=res.se;
                break;
            }
            break;
        }
        int mid=(l+r)/2;
        if(check(mid).fi) l=mid;
        else r=mid;
    }

    cout<<ans<<endl;
    for(int i=1;i<=pos-ans;i++) printf("%d ",a[i]);
    for(int i=pos-ans+1;i<=pos;i++) printf("1 ");
    for(int i=pos+1;i<=n;i++) printf("%d ",a[i]);
}
原文地址:https://www.cnblogs.com/dilthey/p/10473904.html