洛谷·[SDOI2016]征途

初见安~这里是传送门:洛谷P4072 [SDOI2016]征途

 

 

题解

首先设每天走​x_i步,平均数为\overline{x}​,可以把方差的式子写出来:

所以现在的问题就是把原序列​划分成m份,让每一份的平方和尽量小。

f[i][j]​表示到第i份,用了前j个数时的最优解。考虑O(n^3)O(n^3)转移:

s表示前缀和。

这其实就是斜率优化dp的板子了【什。

考虑​k_1>k_2,且从​f[i-1][k_1]转移比​f[i-1][k_2]更优,当且仅当:

展开移向可得:

左边已经很明显了,是关于点​(s[k],f[i-1][k]+s[k]^2)的斜率的式子。

所以斜率优化,j单调递增,考虑维护单调队列下凸壳,满足队头的斜率大于等于​2s[j]即可。因为如果小于,那后面那个节点就优于前一个节点,前面一个要弹出去。

实际操作的时候所有关于斜率的除法都移向写成乘法,避免精度误差。

上代码——

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<queue>
#define maxn 3005
using namespace std;
typedef long long ll;
ll INF = 1e15;
int read() {
    int x = 0, f = 1, ch = getchar();
    while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();}
    while(isdigit(ch)) x = (x << 1) + (x << 3) + ch - '0', ch = getchar();
    return x * f;
}
​
int n, m, a[maxn];
ll sum[maxn], f[maxn][maxn];
int q[maxn], head, tail;
ll y(int i, int j) {return f[i - 1][j] + sum[j] * sum[j];}
signed main() {
    n = read(), m = read();
    for(int i = 1; i <= n; i++) a[i] = read(), sum[i] = sum[i - 1] + 1ll * a[i];
    
    for(int i = 1; i <= n; i++) f[1][i] = sum[i] * sum[i];//预处理
    for(int i = 2; i <= m; i++) {
        head = 1, tail = 0; q[++tail] = 0;//每个j都清空队列
        for(int j = 1; j <= n; j++) {
            while(head < tail && y(i, q[head + 1]) - y(i, q[head]) < sum[j] * 2 * (sum[q[head + 1]] - sum[q[head]])) head++;
            f[i][j] = f[i - 1][q[head]] + (sum[j] - sum[q[head]]) * (sum[j] - sum[q[head]]);//维护队头后更新答案
            while(head < tail && (y(i, q[tail]) - y(i, q[tail - 1])) * (sum[j] - sum[q[tail]]) > (y(i, j) - y(i, q[tail])) * (sum[q[tail]] - sum[q[tail - 1]])) tail--;
            q[++tail] = j;//维护下凸壳,放入节点j
        }
    }
    printf("%lld\n", f[m][n] * m - sum[n] * sum[n]);
    return 0;
}

迎评:)

——End——

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值