初见安~这里是传送门:洛谷P4072 [SDOI2016]征途
题解
首先设每天走步,平均数为
,可以把方差的式子写出来:
所以现在的问题就是把原序列划分成m份,让每一份的平方和尽量小。
设表示到第i份,用了前j个数时的最优解。考虑
转移:
s表示前缀和。
这其实就是斜率优化dp的板子了【什。
考虑,且从
转移比
更优,当且仅当:
展开移向可得:
左边已经很明显了,是关于点的斜率的式子。
所以斜率优化,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——