AcWing 135. 最大子序和
题目传送门
一、暴力+前缀和
- 需要求某一个区间的连续子段和,因此需要用到前缀和
s[]
。 - 区间必然有终点,每个结点都可以作为终点,所以可以通过枚举每个结点作为终点,找出所有的情况。
- 对于任意的
s[i]
,求以i
结尾的区间长度不超过m
的最大连续和,等价于求s[i] - s[j - 1]
的最大值,其中'i - m < = j - 1 < = i - 1'
#include
using namespace std;
//Brute-force 暴力
const int N = 300010;
const int INF = 0x3f3f3f3f;
int s[N];
int res = -INF;
int n, m;
//TLE掉3个点
//通过了 11/14个数据
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> s[i], s[i] += s[i - 1];
//遍历每一个终点
for (int i = 1; i <= n; i++)
//从终点向前,找出所有区间在m之内的数字,通过前缀和计算出区间的累加和,保留最大值
for (int j = i; j && j > i - m; j--)
res = max(res, s[i] - s[j - 1]);
cout << res << endl;
return 0;
}
二、单调队列优化
因为当枚举每个结点i
时,可以视为i
是固定的,而j
是在区间i - m + 1 <= j <= i
内滑动,所以s[i]
是一个固定值,变化的其实是j
,即要求s[j - 1]
的最小值。滑动窗口的区间是[i - m,i - 1]
,单调队列维护的是该区间的最小值,由于滑动窗口不包含i
,因此res
需要在while
上方进行更新。
#include
using namespace std;
const int N = 300010;
const int INF = 0x3f3f3f3f;
int n; //n个数字
int m; //连续长度为m
int q[N]; //单调队列,记录的是序号
int s[N]; //前缀和
int main() {
//优化输入
ios::sync_with_stdio(false);
cin >> n >> m;
//前缀和
for (int i = 1; i <= n; i++) cin >> s[i], s[i] += s[i - 1];
//滑动窗口:i前面(不包括i)长度为m的窗口
//单调队列一般将q中初始化一个0进去(tt=0),防止q队列中没有数据,导致整体逻辑失败。
int hh = 0, tt = 0;
int res = -INF; //预求最大,先设最小
for (int i = 1; i <= n; i++) { //遍历每一个结点
if (q[hh] < i - m)hh++; //如果队列头已经距离i结点大于m个长度,队列头结点需要出队列
res = max(res, s[i] - s[q[hh]]); //通过前缀和计算,获取此时区间和的值,试图更新最大值.其实是sum([队列头+1,i])
while (hh <= tt && s[q[tt]] >= s[i]) tt--;//单调队列,左侧最小
q[++tt] = i; //加入队列
}
//输出结果
printf("%d", res);//是前缀和,就要小心会不会爆int,但此题n,m上限只有300000,即使是n*m也不会爆int,不用开long long
return 0;
}