coj 1411: Longest Consecutive Ones

问移动k次最多能得到的连续的1的个数。

首先,我们要先预处理一下:

用另一个数组存第i个‘1‘的位置pos[i] 和他的前缀和sum[i] = pos[i] + sum[i - 1]。1的总个数为cnt

然后就是从第一个‘1’的位置开始枚举i,另当前能获得的最多的连续的1的个数为maxn - 1,如果区间(i,i + maxn - 1)中的所有1能在k步之内移动到一起,那么我们就更新当前最多连续的1的个数为maxn,重复此操作,直到当前的maxn不满足条件。

这时候,就找下一个i,看下一个i是否能满足。

怎么用O(1)的复杂度判断一个区间内的1时候能否在k步内移到一起呢?

要知道,把所有的1往中间的那个1靠近可以得到最小移动次数。

所以只要分别计算中间那个1的左右两边要移动的次数即可。

这里要用到之前预处理的前缀和。

还有要注意一点,比如中间那个的pos是n,那么从中间往左数的第一个要移动到n - 1,从中间往左数的第二个要移动到n - 2……

#include <iostream>
#include<stdio.h>
#include<cstring>
using namespace std;
char s[100010];
long long k;
int pos[100010];
long long sum[100010]; //sum是前n个位置的总和,会超int

bool ok(int l,int r)
{
    int mid = (l + r)/2;
    int cur_mid = pos[mid];
    int num = mid - l; //mid左边的1的个数
    long long temp = cur_mid * num - num*(num + 1)/2 - (sum[mid - 1] - sum[l - 1]); //中点左边要移动的次数
    num = r - mid;
    temp+=sum[r] - sum[mid] - num * cur_mid - num*(num + 1)/2;
    if(temp > k) return false;
    else return true;
}

int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%s %lld",s + 1,&k);
        memset(pos,0,sizeof pos);
        sum[0] = 0;
        int len = strlen(s + 1);
        int cnt = 1;
        for(int i = 1;i <=len;i++)
        {
            if(s[i] == '1') //预处理
            {
                pos[cnt] = i;
                sum[cnt] = pos[cnt] + sum[cnt - 1];
                cnt++;
            }
        }
        cnt--; //cnt是1的个数
        int ans = 0;
        int maxn = 1;
        int i = 1;
        while(true)
        {
            if(i + maxn - 1 > cnt ) break; //区间大小比1的个数还多
            if( ok(i,i+maxn-1)) {ans = maxn;maxn++;}
            else { i++;}

        }
        printf("%d\n",ans);
    }
    return 0;
}


你可能感兴趣的:(coj 1411: Longest Consecutive Ones)