DP斜率优化
DP斜率优化,实际上就是以斜率作为比较的基准,使用单调队列优化的方法,使用斜率优化的问题应当具有这样的特征——状态转移方程是一个关于dp[i]和dp[j]的线性方程,也即dp[i]与dp[j]具有线性关系。
这样我们在求解dp[i]时,实际上就是求解一个点(dp[j],j),使得过该点的dp[i]的值最小,由于斜率是固定的,这导致我们只需要寻找与dp[j]的点形成的凸包相切的那一点作为最优转移点即可,实际求解过程中,我们只需要用单调队列维持边界上的点,再用二分查找的方式确定相切的点即可。
这样描述还是比较抽象的,我们以实际例题说明吧。
例题
任务安排1
这道题的数据量,我们只要设计出时间复杂度为N^2的即可满足条件。
写出朴素的状态转移方程我们发现,其时间复杂度是O(N^3)的,主要原因在于我们需要保存两个维度的信息,其中一个维度是用来表示当前已经分成的段数,因为每多分一段,就会多停机一次,会对后续结果产生影响。
而我们如果换个思路,在我们考虑将[j,i]的作为一个整体处理时,这一阶段的停机会对[j,n]的所有任务都附加一个s*c[k]的代价,那么这些代价我们在朴素的思想中是在求解dp[i]时集中计算的,这里我们考虑将对后面任务产生的代价都提前附加到求解dp[i]时的代价中,这在本质上是等效的,此时,我们就可以将时间复杂度降低到平方级,可以满足本题的条件。这种思想是一种名为费用提前计算的经典思想。
//做一个复杂DP问题时,先写朴素方法,后优化
//本题中用到的信息有分段数以及任务数,所以dp定义为两维即可表述所有信息
#include<bits/stdc++.h>
using namespace std;
int n,s;
int t[5001];
int c[5001];
long long sumt[5010];
long long sumc[5010];
void simple(){
//朴素动态规划
//当s==0时,此时我们无需考虑阶段数目,只需要直接求解最优值即可
if(s==0){
long long ans=0;
//此时一定是一个任务一段最优
for(int i=1;i<=n;i++)ans+=c[i]*sumt[i];
cout<<ans<<endl;
return;
}
long long dp[n+1][501];//表示是前i个任务分成若干个阶段后的最小费用和
memset(dp,127,sizeof(dp));
dp[0][0]=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=min(i,500);j++){
for(int k=max(j-1,i-100);k<=i-1;k++){
dp[i][j]=min(dp[i][j],dp[k][j-1]+(sumc[i]-sumc[k])*(s*j+sumt[i]));
}
}
}
long long ans=INT_MAX;
for