动规 - 斜率优化


斜率优化,把动规方程稍加变形,再用类似线性规划的方法思想进行优化,一般会用到单调队列优化等;

学习资料

洛谷日报:浅谈斜率优化
斜率优化详解

题目

玩具装箱

题目传送门:luogu3195

题目

题目描述
P教授要去看奥运,但是他舍不下他的玩具,于是他决定把所有的玩具运到北京。他使用自己的压缩器进行压缩,其可以将任意物品变成一堆,再放到一种特殊的一维容器中。P教授有编号为 1 ⋯ N 1\cdots N 1N N N N 件玩具,第 i i i件玩具经过压缩后变成一维长度为 C i C_i Ci.为了方便整理,P教授要求在一个一维容器中的玩具编号是连续的。同时如果一个一维容器中有多个玩具,那么两件玩具之间要加入一个单位长度的填充物,形式地说如果将第 i i i 件玩具到第 j j j 个玩具放到一个容器中,那么容器的长度将为 x = j − i + ∑ k = i j C k x=j-i+\sum\limits_{k=i}^{j}C_k x=ji+k=ijCk制作容器的费用与容器的长度有关,根据教授研究,如果容器长度为 xx ,其制作费用为 ( X − L ) 2 (X-L)^2 (XL)2.其中 L L L 是一个常量。P教授不关心容器的数目,他可以制作出任意长度的容器,甚至超过 L L L 。但他希望费用最小.

感谢@ACの666 提供的Latex题面

输入格式
第一行输入两个整数N,L.接下来N行输入 C i C_i Ci.1<=N<=50000,1<=L, C i C_ i Ci<=10^7
输出格式
输出最小费用

输入输出样例

输入 #1
5 4
3
4
2
1
4
输出 #1
1

分析

d p [ i ] dp[i] dp[i] 为第 i i i 件物品放入时的最小花费(此时第 i i i 件物品是最后一个容器的最后一件物品)

j j j 为上一个容器的最后一个物品

则有动规方程:
d p [ i ] = m i n ( d p [ j ] + [ s u m [ i ] − s u m [ j ] + i − ( j + 1 ) − L ] 2 ) dp[i] = min(dp[j] + [ sum[i] - sum[j]+i-(j+1)-L] ^2 ) dp[i]=min(dp[j]+[sum[i]sum[j]+i(j+1)L]2)
拆开再分组:
d p [ i ] = m i n ( d p [ j ] + [ s u m [ i ] + i − ( s u m [ j ] + j ) − ( 1 + L ) ] 2 ) dp[i] = min(dp[j] + [ sum[i] +i- (sum[j]+j)-(1+L)] ^2 ) dp[i]=min(dp[j]+[sum[i]+i(sum[j]+j)(1+L)]2)
我们先令 L + + L++ L++
再设 a [ i ] = s u m [ i ] + i a[i]=sum[i]+i a[i]=sum[i]+i b [ i ] = a [ i ] + L b[i]=a[i]+L b[i]=a[i]+L
那么方程变为:
d p [ i ] = m i n ( d p [ j ] + ( a [ i ] − b [ j ] ) 2 ) dp[i] = min(dp[j] + (a[i]- b[j]) ^2 ) dp[i]=min(dp[j]+(a[i]b[j])2)
先不考虑 m i n min min,则:
d p [ i ] = d p [ j ] + ( a [ i ] − b [ j ] ) 2 dp[i] = dp[j] + (a[i]- b[j]) ^2 dp[i]=dp[j]+(a[i]b[j])2
拆开:
d p [ i ] = d p [ j ] + a [ i ] 2 − 2 a [ i ] b [ j ] + b [ j ] 2 dp[i] = dp[j] +a[i]^2 -2a[i]b[j]+b[j]^2 dp[i]=dp[j]+a[i]22a[i]b[j]+b[j]2
移项:
2 a [ i ] b [ j ] + d p [ i ] − a [ i ] 2 = d p [ j ] + b [ j ] 2 2a[i]b[j]+dp[i] -a[i]^2= dp[j]+b[j]^2 2a[i]b[j]+dp[i]a[i]2=dp[j]+b[j]2
其中: d p [ i ] − a [ i ] dp[i]-a[i] dp[i]a[i]不受 j j j的影响, b [ j ] b[j] b[j] j j j的直接影响,则:
b [ j ] = x b[j]=x b[j]=x d p [ j ] + b [ j ] 2 = y dp[j]+b[j]^2=y dp[j]+b[j]2=y 2 a [ i ] 2a[i] 2a[i]为一次项系数,求 d p [ i ] dp[i] dp[i]的最小值
转化为线性规划问题。
y = 2 a [ i ] x + d p [ i ] − a [ i ] 2 y=2a[i]x+dp[i]-a[i]^2 y=2a[i]x+dp[i]a[i]2
y = x 2 + d p [ j ] y=x^2+dp[j] y=x2+dp[j]
想象一条斜率为 2 a [ i ] 2a[i] 2a[i]的直线,在逼近二次函数 x 2 + d p [ j ] x^2+dp[j] x2+dp[j]
注意到: a [ i ] a[i] a[i] i i i递增,所以答案的 j j j也是必定递增的
所以动规可以用单调队列优化为线性的。
又由于 d p [ j ] dp[j] dp[j]的不同, y y y不一定是递增的
此时需要我们将队列中下降的部分除去,以保证队列中元素的斜率单调

