BZOJ 4518: [Sdoi2016]征途(斜率优化DP)

题目描述

传送门:
http://www.lydsy.com/JudgeOnline/problem.php?id=4518

题目大意:就是n个数分成m段,每段求和然后使得这些和的方差v最小,输出v*m^2。


题解

我们要求最小值的式子的就是

mi=1m(a[i]summ)2

其中 a[i] 为第 i 段的和, sum 为总和。我们反手化简一下就变成

mi=1ma[i]2sum2

我们对和式做一个 DP

f[i][j]=min(f[i][j],f[i1][k]+(sum[j]sum[k])2)

这就是一个裸的斜率优化了,维护一个下凸壳即可。直接将 f 数组滚动,开一个双端队列维护即可。

但我实在太久没碰过这个东西了,一开始忘了把 g[i1] 先压入队列,又忘了把第 0 段的 g 数组赋初值( g[0]=0 ,其他为 INF ),导致样例都过不了。

感觉自己在复习初二的知识。。


程序

#include 
#include 
#include 
#include 
#include 
#include 
#define maxn 3456
#define INF 10000100653543LL

using namespace std;

typedef long long LL;

int n, m;
LL a[maxn], sum[maxn], f[maxn], g[maxn];
int q[maxn], hh, tt;

LL GetX(int x, int y){
    return sum[x] - sum[y];
}

LL GetY(int x, int y){
    return g[x] + sum[x] * sum[x] - g[y] - sum[y] * sum[y];
}

LL GetAns(int x, int y){
    return g[y] + (sum[x] - sum[y]) * (sum[x] - sum[y]);
}

int main(){

    scanf("%d%d", &n, &m);

    for(int i = 1; i <= n; i++){
        scanf("%lld", &a[i]);
        sum[i] = sum[i-1] + a[i];
    }

    for(int i = 1; i <= n; i++)  g[i] = INF;
    g[0] = 0LL;

    for(int i = 1; i <= m; i++){
        hh = tt = 0;
        q[hh] = i - 1;
        for(int j = i; j <= n; j++){
            while(hh < tt && GetY(q[hh+1], q[hh]) <= 2 * sum[j] * GetX(q[hh+1], q[hh]))  hh ++;
            f[j] = GetAns(j, q[hh]);
            while(hh < tt && GetY(j, q[tt]) * GetX(q[tt], q[tt-1]) <= GetY(q[tt], q[tt-1]) * GetX(j, q[tt]))  tt --;
            q[++tt] = j;
        }
        for(int j = i; j <= n; j++)  g[j] = f[j];
    }

    printf("%lld\n", m*f[n]-sum[n]*sum[n]);

    return 0;
}

你可能感兴趣的:(斜率优化,DP,&,记忆化搜索,单调队列,凸包,BZOJ)