题解【SP1043】 GSS1 - Can you answer these queries I

题目描述

You are given a sequence \(A_1, A_2, ..., A_n(|A_i|≤15007,1≤N≤50000)\).

A query is defined as follows:

\(Query(x,y) = Max(a_i+a_{i+1}+...+a_j;x≤i≤j≤y)\).

Given \(M\) queries, your program must output the results of these queries.

输入输出格式

输入格式

  • The first line of the input file contains the integer \(N\).
  • In the second line, \(N\) numbers follow.
  • The third line contains the integer \(M\).
  • \(M\) lines follow, where line \(i\) contains \(2\) numbers \(x_i\) and \(y_i\).

输出格式

Your program should output the results of the \(M\) queries, one query per line.

输入输出样例

输入样例#1

3 
-1 2 3
1
1 2

输出样例#1

2

题意翻译

给出了序列 \(A_1,A_2,…,A_n(a_i≤15007,1≤N≤50000)\)

查询定义如下: 查询 \((x,y)=\max\{a_i+a_{i+1}+...+a_j;x≤i≤j≤y\}\)

给定\(M\)个查询,程序必须输出这些查询的结果,每行一个查询。

题解

\(SPOJ\)\(GSS\)系列一共有\(8\)题,这\(8\)道题目都是有关数据结构的,与\(Ynoi\)类似。

这是\(SPOJ\)\(GSS\)系列的第一题,考察的是用线段树求区间最大子段和 (本蒟蒻不会猫树)

众所周知,线段树有以下基本的\(3\)个操作:\(pushup\)\(bulid\)\(getans\),这\(3\)个操作分别对应合并区间、建树的求答案。

我们尝试用这三种操作解决这道题:

首先,我们定义一个结构体:

struct Node
{
	int sum, lans, rans, ans;
} t[50005 << 2];

其中,\(sum\)表示区间和,\(lans\)表示最大前缀和,\(rans\)表示最大后缀和,\(ans\)表示区间内的最大子段和,我们的目标是求出\(x\)~\(y\)区间内的\(ans\)

然后,我们分析,如何进行\(pushup\)操作。

易知,这个区间内的区间和就是它子区间的和加上它右子区间的和。

区间最大前缀和是它左子区间最大子段和,与左子区间和加上右子区间的最大前缀和的最大值,最大后缀和同理。

考虑如何合并区间最大子段和?

经过分析,我们得出:区间最大子段和就是\(max(\)左子区间的最大子段和,右子区间的最大子段和,
左子区间的最大后缀+右子区间的最大前缀和\()\)

综上,我们就得出了\(pushup\)的代码:

inline void pushup(int x)
{
	t[x].sum = t[x << 1].sum + t[(x << 1) | 1].sum;//求出区间和
	t[x].lans = max(t[x << 1].lans, t[x << 1].sum + t[(x << 1) | 1].lans);//区间最大前缀和
	t[x].rans = max(t[(x << 1) | 1].rans, t[(x << 1) | 1].sum + t[x << 1].rans);//区间最大后缀和
	t[x].ans = max(max(t[x << 1].ans, t[(x << 1) | 1].ans), t[x << 1].rans + t[(x << 1) | 1].lans);//区间最大子段和
}

\(build\)操作与普通线段树的\(build\)操作一模一样。

下面放出\(build\)操作的代码:

void bulid(int s, int o, int p)
{
	if (s == o)//已经是叶子节点
	{
		t[p].sum = t[p].lans = t[p].rans = t[p].ans = gi();//输入并初始化叶子节点的成员
		return;
	}
	int mid = (s + o) >> 1;//找出区间中点
	bulid(s, mid, p << 1);//递归左子区间
	bulid(mid + 1, o, (p << 1) | 1);//递归右子区间
	pushup(p);//合并区间
}

\(getans\)操作同理。

下面放出\(AC\)代码:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cctype>

using namespace std;

inline int gi()
{
    int f = 1, x = 0; char c = getchar();
    while (c < '0' || c > '9') { if (c == '-') f = -1; c = getchar();}
    while (c >= '0' && c <= '9') { x = x * 10 + c - '0'; c = getchar();}
    return f * x;
}

int n, m;
struct Node
{
	int sum, lans, rans, ans;
} t[50005 << 2];

inline void pushup(int x)//合并操作
{
	t[x].sum = t[x << 1].sum + t[(x << 1) | 1].sum;
	t[x].lans = max(t[x << 1].lans, t[x << 1].sum + t[(x << 1) | 1].lans);
	t[x].rans = max(t[(x << 1) | 1].rans, t[(x << 1) | 1].sum + t[x << 1].rans);
	t[x].ans = max(max(t[x << 1].ans, t[(x << 1) | 1].ans), t[x << 1].rans + t[(x << 1) | 1].lans);
}

void bulid(int s, int o, int p)//建树
{
	if (s == o)
	{
		t[p].sum = t[p].lans = t[p].rans = t[p].ans = gi();
		return;
	}
	int mid = (s + o) >> 1;
	bulid(s, mid, p << 1);
	bulid(mid + 1, o, (p << 1) | 1);
	pushup(p);
}

Node getans(int l, int r, int s, int o, int p)//求答案
{
	if (l <= s && r >= o)//如果包含区间
	{
		return t[p];//就直接返回
	}
	int mid = (s + o) >> 1;//求出中点
	if (l > mid) return getans(l, r, mid + 1, o, (p << 1) | 1);//如果左端点在中点右边,就递归右区间
	if (r <= mid) return getans(l, r, s, mid, p << 1);//如果右端点在中点左边,就递归左区间
	else 
	{
		Node ans, a, b;
		a = getans(l, r, s, mid, p << 1), b = getans(l, r, mid + 1, o, (p << 1) | 1);//求出左区间和右区间的各项参数
		ans.sum = a.sum + b.sum;
		ans.ans = max(max(a.ans, a.rans + b.lans), b.ans);
		ans.lans = max(a.lans, a.sum + b.lans);
		ans.rans = max(b.rans, b.sum + a.rans);//合并答案
		return ans;//最后返回答案
	}
}

int main()//进入主函数
{
	n = gi();//输入节点个数
	bulid(1, n, 1);//建树
	m = gi();//输入询问个数
	for (int i = 1; i <= m; i++)
	{
		int x = gi(), y = gi();
		printf("%d\n", getans(x, y, 1, n, 1).ans);//求出答案
	}
	return 0;//结束
}
posted @ 2019-07-04 09:45  csxsi  阅读(283)  评论(0编辑  收藏  举报