[bzoj1010][HNOI2008]玩具装箱

[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) \]

\[\because j< k ,即 f(j)-f(k)< 0 \]

\[\therefore \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\n",dp[n]);
}

int main()
{
	freopen("toy.in","r",stdin);
	freopen("toy.out","w",stdout);
	init();
	doing();
	return 0;
}


posted @ 2017-10-23 17:03  Deadecho  阅读(188)  评论(0编辑  收藏  举报