首页 > 代码库 > BZOJ1911 [Apio2010]特别行动队 - 动态规划 - 斜率优化

BZOJ1911 [Apio2010]特别行动队 - 动态规划 - 斜率优化

欢迎访问~原文出处——博客园-zhouzhendong&AK

去博客园看该题解

题目传送门

 

 

题意概括

 

把一个整数序列划分成任意连续的段,使得划分出来的每一段的价值和最大。

 

对于某一段,价值的计算公式为 V=ax^2+bx+c,其中 x 为当前段的数值和。

 

题解

这题是博主大蒟蒻的第一道斜率优化DP题……

 

C++:while (1) 懵逼++;

 

Pascal:while (true) do inc(懵逼);

 

本题首先一看就是 DP 题。

 

但是一看 1<=n<=1000000-5<=a<=-1|b|<=10000000|c|<=100000001<=xi<=100

 

彻底吓懵!

 

一脸懵逼……

 

还是一脸懵逼……

 

突然发现可以用斜率优化。

 

为了减少代码量,避免 coder 写两份代码,好心的出题人特意规定了-5<=a<=-1

 

我们来考虑一下:

 

dp[i] 表示划分到前 i 个所能得到的最大价值和。

 

我们设 sum[i] 为前 i 个的前缀和

 

那么 dp[i]=max(dp[j]+a*(sum[i]-sum[j])^2+b*(sum[i]-sum[j])+c)  (0<=j<i)

 

貌似是一个 n^2 的状态转移方程。

 

其实就是一个 n^2 的状态转移方程。

 

接下来就是斜率优化了!

 

我们假设对于 dp[i] 来说,从 j 转移比从 k 转移更优秀(j > k),那么有如下的表达式:

 

dp[j] + a*(sum[i]-sum[j])^2 + b*(sum[i]-sum[j]) + c > dp[k] + a*(sum[i]-sum[k])^2 + b*(sum[i]-sum[k]) + c

 

so

 

dp[j] + a*(sum[i]-sum[j])^2 + b*(sum[i]-sum[j]) > dp[k] + a*(sum[i]-sum[k])^2 + b*(sum[i]-sum[k])

 

dp[j] + a*sum[i]^2 - 2*a*sum[i]*sum[j] + a*sum[j]^2 + b*sum[i] - b*sum[j] > dp[k] + a*sum[i]^2 - 2*a*sum[i]*sum[k] + a*sum[k]^2 + b*sum[i] - b*sum[k]

 

dp[j] - 2*a*sum[i]*sum[j] + a*sum[j]^2 - b*sum[j] > dp[k] - 2*a*sum[i]*sum[k] + a*sum[k]^2 - b*sum[k]

 

(dp[j] + a*sum[j]^2 - b*sum[j]) -  (dp[k] + a*sum[k]^2 - b*sum[k]) > 2*a*sum[i]*(sum[j] - sum[k])

 

so

 

((dp[j] + a*sum[j]^2 - b*sum[j])-(dp[k] + a*sum[k]^2 - b*sum[k])) / (sum[j] - sum[k]) > 2*a*sum[i]

 

我们设 x[p] = sum[p] , y[p] = dp[p] + a*sum[p]^2 - b*sum[p],

 

那么原来的方程可以表示为:

 

(y[j]-y[k]) / (x[j]-x[k]) > 2*a*sum[i]

 

左边不就是斜率的表达式吗!!

 

所以叫斜率优化。

 

Oh~ so_ga

 

当然前面的不懂脑筋都可以搞出来,关键是接下来的:

 

我们设g(i,j) = (y[i]-y[j]) / (x[i]-x[j])

 

注意 a 是一个负数,而且 sum[i] 是随着i的增大而增大的,所以 2*a*sum[i] 一定是单调递减的!

 

如果 g(i,j)>g(j,k) 那么决策 j 一定是没用的!(k<j<i)

 

分两种情况进行讨论:

 

1. 如果g(i,j)>2*a*sum[x],那么说明决策 i 优于决策 j ,那么 j 就是没用的。就算以后 2*a*sum[x] 会变,x 只能变得,所以 2*a*sum[x] 也只能变小,所以该表达式仍然满足。

 

2. 如果g(i,j)<2*a*sum[x],那么g(j,k)<2*a*sum[x],那么 j 就会比 k 劣,同样也会把 j 扔掉。

 

用上以上的分析仍然不够。我们还需要一个合理的高(keng)级(bi)数据结构,单(keng)调(bi)队列!

 

dp 的过程中,按照“如果 g(i,j)>g(j,k) 那么决策 j 一定是没用的!”的规则入队,按照如果 g(i,j)>2*a*sum[x] 的规则出队即可。

 

代码

 

#include <cstdio>
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <cmath>
using namespace std;
typedef long long LL;
const int N=1000000+5;
int n,head,tail;
LL a,b,c,r[N],sum[N],x[N],y[N],dp[N],q[N];
double g(int i,int j){
    double xi=x[i],xj=x[j],yi=y[i],yj=y[j];
    return (yi-yj)/(xi-xj);
}
int main(){
    scanf("%d%lld%lld%lld",&n,&a,&b,&c);
    sum[0]=0;
    for (int i=1;i<=n;i++)
        scanf("%lld",&r[i]),sum[i]=sum[i-1]+r[i];
    memset(x,0,sizeof x);
    memset(y,0,sizeof y);
    head=1,tail=0;
    q[++tail]=0;
    for (int i=1;i<=n;i++){
        while (head+1<=tail&&g(q[head],q[head+1])>2*a*sum[i])
            head++;
        LL s=sum[i]-sum[q[head]];
        dp[i]=dp[q[head]]+a*s*s+b*s+c;
        x[i]=sum[i],y[i]=dp[i]+a*sum[i]*sum[i]-b*sum[i];
        while (head+1<=tail&&g(q[tail-1],q[tail])<g(q[tail],i))
            tail--;
        q[++tail]=i;
    }
    printf("%lld",dp[n]);
    return 0;
}

 

BZOJ1911 [Apio2010]特别行动队 - 动态规划 - 斜率优化