最大子段和问题及变式
最大子段和问题及变式 题解
Lim:除 T8,T9 外所有题目的 \(a_i\) 均可能为负,所有题目的 \(a_i\) 均为整数
T1 P1115 最大子段和
Tag: 线性dp / 单调队优线性dp / 分治,递归
题目描述:
给出一个长度为 \(n\) 的序列 \(a\) ,选出其中连续且非空的一段使得这段和最大。
题解:
Way 1: \(O(n\log n)\)
每次取一个中点,往下分治,往上递归。
Way 2: \(O(n)\)
\(f_i\) 表示以 \(a_i\) 结尾的最大子段和。
\(f_i=\max{(f_{i-1},0)}+a_i\)
对所有 \(f_i\) 取 \(\max\) 得到答案。
Way 3: \(O(n)\)
记 \(sum_i\) 为 \(\sum_{j=1}^ia_i\)
以 \(a_i\) 结尾的最大子段和也可以表示为: \(sum_i-\min_{j=1}^{i-1}sum_j\)
于是从 \(1\) 到 \(n\) 扫一遍,一边维护 \(\min_{j=1}^{i-1}sum_j\) 即可
Main Code: (Way 2)
for(int i=1;i<=n;++i) f[i]=max(f[i-1],0)+a[i],ans=max(ans,f[i]);
T2 子段长度不大于m的最大子段和
Tag: 单调队列优化线性dp
题目描述:
给出一个长度为 \(n\) 的序列 \(a\) ,选出其中连续非空且长度不大于 \(m\) 的一段使得这段和最大。
题解:
参考 T1 的 Way 3 : \(O(n)\)
以 \(a_i\) 结尾的最大子段和可表示为: \(sum_i-\min_{j=i-m}^{i-1}sum_j\)
于是从 \(1\) 到 \(n\) 扫一遍,一边利用单调队列维护 \(\min_{j=i-m}^{i-1}sum_j\) 即可
Main Code:
//Later to complete
T3 子段长度不小于m的最大子段和
题目描述:
给出一个长度为 \(n\) 的序列 \(a\) ,选出其中连续非空且长度不小于 \(m\) 的一段使得这段和最大。
题解:
参考 T1 的 Way 1 : \(O(n\log n)\)
每次取一个中段,往下分治,往上递归。
参考 T1 的 Way 2 : \(O(n)\)
先跑一遍T1: \(f_i\) 表示以 \(a_i\) 结尾的最大子段和, \(f_i=\max{(f_{i-1},0)}+a_i\)
再在每个 \(f\) 后拼上一个长为 \(m-1\) 的子串: \(f_i'=\max{(f_{i-1},0)}+sum_{i+m-1}-sum_{i-1}\)
对所有 \(f_i'\) 取 \(\max\) 得到答案。
参考 T1 的 Way 3 : \(O(n)\)
以 \(a_i\) 结尾的最大子段和可表示为: \(sum_i-\min_{j=1}^{i-m}sum_j\)
于是从 \(m\) 到 \(n\) 扫一遍,同时维护 \(\min_{j=1}^{i-m}sum_j\) 即可
Main Code:
//Later to complete
T4 #A1045 环状最大子段和
题目描述:
给出一个长度为 \(n\) 的环状序列 \(a\) ,即 \(a_1\) 与 \(a_n\) 相邻,选出其中连续且非空的一段使得这段和最大。
题解:
Way 1: \(O(n)\)
断环成链,跑 T2 (子段长度不大于 \(n\) 的最大子段和)。
Way 2: \(O(n)\)
由于所有数的和 \(sum_n\) 是一定的,最大子段和 和 最小子段和 一定正好覆盖整个区间。
利用 T1 的方法,分别跑最大子段和 \(f_i\) 和 最小子段和 \(f_i'\) ,最后答案即为 \(\max{(\max_{i=1}^n{(f_i)},sum_n-\min_{i=1}^n{(f_i')})}\) 。
Main Code:
//Later to complete
T5 P2642 双子序列最大和
题目描述:
给定一个长度为 \(n\) 的序列 \(a\) ,选出其中连续非空且不相交的两段使得这两段和最大。
题解:
用 T1 的方法,分别跑出 **以 \(a_i\) 结束的最大子段和 \(f_i\) ** 和 **以 \(a_i\) 开始的最大子段和 \(f'_i\) ** 。
之后 \(O(n)\) 枚举断点, \(ans=\max_{i=1}^{n-1}{f_i+f'_{i+1}}\) 。
<!!! 套路> 对于序列上两不相交区间的问题,枚举断点取最优解。两个区间的需求不相同时需要把区间信息分别按照两个区间的需求两次预处理。
这里有一道例题:P1091 [NOIP2004 提高组] 合唱队形
Main Code:
//Later to complete
T6 P1121 环状最大两段子段和
题目描述:
给定一个长度为 \(n\) 的环状序列 \(a\) ,即 \(a_1\) 与 \(a_n\) 相邻,选出其中连续非空且不相交的两段使得这两段和最大。
题解:
T4 + T5
用 T5 的方法跑出 双子序列最大和 和 双子序列最小和 ,由 T4 可知这俩一样互补,接着用 T4 的思路跑就可以了。
Main Code:
//Later to complete
T7 最大m段子段和 (hdu1024 Max Sum Plus Plus)
Tag: 区间dp dp
题目描述:
给出一个长度为 \(n\) 的序列 \(a\) ,选出其中连续非空且互不相交的 \(m\) 段使得这 \(m\) 段和最大。
题解:
Way 1: \(O(n^3m^2)\)
emm……这很区间dp。
令 \(f_{l,r,t}\) 表示区间 \([l,r]\) 内取出 \(t\) 段子区间的最大答案。
\(f_{l,r,t}=\max_{k=l}^{r-1}{\max_{p=0}^t{(f_{l,r,t},f_{l,k,p}+f_{k+1,r,t-p})}}\)
枚举 \(t\in[1,m]\rightarrow len\in[1,n]\rightarrow l\in[1,n-len+1]\rightarrow k\in[l,l+len-1]\rightarrow p\in[0,t]\) ,总复杂度 \(O(n^3m^2)\) 。
Way 2: \(O(n^3m)\)
注意到这是一个分段问题,与段数的分配无关,所以可以省掉转移式的最后一维,固定分别分配 \(t-1\) 个和 \(1\) 个段给左右两个区间。
如上图所示,假设绿色的三段是 \(f_{L,R,3}\) 的最优解,我们用 Way 1 的方法更新, \(f_{L,k1,1}+f_{k1+1,R,2}\) 、 \(f_{L,k2,2}+f_{k2+1,R,1}\) 和 \(f_{L,k3,2}+f_{k3+1,R,1}\) 都可以更新到该最优解,但重复更新浪费了时间。
指定分配方式后,该最优解只会被断点 \(k2,k3\dots\) 更新,而 \(k1\) 等重复更新情况被剔除,\(f_{L,k2,1}+f_{k2+1,R,2}\) 等无效更新大幅减少。可以保证正确性,同时优化掉了最后一层循环。
\(f_{l,r,t}=\max_{k=l}^{r-1}{(f_{l,r,t},f_{l,k,t-1}+f_{k+1,r,1})}\)
Way 3: \(O(n^2m)\)
考虑直接将上式中的 \(f_{k+1,r,1}\) 替换为 \(sum_r-sum_k\) 。 别问我咋想到的
为什么这样是对的呢?
设绿色区域为一个最优子段。一定存在一个时刻如图,使得这个区间恰好与 \([k+1,R]\) 重合参与更新。也就是说这样子做保证了每个区间都能被考虑,也就保证了正确性。
那么转移式就变成了酱紫:
\(f_{l,r,t}=\max_{k=l}^{r-1}{(f_{l,r,t},f_{l,k,t-1}+sum_r-sum_k)}\)
注意到修改之后的转移式中 \(f\) 的第一维下标全是 \(l\) ,也就是说都是在同一个 \(l\) 的基础上进行更新。考虑只更新 \(l=1\) 的情况,进而忽略第一维,所有更新都以 \(1\) 为左端点,不会出现未知更新已知的情况,即无后效性,并且依然可以涵盖所有子区间的信息。 别问我咋想到的*2
忽略第一维后, \(f\) 的定义变为:
令 \(f_{r,t}\) 表示区间 \([1,r]\) 内取出 \(t\) 段子区间的最大答案。
转移式变为:
\(f_{r,t}=\max_{k=l}^{r-1}{(f_{r,t},f_{k,t-1}+sum_r-sum_k)}\)
枚举 \(t\in[1,m]\rightarrow r\in[1,n]\rightarrow k\in[1,r]\) ,总复杂度 \(O(n^2m)\) 。
Way 4: \(Time:O(nm)\quad Memory:O(nm)\)
令 \(f_{i,j}\) 表示考虑以 \(a_i\) 结尾选出了 \(j\) 段的最大子段和。
\(f_{i,j}=\max{(f_{i-1,j-1},f_{i-1,j})}+a_i\)
// \(f_{i-1,j-1}\) :前 \(i-1\) 个数选出 \(j-1\) 个数, \(a_i\) 自成一段。
// \(f_{i-1,j}\) :前 \(i-1\) 个数选出 \(j-1\) 个数, \(a_i\) 接到最后一段尾巴上。
Way 5: \(Time:O(nm)\quad Memory:O(m)\)
滚动数组滚掉第一维即可,至此可以通过 hdu1024 。
Main Code:
//Later to complete
T8
Tag: 二分答案 尺取法
题目描述:
给定一个长度为 \(n\) 的序列 \(a\) ,选出其中连续非空且最短的一段使得这一段和大于等于一个给定数 \(S\) 。
SP.Lim: \(a_i>0\)
题解:
注意,此题限制 \(a_i>0\)
Way 1: \(O(n\log n)\)
二分答案。
二分长度,然后跑 T2 。
Way 2: \(O(n)\)
尺取法。
双指针从左往右扫描一遍即可。
Main Code:
//Later to complete
T9
Tag: 尺取法
题目描述:
给定一个长度为 \(n\) 的序列 \(a\) ,选出其中连续非空且最短的一段使得这一段和大于等于一个给定数 \(S\) ,并且使这一段的和尽可能小。
SP.Lim: \(a_i>0\)
题解:
注意,此题限制 \(a_i>0\)
Way 1:
与 T8 Way 1 相似,不过要在跑 T2 的同时维护子段和。
Way 2:
T8 Way 2 ,不用写了吧。
Main Code:
//Later to complete
T10
Tag:
题目描述:
给定一个长度为 \(n\) 的序列 \(a\) ,选出其中连续非空且最短的一段使得这一段和大于等于一个给定数 \(S\) 。
题解:
注意,此题不限制 \(a_i>0\) ,也就是说存在 \(a_i\) 为负。
Way : \(O(n\log n)\)
参考 T1 Way 3 ,我们要求的是 \(\min{(j-i)}\) ,使得 \(sum_i=\min_{k=1}^{j-1}{sum_i}\) , \(sum_j-sum_i\ge S\) ,即 \(sum_i\le S-sum_j\) 。
对 \(sum\) 离散化,开一个权值线段树(或权值树状数组),对每个 \(j\) 在树上二分找到最后一个满足要求(\(\max i\) : \(sum_i\le S-sum_j\) )的位置。 (说详细点就是:在区间 \([1,S-sum_j]\) 内寻找最后一个存在的数,权值线段树可以记录区间存在的数的个数来判断,树状数组上二分的方法详见 \(2022.10.25\ 测试\ T2\ 题解\) )。
Main Code:
//Later to complete