【笔记】【总结】斜率DP及习题

参考:
斜率优化dp学习 - orzzz
斜率优化 - OI wiki

HDU3507 Print Article

通过本题目来分析与总结斜率DP。

给定n(5e5)和m(1000),以及一个长为n的数列 A i A_i Ai,现在要把数列分成若干个连续段。
定义每个连续段的代价是 ( Σ A i ) 2 + m (\Sigma A_i)^2+m (ΣAi)2+m,求划分后的最小代价。

基础DP

设dp[i]表示把前i个数划分完的最小代价,那么:
d p [ i ] = m i n { d p [ j ] + ( s u m [ i ] − s u m [ j ] ] ) 2 + m } , 0 < = j < i , d p [ 0 ] = 0 dp[i]=min\{dp[j]+(sum[i]-sum[j]])^2+m\},0<=j<i,dp[0]=0 dp[i]=min{dp[j]+(sum[i]sum[j]])2+m},0<=j<i,dp[0]=0
展开后简写为: d [ i ] = m i n { d j + s j 2 − 2 s i s j } + s i 2 + m d[i]=min\{d_j+s_j^2-2s_is_j\}+s_i^2+m d[i]=min{dj+sj22sisj}+si2+m


斜率变形

设j<k<i,如果dp[i]从k转移优于从j转移,那么有 d k + s k 2 − 2 s i s k < d j + s j 2 − 2 s i s j d_k+s_k^2-2s_is_k<d_j+s_j^2-2s_is_j dk+sk22sisk<dj+sj22sisj
变形得到 ( d k + s k 2 ) − ( d j + s j 2 ) < 2 s i ( s k − s j ) (d_k+s_k^2)-(d_j+s_j^2)<2s_i(s_k-s_j) (dk+sk2)(dj+sj2)<2si(sksj)
因为 j < k , s j < s k j<k,s_j<s_k j<k,sj<sk,所以 1 / 2 ( d k + s k 2 ) − 1 / 2 ( d j + s j 2 ) s k − s j < s i \frac{1/2(d_k+s_k^2)-1/2(d_j+s_j^2)}{s_k-s_j}<s_i sksj1/2(dk+sk2)1/2(dj+sj2)<si
w i = 1 / 2 ( d k + s k 2 ) w_i=1/2(d_k+s_k^2) wi=1/2(dk+sk2),那么当 w k − w j s k − s j < s i \frac{w_k-w_j}{s_k-s_j} <s_i sksjwkwj<si成立时,i从k转移优于从j转移.

如果把所有 ( s i , w i ) (s_i,w_i) (si,wi)视为坐标系上的点,那么连接j和k对应的点,当它们的斜率即 k j k k_{jk} kjk小于 s i s_i si时,从k转移优于从j转移。
这种转移式变形后通过斜率得到最优转移的方式,就称为斜率DP。


舍弃上凸点,维护下凸包

如果存在a,b,c三个点,满足 s a < s b < s c sa<sb<sc sa<sb<sc k b c < k a b k_{bc}<k_{ab} kbc<kab,即b是一个上凸点(如下图),那么b点一定会被舍弃掉

在这里插入图片描述
证明: s i s_i si一共有三种情况:

  1. s i < k b c < k a b s_i<k_{bc}<k_{ab} si<kbc<kab,此时a优于b,b优于c
    在这里插入图片描述
  2. k b c < s i < k a b k_{bc}<s_i<k_{ab} kbc<si<kab,此时a优于b,c优于b
    在这里插入图片描述
  3. k b c < k a b < s i k_{bc}<k_{ab}<s_i kbc<kab<si,此时b优于a,c优于b
    在这里插入图片描述
    综上,从上凸点转移一定不是最优解,可以舍弃。

舍弃掉所有上凸点后,所有可能最优的点会构成一个下凸包,即对于任意三个点a,b,c,当 s a < s b < s c s_a<s_b<s_c sa<sb<sc成立时,总有 k a b < k b c k_{ab}<k_{bc} kab<kbc
在这里插入图片描述
每当新加入一个点时,检测其与最右端的两个点是否构成上凸关系,如果构成就删去上凸点,直到只剩下 下凸包 为止。

最优转移

现在只剩下一个问题:对于 s i s_i si,如何从下凸包中选择最优转移?

注意到下凸包中n个点所组成的n-1条线段的斜率是依次递增的,我们可以二分出一个点 j j j,使 k j , j + 1 k_{j,j+1} kj,j+1恰好大于 s i s_i si,此时 j j j就是最优转移。

