比较裸的DP+斜率优化啦…… 让窝又想到了BZOJ上A的第一道有意义的题1597…… 作为第27个A的题也让我颇有感触……
设前
i
个玩具放置到
f[i][j]=f[k][j−1]+cost[k+1][i]
其中k为我们枚举的上一个容器的末玩具编号。然后
cost[k+1][i]
用前缀和搞搞也可以
O(1)
弄出来:
cost[i+1][j]=(j−i+sum[j]−sum[i]−L−1)2
。但枚举
k
肯定会TLE… (诶下面这都是些什么…
(Teenagers Lack Entertainment)
(Teenagers Lost Eggs)
(啊好污啊…)
所以我们需要优化。
首先把第二维去掉:完全不需要。然后参阅了黄学长博客,对斜率优化有了更深的理解。
观察 cost 的表达式,我们令 d[i]=sum[i]+i , t=L+1 (少写几个字…),则有
f[k]+(d[j]−d[k]−t)2<f[p]+(d[j]−d[p]−t)2
第一步,证明决策单调性。什么意思呢?我们设 f[i] 从 f[k] 转移比从 f[p] 转移来要优,且 p<k ,那么对于 f[i] 之后的任意状态 f[t] ,都有从 f[k] 转移比从 f[p] 转移来要优。证明并不难,只要设 d[t]=d[i]+s 然后搞搞就行了。
第二步,解出斜率方程。
f[k]+cost[k+1][j]<f[p]+cost[p+1][j]
则有
f[k]+(d[j]−d[k]−t)2<f[p]+(d[j]−d[p]−t)2
由于我们已经获得了单调递增的函数
d
,化简移项一番(把
(f[k]+(d[k]+t)2)−(f[p]+(d[p]+t)2)2×(d[k]−d[p])<=d[i]
有了上述这些性质,我们用单调队列维护一个下凸包即可。具体来说,每次取出
i
作为当前决策点,然后考虑当前队列。如果一开始没有用long long,然后后来打补丁的时候又各种地方忘改成long long……
// BZOJ 1010
#include <cstdio>
#include <cstring>
using namespace std;
const int N=50000+5;
#define LL long long
#define read(x) scanf("%d", &x)
#define rep(i,a,b) for (int i=a; i<=b; i++)
int n, c, L, Q[N];
LL f[N], d[N], t;
LL sqr(LL x) { return x*x; }
double calc(int p, int k) {
return (double)(f[k]+sqr(d[k]+t)-(f[p]+sqr(d[p]+t)))/(double)(2.0*(d[k]-d[p]));
}
int main()
{
read(n); read(L);
d[0]=0;
rep(i,1,n) read(c), d[i]=d[i-1]+c;
rep(i,1,n) d[i]+=i;
int head=1, tail=1;
Q[1]=0; f[0]=0;
t=L+1;
rep(i,1,n) {
while (head<tail && calc(Q[head], Q[head+1])<=d[i]) head++;
int x=Q[head];
f[i]=f[x]+sqr(d[i]-d[x]-t);
while (head<tail && calc(Q[tail-1], Q[tail])>calc(Q[tail], i)) tail--;
Q[++tail]=i;
}
printf("%lld\n", f[n]);
return 0;
}