【BZOJ4518】征途,斜率优化DP

来源:互联网 发布:java时间序列预测模型 编辑:程序博客网 时间:2024/04/29 04:14

写在前面:当时考场上想到了搞一个二维队列记录上次的函数值,但莫名就感觉这样和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 longusing 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 longLL 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 0
原创粉丝点击