【解题报告】UVALive 3938 线段树深入使用

这道题说给n个数,每一次给一段区间求区间最大子段和。简单分析吧,求区间最大子段和一般使用dp,但是这题的查询最多可以有50W个,每次都重新算必定超时。那么就想着要把数据存下来,那么存什么呢?传统的线段树每一个节点会存当前区间我们需要的值,比如max值sum值等,但是在这一题中,我们需要的是求最大子段和的值,但是我们没有必要把他存在节点中。为什么呢?因为查询区间极大概率不能使用一个节点来表示,那么当我需要把两个节点合并求区间最大子段和时,存的最大子段和就失去了其意义。因为不能保证两段节点最大子段和区间是相接的。

因此,在这道题中,我们使用一个sum数组,表示到i为止所有数的和,那么每一段区间的子段和就可以用sum[r]-sum[l-1]来表示。那么还有另一个问题,每一个节点应该存什么呢?每一个节点应该存取该节点所代表区间的最大子段和区间,前缀区间的末尾和后缀区间的开始。为什么要存这些东西呢?一段区间的最大子段和可能是左子树的最大子段和,右子树的最大子段和,或者左子树的后缀加上右子树的前缀。明白了需要存什么,剩下的问题就比较好解决了。

最后,在构建这颗线段树的时候,有什么需要注意的呢?在回溯构造父节点,进行区间合并的时候,需要注意,并不是“如果(左子树的最大前缀和长度==左子树的长度 && 右子树的前缀和>0)就进行合并”,以构建父节点的前缀和为例,正确的式子为:

if(左子树的区间和+右子树的前缀和>左子树的前缀和)

父节点的前缀结尾=右子树的前缀结尾的点

else

父节点的前缀结尾=左子树的前缀结尾点

比如说,左子树为 5 2 -3 而右子树为  8 4 -3,那么按照错误的式子,应该是不进行合并的。但是我们可以发现,左子树的和为4加上右子树的前缀和为4+12=16>7,所以应当进行合并。

#include 
using namespace std
#define maxn 500009
#define lson n << 1
#define rson n << 1 | 1
typedef long long ll;
typedef pair PAIR;
struct node {
	ll l, r, pl, pr;//pl前趋的终止,pr后缀的开始
	PAIR sub;
}seg[maxn << 2];
ll box[maxn], sum[maxn];
ll csum(ll l, ll r) { return sum[r] - sum[l - 1]; }
ll csum(PAIR n) { return sum[n.second] - sum[n.first - 1]; }
PAIR cmax(PAIR a, PAIR b)
{
	if (csum(a) != csum(b))return csum(a) > csum(b) ? a : b;
	return a < b ? a : b;
}
void build(ll n, ll l, ll r)
{
	seg[n].l = l;seg[n].r = r;
	if (l == r)
	{
		sum[l] = box[l] + sum[l - 1];
		seg[n].pl = seg[n].pr = l;
		seg[n].sub = make_pair(l, r);
		return;
	}
	ll mid = (l + r) >> 1;
	build(lson, l, mid);
	build(rson, mid + 1, r);
	seg[n].pl = csum(l, seg[rson].pl) > csum(l, seg[lson].pl) ? seg[rson].pl : seg[lson].pl;
	seg[n].pr = csum(seg[lson].pr, r) >= csum(seg[rson].pr, r) ? seg[lson].pr : seg[rson].pr;
	seg[n].sub = cmax(seg[lson].sub, cmax(seg[rson].sub, make_pair(seg[lson].pr, seg[rson].pl)));
}
PAIR pre_query(ll n, ll l, ll r)
{
	if (seg[n].pl <= r)return make_pair(seg[n].l, seg[n].pl);
	ll mid = (seg[n].l + seg[n].r) >> 1;
	if (r <= mid)return pre_query(lson, l, r);
	PAIR p1 = pre_query(rson, l, r);
	p1.first = seg[n].l;
	return cmax(p1, make_pair(seg[n].l, seg[lson].pl));
}
PAIR suf_query(ll n, ll l, ll r)
{
	if (seg[n].pr >= l)return make_pair(seg[n].pr, seg[n].r);
	ll mid = (seg[n].l + seg[n].r) >> 1;
	if (l > mid)return suf_query(rson, l, r);
	PAIR p1 = suf_query(lson, l, r);
	p1.second = seg[n].r;
	return cmax(p1, make_pair(seg[rson].pr, seg[n].r));
}
PAIR query(ll n, ll l, ll r)
{
	if (l <= seg[n].l && seg[n].r <= r)
		return seg[n].sub;
	ll mid = (seg[n].l + seg[n].r) >> 1;
	if (l > mid)return query(rson, l, r);
	if (r <= mid)return query(lson, l, r);
	PAIR p2 = pre_query(rson, l, r);
	PAIR p3 = suf_query(lson, l, r);
	PAIR p1 = cmax(query(lson, l, r), query(rson, l, r));
	return cmax(p1, make_pair(p3.first, p2.second));
}
int main()
{
	ll n, m, Case = 1, ql, qr;
	while (cin >> n >> m)
	{
		printf("Case %lld:\n", Case++);
		memset(seg, 0, sizeof(seg));
		memset(sum, 0, sizeof(sum));
		for (ll i = 1;i <= n;i++)
			scanf_s("%lld", &box[i]);
		build(1, 1, n);
		while (m--)
		{
			scanf_s("%lld%lld", &ql, &qr);
			PAIR x = query(1, ql, qr);
			printf("%lld %lld\n", x.first, x.second);
		}
	}
	return 0;
}

你可能感兴趣的:(ACM学习)