传送门
斜率优化
当求dp[i]最小值时,满足一次函数 (y=kx+b)
其中 y=dp[j] ,k 与 i 有关,x 与 j 有关,b中包含 dp[i]。
这时问题就可以转化为 在众多点(x,y)中找到一个点使得用斜率为k的直线切这个点时得到的截距b最小。很显然这时dp[i]从这个点转移过来最优。
具体操作是维护一个相邻两点斜率单调递增的下凸壳。
用单调队列维护。
以求dp[i]为例。
先在单调队列中三分找到两点间斜率刚好>=k的点,然后比较一下取最优点进行转移。
然后不断判断队尾点是否符合斜率要求并不断弹出,最后将i插入到队尾。
总复杂度为O(nlogn)
实际上就是把枚举j的复杂度O(n)优化到了O(logn)。
当然有的题目k是单调递增的,这时候就可以每次用k和队首两个点比较并不断弹出队首,每次更新时队首的点即为最优点,这样也省去了三分的复杂度。
这个题就是这个特殊情况。
AC代码
#include<cstdio>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cmath>
#include<algorithm>
using namespace std;
const int maxn=5005;
int n,s;
long long dp[maxn],sumt[maxn],sumf[maxn],q[maxn],l=1,r=1;
inline double cal(int l,int r){
return 1.0*(dp[r]-dp[l])/(sumf[r]-sumf[l]);
}
int main(){
ios::sync_with_stdio(false);
cin>>n>>s;
for(int i=1;i<=n;i++){
cin>>sumt[i]>>sumf[i];
sumt[i]+=sumt[i-1];
sumf[i]+=sumf[i-1];
}
dp[0]=q[1]=0;
for(int i=1;i<=n;i++){
while(l<r&&dp[q[l+1]]-dp[q[l]]<=(s+sumt[i])*(sumf[q[l+1]]-sumf[q[l]])) l++;
dp[i]=dp[q[l]]+s*(sumf[n]-sumf[q[l]])+sumt[i]*(sumf[i]-sumf[q[l]]);
while(l<r&&cal(q[r-1],q[r])>=cal(q[r],i)) r--;
q[++r]=i;
}
cout<<dp[n];
return 0;
}