【APIO2014】序列分割
题目描述:
你正在玩一个关于长度为 n 的非负整数序列的游戏。这个游戏中你需要把序列分成 k + 1 个非空的块。为了得到 k + 1块,你需要重复下面的操作 k 次:
选择一个有超过一个元素的块(初始时你只有一块,即整个序列)
选择两个相邻元素把这个块从中间分开,得到两个非空的块。
每次操作后你将获得那两个新产生的块的元素和的乘积的分数。你想要最大化最后的总得分。
输入输出格式
输入格式:
第一行包含两个整数 (n) 和 (k)。保证 (k + 1 leq n)。
第二行包含 (n) 个非负整数 (a_1, a_2, cdots, a_n (0 leq a_i leq 10^4)),表示前文所述的序列。
输出格式:
第一行输出你能获得的最大总得分。
第二行输出 (k) 个介于 (1) 到 (n - 1) 之间的整数,表示为了使得总得分最大,你每次操作中分开两个块的位置。第 (i) 个整数 (s_i) 表示第 (i) 次操作将在 (s_i) 和 $s_{i + 1} $之间把块分开。
如果有多种方案使得总得分最大,输出任意一种方案即可。
输入输出样例
输入样例#1:
7 3 4 1 3 4 0 2 3
输出样例#1:
108 1 3 5
说明
你可以通过下面这些操作获得 (108) 分:
初始时你有一块 ((4, 1, 3, 4, 0, 2, 3))。在第 (1) 个元素后面分开,获得 (4 imes (1 + 3 + 4 + 0 + 2 + 3) = 52) 分。
你现在有两块 ((4), (1, 3, 4, 0, 2, 3))。在第 (3) 个元素后面分开,获得 ((1 + 3) imes (4 + 0 + 2 + 3) = 36) 分。
你现在有三块 ((4), (1, 3), (4, 0, 2, 3))。在第 (5) 个元素后面分开,获得 ((4 + 0) imes (2 + 3) = 20) 分。
所以,经过这些操作后你可以获得四块 ((4), (1, 3), (4, 0), (2, 3)) 并获得 (52 + 36 + 20 = 108) 分。
限制与约定
第一个子任务共 (11) 分,满足 (1 leq k < n leq 10)。
第二个子任务共 (11) 分,满足 (1 leq k < n leq 50)。
第三个子任务共 (11) 分,满足 (1 leq k < n leq 200)。
第四个子任务共 (17) 分,满足 (2 leq n leq 1000, 1 leq k leq min{n - 1, 200})。
第五个子任务共 (21) 分,满足 (2 leq n leq 10000, 1 leq k leq min{n - 1, 200})。
第六个子任务共 (29) 分,满足 (2 leq n leq 100000, 1 leq k leq min{n - 1, 200})。
题解
在解决这个问题之前,我们先大胆口胡一个结论:分割的顺序不影响最后的结果。
接下来就是这个结论的证明了:
设两次分割把队列分为了A,B,C三个部分。
- 先分割A,B之间,再分割B,C之间:(ans=A*(B+C)+B*C=A*B+A*C+B*C)
- 先分割B,C之间,再分割A,B之间:(ans=C*(A+B)+A*B=A*B+A*C+B*C)
由此可以得出分割的顺序不影响最后的结果。
接下来我们就可以进行dp了。
这是一个很明显的二维dp。
(dp[i][k])表示前(i)个数被分割了(k)次所能得到的最大的答案。
转移方程为(dp[i][k]=max(dp[j][k-1]+sum_{u=1}^{j}a[u]*sum_{u=j+1}^{i}a[u]))(在第(k)个数和第(k+1)个数之间分割)
证明:
假设(dp[j][k-1])最右边的分割点为(o)和(o+1)(从(j)转移后(dp[i][k])最后),
则(dp[i][k-1]=dp[j][k-1]+sum_{u=1}^{o}a[u]*sum_{u=j+1}^{i}a[u])
所以(dp[i][k]=dp[i][k-1]+sum_{u=o+1}^{j}a[u]*sum_{u=j+1}^{i}a[u])
(=dp[j][k-1]+sum_{u=1}^{o}a[u]*sum_{u=j+1}^{i}a[u]+sum_{u=o+1}^{j}a[u]*sum_{u=j+1}^{i}a[u])
(=dp[j][k-1]+sum_{u=1}^{j}a[u]*sum_{u=j+1}^{i}a[u])
接下来我们用前缀和减小时间复杂度,
设(sum[i]=sum_{j=1}^{i}a[j])
所以(dp[i][k]=max(dp[j][k-1]+sum[j]*(sum[i]-sum[j])))
这个转移方程时间复杂度为O(n^2),因为(2 leq n leq 100000)所以我们用斜率优化。
重点:
我们取(j),(p)满足 (p<j<i)且(j)比(k)更优,那么有如下不等式:
化简后得:
接下来维护一个下凸壳就可以了。
注意:题目中(a[i])的范围是非负整数,所以(a[i])可以为0,计算时需要特判。
上代码:
#include<bits/stdc++.h>
#define inf 1e18
using namespace std;
int n,k;
long long a[100009],s[100009];
long long dp[100009];
long long pp[1000009],g[100009];
long long l=1,r=1;
double js(int j,int k){
if(s[j]==s[k]) return -inf;
return 1.0*(g[k]-g[j]+s[j]*s[j]-s[k]*s[k])/(s[j]-s[k]);
}
int main(){
scanf("%d%d",&n,&k);
for(int j=1;j<=n;j++)
scanf("%lld",&a[j]);
for(int j=1;j<=n;j++)
s[j]=s[j-1]+a[j];
for(int o=1;o<=k;o++){
l=1;r=1;
pp[1]=0;
for(int j=1;j<=n;j++){
while(l<r && js(pp[l+1],pp[l])<s[j]) l++;
dp[j]=g[pp[l]]+(s[j]-s[pp[l]])*s[pp[l]];
while(l<r && js(pp[r],pp[r-1])>js(j,pp[r])) r--;
pp[++r]=j;
}
for(int j=1;j<=n;j++)
g[j]=dp[j];
}
printf("%lld",dp[n]);
return 0;
}