[bzoj1010][HNOI2008]玩具装箱
标签: DP 斜率优化
题解
(找草稿纸ing......)
首先我们可以很容易写出dp的方程。
[dp[i]=max(dp[j]+i-j+1+sum_{l=j+1}^ic[l]-L)^2))
]
[即dp[i]=max(dp[j]+(i-j-1+sum(i)-sum(j)-L)^2)
]
[令f(i)=sum(i)+i,C=L+1
]
[则dp[i]=max(dp[j]+(f(i)-f(j)-C)^2)
]
(现在假设i由j转移过来且j< k)
[那么有dp[j]+(f(i)-f(j)-C)^2<=dp[k]+(f(i)-f(k)-C)^2
]
[化简得dp[j]-dp[k]+f(j)^2-f(k)^2<=2(f(j)-f(k))×(f(i)-C)
]
[ecause j< k ,即 f(j)-f(k)< 0
]
[ herefore frac{dp[j]-dp[k]+f(j)^2-f(k)^2}{2(f(j)-f(k))}>=f(i)-C
]
左边其实就是求斜率的公式,注意到值只与j,k有关,所以可以看成两个点。
右边是递增的,这给我们动态弹点提供了条件。
所以我们可以用单调队列来维护一个下凸壳。
Code
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<queue>
#include<stack>
#include<set>
#include<map>
using namespace std;
#define ll long long
#define REP(i,a,b) for(int i=(a),_end_=(b);i<=_end_;i++)
#define DREP(i,a,b) for(int i=(a),_end_=(b);i>=_end_;i--)
#define EREP(i,a) for(int i=start[(a)];i;i=e[i].next)
inline int read()
{
int sum=0,p=1;char ch=getchar();
while(!(('0'<=ch && ch<='9') || ch=='-'))ch=getchar();
if(ch=='-')p=-1,ch=getchar();
while('0'<=ch && ch<='9')sum=sum*10+ch-48,ch=getchar();
return sum*p;
}
const int maxn=5e4+20;
ll n,C;
ll dp[maxn],s[maxn];
void init()
{
n=read();C=read()+1;
REP(i,1,n)s[i]=read()+1+s[i-1];
}
double count(int j,int k)
{
return (double)(dp[j]+s[j]*s[j]-dp[k]-s[k]*s[k])/(2*(s[j]-s[k]));
}
int q[maxn],head,tail;
void doing()
{
head=1;q[++tail]=0;
REP(i,1,n)
{
while(head<tail && count(q[head],q[head+1])<s[i]-C)head++;
int x=q[head];
dp[i]=dp[x]+(s[i]-s[x]-C)*(s[i]-s[x]-C);
while(head<tail && count(q[tail-1],q[tail])>count(q[tail],i))tail--;
q[++tail]=i;
}
printf("%lld
",dp[n]);
}
int main()
{
freopen("toy.in","r",stdin);
freopen("toy.out","w",stdout);
init();
doing();
return 0;
}