斜率优化DP和四边形不等式优化DP整理

当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;
}
当做出暴力DP版本之后,只需再多考虑一步就可以变成斜率优化DP

对于状态转移方程dp[i] = dp[j] + (sum[i]-sum[j]+10)*w[i]

考虑 k < j < i 且假设 i状态由j状态转移得到比由k状态转移得到更优

即:dp[j] + (sum[i]-sum[j]+10)*w[i] <= dp[k] + (sum[i] - sum[k] + 10)*w[i]

(这里取小于号是因为dp保存的是最小花费,花费越小越好,取等是因为j比k大,所以就算k,j一样优也选j)

这个不等式化简之后就是

dp[j] - dp[k] <= w[i]*(sum[j]-sum[k])

这里的w[i]满足单调递增

有了上面的不等式和单调条件就可以斜率优化了,主要做法就是利用单调队列维护满足的点

比如j状态优于k状态,就可以将k永远的剔除了

具体对于子状态的维护见代码里面有2个对队列进行的删除的操作,一个是在求dp[i]时在队首删除

一个是在将状态i加入队列时在队尾删除的操作

版本二:(O(N))

#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种珍珠的价值
	
	dp[j] - dp[k] <= w[i]*(sum[j]-sum[k])
	The qualities of the classes (and so the prices) are given in ascending order.
	So w[i]单增 --斜率DP 
*/
const int N = 111;
int w[N],dp[N],sum[N];
int q[N];
int DP(int i,int j)
{
	return dp[j] + (sum[i]-sum[j]+10)*w[i];
}
int dy(int i,int j)
{
	return dp[i]-dp[j];
}
int dx(int i,int j)
{
	return sum[i]-sum[j];
}
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;
	}
		int head = 0,tail = 0;
		q[tail++] = 0;
		for (int i = 1;i <= n;++i)
		{
			while (head+1<tail&&dy(q[head+1],q[head])<=w[i]*dx(q[head+1],q[head])) ++head;
			dp[i] = DP(i,q[head]);
			while (head+1<tail&&dy(i,q[tail-1])*dx(q[tail-1],q[tail-2])<=dy(q[tail-1],q[tail-2])*dx(i,q[tail-1])) --tail;
			q[tail++] = i;
		}
		Pintc(dp[n],'\n');
    }
    return 0;
}

POJ 1260 Pearls   和上面一题一样的


3.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值