P7891 [入门赛 #10] Coin Selection G(hard version) 题解

题目传送门

错误思路

暴力模拟,显然 $O(n^2)$,过不了 $1 \leq n \leq 10^5$。

正解

思路

重新看题面,可以发现想快速查找

面值不超过当前自己钱包中硬币的总面额的硬币中面额最大的一枚硬币

可使用二分法。

算法流程

  • 将读入的硬币数组排序(或在输入时就用二分插入使数组有序)。
  • 判断当前自己钱包里已有硬币的总面额是否大于等于最小的硬币。
    • 若是,则取合适的硬币。
    • 若不是,则取面值最小的硬币。
  • Farmer John 和 Bessie 交替取硬币。

STL

因为硬币一直被取走,所以可以用 vector 动态存储硬币面值。

lower_bound(begin,end,value) 查找从小到大排序的数组在[begin,end)区间中第一个大于等于 value 的数,并返回一个迭代器指向此数。

代码

//by Po7ed
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main()
{
    long long n;
    scanf("%lld",&n);
    vector<long long> v;//存储硬币面值
    long long t;
    for(long long i=0;i<n;i++)
    {
        scanf("%lld",&t);
        v.insert(lower_bound(v.begin(),v.end(),t,greater<long long>()),t);
        //此处在输入时直接用lower_bound插入面值。使得v有序(从大到小)。
        //lower_bound用greater<long long>()重载,使lower_bound知道v是从大到小排序的。下同。
    }
    long long a=0,b=0;
    for(long long i=1;v.size();i++)
    {
        if(i&1)//当取奇数次时
        {
            if(a<v.back())//如果面值不够
            {
                a+=v.back();
                v.pop_back();
            }
            else//如果面值够
            {
                t=lower_bound(v.begin(),v.end(),a,greater<long long>())-v.begin();
                //查找合适的硬币
                a+=v[t];
                v.erase(v.begin()+t);
            }
        }
        else//当取偶数次时
        {
            if(b<v.back())//如果面值不够
            {
                b+=v.back();
                v.pop_back();
            }
            else//如果面值够
            {
                t=lower_bound(v.begin(),v.end(),b,greater<long long>())-v.begin();
                //查找合适的硬币
                b+=v[t];
                v.erase(v.begin()+t);
            }
        }
    }
    printf("%lld %lld",a,b);
    return 0;
}

注意事项

不开 long long 见祖宗!

参考

upper_bound和lower_bound用法(史上最全)

posted @ 2023-07-02 16:08  Po7ed  阅读(25)  评论(0编辑  收藏  举报  来源