ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

DP(动态规划)优化——斜率优化

2022-06-25 15:35:28  阅读:133  来源: 互联网

标签:直线 le 截距 int 斜率 dp 优化 DP


斜率优化

介绍

斜率优化就是把 dp 的过程转换为求函数截距最小值和最大值的奇妙方法

网上的绝大部分博客都是一来就讲要怎么怎么做,却没有人讲怎么想到这么做的,这里就来讲一下究竟是怎么从一个普普通通的转移式想到去把它转换为求截距的

对于一个 dp 式子,比如 \(f_i=\min\limits_{1\le j<i}\{f_j+s_is_j\}\)

不难发现如果用最朴素的 dp 方法只能做到 \(\Theta(n^2)\),怎么优化呢?

对于每一个 \(f_i\) 来说我们都是通过前面的已经算出的值来推导的,就像是一个函数关系,我们就希望把这样的式子转换一下,能通过图像的方式来解决问题

受到我们高中学过的线性规划启发,在线性规划中如果已经确定约束条件下点集 \((x,y)\) 的范围时我们就把要求的关于 \(x,y\) 的式子当做一个线性函数,这条直线的斜率是固定的,但是截距却不固定(截距就是要求的答案),通过上下平移直线求得直线与点集相切时的截距就是答案,能不能把这种思想也转移到 dp 优化上呢?

由于我们想把要求的最小值转移到截距上,就稍微变形一下一般的直线方程 \(b=kx+y\)

尝试一下把 dp 的转移也变成这种形式,就有 \(f_i=\min\limits_{1\le j<i}\{s_is_j+f_j\}\)

其中

\[\begin{aligned} b&=f_i\\ k&=s_i\\ x&=s_j\\ y&=f_j \end{aligned} \]

那么 \((s_j,f_j)\) 就可以看作是平面上的一些已知点,而对于一个固定的 \(i\),斜率也是固定的,问题就转化为了求在已知 \((s_1,f_1),(s_2,f_2),\cdots,(s_{i-1},f_{i-1})\) 这些点后求斜率为 \(s_i\) 的直线的截距最小值

所以我们只用维护一个下凸包,然后找凸包上斜率第一个大于和第一个小于 \(s_i\) 的直线所交的点,直线过这个点时的截距就是答案

如果斜率保证每次插入一个新点都一定会变大而且不会影响凸包上前面的点那么就可以用单调队列方便地维护斜率,找凸包上斜率第一个大于和第一个小于 \(s_i\) 的直线所交的点

如果没有这样的性质那么一般就会更麻烦一点,使用二分或者平衡树之类的方法

例题

Luogu P3648 [APIO2014]序列分割

题意

你正在玩一个关于长度为 \(n\) 的非负整数序列的游戏。这个游戏中你需要把序列分成 \(k + 1\) 个非空的块。为了得到 \(k + 1\) 块,你需要重复下面的操作 \(k\) 次:

选择一个有超过一个元素的块(初始时你只有一块,即整个序列)

选择两个相邻元素把这个块从中间分开,得到两个非空的块

每次操作后你将获得那两个新产生的块的元素和的乘积的分数。你想要最大化最后的总得分

数据满足 \(2 \leq n \leq 100000, 1 \leq k \leq \min\{n - 1, 200\}\)

题解

首先不难发现,分割顺序对于最终的答案没有影响

证明:考虑没有被分割的一整块,这一整块之间没有贡献,假设 \(a_i\) 是这一块中的数字,\(b_i\) 是这一块外的数字,那么 \(a_i\) 对答案的贡献就是 \(a_i\sum_k b_k\),因为 \(a_i\) 与 \(b_k\) 被分割一次之后就不会再有贡献,所以分割顺序对于最终的答案没有影响

考虑令 \(s_i\) 表示原序列的前缀和, \(F_{i,j}\) 表示前 \(i\) 个数,切 \(j\) 刀能产生的最大贡献

那么有 \(F_{i,k}=\max\limits_{1\le j<i}\{F_{j,k-1}+s_j(s_i-s_j)\}\)

