这是一个奇怪的东西- -
http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=16331
以toy为例浅谈斜率优化
(可以略过前面看分割线以后,转移方程在前面)
•直接来看这样一个问题:
•BZOJ 1010 TOY
•[HNOI2008]
•P教授要去看奥运,但是他舍不下他的玩具,于是他决定把所有的玩具运到北京。他使用自己的压缩器进行压缩,其可以将任意物品变成一堆,再放到一种特殊的一维容器中。P教授有编号为1...N的N件玩具,第i件玩具经过压缩后变成一维长度为Ci.为了方便整理,P教授要求在一个一维容器中的玩具编号是连续的。同时如果一个一维容器中有多个玩具,那么两件玩具之间要加入一个单位长度的填充物,形式地说如果将第i件玩具到第j个玩具放到一个容器中,那么容器的长度将为 x=j-i+Sigma(Ck) i<=K<=j 制作容器的费用与容器的长度有关,根据教授研究,如果容器长度为x,其制作费用为(X-L)^2.其中L是一个常量。P教授不关心容器的数目,他可以制作出任意长度的容器,甚至超过L。但他希望费用最小.
•Input
•第一行输入两个整数N,L.接下来N行输入Ci.1<=N<=50000,1<=L,Ci<=10^7
•Output
•输出最小费用
•
•
•相信如果是dp很快就能写出状态
•Define dp[i]为前i个玩具都装入好的最小代价
•转移:dp[i]=min{dp[j-1]+(sum[i]-sum[j-1]+i-j-L)^2} j<=i
•前j-1个放在一起,第j到i放一起,找一个最小代价
•
•这样就完了?
•观察数据范围。
•N<=50000
•而上面的转移是n^2的,TLE.
•
•
•怎么办?
•我们观察之前的状态,会发现一些状态根本不是最优的,但是n^2枚举的时候仍然寻找到了,浪费了很多时间。
-------------------------------------------------------------------正文------------------------------------------------------------
•所以,引入斜率优化。
•
•
在这里,我们得到一个转移方程.
Dp[i]=min{dp[j-1]+(sum[i]-sum[j-1]+i-j-L)^2}
定义:g[i]=sum[i]+i-L,h[j]=sum[j-1]+j
则Dp[i]=min{dp[j-1]+g[i]^2+h[j]^2-2*g[i]*h[j]}
这一步我们将i,j独立开来,得到dp[j-1],g[i]^2,h[j]^2.
但是2g[i]h[j]并没有分离
•对于刚才得到的式子:
•Dp[i]=min{dp[j-1]+g[i]^2+h[j]^2-2g[i]h[j]};
•考虑对于每一个确定的i,什么时候的j<=i使得dp[j-1]+h[j]^2-2g[i]h[j] ①最小.
•这样考虑:对于一个j1<j2<=i
•我们需要知道什么时候有dp[i](j2)<dp[i](j1)
•也就是,我们再定义y[j]=dp[j-1]+h[j]^2
•y[j2]-2g[i]h[j2]<y[j1]-2g[i]h[j1]
•y[j2]-y[j1]<2g[i](h[j2]-h[j1]) h[j]递增
•∴(y[j2]-y[j1])/(h[j2]-h[j1])<2g[i]
•观察这个式子:
•(y[j2]-y[j1])/(h[j2]-h[j1])<2g[i]
•是不是有点斜率的感觉,这个就涉及新知识斜率优化。
•我们要怎么维护.
•定义T(j1,j2)=左边<2g[i]表示满足这个关系的时候,j2比j1更优.
•也就是说T(j1,j2)>2g[i]的时候,j1比j2更优
•那么,我们就有眉目了
•已经知道T(j1,j2)>2g[i]的时候j1更优
•而且我们知道g[i]单调递增
•考虑对于一组x<y,T(x,y)<2g[i]<2g[i+1]<2```
•也就是说T(x,y)在之后永远满足这个条件,也就是恒有T(x,y)<2g[i0],i0>i,y比x更优.
•x就没有必要保留.
•我们需要保留的只是T(x,y)>2g[i],这样才能保证最优解存在且没有多余的项。
•当T(j1,j2)>2g[i]的时候,j1更优
•于是我们保留2g[i]<T(j1,j2)<T(j2,j3)<``````
•2g[i]<T(j1,j2)<T(j2,j3)<```
•可以知道,j1比j2优,j2比j3优,j3比j4优·····
•最优在队首
•
•也就是只需要维护2g[i]<T(j1,j2)<T(j2,j3)<T(j3,j4)<T(j4,j5)<```
•最优决策取队首就行了
•
•用双端队列维护
•这里要注意的是我们状态定义没错,但是实现起来会有一定问题(初始化下标为负(●—●))
•所以,重新定义状态转移
•Dp[i]=min{dp[j]+(sum[i]-sum[j]+i-(j+1)-L)^2}
•为了巩固之前的方法,尝试自己按照流程做一次。
•定义新函数->参数分离(i,j分开表达)->斜率表达式->讨论情况(T(x,y)?g[i])->决定最优解(T(j1,j2)?T(j2,j3))->取最优决策(怎样维护,最优解是否在队首或队尾)
☆重新分析是为了有对这个算法的整体把握,注意体会上一行算法流程的每一步的思路。
值得注意的是,这里的斜率表达式T(j1,j2)=(y[j2]-y[j1])/(h[j2]-h[j1])<2g[i] 中g[i]是单调的,并且分母表达式中函数也是单调的, 仅在这种情况下可维护单调性T(j1,j2)?T(j2,j3)?T(j3,j4),且在队首或队尾取得最优决策!!!
以上属于简单的斜率优化,重在理解分析过程,代码的实现难度并不大,一般采用双端队列维护 (建议手工队列)
下面附上AC代码: bzoj1010
(带有简单分析)
//http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=16331
#include<cstdio>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<queue>
#include<cmath>
#define ll long long
using namespace std;
int n,L;
const int maxn=50000+20;
ll dp[maxn];
ll sum[maxn];
ll g[maxn],h[maxn];
//想法对的,但没法实现,原因:sum[j-1]为负
//dp[i]=dp[j]+(sum[i]-sum[j]+i-(j+1)-L)^2
//g[i]=sum[i]+i-L;
//h[j]=sum[j]+j+1
//dp[i]=dp[j]+g[i]*g[i]+h[j]*h[j]-2g[i]h[j]
//对于j2>j1 dp[i](j2)<dp[i](j1)
//dp[j2]+h[j2]^2-2g[i]h[j2]<dp[j1]+h[j1]^2-2g[i]h[j1]
//y[j]=dp[j]+h[j]^2
//y[j2]-y[j1]<2g[i](h[j2]-h[j1])
//T(x,y)=(y[j2]-y[j1])/(h[j2]-h[j1])
//维护2g[i]<T(j1,j2)<T(j2,j3)<``` 最优决策在j1
int head,tail;
int q[maxn];//第几个
double T(int x,int y) //得到斜率
{
return 1.0*(dp[x]+h[x]*h[x]-dp[y]-h[y]*h[y])/(h[x]-h[y]);
}
int main()
{
while(scanf("%d%d",&n,&L)!=EOF)
{
ll x;
head=tail=0;//队空
memset(sum,0,sizeof(sum));
memset(dp,0x3f3f3f3f,sizeof(dp));
g[0]=-L;
h[0]=1;
for(int i=1;i<=n;i++)
{
scanf("%lld",&x);
sum[i]=sum[i-1]+x;
g[i]=sum[i]+i-L;
h[i]=sum[i]+i+1;
}
dp[0]=0;
//线性预处理
q[0]=0;
for(int i=1;i<=n;i++)
{
//维护单调性2g[i]<T(j1,j2)<T(j2,j3)<T(j3,j4)
while(head<tail&&T(q[head],q[head+1])<=2*g[i])head++;
int t=q[head];
dp[i]=dp[t]+g[i]*g[i]+h[t]*h[t]-2*g[i]*h[t];
while(head<tail&&T(q[tail],i)<T(q[tail-1],q[tail]))tail--;
q[++tail]=i;
}
printf("%lld\n",dp[n]);
}
return 0;
}