从一道题来看如何分析斜率优化DP

版权声明:233 https://blog.csdn.net/gmh77/article/details/89179493

一生之敌斜率优化

前言

斜率优化是1D1D的特殊形式,因为可以直接求出失效时间所以不需要二分
关于斜率优化的维护内容不细讲自行百度,这里主要讲解题过程

例题1

jzoj1116. TOY
Description
8月P教授要去看奥运,但是他割舍不下自己的一大堆智力玩具。于是,他决定把所有玩具都运到北京去。P教授使用自己的物体维数压缩器ODZ(Object Dimension Zipper)来给玩具装箱。ODZ 可以将任意物品变成一维,再装到一种特殊的一维容器中。P教授有编号为1…N的N件玩具,第i件玩具经过ODZ处理后一维长度是Ci。为了方便整理,P教授要求在一个一维容器中的玩具编号是连续的。同时,如果一个一维容器中有多个玩具,那么相信两件玩具之间要加入1个单位长度的填充物。

形式地说,如果将第i到第j件玩具放在一个容器中,那容器的长度将为:

x=j-i+sigma(Ck) //i<=k<=j

制作容器的费用与容器长度有关。根据P教授的研究,如果容器长度为x,其制作费用为(x-L)^2,其中L是一个常量。P教授不关心容器的数目,他可以制造出任意长度的容器(甚至超过L),但他希望费用最小。

Input
第一行输入两个整数N和L,接下来N行输入Ci。1<=N<=50000,1<=L,Ci<=10^7。

Output
输出最小费用。

Sample Input
5 4
3
4
2
1
4

Sample Output
1

分析

显然方程式是
f [ i ] = ( i j + s [ i ] s [ j 1 ] L ) 2 + f [ j 1 ] f[i]=(i-j+s[i]-s[j-1]-L)^2+f[j-1] (s表示前缀和)
拆开可得
f [ i ] = i 2 + j 2 + s [ i ] 2 + s [ j 1 ] 2 + L 2 2 i j + 2 i s [ i ] 2 i s [ j 1 ] 2 i L 2 j s [ i ] + 2 j s [ j 1 ] + 2 j L 2 s [ i ] s [ j 1 ] 2 s [ i ] L + 2 s [ j 1 ] L + f [ j 1 ] f[i]=i^2+j^2+s[i]^2+s[j-1]^2+L^2-2ij+2is[i]-2is[j-1]-2iL-2js[i]+2js[j-1]+2jL-2s[i]s[j-1]-2s[i]L+2s[j-1]L+f[j-1]
分成之和i有关,只和j有关和与ij都有关的三类:
i: i 2 + s [ i ] 2 + L 2 + 2 i s [ i ] 2 i L 2 s [ i ] L i^2+s[i]^2+L^2+2is[i]-2iL-2s[i]L
j: j 2 + s [ j 1 ] 2 + 2 j s [ j 1 ] + 2 j L + 2 s [ j 1 ] L + f [ j 1 ] j^2+s[j-1]^2+2js[j-1]+2jL+2s[j-1]L+f[j-1]
ij: 2 i j 2 i s [ j 1 ] 2 j s [ i ] 2 s [ i ] s [ j 1 ] -2ij-2is[j-1]-2js[i]-2s[i]s[j-1]
G [ j ] = j 2 + s [ j 1 ] 2 + 2 j s [ j 1 ] + 2 j L + 2 s [ j 1 ] L + f [ j 1 ] G[j]=j^2+s[j-1]^2+2js[j-1]+2jL+2s[j-1]L+f[j-1]
因为 2 i j 2 i s [ j 1 ] 2 j s [ i ] 2 s [ i ] s [ j 1 ] = 2 ( i + s [ i ] ) ( j + s [ j 1 ] ) -2ij-2is[j-1]-2js[i]-2s[i]s[j-1]=-2(i+s[i])(j+s[j-1])
S = i + s [ i ] , F [ j ] = j + s [ j 1 ] S=i+s[i],F[j]=j+s[j-1]

于是 f [ i ] = ( i j + s [ i ] s [ j 1 ] L ) 2 + f [ j 1 ] = ( i 2 + s [ i ] 2 + L 2 + 2 i s [ i ] 2 i L 2 s [ i ] L ) + G [ j ] 2 S F [ j ] f[i]=(i-j+s[i]-s[j-1]-L)^2+f[j-1]=(i^2+s[i]^2+L^2+2is[i]-2iL-2s[i]L)+G[j]-2SF[j]
然后斜率优化基本套路,考虑j和k两个状态(j<k)
显然S递增且F[j]<F[k],所以-2SF[j]>-2SF[k],k的增长量比j小
而题目求的是答案最小,所以随着时间的推移k一定会比j优
于是j只有初值比k小才有存在的价值,即在第i时刻j比k优
因为队列中的数按照时间顺序加入,所以可以得知每次取队头最优,并且最早失效的状态也是队头
所以可以这样列:
G [ j ] 2 S F [ j ] &lt; G [ k ] 2 S F [ k ] G[j]-2SF[j]&lt;G[k]-2SF[k] (F[j]<F[k])
G [ j ] G [ k ] F [ j ] F [ k ] &gt; 2 S \frac{G[j]-G[k]}{F[j]-F[k]}&gt;2S
若队头失效,则
G [ j ] G [ k ] F [ j ] F [ k ] &lt; = 2 S \frac{G[j]-G[k]}{F[j]-F[k]}&lt;=2S
但下一个仍有效,即 G [ j ] G [ k ] F [ j ] F [ k ] &gt; 2 S \frac{G[j]-G[k]}{F[j]-F[k]}&gt;2S
所以可以得知维护的斜率单调递增

以后还有其他题再补上

code

#include <iostream>
#include <cstdlib>
#include <cstdio>
#define fo(a,b,c) for (a=b; a<=c; a++)
#define fd(a,b,c) for (a=b; a>=c; a--)
#define min(a,b) (a<b?a:b)
#define sqr(x) ((x)*(x))
using namespace std;

long long a[50001];
long long s[50001];
long long f[50001];
long long G[50001];
long long F[50001];
int d[50001];
long long L,S,n,i,j,k,l,h,t;

long double xl(int x,int y)
{
	return (long double)(G[x]-G[y])/(F[x]-F[y]);
}

int main()
{
	scanf("%lld%lld",&n,&L);
	fo(i,1,n)
	scanf("%lld",&a[i]),s[i]=s[i-1]+a[i];
	
	h=1;
	t=0;
	fo(i,1,n)
	{
		G[i]=sqr(i)+sqr(s[i-1])+2*i*s[i-1]+2*i*L+2*s[i-1]*L+f[i-1];
		F[i]=i+s[i-1];
		S=2*(i+s[i]);
		
		while (h<t && xl(d[t-1],d[t])>=xl(d[t],i)) --t;
		d[++t]=i;
		while (h<t && xl(d[h],d[h+1])<=S) ++h;
		
		f[i]=G[d[h]]+sqr(i)+sqr(s[i])+sqr(L)+2*i*s[i]-2*i*L-2*s[i]*L-2*(i+s[i])*(d[h]+s[d[h]-1]);
	}
	
	printf("%lld\n",f[n]);
}

猜你喜欢

转载自blog.csdn.net/gmh77/article/details/89179493