依然是生成函数,但是加了一个限制条件:每种方案的硬币总数不能超过100枚。
解决的办法是维护一个状态矩阵:A[i][j];[i]的意义是凑成i元,[j]的意义是用j枚硬币来凑;A[i][j]的意义是用j枚硬币凑成i元的解决方案数目。
于是多项式乘法中的两项相乘就成了状态转移的过程,例如(1+a1*x+a2*x^2+a3*x^3+...)(1+x^5+...),目前凑成3元有a3种方案,则:
A[3][0] + A[3][1] + A[3][2] + A[3][3] = a3
若a3*x^3与x^5相乘,意味着引进1个5元硬币,于是:
A[8][1]增加了A[3][0]种
A[8][2]增加了A[3][1]种
A[8][3]增加了A[3][2]种
A[8][4]增加了A[3][3]种
最后只要对A[sum][i]求和即可,i属于[0, 100]。
//还是生成函数,但要求硬币总数<=100
#include <cstdio>
#define MAXVAL 256
#define MAXCOIN 256
#define COINLIMIT 100
int coins[5]={1,5,10,25,50};
//[i][j]表示用j个硬币凑成i元有多少种方案
int CurrentAns[MAXVAL][MAXCOIN];
int Temp[MAXVAL][MAXCOIN];
int solutions(int sum)
{
//初始化
//也就是描述(1+x+x^2+x^3+...)这个式子,由1元硬币组成的
for (int i=0; i<=sum; ++i)
{
for (int j=0; j<MAXCOIN; ++j)
{
CurrentAns[i][j]=0;
if(i==j) CurrentAns[i][j]=1; //这个矩阵的对角线元素一定都是1,凑m元用m个硬币,显然只有1种方案;且矩阵在对角线以下才有元素。
}
}
//开始多项式乘法
for (int i=1; i<5; ++i) //遍历5,10,25,50四种规格的硬币
{
//先把当前的状态表暂存下来
for (int j=0; j<=sum; ++j)
{
for (int k=0; k<MAXCOIN; ++k)
{
Temp[j][k]=CurrentAns[j][k];
}
}
int CurVal=coins[i];
for (int j=1; j*CurVal<=sum; ++j) //当前参与乘法的项。新增j个硬币,j>=1。
{
for (int k=0; k+j*CurVal<=sum; ++k) //遍历乘号左边的式子的每一项,但并不是乘号左边多项式的所有项都需要和当前参与乘法的项相乘
{
/******************
乘法前的状态:k元,分别可用0,1,2,...,k枚硬币凑出,即[k][0], [k][1], [k][2], ..., [k][k]
乘法后的状态:k+j*CurVal元,分别可用0+j,1+j,2+j,...,k+j枚硬币凑。
状态转移。
******************/
for (int l=0; /*l+j<=COINLIMIT*/l<=k; ++l) //这个式子,循环条件l<=k就不对,l+j<=100就对了,何解?因为数组的列数只有110,最初开辟的时候,越界了!哈!把数组开大,列数和行数相等,现在l<=k是对的,证实了我之前的猜测,这里根本不用限制硬币总数。
{
CurrentAns[k+j*CurVal][l+j]+=Temp[k][l];
}
}
}
}
//在最后得到的表中统计sum元,且总硬币数小于100的solution总共有多少个
int cnt=0;
for (int i=0; i<=COINLIMIT; ++i)
{
cnt+=CurrentAns[sum][i];
}
return cnt;
}
int main()
{
int n;
while (scanf("%d", &n)!=EOF)
{
printf("%d\n", solutions(n));
}
return 0;
}