题目链接:点我啊╭(╯^╰)╮
题目大意:
你的银行账户余额在
范围内
你需要把它全部取出来,若取
若当前余额
,则耗费
元取出
若当前余额
,则耗费
元,取出失败
问全取出最坏情况下的最低耗费
解题思路:
假设
,那么很明显是二分
每次取中点,这样一定最贪心
问题在于
,那么中点就不一定是最贪心的
所以要枚举中间每一个点
发现答案与区间长度
有关,与
无关
但是发现
时,当我们取到
时,无论成功失败,
都不需要再取
如果余额在
,当我们取到
时,如果失败,还要取
所以分两种情况DP:
表示 左端点为
,长度为
的答案,
则表示左端点不为
枚举
,若
失败,则范围变为
若
成功,则变为
这样的
是
的,很明显看出来区间长度越长,答案越大
也就是
单调不减,那么
里的函数就是一个单调不增,一个单调不减
所以
,就是先减后增
所以对与某一个
,设其决策点为
易得对于
的点,其决策点
只会往后移
均摊时间复杂度:
核心:DP + 单调优化
#include<bits/stdc++.h>
#define rint register int
#define deb(x) cerr<<#x<<" = "<<(x)<<'\n';
using namespace std;
typedef long long ll;
const int maxn = 2e5 + 5;
int T, x, y, a, b;
ll dp[maxn][2];
ll slope0(int j, int i){
return max(dp[j-1][0] + b, dp[i-j][0] + a);
}
ll slope1(int k, int i){
return max(dp[k-1][1] + b, dp[i-k][0] + a);
}
int main() {
scanf("%d", &T);
while(T--){
scanf("%d%d%d%d", &x, &y, &a, &b);
int n = y - x;
dp[0][0] = 0, dp[0][1] = a;
for(int i=1, j=1, k=1; i<=n; i++){
while(j<i && slope0(j+1, i) <= slope0(j, i)) j++;
while(k<i && slope1(k+1, i) <= slope1(k, i)) k++;
dp[i][0] = slope0(j, i), dp[i][1] = slope1(k, i);
}
printf("%lld\n", dp[n][x>0]);
}
}