接下来是动态规划里的经典问题----背包问题。
这小节讲得是0-1背包(ZeroOnePack)。
问题模型:
给定一个载重量为m,n个物品,其重量为wi,价值为vi,1<=i<=n,要求:把物品装入背包,每个物品都只有一件, 并使包内物品价值最大。
题目的要求是
(1)每种物品只有一个,要么取,要么不取。(这就是为何叫0-1背包 0就是不取,1就是取 只有这两种状态)
(2)背包有容量的大小,最多只能装重量为m的物品(不一定要装满)。
(3)使得物品价值最大。
很明显直接按(价值)贪心是不可行的。
例如m=105;
v={20,15,1,5}
w={100,10,10}
如果按照贪心去做,得到的是20,然而最优解是15+15.
然后尝试从取和不取这种思想去递归求解。
int solve(int W,int i)
{
if(i==n)
return 0;
if(w[i]>W)
{
return solve(W,i+1);
}
else
{
return max(solve(W-w[i],i+1)+v[i],solve(W,i+1));
}
}
我们可以得知上面的函数的复杂度为O(2^n)。是个指数级的复杂度,适用范围很小,当n很大时,程序会耗费很多时间。
所以我们可以将计算出来的结果记录下来,当需要用时直接返回即可(也称为记忆化搜索)。
int rec(int i,int j)
{
if(dp[i][j]>=0)
return dp[i][j];
if(i==n)
return 0;
else if(j<w[i])
{
return rec(i+1,j);
}
else
return max(rec(i+1,j),rec(i+1,j-w[i])+v[i]);
}
void solve()
{
//初始化为负值,代表未被计算过
memset(dp,-1,sizeof(dp));
printf("%d\n",rec(0,W));
}
这样将复杂度降低到O(nW)。即是参数的组合的个数。
最后是动态规划的思想,从前面的分析可得,
if(j<w[i])
dp[i+1][j]=dp[i][j]
else
dp[i+1][j]=max(dp[i][j],dp[i][j-w[i]]+v[i])
void solve()
{
for(int i=0;i<n;i++)
{
for(int j=0;j<=W;j++)
{
if(j<w[i]) //不能取
dp[i+1][j]=dp[i][j];
else //不取 和 取 的最大值
dp[i+1][j]=max(dp[i][j],dp[i][j-w[i]]+v[i]);
}
}
}
运行dp数组的变化:
图片摘自:http://www.cnblogs.com/fly1988happy/archive/2011/12/13/2285377.html
if(j<w[i])
dp[i+1][j]=dp[i][j]
else
dp[i+1][j]=max(dp[i][j],dp[i][j-w[i]]+v[i])
从状态转移方程可以看出计算dp[i+1][]时只用到dp[i]。所以可以将其空间从O(nW)优化成O(W)。void solve()
{
for(int i=0;i<n;i++)
{
for(int j=W;j>=w[i];j--)
{
dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
}
}
printf("%d\n",dp[W]);
}
第二个循环逆序表示dp[j]和dp[j-c[i]]+w[i]是前一个状态的。
最后是时间复杂度的常数级优化(当W比较大时有用)
由于只需要最后dp[W]的值,倒推前一个物品,其实只要知道dp[W-w[n]]即可。以此类推,
对以第i个背包,其实只需要知道到dp[W-sum{w[i..n]}]即可,即代码中的
for i=1..N
for v=V..w[i]
可以改成
for i=1..n
b=max{V-sum{w[i..n]},c[i]}
for v=V..b
这对于V比较大时是有用的。
代码实现:
memset(dp,0,sizeof(dp));
memset(s,0,sizeof(s));
for(int i=1;i<=n;i++)
{
scanf("%d %d",&v[i],&w[i]);
s[i]=s[i-1]+w[i];
}
for(int i=1;i<=n;i++)
{
int b=max(w[i],W-s[n]+s[i-1]);
for(int j=W;j>=b;j--)
dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
}
printf("%d\n",dp[W]);
推荐题目: HDU2602 HDU1864