代码
/*************************
User:Mandy.H.Y
Language:c++
Problem:luogu3195 Toy
Algorithm:斜率优化
Date:2019.7.24
Scores: 
*************************/

#include<bits/stdc++.h>

using namespace std;

const int maxn = 5e4 + 5;

int n,L;
long long a[maxn],b[maxn],dp[maxn];
long long l,r,q[maxn << 1];

template<class T>inline void read(T &x){
	x = 0;bool flag = 0;char ch = getchar();
	while( ! isdigit(ch)) flag |= ch == '-',ch = getchar();
	while(isdigit(ch)) x = (x << 1) + (x << 3) + (ch ^ 48),ch = getchar();
	if(flag) x = -x;
}

template<class T>void putch(const T x){
	if(x > 9) putch( x / 10);
	putchar(x % 10 | 48);
}

template<class T>void put(const T x){
	if(x < 0) putchar('-'),putch(-x);
	else putch(x);
}

void file(){
	freopen("3195.in","r",stdin);
	freopen("3195.out","w",stdout);
}

void readdata(){
	read(n);read(L);
	++L;
	for(int i = 1;i <= n; ++ i){
		long long x;read(x);
		a[i] = a[i - 1] + x;
	}
	for(int i = 1;i <= n; ++ i){
		a[i] += i;
		b[i] = a[i] + L;
	}//i不能累加,要单独加上去 
	
}

void work(){
	l = r = 1;//保证队列中至少两个元素 
	b[0] = L;//初始化 
	for(int i = 1;i <= n; ++ i){
		long long k = a[i] << 1;
		while(l < r && 
			 (b[q[l + 1]] * b[q[l + 1]] + dp[q[l + 1]] - b[q[l]] * b[q[l]] - dp[q[l]]) <=
			 (k * (b[q[l + 1]] - b[q[l]])))
			 ++ l;
		
		dp[i] = dp[q[l]] + (a[i] - b[q[l]]) * (a[i] - b[q[l]]);
		
		while(l < r &&
			 (dp[q[r]] + b[q[r]] * b[q[r]] - dp[q[r - 1]] - b[q[r - 1]] * b[q[r - 1]]) * (b[i] - b[q[r]]) >=
			 (dp[i] + b[i] * b[i] - dp[q[r]] - b[q[r]] * b[q[r]]) * (b[q[r]] - b[q[r - 1]]))
			 -- r;
		q[++ r] = i;
	}
	put(dp[n]);
}

int main(){
//	file();
	readdata();
	work();
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值