【poj3061】Subsequence (尺取,二分)

Subsequence
Time Limit: 1000MS Memory Limit: 65536K
Total Submissions: 28313 Accepted: 11883
Description

A sequence of N positive integers (10 < N < 100 000), each of them less than or equal 10000, and a positive integer S (S < 100 000 000) are given. Write a program to find the minimal length of the subsequence of consecutive elements of the sequence, the sum of which is greater than or equal to S.
Input

The first line is the number of test cases. For each test case the program has to read the numbers N and S, separated by an interval, from the first line. The numbers of the sequence are given in the second line of the test case, separated by intervals. The input will finish with the end of file.
Output

For each the case the program has to print the result on separate line of the output file.if no answer, print 0.
Sample Input

2
10 15
5 1 3 5 10 7 4 9 2 8
5 11
1 2 3 4 5
Sample Output

2
3
Source

Southeastern Europe 2006

题意:

给一个正整数序列,求区间和大于等于S的最小区间长度。

思路分析:

n为10^5,时间复杂度只能在O(nlgn)和O(n).
一般来说,对于求区间和的问题,我们可以预处理序列的前缀和,因为求出前缀和后,对于任意一个区间和都可以在O(1)时间复杂度实现。下面给出两个方法。

  1. 二分法 O(nlgn)
    为什么可以想到用二分呢?因为题目有一个信息:序列为正整数序列。这就意味着我们所求的前缀和是有序的。我们可以枚举起点i,对于每个起点i,我们要在前缀和数组里找到最近的一个sum[j]使得sum[j]-sum[i]>=S. 即 sum[j] >= S+sum[i],然后求区间长度。这个可以用lower_bound(S+sum[i])实现。下面上代码:
#include 
#include 
#include 
#include 
#include 
#define ll long long
using namespace std;

int main()
{
	int T, N, S;
	int a[100010];
	ll sum[100010];
	cin>>T;
	while(T--)
	{
		int ans = 10000000;
		memset(sum,0,sizeof(sum));
		scanf("%d%d",&N, &S);
		a[0] = sum[0] = 0;
		for(int i = 1;i <= N;i++)
		{
			scanf("%d",&a[i]);
			sum[i] = a[i] + sum[i-1];
			cout<<sum[i]<<" ";
		}
		cout<<endl;
		for(int i = 1;sum[N]-sum[i] >= S;i++)
		{
			int cnt = lower_bound(sum+i,sum+N+1,S+sum[i])-(sum+i);	//查找[i,N]中第一个大于等于S+sum[i]的下标减去起点 
			cout<<S+sum[i]<<" "<<cnt<<endl;
			ans = min(cnt,ans); 
		}
		if(ans == 10000000) cout<<0<<endl;
		else cout<<ans<<endl;
	}
	return 0;
}
  1. 尺取法 O(n)
    其实我更愿意称它为滑动窗口。首先我们将l, r指针指向第一个元素,维护一个sum表示[l,r]的区间和。一开始sum = a[0]. 然后r往前走,sum += a[r]. 直到sum>=S,即[l,r]区间和>=S. 这时候我们移动左指针,同时sum -= a[l],开始比较区间的最小长度。直到[l,r] < S的时候,r继续往前走。这样的过程一直到r走到尽头结束。下面上代码:
#include 
#include 
using namespace std;

int main()
{
	int T, N, S;
	int a[100010];
	cin>>T;
	while(T--)
	{
		
		scanf("%d%d",&N, &S);
		for(int i = 0;i < N;i++) scanf("%d",&a[i]);
		int l = 0, r = 0, sum = a[0], ans = 100000000, cnt = 0;
		while(r < N)
		{
			if(sum < S)	//总和小于S,r一直右移,更新[l,r]区间和 
			{
				r++;
				sum += a[r];
			}
			else
			{
				while(l < r && sum >= S)	//总和大于等于S,l一直右移,比较最短,更新[l,r]区间和 
				{
//					cout<
					cnt = r-l+1;
					ans = min(cnt,ans);
					sum -= a[l];
					l++;
				}
			}
		}
		if(cnt == 0) ans = 0;
		cout<<ans<<endl; 
	}
	return 0;
}

你可能感兴趣的:(算法)