BZOJ.4072.[SDOI2016]征途(DP 斜率优化)

题目链接

题目要求使得下面这个式子最小(\(\mu=\frac{\sum_{i=1}^ma_i}{m}\)是平均数,\(a_i\)为第\(i\)段的和): \[\frac{\sum_{i-1}^m(\mu -a_i)^2}{m}*m^2\]
\(m\)可以乘进去,得: \[m\times\sum_{i=1}^m(a_i-\frac{sum}{m})^2\]
注意到其中有\(m\)\(sum\),于是连同乘上的\(m\)一起提出来就是\(m\times\sum_{i=1}^m(a_i^2-2*a_i*\frac{sum}{m})+sum^2\)
\(\sum_{i=1}^m2*a_i\)又是一个\(sum\),于是最终化简为\[m\times\sum_{i=1}^ma_i^2-sum^2\]
使\(\sum_{i=1}^ma_i^2\)最小,转移方程是\(f[i][j]=f[i-1][k]+(s_j-s_k)^2\)
容易想到有单调性,用斜率优化即可。
\[f_j-f_k+s_j^2-s_k^2\leq 2*s_i*(s_j-s_k)\]
注意初始化。。即到某点时分一组的值。

下附错误代码(化简不好导致。。)

//868kb 272ms
#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
#define gc() getchar()
const int N=3005;

int n,m,sum[N],f[N][2],q[N];

inline int read()
{
    int now=0;register char c=gc();
    for(;!isdigit(c);c=gc());
    for(;isdigit(c);now=now*10+c-'0',c=gc());
    return now;
}
inline int X(int j,int k){
    return sum[j]-sum[k];
}
inline int Y(int j,int k,int s){
    return f[j][s]-f[k][s]+(sum[j]*sum[j]-sum[k]*sum[k]);
}
inline int Calc_F(int i,int j,int s){
    return f[j][s]+(sum[i]-sum[j])*(sum[i]-sum[j]);
}

int main()
{
    n=read(),m=read();
    for(int i=1; i<=n; ++i) sum[i]=sum[i-1]+read(),f[i][0]=sum[i]*sum[i];
    int p=0;
    for(int j=2; j<=m; ++j)
    {
        int h=1,t=1; q[1]=j-1;//这可以初始化为这个,i从j枚举。
        for(int i=j; i<=n; ++i)
        {
            while(h<t && Y(q[h+1],q[h],p)<=2*sum[i]*X(q[h+1],q[h])) ++h;
            f[i][p^1]=Calc_F(i,q[h],p);
            while(h<t && 1ll*Y(i,q[t],p)*X(q[t],q[t-1])<=1ll*Y(q[t],q[t-1],p)*X(i,q[t])) --t;
            q[++t]=i;
        }
        p^=1;
    }
    printf("%d",m*f[n][p]-sum[n]*sum[n]);

    return 0;
}

错误做法:

题目要求使得下面这个式子最小(\(\mu=\frac{\sum_{i=1}^ma_i}{m}\)是平均数,\(a_i\)为第\(i\)段的和): \[\frac{\sum_{i-1}^m(\mu -a_i)^2}{m}*m^2\]
\(m\)可以乘进去,得: \[m\times\sum_{i=1}^m(\frac{sum}{m}-a_i)^2\]
那是个平方,于是写成俩,把\(m\)乘进一个去,得: \[\frac{sum^2}{m}-2*a_i*sum+a_i^2*m\]
其中 \(a_i=sum_{now}-sum_i\)\(\frac{sum^2}{m}\)是一个常数,直接用。
显然(...)具有决策单调性。然后展开一堆式子,得到。。不写了。
但是不对,暴力也可能和答案差1,因为\(sum^2\)不一定整除\(m\),但那个常数的位置还没法弄掉分母,所以计算\(f[]\)的时候就gg。
怕不是还能用double水

#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
#define gc() getchar()
const int N=3005;

int n,m,w,sum[N],f[N],f2[N][N],q[N];

inline int read()
{
    int now=0;register char c=gc();
    for(;!isdigit(c);c=gc());
    for(;isdigit(c);now=now*10+c-'0',c=gc());
    return now;
}
inline int X(int j,int k){
    return m*(sum[j]-sum[k]);
}
inline long long Y(int j,int k){
    return f[j]-f[k]+2*(sum[j]-sum[k])*sum[n]+(sum[j]*sum[j]-sum[k]*sum[k])*m;
}
inline int Calc_F(int i,int j){
    int ai=sum[i]-sum[j];
    return f[j]+w-2*ai*sum[n]+ai*ai*m;
}

int main()
{
    n=read(),m=read();
    for(int i=1; i<=n; ++i) sum[i]=sum[i-1]+read();
    w=sum[n]*sum[n]/m;
    memset(f2,0x3f,sizeof f2);
    f2[0][0]=1;
    for(int i=1; i<=m; ++i)
    {
//      int h=1,t=1; q[1]=0;
        for(int j=i; j<=n; ++j)
        {
//          while(h<t && Y(q[h+1],q[h])<=2*sum[j]*X(q[h+1],q[h])) ++h;
//          f[j]=Calc_F(j,q[h]);
//          while(h<t && Y(i,q[t])*X(q[t],q[t-1])<=Y(q[t],q[t-1])*X(i,q[t])) --t;
//          q[++t]=j;
            for(int k=0; k<j; ++k)
            {
                int ai=sum[j]-sum[k];
                f2[i][j]=std::min(f2[i][j],f2[i-1][k]+w-2*ai*sum[n]+ai*ai*m);
            }
        }
    }
    printf("%d",f2[m][n]);

    return 0;
}

转载于:https://www.cnblogs.com/SovietPower/p/8694348.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值