0-1揹包:
0-1揹包问题描述:有N件物品和一个容量为C的揹包。第i件物品的重量是w[i],
这也正是0-1的意义所在。可以把部分揹包问题看作是拿金粉,而0-1揹包问题
则是拿金块,一个可分,一个不可分。
状态转移方程:
这是最基础的揹包问题,特点是:每种物品仅有一件,可以选择放或不放。
用子问题定义状态:即f[i][w]表示前i件物品恰放入一个容量为c的揹包
可以获得的最大价值。则其状态转移方程便是:
f[i][c]=max{f[i-1][c],f[i-1][c-w[i]]+v[i]}
(前i个等于前i-1个(选i还是不选i))
//完全揹包
有n种重量和价值分别为wi,vi的物品。从这些物品中挑选总重量不超过W的
物品,求出挑选物品价值总和的最大值。在这里,每种物品可以挑选任意多件。
//如果延续之前的思路:那么
//
这个问题非常类似于0-1揹包问题,所不同的是每种物品有无限件。也就是从每种物品的角度考虑,与它相关的策略已并非取或不取两种,而是有取0件、取1件、取2件……等很多种。如果仍然按照解01揹包时的思路,令f[i][c]表示前i种物品恰放入一个容量为c的揹包的最大权值。仍然可以按照每种物品不同的策略写出状态转移方程,像这样:
f[i][c]=max{f[i-1][c-k*w[i]]+k*w[i]| 0<=k*w[i]<=c }
这跟0-1揹包问题一样有O(CN)个状态需要求解,但求解每个状态的时间已经不是常数了,求解状态f[i][c]的时间是O(c/w[i]),总的复杂度可以认为是O(CN*Σ(c/w[i])),是比较大的。实现代码如下:
#####完全揹包问题 解法1########
def knap_complete(N, C):
for i in range(0, N):
for c in range(C, -1, -1):
for k in range(0, c/w[i]+1):
if k*w[i] <= c:
f[c] = max(f[c], f[c-k*w[i]] + k*v[i])
也可以再化解:
dp[i+1][j]
=max{dp[i][j-k*w[i]]+k*v[i]|0<=k}
=max(dp[i][j],max{dp[i][j-k*w[i]]+k*v[i]|1<=k})
=max(dp[i][j],max{dp[i][(j-w[i])-k*w[i]]+k*v[i]|0<=k}+v[i])
=max(dp[i][j],dp[i+1][j-w[i]]+v[i])
//也用这个方程去求解(解法#2)。
//题目:
You are given coins of different denominations and a total amount of money amount. Write a function to compute the fewest number of coins that you need to make up that amount. If that amount of money cannot be made up by any combination of the coins, return -1.
Example 1:
Input: coins = [1, 2, 5], amount = 11
Output: 3
Explanation: 11 = 5 + 5 + 1
Example 2:
22
Input: coins = [2], amount = 3
Output: -1
Note:
You may assume that you have an infinite number of each kind of coin.
//minCoin[n]表示和为n时,硬币的最小数量,满足最优子结构(解法#3)
import java.util.Arrays;
import java.util.Collections;
class Solution {
private int minCoin[]=null;
public int coinChange(int[] coins, int amount) {
minCoin=new int[amount+1];
Arrays.fill(minCoin,-2);
return coinCount(coins,amount);
}
public int coinCount(int[] coins,int amount)
{ //System.out.println(amount);
if(amount<0)
{
return -1;
}
if(minCoin[amount]!=-2)
{
return minCoin[amount];
}
else
{
if(amount==0)
{
minCoin[amount]=0;
return 0;
}
int minValue=Integer.MAX_VALUE;
for(int i=0;i<coins.length;i++)
{
//System.out.println(amount-coins[i]);
int childValue=coinCount(coins,amount-coins[i])+1;
//System.out.println("&&"+childValue+"&&");
if(childValue!=0)
{
if(minValue>childValue)
{
minValue=childValue;
}
}
}
if(minValue==Integer.MAX_VALUE)
{
minCoin[amount]=-1;
return -1;
}
else
{
minCoin[amount]=minValue;
return minValue;
}
}
}
public static void main(String [] args)
{
Solution solution=new Solution();
int []coins={1,2,5};
int amount=11;
int result=solution.coinChange(coins,amount);
System.out.print(result);
}
}
//换一种写法
时间限制:1秒
空间限制:32768K
给你六种面额 1、5、10、20、50、100 元的纸币,假设每种币值的数量都足够多,编写程序求组成N元(N为0~10000的非负整数)的不同组合的个数。
输入描述:
输入包括一个整数n(1 ≤ n ≤ 10000)
输出描述:
输出一个整数,表示不同的组合方案数
输入例子1:
1
输出例子1:
1
//超时解:
//仿照楼上例子写的一个的例子,超时,
//由于这里是求所有的组合数如果用dp[n]表示何为n的所有组合数
//dp[n]=dp[n-v[0]]+dp[n-v[1]]+...
//这里存在可能重复的组合数
//例如:dp[n-v[0]]和dp[n-v[1]]可能完全一样,只是取的顺序不同
//所以这种状态转移方程不可行
//楼下这种复杂度太高,指数级的。
import java.util.*;
public class Main{
public static int v[]={1,5,10,20,50,100};
public static void main(String [] args)
{
Scanner sc=new Scanner(System.in);
int n=sc.nextInt();
dfs(n);
System.out.println(sum);
}
public static int sum=0;
public static void dfs(int n)
{
if(n<0)
{
return ;
}
if(n==0)
{
sum=sum+1;
return ;
}
for(int i=0;i<v.length;i++)
{
dfs(n-v[i]);
}
}
}
//换一种思路(参照完全揹包的状态转移方程):
f[i][c]=sum{f[i-1][c-k*w[i]]+k*w[i]| 0<=k*w[i]<=c }
//子问题没有相交。所以是可行的。
多重揹包
问题描述:
有N件物品和一个容量为M的揹包。第i件物品的费用是w[i],
价值是v[i],数量是c[i]。求解将哪些物品装入揹包可使价值总和最大。
首先,定义状态d[i][j]:d[i][j]表示前i个物品剩余体积为M的情况下的最大价值
接着确定状态转移方程,还是和完全揹包一样
这里写图片描述
和完全揹包问题的状态转移方程相比,多重揹包问题的状态转移方程仅仅多了一个限制条件:0<=k<=c[i]。
完整代码如下:
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int N = sc.nextInt();
int M = sc.nextInt();
int[] d = new int[M+1];
for (int i = 1; i <= N; i++) {
int w = sc.nextInt();
int v = sc.nextInt();
int c = sc.nextInt();
for (int j = M; j >= M; j--) {
for (int k = 0; k <= c && k*w<=j; k++) {
d[j] = d[j]>d[j-k*w]+k*v?d[j]:d[j-k*w]+k*v;
}
}
}
System.out.println(d[M]);
}
}