个人练习-PAT甲级-1044 Shopping in Mars

题目链接https://pintia.cn/problem-sets/994805342720868352/problems/994805439202443264

很久没有练习,为了考试开始临时抱佛脚。。

思维有点钝化,起初看到就是简单的遍历区间,但用脚趾头想都知道会超时。看了柳神思路大概是用二分法。但已经不记得二分法怎么写了,于是先抄了一遍,对着抄的代码再做分析。抄的时候感慨柳神写得真是漂亮,自己简直毫无改进余地。。

完整代码:

#include<iostream>
#include<vector>
#include<string>
#include<algorithm>
#include<stdio.h>
#include<math.h>
#include<map>
#include<set>
#include<queue>
#include<string.h>

using namespace std;

int N, M;
vector<int> s, ret;

void biSec(int i, int& j, int& tmpsum){
    int left = i, right = N;
    while (left < right){
        int mid = (left + right) / 2;
        if (s[mid] - s[i-1] >= M)
            right = mid;
        else
            left = mid + 1;
    }
    j = right;
    tmpsum = s[j] - s[i-1];
}


int main() {
    cin>>N>>M;
    s.resize(N+1, 0);
    for (int i = 1; i <= N; i++){
        cin>>s[i];
        s[i] += s[i-1];
    }
    int minAns = s[N];
    for (int i = 1; i <= N; i++){
        int j, tmpsum;
        biSec(i, j, tmpsum);
        if (tmpsum > minAns)
            continue;
        if (tmpsum >= M){
            if (tmpsum < minAns){
                ret.clear();
                minAns = tmpsum;
            }
            ret.push_back(i);
            ret.push_back(j);
        }
    }
    for (int i = 0; i < ret.size(); i+=2)
        cout<<ret[i]<<"-"<<ret[i+1]<<endl;

    return 0;
}

按照程序执行的思路,首先是先读取了数据,以前N位和的形式保存

	for (int i = 1; i <= N; i++){
        cin>>s[i];
        s[i] += s[i-1];
    }

这里的minAns表示的应该是一串diamonds的子串“大于等于需求M”的最小的可能值,初始应为整串的总和,即s[N]

int minAns = s[N];

在下一个循环里做的事情是,得出一个合适的区间[i,j],在该区间内的和为tmpsum。如果tmpsum比最小可能和minAns大,那么根本没必要考虑它(相当于Loss过大了);否则有可能要重置minAns
检查minAns是否大于等于M,因为这是必要条件。满足之后,如果tmpsumminAns更小,说明找到Loss更小的值了,清空ret数组,更新minAns,再把区间push进结果数组ret。而如果tmpsum == minAns,即Loss不变,则不用更新,只需push。

	for (int i = 1; i <= N; i++){
        int j, tmpsum;
        biSec(i, j, tmpsum);
        if (tmpsum > minAns)
            continue;
        if (tmpsum >= M){
            if (tmpsum < minAns){
                ret.clear();
                minAns = tmpsum;
            }
            ret.push_back(i);
            ret.push_back(j);
        }
    }

下面来看关键的二分函数biSec。二分的目的是,对每一个i,找到一个合适的位置j,使得ij的区间和能够满足条件并且Loss最小。而这个j是通过不断压缩leftright来得到的。
每次二分开始时,right取最右的Nleft至少要大于等于i,所以从i开始。
while循环中不断缩减区间,candidate的j实际上是从mid来的,所以每次根据leftright计算出mid后,只需要看imid的和是否大于等于M即可。
如果大了,说明区间右边太广,或者说重心偏右,要缩减右边界,因为已经满足大于等于M的条件,那么可以直接把右边界缩减为mid
如果小了,说明区间重心偏左,要增大左边界,让left++就好。
通过这样一个过程,对每一个i,都找到了最合适的j以及它们的区间和tmpsum,返还给主程序即可。

void biSec(int i, int& j, int& tmpsum){
    int left = i, right = N;
    while (left < right){
        int mid = (left + right) / 2;
        if (s[mid] - s[i-1] >= M)
            right = mid;
        else
            left = mid + 1;
    }
    j = right;
    tmpsum = s[j] - s[i-1];
}

注意到的一个小点:最终给j赋值用的是right,因为此时保证循环已经跳出来了(即left>=right),那么这个right就是从之前的mid而来的,是正确的。由于left是每次+1递增的,所以在跳出循环时最大可能是right+1,这个是赋给j是不正确的。

纸上得来终觉浅,就算抄了一遍代码,也是糊里糊涂的。只有把思路都写下来,相当于“讲给别人听”,才能更加深印象,理解透彻。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值