斜率DP题目小结

如果K是常数:单调队列

如果X单调,K单调:斜率优化

若K不单调,X单调:二分在(上)下凸壳上寻找斜率作为dp值。 

若X不单调,K不单调:需要用Splay/CDQ分治维护(上)下凸壳。

题目1:华工G:https://www.nowcoder.com/acm/contest/94/G

DP方程:

题解:X单调,K单调,斜率优化+二分:二分找k作为答案

代码:https://www.nowcoder.com/acm/contest/view-submission?submissionId=25108586


题目2:BZOJ1010:https://www.lydsy.com/JudgeOnline/problem.php?id=1010

DP方程:


,(Y X里面的i应该写成j)

题解:X单调,K单调,斜率优化:

理解与记忆:

出队:斜率式比较:假设j>k,j比k优则有....的那条式子.不断head+1与head比较,得到一个合适的dp(i)值

入队:用斜率逼近来记忆,维护下凸包:判断Ktail tail-1>Ktail i说明K=g(i)逼近时不会碰到tail,不断去尾,最后i入队

模板代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=50005;
ll s[maxn];int q[maxn];ll dp[maxn];ll g[maxn];
int n; ll l;
inline ll Y(int i,int j){return (dp[i]+g[i]*g[i]+2*g[i]*(l+1))-(dp[j]+g[j]*g[j]+2*g[j]*(l+1));}
inline ll X(int i,int j){return (g[i]-g[j])*2;}
inline ll f(int i,int j){return dp[j]+(g[i]-g[j]-l-1)*(g[i]-g[j]-l-1);}
int main()
{
	scanf("%d %lld",&n,&l);ll t=0;int head=0,tail=0;
	memset(dp,0,sizeof(dp));memset(q,0,sizeof(q));memset(s,0,sizeof(s));
	for(int i=1;i<=n;i++)scanf("%lld",&t),s[i]=s[i-1]+t,g[i]=s[i]+i;
	for(int i=1;i<=n;i++)
	{
		while(head<tail&&Y(q[head+1],q[head])<=g[i]*X(q[head+1],q[head]))head++;
		dp[i]=f(i,q[head]);
		while(head<tail&&Y(i,q[tail])*X(q[tail],q[tail-1])<=Y(q[tail],q[tail-1])*X(i,q[tail])  )tail--;
		q[++tail]=i;
	}	
	printf("%lld\n",dp[n]);
} 

题目3:hdu3045:http://acm.hdu.edu.cn/showproblem.php?pid=3045,区间长度不少于m,同POJ3709

DP方程:


题解:x单调,k单调,斜率优化+延迟加入走起

延迟加入的理解:求出dp(j)之后,dp(j)只对dp(j+m),dp(j+m+1)...以后的状态起决策作用

举个例子:求dp(10),m=3  那么决策应该从dp1~dp7选min  

求出dp10之后,才入队dp8,因为dp(11)会用到dp(8),

那什么时候才开始入队呢?i>=2m-1的时候,n=5,m=3理解一下

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=400005;
ll s[maxn];int q[maxn];ll dp[maxn];ll a[maxn];
int n; int m;
inline ll Y(ll i,ll j){return (dp[i]-s[i]+i*a[i+1])-(dp[j]-s[j]+j*a[j+1]);}
inline ll X(ll i,ll j){return (a[i+1]-a[j+1]);}
inline ll f(ll i,ll j){return dp[j]+s[i]-s[j]-a[j+1]*(i-j);}
int main()
{
	while(~scanf("%d %d",&n,&m))
	{
		memset(dp,0,sizeof(dp));	memset(q,0,sizeof(q));	memset(s,0,sizeof(s));
		for(int i=1;i<=n;i++)scanf("%I64d",&a[i]);
		sort(a+1,a+1+n);
		for(int i=1;i<=n;i++)s[i]=s[i-1]+a[i];
		int head=0,tail=1;
		for(int i=1;i<=n;i++)
		{
			while(head<tail&&Y(q[head+1],q[head])<=1ll*i*X(q[head+1],q[head]))head++;
			dp[i]=f(i,q[head]);
			int late=i-m+1;
			if(i>=2*m-1)
			{
				while(head<tail&&Y(late,q[tail])*X(q[tail],q[tail-1])<=Y(q[tail],q[tail-1])*X(late,q[tail])  )tail--;
				q[++tail]=late;
			}
		}	
		printf("%I64d\n",dp[n]);
	}
} 

