1 条题解
-
0
自动搬运
来自洛谷,原作者为

Develop
我在努力着搬运于
2025-08-24 21:21:12,当前版本为作者最后更新于2019-12-01 22:15:24,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
2019.11.30
关于最大子段和及其变式
增加了一些变式的题面描述,一个,补了一些锅
最大子段和
枚举区间起点和终点,暴力求和;
枚举左右端点时 -> 可以动态维护~的前缀和;
预处理前缀和,枚举左右端点时计算;
枚举中点,分别往前面和后面计算一个包含的最大子段和,预处理前缀和一次判断是的;
考虑,设状态为包含且以结尾的最大值,方程:;
一个以结尾最大子段的定义: ,计算可以动态维护一个前缀和的最小值;
子段长度不大于m的最大子段和
仍然考虑最大子段和的定义,动态维护,使用单调队列维护,队列中的数递增(参考滑动窗口);
子段长度不小于m的最大子段和
可以将分治思想中的枚举中点改成枚举一个中段,同理;(但是显然需要枚举一个段的话编码难度会提升)
先计算一次不限长最大子段和,再从头开始,枚举每一个长为m的子段,在它前面拼上以结尾的最大子段和;方程:,最后取一个;
动态维护,比上一种编码简洁,只需要一次循环,维护前缀和;
环状最大子段和
在最大子段和的基础上,认为a[1]和a[n]是相邻的。将序列倍长,就可以做一遍长度不大于m的最大子段和
考虑子段是否过端点,过端点就取反,其实就是求一个最小子段和;不过端点就没有影响。
环状最大两段子段和
考虑破环成链,枚举破坏的断点,在链上求最大两段子段和;维护一个为以结尾的最大子段和,一个为以开头的最大子段和;(其实就是倒着求一遍)复杂度;
对于答案可能有的所有情况,一种是有一段跨过了端点,另一种是没有跨过端点;所以可以先求一遍两段最大子段和,再对整个序列取反,再求一遍(这时其实就是求出两段的最小子段和,用总和减去后就是跨过端点的两段最大子段和),这时把两种情况取一个;
最大m段子段和
给定由n个整数(可能为负整数)组成的序列, 以及一个正整数m,要求确定序列m个不相交子段, 使得这m个子段的总和达到最大,求出最大和。不用说了,暴力枚举每一段的左右端点,求和;
还可以再优化,动态维护每一段当前之前,上一段结尾之后的前缀和,可以递归实现;当然递归的形式会减慢速度;
难道不像一个区间dp??我们设状态为区间内已经分配了k段的最大值;方程为:$f1[i][j][k]=max(f1[i][j][k],f1[i][p][k-1]+f1[p+2][j][1])~(p>i,p+2<=j)$;这转移显然有:
for(int p=1;p<=m;p++) for(int i=1;i<=n;i++) for(int j=i;j<=n;j++) for(int q=i+1;q+2<=j;q++) f[i][j][p]=max(f[i][j][p],f[i][q][p-1]+f[q+2][j][1]);其实本来方程中枚举段数也需要一层循环,但是我们发现这是一个分段问题,和选取的段数先后无关,故我们可以直接把一段拼上段。(对于这种dp我更愿意称它为分段dp而不是区间dp)接着我们将方程转化一下,会发现其实就是!这时方程变为$f1[i][j][k]=max(f1[i][j][k],f1[i][p][k-1]+sum[j]-sum[q+1])~(p>i,p+2<=j)$;我们再次观察,发现转移可以优化:第一维所涉及的转移全部是!这时把第一维去掉,状态定义转化为区间内已经分配了k段的最大值:;这时转移就有:
for(int p=1;p<=m;p++) for(int j=1;j<=n;j++) for(int q=i+1;q+2<=j;q++) f[j][p]=max(f[j][p],f[j][p-1]+sum[j]-sum[q+1]);上一种方法的方程已经变为$f1[j][p]=max(f1[j][p],f[j][q][p-1]+sum[j]-sum[q+1])$,我们再看看还是否可以优化。我们发现,求m段子段的过程中,当我已经求出前个元素,段的时候,我要做的,其实就是考虑向后多少个元素后再重新开一段。所以该状态为表示前个元素,段,第i个元素是否选的最大子段和。转移就好写了:
f[i][j][1]=max(f[i-1][j][1],f[i-1][j-1][0])+a[i]; f[i][j][0]=max(f[i-1][j][1],f[i-1][j][0]);只含正整数的不小于()的最小子段和的长度()
给出了N个正整数(10 <N <100 000)的序列, 每个正整数均小于或等于10000,并给出了一个正整数S(S <100 000 000)。 编写程序以查找序列中连续元素的子序列的最小长度,其总和大于或等于S。枚举左右端点,用前缀和计算,取最小值;
考虑二分答案,每次计算不超过当前长度下的最大子段和能否不小于,具体操作见
一种新的方法:尺取法。因为题目所给数均为正整数,所以可以设一个队首和一个队尾,总和小于就往后加一个数,大于就在最前面减一个数。(决策的单调性是应用尺取法的前提,老师将它比作虫子向前蠕动的样子)同样,尺取法不仅能求长度,也能求大于的最小子段和。而二分得到的答案,最短的不一定是最接近的。
含负数的不小于()的最小子段和的长度()
现将所有前缀和算出来,离散化;题目所求即为求一个,满足$sum[j]-sum[i]>=S~(1<=i<=j,sum[i]=min(sum[1]~to~sum[j-1]))$;即。开一个值域树状数组,表示的是前缀和为(离散化后的)的前缀的最后一个位置。相当于在树状数组内每次查询小于的那一段中位置的最大值。这样就求出了最小长度;而对于最小值的话,查询第一个就可以了。
含/不含负数的不大于S的最大子段和的长度/值
这些其实都可以类比上一个的做法,不说了。
一些其他的
有些题目中要求的是不相邻的两个子段,有些又没有要求。其实不相邻意味着两个子段之间至少隔一个数;不然的话,两个子段虽然接在了一起,但是仍可以认为是两个子段。这个时候其实关于转移,只需要转移改成就可以了。
启示:其实我们在优化算法时,优化枚举的核心即为减少枚举过程中的重复计算。
- 1
信息
- ID
- 123
- 时间
- 1000ms
- 内存
- 125MiB
- 难度
- 4
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者