[斜率优化][dp] 洛谷 P3648 序列分割

题目描述

你正在玩一个关于长度为 nn 的非负整数序列的游戏。这个游戏中你需要把序列分成 k + 1k+1 个非空的块。为了得到 k + 1k+1 块,你需要重复下面的操作 kk 次:

选择一个有超过一个元素的块(初始时你只有一块,即整个序列)

选择两个相邻元素把这个块从中间分开,得到两个非空的块。

每次操作后你将获得那两个新产生的块的元素和的乘积的分数。你想要最大化最后的总得分。

输入输出格式

输入格式:

 

第一行包含两个整数 nn 和 kk。保证 k + 1 \leq nk+1n。

第二行包含 nn 个非负整数 a_1, a_2, \cdots, a_na1,a2,,an (0 \leq a_i \leq 10^4)(0ai104),表示前文所述的序列。

 

输出格式:

 

第一行输出你能获得的最大总得分。

第二行输出 kk 个介于 11 到 n - 1n1 之间的整数,表示为了使得总得分最大,你每次操作中分开两个块的位置。第 ii个整数 s_isi 表示第 ii 次操作将在 s_isi 和 s_{i + 1}si+1 之间把块分开。

如果有多种方案使得总得分最大,输出任意一种方案即可。

 

输入输出样例

输入样例#1:
7 3
4 1 3 4 0 2 3
输出样例#1:
108
1 3 5

说明

你可以通过下面这些操作获得 108108 分:

初始时你有一块 (4, 1, 3, 4, 0, 2, 3)(4,1,3,4,0,2,3)。在第 11 个元素后面分开,获得 4 \times (1 + 3 + 4 + 0 + 2 + 3) = 524×(1+3+4+0+2+3)=52 分。

你现在有两块 (4), (1, 3, 4, 0, 2, 3)(4),(1,3,4,0,2,3)。在第 33 个元素后面分开,获得 (1 + 3) \times (4 + 0 + 2 + 3) = 36(1+3)×(4+0+2+3)=36 分。

你现在有三块 (4), (1, 3), (4, 0, 2, 3)(4),(1,3),(4,0,2,3)。在第 55 个元素后面分开,获得 (4 + 0) \times (2 + 3) = 20(4+0)×(2+3)=20 分。

所以,经过这些操作后你可以获得四块 (4), (1, 3), (4, 0), (2, 3)(4),(1,3),(4,0),(2,3) 并获得 52 + 36 + 20 = 10852+36+20=108 分。

限制与约定

第一个子任务共 11 分,满足 1 \leq k < n \leq 101k<n10。

第二个子任务共 11 分,满足 1 \leq k < n \leq 501k<n50。

第三个子任务共 11 分,满足 1 \leq k < n \leq 2001k<n200。

第四个子任务共 17 分,满足 2 \leq n \leq 1000, 1 \leq k \leq \min\{n - 1, 200\}2n1000,1kmin{n1,200}。

第五个子任务共 21 分,满足 2 \leq n \leq 10000, 1 \leq k \leq \min\{n - 1, 200\}2n10000,1kmin{n1,200}。

第六个子任务共 29 分,满足 2 \leq n \leq 100000, 1 \leq k \leq \min\{n - 1, 200\}2n100000,1kmin{n1,200}。

 

题解

  • 题目大意:有一个长度为n的序列,将其分成k+1块,每次分块将两个相邻的数从中间分开,每次贡献就是新分出来的两个块的元素和的乘积,最大化贡献
  • 显然,我们可以得到一个转移方程,f[i][j]=max(f[i−1][k]+sum[k]×(sum[j]−sum[k]))
  • 但是直接做会炸,然后斜率优化就好了

代码

 1 #include <cstdio>
 2 #include <iostream>
 3 using namespace std;
 4 #define ll long long 
 5 #define sqr(x) (x)*(x)
 6 #define N 100010
 7 #define eps 1e18;
 8 int n,k,head,tail,Q[N],ans[210][N],a[N];
 9 ll sum[N],f[2][N];
10 double calc(int i,int j,int k)
11 {
12     if (sum[i]==sum[j]) return -eps;
13     return (f[(k+1)&1][j]-sqr(sum[j])-f[(k+1)&1][i]+sqr(sum[i]))*1.0/(sum[i]-sum[j]);
14 }
15 int main()
16 {
17     scanf("%d%d",&n,&k);
18     for (int i=1;i<=n;i++) scanf("%d",&a[i]),sum[i]=sum[i-1]+a[i];
19     for (int i=1;i<=k;i++,head=tail=0)
20         for (int j=1;j<=n;j++)
21         {
22             while (head<tail&&calc(Q[head],Q[head+1],i)<=sum[j]) head++;
23             f[i&1][j]=f[(i+1)&1][Q[head]]+sum[Q[head]]*(sum[j]-sum[Q[head]]),ans[i][j]=Q[head];
24             while (head<tail&&calc(Q[tail-1],Q[tail],i)>=calc(Q[tail],j,i)) tail--;
25             Q[++tail]=j;
26         }
27     printf("%lld\n",f[k&1][n]);
28     for (int i=k,j=n;i>=1;i--) printf("%d ",j=ans[i][j]);
29 }

 

posted @ 2019-02-19 17:07  BEYang_Z  阅读(244)  评论(0编辑  收藏  举报