01背包,有一点变形。
给一些发票(物品),每张发票都有一个报销额(价值),每张发票还有一个判断合不合法的过程(这题很坑,话都说不明白。它要求的是每张发票上的A
,B
,C
每类都不大于600.0
,而不是每一项),每张发票要么全报要么全不报。问你在总报销额不大于Q
的情况下最大的总报销额是多少。
刚一看会发现一个问题,它没有一个显式的背包容量的概念。(当然,这道题可以把价值和重量当成一个东西,有人这样做出来了,但是我不想这样做,因为这题没有给明确的小数位数,还得猜)
既然它没给每件物品的重量,那么就用每张发票本身来表示,每张发票的重量都是1
,背包容量就是发票的张数。
现在的问题变成:(在前i
张发票可供选择的情况下,)选择不超过j
张发票,可获得的最大报销额是多少。
这个问题变得很微妙,i
,j
的取值范围是一样的,但是意义不同。i
是限制选取范围,j
是限制选取数量。i
的实际意义是id
,j
的实际意义是数量。
其实可以发现,因为每件物品的重量都是单位值1
(能整除任何容量),所以这道题当j<=i
时,其实都是默认恰好装满的。
最后,只要从大到小枚举张数就好了,碰见第一个不大于Q
的值就是答案。
(不针对这道题:在求最大价值、已运行完毕的前提下,dp
序列的值(背包容量从小到大)一定是递增的(不是严格递增)。而如果限制了恰好装满,则不满足)。
(如果这道题问不大于Q
的情况下最多报多少张,该怎么求?(改成最小值的恰好装满,用最小值去拟合这个上限Q
))
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <vector>
#include <cstring>
#include <string>
#include <queue>
using namespace std;
double Q; int N; // 定义不同类型不能用逗号隔开,相同类型才可以
double p[31];
double dp[31];
bool invalid[31];
int num;
void init()
{
fill(dp, dp + 31, 0.0); // 初始化double类型不要用memset
memset(invalid, 0, sizeof invalid);
num = 0;
}
int main()
{
int a; char c; double b; //
for (; ~scanf("%lf%d", &Q, &N);)
{
if (N == 0) break;
init();
for (int i = 1; i <= N; i++)
{
double A_sum = 0.0, B_sum = 0.0, C_sum = 0.0, sum = 0.0;
scanf("%d", &a);
for (; a--;)
{
scanf(" %c:%lf", &c, &b); // 这里要吸收一个空格,非常重要!
if (invalid[i]) continue; // 还得等它输入完这张发票的其他项
if (c == 'A') A_sum += b; // 是一张上的单类不能超600,不是单项
if (c == 'B') B_sum += b;
if (c == 'C') C_sum += b;
sum += b;
if ((c != 'A' && c != 'B' && c != 'C') || (A_sum > 600.0 || B_sum > 600.0 || C_sum > 600.0) || sum > 1000.0)
{
invalid[i] = true;
continue;
}
}
if (!invalid[i])
{
p[i] = sum;
num++; // 其实可以直接拿num当下标,下面就不用判断了
}
}
for (int i = 1; i <= N; i++)
{
if (!invalid[i])
{
for (int j = num; j >= 1; j--) // 每张发票的重量都是“1”
{
dp[j] = max(dp[j], dp[j - 1] + p[i]);
}
}
}
for (int i = num; i >= 0; i--)
{
if (dp[i] <= Q)
{
printf("%.2lf\n", dp[i]);
break;
}
}
}
return 0;
}