[APIO2014]序列分割
题意
思考
这题竟然卡常 \(+\) 卡精度,我 \(……\)
令 \(f[i][k]\) 表示前 \(i\) 个元素分了 \(k\) 次的最大值,我们可以得出转移方程(\(sum[]\)为前缀和数组):
\[f[i][k] = max\{f[j][k-1] + sum[j] * (sum[i] - sum[j])\}
\]
这一个转移朴素来算是 \(O(n^2k)\) 的复杂度,我们不妨把式子拆开:
\[f[i][k] = f[j][k-1] + sum[j] * (sum[i] - sum[j])\\
= f[j][k-1] + sum[i]*sum[j] - sum[j] ^ 2\\
f[i][k] - sum[i] * sum[j] = f[j][k-1] - sum[j]^2\]
将 \(f[i][k]\) 看作 \(y\) 上截距, \(sum[j]\) 看作自变量, \(f[j][k-1] - sum[j]^2\) 看作因变量,\(-sum[i]\) 则为斜率,由于\(-sum[i]\) 单调递减,我们可以考虑斜率优化,维护一个上凸包,复杂度为 \(O(nk)\),空间的话,\(f[][]\) 数组可以滚动,但是我太懒了,所以没滚\(……\)
由于卡常,我开了 \(O2\) (我太菜了)
有一点要注意的就是计算斜率的时候分母有可能为 \(0\),我们特判一下
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef long double D;
const int N = 100010;
const int K = 220;
ll f[N][K], a[N], sum[N], q[N], road[N][K], n, kmax;
D X(int j){ return (D)sum[j]; }
D Y(int j, int k){ return (D)f[j][k-1] - (D)sum[j] * (D)sum[j]; }
D slope(int i, int j, int k){
if(sum[i] == sum[j]) return -0x3f3f3f3f;
return ( Y(i, k) - Y(j, k) ) / ( X(i) - X(j) );
}
void print(int i, int k){
if(k == 0) return;
print(road[i][k], k-1);
printf("%lld ", road[i][k]);
}
int main(){
scanf("%lld%lld", &n, &kmax);
for(int i=1; i<=n; i++) scanf("%lld", &a[i]), sum[i] = sum[i-1] + a[i];
for(int k=1; k<=kmax; k++){
int h = 1, t = 1; q[1] = 0;
for(int i=1; i<=n; i++){
while(h < t && slope(q[h], q[h + 1], k) >= -sum[i] ) h ++;
f[i][k] = f[q[h]][k-1] + sum[q[h]] * (sum[i] - sum[q[h]]);
road[i][k] = q[h];
while(h < t && slope(q[t - 1], q[t], k) <= slope(q[t], i, k) ) t --;
q[++t] = i;
}
}
printf("%lld\n", f[n][kmax]);
print(n, kmax);
return 0;
}
总结
注意精度??
注意分母为 \(0\) 的特判
注意常数优化