bzoj 4518 [Sdoi2016]征途 动态规划+斜率优化

Description

Pine开始了从S地到T地的征途。
从S地到T地的路可以划分成n段,相邻两段路的分界点设有休息站。
Pine计划用m天到达T地。除第m天外,每一天晚上Pine都必须在休息站过夜。所以,一段路必须在同一天中走完。
Pine希望每一天走的路长度尽可能相近,所以他希望每一天走的路的长度的方差尽可能小。
帮助Pine求出最小方差是多少。
设方差是v,可以证明,v×m^2是一个整数。为了避免精度误差,输出结果时输出v×m^2。
Input

第一行两个数 n、m。
第二行 n 个数,表示 n 段路的长度
Output

一个数,最小方差乘以 m^2 后的值

Sample Input

5 2

1 2 5 8 6
Sample Output

36
HINT

1≤n≤3000,保证从 S 到 T 的总路程不超过 30000


传送门
假设说用S(i,j)表示i~j内的数字和,sum表示S(1,n),
然后设分成的m段是:(Li,Ri),其中Li=R(i-1)+1
那么我们知道答案是:

min{1m[S(Li,Ri)avg]2}m2avg=summ

我们把 m2 乘进去,那么马上得到了:
min{m(S(Li,Ri)summ)2}

再化简,并且把平方项展开:
min{(mS(Li,Ri)22sumS(Li,Ri)+(summ)2m}1<=i<=m

由于i有m个,所以最后的那一项到最后也是整数,而且可以提取出来。
所以经过化简,我们得到了最终答案的表达式:
min{(mS(Li,Ri)22sumS(Li,Ri)}+sum21<=i<=m

为了维护前面min里面的部分的最小值,设计一个简单的dp:
f[i][j]表示前i个,分了j块的最小花费。
f[i][j]=min{f[k][j]+calc(k+1,i)}
发现这是一个O(N^2*M)的算法,那么就可以考虑斜率优化啦
……为了方便就把j,i互换吧,因为斜率优化是在i维上取最优值。
f[j][i],,
斜率推导过程就不说了。。= =
现在微辣方便就用Si表示1~i这个前缀和,
而且f[i],j维先不写出来吧!
反正斜率推出来是酱紫的:
mSj2mSk2+2sumSj2sumSk+f[j]f[k]2mSj2mSkj<k<ijk

那么每次维护上一层的头,这一层的尾,就可以了。。
不卡精度,直接维护斜率就好了。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int
    N=3005;
const ll
    inf=2000000000LL;
int n,m,head[2],tail[2],Q[2][N];
ll sum[N],f[N][N];
ll calc(int i,int j,int k){
    return f[j-1][k]+(ll)m*(sum[i]-sum[k])*(sum[i]-sum[k])-2LL*sum[n]*(sum[i]-sum[k]);
}
double xl(int o,int j,int k){
    ll t1=sum[j]*sum[j]*m-sum[k]*sum[k]*m+sum[n]*sum[j]*2LL-sum[n]*sum[k]*2LL+f[o][j]-f[o][k],
        t2=2LL*m*(sum[j]-sum[k]);
    return (double)t1/(double)t2;
}
int main(){
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++){
        scanf("%lld",&sum[i]);
        sum[i]+=sum[i-1];
    }
    head[0]=tail[0]=0,Q[0][0]=0;
    for (int j=1;j<=m;j++){
        int now=j&1,pre=now^1;
        head[now]=tail[now]=0;
        for (int i=1;i<=n;i++){
            while (head[pre]<tail[pre] &&
                calc(i,j,Q[pre][head[pre]])>
                calc(i,j,Q[pre][head[pre]+1])) head[pre]++;
            f[j][i]=calc(i,j,Q[pre][head[pre]]);
            while (head[now]<tail[now] &&
                xl(j,Q[now][tail[now]-1],Q[now][tail[now]])>
                xl(j,Q[now][tail[now]],i)) tail[now]--;
            Q[now][++tail[now]]=i;
        }
    }
    printf("%lld\n",f[m][n]+sum[n]*sum[n]);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值