Description
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
输出最小费用
Sample Input
5 4
3
4
2
1
4
Sample Output
1
分析:
斜率优化dp
这道题不限箱子的数目,所以一维就够了(终于是一维了,受不了二维)
f[i]=f[j]+((i-j-1+sum[i]-sum[j])-L)^2
斜率优化
画柿子演示:
设k < j < i,转移点j比k更优
则有:
斜率的形式我们有了
g[j][k] < i+sum[i]
表示j比k优
关键点
g[i][j] < g[j][k]
j点不可能作为转移点
主要操作:
1.维护一个双端对列
2.在转移的时候,如果队列中依次有a,b,c等元素
若g[b][a] < sum[i]+i,a出队
直到g[x][y]>=sum[i]+i
从队首转移
3.入队的时候,如果队列中依次有a,b,c等元素,入队元素为d
若g[d][c] < g[c][b] ,c出对
直到g[d][x]>=g[x][y] ,d入队
tip
不用特殊计算f[1]的值,
直接在循环里维护就行了(这样既可以维护f,也可以入队)
我这个zz
在写转移方程的时候竟然忘了累加
f[i]=f[q[tou]]+sqr(i-q[tou]+sum[i]-sum[q[tou]]-L);
我发现只要是牵扯到L,用到的都是L+1,所以我干脆在一开始的时候就L++
这里写代码片
#include<cstdio>
#include<cstring>
#include<iostream>
#include<cmath>
#define ll long long
using namespace std;
const int N=50010;
int n,q[N],tou,wei;
ll l,a[N],f[N];
ll sqr(ll x)
{
return x*x;
}
double get(int j,int k)
{
double x1=(double)f[j]+sqr(j+a[j])+2*l*(j+a[j]);
double x2=(double)f[k]+sqr(k+a[k])+2*l*(k+a[k]);
double x3=(double)2*(j+a[j]-k-a[k]);
return (double)(x1-x2)/x3;
}
void doit()
{
int i,j;
tou=wei=0;
for (i=1;i<=n;i++)
{
while (tou<wei&&get(q[tou+1],q[tou])<=i+a[i]) tou++;
f[i]=f[q[tou]]+sqr((i-q[tou]+a[i]-a[q[tou]])-l);
while (tou<wei&&get(i,q[wei])<=get(q[wei],q[wei-1])) wei--;
q[++wei]=i;
}
printf("%lld",f[n]);
}
int main()
{
scanf("%d%lld",&n,&l);
l++;
for (int i=1;i<=n;i++) scanf("%lld",&a[i]),a[i]+=a[i-1];
doit();
return 0;
}