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; }