HDU 2955
这道题如果把被抓概率当作背包容量,将会十分麻烦,因为概率不一定是2位小数,放大后空间和时间上都过不去。这时候需要转化,我们可以求盗取i单位钱不被抓获的的可能性最大值,容量变为盗取的钱数,问题就好解决了。
#include <iostream>
#include <stdio.h>
#include <string.h>
using namespace std;
int main()
{
int t;
scanf("%d", &t);
while(t--){
double p;
int n;
scanf("%lf %d", &p, &n);
int sum = 0;
double c[105];
int v[105];
for(int i = 1; i <= n; i++){
scanf("%d %lf", &v[i], &c[i]);
sum += v[i];
c[i] = 1 - c[i];
}
double dp[10005];
for(int i = 1; i <= 10004; i++) dp[i] = 0;
dp[0] = 1;
for(int i = 1; i <= n; i++)
for(int j = sum; j >= v[i]; j--)
dp[j] = max(dp[j], dp[j - v[i]] * c[i]);
int res = 0;
for(int i = sum; i >= 0; i--){
if(dp[i] - 1 + p > 1e-6){
res = i;
break;
}
}
printf("%d\n", res);
}
return 0;
}
HDU3496
这个01背包扩展到了二维。题目限定必须购买某个数量的物品,不能多不能少。这种限定条件的应对策略是将dp[0][0...V]初始化为0,其他初始化为负无穷大。这样做的理由是:如果要求背包恰好装满,那么此时只有容量为0的背包可能被价值为0的“恰好装满”,其它容量的背包均没有合法的解,属于未定义的状态,它们的值就都应该是-∞了。如果背包并非必须被装满,那么任何容量的背包都有一个合法解“什么都不装”,这个解的价值为0,所以初始时状态的值也就全部为0了。
#include <iostream>
#include <stdio.h>
#include <string.h>
using namespace std;
const int INF = 999999;
int main()
{
int t;
scanf("%d", &t);
while(t--){
int n, m, l;
scanf("%d %d %d", &n, &m, &l);
int c[105], v[105];
for(int i = 1; i <= n; i++) scanf("%d %d", &c[i], &v[i]);
int dp[105][1005];
memset(dp, -INF, sizeof(dp));
for(int i = 0; i <= l; i++) dp[0][i] = 0;
for(int i = 1; i <= n; i++)
for(int j = m; j >= 1; j--)
for(int k = l; k >= c[i]; k--)
dp[j][k] = max(dp[j][k], dp[j - 1][k - c[i]] + v[i]);
if(dp[m][l] < 0) dp[m][l] = 0;
printf("%d\n", dp[m][l]);
}
return 0;
}<strong>
</strong>
做了几天的01背包,这里做个小结。
1.循环的起始和顺序
for(int i = 1; i <= n; i++)
for(int j = sum; j >= c[i]; j--)
dp[j] = max(dp[j], dp[j - c[i]] + v[i]);
这是01背包最基本的循环。它可行的原因是:对第i个物品进行选择时,前i-1个物品已经处理完毕,并且dp[j - c[i]]已经得到。因此必须先对物品编号进行循环,而占用的容量必须从大到小枚举。
2.如果限定背包必须装满,可以参考HDU3496的应对策略。
3.一个问题中的容量、价值不是固定的,确定何者为容量,何者为价值,往往是解决难题的关键。对复杂的问题,必须要化归为基本的背包问题。
4.一点体会:dp很抽象,而且一个状态转移方程摆出来,有的看似神奇,需要慢慢揣摩;有的看似理所当然,实则需要分析它的循环起始、初始化等等,copy一遍当然简单,但是理解不了精髓。一个比较好的学习方法是:打印出每个状态,看看每一步发生了什么,状态是如何转移的,想想为什么这个方程可行。