题目4:hdu3507:http://acm.hdu.edu.cn/showproblem.php?pid=3507

DP方程:,m是常数


题解:直接斜率优化搞,跟题目2一样

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=500005;
ll s[maxn];int q[maxn];ll dp[maxn];
int n,m;
inline ll Y(int i,int j){return dp[i]+s[i]*s[i]-dp[j]-s[j]*s[j];}
inline ll X(int i,int j){return 2*(s[i]-s[j]);}
inline ll f(int i,int j){return dp[j]+m+(s[i]-s[j])*(s[i]-s[j]);}
int main()
{
	while(~scanf("%d %d",&n,&m))
	{
		ll t=0;int head=0,tail=0;
		memset(dp,0,sizeof(dp));	memset(q,0,sizeof(q));	memset(s,0,sizeof(s));
		for(int i=1;i<=n;i++)scanf("%I64d",&t),s[i]=s[i-1]+t;
		for(int i=1;i<=n;i++)
		{
			while(head<tail&&Y(q[head+1],q[head])<=s[i]*X(q[head+1],q[head]))head++;
			dp[i]=f(i,q[head]);
			while(head<tail&&Y(i,q[tail])*X(q[tail],q[tail-1])<=Y(q[tail],q[tail-1])*X(i,q[tail])  )tail--;
			q[++tail]=i;
		}	
		printf("%I64d\n",dp[n]);
	}	
} 


题目5:BZOJ4518:https://www.lydsy.com/JudgeOnline/problem.php?id=4518

DP方程:前j条路分成i段:


题解:X单调,K单调,斜率优化走起,不过这次是二维,注意写法

对每个i跑一遍斜率优化得出dp i j 即可,注意边界!

代码:

//WA 点 初始化的锅dp[0][0]=0;其余为inf 
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=3005;
ll a[maxn];ll s[maxn];ll dp[maxn][maxn];ll q[maxn];
inline ll Y(int i,int j,int k){return (dp[i-1][j]+s[j]*s[j])-(dp[i-1][k]+s[k]*s[k]);}
inline ll X(int j,int k){return 1ll*2*(s[j]-s[k]);}
inline ll f(int i,int j,int k){return dp[i-1][k]+(s[j]-s[k])*(s[j]-s[k]);}
int main()
{
	ll n,m;
	scanf("%lld %lld",&n,&m);s[0]=0;
	for(int i=1;i<=n;i++) scanf("%lld",&a[i]),s[i]=s[i-1]+a[i];
	for(int i=0;i<=m;i++)for(int j=0;j<=n;j++)dp[i][j]=1000000000000000000;
	dp[0][0]=0;
	for(int i=1;i<=m;i++)
	{
		//memset(q,0,sizeof(q));
		int head=0;int tail=0;
		for(int j=1;j<=n;j++)
		{
			while(head<tail&&Y(i,q[head+1],q[head])<=X(q[head+1],q[head])*s[j])head++;
			dp[i][j]=f(i,j,q[head]);
			while(head<tail&&Y(i,j,q[tail])*X(q[tail],q[tail-1])<=X(j,q[tail])*Y(i,q[tail],q[tail-1]))tail--;
			q[++tail]=j;
		}
	}
	printf("%lld\n",m*dp[m][n]-s[n]*s[n]);	
}

滚动数组的写法:

