#斜率优化,单调队列,动态规划#洛谷 4072 JZOJ 4475 征途

题目

把一个长度为 n n n的序列分成 m m m份,使它的方差最小


分析

首先要剖析方差公式,设 s u m [ m ] = ∑ i = 1 m a [ i ] sum[m]=\sum_{i=1}^ma[i] sum[m]=i=1ma[i]
S 2 m 2 = m ∑ i = 1 m ( a [ i ] − a v e ) 2 S^2m^2=m\sum_{i=1}^m(a[i]-ave)^2 S2m2=mi=1m(a[i]ave)2
那么
S 2 m 2 = m ∑ i = 1 m ( a [ i ] ) 2 − 2 s u m [ n ] ∑ i = 1 m a [ i ] + ( s u m [ n ] ) 2 S^2m^2=m\sum_{i=1}^{m}(a[i])^2-2sum[n]\sum_{i=1}^ma[i]+(sum[n])^2 S2m2=mi=1m(a[i])22sum[n]i=1ma[i]+(sum[n])2
实际上 ∑ i = 1 n a [ i ] = s u m [ n ] \sum_{i=1}^{n}a[i]=sum[n] i=1na[i]=sum[n],所以
S 2 m 2 = m ∑ i = 1 m ( a [ i ] ) 2 − ( s u m [ n ] ) 2 S^2m^2=m\sum_{i=1}^{m}(a[i])^2-(sum[n])^2 S2m2=mi=1m(a[i])2(sum[n])2
然后移项得到
S 2 m 2 + ( s u m [ n ] ) 2 = m ∑ i = 1 m ( a [ i ] ) 2 S^2m^2+(sum[n])^2=m\sum_{i=1}^{m}(a[i])^2 S2m2+(sum[n])2=mi=1m(a[i])2
那么终于到正题了,也就是说把 n n n个数分成 m m m份,使平方和最小
所以说就是斜率优化,设 f [ n ] , g [ n ] f[n],g[n] f[n],g[n]分别表示该份和上一份的状态,那么
f [ i ] = g [ q [ h e a d ] ] + ( s u m [ i ] − s u m [ q [ h e a d ] ] ) 2 f[i]=g[q[head]]+(sum[i]-sum[q[head]])^2 f[i]=g[q[head]]+(sum[i]sum[q[head]])2
揭开之后发现它维护的是上凸壳,然后用单调队列维护即可


代码

#include <cstdio>
#include <cctype>
#include <cstring>
#define rr register
#define p(x) ((x)*(x))
#define t(x) (g[x]+p(sum[x]))
using namespace std;
int n,m,q[3001]; long long f[3001],g[3001],sum[3001];
inline signed iut(){
    rr int ans=0; rr char c=getchar();
    while (!isdigit(c)) c=getchar();
    while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
    return ans;
}
inline double slope(int i,int j){return (t(j)-t(i))*1.0/(sum[j]-sum[i]);}
signed main(){
    n=iut(); m=iut();
    for (rr int i=1;i<=n;++i) sum[i]=sum[i-1]+iut(),g[i]=p(sum[i]);
    for (rr int j=1;j<m;++j){
        rr int head=1,tail=1; q[1]=j;
        for (rr int i=j+1;i<=n;++i){
            while (head<tail&&slope(q[head],q[head+1])<2*sum[i]) ++head;
            f[i]=g[q[head]]+p(sum[i]-sum[q[head]]);
            while (head<tail&&slope(q[tail-1],q[tail])>slope(q[tail],i)) --tail;
            q[++tail]=i;
        }
        memcpy(g,f,sizeof(f));
    }
    printf("%lld",f[n]*m-p(sum[n]));
    return 0;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值