关于字符串(zero)的特殊解法

字符串(zero)
给你一个只包含“0”与“1”的字符串S,你可以对这个字符串进行操作,每次操作将字符串中任意一个字符移动到字符串的任意位置,例如S=“0010”’,你可以将首位的0移动到末尾位置,使得S=“0100”。
现在有Q次询问,每次询问给出一个操作次数上限K,每次询问你需要回答在对字符串S进行不超过K次操作后,字符串最长连续“0”的长度。
输入
第一行一个01字符串S
第二行一个整数Q,代表询问次数
接下来Q行,每行一个数字Ki
输出
输出Q行,每行一个整数,代表对应询问中字符串最长连续“0”的长度
样例输入
0000110000111110
5
1
2
3
4
5
样例输出
5
8
9
9
9
数据规模
对于30%的数据,N≤1,000,Q≤1,000,字符串长度为N
对于100%的数据,N≤1,000,000,N×Q≤20,000,000, Ki≤Q
时限2s,空间256MB

首先分析题目,我们可以知道,题目的大概意思是对于K次移动操作在一个字符串上,可以得到连续的0多少个。
实际上,操作我们只需要分成两类,一类是把1移走(移到字符串的边缘,相当于删除了1),另一类是把0移入一串0中。
并且,由于它的询问个数各不相同,形成的解也各不相关,所以动态规划之类的做法并不容易,那么我们就可以想到,如果在这个字符串中,任意取一段区间[l,r],并且保证这里面的1的个数≤k,那么我们就可以肯定,区间[l,r]中形成的一串0的个数就是其本身0的个数+k次操作-其本身1的个数。
另一方面,我们可以肯定,为了保证取得值更大,无论是l还是r,都应当指向0处,而l和r一个个移动很明显会耗费不少时间。
所以我们可以大胆的建立一个数组x[],对于这样一个数组,我们肯定,一开始读入的字符串中,一开始的连续的1是毫无意义的。
所以,我们可以把初始的1删去,保证一开始的读入是0,并且最后的读入中,读入的1也可以删去。
那么我们就可以得到一个x[n]的数组,这个数组中,我们记录的是一串相同数据的大小。
x[1]是第一串0的个数,x[2]指第一串1的个数。用奇数偶数来区分0或者1。
然后同时建立一个sum[]数组,对于一个值sum[i],表示从一开始到i时0或1的总个数,用奇数偶数来区分是0的个数还是1的个数。
然后我们可以枚举r的位置,因为我们保证r一定指向0的区间,所以每一次跳跃都可以使用+2的方式,l同理。
如果同时枚举l和r,空间复杂度必定大大增加,所以我们可以使用单调队列的方式来优化l,解决题目。
如果不使用单调队列,那么我们就可以使用如下方法。
对于一个初始也是唯一的区间[l,r],在r=r+2后,我们可以知道这一个区间[l,r]的最优解可能还是[l,r],也可能是[r,r],所以我们只需要讨论一次这种情况,询问是否需要将l重新移动到r的位置就好了。
但是如果一个区间[l,r]到一定的程度,其中的1的个数超过了我们所求的k,那么就需要强制的将l向后移动,那么由于没有使用单调队列,我们就需要重新扫一遍[l,r]区间中,求出其中的最优解。
当然,判断它是否为最优解,我们只需要确定比较ans的值就好了。
因为ans=(k+[l,r]中0的个数-[l,r]中1的个数)
所以我们只需要比较,不同的ans的值并且对这个比较式进行化简,就可以对当前的最优情况进行考虑了。

代码如下:

#include <cstdio>

using namespace std;

bool bo=true;
int x[1000000];
int sum[1000000];
int n=1,q;
int ans;
int l,r;

inline void readit()
{
    bool bo=true;
    char c=getchar();
    while (c!='0')
        c=getchar();
    while ((c=='0')||(c=='1'))
    {
        if ((c-'0')!=n%2)
            x[n]++;
        else
        {
            if (n==2)
                sum[1]=x[1];
            if (n>=2)
                sum[n]=sum[n-2]+x[n];
            x[++n]++;
        }
        c=getchar();
    }
    return ;    
}

inline int read()
{
    int x=0;
    char c=getchar();
    while ((c<'0')||(c>'9'))
        c=getchar();
    while ((c>='0')&&(c<='9'))
    {
        x=(x<<3)+(x<<1)+c-'0';
        c=getchar();
    }
    return x;
}

int max(int ,int );
int min(int ,int );
int get0(int ,int );
int get1(int ,int );

int main()
{
    readit();
    if (n%2)
        sum[n]=sum[n-2]+x[n];
    else
        n--;
    q=read();
    for (int i=1;i<=q;i++)
    {

        int k=read();
        ans=0;
        int l=1;
        for (r=1;r<=n;r+=2)
        {
            bool bo=false;
            while (k<get1(l,r))
            {
                l+=2;
                bo=true;
            }
            if (bo)
                for (int j=l+2;j<r;j+=2)
                {
                    if (get0(l,r)-get1(l,r)-get0(j,r)+get1(j,r)<=0)
                        l=j;
                }
            if (get0(l,r)-get1(l,r)-x[r]<=0)
                l=r;
            ans=max(ans,(k+get0(l,r)-get1(l,r)));
        }
        printf("%d\n",min(ans,sum[n]));
    }
    return 0;
}

int get0(int l,int r)
{
    return sum[r]-sum[l-2];
}

int get1(int l,int r)
{
    return sum[r-1]-sum[l-1];
}

int min(int a,int b)
{
    if (a<=b)
        return a;
    else
        return b;
}

int max(int a,int b)
{
    if (a>=b)
        return a;
    else
        return b;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值