[Luogu P3648] [APIO2014]序列分割

洛谷传送门

题目描述

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

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

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

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

输入输出格式

输入格式:

第一行包含两个整数 n n n k k k 。保证 k + 1 ≤ n k + 1 \leq n k+1n

第二行包含 n n n 个非负整数 ( a 1 , a 2 , ⋯   , a n , 0 ≤ a i ≤ 1 0 4 a_1, a_2, \cdots, a_n, 0\leq a_i \leq 10^4 a1,a2,,an,0ai104 ) ,表示前文所述的序列。

输出格式:

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

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

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

输入输出样例

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

说明
你可以通过下面这些操作获得 108 108 108 分:

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

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

你现在有三块$ (4), (1, 3), (4, 0, 2, 3)$。在第 5 个元素后面分开,获得 ( 4 + 0 ) × ( 2 + 3 ) = 20 (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) (4),(1,3),(4,0),(2,3) 并获得$ 52 + 36 + 20 = 108$ 分。

数据范围
第一个子任务共 11 分,满足 1 ≤ k &lt; n ≤ 10 1 \leq k &lt; n \leq 10 1k<n10

第二个子任务共 11 分,满足 1 ≤ k &lt; n ≤ 50 1 \leq k &lt; n \leq 50 1k<n50

第三个子任务共 11 分,满足 1 ≤ k &lt; n ≤ 200 1 \leq k &lt; n \leq 200 1k<n200

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

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

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

解题分析

首先我们可以发现分割顺序并不影响结果…(大家手动模拟一下), 那么我们用 s u m ( i ) sum(i) sum(i)表示第i个元素的前缀和, 用 d p [ i ] [ j ] dp[i][j] dp[i][j]表示前 i i i个元素切 j j j刀,那么我们可以得到如下转移方程:

d p [ i ] [ j ] = m a x ( d p [ m ] [ j − 1 ] + ( s u m [ i ] − s u m [ m ] ) ∗ s u m [ m ] ) , m ∈ [ j ,   i − 1 ] dp[i][j]=max(dp[m][j-1]+(sum[i]-sum[m])*sum[m]),m \in [j,\ i-1] dp[i][j]=max(dp[m][j1]+(sum[i]sum[m])sum[m]),m[j, i1]

然后我们可以发现无论是时间还是空间都过不去…

考虑到 d p [ i ] [ j ] dp[i][j] dp[i][j]只由 d p [ i − 1 ] [ m ] dp[i-1][m] dp[i1][m]转移而来, 我们可以使用滚动数组来优化空间的问题。至于时间, 可以使用斜率优化来降低至 O ( n k ) O(nk) O(nk)

因为只从上一轮转移, 我们在这里忽视 i i i, 只考虑 j j j。 设从 j j j转移没有从 k k k转移优, 那么有如下方程:

d p [ j ] + s u m [ j ] ∗ ( s u m [ i ] − s u m [ j ] ) ≤ d p [ k ] + s u m [ k ] ∗ ( s u m [ i ] − s u m [ k ] ) dp[j]+sum[j]∗(sum[i]−sum[j])≤dp[k]+sum[k]∗(sum[i]−sum[k]) dp[j]+sum[j](sum[i]sum[j])dp[k]+sum[k](sum[i]sum[k])

将sum[i]提出来, 得到以下方程:

s u m [ i ] ≥ d p [ k ] − d p [ j ] − s u m [ k ] 2 + s u m [ j ] 2 s u m [ j ] − s u m [ k ] sum[i]\geq \frac {dp[k]-dp[j]-sum[k]^2+sum[j]^2}{sum[j]-sum[k]} sum[i]sum[j]sum[k]dp[k]dp[j]sum[k]2+sum[j]2

因为 s u m [ i ] sum[i] sum[i]单增, 我们就可以使用单调队列优化dp了。

代码如下:

#include <cstdio>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <cctype>
#include <algorithm>
#define R register
#define IN inline
#define W while
#define gc getchar()
#define MX 100005
#define ll long long
#define db double
using namespace std;
template <class T>
IN void in (T &x)
{
	x = 0; R char c = gc;
	W (!isdigit(c)) c = gc;
	W (isdigit(c))
	{x = (x << 1) + (x << 3) + c - 48, c = gc;}
}
template <class T>
IN T sqr(const T &x)
{return x * x;}
ll sum[MX], dp[2][MX], divi[205][MX], que[MX];
int num, cut, head, tail;
int t;
IN db slope(const int &x, const int &y)
{
	if(sum[x] == sum[y]) return -1e18;
	return (db)(sqr(sum[y]) - sqr(sum[x]) + dp[t&1^1][x] - dp[t&1^1][y]) / (sum[y] - sum[x]);
}
int main(void)
{
	int a;
	in(num), in(cut);
	for (R int i = 1; i <= num; ++i)
	{
		in(a);
		sum[i] = sum[i - 1] + a;
	}
	for (t = 1; t <= cut; ++t)
	{
		head = tail = 0;
		for (R int j = 1; j <= num; ++j)
		{
			W (head < tail && slope(que[head], que[head + 1]) <= sum[j]) head++;
			dp[t&1][j] = dp[t&1^1][que[head]] + (sum[j] - sum[que[head]]) * sum[que[head]];
			divi[t][j] = que[head]; //记录每次在哪里切下的
			W (head < tail && slope(que[tail - 1], que[tail]) > slope(que[tail], j)) tail--;
			que[++tail] = j; 
		}
	}
	printf("%lld\n", dp[cut&1][num]);
	for (R int i = cut, now = num; i > 0; --i)
	{
		now = divi[i][now];
		printf("%d ", now);//回溯输出切的位置
	}
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值