bzoj4518 征途

4 篇文章 0 订阅

题意

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

题解

此题有斜率优化做法
但是这篇blog是想介绍带权二分
首先推推式子
设xi为第i天走的步数,
那么显然有 ∑ i = 1 m x i = m x ‾ = S \sum_{i=1}^mx_i=m\overline x=S i=1mxi=mx=S
v = ∑ i = 1 m ( x i − x ‾ ) 2 m = ∑ i = 1 m ( x i 2 − 2 x ‾ x i + x ‾ 2 ) m = ∑ i = 1 m x i 2 − ∑ i = 1 m 2 x ‾ x i + ∑ i = 1 m x ‾ 2 m = ∑ i = 1 m x i 2 − m x ‾ 2 m v=\frac{\sum_{i=1}^m(x_i-\overline{x})^2}{m}=\frac{\sum_{i=1}^m(x_i^2-2\overline{x}x_i+\overline{x}^2)}{m}=\frac{\sum_{i=1}^mx_i^2-\sum_{i=1}^m2\overline{x}x_i+\sum_{i=1}^m\overline{x}^2}{m}=\frac{\sum_{i=1}^mx_i^2-m\overline x^2}{m} v=mi=1m(xix)2=mi=1m(xi22xxi+x2)=mi=1mxi2i=1m2xxi+i=1mx2=mi=1mxi2mx2
然后 v ⋅ m 2 = m ∑ i = 1 m x i 2 − ( m x ‾ ) 2 = m ∑ i = 1 m x i 2 − S 2 v\cdot m^2=m\sum_{i=1}^mx_i^2-(m\overline x)^2=m\sum_{i=1}^mx_i^2-S^2 vm2=mi=1mxi2(mx)2=mi=1mxi2S2
这个式子只与 ∑ i = 1 m x i 2 \sum_{i=1}^mx_i^2 i=1mxi2有关
那么我们就把问题转换成了:有n个数,你要把他们分成连续的m段,求最小的平方和
那么可有dp(i,j)表示前i个数,恰好分成j段
d p ( i , j ) = m i n ( d p ( k , j − 1 ) + ( S [ i ] − S [ k ] ) 2 ) dp(i,j)=min(dp(k,j-1)+(S[i]-S[k])^2) dp(i,j)=min(dp(k,j1)+(S[i]S[k])2)
显然会T
于是对于这种限制恰好个数的,我们就可以上今天的主角带权二分了
首先显然可以发现的一点是随着j的增大,dp(n,j)是一个下凸的曲线 (怎么证?咕咕咕…)

感性的想(理性的请转步论文),我们可以给每次增加段数的时候加上一个额外代价(或者额外收益),进而控制达到最大值时的段数
然后我们可以通过二分来调控这个额外值
最后再减去即可

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=3005;
const int INF=0x3f3f3f3f;
int x[N];
int s[N];
int n,m;
int X;
ll d[N];
int f[N];
bool check(){
    memset(d,0x3f,sizeof d);
    d[0]=f[0]=0;
    for(int i=1;i<=n;i++)
        for(int j=0;j<i;j++){
            if(d[i]>d[j]+1ll*(s[i]-s[j])*(s[i]-s[j])+X){
                d[i]=d[j]+1ll*(s[i]-s[j])*(s[i]-s[j])+X;
                f[i]=f[j]+1;
            }
            else if(d[i]==d[j]+1ll*(s[i]-s[j])*(s[i]-s[j])+X&&f[i]<f[j]+1){
                f[i]=f[j]+1;
            }
        }
    return f[n]>=m;
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%d",&x[i]);
    for(int i=1;i<=n;i++)
        s[i]=s[i-1]+x[i];
    int l=-INF,r=INF;
    int ansX;
    while(l<=r){
        X=(l+r)>>1;
        if(check()){
            ansX=X;
            l=X+1;
        }
        else{
            r=X-1;
        }
    }
    X=ansX;
    check();
    ll ans=(d[n]-m*X)*m-1ll*s[n]*s[n];
    printf("%lld\n",ans);
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值