而当全部的 s i s_i si单调递增时,还可以用队列来维护下凸包,当 k j , j + 1 k_{j,j+1} kj,j+1小于 s i s_i si后,直接将 j j j扔出队列。实际上,为了移除队尾的上凸点,这里的队列应当采用双端队列。

斜率DP算法流程总结
  1. 找到dp的转移式,通过斜率分析得到点的表示 ( x i , y i ) (x_i,y_i) (xi,yi)及目标斜率的表示。
  2. 用双端队列维护一个下凸包,每当新来一个点时,末尾不断出队直到构不成上凸包。
  3. 选择最优解,即 k j , j + 1 k_{j,j+1} kj,j+1大于目标斜率的第一个 j j j。当目标斜率递增时,每次更新队首,然后可以选择队首作为最优解。
代码实现

如果比较时算出斜率,按double比较,会WA。
分别计算 x i x_i xi的差以及 y i y_i yi的差,然后按整数乘起来比较,就过了,浮点数太不可靠了!

/* LittleFall : Hello! */
#include <bits/stdc++.h>
using namespace std; using ll = long long; inline int read();
const int M = 500016, MOD = 1000000007;

ll dp[M], sum[M];
ll q[M], ql, qr;
ll sub_up(int k, int j) //获得w[k]-w[j]
{
	return (dp[k]+sum[k]*sum[k]) - (dp[j]+sum[j]*sum[j]);
}
ll sub_do(int k, int j) //获得s[k]-s[j]
{
	return 2*(sum[k] - sum[j]);
}
int main(void)
{
	#ifdef _LITTLEFALL_
	freopen("in.txt","r",stdin);
    #endif

	int n,m;
	while(~scanf("%d%d",&n,&m))
	{
		for(int i=1; i<=n; ++i)
			sum[i] = sum[i-1] + read();
		
		ql = qr = 0; q[qr++] = 0; 
		for(int i=1; i<=n; ++i)
		{
			while(qr-ql>=2 && 
				sub_up(q[ql+1],q[ql])<=sum[i] * sub_do(q[ql+1],q[ql])
			) ++ql; // sum[i]变大,队列pop_front

			int j = q[ql]; 
			dp[i] = dp[j] + (sum[i]-sum[j])*(sum[i]-sum[j]) + m; //获得j,计算dp[i]

			while(qr-ql>=2 && 
				sub_up(q[qr-1],q[qr-2]) * sub_do(i,q[qr-1]) >= sub_do(q[qr-1],q[qr-2]) * sub_up(i,q[qr-1])
			) --qr; // 为了将i加入队列,扔掉上凸点,维护下凸包

			q[qr++] = i; 
		}
		cout << dp[n] << "\n";
	}


    return 0;
}


inline int read(){
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9') {if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
其它

如果斜率式是 k j k > s i k_{jk}>s_i kjk>si k k k j j j优,那么应当维护一个上凸包而不是下凸包。

下凸包( k j k < s i k_{jk}<s_i kjk<si,相邻斜率递增)对应目标斜率 s i s_i si递增,上凸包( k j k > s i k_{jk}>s_i kjk>si,相邻斜率递减)对应目标斜率 s i s_i si递减,满足这两个条件之一时就可以使用双端队列维护凸包。

如果遇上 上凸包+目标斜率递增 或 下凸包+目标斜率递减 呢?这种时候只需要在队尾操作,用单调栈即可。

如果目标斜率没有单调性,就需要在凸包中二分。

如果如果 ( x a − x b ) (x_a-x_b) (xaxb)的符号无法确定,就需要用平衡树维护凸包,或者整体二分。

这些情况可以在下面的习题博客中了解。

习题

HDU3507 Print Article;即上述例题。
洛谷P3628 [APIO2010]特别行动队;注意尽量不要出现分母为负数的情况。
洛谷P3195 [HNOI2008]玩具装箱TOY;和前面两道基本一样,三道里面做一两道就行。
洛谷P4072 [SDOI2016]征途;二维dp,最好先写出基本dp再去优化。
洛谷P5504 [JSOI2011]柠檬;上凸包对应斜率递增,用单调栈维护凸包。
洛谷P2120 [ZJOI2007]仓库建设;可以作为板子题或练手题使用,检测熟练度。
洛谷P4027 [NOI2007]货币兑换;平衡树维护凸包,凸包中二分斜率,是斜率DP进阶好题

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值