题解:[Apio2014]序列分割
其实下面的这个题是一个简化版本,原题要给dp数组开滚动数组,这个很简单就不写了;还有就是原题要输出方案,注释在代码里。
Luogu: [APIO2014] 序列分割
题面
题目描述
小H最近迷上了一个分隔序列的游戏。在这个游戏里,小H需要将一个长度为n的非负整数序列分割成k+1个非空的子序列。为了得到k+1个子序列,小H需要重复k次以下的步骤:
1.小H首先选择一个长度超过1的序列(一开始小H只有一个长度为n的序列——也就是一开始得到的整个序列);
2.选择一个位置,并通过这个位置将这个序列分割成连续的两个非空的新序列。
每次进行上述步骤之后,小H将会得到一定的分数。这个分数为两个新序列中元素和的乘积。小H希望选择一种最佳的分割方式,使得k轮之后,小H的总得分最大。
输入格式
输入第一行包含两个整数n,k(k+1≤n)。
第二行包含n个非负整数a1,a2,…,an(0≤ai≤10^4),表示一开始小H得到的序列。
输出格式
输出第一行包含一个整数,为小H可以得到的最大分数。
样例
样例输入
样例输出
数据范围与提示
数据满足2≤n≤100000,1≤k≤min(n -1,200)。
注意,输入的a[i]里可能有0
解法
转移方程
p.s. 当我们确定了最后切分出来的是哪些区间,我们的答案就是一样的
时间复杂度
使用前缀和优化:
记
优化
法1:分离变量后使用单调队列
从小到大枚举m,固定当前m,考虑到当前在计算f[m][i]:
假如有两个不同决策点j,k,尝试分析它们谁更优:
不妨设 j<k<i
对于当前的i,k比j更优等价于:
将j,k分到不等式一侧,i分到另一侧得:
记右式为
对于固定的m,有j
三个决策点,易知,有 j1
双端队列维护j[l]…j[r]从小到大,g函数也是从小到大的
对于任意
1 | //框架 |
然后我们可以解决本题
1 | #include<bits/stdc++.h> |
法2:斜率优化(aka 数形结合)
=$f[m-1][j]-s[j]s[j]+s[j]s[i]$
相当于我们有若干个选项 j,每个j提供一个一次函数 y[j]
对于所有
此时我们维护若干个一次函数,要支持插入和求最大值
对于本题的特殊的单调性,即每次插入的
于是,我们可以用双端队列来维护
1 | //框架 |
与刚刚的单调队列优化相比,
此处也可以用维护凸壳来实现
一般地,给定dp方程
通过恒等变形,使其变成:
这使得我们联想到了斜率公式
这样可以询问到一个点连到现有的点的斜率最大
如何维护下凸线?
如果每次加入点的x坐标是单调增的:和上面两种方法几乎一样的单调队列写法即可维护下凸线
如何处理查询?
对于给定的查询点,凸壳上的点到这个查询点的斜率是先严格单调增,(然后可能会恒等不变一段),然后再严格单调减的。
在凸壳上使用三分法
斜率优化代码会长成这样:(其实长得跟上面的没什么区别qwq)
1 | double slope(int a,int b) |
法3:二分队列
考虑一个dp方程
对于某两个
即满足强决策单调性:一旦一个后来者超过你,他就会一直超过你,所以随着i增大,最优决策点也严格单调增
而本题满足
考虑:每个决策点j,是哪些i的最优决策点
对于每个
对于
所有这些
我们尝试维护当前[0,i-1]间的最优决策区间:
但是那些>=i的决策点,未来可能“抢走”当前某个j的
也就是我们将维护若干个三元组(j,l,r)
然后在队列里二分就可以找到切分点了
题解:[Apio2014]序列分割
https://imoliviauu.github.io/2024/07/15/solution-luogu-p3648/
install_url
to use ShareThis. Please set it in _config.yml
.