[Luogu] P1899 魔法物品
Luogu P1899 魔法物品
题目描述:
有两种类型的物品:普通物品和魔法物品。普通物品没有魔法属性,而魔法物品拥有一些魔法属性。每种普通物品有一个价值\(P\),但每种魔法物品有两种价值:鉴定前的价值\(P_{1}\)和鉴定后的价值\(P_{2}\)(当然,\(P_{1}\)总是大于\(P_{2}\))。
为了鉴定一个魔法物品,你需要购买一个魔法卷轴,用它来鉴定魔法物品。鉴定完一件魔法物品之后,鉴定卷轴就会消失。每个鉴定卷轴需要\(S\)元钱,如果没有足够的钱,你将无法购买任何魔法卷轴。
现在,你正在一个集市中,同时拥有很多物品。你知道每件物品的价值并且想要出售全部物品。那么,你最多可以获得多少钱呢?
你可以假定:
- 开始的时候你没有钱。
- 所有的魔法物品还没有被鉴定。
- 只要你有足够的钱,你可以购买任意多的魔法卷轴。
输入输出格式:
输入格式(magic.in):
第一行有两个整数\(N\)和\(S\)(\(0 < S \leqslant 10000\)),表示你拥有的物品数和一个鉴定卷轴价格。
接下来\(N\)行,每行给出一件物品的价格。
对于每件普通物品,那一行仅有一个整数\(P\)(\(0 < P \leqslant 10000\))。
对于每件普通物品,那一行将会有两个整数\(P_{1}\)和\(P_{2}\)(\(0 < P_{1} < P_{2} \leqslant 10000\))。
输出格式(magic.out):
一个整数表示你最多能够获得多少钱。
样例:
输入#1:
2 10 10 20 100
输出#1:
100
数据规模:
对于30%的数据,\(N \leqslant 50\);
对于100%的数据,\(N \leqslant 1000\)。
题解:
首先考虑输入时的处理。显然在处理普通物品的时候,可以直接选择把他们卖掉,作为买卷轴的本钱。其次考虑魔法物品。当魔法物品的\(P_{2}\)减掉\(S\)不大于\(P_{1}\)时,把它卖掉也是一定一的可以的。由此,我们将只把\(P_{2} - S \geqslant P_{1}\)的魔法物品存下来。
其次进行对魔法物品的处理。(为方便表示,设\(ans\)为卖掉所有可卖物品后手中的金钱,\(P_{i,1}, P_{i, 2}\)分别代表第\(i\)个待处理魔法物品的\(P_{1}, P_{2}\),\(tot\)为所有物品的\(P_{1}\))。
- 首先是当\(ans \geqslant S\)时。因为所有待处理的物品\(i\)都满足\(P_{i, 2} - S \geqslant P_{i, 1}\),所以,先买到一个魔法卷轴、鉴定并且出售第一个魔法物品后,\(ans\)的值一定比买卷轴之前大。所以直接把所有未处理的魔法物品进行鉴定,然后按魔法价格售出即可。
- 其次是当\(ans < S\)时。我们需要售出一些魔法物品使\(ans \geqslant S\),之后即可按上一种情况处理。这个时候就需要跑个DP来计算卖哪个(哪些)比较优。可得\(P_{i, 2} - S - P_{i, 1}\)为第\(i\)个物品的损失,转移方程:\(F[j] = min(F[j], f[j - P_{i, 1}] + (P_{i, 2} - S - P_{i, 1}))\)(其中\(F[j]\)为不算\(ans\)在内,再得到\(j\)元时所产生的最小损失和)。跑一遍DP计算最小的损失(设为\(minn\))(\(S - ans \leqslant j \leqslant tot\))。如果能够凑出使\(ans \geqslant S\)的金钱的话,则按情况一处理,然后\(ans - minn\)即可;如果不能,意味着一个魔法卷轴都买不到,直接输出\(tot\)即可。
另:如果单纯按情况二写的话,时间复杂度会出锅(\(N * tot \leqslant 1e^{10}\));所以需要将DP稍微优化一下:令\(F[j]\)代表当所得金钱\(\geqslant j\) 时最小的代价和。转移方程:\(F[j] = min(f[j], f[max(j - P_{i, 1}, 0)] +(P_{i, 2} - S - P_{i, 1}))\)。(\(N * S \leqslant 5e^{6}\))
代码:
#include <cstdio>
const int INF = 999999999;
const int MAXP = 5000;
int n, s, top, ans, tmp, tmp2, tmp3, tot, xxx;
int p[1010], o[1010], f[5001];
char c(0);
inline int min(int a, int b) {
return (a < b ? a : b);
}
inline int max(int a, int b) {
return (a > b ? a : b);
}
int main() {
scanf("%d%d", &n, &s);
for (int i = 1; i <= n; ++i) {
scanf("%d", &tmp);
tot = tot + tmp;
scanf("%c", &c);
if(c == ' ') {
scanf("%d", &tmp2);
if(tmp2 - s > tmp) {
p[++top] = tmp2;
o[top] = tmp;
}
else ans = ans + tmp;
} else {ans = ans + tmp;}
}
xxx = ans; //存储ans的值,因为下一步要对ans进行操作
for (int i = 1; i <= top; ++i) //按情况一处理
ans = ans + p[i] - s;
if(xxx < s){ //如果为情况二
tmp3 = min(MAXP, tot); //因为f[j]代表了大于等于j的最小值,所以DP跑到卷轴价格的最大值5000即可
for (int i = 1; i <= tmp3; ++i) f[i] = INF;
for (int i = 1; i <= top; ++i) {
tmp = o[i]; tmp2 = p[i] - s - tmp;
for (int j = tmp3; j >= 1; j--)
f[j] = min(f[j], f[max(j - tmp, 0)] + tmp2); //取max(j-tmp,0)以免得负值
}
int minn(INF);
for (int i = s - xxx; i <= tmp3; ++i) //取最小代价和
minn = min(minn, f[i]);
if(minn < INF) printf("%d\n", ans - minn); //
else printf("%d\n", tot);
} else {printf("%d\n", ans); return 0;}
return 0;
}