HNOI2008 玩具装箱Toy

传送门

这道题的题目描述好像很奇怪……不过想了一下,应该要求的是每个做出来的箱子是不能有空的。(否则的话这题也太简单了)

朴素的DP方程很好想,设sum[i]为前i个物品的长度之和,方程就是dp[i] = min(dp[j] + (sum[i] - sum[j] + i - j - 1) ^ 2)

不过这么做的复杂度是O(n^2)的,而题目的数据范围要求是50000,会超时,必须优化。

听说这是一道非常经典的斜率优化入门题……😂

现在对于一个i,假设我们有两种选取方法:j,k。当j优于k时,则有:dp[j] + (sum[i] - sum[j] + i - j - 1 - L)^2 < dp[k] + (sum[i] - sum[k] + i - k - 1 - L)^2

我们设g[i] = sum[i] + i,C = L+1,并且把式子展开,将两边相同的项消去并移项,得到:(dp[j] + (g[j] + C)^2) - (dp[k] + (g[k] + C)^2) < 2*g[i]*(g[j] - g[k])

把右面一项中的(g[j]-g[k])移过来,可以得到左边的式子看起来是一个斜率的形式。

我们记slope(k,j) = (dp[j] + (g[j] + C)^2) - (dp[k] + (g[k] + C)^2) / (2*(g[j] - g[k])),那么当j决策优于k决策时,必有slope(k,j) < g[i].反之亦然。

那我们的做法就比较清晰了。我们建立一个单调队列,队首元素为当前最优解。从1~n枚举每一个i,如果当前的队列是空的就把当前元素压进来。

否则我们有两种操作要做: 

1.对于队首元素(head),如果满足slope(q[head],q[head+1]) < g[i],那么说明当前的队首并不是最优解,将其弹出队列。

这点很显然,因为如果当前的队首元素不是最优解,而队首元素的位置还在最前面,说明它再也不可能成为最优解,直接将其弹出队列即可。

2.对于队尾元素,如果满足slope(q[tail-1],q[tail]) > slope(q[tail],i),那么将tail元素弹出,之后继续比较。

为什么?因为tail元素如果想成为最优解,首先它必须满足slope(q[tail-1],q[tail])< g[i],但是此时因为slope(q[tail],i)也小于slope(q[tail-1],q[tail]),也就是说i比tail更优,那么tail无论如何也不可能是最优解。否则,若slope(q[tail-1],q[tail] > g[i]),那么可以直接说明tail没有tail-1优,直接排除即可。

这样就可以了,代码很简短。

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<queue>
#include<cstring>
#define rep(i,a,n) for(int i = a;i <= n;i++)
#define per(i,n,a) for(int i = n;i >= a;i--)
#define enter putchar('\n')
using namespace std;
typedef long long ll;
const int M = 50005;
ll n,L,f[M],dp[M],q[M],head,tail; 
ll read()
{
    ll ans = 0,op = 1;
    char ch = getchar();
    while(ch < '0' || ch > '9')
    {
    if(ch == '-') op = -1;
    ch = getchar();
    }
    while(ch >='0' && ch <= '9')
    {
    ans *= 10;
    ans += ch - '0';
    ch = getchar();
    }
    return ans * op;
}
double slope(ll x,ll y)
{
    return (double) (dp[y]-dp[x]+(f[y]+L)*(f[y]+L)-(f[x]+L)*(f[x]+L)) / (2.0 * (f[y] - f[x]));
}
int main()
{
    n=read(),L=read(),L++;
    rep(i,1,n) f[i] = read(),f[i] += f[i-1] + 1;
    rep(i,1,n)
    {
        while(head < tail && slope(q[head],q[head+1]) <= f[i]) head++;
        ll t = q[head];
        dp[i] = dp[t] + (f[i] - f[t] - L) * (f[i] - f[t] - L);
        while(head < tail && slope(q[tail],i) < slope(q[tail-1],q[tail])) tail--;
        q[++tail] = i;
    } 
    printf("%lld\n",dp[n]);
    return 0;
}

 

posted @ 2018-08-25 15:56  CaptainLi  阅读(141)  评论(0编辑  收藏  举报