bzoj4518 [Sdoi2016]征途(斜率优化DP)

83 篇文章 0 订阅
51 篇文章 1 订阅

bzoj4518 [Sdoi2016]征途

原题地址http://www.lydsy.com/JudgeOnline/problem.php?id=4518

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

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

题解:
如果设每一天的旅程分别是 t1,t2...tm ,每一段路的前缀和分别是 s1,s2...sn
ans=m21mmi=1(tisnm)2
=mmi=1t2i+s2n2snti
=mmi=1t2is2n

dp[i][k] 表示前i段路分成k段 t2i 的最小值。

dp[i][k]=min(dp[j][k1]+(sisj)2)1k<=j (可以一天不走路)
dp[i][k]=dp[j][k1]+(sisj)2
dp[i][k]+2sisj=dp[j][k1]s2j+s2i
又是截距的形式。
求min值,下凸壳, x=2sj 单增, k=2si 单增,直接用队列维护即可。

注意:可以一天不走路,于是转移时注意一下,或者每个k的f[n]取个max。

代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#define LL long long
using namespace std;
const int N=3005;
const LL inf=1e15;
int n,m,Q[N];
LL f[N],g[N],s[N];
LL cal(int x,int i) {return f[x]+s[x]*s[x]-2*s[i]*s[x];}
long double slope(int x,int y) {return (long double) (f[y]-f[x]+s[y]*s[y]-s[x]*s[x])/(s[y]-s[x]);}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) {scanf("%lld",&s[i]); s[i]+=s[i-1];}
    for(int i=1;i<=n;i++) f[i]=inf; f[0]=0; LL ret=inf;
    for(int k=1;k<=m;k++)
    {
        int h=1; int t=0; 
        for(int i=0;i<=n;i++)
        {
            while(h<t&&cal(Q[h],i)>=cal(Q[h+1],i)) h++;
            if(h>t) g[i]=inf;
            else g[i]=cal(Q[h],i)+s[i]*s[i];
            while(h<t&&slope(Q[t-1],Q[t])>=slope(Q[t-1],i)) t--;
            t++; Q[t]=i;
        }
        for(int i=0;i<=n;i++) f[i]=g[i]; ret=min(ret,f[n]);
    }
    ret=1LL*m*ret-s[n]*s[n];
    printf("%lld\n",ret);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值