题目大意
给定一个长度为
n
n
n 的序列,你需要将其切割成大小任意的
k
k
k 段,设切割的位置为
(
i
,
i
+
1
)
(i,i+1)
(i,i+1),则该次切割的价值为
i
i
i 位置所在段的大小乘上
i
+
1
i+1
i+1 位置所在段的大小,求最大的价值,并输出任意一种切割方法。(Special Judge)
数据范围
2
⩽
n
⩽
1
0
5
,
1
⩽
k
⩽
m
i
n
(
n
−
1
,
200
)
2\leqslant n\leqslant 10^5,1\leqslant k\leqslant min(n−1,200)
2⩽n⩽105,1⩽k⩽min(n−1,200)
题解
手玩几次就能发现,答案和分割顺序无关,下面是证明。
如果我们有长度为 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]
f[i][j] 表示前
i
i
i 个数切割成
j
j
j 段能得到的最大价值,
s
u
m
[
n
]
=
∑
i
=
1
n
a
[
i
]
sum[n]=\sum\limits_{i=1}^na[i]
sum[n]=i=1∑na[i]
转移:
f
[
i
]
[
j
]
=
max
k
=
1
i
−
1
f
[
k
]
[
j
−
1
]
+
s
u
m
[
k
]
∗
(
s
u
m
[
i
]
−
s
u
m
[
k
]
)
f[i][j]=\max\limits_{k=1}^{i-1} f[k][j-1]+sum[k]*(sum[i]-sum[k])
f[i][j]=k=1maxi−1f[k][j−1]+sum[k]∗(sum[i]−sum[k])
int cur=0;
for(int j=1;j<=k;++j,cur^=1)
for(int i=1;i<=n;i++)
for(int k=1;k<i;k++)
f[cur][i]=max(f[cur][i],f[cur^1][k]+sum[k]*(sum[i]-sum[k]));
这样时间复杂度为 Θ ( n 2 k ) \Theta(n^2k) Θ(n2k),但可以发现时间炸了。
下面大胆地假设我们没有学过斜率优化
考虑优化,在转移的时候,考虑两个点 k a k_a ka 和 k b k_b kb 满足 k a < k b k_a<k_b ka<kb,若 k a k_a ka 优于 k b k_b kb,则有 f [ k a ] [ j − 1 ] + s u m [ k a ] ∗ ( s u m [ i ] − s u m [ k a ] ) > f [ k b ] [ j − 1 ] + s u m [ k b ] ∗ ( s u m [ i ] − s u m [ k b ] ) f[k_a][j-1]+sum[k_a]*(sum[i]-sum[k_a])>f[k_b][j-1]+sum[k_b]*(sum[i]-sum[k_b]) f[ka][j−1]+sum[ka]∗(sum[i]−sum[ka])>f[kb][j−1]+sum[kb]∗(sum[i]−sum[kb])
考虑化简,得 f [ k a ] [ j − 1 ] + s u m [ k a ] ∗ s u m [ i ] − s u m [ k a ] 2 > f [ k b ] [ j − 1 ] + s u m [ k b ] ∗ s u m [ i ] − s u m [ k b ] 2 f[k_a][j-1]+sum[k_a]*sum[i]-sum[k_a]^2>f[k_b][j-1]+sum[k_b]*sum[i]-sum[k_b]^2 f[ka][j−1]+sum[ka]∗sum[i]−sum[ka]2>f[kb][j−1]+sum[kb]∗sum[i]−sum[kb]2
移项,合并同类项
f
[
k
a
]
[
j
−
1
]
−
f
[
k
b
]
[
j
−
1
]
+
s
u
m
[
k
b
]
2
−
s
u
m
[
k
a
]
2
>
(
s
u
m
[
k
b
]
−
s
u
m
[
k
a
]
)
∗
s
u
m
[
i
]
f[k_a][j-1]-f[k_b][j-1]+sum[k_b]^2-sum[k_a]^2>(sum[k_b]-sum[k_a])*sum[i]
f[ka][j−1]−f[kb][j−1]+sum[kb]2−sum[ka]2>(sum[kb]−sum[ka])∗sum[i]
两边同时除以
s
u
m
[
i
]
>
0
sum[i]>0
sum[i]>0,得
f
[
k
a
]
[
j
−
1
]
−
f
[
k
b
]
[
j
−
1
]
+
s
u
m
[
k
b
]
2
−
s
u
m
[
k
a
]
2
s
u
m
[
k
b
]
−
s
u
m
[
k
a
]
>
s
u
m
[
i
]
\frac {f[k_a][j-1]-f[k_b][j-1]+sum[k_b]^2-sum[k_a]^2}{sum[k_b]-sum[k_a]}>sum[i]
sum[kb]−sum[ka]f[ka][j−1]−f[kb][j−1]+sum[kb]2−sum[ka]2>sum[i]
再整理一下 ( f [ k a ] [ j − 1 ] − s u m [ k a ] 2 ) − ( f [ k b ] [ j − 1 ] − s u m [ k b ] 2 ) s u m [ k b ] − s u m [ k a ] > s u m [ i ] \frac {(f[k_a][j-1]-sum[k_a]^2)-(f[k_b][j-1]-sum[k_b]^2)}{sum[k_b]-sum[k_a]}>sum[i] sum[kb]−sum[ka](f[ka][j−1]−sum[ka]2)−(f[kb][j−1]−sum[kb]2)>sum[i]
因此,对于 k a k_a ka 来说, k a k_a ka 优于 k b k_b kb 当且仅当上面的式子成立。
接下来是跳跃的一步骤,对于每个
k
t
k_t
kt,在平面直角坐标系中绘制点
K
t
(
s
u
m
[
k
t
]
,
f
[
k
t
]
[
j
−
1
]
−
s
u
m
[
k
t
]
2
)
K_t(sum[k_t],f[k_t][j-1]-sum[k_t]^2)
Kt(sum[kt],f[kt][j−1]−sum[kt]2),则
s
p
o
l
e
a
,
b
=
(
f
[
k
a
]
[
j
−
1
]
−
s
u
m
[
k
a
]
2
)
−
(
f
[
k
b
]
[
j
−
1
]
−
s
u
m
[
k
b
]
2
)
s
u
m
[
k
b
]
−
s
u
m
[
k
a
]
spole_{a,b}=\dfrac {(f[k_a][j-1]-sum[k_a]^2)-(f[k_b][j-1]-sum[k_b]^2)}{sum[k_b]-sum[k_a]}
spolea,b=sum[kb]−sum[ka](f[ka][j−1]−sum[ka]2)−(f[kb][j−1]−sum[kb]2)恰好为直线
K
a
K
b
K_aK_b
KaKb 的斜率,如下图,直线
K
1
K
2
K_1K_2
K1K2 的斜率恰好就是
s
p
o
l
e
1
,
2
spole_{1,2}
spole1,2。
至此,我们得到了一条结论,当且仅当
k
a
<
k
b
k_a<k_b
ka<kb 且
s
p
o
l
e
a
,
b
>
s
u
m
[
i
]
spole_{a,b}>sum[i]
spolea,b>sum[i] 时,
k
a
k_a
ka 优于
k
b
k_b
kb。
接下来看看上面的图,考虑点
K
3
K_3
K3,可以发现
K
3
K_3
K3 有一个糟糕的特点:
s
p
o
l
e
2
,
3
>
s
p
o
l
e
3
,
4
spole_{2,3}>spole_{3,4}
spole2,3>spole3,4。
这个特点意味着什么?假设
s
p
o
l
e
3
,
4
>
s
u
m
[
i
]
spole_{3,4}>sum[i]
spole3,4>sum[i],也就是说如果
k
3
k_3
k3 优于
k
4
k_4
k4,那么
s
p
o
l
e
2
,
3
>
s
p
o
l
e
3
,
4
>
s
u
m
[
i
]
spole_{2,3}>spole_{3,4}>sum[i]
spole2,3>spole3,4>sum[i],即
k
2
k_2
k2 优于
k
3
k_3
k3。
同理,如果
s
p
o
l
e
2
,
3
<
s
u
m
[
i
]
spole_{2,3}<sum[i]
spole2,3<sum[i],也就是说如果
k
2
k_2
k2 劣于
k
3
k_3
k3,则
s
p
o
l
e
3
,
4
<
s
p
o
l
e
2
,
3
<
s
u
m
[
i
]
spole_{3,4}<spole_{2,3}<sum[i]
spole3,4<spole2,3<sum[i],因而
k
4
k_4
k4 优于
k
3
k_3
k3。
因此,无论如何,
k
3
k_3
k3 都不可能是本次转移的最优点。因此我们可以直接删除点
k
3
k_3
k3。
现在斜率是单调上升的,而且从某个值开始大于
s
u
m
[
i
]
sum[i]
sum[i],考虑上面的结论,当
s
p
o
l
e
a
,
b
>
s
u
m
[
i
]
spole_{a,b}>sum[i]
spolea,b>sum[i] 时,靠右的点所表示的状态更优。
换句话说,我们最终转移的位置必然是上面所有
K
K
K 中,第一个满足
s
p
o
l
e
i
,
i
的
下
一
个
点
>
s
u
m
[
i
]
spole_{i,i的下一个点}>sum[i]
spolei,i的下一个点>sum[i] 的点,因此这里可以二分。
还有一个问题,如何加入新的点
K
6
K_6
K6?
对于上图,可以发现,加入点
K
6
K_6
K6 后,
K
5
K_5
K5 变成了上升点,不可能成为最优解,因此可以直接删除,然后继续判断
K
4
K_4
K4 是否为上升点(是有可能的),以此类推。
至此,转移的复杂度降到了
Θ
(
log
n
)
\Theta(\log n)
Θ(logn),总时间复杂度为
Θ
(
n
k
log
n
)
\Theta(nk\log n)
Θ(nklogn)。
这样就够了吗?当然不。
可以发现,我们要求
K
a
K_a
Ka 大于的东西
s
u
m
[
i
]
sum[i]
sum[i],随着
i
i
i 的增加是在递增的,也就是说,如果这个
K
a
<
s
u
m
[
i
]
K_a<sum[i]
Ka<sum[i],那
K
a
K_a
Ka 不可能再大于任何
s
u
m
[
j
]
(
j
>
i
)
sum[j](j>i)
sum[j](j>i),即
∀
j
>
i
,
K
a
<
s
u
m
[
j
]
\forall j>i,K_a<sum[j]
∀j>i,Ka<sum[j],因此,我们大可直接删除这个
K
a
K_a
Ka。
这样,每个点最多进入这个序列
1
1
1 次,从这个序列出来
1
1
1 次,其实就是个(单调)队列,故总时间复杂度为
Θ
(
n
k
)
\Theta(nk)
Θ(nk)。
代码
#include <cstdio>
#include <cstring>
const int N=1e5+5,M=205;
int q[N],pre[N][M],n,k,cur;
long long sum[N],f[2][N];
double slope(int i,int j) {
if(sum[i]==sum[j]) return -1;
//由于原序列中有0的存在,因此需要特殊判断斜率为0的直线,否则会出现除以0错误
return 1.0*((f[cur^1][i]-sum[i]*sum[i])-(f[cur^1][j]-sum[j]*sum[j]))/(sum[j]-sum[i]);
}
int main() {
scanf("%d%d",&n,&k);
for(int i=1,x; i<=n; ++i)
scanf("%d",&x),sum[i]=sum[i-1]+x;
for(int j=1,l,r; j<=k; ++j,cur^=1) {
q[l=r=1]=0;
for(int i=1; i<=n; ++i) {
//去除不大于sum[i]的点
while(l<r&&slope(q[l],q[l+1])<=sum[i]) ++l;
int &t=q[l];
f[cur][i]=f[cur^1][t]+sum[t]*(sum[i]-sum[t]);
pre[i][j]=t;
//去除加入i可能导致的上升点
while(l<r&&slope(q[r-1],q[r])>=slope(q[r],i)) --r;
q[++r]=i;
}
}
printf("%lld\n",f[cur^1][n]);
for(int x=n,i=k; i>=1; --i)//输出路径
x=pre[x][i],printf("%d%c",x," \n"[i==1]);
return 0;
}