luogu P5961 [BalticOI 2006] coin collector钱币收藏家 题解

题解 P5961 [BalticOI 2006] coin collector钱币收藏家

题目传送门


题目大意

由小到大给定 \(n\) 种硬币面额,和一个已有钱数。求通过购买能得到多少种未拥有的硬币种数,且使花费价格尽可能大。


题解

考虑从头开始遍历每种硬币(面额)\(c\),用栈存储每个已选的硬币面额,并记录目前已选硬币面额的总值 \(sum\),即购买后找零的总钱数,对于目前遍历到的硬币面额有 \(2\) 种操作:

一,如果 \(sum < c\)

  • 如果该硬币未被收藏,那么出于贪心考虑,要将它压入已选的栈中,并累计 \(sum\)

  • 如果该硬币已收藏,那么跳过(因为已收藏的硬币不会为答案提供任何贡献)。

二,如果 \(sum \ge c\)

出于贪心考虑,我们要将它压入栈中。

别急

如果 \(sum \ge c\) 那么说明我们目前已选的硬币组合是不合法的。因为此时找零的会是一个面额为 \(c\) 的硬币,而不是我们所选的硬币组合。

于是现在又有两种情况

选择它或跳过它。

考虑尝试把它加入答案会不会更优。

若想把目前的 \(c\) 加入,则必须弹出且只用弹出目前栈顶的硬币。

为什么是只用?

不妨令目前的硬币面额为 $ c $,栈顶的硬币面额为 $ x $,除去栈顶硬币的面额总和为 \(x_{pre}\)

因为目前栈中的硬币面额是合法的。

所以 \(x_{pre} < x\)

由题 \(x < c\)

那么根据不等式的传递性:

\(x_{pre} < c\)

所以只用弹出栈顶元素。

弹出一个元素,再加入一个元素,对于目前的答案并不会有变化,但弹出后,可以获得更多的选择机会。所以我们应该选择加入目前的硬币。

代码

tip:在实现时需要加入一个特判,按照以上的贪心思想,最后有可能不会购买东西,但 \(k \ge 2\),可以购买一个一分钱的物品,让购买商品尽可能贵

pop前一定要判断栈是否为空!

#include<bits/stdc++.h>

using namespace std;

inline int read();

int n,k,c,d,sum;//n->硬币的种类//k->纸币的面值//c->硬币的面值//d->硬币是否收藏//sum->目前已选择的硬币的总面值 

stack<int> s;//储存已选择的硬币 

int main(){
	n=read(),k=read(); 
	for(int i=1;i<=n;i++) 
	{
		c=read(),d=read();
		
		if(sum>=c)//如果 目前已选择的硬币总面值 大于等于 目前的硬币面值 
		{
			if(!s.empty()) sum-=s.top(),s.pop();//放弃前面的选择 
			if(!d&&sum+c<=k) sum+=c,s.push(c);//将 目前的硬币面额 压入栈中 并计算目前的选择的钱数总和 
		}
		else if(!d)//如果 前已选择的硬币总面值 小 目前的硬币面值 且 目前的硬币没有被收藏 
		{
			if(sum+c>k) break;//如果 选择目前的硬币 会使购买的 物品的价格高于纸币的价格,即k 那么说明已找到答案 
			sum+=c; s.push(c);//将 目前的硬币 压入栈中 并计算目前的选择的钱数总和
		}
	}
	
	if(!sum) sum=1;//特判 如果一个硬币也没有选  但是k>=2 所以至少是可以花费 1 分钱的 
	printf("%d\n%d",s.size(),k-sum); 
	return 0; 
}

inline int read()
{
	int x=0,f=1;
	char c=getchar();
	while(c<'0'||c>'9')
	{
		if(c=='-') f=0;
		c=getchar();
	}
	while(c>='0'&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
	return f?x:(~(x-1));
}
posted @ 2025-02-09 08:35  yzjznbQwQ  阅读(9)  评论(0)    收藏  举报