题目链接:
https://codeforces.com/problemset/problem/1703/G
题意:
有 \(n\) 个箱子需要按顺序打开,第 \(i\) 个箱子中有 \(a_i\) 个金币,可以用好钥匙和坏钥匙去开箱子,好钥匙需要花费 \(k\) 个金币,坏钥匙不花费金币,但是会使当前这个箱子开始到第 \(n\) 个箱子中的金币全部减半。开箱的过程中金币可以是负数的,即可以赊账,问最多能留下多少硬币。
思路:
对于第 \(i\) 个箱子,选择哪个钥匙取决于哪个钥匙的花费少,当坏钥匙的花费小于好钥匙的花费的时候,后面所有的箱子都会采用坏钥匙去开。
因为当前用了坏钥匙,后面的箱子的金币减半,那么坏钥匙的花费就更小了,所以整个开箱的过程分为两段,前面是好钥匙,后面是坏钥匙。
对于坏钥匙而言,最多用 32 次,后面所有的箱子就没有金币了。所以可以采用暴力的做法,枚举使用坏钥匙的第一个箱子,去求出最大的剩余金币。
#include <bits/stdc++.h>
using namespace std;
#define LL long long
void solve(){
LL n, k;
cin >> n >> k;
vector <LL> a(n + 1), s(n + 1), mi(40);
for (int i = 1; i <= n; i ++ ){
cin >> a[i];
s[i] = s[i - 1] + a[i];
}
mi[1] = 2;
for (int i = 2; i <= 32; i ++ )
mi[i] = mi[i - 1] * 2;
LL ans = 0;
for (int i = 0; i <= n; i ++ ){
LL sum = s[i] - i * k;
for (int j = i + 1; j <= min(i + 32LL, n); j ++ ){
sum += a[j] / mi[j - i];
}
ans = max(ans, sum);
}
cout << ans << "\n";
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);
LL T;
cin >> T;
while(T -- ){
solve();
}
return 0;
}
坏钥匙最多 32 个,所以也可以采用 \(dp\),定义 \(dp[i][j]\) 为前 \(i\) 个箱子用了 \(j\) 个坏钥匙所获得的最大的收益。
#include <bits/stdc++.h>
using namespace std;
#define LL long long
const LL INF = 1e18;
void solve(){
LL n, k;
cin >> n >> k;
vector <LL> a(n + 1);
vector < vector<LL> > dp(n + 1, vector<LL> (40, -INF) );
for (int i = 1; i <= n; i ++ )
cin >> a[i];
dp[0][0] = 0;
for (int i = 1; i <= n; i ++ ){
for (int j = 0; j <= 33; j ++ ){
dp[i][j] = max(dp[i][j], dp[i - 1][j] + (a[i] >> j) - k); //使用好钥匙
if (j)
dp[i][j] = max(dp[i][j], dp[i - 1][j - 1] + (a[i] >> j) ); //使用坏钥匙
}
dp[i][33] = max(dp[i][33], dp[i - 1][33]); //注意更新使用了所有坏钥匙的情况
}
LL ans = -INF;
for (int j = 0; j <= 33; j ++ )
ans = max(ans, dp[n][j]);
cout << ans << "\n";
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);
LL T;
cin >> T;
while(T -- ){
solve();
}
return 0;
}