(自己的理解:首先考虑单调队列,不行时考虑斜率,再不行就考虑不等式什么的东西)
当dp的状态转移方程dp[i]的状态i需要从前面(0~i-1)个状态找出最优子决策做转移时 我们常常需要双重循环
(一重循环跑状态 i,一重循环跑 i 的所有子状态)这样的时间复杂度是O(N^2)而 斜率优化或者四边形不等式优化后的DP
可以将时间复杂度缩减到O(N)
O(N^2)可以优化到O(N) ,O(N^3)可以优化到O(N^2),依次类推
斜率优化DP和四边形不等式优化DP主要的原理就是利用斜率或者四边形不等式等数学方法
在所有要判断的子状态中迅速做出判断,所以这里的优化其实是省去了枚举i的子状态的循环,几乎就是直接把最优的子状态找出来了
其中四边形不等式优化是用数组s边跑边求最优的子状态,例如用s[i][j]保存dp[i][j]的最优子状态
斜率优化的话是将后面可能用到的子状态放到队列中,要求的当前状态的最优状态就是队首元素q[head]
另外,网上见到很多用二分+DP解斜率优化的问题。
以dp求最小值为例:
主要的解题步骤就是先写出dp的状态转移方程,然后选取两个子状态p,q
假设p < q而决策q比p更好,求出斜率不等式,然后就可以写了
至于经常有题目控制子决策的范围什么的(比如控制区间长度,或者控制分组的组数),就需要具体情况具体分析
1 HDU 1300 Pearls
最最最简单的斜率DP优化的题,就算不用优化,O(N^2)的算法也可以AC
这题绝壁是最最最适合入门的斜率DP的题,我发誓!!!
版本一:(O(N^2))
#define mem(a,x) memset(a,x,sizeof(a)) #include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<queue> #include<set> #include<stack> #include<cmath> #include<map> #include<stdlib.h> #include<cctype> #include<string> #define Sint(n) scanf("%d",&n) #define Sll(n) scanf("%I64d",&n) #define Schar(n) scanf("%c",&n) #define Schars(s) scanf("%s",s) #define Sint2(x,y) scanf("%d %d",&x,&y) #define Sll2(x,y) scanf("%I64d %I64d",&x,&y) #define Pint(x) printf("%d",x) #define Pllc(x,c) printf("%I64d%c",x,c) #define Pintc(x,c) printf("%d%c",x,c) using namespace std; typedef long long ll; /* dp[i]表示买前i种珍珠的最少花费 dp[i] = min(dp[j] + (sum[i] - sum[j] + 10)*w[i]) 其中sum[i]-sum[j]表示第j+1种珍珠到第i种珍珠所需的数量 w[i]表示第i种珍珠的价值 */ const int N = 111; int w[N],dp[N],sum[N]; int main() { int T;Sint(T); while (T--) { int n;Sint(n); for (int i = 1,x;i <= n;++i) { Sint2(x,w[i]); sum[i] = sum[i-1] + x; } dp[1] = (sum[1]+10)*(w[1]); for (int i = 2;i <= n;++i) { dp[i] = dp[i-1] + (sum[i]-sum[i-1]+10)*w[i]; for (int j = 0;j < i-1;++j) { dp[i] = min(dp[i],dp[j] + (sum[i]-sum[j]+10)*w[i]); } } Pintc(dp[n],'\n'); } return 0; }