Problem
你正在玩一个关于长度为 n n n 的非负整数序列的游戏。这个游戏中你需要把序列分成 ( k + 1 ) (k + 1) (k+1) 个非空的块。为了得到 ( k + 1 ) (k + 1) (k+1) 块,你需要重复下面的操作 k k k 次:
- 选择一个有超过一个元素的块(初始时你只有一块,即整个序列)。
- 选择两个相邻元素把这个块从中间分开,得到两个非空的块。
每次操作后你将获得那两个新产生的块的元素和的乘积的分数。你想要最大化最后的总得分。
数据范围: 0 ≤ a i ≤ 1 0 4 0≤a_i≤10^4 0≤ai≤104, 2 ≤ n ≤ 100000 2≤n≤100000 2≤n≤100000, 1 ≤ k ≤ m i n { n − 1 , 200 } 1≤k≤min\{n−1,200\} 1≤k≤min{n−1,200}。
Solution
这篇博客写得很好,接下来有部分内容取自这里。
首先有一个结论,即当分割的位置确定时,分割的先后顺序对答案无影响。
下面是简单的说明:
如果我们有长度为 3 3 3 的序列 x , y , z x,y,z x,y,z 将其分为 3 3 3 部分,有如下两种分割方法:
- 先在 x x x 后面分割,答案为 x ( y + z ) + y z x(y+z)+yz x(y+z)+yz,即 x y + y z + z x xy+yz+zx xy+yz+zx。
- 先在 y y y 后面分割,答案为 ( x + y ) z + x y (x+y)z+xy (x+y)z+xy,即 x y + y z + z x xy+yz+zx xy+yz+zx。
这个结论可以扩展到任意长度的序列(分析一下贡献),证毕。
定义 F i , j F_{i,j} Fi,j 表示前 i i i 个数进行 j j j 次切割的最大得分。记 S i S_i Si 表示 a i a_i ai 的前缀和,那么转移方程为:
F i , k = max j = 1 i − 1 { F j , k − 1 + S j ( S i − S j ) } F_{i,k}=\max_{j=1}^{i-1}\{F_{j,k−1}+S_j(S_i−S_j)\} Fi,k=j=1maxi−1{Fj,k−1+Sj(Si−Sj)}
发现 k k k 只会从 k − 1 k-1 k−1 转移过来,因此可以用滚动数组来优化掉 k k k 这一维。
现在记 F i , k F_{i,k} Fi,k 为 f i f_i fi, F j , k − 1 F_{j,k−1} Fj,k−1 为 g j g_j gj,那么方程为:
f i = max j = 1 i − 1 g j + S j ( S i − S j ) f_i=\max_{j=1}^{i-1}{g_j+S_j(S_i−S_j)} fi=j=1maxi−1gj+Sj(Si−Sj)
感觉是一个可以斜率优化的式子!
按照套路,我们取 j , k j,k j,k 满足 1 ≤ k < j < i 1≤k<j<i 1≤k<j<i 且 j j j 比 k k k 更优,那么有如下不等式:
g j + S j ( S i − S j ) > g k + S k ( S i − S k ) g_j+S_j(S_i−S_j)>g_k+S_k(S_i−S_k) gj+Sj(Si−Sj)>gk+Sk(Si−Sk)
化个简,得到:
( g j − S j 2 ) − ( g k − S k 2 ) S k − S j < S i \frac{(g_j-S_j^2)-(g_k-S_k^2)}{S_k-S_j}<S_i Sk−Sj(gj−Sj2)−(gk−Sk2)<Si
维护一个下凸壳然后斜率优化即可。
注:本题中 a i a_i ai 是非负整数,所以 S k − S j S_k−S_j Sk−Sj 可能等于 0 0 0。这种情况需要特判, s l o p e slope slope 需要返回 − i n f −inf −inf。
然后输出方案的话直接记一个 p r e pre pre 表示从哪转移过来即可。
时间复杂度: O ( n k ) O(nk) O(nk)
Code
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 100005
#define ll long long
using namespace std;
ll f[N],g[N];
int S[N],Q[N],pre[N][205];
ll ordi(int x) {return g[x]-1ll*S[x]*S[x];}
double slope(int x,int y){
return (S[x]==S[y])?-1e20:1.0*(ordi(x)-ordi(y))/(S[y]-S[x]);
}
int main(){
int n,k,i,j,x;
scanf("%d%d",&n,&k);
for(i=1;i<=n;++i)
scanf("%d",&x),S[i]=S[i-1]+x;
for(j=1;j<=k;++j){
int l=0,r=0;
for(i=1;i<=n;++i){
while(l<r&&slope(Q[l],Q[l+1])<=S[i]) l++;
f[i]=g[Q[l]]+1ll*S[Q[l]]*(S[i]-S[Q[l]]),pre[i][j]=Q[l];
while(l<r&&slope(Q[r-1],Q[r])>=slope(Q[r],i)) r--;
Q[++r]=i;
}
memcpy(g,f,sizeof(g));
}
printf("%lld\n",f[n]);
for(x=n,i=k;i>=1;--i) x=pre[x][i],printf("%d ",x);
return 0;
}