(精选上好代码讲解产自原装进口白皮书)
01背包问题
有n个重量和价值分别为wi,vi的物品,从这些物品中挑选出总重量不超过W的物品,求所有挑选方案中价值总和的最大值。
首先我们用朴素的方式搜索一遍:
int rec(int i,int j){ int res; if(i==n) res=0;//已经没有剩余物品了 else if(j<w[i]) res=rec(i+1,j);//无法挑选这个物品 else res=max(rec(i+1,j),rec(i+1,j-w[i])+v[i]);//挑选和不挑选两种情况都尝试一下 return res; } /*搜索深度为n 时间O(2^n)*/
优化一下(记忆数组)
int dp[maxn][maxn]; int rec(int i,int j){ if(dp[i][j]>0) return d[i][j];//已经计算过的话直接使用之前的结果 int res; if(i==n) res=0; else if(j<w[i]) res=rec(i+1,j); else res=max(rec(i+1,j),rec(i+1,j-w[i])+v[i]); return dp[i][i]=res;//结果记录在数组中 }
不用递归函数,直接利用递推式:
int dp[maxn][maxn]; void fun() { for(int i = n - 1; i >= 0; i--) { for (int j = 0; j <= w; j++) { if(j < w[i]) { dp[i][j] = dp[i + 1][j]; } else { dp[i][j] = max(dp[i + 1][j], dp[i + 1][j - w[i]] + v[i]); } } } cout<<dp[0][w]<<endl; }
以上DP都是关于i的逆向进行的循环
那么正向定义如下:
if (j<w[i]) f[i][j] = f[i-1][j] else f[i][j] = max(f[i-1][j], f[i-1][j-w[i]] + v[i])
举个例子
(w,v)//w是重量,v是价值
(2,3)
(1,2)
(3,4)
(2,2)
i\j |
0 |
1 |
2 |
3 |
4 |
5 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
0 |
0 |
3 |
3 |
3 |
3 |
2 |
0 |
2 |
3 |
5 |
5 |
5 |
3 |
0 |
2 |
3 |
5 |
6 |
- |
如图所示,求dp[3][4]=max(dp[2][4],dp[2][1]+4)
有关01背包的练手题~
题意:求最长公共子序列
思路:01背包模板
#include<stdio.h> #include<string.h> #define MAX(a,b) (a>b?a:b) const int MAXN=1010; int dp[MAXN][MAXN]; char a[MAXN],b[MAXN]; int main(){ while(~scanf("%s%s",a+1,b+1)){ memset(dp,0,sizeof(dp)); int i,j; for( i=1;a[i];i++){ for(j=1;b[j];j++){ if(a[i]==b[j])dp[i][j]=dp[i-1][j-1]+1; else dp[i][j]=MAX(dp[i][j-1],dp[i-1][j]); } } printf("%d\n",dp[i-1][j-1]); } return 0;}