比较裸的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;
}

本文详细解析了一道经典的DP题目,通过引入斜率优化的方法,有效地解决了原问题中因枚举导致的时间复杂度过高的问题。文章给出了完整的实现代码,并通过理论推导验证了决策单调性的正确性。
8879

被折叠的 条评论
为什么被折叠?



