Boys, get rid of singlehood
重写完全背包的动态规划的状态及状态方程:
完全背包是在N种物品中选取若干件(同一种物品可多次选取)放在空间为V的背包里,每种物品的体积为C1,C2,…,Cn,与之相对应的价值为W1,W2,…,Wn.求解怎么装物品可使背包里物品总价值最大。
设物品种类为N,背包容量为V,每种物品的体积为C[i],价值为W[i]。
子问题定义:F[i][j]表示前i种物品中选取若干件物品放入剩余空间为j的背包中所能得到的最大价值。
状态方程为:
(2-2)
在文章《背包问题——“01背包”最优方案总数分析及实现》中曾定义G[i][j]代表F[i][j]的方案总数。这里我们也做相同的定义,那么最终的结果应该为G[N][V]。
由01背包转变到完全背包后G[i][j]该怎么求?
对于01背包来说,G[i][j]求法如下(摘自:《背包问题——“01背包”最优方案总数分析及实现》):
如果F[i][j]=F[i-1][j]且F[i][j]!=F[i-1][j-C[i]]+W[i]说明在状态[i][j]时只有前i-1件物品的放入才会使价值最大,所以第i件物品不放入,那么到状态[i][j]的方案数应该等于[i-1][j]状态的方案数即G[i][j]=G[i-1][j];
如果F[i][j]=F[i-1][j-C[i]]+W[i] 且F[i][j]!=F[i-1][j]说明在状态[i][j]时只有第i件物品的加入才会使总价值最大,那么方案数应该等于[i-1][j-C[i]]的方案数,即G[i][j]=G[i-1][j-C[i]];
如果F[i][j]=F[i-1][j-C[i]]+W[i] 且F[i][j]=F[i-1][j]则说明即可以通过状态[i-1][j]在不加入第i件物品情况下到达状态[i][j],又可以通过状态[i-1][j-C[i]]在加入第i件物品的情况下到达状态[i][j],并且这两种情况都使得价值最大且这两种情况是互斥的,所以方案总数为G[i][j]=G[i-1][j-C[i]]+ G[i-1][j]。
对于完全背包,我们也可以根据其状态方程来进行条件判断:
如果F[i][j] = F[i-1][j]且F[i][j] != F[i][j-C[i]]+W[i],说明背包总不存在第i种物品,也就是说背包种物品仍由前i-1种物品组成,那么应该有G[i][j] = G[i-1][j];
如果F[i][j] = F[i][j-C[i]]+W[i]且F[i][j] != F[i-1][j],则说明背包中必定存在第i种物品使背包达到[i][j]状态的最大值,G[i][j] = G[i][j-C[i]];
如果F[i][j] = F[i][j-C[i]]+W[i]且F[i][j] = F[i-1][j],则说明背包中存在i与不存在i都可以达到最大值,那么这个方案数应该由两者叠加,因为这两种情况是互斥的,即G[i][j] = G[i-1][j]+G[i][j-C[i]]。
伪代码如下(注意和01背包情况的区分):
- F[0][] ← 0
- F[][0] ← 0
- G[][ ] ← 1
- for i ← 1 to N
- do for j ← 1 to V
- F[i][j] ← F[i-1][j]
- G[i][j] ← G[i-1][j]
- if (j >= C[i])
- if (F[i][j] < F[i][j-C[i]]+W[i])
- then F[i][j] ← F[i][j-C[i]]+W[i]
- G[i][j] ← G[i][j-C[i]]
- else if (F[i][j] = F[i][j-C[i]]+W[i])
- then G[i][j] ← G[i-1][j]+G[i][j-C[i]]
- return F[N][V] and G[N][V]
同样,上述方法在保存状态F[][]及G[][]时需要O(NV)的空间复杂度,下面我们对空间复制度进行优化。
压缩空间复杂度为O(V)
F[i][j]与G[i][j]只分别与F[i-1][]和G[i-1][]的状态有关,所以我们可以用两个一维数组F[]和G[]来替换二维数组F[][]和G[][]。
直接给出伪代码:
- F[] ← 0
- G[] ← 1
- for i ← 1 to N
- do for j ← C[i] to V
- if (F[j] < F[j-C[i]]+W[i])
- then F[j] ← F[j-C[i]]+W[i]
- G[j] ← G[j-C[i]]
- else if (F[j] = F[j-C[i]]+W[i])
- then G[j] ← G[j]+G[j-C[i]]
- return F[V] and G[V]
和01背包的最优方案总数相比上述伪代码只是调换了一下C[i]与V的遍历顺序,具体思想请看博文《背包问题——“完全背包”详解及实现(包含背包具体物品的求解)》
例题:::
Boys, get rid of singlehood!
Time Limit : 3000/1000ms (Java/Other) Memory Limit : 65535/32768K (Java/Other)
Total Submission(s) : 191 Accepted Submission(s) : 85
Font: Times New Roman | Verdana |Georgia
Font Size: ← →
Problem Description
of this activity is that when the customer pays for the goods which he buys, if he can answer how
many different ways to pay the total money, he will be given a discount. But it is a hard problem
for Tom. Therefore, he asks you for help. Tom always thinks that you are clever enough to solve
this problem.
To simplify the problem, we assume there are only 3 nominal amounts of banknotes, 1 yuan,
2 yuan and 5 yuan.
Given an amount of N yuan, you are required to work out how many ways to pay with those
3 nominal amounts of banknotes mentioned above.
Input
N (1<= N <= 11111). Input is terminated by a value of zero (0) for n.
Output
Sample Input
Sample Output
Author
#include<string.h>
int val[11115];
int ge[11115];
int a[3]={1,2,5};
int n;
int bag()
{
int i,j;
memset(val,0,(n+3)*sizeof(val[0]));
// memset(ge,0,(n+3)*sizeof(ge[0]));
for(i=0;i<=n;i++)
ge[i]=1;
for(i=0;i<3;i++)
for(j=0;j<=n;j++)
{
if(j>=a[i])
{
if(val[j]<val[j-a[i]]+a[i])
{
val[j]=val[j-a[i]]+a[i];
ge[j]=ge[j-a[i]];
}
else if(val[j]==val[j-a[i]]+a[i])
{
val[j]=val[j];
ge[j]=ge[j]+ge[j-a[i]];
}
}
}
return ge[n];
int main()
{
int i,j;
while(scanf("%d",&n))
{
if(n==0) return 0;
else
{
bag();
//for(i=0;i<=n;i++)
// printf("%d ",ge[i]);
printf("%d\n",ge[n]);
}
}
return 0;
}
一共有3种面值——1元、2元、5元,求有多少种组合。因为有1元的存在,不管2元和5元的怎么拼都能凑成恰好的钱数,所以可以不考虑1元的(只要2元和5元组合小于等于钱数即可);有几个2元的一共有(钱数/2+1(1是指不用两元的这种))种情况,所以现在讨论用几个5元的就可以了;5元的情况用一个for循环从0到钱数/5一扫就可以了。计数器计数。
代码如下:
#include<stdio.h>
main()
{
int n,i,j,m,cnt;
while(scanf("%d",&n)&&n!=0)
{
cnt=0;
for(i=0;i<=n/5;i++)
{
m=n;
cnt+=(m-5*i)/2+1;
}
printf("%d\n",cnt);
}
return 0;
}