征途(bzoj 4518)

题目

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

思路:这道题也是典型的斜率优化DP,不过自己之前没有好好看网上大神的代码,仅仅看了学姐给的代码,后来发现,学姐给的代码我估摸着只有她那种经常拿金牌的大佬才能看懂,思路不一样,以至于在网上学的时候很迷惑,这篇博客算是将自己带入"正轨",用平常的方法来做吧~

关于状态转移以及选取边界的问题可以看这位大佬的斜率优化DP

然后再看看这个落谷第二个解法,相信会有所启发的,鄙人不才,没有能力讲好~

个人注意点:
1.明白最后弄出来的一次函数,谁是变量,谁是因变量
2.明白枚举的是什么~

代码如下:

#include<cstring>
#include<cstdio>
#include<queue>
#define N 3005
#define int long long

int f[N][N];//纪录结果
int que[N];//记录队列
int sl[N];//纪录前缀和
int l[N];//纪律输入的
int n,m;

int min(int a,int b){return a<b?a:b;}
double slope(int u,int j,int k){return double(f[u][j]-f[u][k]+sl[j]*sl[j]-sl[k]*sl[k])/(double)(sl[j]-sl[k]);}
//这个嘛,就是算斜率,关键之处~

main(){
    scanf("%lld%lld",&n,&m);
    memset(f,0x3f,sizeof(f));
    for(int i=1;i<=n;++i)scanf("%lld",&l[i]);
    for(int i=1;i<=n;++i)sl[i]=sl[i-1]+l[i];
    int h,t;
    f[0][0]=0;
    for(int i=1;i<=n;i++)f[1][i]=sl[i]*sl[i];
    for(int p=2;p<=m;p++){//我没有滚动数组,只好用二维数组,枚举n^2
        h=1,t=0;//分别枚举的是f[i][j]中的i和j
        for(int i=1;i<=n;i++) { 
            while(h<t&&slope(p-1,que[h],que[h+1])<2*sl[i])h++;
            int j=que[h];
            f[p][i]=f[p-1][j]+(sl[j]-sl[i])*(sl[j]-sl[i]); 
            while(h<t&&slope(p-1,que[t-1],que[t])>slope(p-1,que[t],i))t--;
            que[++t]=i;
        }
    }
    int ans=m*f[m][n]-sl[n]*sl[n];
    printf("%lld\n",ans);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值