CF922E Birds (DP)
CF922E Birds (DP)
题目传送门
题目大意(翻译):
在一个公园的小巷里有一排树,每棵树上都有一个巢。在 i 的第一个巢里有 ci 只鸟;要从这个巢穴召唤一只鸟,小鬼需要呆在这棵树下,这需要消耗他的法力点。然而,每召唤一只小鸟,小鬼的法力值就增加 B 点。小恶魔可以逐个召唤鸟,他可以从 0 到 ci 召唤 i 个巢中的任意数量的鸟。
最初小鬼站在第一棵树下,法力值为 W 点,他的法力值也等于 W 点。他只能向前移动,每次从一棵树移动到下一棵树时,他恢复 X 点法力值(但不能超过他当前的法力值)。只向前移动,恶魔能召唤的鸟的最大数量是多少?
思路分析:
如果我们考虑直接对每一棵树的每一种情况进行 dp 的话,会发现这个不可行,因为每一棵树的鸟的数量都一样,并且状态转移方程十分难写
所以我们换一种思路,用 dp[i][j] 来表示到第 i 棵树召唤了 j 只鸟剩的最大魔力,这样我们的状态转移方程就好写了,直接见代码吧(添加了注释)(太懒了)
(蒟蒻的第一篇题解,这题也是研究了别人的题解才做出来的,路过的道友们酌情观看QWQ)
#include<iostream>
#include<algorithm>
#include <vector>
using namespace std;
typedef long long ll;
int n, w, b, x;
// -- 初始化为-1表示没有取到过j只鸟
// 不能初始化为0是因为dp[j]=0可能是由于取了j只鸟后才变为0,所以j是我们应该考虑的答案
// -- 仔细观察dp的过程可以发现这里的dp数组可以去掉一个维度
vector<ll> dp(10010, -1);
ll c[1010], va[1010];
ll p[20];
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
p[0] = 1;
// 用这个p数组可以快速的枚举出每一个数的每一种情况(具体见dp过程)
// 原理可以去问问ai,~~我也不太懂怎么来的
for(int i = 1;i <= 15;i ++) p[i] = p[i - 1] * 2;
cin >> n >> w >> b >> x;
dp[0] = w;
for(int i = 1;i <= n;i ++) cin >> c[i];
for(int i = 1;i <= n;i ++) cin >> va[i];
ll sm = 0;
for(int i = 1;i <= n;i ++){
// sm 表示当前有可能取到的最多的数量
sm += c[i];
// 每走一步恢复,但不能超出限制
for(int j = 0;j <= sm;j ++){
if(dp[j] == -1) continue;
dp[j] = min(dp[j] + x, 1LL * j * b + w);
}
for(int j = 0;j <= 14;j ++){
if(c[i] >= p[j]){
c[i] -= p[j];
// 倒序枚举
for(int k = sm;k >= p[j];k --){
if(dp[k - p[j]] < p[j] * va[i]) continue;
dp[k] = max(dp[k], dp[k - p[j]] - p[j] * va[i]);
}
}else break;
}
if(c[i]){
for(int j = sm;j >= c[i];j --){
if(dp[j - c[i]] < c[i] * va[i]) continue;
dp[j] = max(dp[j], dp[j - c[i]] - c[i] * va[i]);
}
}
}
for(int i = sm;i >= 0;i --){
if(dp[i] >= 0){
cout << i;
break;
}
}
return 0;
}

浙公网安备 33010602011771号