题目链接
题意:把一个长度为n的序列分割成k+1份。每次选中一个长度大于一的块,把它分成两份称为一次分割。每次分割的权值等于这一刀 左边新块权值和 与 右边新块权值和 的乘积。
求最大权值。
2
≤
n
≤
100000
,
1
≤
k
≤
min
{
n
−
1
,
200
}
2 \leq n \leq 100000, 1 \leq k \leq \min\{n - 1, 200\}
2≤n≤100000,1≤k≤min{n−1,200}
考虑前三个子任务 裸的区间dp啊
f
[
l
e
f
t
]
[
r
i
g
h
t
]
[
k
]
=
max
j
=
l
e
f
t
r
i
g
h
t
−
1
max
k
k
=
1
k
−
1
f
[
l
e
f
t
]
[
j
]
[
k
k
]
+
f
[
j
+
1
]
[
r
i
g
h
t
]
[
k
−
k
k
]
+
s
u
m
(
l
e
f
t
,
j
)
∗
s
u
m
(
j
+
1
,
r
i
g
h
t
)
f[left][right][k] = \max_{j = left}^{right - 1} \max_{kk = 1}^{k - 1} f[left][j][kk] + f[j + 1][right][k - kk] + sum(left, j) * sum(j + 1, right)
f[left][right][k]=maxj=leftright−1maxkk=1k−1f[left][j][kk]+f[j+1][right][k−kk]+sum(left,j)∗sum(j+1,right)
考虑一个序列 已经被分成了k+1份
那么分割的顺序其实是不影响结果的
因为结果
∑
i
=
1
k
+
1
∑
j
=
1
k
+
1
s
u
m
(
块
i
)
∗
s
u
m
(
块
j
)
\sum_{i = 1}^{k + 1} \sum_{j = 1}^{k + 1} sum(块i) * sum(块j)
∑i=1k+1∑j=1k+1sum(块i)∗sum(块j)
那么上面那个dp 维护的分割顺序就可以去掉了
则有
f
[
i
]
[
k
]
=
max
j
=
1
i
−
1
f
[
j
]
[
k
−
1
]
+
s
u
m
(
1
,
j
)
∗
s
u
m
(
j
+
1
,
i
)
f[i][k] = \max_{j = 1}^{i - 1} f[j][k - 1] + sum(1, j) * sum(j + 1, i)
f[i][k]=maxj=1i−1f[j][k−1]+sum(1,j)∗sum(j+1,i)
至于位置的话 就记录一下pre[i][k] = j就okk了
然鹅这个式子的时间复杂度还是
O
(
n
2
k
)
O(n^2k)
O(n2k)
还要做一下斜率优化qvq
补一波斜率优化 传送门
看一下上面那个式子
f
[
i
]
[
k
]
=
max
j
=
1
i
−
1
f
[
j
]
[
k
−
1
]
+
s
u
m
(
1
,
j
)
∗
s
u
m
(
j
+
1
,
i
)
f[i][k] = \max_{j = 1}^{i - 1} f[j][k - 1] + sum(1, j) * sum(j + 1, i)
f[i][k]=j=1maxi−1f[j][k−1]+sum(1,j)∗sum(j+1,i)
=
max
j
=
1
i
−
1
f
[
j
]
[
k
−
1
]
+
s
u
m
[
j
]
∗
(
s
u
m
[
i
]
−
s
u
m
[
j
]
)
= \max_{j = 1}^{i - 1} f[j][k - 1] + sum[j] * (sum[i] - sum[j])
=j=1maxi−1f[j][k−1]+sum[j]∗(sum[i]−sum[j])
随j改变的量是
s
u
m
[
j
]
∗
s
u
m
[
i
]
,
s
u
m
[
j
]
2
,
f
[
j
]
[
k
−
1
]
sum[j] * sum[i], sum[j]^2, f[j][k - 1]
sum[j]∗sum[i],sum[j]2,f[j][k−1]
所以可移项得
−
s
u
m
[
i
]
∗
s
u
m
[
j
]
+
f
[
i
]
=
f
[
j
]
−
s
u
m
[
j
]
2
-sum[i]*sum[j] + f[i] = f[j] - sum[j]^2
−sum[i]∗sum[j]+f[i]=f[j]−sum[j]2
为了方便维护 把变量系数换成正的
s
u
m
[
i
]
∗
s
u
m
[
j
]
−
f
[
i
]
=
−
f
[
j
]
+
s
u
m
[
j
]
2
sum[i]*sum[j] - f[i] = -f[j] + sum[j]^2
sum[i]∗sum[j]−f[i]=−f[j]+sum[j]2
即过
P
(
s
u
m
[
j
]
,
−
f
[
j
]
+
s
u
m
[
j
]
2
)
P(sum[j], -f[j] + sum[j]^2)
P(sum[j],−f[j]+sum[j]2)的最小截距
然后就okk啦
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int N = 1e5 + 5;
const int Kmax = 205;
int n, pos, K;
long long f[N][2], sum[N];
int pre[N][Kmax];
int que[N], head, tail;
//P(sum[j], -f[j] + sum[j]^2)
//f[i][k] = = f[j][k - 1] + sum[j] * (sum[i] - sum[j])
inline double slope(int x, int y){
if(sum[x] == sum[y]) return -1e9;
return (sum[y] * sum[y] - sum[x] * sum[x]
- f[y][pos ^ 1] + f[x][pos ^ 1])
/ (1.0 * (sum[y] - sum[x]));
//死在这里了 如果直接调用X,Y会爆炸
}
int main() {
scanf("%d%d", &n, &K);
for(int i = 1; i <= n; ++i){
scanf("%lld", &sum[i]);
sum[i] += sum[i - 1];
}
for(int k = 1; k <= K; ++k){
pos = k & 1;
head = tail = 1;
for(int i = 1; i <= n; ++i){
while(head < tail && slope(que[head], que[head + 1]) <= sum[i]) ++head;
f[i][pos] = f[que[head]][pos ^ 1] + sum[que[head]] * (sum[i] - sum[que[head]]);
pre[i][k] = que[head];
while(head < tail && slope(que[tail - 1], que[tail]) >= slope(que[tail - 1], i)) --tail;
que[++tail] = i;
}
}
printf("%lld\n", f[n][pos]);
for(int i = pre[n][K], j = K; i; i = pre[i][--j]) printf("%d ", i);
return 0;
}