核心
把问题分解成相对简单的子问题,来求解复杂问题的方法。核心就是记住已经求解过的子问题的值(与递归的区别)。
解决办法:列出状态转移方程
例一:金币系统
现有的货币系统1、2、5、10等面值。这样18元的价格可以有18x(1元), 9x(2元), 8x(2元)+2x(1元), 3x(5元)+1x(2元)+1x(1元), 等表示方法。
你的任务就是,给定面值的硬币Vi,硬币个数无限多,求出N的价格可以有多少种表示方法。
Input
第一行为一个数字Z,表示一下有Z组测试数据。
每一组测试数据由两行组成,
第一行为两个数V,N。有V (1 <= V <= 25)种硬币,和需要表示的价格N (1 <= N <= 10,000)。
第二行V个数,空格分隔。每一个数Vi表示一种已有的硬币面值。
Output
每组测试数据有一行输出,包括一个数。表示N的价值在由Vi组成的金币系统中有p种表示法。
Sample Input
1
3 10
1 2 5
Sample Output
10
Hint
数据量可能会比较大,保证long long不会溢出。
分析
将v种金币存到a[30]中,求n面值的金币可以有多少种方式组合得到,并用dp[n]存储,现例:a[0]=1,a[1]=2,a[2]=5,n=10
dp[0]=1,面值为0,当然是一种方式 (置dp全为0,再置dp[0]=1)
dp[1]=1=dp[0+a[0]],故dp[1]+=dp[0],面值为1,当然是一种方式
dp[2]=2=dp[1+a[0]]、dp[0+a[1]],故dp[2]+=dp[1],dp[2]+=dp[0],面值为2
dp[3]=dp[2+a[0]]、dp[1+a[1]],故dp[3]+=dp[2],dp[3]+=dp[1],面值为3
转态转移方程:
dp[j+a[i]]+=dp[j]
即面值为10(n=10)的组合方式,分解成·10-a[0]=9,10-a[1]=8,10-a[2]=5
所以dp[10]+=dp[9],dp[10]+=dp[8],dp[10]+=dp[5];//自顶向下理解
(即分解成9的组合方式数+8的组合方式数+5的组合方式数)
9-a[0]=8,9-a[1]=7,9-a[2]=4
dp[9]+=dp[8],dp[9]+=dp[7],dp[9]+=dp[4];
dp[0]=1;
for(int i=0; i<v; i++)
{
for(int j=0; j+a[i]<=n; j++)
{
dp[j+a[i]]+=dp[j];//自底向上方法
}
}
代码:
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>//memset方法
#include <vector>
#define LL long long
using namespace std;
int main()
{
LL a[30],dp[10001];
int m,v,n,sum;
scanf("%d",&m);
while(m--)
{
sum=0;
scanf("%d%d",&v,&n);
for(int i=0; i<v; i++)
{
scanf("%lld",a+i);
}
memset(dp,0,sizeof(dp));
dp[0]=1;
for(int i=0; i<v; i++)
{
for(int j=0; j+a[i]<=n; j++)
{
dp[j+a[i]]+=dp[j];
}
}
printf("%lld\n",dp[n]);
}
return 0;
}