poj3061:Subsequence(最短子序列和) -- 前缀和与二分

题面:

在这里插入图片描述

题目链接: http://poj.org/problem?id=3061

题目大意:

给出了一个N个正整数(10 <N <100 000)的序列,每个正整数小于或等于10000,并给出了一个正整数S(S <100 000 000)。编写程序以查找序列中连续元素的子序列的最小长度,其总和大于或等于S。

题目分析:

求连续元素的子序列,可以知道我们最后得到的序列是原序列中的一个区间,要想求出序列中某个子序列的和,我们可以采用前缀和的知识,例如 l ~ r 的区间和 = sum[r] - sum[l - 1]。
得到前缀和之后,我们可以采取双循环枚举的方式枚举序列的所有子区间,从而得出合适的区间长度。

	for(int i = 1; i <= n; i ++) {
		for(int j = i; j <= n; j ++ ){
			if(sum[j] - sum[i - 1] >= s) {
				ans = min(ans,j - i + 1);
				break;
			} 
		}
	}

幸福总是短暂的,看一下 n 的取值范围, 双循环是铁定超时的,如何优化呢?
分析第二重循环可以发现,我们只需要找到第一个满足 sum[j] - sum[i - 1] >= s 的就可以直接 break 了,但是我们需要去枚举每一个 sum[j],比较浪费时间,可以发现,sum[x] 是一个递增的序列,这时候我们就可以采取二分的策略,寻找到第一个满足 sum[j] - sum[i - 1] >= s 的 j

Code:

#include <cstdio>
#include <string>
#include <cstring>
#include <iostream>
#include <algorithm>

#define INF 0x3f3f3f3f

using namespace std;

const int maxn = 1e5 + 10;

typedef long long LL;

LL sum[maxn],a[maxn];

int t,n,s; 

int main(void) {
	cin >> t;
	while(t --) {
		memset(sum,0,sizeof(sum));
		cin >> n >> s;
		for(int i = 1; i <= n; i ++) {
			cin >> a[i];
			// 前缀和 
			sum[i] = sum[i - 1] + a[i];
		}
		// 特判,如果整个序列和 < s,则一定没有合适的区间 
		if(sum[n] < s) puts("0");
		else {
			int ans = INF;
			for(int i = 1; i <= n; i ++) {
				int l = i;
				// 后面的判断没有意义,可以直接跳出循环 
				if(sum[n] - sum[l - 1] < s) break;
				int r = n;
				// 二分查找最小的右端点满足 sum[mid] - sum[i - 1] >= s 
				while(l < r) {
					int mid = l + r >> 1;
					if(sum[mid] - sum[i - 1] < s) l = mid + 1;
					else r = mid;
				}
				// 寻找到最小的区间 
				ans = min(ans,r - i + 1);
			}
			cout << ans<< endl;
		}
	}
	return 0;
}

在这里插入图片描述
在这里插入图片描述

©️2020 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页