问移动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; }