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

Problem

【APIO2014】序列分割

Time  Limit:  40  Sec\mathrm{Time\;Limit:\;40\;Sec}
Memory  Limit:  128  MB\mathrm{Memory\;Limit:\;128\;MB}

Description

H\mathrm{小H}最近迷上了一个分隔序列的游戏。
在这个游戏里,H\mathrm{小H}需要将一个长度为nn的非负整数序列分割成k+1k+1个非空的子序列。
为了得到k+1k+1个子序列,H\mathrm{小H}需要重复kk次以下的步骤:

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

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

Input

输入第一行包含两个整数n,k  (k+1n)n,k\;(k+1\le n)
第二行包含nn个非负整数a1,a2,,an  (0ai104)a_1,a_2,\cdots,a_n\;(0\le a_i\le10^4),表示一开始H\mathrm{小H}得到的序列。

Output

输出第一行包含一个整数,为H\mathrm{小H}可以得到的最大分数。

Sample Input

1
2
7 3
4 1 3 4 0 2 3

Sample Output

1
108

Hint

在样例中,H\mathrm{小H}可以通过如下3轮操作得到108分:

  1. 一开始H\mathrm{小H}有一个序列(4,1,3,4,0,2,3)(4,1,3,4,0,2,3)H\mathrm{小H}选择在第11个数之后的位置将序列分成两部分,并得到4×(1+3+4+0+2+3)=524\times (1+3+4+0+2+3)=52分。
  2. 这一轮开始时H\mathrm{小H}有两个序列:(4),  (1,3,4,0,2,3)(4),\;(1,3,4,0,2,3)H\mathrm{小H}选择在第3个数字之后的位置将第二个序列分成两部分,并得到(1+3)×(4+0+2+3)=36(1+3)\times(4+0+2+3)=36分。
  3. 这一轮开始时H\mathrm{小H}有三个序列:(4),  (1,3),  (4,0,2,3)(4),\;(1,3),\;(4,0,2,3)H\mathrm{小H}选择在第55个数字之后的位置将第三个序列分成两部分,并得到(4+0)×(2+3)=20(4+0)\times(2+3)=20分。

经过上述三轮操作,H\mathrm{小H}将会得到四个子序列:(4),  (1,3),  (4,0),  (2,3)(4),\;(1,3),\;(4,0),\;(2,3)并总共得到52+36+20=10852+36+20=108分。

标签:斜率优化DP

Solution

易得DP\mathrm{DP}方程:f[t][i]f[t][i]表示玩tt轮时只考虑[1,i][1,i]区间内的数的最大贡献。那么有f[t][i]=maxj=0i1{f[t1][j]+(SiSj)×Sj}f[t][i]=\max_{j=0}^{i-1}\lbrace{f[t-1][j]+(S_i-S_j)\times S_j}\rbrace
简单推一推:

f[t][i]=maxj=0i1{f[t1][j]+(SiSj)×Sj}=maxj=0i1{f[t1][j]+Sj×SiSj2}=maxj=0i1{SiSj+f[t1][j]Sj2}\begin{aligned} f[t][i]&=\max_{j=0}^{i-1}\lbrace{f[t-1][j]+(S_i-S_j)\times S_j}\rbrace\\ &=\max_{j=0}^{i-1}\lbrace{f[t-1][j]+S_j\times S_i-S_j^2}\rbrace\\ &=\max_{j=0}^{i-1}\lbrace{S_iS_j+f[t-1][j]-S_j^2}\rbrace\\ \end{aligned}

套上斜率优化:

For  i[1,n],  if  p<q,  q  is  better  than  p,  thenSpSi+f[t1][p]Sp2SqSi+f[t1][q]Sq2Let  an=Sn,  bn=f[t1][n]Sn2,then  (apaq)SibqbpIf  (apaq)Sibqbp,  then  we  can  pop  p  out  of  the  stack.For\;i\in[1,n],\;if\;p<q,\;q\;is\;better\;than\;p,\;then\\ S_pS_i+f[t-1][p]-S_p^2\le S_qS_i+f[t-1][q]-S_q^2\\ Let\;a_n=S_n,\;b_n=f[t-1][n]-S_n^2,\\ then\;(a_p-a_q)S_i\le b_q-b_p\\ \therefore If\;(a_p-a_q)S_i\le b_q-b_p,\;then\;we\;can\;pop\;p\;out\;of\;the\;stack.

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

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;
}