大概意思就是在 \(j\) 屁股后面再切一刀,新增的贡献就是 \(\sum_{k=1}^ja_k\times\sum_{p=j+1}^ia_p=s_j(s_i-s_j)\)

但是这样的话空间就炸了,不过发现 \(F_{i,j}\) 只与 \(F_{k,j-1}\) 也就是上一次状态有关,于是乎使用滚动数组优化

让 \(f_i=F_{i,j},\ g_i=F_{i,j-1}\)

就有 \(f_i=\max\limits_{1\le j<i}\{g_j+s_j(s_i-s_j)\}=\max\limits_{1\le j<i}\{g_j+s_is_j-s^2_j)\}\)

惊讶地发现有 \(s_is_j\),遂使用斜率优化,先转换一下式子

\[\begin{aligned} f_i&=\max\limits_{1\le j<i}\{s_is_j+g_j-s^2_j\}\\ y&=kx+b\\ s^2_j-g_j&=s_is_j-f_i \end{aligned} \]

这里注意到截距 \(b=-f_i\),要让 \(f_i\) 最大就要让截距最小

所以就等价于在点 \((s_j,s_j^2-g_j)\) 中找到让斜率为 \(s_i\) 的直线的最小截距,即求下凸包,每次新加入的点 \(s_i\) 都比之前的所有点更大(或相等),所以单调队列维护就行,需要注意的是如果 \(a_i=0\) 那么 \(s_i-s_{i-1}=0\),所以斜率可能不存在,需要特判一下

Code

使用 STL,更加优雅的代码

有些细节注释提了一下

#include<bits/stdc++.h>
#define in read()
typedef long long ll;
#define int ll
using namespace std;
inline int read()
{
    char c=getchar();
    int x=0,f=1;
    while(c<48)c=getchar();
    while(c>47)x=(x*10)+(c^48),c=getchar();
    return x*f;
}
inline void mwrite(int a){if(a>9)mwrite(a/10);putchar((a%10)|48);}
inline void write(int a,char c){mwrite(a),putchar(c);}
const int MAXN=1e5+5;
int n,k;
int s[MAXN],f[MAXN],g[MAXN];
int cut[MAXN][205];
deque<int>q;//装已有点集
inline int sqr(int x){return x*x;}
double slope(int i,int j)//算斜率 
{
	if(s[i]==s[j]) return -1e18;//斜率不存在,就直接返回一个极小值 
	return 1.0*((sqr(s[j])-g[j])-(sqr(s[i])-g[i]))/(s[j]-s[i]);//(s_i,g_i-s_i^2)
}
signed main()
{
	n=in,k=in;
	for(int i=1;i<=n;++i) s[i]=s[i-1]+in;
	for(int o=1;o<=k;++o)
	{
		while(!q.empty())q.pop_back();
		q.emplace_back(0);
		for(int i=1;i<=n;++i)
		{
			while(q.size()>1&&slope(q.front(),(*++q.begin()))<=1.0*s[i])q.pop_front();//凸包里面斜率比要求的斜率还要小的点都不可能再被选择
			//此时队首的就是第一个斜率小于直线的点,直线过这个点时截距最小 
			f[i]=g[q.front()]+s[q.front()]*(s[i]-s[q.front()]);
			cut[i][o]=q.front();//记一下前i个数第o刀切在哪里
			while(q.size()>1&&slope((*----q.end()),q.back())>=slope(q.back(),i)) q.pop_back();//加入新点
			q.emplace_back(i); 
		}
		for(int i=1;i<=n;++i) g[i]=f[i];
	}
	write(f[n],'\n');
	for(int i=k,x=n;i;--i) x=cut[x][i],write(x,' '); 
    return 0;
}

该文为本人原创,转载请注明出处

博客园传送门

知乎传送门

标签:直线,le,截距,int,斜率,dp,优化,DP
来源: https://www.cnblogs.com/cmy-blog/p/slope-optimization.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有