problem
你正在玩一个关于长度为 n n n 的非负整数序列的游戏。这个游戏中你需要把序列分成 k + 1 k+1 k+1 个非空的块。
为了得到 k + 1 k+1 k+1 块,你需要重复下面的操作 k k k 次:
- 选择一个有超过一个元素的块(初始时你只有一块,即整个序列)
- 选择两个相邻元素把这个块从中间分开,得到两个非空的块。
- 每次操作后你将获得那两个新产生的块的元素和的乘积的分数。
你想要最大化最后的总得分。
输出最后得分以及划分的任一方案。
solution
前提结论:得分与切的先后顺序无关。
假设要把 a b c abc abc 切成 a ∣ b ∣ c a\mid b\mid c a∣b∣c。
- 先切 a ∣ b c a\mid bc a∣bc,再切 a ∣ b ∣ c a\mid b\mid c a∣b∣c, a n s = a ∗ ( b + c ) + b ∗ c = a b + a c + b c ans=a*(b+c)+b*c=ab+ac+bc ans=a∗(b+c)+b∗c=ab+ac+bc。
- 先切 a b ∣ c ab\mid c ab∣c,再切 a ∣ b ∣ c a\mid b\mid c a∣b∣c, a n s = ( a + b ) ∗ c + a ∗ b = a c + b c + a b ans=(a+b)*c+a*b=ac+bc+ab ans=(a+b)∗c+a∗b=ac+bc+ab。
这样就可以从左到右 d p dp dp 了。记 s u m i = ∑ j = 1 i a i sum_i=\sum_{j=1}^ia_i sumi=∑j=1iai,即元素分数前缀和。
设 f ( i , j ) : f(i,j): f(i,j): 前 i i i 个数切成 j j j 段的最大得分。
则 f ( i , j ) = max k < i { f ( k , j − 1 ) + s u m ( k ) ∗ ( s u m ( i ) − s u m ( k ) ) } f(i,j)=\max_{k<i}\Big\{f(k,j-1)+sum(k)*\big(sum(i)-sum(k)\big)\Big\} f(i,j)=maxk<i{f(k,j−1)+sum(k)∗(sum(i)−sum(k))}。
j j j 只跟 j − 1 j-1 j−1 有关,考虑提到最外面循环,内层简写 f ( i , j − 1 ) = f ( i ) f(i,j-1)=f(i) f(i,j−1)=f(i)。
假设 k 1 < k 2 k_1<k_2 k1<k2,且 k 1 k_1 k1 决策优于 k 2 k_2 k2。
则必定满足:
f
(
k
1
)
+
s
u
m
k
1
∗
s
u
m
i
−
s
u
m
k
1
2
>
f
(
k
2
)
+
s
u
m
k
2
∗
s
u
m
i
−
s
u
m
k
2
2
f(k_1)+sum_{k_1}*sum_i-sum_{k_1}^2>f(k_2)+sum_{k_2}*sum_i-sum_{k_2}^2
f(k1)+sumk1∗sumi−sumk12>f(k2)+sumk2∗sumi−sumk22
f ( k 1 ) − s u m k 1 2 − f ( k 2 ) + s u m k 2 2 > ( s u m k 2 − s u m k 1 ) s u m i f(k_1)-sum_{k_1}^2-f(k_2)+sum_{k_2}^2>(sum_{k_2}-sum_{k_1})sum_i f(k1)−sumk12−f(k2)+sumk22>(sumk2−sumk1)sumi
s
u
m
sum
sum 是前缀和数组,所以
s
u
m
k
2
≥
s
u
m
k
1
sum_{k_2}\ge sum_{k_1}
sumk2≥sumk1,具有单调性。
(
f
(
k
1
)
−
s
u
m
k
1
2
)
−
(
f
(
k
2
)
−
s
u
m
k
2
2
)
(
−
s
u
m
k
1
)
−
(
−
s
u
m
k
2
)
>
s
u
m
(
i
)
\frac{\big(f(k_1)-sum_{k_1}^2\big)-\big(f(k_2)-sum_{k_2}^2\big)}{(-sum_{k_1})-(-sum_{k_2})}>sum(i)
(−sumk1)−(−sumk2)(f(k1)−sumk12)−(f(k2)−sumk22)>sum(i)
标准斜率优化的式子,因为
O
(
n
k
)
O(nk)
O(nk) 无法再接受一个
log
\log
log ,所以我最热爱的李超树就被
pass
\text{pass}
pass 掉了。
斜率优化想必最头疼的就是用队列维护首尾弹出时的大小于符号的定向问题。
网上一堆凸包图形,对于我这种平面几何感几乎为零的蒟蒻而言简直就是天方夜谭。
所以这里写一种判断方法吧,不保证正确性
-
队首的弹出很简单,因为上面的斜率优化式子已经化出来了,直接 s l o p e ( q [ h e a d ] , q [ h e a d + 1 ] ) slope(q[head],q[head+1]) slope(q[head],q[head+1]) 和 s u m ( i ) sum(i) sum(i) 比较。
如果 ≤ s u m ( i ) \le sum(i) ≤sum(i) 说明 k 2 k_2 k2 即后者 q [ h e a d + 1 ] q[head+1] q[head+1] 更优。
就需要弹队首。
-
队尾的弹出较为复杂,考虑的是 s l o p e ( q [ t a i l − 1 ] , q [ t a i l ] ) slope(q[tail-1],q[tail]) slope(q[tail−1],q[tail]) 和 s l o p e ( q [ t a i l ] , i ) slope(q[tail],i) slope(q[tail],i)。
准确而言是考虑 q [ t a i l ] q[tail] q[tail] 可不可能做队首。
那么当现在的队尾在后面某个枚举位置时变成了队首,
就必须要满足 s l o p e ( q [ t a i l − 1 ] , q [ t a i l ] ) ≤ s u m p slope(q[tail-1],q[tail])\le sum_p slope(q[tail−1],q[tail])≤sump,这样才能弹出 q [ t a i l − 1 ] q[tail-1] q[tail−1]。
-
假设弹队尾的条件是 s l o p e ( q [ t a i l − 1 ] , q [ t a i l ] ) ≤ s l o p e ( q [ t a i l ] , i ) slope(q[tail-1],q[tail])\le slope(q[tail],i) slope(q[tail−1],q[tail])≤slope(q[tail],i)。
显然存在 s l o p e ( q [ t a i l − 1 ] , q [ t a i l ] ) ≤ s u m p < s l o p e ( q [ t a i l ] , i ) slope(q[tail-1],q[tail])\le sum_p<slope(q[tail],i) slope(q[tail−1],q[tail])≤sump<slope(q[tail],i) 的可能性。
所以这么弹可能把答案取值点给弹出去。
-
假设弹队尾的条件是 s l o p e ( q [ t a i l − 1 ] , q [ t a i l ] ) ≥ s l o p e ( q [ t a i l ] , i ) slope(q[tail-1],q[tail])\ge slope(q[tail],i) slope(q[tail−1],q[tail])≥slope(q[tail],i)。
因为 s l o p e ( q [ t a i l ] , i ) ≤ s l o p e ( q [ t a i l − 1 ] , q [ t a i l ] ) ≤ s u m p slope(q[tail],i)\le slope(q[tail-1],q[tail])\le sum_p slope(q[tail],i)≤slope(q[tail−1],q[tail])≤sump,
所以 q [ t a i l − 1 ] q[tail-1] q[tail−1] 被 q [ t a i l ] q[tail] q[tail] 弹出后,马上 q [ t a i l ] q[tail] q[tail] 就会被 i i i 弹出去。
那么 q [ t a i l ] q[tail] q[tail] 永远都不可能当队首。
综上,我们确定了队尾判断的符号。
-
注意:前面的表述据说明 s u m i sum_i sumi 不是严格递增的,存在平台,所以遇到 s u m i = s u m j sum_i=sum_j sumi=sumj 返回斜率极小值即可。
code
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define maxn 100005
int n, k, d;
int a[maxn], sum[maxn], q[maxn];
int g[maxn][205], f[maxn][205];
double slope( int x, int y ) {
if( sum[x] == sum[y] ) return -1e18;
return (f[x][d] - sum[x] * sum[x] - f[y][d] + sum[y] * sum[y]) * 1.0 / (sum[y] - sum[x]);
}
void print( int n, int d ) {
if( d == 1 ) return;
print( g[n][d], d - 1 );
printf( "%d ", g[n][d] );
}
signed main() {
scanf( "%lld %lld", &n, &k );
for( int i = 1;i <= n;i ++ ) scanf( "%lld", &a[i] );
for( int i = 1;i <= n;i ++ ) sum[i] = sum[i - 1] + a[i];
for( d = 1;d <= k;d ++ ) {
int head = 1, tail = 0;
for( int i = 1;i <= n;i ++ ) {
while( head < tail and slope( q[head], q[head + 1] ) <= sum[i] ) head ++;
f[i][d + 1] = f[q[head]][d] + sum[q[head]] * ( sum[i] - sum[q[head]] );
g[i][d + 1] = q[head];
while( head < tail and slope( q[tail - 1], q[tail] ) >= slope( q[tail], i ) ) tail --;
q[++ tail] = i;
}
}
printf( "%lld\n", f[n][d] );
print( n, d );
return 0;
}