题意:给出一个数字序列与一个数M,在数字序列中求出所有和值为s的连续子序列(区间下标左端点小的先输出)。若没有这样的序列,求出和值恰好大于S的子序列(即在所有和值大于M的子序列中和值最接近S)。假设序列下标从1开始。
思路:令sum[i]表示A[1】到A[i]的和值(A表示输入的数组)。Sum[i]严格遵守递增序列,因此只需要计算sum[j]-sum[i-1],即可得到和值为M的连续子序列。
递增序列,利用二分法:在sum数组查找第一个大于sum[i-1]+M的的元素的值,令其为sum[j]。 利用upper_bound。二分法查找
//求序列中第一个大于X的元素的位置
int upper_bound(int a[],int left,int right,int x){
int mid;
while(left<right){
mid=(left+right)/2;
if(a[mid]>x)right=mid;
else left=mid+1;
}
return left;
}
那么在J前面一个位置有可能满足sum[j-1]+sum[i-1]==M,因此通过的J值可以分两个步骤,先查看能否找到=M,如果不能则求出最接近M的值。
考虑题目要输出所有方案,因此要遍历两次序列,第一次查找出M的值或者是最接近M的数字,第二次输出方案。
#include<cstdio>
int sum[110];
//返回第一个大于X的整数的位置
int search(int left,int right,long x){
int mid;
while(left<right){
mid=(left+right)/2;
if(sum[mid]>x)right=mid; //此处如果sum[maxn]定义在Main 方法中,则会报错
else left=mid+1;
}
return left;
}
int main(){
int n,m;
scanf("%d%d",&n,&m);
sum[0]=0;
for(int i=1;i<=n;i++){
scanf("%d",&sum[i]);
sum[i]+=sum[i-1];
}
for(int i=1;i<=n;i++){
printf("%d",sum[i]);
if(i<n)printf("-");
}
printf("\n");
int nearS=1000010; //先定义nearS为一个较大的数字,之后不断更新nearS
for(int i=1;i<=n;i++){
int j=search(i,n+1,sum[i-1]+m); //查找在[i,n]范围, 注意是sum[i-1]+m
if(sum[j-1]-sum[i-1]==m){ //这里变成了j-1,因为查找的j是第一个大于sum[i-1]+m的数
nearS=m;
break;
}else if(j<=n&&sum[j]-sum[i-1]<nearS) {
nearS=sum[j]-sum[i-1]; //不断更新最小nearS的值
}
}
for(int i=1;i<=n;i++){
int j=search(i,n+1,sum[i-1]+nearS);
if(sum[j-1]-sum[i-1]==nearS){
printf("%d-%d\n",i,j-1);
}
}
return 0;
}