写起来还挺麻烦的。因为一看数列可能会长到10^5,所以求解的时候,不能硬着来,比方说寻找一个数段i~j,使得Di加到Dj之和尽量接近题目给定的值M,那么在i既定的情况下,要找这个j,就不能一个个遍历过来,否则求整个题目的解就需要n^2复杂度。而且为了保险,计算Di加到Dj的和也不要一个个加过来,也要用log(n)的复杂度来,这部分我是用树状数组实现的。
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
const int N=100005;
long long tree[N];
long long inpu[N];
int n;
long long target;
long long calcu(int x){//求和函数,tree[]数组就是树状数组
if(x>n)return -1;
long long re=0;
while(x>0){
re+=tree[x];
x-=(x&-x);
}
return re;
}
typedef struct message{//用二分法求解的时候,既需要知道数段的起点、终点,还需要知道数段和与题目给定的值之差,所以我用结构体保存解的信息
int from,to;
long long more;
void oupu(){
//printf("%d-%d more=%lld\n",from,to,more);
printf("%d-%d\n",from,to);
}
}message;
message ans[N];
int cnt;
message binary_find(int x){
message re;
re.from=x;
long long now=calcu(x-1),then;
if(calcu(n)-now<target){//如果从给定的Dx开始一直加到Dn也不能达到target,那说明以Dx为开始的数段没有一个能成为合法解
re.more=999999999;//所以把差值设成九个九,保证它返回回去了以后也不会被留在ans[]数组里
return re;
}
if(calcu(x)-now>target){//如果光是Dx就已经大于target了,那也不用再二分查找了,Dx就是数段的终点
re.to=x;
re.more=calcu(x)-now;
return re;
}
int lf=x,rt=n,mid=(lf+rt)/2;
while(1){<span style="white-space:pre"> </span>//二分查找
then=calcu(mid)-now;
if(then==target){
re.to=mid;
re.more=0;
return re;
}
if(lf==rt){
if(then>target){
re.to=mid;
re.more=then-target;
return re;
}
else if(then<target){
re.to=mid+1;
re.more=calcu(mid+1)-now-target;
return re;
}
}
if(then>target){
rt=mid;
mid=(lf+rt)/2;
}
else if(then<target){
lf=mid+1;
mid=(lf+rt)/2;
}
}
}
int main(){
int i;
scanf("%d%lld",&n,&target);
memset(tree,0,sizeof(tree));
int temp;
for(i=1;i<=n;i++){<span style="white-space:pre"> </span>//读入数列,并且把树状数组处理好,以供calcu()函数使用
scanf("%lld",&inpu[i]);
temp=i;
while(temp<=n){
tree[temp]+=inpu[i];
temp+=(temp&-temp);
}
}
message msg;
for(i=1;i<=n;i++){
msg=binary_find(i);
//msg.oupu();
if(cnt==0||msg.more<ans[0].more){//如果 当前解 比 原有解 的差值更小,那就把原有解都舍弃掉,只保存当前解
ans[0]=msg;
cnt=1;
}
else if(msg.more==ans[0].more){ //如果 当前解 与 原有解 的差值一样大,那就把它也存进去
ans[cnt++]=msg;
}
}
for(i=0;i<cnt;i++)
ans[i].oupu();
return 0;
}