『题解』Luogu P8444 不等价交换法则

题目传送门

题目大意

\(n\) 件商品,其中第 \(i\) 件的价格为 \(a_i\)

你有 \(w\) 元钱,你仅可选择一件商品购买。但你可以用已有的商品交换剩余的商品(也可以不换),但你获得的商品价值和不能大于你用于交换的商品价值。

求最多能获得的商品数

思路

显然,我们的 \(w\) 元钱购买的商品应该买能买的商品里最大价值的,这样就可以最大化 \(w\) 元钱带来的收益。

我们要怎么样处理交换呢?

我们可以发现一个性质:用一件价值大的商品交换若干小的商品,再交换交换交换······所得来的物品个数和直接用买来的商品交换一次所得来的一样,甚至少,还要考虑选择哪些物品来交换。

就比如说有 \(10\) 件物品,价值分别为 \(1,2,\dots,9,10\),买来一个十元的物品,最多能换 \(4\) 个物品。

\(4\) 个物品的方案:用 \(10\) 直接换 \(1,2,3,4\)

那多换几次所得来的结果呢?也可以是 \(4\),一种方案是:\(10 \to 6,4\)\(6 \to 1,2,3\),还是能换到 \(1,2,3,4\)

显然,换来的物品都是价值最小的几个。

有了这个性质,贪就行了。

我这用一个小根堆来找最小元素,并在交换前 \(\mathcal{O}(n)\) 初始化并 \(\mathcal{O}(n)\) 找到最大价值的可以买的物品。

然后一直取小根堆堆顶元素,可以的话换到,否则就直接输出答案。

关于小根堆初始化的复杂度,可以参考『学习笔记』二叉堆

代码

#include <iostream>
#include <cstring>
using namespace std;
template<typename T=int>
inline T read(){
    T X=0; bool flag=1; char ch=getchar();
    while(ch<'0' || ch>'9'){if(ch=='-') flag=0; ch=getchar();}
    while(ch>='0' && ch<='9') X=(X<<1)+(X<<3)+ch-'0',ch=getchar();
    if(flag) return X;
    return ~(X-1);
}

const int N=1e6+5;
int n,w,s,ans;
int a[N];

template<class T=int>
class Heap{
    public:
        Heap(bool (*_cmp)(T,T)=[](T a,T b)->bool{return a<b;}):cmp(_cmp){clear();}
        void clear(){
            memset(a,0,sizeof(a));
            l=0;
        }
        void build(T *_a,int n){
            for(int i=1; i<=n; i++)
                a[i]=_a[i-1];
            l=n;
            for(int i=n>>1; i; i--)
                sink(i);
        }
        void push(T x){
            a[++l]=x;
            swim(l);
        }
        void pop(){
            a[1]=a[l--];
            sink(1);
        }
        T top(){return a[1];}
        bool empty(){return l==0;}
        int size(){return l;}
    private:
        T a[N];
        int l;
        bool (*cmp)(T,T);
        void swim(int x){ // 上浮 log n
            for(int i=x; i>1 && cmp(a[i],a[i>>1]); i>>=1)
                swap(a[i],a[i>>1]);
        }
        void sink(int x){ // 下沉 log n
            for(int i=x,t=son(i); t<=l && cmp(a[t],a[i]); i=t,t=son(i))
                swap(a[i],a[t]);
        }
        inline int ls(int x){return x<<1;} // 取左右儿子
        inline int rs(int x){return x<<1|1;}
        // 取较小的儿子
        inline int son(int x){return ls(x)+(rs(x)<=l && cmp(a[rs(x)],a[ls(x)]));}
};
Heap h;

int main(){
    n=read();
    for(int i=1; i<=n; i++)
        a[i]=read();
    w=read();
    h.build(a+1,n); // 建堆
    for(int i=1; i<=n; i++) // 找最大价值
        s=max(s,a[i]<=w ? a[i] : 0);
    // 开始交换
    while(!h.empty())
        if(h.top()<=s)
            s-=h.top(),h.pop(),ans++;
        else
            break;
    printf("%d\n",ans);
    return 0;
}
posted @ 2022-07-25 10:01  仙山有茗  阅读(161)  评论(0编辑  收藏  举报