BZOJ4518: 征途 题解

本文介绍了一种利用动态规划与斜率优化的方法来解决特定问题:给定一系列数值,将其划分为若干段,使得每段内的数值方差之和最小。文章详细解析了算法思路,并给出了完整的代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

方差的定义都记不得了,还查了一波百度百科…
dp[i][j]表示已经考虑了i天,考虑到第j段路的最小方差,可以这样算的原因是这串数的平均数已经确定了,是summsumm,所以后面的分配不会影响前面的贡献
显然有dp[i][j]=minj1k=1dp[i1][k]+calc(j+1,i)dp[i][j]=mink=1j−1dp[i−1][k]+calc(j+1,i)
其中calc(j+1,i)=m21m(ik=j+1a[k]average)2calc(j+1,i)=m2∗1m∗(∑k=j+1ia[k]−average)2
m2m2是题目里要求乘的,1m1m来自方差的定义
sumsum数组表示原数组的前缀和,则calc(j+1,i)=m(sum[i]sum[j]average)2calc(j+1,i)=m(sum[i]−sum[j]−average)2
式子里面多了一个average,不爽,考虑怎么把它弄出去
展开,calc(j+1,i)=m[(sum[i]sum[j])22(sum[i]sum[j])average+average2]=m(sum[i]sum[j])2+2(sum[i]sum[j])sum[n]mm+sum[n]2m2mcalc(j+1,i)=m[(sum[i]−sum[j])2−2(sum[i]−sum[j])average+average2]=m(sum[i]−sum[j])2+2(sum[i]−sum[j])sum[n]m∗m+sum[n]2m2∗m
最后一项sum[i]2msum[i]2m是常数,且做了m轮之后的和是sum[n]
中间的项里面做了m轮之后,sum[i]-sum[j]相邻的项会抵消,所以和是sum[n]-sum[0]=sum[n],所以中间的项也是常数
所以我们只要考虑第一项就可以了
于是这是一个经典的dp+斜率优化

#include <cstdio>
#include <iostream>
#include <cstring>
#include <string>
#include <cstdlib>
#include <utility>
#include <cctype>
#include <algorithm>
#include <bitset>
#include <set>
#include <map>
#include <vector>
#include <queue>
#include <deque>
#include <stack>
#include <cmath>
#define LL long long
#define LB long double
#define x first
#define y second
#define Pair pair<int,int>
#define pb push_back
#define pf push_front
#define mp make_pair
#define LOWBIT(x) x & (-x)
using namespace std;

const int MOD=1e9+7;
const LL LINF=2e16;
const int INF=1e9;
const int magic=348;
const double eps=1e-10;
const double pi=3.14159265;

inline int getint()
{
    char ch;int res;bool f;
    while (!isdigit(ch=getchar()) && ch!='-') {}
    if (ch=='-') f=false,res=0; else f=true,res=ch-'0';
    while (isdigit(ch=getchar())) res=res*10+ch-'0';
    return f?res:-res;
}

int n,m;
LL dp[3048],tmp[3048];
LL a[3048],sum[3048];
int q[3048],head,tail;

inline double calc(int ind1,int ind2)
{
    int pos1=q[ind1],pos2=q[ind2];
    return (dp[pos1]+sum[pos1]*sum[pos1]-dp[pos2]-sum[pos2]*sum[pos2])*1.0/(sum[pos1]-sum[pos2]);
}

int main ()
{
    int i,sta;
    n=getint();m=getint();
    for (i=1;i<=n;i++) a[i]=getint(),sum[i]=sum[i-1]+a[i];
    for (i=1;i<=n;i++) dp[i]=1e9;dp[0]=0;
    for (sta=1;sta<=m;sta++)
    {
        head=tail=1;q[head]=sta-1;
        for (i=0;i<=n;i++) tmp[i]=INF;
        for (i=sta;i<=n;i++)
        {
            while (head<tail && calc(head,head+1)<2*sum[i]) head++;
            tmp[i]=dp[q[head]]+(sum[i]-sum[q[head]])*(sum[i]-sum[q[head]]);
            q[++tail]=i;
            while (head+1<tail && calc(tail-2,tail-1)>calc(tail-1,tail)) q[tail-1]=q[tail],tail--;
        }
        for (i=0;i<=n;i++) dp[i]=tmp[i];
    }
    printf("%lld\n",dp[n]*m-sum[n]*sum[n]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值