【动态规划/递归】(团队内部比赛试题)T134293 T2.货币系统问题
【题目描述】货币系统问题(money.cpp/c/pas)
题目描述
货币是在国家或经济体内的物资与服务交换中充当等价物,或是偿还债务的特殊商品,是用作交易媒介、储藏价值和记账单位的一种工具。魔法世界的货币的历史,可以追溯至史前以物易物的阶段,后来经过金属货币、金银、纸币以及金银本位制度,演化至现代的货币体系,现已知魔法世界的货币系统有V种面值,求组成面值为N的货币有多少种方案。
输入格式
第一行为两个整数V和N,V是货币种类数目,1≤V≤25,N是要构造的面值,1≤N≤1000。 第二行为V种货币的面值。
输出格式
输出方案数。
输入输出样例
输入 #1
3 10
1 2 5
输出 #1
10
一、初始思路(错误)
如果这题不考虑重复的凑数方法(比如凑6块钱,1,2,3和3,2,1是不同的两种方案),那么这题会好做很多。
首先考虑动态规划废话,这次考试专题就是动规,还能是其他思路吗。
设 \(ccf[]\) 为所有面值的货币,比如样例的\(ccf[0]=1,ccf[1]=2,ccf[3]=5\)。
设 \(F[i]\) 为组成面值为\(i\)元的货币的方案数。
则很容易得到状态转移方程:
\(F[i]=\sum_{p=0}^{V}F[i-ccf[p]]. (i-ccf[p]>0)\)
其中\(V\)表示货币种类数目。注意如果\(i-ccf[p] \le 0\),则不能继承状态。
比如说\(F[10]\),它能从\(F[9]\)加\(1\)块钱凑成,从\(F[8]\)加\(2\)块钱凑成,从\(F[5]\)加\(5\)块钱凑成。所以\(F[10]\)应该是这些状态的和。
对于初始状态,对于\(ccf[]\)中的每一个数,\(F[ccf[i]]=1(0\le i \le V)\).因为它们一种货币的面值对应的货币本身就肯定有(至少)一种方案。只要用各自的货币一张就可以。\
二、正确思路
按照上面的思路,我写了第一版代码(由于不对就不写了,我水博客的习惯必须得改改了)。最后算出来\(F[10]\)竟然有\(128\)种方案!远远超过了答案数值\(10\)。
没错,问题就出现将“本质相同的”方案算作了多个方案。
那怎么办呢?
我列举了一下\(F[10]\)的所有方案:
5 5
5 2 2 1
5 2 1 1 1
5 1 1 1 1 1
2 5 2 1
2 2 2 2 2
2 2 2 2 1 1
2 2 2 1 1 1 1
2 2 1 1 1 1 1 1
2 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
正好\(10\)种方案,每种方案不重不漏。大家发现了什么规律?
对!就是每一个数必须不大于它的前一个数!就比如说上面划掉的方案就和第二种方案重复了,正是因为\(2 < 5\),没有遵循这一原则!
但是难题又来了。我本来以为上面的思路改一改就行,但是你不能保证一个状态继承的前一个状态组成的方案满足上述条件,比如\(F[5]\)从\(F[3]\)继承而来(就是在\(F[3]\)的每一个方案后面续个\(2\)),你能保证\(F[3]\)中的每一个方案的组成元素都大于等于\(2\)吗?显然不行可能是我这个蒟蒻没有想到罢了。
所以说,递推形式的动态规划不能使用了。我们可以尝试一下递归写法的动态规划,谁说动态规划必须要建立状态数组?
我们可以编写递归函数\(int dfs(int money,int biggest)\),其中money代表当前子状态要凑的钱数,biggest就是这个状态最大能选择面值为多少的钱币。
1.当\(money<0\)时,当前状态无效,返回\(0\);
2.当\(money=0\)时,说明凑够了钱,找到了一种方案,返回\(1\);
3.其他状态,返回的值为\(\sum_{p=0}^{V}dfs(money-ccf[p],ccf[p])\),第一个参数是这个状态向其他子状态的转移,第二个参数是指,以后选的每一个钱数都不能超过这个值。
在步骤3中,如果选中的\(ccf[p]>biggest\),则依照上述规则不能选。由于\(ccf\)已经从小到大排好序了,比\(ccf[p]\)再高的币值都肯定不能选择,所以遇到一个\(ccf[p]>biggest\)时直接从求和的循环里break掉就行了。
代码如下:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
using namespace std;
int N,V,tmp;
vector<int> ccf;//使用动态数组更方便
inline int read()//快读不说了
{
int f=1,ans=0;
char c;
c=getchar();
while(c<'0'||c>'9')
{
if(c=='-')
{
f=-1;
}
c=getchar();
}
while(c>='0'&&c<='9')
{
ans=ans*10+c-'0';
c=getchar();
}
return ans*f;
}
int dfs(int money,int biggest)
{
if(money<0) return 0;//无效
else if(money==0) return 1;//一种方案
else
{
int k=0;//子状态的最后答案
for(int i=0;i<ccf.size();i++)//for每个面值货币
{
if(ccf[i]>biggest) break;//已经超过了限定值
k+=dfs(money-ccf[i],ccf[i]);
}
return k;
}
}
int main()
{
V=read();
N=read();
for(int i=1;i<=V;i++)
{
tmp=read();
ccf.push_back(tmp);
}
sort(ccf.begin(),ccf.end());//先排序
cout<<dfs(N,N);//第二个参数可以是任意一个较大的数
return 0;
}