BZOJ3675【APIO2014】序列分割 <斜率优化>

Problem

【APIO2014】序列分割


Description

最近迷上了一个分隔序列的游戏。
在这个游戏里, 需要将一个长度为 的非负整数序列分割成 个非空的子序列。
为了得到 个子序列, 需要重复 次以下的步骤:

  1. 首先选择一个长度超过 的序列(一开始 只有一个长度为 的序列,也就是一开始得到的整个序列)
  2. 选择一个位置,并通过这个位置将这个序列分割成连续的两个非空的新序列

每次进行上述步骤之后, 将会得到一定的分数。这个分数为两个新序列中元素和的乘积。
希望选择一种最佳的分割方式,使得 轮之后, 的总得分最大。

Input

输入第一行包含两个整数
第二行包含 个非负整数 ,表示一开始 得到的序列。

Output

输出第一行包含一个整数,为 可以得到的最大分数。

Sample Input

1
2
7 3
4 1 3 4 0 2 3

Sample Output

1
108

Hint

在样例中, 可以通过如下3轮操作得到108分:

  1. 一开始 有一个序列 选择在第 个数之后的位置将序列分成两部分,并得到 分。
  2. 这一轮开始时 有两个序列: 选择在第3个数字之后的位置将第二个序列分成两部分,并得到 分。
  3. 这一轮开始时 有三个序列: 选择在第 个数字之后的位置将第三个序列分成两部分,并得到 分。

经过上述三轮操作, 将会得到四个子序列: 并总共得到 分。

标签:斜率优化DP

Solution

易得 方程: 表示玩 轮时只考虑 区间内的数的最大贡献。那么有
简单推一推:

套上斜率优化:

注意这里不要写成斜率的形式,因为 可能等于
按照不等式用单调栈对每层 进行维护即可,这里可以做 次一维 ,每次重新算 数组。

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <bits/stdc++.h>
#define MAX_N 100000
using namespace std;
typedef long long lnt;
template <class T> inline void read(T &x) {
x = 0; int c = getchar(), f = 1;
for (; !isdigit(c); c = getchar()) if (c == 45) f = -1;
for (; isdigit(c); c = getchar()) (x *= 10) += f*(c-'0');
}
int n, m, l, r, que[MAX_N+5]; lnt s[MAX_N+5];
lnt f[MAX_N+5], a[MAX_N+5], b[MAX_N+5];
bool chk(int p, int l, int r) {
lnt x = (b[p]-b[que[r]])*(a[que[r-1]]-a[que[r]]);
lnt y = (b[que[r]]-b[que[r-1]])*(a[que[r]]-a[p]);
return x <= y;
}
int main() {
read(n), read(m);
for (int i = 1, x; i <= n; i++)
read(x), s[i] = s[i-1]+x;
for (int i = 1; i <= n; i++)
a[i] = s[i], b[i] = -s[i]*s[i];
for (int k = 1; k <= m; k++) {
que[l = r = 0] = 0;
for (int i = 1; i <= n; i++) {
while (l < r && s[i]*(a[que[l]]-a[que[l+1]]) <= b[que[l+1]]-b[que[l]]) l++;
f[i] = a[que[l]]*s[i]+b[que[l]]; while (l < r && chk(i, l, r)) r--; que[++r] = i;
}
for (int i = 1; i <= n; i++) b[i] = f[i]-s[i]*s[i];
}
return printf("%lld\n", f[n]), 0;
}
------------- Thanks For Reading -------------
0%