【BZOJ4518】征途,斜率优化DP

写在前面:当时考场上想到了搞一个二维队列记录上次的函数值,但莫名就感觉这样和N^3做法没有区别(这告诉我们当你写出部分分而且还有大把的时间时就不要放弃写标算的希望),就弃疗了,不过就算搞出来了应该也不能AC吧,斜率优化DP仍然无法理解透彻
传送门
题意:给定n个数,按顺序每次取若干个数(第一次从1开始取,之后都是从上一次的结尾处+1开始取,比如上一次取到第3个数,这一次就从第4个数开始取,不得重复),求m次取完后的最小方差,并且答案要乘以 m2
思路:没别的,先化式子, m2 一定大于0所以我们可以把它加到求方差的公式里,随后我们就发现可以约分掉一坨东西,最后的答案就是
mΣmi=1xi2Σmi=1xi
其中xi为第i次取得的数的总和
,显然 Σmi=1xi 就是n个数的总和,m也为定值,我们只要让 Σmi=1xi2 最小就可以了。我们把f[i][j]作为用过了第i次一共取了j个数的最小值,转移方程显然就是
f[i][j]=min(f[i1][jk]+(sum[j]sum[jk])2)k=1..j
初始化 f[0][0]=0
答案为 mf[m][n]sum[n]2
复杂度为 O(n3) ,可以过60%的数据
代码:
60分live版

#include<iostream>
#include<cstring>
#include<cstdio>
#define LL long long
using namespace std;
LL sum[3002];
LL f[1006][1006];
int m,n,a[1010];
int main()
{
    freopen("journey.in","r",stdin);
    freopen("journey.out","w",stdout);
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++)
        scanf("%d",&a[i]),
        sum[i]=sum[i-1]+a[i];
    memset(f,63,sizeof(f));
    f[0][0]=-sum[n]*sum[n];
    for (int i=0;i<m;i++)
        for (int j=0;j<=n;j++)
            for (int k=0;k<=n-j;k++)
            f[i+1][j+k]=min(f[i+1][j+k],f[i][j]+(sum[j+k]-sum[j])*(sum[j+k]-sum[j])*m);
    printf("%I64d",f[m][n]);
}

std要求 O(n2) 做法,我们比较容易想到斜率优化,快速得出k来(现场时没有写出怎么斜率优化来~~~好像有那么一点思路但没有什么用啊)
至于具体思路吗……
我们设x>y,且j由x转移比y转移更优,则
f[i1][x]+(sum[j]sum[x])2<f[i1][y]+(sum[j]sum[y])2
化简得
f[i1][x]+sum[x]2(f[i1][y]+sum[y]2)sum[x]sum[y]<2sum[j]
那么我们就可以开始斜率优化了……
100分(参考TA学长和lcomyn学长)

#include<bits/stdc++.h>
using namespace std;
#define LL long long
LL sum[3002],f[3002][3002];
int n,m,head,tail,a;
int q[3002];
inline double GET(int i,int x,int y)
{
    return ((double)(f[i][x]+sum[x]*sum[x]-f[i][y]-sum[y]*sum[y]))/((double)(sum[x]-sum[y]));
}
main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++)
        scanf("%d",&a),
        sum[i]=sum[i-1]+a;
    memset(f,63,sizeof(f));
    f[0][0]=0;
    for (int i=1;i<=m;i++)
    {
        head=tail=1;
        for (int j=1;j<=n;j++)
        {
            while (head<tail&&GET(i-1,q[tail],q[tail-1])>GET(i-1,j,q[tail])) tail--;
            q[++tail]=j;
            while (head<tail&&GET(i-1,q[head+1],q[head])<(double)(sum[j]<<1)) head++;
            f[i][j]=(sum[j]-sum[q[head]])*(sum[j]-sum[q[head]])+f[i-1][q[head]];
        }
    }
    printf("%lld\n",f[m][n]*m-sum[n]*sum[n]);
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值