Good Key, Bad Key[*1600][思维][DP][贪心]
你有 \(n\) 个箱子。第 \(i\) 个箱子中有 \(a_i\) 个硬币。你需要按照从箱子 \(1\) 号到箱子 \(n\) 号的顺序打开所有 \(n\) 个箱子。
你可以用以下两种钥匙之一打开一个箱子:
- 好钥匙:使用一次消耗 \(k\) 个硬币。
- 坏钥匙:使用时不消耗硬币,但会使所有未打开的箱子中的硬币数减半(包括正要打开的这个箱子)。硬币减半时向下取整。比如,用坏钥匙打开箱子 \(i\) 号时,$ a_i = \lfloor{\frac{a_i}{2}\rfloor} $ , $ a_{i+1} = \lfloor\frac{a_{i+1}}{2}\rfloor, \dots, a_n = \lfloor \frac{a_n}{2}\rfloor $ ;
所有钥匙用过一次就会断掉(别想着买一把好钥匙开完所有箱子了),好钥匙需要重复付费,坏钥匙效果会重复计算。
也就是说,你总共需要使用 \(n\) 把钥匙,每个箱子用一把。开始时,你没有硬币和钥匙,如果想用好钥匙,你就得去买。值得注意的是,在这个过程中你可以赊账买钥匙;例如,如果你只有 \(1\) 个硬币,你也可以购买价值 \(k=3\) 个硬币的好钥匙,你的余额会变成 \(-2\) 个硬币。
你需要求出开完所有箱子之后你能获得的最大硬币数量(显然大于等于 \(0\) )。
输入格式
第一行包含一个整数 \(t\)(\(1 \leq t \leq 10 ^4\)),表示测试数据的组数。
- 每组测试数据的第一行包含两个整数 \(n\) 和 \(k\)(\(1 \leq n \leq 10^5\),\(0 \leq k \leq 10^9\)),分别表示箱子的个数和每把好钥匙的花费。
- 每组测试数据的第二行包含 \(n\) 个整数 \(a_i\)(\(0 \leq a_i \leq 10^9\)),表示每个箱子中硬币的数量。
所有测试数据中 \(n\) 的总和不超过 \(10^5\) 。
提示
In the first test case, one possible strategy is as follows:
- Buy a good key for $ 5 $ coins, and open chest $ 1 $ , receiving $ 10 $ coins. Your current balance is $ 0 + 10 - 5 = 5 $ coins.
- Buy a good key for $ 5 $ coins, and open chest $ 2 $ , receiving $ 10 $ coins. Your current balance is $ 5 + 10 - 5 = 10 $ coins.
- Use a bad key and open chest $ 3 $ . As a result of using a bad key, the number of coins in chest $ 3 $ becomes $ \left\lfloor \frac{3}{2} \right\rfloor = 1 $ , and the number of coins in chest $ 4 $ becomes $ \left\lfloor \frac{1}{2} \right\rfloor = 0 $ . Your current balance is $ 10 + 1 = 11 $ .
- Use a bad key and open chest $ 4 $ . As a result of using a bad key, the number of coins in chest $ 4 $ becomes $ \left\lfloor \frac{0}{2} \right\rfloor = 0 $ . Your current balance is $ 11 + 0 = 11 $ .
At the end of the process, you have $ 11 $ coins, which can be proven to be maximal.
思路
DP思路
我们通过观察可以发现
每一个箱子选取哪一个钥匙都不会对之前选取过的箱子产生影响
由此我们可以想出状态转移的方程(\(j\)是选取的坏钥匙的数量)
\(f_{i,j}\)=\(max\)(\(f_{i-1,j}\)+\(coin[i][j]\)-\(k\),\(f_{i-1,j-1}\)+\(coin[i][j]\))
由此便变成了一道显然的DP题目
由于\(10^9\)的二进制最多有29位,因此我们可以预处理出来每个箱子处理30次的结果
这个常数项很小,可以接受
当是在31个箱子后面的时候,选用30个坏钥匙以后后面的硬币都会变成0个
因此便可以从 前一个个箱子的选用30个坏钥匙的状态转移过来
key code
const int N=2e5+10;
int n,m;
int a[N][35];
int dp[N][35];
int k;
void solve(){
//try it again.
cin>>n>>k;
fup(i,1,n){
fup(j,0,32){
dp[i][j]=-INF;
}
}
up(1,n){
cin>>a[o][0];
fup(j,1,30){
a[o][j]=(a[o][j-1]>>1);
}
}
fup(i,1,n){
fup(j,0,min(30ll,i)){
dp[i][j]=max(dp[i-1][j]+a[i][j]-k,dp[i][j]);
if(j-1>=0)dp[i][j]=max(dp[i-1][j-1]+a[i][j],dp[i][j]);
}
if(i>=31)dp[i][30]=max(dp[i-1][30],dp[i][30]);
}
int maxl=-INF;
fup(j,0,30)maxl=max(maxl,dp[n][j]);
cout<<maxl<<endl;
}
贪心思路
每一个好钥匙的贡献都是\(-k\)
每一个坏钥匙的贡献都是\(-\frac{s[n]-s[i-1]}{2}\)
我们可以发现当选用坏钥匙更优的时候,后面坏钥匙的贡献的绝对值将会越来越小
说明自从选用了第一个坏钥匙,后面所有的状态都是选用坏钥匙的
用此便可以引出贪心的做法
从后往前选节点
从节点往后遍历
key code
const int N=2e5+10;
int n,m,k;
int a[N];
int s[N];
void solve(){
//try it again.
cin>>n>>k;
up(1,n)cin>>a[o];
up(1,n)s[o]=s[o-1]+a[o];
int ans=s[n]-n*k;
int last=n;
fdn(i,n,1){
int sum=0;
fup(j,i,last){
a[j]/=2;
sum+=a[j];
}
while(!a[last])last--;
ans=max(ans,s[i-1]-(i-1)*k+sum);
}
cout<<ans<<endl;
}
总结
DP的确让人望而止步
但是如果看出状态转移方程来,那么代码也是比较容易写的
贪心需要发现问题的性质,对洞察力的需要比较高

浙公网安备 33010602011771号