1、最大子序和
集合
f[i]
:表示以i
为右端点,长度不超过m
的连续子区间的区间总和最大
集合划分
以i
的取值来划分。即 i = 0 , 1 , . . . , k , . . . n i = {0, 1, ..., k, ... n} i=0,1,...,k,...n,求每一类的最大值,然后取一个整体的最大值即可。
若以 i = k i = k i=k的时候, f ( k ) f(k) f(k)表示以 A [ k ] A[k] A[k]结尾的,长度不超过 m m m的连续子序列和的最大值。所以可以枚举长度分别为 j = 1 、 2 、 3 、 . . . 、 m j =1、2、3、...、m j=1、2、3、...、m的以 A [ k ] A[k] A[k]结尾的连续子序列的和,取一个最大值即可。
求一段区间的和,可以使用前缀和来求,如上面例子中,任意一段长度为 j j j的区间和就表示为 s [ k ] − s [ k − j ] s[k] - s[k-j] s[k]−s[k−j],又因为 s [ k ] s[k] s[k]固定,所以有 f [ k ] = m a x ( s [ k ] − s [ k − j ] ) = s [ k ] − m i n ( s [ k − j ] ) f[k] = max(s[k]-s[k-j])=s[k]-min(s[k-j]) f[k]=max(s[k]−s[k−j])=s[k]−min(s[k−j])其中 j ∈ [ 1 , m ] j\in[1,m] j∈[1,m],也就是在长度为 m m m的区间中找一个最小值,这里可以使用单调队列来求解。
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 300010, INF = 0x3f3f3f3f;
int n, m;
int s[N], q[N]; // s前缀和,q单调队列(队列中存储的是下标)
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i ++ )
{
scanf("%d", &s[i]);
s[i] += s[i - 1];
}
int res = -INF; // 序列长度最少是0
int hh = 0, tt = 0; // 最初队列非空,存储了一个0
for (int i = 1; i <= n; i ++ )
{
if (q[hh] < i - m) hh ++ ; // 队头已经滑出
res = max(res, s[i] - s[q[hh]]);
while (hh <= tt && s[q[tt]] >= s[i]) tt -- ; // 队头存储的是最小值
q[ ++ tt] = i;
}
printf("%d\n", res);
return 0;
}
2、旅行问题
环形问题,首先破环成链,形成长度为 2 × n 2\times n 2×n的链。根据题目中的三个条件:
- 会先在一个点获得油量
- 然后消耗油量到达下一个点
- 消耗后的油量不能小于 0 0 0
顺时针方向
使用前缀和思想:图中每个点 i i i表示从 i i i点加 o [ i ] o[i] o[i]的油,再消耗 d [ i ] d[i] d[i]的油(一公里一升油)所剩下的油量,即 o [ i ] − d [ i ] o[i]-d[i]