动态区间最大子序和问题
【问题描述】
给出一个序列A[0..N-1]和M个操作,每个操作都是以下三种之一:
①:查询区间最大子序和操作
格式:1 l r
表示:查询A[l..r]内的最大子序和(就是A[l..r]内的和最大的连续子序列的和),0<=l<=r<N;
②:修改单个值操作
格式:2 i x
表示:将A[i]的值改为x,0<=i<N;
③:修改整段值操作
格式:3 l r x
表示:将A[l..r]的值全部改为x,0<=l<=r<N。
【具体题目】TYVJ1427(只支持前两个操作)
【分析】
由于本题是对区间进行的操作,很自然的想到线段树,但是线段树的结点该记录哪些信息?
首先,区间内最大子序和的值是一定要维护的,将其记为midmax。然后,由于该区间的和最大的连续子序列可能完全位于其左半段中或右半段中,也可能跨越左半段和右半段,并且在跨越的时候,这个最大子序列必然是跨越了左半段的右端和右半段的左端,所以对于结点还需要维护两个值:区间左端最大子序和的值(就是从该区间最左元素开始的连续子序列的最大和,记为lmax)和区间右端最大子序和的值(就是在该区间最右元素终止的连续子序列的最大和,记为rmax),可以发现,只记录这三个值还不能进行维护,还需要记录一个值sum,表示该区间内所有元素值的和,这时,可以进行维护了:
p->sum = p->lch->sum + p->rch->sum;
p->lmax = max(p->lch->lmax, p->lch->sum + p->rch->lmax);
p->rmax = max(p->lch->rmax, p->rch->sum + p->lch->rmax);
p->midmax = max(p->lch->midmax, p->rch->midmax, p->lch->rmax + p->rch->lmax);
边界(对于叶结点):
p->sum = p->lmax = p->rmax = p->midmax = x;(x是该叶结点的元素值)
然后,考虑各个操作:
①:查询区间最大子序和操作
很明显,要将A[l..r]表示成若干个结点区间的并。设这些结点从左到右依次为p[1]、p[2]……p[S]。
设F1[i]为p[1..i]中
可继续延伸的最大子序和(就是该子序列在p[i]的右端终止),F0[i]为p[1..i]中
不可继续延伸的最大子序和(就是该子序列不在p[i]的右端终止),则
F0[i] = max(p[i]->midmax, F1[i - 1] + p[i]->lmax);
F1[i] = max(p[i]->rmax, F1[i - 1] + p[i]->sum);
边界:F0[0] = F1[0] = 0;
然后,取F0、F1中出现的最大值,就是本次查询的结果。
具体实现时,不需保存所有F0、F1的值,可用迭代实现(因为F0、F1均只和上一阶段的F1值有关)。
②:修改单个值操作
这个很容易实现,只需直接修改即可,注意改完后,该叶结点的所有上层结点的sum、lmax、rmax、midmax值都要重新维护。
③:修改整段值操作
这个比操作②稍难一点,但其实也很容易,只要引入一个标记即可。每当某结点被打上标记x(表示该结点被整体赋值为x)操作之后,将其sum值改为(r-l+1)*x,lmax、rmax和midmax值则需要视x的符号而定:若x>=0,lmax=rmax=midmax=sum;若x<0,lmax=rmax=midmax=x。剩下的就是维护标记了。
总之,这个模型是一个很好的线段树模型,思考难度适中,但真正实现起来又不是很容易……
代码(TYVJ1427,时间贼长,用ZKW树的神犇不要鄙视):
#include <iostream>
#include <stdio.h>
using namespace std;
#define re(i, n) for (int i=0; i<n; i++)
const int MAXN = 500000, INF = ~0U >> 2;
struct tree {
int l, r, sum, lmax, rmax, midmax;
tree *lch, *rch;
} *t0 = 0;
int n, st[MAXN], a, b, f0, f1, res;
inline int max(int s1, int s2) {return s1 >= s2 ? s1 : s2;}
inline int max(int s1, int s2, int s3) {
int max0 = max(s1, s2);
return max0 >= s3 ? max0 : s3;
}
void mkt(tree *&t1, int l0, int r0)
{
t1 = new tree;
t1->l = l0; t1->r = r0; t1->lch = t1->rch = 0;
if (l0 == r0) t1->sum = t1->lmax = t1->rmax = t1->midmax = st[l0]; else {
int mid = l0 + r0 >> 1;
mkt(t1->lch, l0, mid); mkt(t1->rch, mid + 1, r0);
t1->sum = t1->lch->sum + t1->rch->sum;
t1->lmax = max(t1->lch->lmax, t1->lch->sum + t1->rch->lmax);
t1->rmax = max(t1->rch->rmax, t1->lch->rmax + t1->rch->sum);
t1->midmax = max(t1->lch->midmax, t1->rch->midmax, t1->lch->rmax + t1->rch->lmax);
}
}
void opr1(tree *t1)
{
int l0 = t1->l, r0 = t1->r;
if (l0 > b || r0 < a) return;
if (l0 >= a && r0 <= b) {
f0 = max(t1->midmax, f1 + t1->lmax);
f1 = max(t1->rmax, f1 + t1->sum);
if (f0 > res) res = f0;
if (f1 > res) res = f1;
return;
}
opr1(t1->lch); opr1(t1->rch);
}
void opr2(tree *&t1)
{
int l0 = t1->l, r0 = t1->r;
if (l0 > a || r0 < a) return;
if (l0 == r0) {
t1->sum = t1->lmax = t1->rmax = t1->midmax = b;
return;
}
opr2(t1->lch); opr2(t1->rch);
t1->sum = t1->lch->sum + t1->rch->sum;
t1->lmax = max(t1->lch->lmax, t1->lch->sum + t1->rch->lmax);
t1->rmax = max(t1->rch->rmax, t1->lch->rmax + t1->rch->sum);
t1->midmax = max(t1->lch->midmax, t1->rch->midmax, t1->lch->rmax + t1->rch->lmax);
}
void solve()
{
int m, x;
scanf("%d%d", &n, &m);
re(i, n) scanf("%d", &st[i]);
mkt(t0, 0, n - 1);
re(i, m) {
scanf("%d%d%d", &x, &a, &b); a--;
if (x == 1) {
if (a > --b) {int tmp = a; a = b; b = tmp;}
f0 = f1 = 0; res = -INF;
opr1(t0);
printf("%d\n", res);
} else opr2(t0);
}
}
int main()
{
solve();
return 0;
}
【扩展1】动态区间最大空当问题:
一个01序列(就是每个元素都是0或1的序列),一开始为全0,三个操作:
将一段全0的连续子序列改为全1;
‚将一段全1的连续子序列改为全0;
ƒ求整个序列的最大空当(就是长度最大的全0连续子序列的长度)
(其实第
ƒ个操作是可以加强的:求一段给定区间内的最大空当,这时就需要按照上面的动态区间最大子序和问题一样,分别处理了)
【具体题目】
PKU1823有了上一个模型,这个模型直接秒掉(类似地搞即可)
代码:
#include <iostream>
#include <stdio.h>
using namespace std;
#define re(i, n) for (int i=0; i<n; i++)
struct tree {
int l, r, len, lfe, rte, mide, mk;
tree *lch, *rch;
} *t0 = 0;
int n, a, b, res = 0;
bool mk0;
inline int max(int s1, int s2, int s3) {
int s0 = s1 >= s2 ? s1 : s2;
return s0 >= s3 ? s0 : s3;
}
void mkt(tree *&t1, int l0, int r0)
{
t1 = new tree;
t1->l = l0; t1->r = r0; t1->lfe = t1->rte = t1->mide = t1->len = r0 - l0 + 1; t1->mk = -1; t1->lch = t1->rch = 0;
if (l0 < r0) {int mid = l0 + r0 >> 1; mkt(t1->lch, l0, mid); mkt(t1->rch, mid + 1, r0);}
}
void solve(tree *&t1)
{
int l0 = t1->l, r0 = t1->r;
if (l0 > b || r0 < a) return;
if (l0 >= a && r0 <= b) {
t1->mk = mk0;
if (mk0) t1->lfe = t1->rte = t1->mide = 0; else t1->lfe = t1->rte = t1->mide = t1->len;
return;
}
if (!t1->mk) {
t1->mk = -1;
t1->lch->mk = 0; t1->lch->lfe = t1->lch->rte = t1->lch->mide = t1->lch->len;
t1->rch->mk = 0; t1->rch->lfe = t1->rch->rte = t1->rch->mide = t1->rch->len;
}
if (t1->mk == 1) {
t1->mk = -1;
t1->lch->mk = 1; t1->lch->lfe = t1->lch->rte = t1->lch->mide = 0;
t1->rch->mk = 1; t1->rch->lfe = t1->rch->rte = t1->rch->mide = 0;
}
solve(t1->lch); solve(t1->rch);
if (t1->lch->lfe == t1->lch->len) t1->lfe = t1->lch->len + t1->rch->lfe; else t1->lfe = t1->lch->lfe;
if (t1->rch->rte == t1->rch->len) t1->rte = t1->lch->rte + t1->rch->len; else t1->rte = t1->rch->rte;
t1->mide = max(t1->lch->mide, t1->rch->mide, t1->lch->rte + t1->rch->lfe);
}
int main()
{
int m, x;
scanf("%d%d", &n, &m);
mkt(t0, 0, n - 1);
re(i, m) {
scanf("%d", &x);
if (x == 1) {
mk0 = 1;
scanf("%d%d", &a, &b); b += --a; b--;
solve(t0);
}
if (x == 2) {
mk0 = 0;
scanf("%d%d", &a, &b); b += --a; b--;
solve(t0);
}
if (x == 3) printf("%d\n", t0->mide);
}
return 0;
}