01背包,,,非常灵活的变形。
给你很多个银行(物品),每个银行的钱数(重量)和被抓概率(价值),一个人去抢劫这些银行,但是总体被抓概率要小于一个固定值P
,在这基础上最多能抢多少钱。(一个银行要么不被抢,要么全被抢)
乍一看肯定是钱数当成价值啊,因为正好要求最大化钱数,所以被抓概率就是重量了?。。不行的。概率值是个小数,而dp[]
的下标就是当前背包容量,这概率连乘起来位数多了去了,数组下标没法搞。
所以只能概率当成价值,钱数当成重量(N*Mj<=10000
)。然后再看这题,它给的是被抓概率,然而总体被抓概率不是被抓概率的连乘,所以我们翻一下,把每个银行的不被抓概率当成价值。
这就变成了:(在可供抢劫范围为前i
个银行的情况下,)抢劫不超过当前钱数下,最大的不被抓概率是多少。
但是,细心的话你会发现一个问题。 不抢一家银行的不被抓概率最大,那么所有钱数下的最大不被抓概率都是1
。怎么办?其实考虑dp
的初始值也能发现,按理说,本来dp
的初值应该都是1.0
,但那样的话就永远不会再被更新了。
所以要加个限制,必须恰好装满。 所以只有dp[0]
的初值是1.0
,其他都是不合法的值(因为是乘积传递,用0.0
就可以了)。
现在问题变成了:(在可供抢劫范围为前i
个银行的情况下,)恰好抢劫当前钱数下,最大的不被抓概率是多少。
接下来运行dp
,最后,我们从大到小枚举钱数,当概率第一次大于P
时,此时的钱数就是答案了。为什么能这么求?因为我们算的都是最大不被抓概率,如果最大概率都无法大于一个下限,那么意味着这个钱数下肯定无解;如果之后钱数下的概率比答案更大,那么也没有用,因为只要概率大于下限了,我们并不关心哪个更大,我们只关注哪个抢的钱多。
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <vector>
#include <cstring>
#include <string>
#include <queue>
using namespace std;
int N, T;
double P;
double dp[10001]; // 恰好!!抢劫这些钱的最大逃脱概率,背包容量是当前抢劫钱数
double p[101];
int w[101];
int sum; // 背包容量
void init()
{
fill(dp + 1, dp + 10001, 0.0); // 恰好装满系列。价值是乘积,需要额外注意初始化!
dp[0] = 1.0;
sum = 0;
}
int main()
{
scanf("%d", &T);
for (; T--;)
{
scanf("%lf%d", &P, &N);
P = 1.0 - P;
init();
for (int i = 1; i <= N; i++)
{
scanf("%d%lf", &w[i], &p[i]);
p[i] = 1.0 - p[i]; // 所谓逃脱概率 = (1-p[1])*(1-p[2])*(1-p[3])... p[i]表示该次被抓概率
sum += w[i];
}
for (int i = 1; i <= N; i++)
{
for (int j = sum; j >= w[i]; j--)
{
dp[j] = max(dp[j], dp[j - w[i]] * p[i]);
//cout << " i= " << i << " j= " << j << " " << dp[j] << endl; 弄不清的时候,不妨看看运行过程
}
}
for (int i = sum; i >= 0; i--)
{
if (dp[i] > P)
{
printf("%d\n", i);
break;
}
}
}
return 0;
}