2019HDU多校赛 第七场 HDU 6652 Getting Your Money Back(区间dp + 单调优化)_区间dp

2019HDU多校赛 第七场 HDU 6652 Getting Your Money Back(区间dp + 单调优化)_2019HDU多校赛_02

 

 

大致题意:告诉你你的存款余额在一个区间内,然后每次你可以猜一个数字,如果余额大于等于你猜的数字,那么你可以取走这些钱并且代价为a,否则为代不能取走且价为b。你可以多次重复这个动作,直到你能够确定你初始时的总共有多少钱。现在问你,最少花费多少的代价能够知道你初始时的账户余额。

这题有点像二分的意思,如果a和b相等,那么显然按照二分的策略即是最优。考虑到这一点,我们显然可以发现,这题的答案与具体区间在哪无关,只与区间大小有关。然后,可以分为两类,一类是左端点为0的,相当于左端点可以不考虑,另一类是左端点不为0的。正如之前所说的,如果是二分的话,是选择中间点作为分界,现在不是二分,我们就得考虑枚举这个分界点。我们考虑dp,令dp[i][0]表示跨度为i的区间左端点为0时最少的花费,dp[i][1]表示跨度为i且左端点不为0时最少的花费。很容易有转移方程:

                       

2019HDU多校赛 第七场 HDU 6652 Getting Your Money Back(区间dp + 单调优化)_区间dp_03

                       

2019HDU多校赛 第七场 HDU 6652 Getting Your Money Back(区间dp + 单调优化)_#define_04

具体表示枚举中间的分界点j,如果正确答案在j左边的时候代价为dp[j-1][0]+b,如果在右边的时候代价为dp[i-j][0]+a,取二者中大的表示最坏清空。然而,直接转移复杂度是O(N^2)的,本题这么做会TLE,所以要考虑进行优化。

显然,区间跨度越大,花费的代价也就越大,dp[i][0]和dp[i][1]是具有单调性的。然后考虑这个转移方程,取前后两部分大的那个,可以预见,代价随着决策点j从左往右移动先变小后变大。所以,我们可以考虑直接对这个转移方程进行三分,找到中间的最小决策。但是,用上三分理论复杂度可以过去,不过我写的确实TLE了,当然可能是我写残了。

于是,继续考虑转移方程,可以发现,我们转移的时候,随着区间跨度的变大,最优决策点也是单调不降的。因此我们可以利用这个性质把时间复杂度变成线性的。初始时决策点就在最左边,然后每次计算出答案后尝试移动决策点看是否会更优,如果更优那么往右移动。如此,时间复杂度均摊下来就是O(N)的,实际运行速度也是非常的块。具体见代码:

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f3f3f3f3fll
#define eps 1e-4
#define pi 3.141592653589793
#define mod 998244353
#define P 1000000007
#define LL long long
#define pb push_back
#define fi first
#define se second
#define cl clear
#define si size
#define lb lower_bound
#define ub upper_bound
#define bug(x) cerr<<#x<<" : "<<x<<endl
#define mem(x) memset(x,0,sizeof x)
#define sc(x) scanf("%d",&x)
#define scc(x,y) scanf("%d%d",&x,&y)
#define sccc(x,y,z) scanf("%d%d%d",&x,&y,&z)
using namespace std;

const int N = 200010;
LL dp[N][2];
int T,n,a,b;

int main(){

int T;
sc(T);
while(T--){
int x,y; scc(x,y);
n=y-x; scc(a,b);
for(int i=1;i<=n;i++)
dp[i][0]=dp[i][1]=0;
dp[0][0]=0; dp[0][1]=a;
for(int i=1,j=1,k=1;i<=n;i++)
{
LL tmp1=max(dp[j-1][0]+b,dp[i-j][0]+a);
LL tmp2=max(dp[k-1][1]+b,dp[i-k][0]+a);
while(j<i&&max(dp[j][0]+b,dp[i-j-1][0]+a)<=tmp1)
tmp1=max(dp[j][0]+b,dp[i-j-1][0]+a),j++;
while(k<i&&max(dp[k][1]+b,dp[i-k-1][0]+a)<=tmp2)
tmp2=max(dp[k][1]+b,dp[i-k-1][0]+a),k++;
dp[i][0]=tmp1; dp[i][1]=tmp2;
}
if(x==0) printf("%lld\n",dp[n][0]);
else printf("%lld\n",dp[n][1]);

}
}