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));
}
                    
                
                
            
        
浙公网安备 33010602011771号