Description
1 @ US$3 + 1 @ US$2 1 @ US$3 + 2 @ US$1 1 @ US$2 + 3 @ US$1 2 @ US$2 + 1 @ US$1 5 @ US$1Write a program than will compute the number of ways FJ can spend N dollars (1 <= N <= 1000) at The Cow Store for tools on sale with a cost of $1..$K (1 <= K <= 100).
Input
Output
Sample Input
5 3
Sample Output
5
题意:给出两个数,n,m,问m以内的整数有多少种组成n的方法
思路:一看就是完全背包,然后果断敲了,提交,WA,寻找原因所在,发现输入1000,100,时候,输出的明显是垃圾值,那么这题很明显后面的结果会很大,然而循环1000*100的话,用大数绝对超时,参考别人的代码后,才发现能够用将大数分开处理的方法,下面是原理
整数划分是把一个正整数 N 拆分成一组数相加并且等于 N 的问题.
比如:
6
5 + 1 (序列)
4 + 2, 4 + 1 + 1
3 + 3, 3 + 2 + 1, 3 + 1 + 1 + 1
2 + 2 + 2, 2 + 2 + 1 + 1, 2 + 1 + 1 + 1 + 1
1 + 1 + 1 + 1 + 1 + 1假设F(N,M) 整数 N 的划分个数,其中 M 表示将 N 拆分后的序列中最大数
考虑边界状态:
M = 1 或者 N = 1 只有一个划分 既: F(1,1) = 1
M = N : 等于把M - 1 的划分数加 1 既: F(N,N) = F(N,N-1) + 1
M > N: 按理说,N 划分后的序列中最大数是不会超过 N 的,所以 F(N,M ) = F(N,N)
M < N: 这个是最常见的, 他应该是序列中最大数为 M-1 的划分和 N-M 的划分之和, 比如F(6,4),上面例子第三行, 他应该等于对整数 3 的划分, 然后加上 2 的划分(6-4) 所以 F(N,M) = F(N, M-1) + F(N-M,M)用动态规划来表示
dp[n][m]= dp[n][m-1]+ dp[n-m][m]
dp[n][m]表示整数 n 的划分中,每个数不大于 m 的划分数。
则划分数可以分为两种情况:
a. 划分中每个数都小于 m, 相当于每个数不大于 m- 1, 故
划分数为 dp[n][m-1].
b. 划分中有一个数为 m. 那就在 n中减去 m , 剩下的就相当
于把 n-m 进行划分, 故划分数为 dp[n-m][m];
#include <stdio.h> #include <string.h> #include <algorithm> using namespace std; __int64 a[1005][105],b[1005][105],inf = 1; int main() { int n,m,i,j,k; for(i = 0;i<18;i++) inf*=10; while(~scanf("%d%d",&n,&m)) { memset(a,0,sizeof(a)); memset(b,0,sizeof(b)); for(i = 0;i<=m;i++) { a[0][i] = 1; } for(j = 1;j<=m;j++) { for(i = 1;i<=n;i++) { if(i<j) { a[i][j] = a[i][j-1]; b[i][j] = b[i][j-1]; continue; } b[i][j] = b[i-j][j]+b[i][j-1]+(a[i-j][j]+a[i][j-1])/inf;//处理大数前面的部位,当超过__int64时,就开始存入b数组,因为__in64是9.22..*10^18次方,下面的式子保证了两个a想加必定不超过__in64 a[i][j] = (a[i-j][j]+a[i][j-1])%inf;//保留后面的部位 } } if(b[n][m]) printf("%I64d",b[n][m]); printf("%I64d\n",a[n][m]); } return 0; }