洛谷 P4072 [SDOI2016]征途 dp+斜率优化

Description

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

Input

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

Output

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

Sample Input

5 2
1 2 5 8 6

Sample Output

36

Data Constraint

对于 30% 的数据,1≤n≤10。
对于 60% 的数据,1≤n≤100。
对于 100% 的数据,1≤n≤3000。
保证从 S 到 T 的总路程不超过 30000。

分析:
化简式子得
v ∗ m 2 = m ∗ ∑ i = 1 m v i 2 − s u m 2 v*m^2=m*\sum_{i=1}^{m}v_i^2-sum^2 vm2=mi=1mvi2sum2
关键求子段平方和最小值。
斜率优化非常套路,不过我忘记了前面状态看成点,当前状态看成直线的那种做法。
这里讨论把前面状态看成直线,当前状态看成点的方法。
显然
f [ i ] [ k ] = min ⁡ j = 0 i − 1 f [ j ] [ k − 1 ] + ( s u m [ i ] − s u m [ j ] ) 2 f[i][k]=\min_{j=0}^{i-1}f[j][k-1]+(sum[i]-sum[j])^2 f[i][k]=j=0mini1f[j][k1]+(sum[i]sum[j])2
也就是
f [ i ] [ k ] − s u m [ i ] 2 = − 2 ∗ s u m [ j ] ∗ s u m [ i ] + s u m [ j ] 2 + f [ j ] [ k − 1 ] f[i][k]-sum[i]^2=-2*sum[j]*sum[i]+sum[j]^2+f[j][k-1] f[i][k]sum[i]2=2sum[j]sum[i]+sum[j]2+f[j][k1]
显然
k = − 2 ∗ s u m [ j ] k=-2*sum[j] k=2sum[j] b = s u m [ j ] 2 + f [ j ] [ k − 1 ] b=sum[j]^2+f[j][k-1] b=sum[j]2+f[j][k1]
因为斜率递减,而自变量递增,那么一旦一条直线比后面的直线不优,那么这条直线可以直接弹掉。
如果一条直线被它左右两条直线所完全覆盖,那么这条直线也没有用。可以通过求左右两条直线的交点是否比当前直线优。

代码:

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#define LL long long

const int maxn=3e3+7;

using namespace std;

int n,m,h,t;
LL sum[maxn],f[maxn][maxn];

struct node{
    LL k,b;
}q[maxn];

LL get(int p,LL x)
{
    return q[p].k*x+q[p].b;
}

bool check(int x,int y,int z)
{
    LL w1=(LL)(q[z].k-q[y].k)*(q[z].b-q[x].b);
    LL w2=(LL)(q[x].k-q[z].k)*(q[y].b-q[z].b);
    return w1<=w2;
}

int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++) scanf("%lld",&sum[i]);
    for (int i=1;i<=n;i++) sum[i]+=sum[i-1];
    for (int j=0;j<=n;j++) f[j][1]=sum[j]*sum[j];			
    for (int i=2;i<=m;i++)
    {
        h=1,t=0;
        for (int j=0;j<=n;j++)
        {
            if (h<=t)
            {
                while ((h<t) && (get(h,sum[j])>=get(h+1,sum[j]))) h++;
                f[j][i]=get(h,sum[j])+sum[j]*sum[j];
            }
            q[++t]=(node){-2*sum[j],f[j][i-1]+sum[j]*sum[j]};
            while ((t-h>1) && (check(t-2,t-1,t)))
            {
                q[t-1]=q[t];
                t--;
            }
        }
    }
    printf("%lld",f[n][m]*m-sum[n]*sum[n]);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值