『题解』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;
}