当前位置: 首页 > news >正文

洛谷 P3648 APIO2014 序列分割 题解

写了挺多斜率优化的题目了,这道(差点)就速切了,原因还是单调队列维护斜率的写法出锅。

题意

题目描述

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

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

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

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

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

输入

7 3
4 1 3 4 0 2 3

输出

108
1 3 5

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}

思路

对序列分割,分割的顺序会不会对答案造成影响呢?

之前 smsky 选拔的时候,第一题与之是类似的。我们可以猜测:对于这种大块的乘积,如果固定了分割点,无论分割顺序如何,都不会改变结果

考虑简要地证明这个结论:设序列有三个分割点 1 , 2 , 3 1,2,3 1,2,3,序列被分割成了元素和依次为 a , b , c , d a,b,c,d a,b,c,d 的四块。

  • 1 , 2 , 3 1,2,3 1,2,3 的顺序分割: a n s 1 = a ( b + c + d ) + a ( b + c ) + c d = a b + a c + a d + b c + b d + c d ans_1=a(b+c+d)+a(b+c)+cd=ab+ac+ad+bc+bd+cd ans1=a(b+c+d)+a(b+c)+cd=ab+ac+ad+bc+bd+cd
  • 3 , 1 , 2 3,1,2 3,1,2 的顺序分割: a n s 2 = ( a + b + c ) d + a ( b + c ) + b c = a d + b d + c d + a b + a c + b c ans_2=(a+b+c)d+a(b+c)+bc=ad+bd+cd+ab+ac+bc ans2=(a+b+c)d+a(b+c)+bc=ad+bd+cd+ab+ac+bc;
  • 1 , 3 , 2 1,3,2 1,3,2 的顺序分割:。。。

多枚举几组,我们发现 a n s ans ans 始终不变。

因此简证成立。

那么我们就可以 1 ∼ n 1\sim n 1n 枚举分割点了。考虑朴素的 dp,设 f i , t f_{i,t} fi,t 表示前 i i i 个数,分割了 t t t 次,且在 i i i 之后不分割的最小花费。(这样子就不用特意讨论 n n n 之后还被分割了的情况了)

处理前缀和 p r e i = ∑ j = 1 i a j pre_i=\sum_{j=1}^{i}a_j prei=j=1iaj。枚举分割点 j j j,在 1 ∼ i 1\sim i 1i 分成了 1 ∼ j 1\sim j 1j j + 1 ∼ i j+1\sim i j+1i 两块,和分别是:
f i , t = max ⁡ j = 1 i − 1 { f j , t − 1 + p r e j ( p r e i − p r e j ) } f_{i,t}=\max_{j=1}^{i-1}\left\{f_{j,t-1}+pre_j(pre_i-pre_j)\right\} fi,t=j=1maxi1{fj,t1+prej(preiprej)}

设两个决策点 j 1 < j 2 j1<j2 j1<j2 j 2 j2 j2 优于 j 1 j1 j1,那么:
f j 1 , t − 1 + p r e j 1 ( p r e i − p r e j 1 ) < f j 2 , t − 1 + p r e j 2 ( p r e i − p r e j 2 ) f_{j1,t-1}+pre_{j1}(pre_i-pre_{j1})<f_{j2,t-1}+pre_{j2}(pre_i-pre_{j2}) fj1,t1+prej1(preiprej1)<fj2,t1+prej2(preiprej2)

f j 1 , t − 1 + p r e i p r e j 1 − p r e j 1 2 ) < f j 2 , t − 1 + p r e i p r e j 2 − p r e j 2 2 f_{j1,t-1}+pre_ipre_{j1}-pre_{j1}^2)<f_{j2,t-1}+pre_ipre_{j2}-pre_{j2}^2 fj1,t1+preiprej1prej12)<fj2,t1+preiprej2prej22

f j 1 , t − 1 − f j 2 , t − 1 + p r e j 2 2 − p r e j 1 2 < p r e i ( p r e j 2 − p r e j 1 ) f_{j1,t-1}-f_{j2,t-1}+pre_{j2}^2-pre_{j1}^2<pre_i(pre_{j2}-pre_{j1}) fj1,t1fj2,t1+prej22prej12<prei(prej2prej1)

注意到 p r e j 2 − p r e j 1 ≥ 0 pre_{j2}-pre_{j1}\ge 0 prej2prej10,依旧注意避开 0 0 0;如果非 0 0 0 就可以直接除过去:
f j 1 , t − 1 − f j 2 , t − 1 + p r e j 2 2 − p r e j 1 2 p r e j 2 − p r e j 1 < p r e i \frac{f_{j1,t-1}-f_{j2,t-1}+pre_{j2}^2-pre_{j1}^2}{pre_{j2}-pre_{j1}}<pre_i prej2prej1fj1,t1fj2,t1+prej22prej12<prei