//WA 点 初始化的锅dp[0][0]=0;其余为inf 
//入队注释那里,乘法貌似溢出了QAQ ull都WA得换成除法才能AC
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=3005;
ll a[maxn];
ll s[maxn];
ll dp[2][maxn];
ll q[maxn];
inline ll Y(int i,int j,int k){return (dp[i^1][j]+s[j]*s[j])-(dp[i^1][k]+s[k]*s[k]);}
inline ll X(int j,int k){return 1ll*(s[j]-s[k]);}
inline ll f(int i,int j,int k){return dp[i^1][k]+(s[j]-s[k])*(s[j]-s[k]);}
int main()
{
	ll n,m;
	scanf("%lld %lld",&n,&m);s[0]=0;
	for(int i=1;i<=n;i++) scanf("%lld",&a[i]),s[i]=s[i-1]+a[i];
	for(int i=0;i<=1;i++)for(int j=0;j<=n;j++)dp[i][j]=2000000000000000000;
	dp[0][0]=0;
	for(int i=1,t=1;i<=m;t^=1,i++)
	{
		//memset(q,0,sizeof(q));
		int head=0;int tail=0;
		for(int j=1;j<=n;j++)
		{
			while(head<tail&&Y(t,q[head+1],q[head])<=X(q[head+1],q[head])*s[j]*2)head++;
			dp[t][j]=f(t,j,q[head]);
			//while(head<tail && (unsigned long long)Y(t,j,q[tail])*(unsigned long long)X(q[tail],q[tail-1])<=(unsigned long long)X(j,q[tail])*(unsigned long long)Y(t,q[tail],q[tail-1]) )tail--;
			while(head<tail && (double)Y(t,j,q[tail])/(double)X(j,q[tail])<=(double)Y(t,q[tail],q[tail-1])/(double)X(q[tail],q[tail-1] ))tail--;
			q[++tail]=j;
		}
	}
	printf("%lld\n",m*dp[m&1][n]-s[n]*s[n]);	
}


