PAT A1044 Shopping in Mars (25point(s))

题目链接
本题我先用最直接的遍历法对每一个数遍历寻找满足条件的子串,代码如下:

#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;
}
发布了81 篇原创文章 · 获赞 0 · 访问量 669

猜你喜欢

转载自blog.csdn.net/weixin_44546393/article/details/105394844