s l o p e < p r e i slope<pre_i slope<prei p r e i pre_i prei 单增,和玩具装箱是同一类,维护单调递增斜率,最优决策扔队首就好。

因为还有一个切割次数的维度,所以可以对每一次切割 t ∈ [ 1 , k ] t\in[1,k] t[1,k],每次 t + 1 ← t t+1\leftarrow t t+1t 转移的时候做一次斜率优化 dp 即可。

题目还有一个要求就是求切割的方案,那也很好做,就是记录前缀数组 f a t i , t fat_{i,t} fati,t 表示枚举到 i i i,切割了 t t t 次的第 t t t 次切割点。由于单调队列直接维护反馈的就是最优决策点,那么用 f a t fat fat 数组记录每次的队首就好。

有一点要注意,因为 f a t fat fat 数组与单调队列维护的最优决策点挂钩,那么单调队列应当严谨维护。在上一篇题解,弹出队尾时时要写 ≤ \le 之外,还要把队首往后压,防止出现 0 0 0 的情况(其实就是把推式子的时候,把两个决策点 j 1 j1 j1 j 2 j2 j2 之间的符号改成 ≤ \le ,对答案 1 1 1 正确性没有影响,但是缩减了很多不必要的 0 0 0 决策点)

代码

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define dd double
const ll N=1e5+9,K=202,inf=0x7f7f7f7f;
ll n,k,a[N];
ll f[N][K],q[N],pre[N];
int fat[N][K];
ll _2(ll x)
{
	return x*x;
}
dd slope(ll j1,ll j2,ll t)
{
	if(pre[j1]==pre[j2])return -inf;
	return (dd)(f[j1][t-1]-f[j2][t-1]+_2(pre[j2])-_2(pre[j1]))/(dd)(pre[j2]-pre[j1]);
}
int main()
{
	scanf("%lld%lld",&n,&k);
	for(int i=1;i<=n;i++)
	{
		scanf("%lld",&a[i]);
		pre[i]=pre[i-1]+a[i];
	}
	for(int t=1;t<=k;t++)
	{
		ll hh=0,tt=0;
		for(int i=1;i<=n;i++)
		{
			while(hh<tt&&slope(q[hh],q[hh+1],t)<=pre[i])hh++;
			f[i][t]=f[q[hh]][t-1]+pre[q[hh]]*(pre[i]-pre[q[hh]]);
			fat[i][t]=q[hh];
			while(hh<tt&&slope(q[tt],i,t)<=slope(q[tt-1],q[tt],t))tt--;
			q[++tt]=i;
		}
	}
	printf("%lld\n",f[n][k]);
	for(int tem=n,i=k;i>=1;i--)
	{
		tem=fat[tem][i];
		printf("%d ",tem);
	}
	return 0;
}

相关文章:

  • python虚拟环境
  • CDAM 第八章 数据资产运营
  • 计算机毕业设计Python+DeepSeek-R1大模型农作物害虫识别 机器学习农作物健康识别系统 人工智能 图像识别 机器学习 深度学习
  • swift4-汇编分析枚举内存布局
  • PHP Error处理指南
  • OpenCV下载与配置(vistual studio 2022)
  • Deepseek×ComfyUI革命性工作流:AI图像3倍速精修实战指南
  • PyCharm使用中文版还是英文版较好?出于编程能力进阶和编程复杂性提高的考虑。
  • WHAT - 前端异步事件流处理场景梳理
  • 嵌入式开发中,TEE镜像是什么东西?
  • 面试准备——云相册项目(1)基础
  • 网上花店微信小程序+论文源码调试讲解
  • 如何优化FFmpeg拉流性能及避坑指南
  • 前端基础之ajax
  • 【Pandas】pandas Series argmax
  • 工厂模式:简单工厂、工厂方法以及抽象工厂
  • 重定位(2)
  • Qt的QDateTimeEdit控件的使用
  • 并发编程(线程安全)面试题及原理
  • 华为云IAM 用户名和IAM ID
  • 车载抬头显示爆发在即?业内:凭借市场和产业链优势,国内供应商实现反超
  • 国台办:民进党当局刻意刁难大陆配偶,这是不折不扣的政治迫害
  • 在笔墨金石间,看胡问遂与梅舒适的艺术对话
  • 山东省市监局“你点我检”专项抽检:一批次“无抗”鸡蛋农兽药残留超标
  • 外媒:初步结果显示,菲律宾前总统杜特尔特当选达沃市市长
  • 60余年产业积累,“江苏绿心”金湖炼就“超级石油工具箱”