PAT甲级-1044 Shopping in Mars

题目

 

题目大意

一串项链上有n个钻石,输入给出每个钻石的价格。用m元买一个连续的项链子串(子串长度可为1),如果不能恰好花掉m元,就要找到最小的大于m的子串,如果有重复就输出多个,按递增顺序输出子串的前端和后端索引。

原来的思路

取连续的子串使和恰等于m,没有恰等于就找最小的大于。可以将子串依次累加,使得每个位置都是起始位置到该位置的序列和,整个数组显递增顺序,就可以用右边减左边的方式找到满足条件的子串。直接暴力for循环,这里我的数组从0开始,没有从1开始,所以又将满足条件的子串首端索引为0的情况单拎出来,很不推荐。其次还将等于m和大于m的情况拆开求解,设了个flag,如果前期有输出的话flag为1,没有输出flag为0时再考虑样例二。没有看成>=统一求解,也不推荐。所以就超时了。

原来的代码

#include <iostream>
#include <vector>
using namespace std;

int main(){
    int n, money;
    cin >> n >> money;
    vector<int> v(n);
    for (int i = 0; i < (int)v.size(); i++){
        int num;
        cin >> num;
        i == 0 ? v[i] = num : v[i] = num + v[i - 1];
    }  // 这个思路很重要

    int flag = 0;
    for (int i = 0; i < (int)v.size(); i++){
        if (v[i] == money){
            cout << "1-" << i + 1 << endl;
            flag = 1;
        }
    }  // 查找从起始位置开始符合money的子串
    int maxi = 0;
    int q, h;  // 记录前后i的位置
    for (int i = 0; i < (int)v.size(); i++){
        for (int j = i - 1; j >= 0; j--){
            if (v[i] - v[j] == money){
                cout << j + 2 << "-" << i + 1 << endl;
                flag = 1;
            }else if (v[i] - v[j] > money){
                if (maxi < v[i] - v[j]){
                    maxi = v[i] - v[j];
                    q = j + 2;
                    h = i + 1;
                }
                break;
            }
        }
    }

    if (flag == 0){
        cout << q << "-" << h << endl;
        for (int i = h; i < (int)v.size(); i++){
            for (int j = i - 1; j >= 0; j--){
                if (v[i] - v[j] == maxi){
                    cout << j + 2 << "-" << i + 1 << endl;
                }else if (v[i] - v[j] > maxi){
                    break;
                }
            }
        }
    } //测试样例2

    return 0;
}

思路

改进参考了柳神的代码。首先就是将数组容量改为n + 1,让钻石从1开始。之前我用了两个for循环,第一个遍历i,第二个遍历j,找满足v[j] - v[i] = m的值。数组是递增顺序,可以将第二个循环用二分法优化,即在给定区间找j。然后就是将等于m和大于m这两种情况合并成>=m,也即找到>=m的最小区间,定i找j,也就是找区间的最小右边界。这时就需要考虑结果的输出形式,不能边找边输出,只能用个数组存,如果有更小的边界值就清空数组重新放。

知识点

在二分查找中,如果要找某区间的最小边界,而不是具体的找某个值,while循环的条件就要设为low < high,如果要用二分查找找到某个值,条件就设为low <= high,此时循环最终范围缩小到1位。

如果要在函数中调用数组,要将该数组设成全局变量,如果设成局部变量不断引用,会占用大量堆栈空间,可能会超时。某个数的话设成局部变量还ok。

AC代码

#include <iostream>
#include <vector>
using namespace std;

int n, money;
vector<int> v;  // 不设成全局变量就会超时

void find_j(int i, int &j, int &mint){
    int high = n;
    int low = i;
    while (low < high){  // 二分查找最小满足条件的边界,所以不加=号
        int mid = (low + high) / 2;
        if (v[mid] - v[i - 1] < money){
            low = mid + 1;
        }else{
            high = mid;  // 这里保留mid,进一步缩小范围,因为mid也可能是最小索引
        }
    }
    j = high;  // high值才是最终的二分查找结果
    mint = v[j] - v[i - 1];
}  // 二分法找到满足条件的最小索引

int main(){
    cin >> n >> money;
    v.resize(n + 1);
    for (int i = 1; i <= n; i++){
        int num;
        cin >> num;
        v[i] = num + v[i - 1];
    }  // 累加使得数组呈递增

    int mini = v[n];
    vector<int> res;
    for (int i = 1; i <= n; i++){
        int j, mint;  // i在前,j在后
        find_j(i, j, mint);  // 使用二分查找找到最小满足条件的j
        if (mint <= mini && mint >= money){
            if (mint < mini){
                mini = mint;
                res.clear();
            }
            res.push_back(i);
            res.push_back(j);
        }
    }

    for (int i = 0; i < (int)res.size(); i += 2){
        cout << res[i] << "-" << res[i + 1] << endl;
    }

    return 0;
}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值