<think>好的,用户想了解斜率优化动态规划的概念及实现方法。首先,我需要回忆一下动态规划的基础知识,特别是优化方法。动态规划通常有递推和记忆化搜索两种实现方式,而斜率优化属于一种高级的优化技巧,用于降低时间复杂度。 用户提到的参考引用中提到,递推的效率比记忆化搜索高,并且很多优化技巧如滚动数组、单调队列、斜率优化都建立在递推上。所以,斜率优化应该属于递推方法的一种扩展。 接下来,我需要明确斜率优化的核心思想。根据已有的知识,斜率优化通常用于处理状态转移方程中的决策变量,通过分析转移方程的数学形式,将其转化为斜率问题,从而利用单调队列或凸包等数据结构来优化决策过程。 例如,当状态转移方程形如$dp[i] = \min\{dp[j] + f(i,j)\}$,其中$f(i,j)$可以分解为与i和j相关的项,可能可以将其转换为斜率的形式。比如,若$f(i,j)=a[i]*b[j]+c[i]+d[j]$,可能将式子整理为关于$b[j]$和$dp[j]+d[j]$的线性表达式,进而比较同j值的斜率,选择最优的决策点。 需要解释为什么这种方法有效,可能涉及到维护一个下凸包或上凸包,确保每次决策时选取的j是最优的,从而将时间复杂度从O(n^2)降低到O(n)或O(n log n)。 然后,结合用户提供的引用内容,特别是引用[2]提到的QP优化DP结合,可能斜率优化在路径规划中的应用,但用户的问题更关注动态规划中的斜率优化,所以需要专注于算法实现的步骤。 需要给出具体的例子,比如任务安排问题,或者最大子序列问题,来展示斜率优化的应用步骤。例如,经典的任务安排问题,其中状态转移方程可以斜率优化,通过维护一个单调队列来快速找到最优的前驱状态。 在实现步骤方面,应该分步骤解释:问题分析、状态转移方程整理为斜率形式、数据结构的选择(如单调队列)、维护凸包的过程、以及最终的时间复杂度分析。 同时,要注意数学表达式的正确格式,使用$$...$$和$...$,并确保LaTeX语法正确。例如,将状态转移方程写成$$dp[i] = \min_{j<i}\{ dp[j] + (S + T_i)(C_i - C_j) \}$$,然后展开分析。 另外,用户提供的引用中有提到变积分PID和QP优化,虽然这些可能与斜率优化无直接关联,但需要确保混淆概念,专注于动态规划中的斜率优化部分。 最后,生成相关问题时要围绕斜率优化的应用场景、与其他优化方法的比较、具体实现中的挑战等,帮助用户深入理解。</think>### 斜率优化动态规划详解 #### 概念解析 斜率优化动态规划(Slope Optimization DP)是一种通过**分析状态转移方程的斜率特性**来优化决策过程的方法,可将部分动态规划问题的时间复杂度从$O(n^2)$降至$O(n)$或$O(n \log n)$[^1]。其核心是将状态转移方程转换为**线性关系式**,通过维护**决策点集的凸包**快速筛选最优决策。 #### 算法原理 1. **状态转移方程重构** 设状态转移方程为: $$dp[i] = \min_{j<i}\{ dp[j] + f(i,j) \}$$ 若$f(i,j)$可分解为$A(i) \cdot B(j) + C(i) + D(j)$形式,则方程可改写为: $$dp[i] = C(i) + \min_{j<i}\{ A(i) \cdot B(j) + (dp[j] + D(j)) \}$$ 此时决策变量$j$的优劣可转化为**点$(B(j), dp[j]+D(j))$的斜率比较**。 2. **凸包维护** 通过维护决策点集的**下凸包**(当$A(i)$单调递增)或**上凸包**(当$A(i)$单调递减),使用单调队列/栈快速剔除无效决策点。 #### 实现步骤(以经典任务安排问题为例) **问题描述** 处理$n$个任务,每个任务耗时$t_i$,费用系数$c_i$。目标最小化总费用: $$总费用 = \sum_{i=1}^n (\text{完成时间} \times c_i)$$ **状态转移方程** $$dp[i] = \min_{j<i}\left\{ dp[j] + (S + T_i)(C_i - C_j) \right\}$$ 其中$T_i = \sum_{k=1}^i t_k$, $C_i = \sum_{k=1}^i c_k$,$S$为启动延迟。 **斜率优化过程** 1. **方程变形** 将方程展开为: $$dp[i] = S \cdot C_i + T_i \cdot C_i + \min_{j<i}\{ dp[j] - S \cdot C_j - T_i \cdot C_j \}$$ 令: $$ \begin{cases} k = T_i \\ x_j = C_j \\ y_j = dp[j] - S \cdot C_j \\ \end{cases} $$ 则状态转移简化为: $$dp[i] = (S + T_i)C_i + \min_{j<i}\{ y_j - k x_j \}$$ 2. **决策点筛选** 最优决策点需满足: $$\frac{y_j - y_k}{x_j - x_k} \leq k_i \quad (\text{下凸包条件})$$ 使用单调队列维护凸包,队首即为当前最优决策。 **Python实现示例** ```python def slope_optimization_dp(n, S, t, c): T = [0]*(n+1) # 前缀时间和 C = [0]*(n+1) # 前缀费用和 dp = [0]*(n+1) q = deque([0]) # 单调队列存储决策点索引 for i in range(1, n+1): T[i] = T[i-1] + t[i-1] C[i] = C[i-1] + c[i-1] # 剔除队首非最优决策 while len(q)>=2 and (dp[q[1]] - dp[q[0]]) <= (S + T[i])*(C[q[1]] - C[q[0]]): q.popleft() j = q[0] dp[i] = dp[j] + (S + T[i] - T[j])*(C[i] - C[j]) # 维护下凸包 while len(q)>=2 and (dp[i]-dp[q[-1]])*(C[q[-1]]-C[q[-2]]) <= (dp[q[-1]]-dp[q[-2]])*(C[i]-C[q[-1]]): q.pop() q.append(i) return dp[n] ``` #### 关键优化点 - **时间复杂度**:从$O(n^2)$优化至$O(n)$ - **数据结构**:单调队列实现$O(1)$时间复杂度的决策筛选 - **适用条件**:状态转移方程需满足**决策单调性**且参数满足**斜率单调性** #### 典型应用场景 1. 任务调度问题 2. 最优库存控制 3. 资源分配优化 4. 路径规划中的轨迹平滑(如自动驾驶中的QP优化处理[^2])
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值