HDU 3507 Print Article(dp斜率优化详解)

在这里插入图片描述


所谓 dp 斜率优化,一般是用在转移方程类似一次函数,又要取最优解(最大值、最小值),这种情况其实可以用李超树维护,但横坐标值域范围不确定,有的题可能会横坐标非常大需要动态开点。

题目大意是一串数字,将这串数字划分成若干段,每一段的代价为 :(设i,j为一段(sum[i] - sum[j])^2 + M,要求对这串数字进行划分,总代价和最小为多少。

考虑 dp, dp[i]表示前 i 个数字的最小划分代价,显然有转移方程:dp[i] = min(dp[j] + (sum[i] - sum[j])^2 + M)
拆开可以得到:dp[i] = min(dp[j] + sum[i]^2 + sum[j]^2 - 2*sum[i]*sum[j] + M)

显然每一次可以将 -2 * sum[j] * x + sum[j]^2 + dp[j] 作为一个一次函数,其中 k = -2 * sum[j],b = sum[j]^2 + dp[j],将这条直线维护到李超树上,转移时查询这些线段在 sum[i] 处的最小值,这里主要介绍斜率优化的做法。

转移方程要取一个最优决策点进行转移,设当前要求 d p [ i ] dp[i] dp[i] k < j k < j k<j,在 j j j 点转移比在 k k k 点转移更优,则满足:
d p [ j ] + s u m [ j ] 2 − 2 ∗ s u m [ i ] ∗ s u m [ j ] + s u m [ j ] + M < d p [ k ] + s u m [ i ] 2 − 2 ∗ s u m [ i ] ∗ s u m [ k ] + s u m [ k ] 2 + M dp[j] + sum[j]^2-2*sum[i]*sum[j]+sum[j]+M < dp[k] + sum[i]^2 - 2 * sum[i]*sum[k]+sum[k]^2+M dp[j]+sum[j]22sum[i]sum[j]+sum[j]+M<dp[k]+sum[i]22sum[i]sum[k]+sum[k]2+M
两边移项可以得到: d p [ j ] + s u m [ j ] 2 − ( d p [ k ] + s u m [ k ] 2 ) s u m [ j ] − s u m [ k ] < 2 ∗ s u m [ i ] \frac{dp[j]+sum[j]^2-(dp[k]+sum[k]^2)}{sum[j]-sum[k]}<2*sum[i] sum[j]sum[k]dp[j]+sum[j]2(dp[k]+sum[k]2)<2sum[i]

x [ i ] = s u m [ i ] , y [ i ] = d p [ i ] + s u m [ i ] 2 x[i] = sum[i],y[i] = dp[i] + sum[i]^2 x[i]=sum[i],y[i]=dp[i]+sum[i]2,这个式子等价于 y [ j ] − y [ k ] x [ j ] − x [ k ] < 2 ∗ s u m [ i ] \displaystyle\frac{y[j] - y[k]}{x[j]-x[k]} <2*sum[i] x[j]x[k]y[j]y[k]<2sum[i]

左边是一个斜率式子,令 K ( j , k ) K(j,k) K(j,k) 表示: k < j k < j k<j y [ j ] − y [ k ] x [ j ] − x [ k ] \displaystyle\frac{y[j] - y[k]}{x[j]-x[k]} x[j]x[k]y[j]y[k] 的值

注意要满足 k < j,显然 K ( j , k ) K(j,k) K(j,k) K ( k , j ) K(k,j) K(k,j) 的值相同,但他们含义不同。
k < j k < j k<j,且 K ( j , k ) < 2 ∗ s u m [ i ] K(j,k) < 2 * sum[i] K(j,k)<2sum[i],则 j j j 点比 k k k 点更优(两个条件都要满足)

k < j < i,若 K ( j , k ) ≥ K ( i , j ) K(j,k) \geq K(i,j) K(j,k)K(i,j),则 j j j 一定不是最优点,如下图(呈上凸包):
在这里插入图片描述

证明很简单,若 K ( j , k ) > 2 ∗ s u m [ i ] K(j,k) > 2 * sum[i] K(j,k)>2sum[i] j j j k k k 优,则 K ( i , j ) > 2 ∗ s u m [ i ] K(i,j) > 2 * sum[i] K(i,j)>2sum[i] 必定同时成立, i i i j j j 更优。反之, k k k j j j 更优,综上,如果出现这种情况,则 j j j 点一定不是最优决策点,在转移时可以直接挖掉这个点。

根据这个结论,若将 [1,i - 1] 里非最优点挖掉, K ( j + 1 , j ) K(j + 1,j) K(j+1,j) 满足单调递增,如下图:
在这里插入图片描述
维护的一定是一个下凸包,维护斜率单调递增可以用单调栈。

由于 K ( j + 1 , j ) K(j + 1,j) K(j+1,j) 具有单调性,可以通过二分答案的方式找到第一个 j j j 满足 K ( j + 1 , j ) ≥ 2 ∗ s u m [ i ] K(j + 1,j) \geq 2 * sum[i] K(j+1,j)2sum[i],这样复杂度可以控制在 O ( n log ⁡ n ) O(n\log n) O(nlogn)

而这题在dp求解过程中, s u m [ i ] sum[i] sum[i] 单调递增,可以用优先队列,在寻找最优点时,满足 K ( j + 1 , j ) < 2 ∗ s u m [ i ] K(j + 1,j) < 2 * sum[i] K(j+1,j)<2sum[i] 时就将 j j j 推出队列,因为 s u m [ i ] sum[i] sum[i] 是递增的,在后面的转移中 j j j 点不可能再作为最优点来转移,整个复杂度控制在 O ( n ) O(n) O(n)

实现的时候,用 double 类型会有误差问题(比如这题就会出错,好在 X ( i ) = s u m [ i ] X(i) = sum[i] X(i)=sum[i],满足单调性,容易判断 X ( i ) − X ( j ) X(i) - X(j) X(i)X(j) 的正负号),将除法移项变成乘法要注意正负号


代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn = 5e5 + 10;
typedef long long ll;
int n,m,a[maxn];
ll sum[maxn],dp[maxn];
ll X(int i) {
	return sum[i];
}
ll Y(int i) {
	return dp[i] + sum[i] * sum[i];
}
bool g(int i,int j,int k) {				// i > j > k,K(j,k) >= K(i,j)
	return (Y(j) - Y(k)) * (X(i) - X(j)) >= (Y(i) - Y(j)) * (X(j) - X(k));
}
int q[maxn],front,rear;		//由于这里 sum[i]  单调递增,可以用单调队列,双指针扫 
int main() {
	while (scanf("%d%d",&n,&m) != EOF) {
		for (int i = 1; i <= n; i++)
			scanf("%d",&a[i]);
		sum[0] = dp[0] = 0;
		for (int i = 1; i <= n; i++)
			sum[i] = sum[i - 1] + a[i];
		front = 0, rear = 0;					//初始要把 0 放入队列,方便处理 dp 的第一个点 
		q[++rear] = 0;
		for (int i = 1; i <= n; i++) {
			while (front + 1 < rear && (Y(q[front + 2]) - Y(q[front + 1])) < 2 * sum[i] * (X(q[front + 2]) - X(q[front + 1]))) 
				front++;	//front + 1 指向的是最优转移点
			int v = q[front + 1];
			dp[i] = dp[v] + (sum[i] - sum[v]) *  (sum[i] - sum[v]) + m;
			// 把 i 放进队列 
			while (front + 1 < rear && g(i,q[rear],q[rear - 1])) rear--;
			q[++rear] = i;
		}
		printf("%lld\n",dp[n]);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值