题目大意:
- 题目给出一串正整数num[1-n],和一个既定和M,让你输出数组中连续的、和等于M 的部分子序列。
- 如果不能满足部分和恰好等于M,则输出和大于M且与M最接近的连续子序列。
- 如果答案不唯一,则按照左端升序输出。
注意:
- 双层枚举会超时,用二分。
- 因为求的是部分和,所以直接存储sum比较方便。如:num[i]到num[j]的部分和可以写成 sum[j]-sum[i-1].
- 用二分返回的是数组区间**[ i,n+1)**中第一个大于M的值的位置,(注意区间)可以直接用封装函数upper_bound,也可以自己写。时间复杂度是O(logn) 。回到主函数后,有两种情况:
- sum[j-1]-sum[i-1]==M
- sum[i]-sum[j-1]>M && sum[i]-sum[j-1]<nearM && j<=n
代码:
/*Sample Input 1: 16 15
3 2 1 5 4 6 8 7 16 10 15 11 9 12 14 13---------
1-5 4-6 7-8 11-11
Sample Input 2:
5 13
2 4 5 7 9---------
2-4 4-5*/
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;
const int MAXN = 100010;
const int MAXM = 100000010;
int sum[MAXN] = {0}, ans[MAXN] = {0}, n, M;
/** \brief 二分法返回[L,R)范围内第一个大于M的数位
*
* \param i int
* \param M int
* \return int
*
*/
int func(int L, int R,int M)
{
int mid, left=L,right = R;
while(left < right)
{
mid = (left + right) / 2;
if(sum[mid]>M)
right=mid;
else
left=mid+1;
}
return left;
}
int main()
{
int temp;
scanf("%d %d", &n, &M);
for(int i = 1; i <= n; i++)
{
int t;
scanf("%d", &t);
sum[i] = sum[i - 1] + t;
}
int nearM = MAXM; //设置初始的nearM为最大值
for(int i = 1; i <= n; i++)
{
int j=func(i,n+1,M+sum[i-1]); //在[i,n+1)范围内寻找第一个大于M+sum[i-1]的值
if(sum[j-1]-sum[i-1]==M)
{
nearM=M;
break;
}
else if(j<=n&&nearM>sum[j]-sum[i-1])
nearM=sum[j]-sum[i-1];
}
//printf("nearM=%d\n",nearM);
for(int i = 1; i <= n; i++)
{
//求右端点
int j= func(i,n+1,sum[i-1]+nearM);
if(sum[j-1]-sum[i-1]==nearM)
{
printf("%d-%d\n",i,j-1);
}
}
return 0;
}