题目链接
本题我先用最直接的遍历法对每一个数遍历寻找满足条件的子串,代码如下:
#include<cstdio>
#include<algorithm>
using namespace std;
int chain[100010];
int main(){
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){//数组下标从1开始
scanf("%d",chain+i);
}
int temp,flag=0;//flag=1表示恰好能付
for(int i=1;i<=n;i++){
//从第一个数开始遍历,寻找恰好能付的子串
temp=chain[i];
if(temp==m){//一个钻石就够
printf("%d-%d\n",i,i);
flag=1;
continue;
}
else if(temp>m) continue;
else {//一个钻石不够,要找子串
for(int j=i+1;j<=n;j++){
temp+=chain[j];
if(temp==m) {
printf("%d-%d\n",i,j);
flag=1;
break;
}
else if(temp>m) break;
}
}
}
int lost=1010;//lost表示多付时的最小损失
if(!flag) {for(int i=1;i<=n;i++){//多付了,先找出最小损失
temp=chain[i];
if(temp>m){
lost=min(lost,temp-m);
continue;
}
else {for(int j=i+1;j<=n;j++){
temp+=chain[j];
if(temp>m) {
lost=min(lost,temp-m);
break;
}
}
}
}
for(int i=1;i<=n;i++){//再找出损失等于最小损失的子串
temp=chain[i];
if(temp>m){
if(temp-m==lost){
printf("%d-%d\n",i,i);
continue;
}
else if(temp-m>lost) continue;
}
else {for(int j=i+1;j<=n;j++){
temp+=chain[j];
if(temp-m==lost) {
printf("%d-%d\n",i,j);
break;
}
else if(temp-m>lost)
break;
}
}
}
}
return 0;
}
有超时也是意料之中的,毕竟满分也不是那么好拿的,考试的时候拿到这么多分就够了,练习的话当然要想一想更好的解法了。
解法二:用到sum[i]=a[1]+a[2]+…+a[i]。即将钻石货币数组第一个数作为参照点,sum[i]表示a[1]到a[i]的和值。sum数组是严格单调递增的,可以用二分法来做。枚举左端点i(1<=i<=n),然后在sum数组的[i,n]范围内查找值为sum[i-1]+m的元素(由sum[j]-sum[i-1]=m推得)是否存在。若存在,将其下标作为右端点j;若不存在,找到第一个大于sum[i-1]+m的元素sum[j]。用库函数lower_bound可以完成这个工作。
代码对钻石货币数组遍历两次,第一次遍历求出大于等于m的最接近m的near(将恰好能够支付的情况和多付的情况并作一起输出);第二次遍历输出那些sum[j]-sum[i-1]=near的i和j。总的时间复杂度为O(nlogn)。
AC代码如下:
#include<cstdio>
#include<algorithm>
using namespace std;
int sum[100010];
int main(){
int n,m;
sum[0]=0;//初始化
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%d",sum+i);
sum[i]+=sum[i-1];//求sum[i]
}
int near=m+1010;
for(int i=1;i<=n;i++){//枚举左端点
int j=lower_bound(sum+i,sum+n+1,m+sum[i-1])-sum;
if(sum[j]-sum[i-1]==m){
near=m;
break;//存在恰好够付的方案,直接结束查找
}
else if(j<=n&&sum[j]-sum[i-1]<near)
//没有恰好够付的方案,需要使损失降到最低
//必须要加上j<=n这一条件,因为lower_bound函数没有找到满足条件的元素,返回的是其应该插入的位置
near=sum[j]-sum[i-1];
}
for(int i=1;i<=n;i++){//输出所有方案
int j=lower_bound(sum+i,sum+n+1,m+sum[i-1])-sum;
if(sum[j]-sum[i-1]==near){
printf("%d-%d\n",i,j);
}
}
return 0;
}