例题3.9 动态最大连续和 UVa1400

1.题目描述:点击打开链接

2.解题思路:本题利用线段树解决。事先构造一棵线段树,在每个线段树的结点中维护三个成员变量:max_sub,max_prefix,max_suffix。设此时线段树结点为x,左右端点分别为l,r

  • max_sub:区间连续最大和
  • max_prefix:区间前缀最大和
  • max_suffix:区间后缀最大和

为了同时知道上述三个最大和的左右端点,令它们都是一个结构体segment,成员变量是l,r,val(左端点l,右端点r,最大和val)。那么现在问题的关键是如何查找给定区间[a,b]中这个max_sub。

利用分治法的思想:假设目前为结点u,左右端点分别是L,R,中点是M。那么分三种情况:(1)起点和终点都在左子结点u*2中,那么max_sub(a,b)就是左子结点中的max_sub。

(2)起点和终点都在右子结点u*2+1中,那么max_sub(a,b)就是右子结点中的max_sub。(3)起点在左子结点中,终点在右子结点中,那么max_sub(a,b)就是左子结点的max_suffix和右子结点的max_prefix之和(注意要重载segment结构体中的’+‘)。最后取这三种情况的最大者即可(注意要重载’<‘)。

同理也可以像上述分析那样递推得到max_prefix,max_suffix,具体过程见代码注释。整个过程中建树的时间复杂度是O(N),查询的时间复杂度是O(logN)。

3.代码:

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<algorithm>
#include<string>
#include<sstream>
#include<set>
#include<vector>
#include<stack>
#include<map>
#include<queue>
#include<deque>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<ctime>
#include<functional>
using namespace std;

#define N 500000+10
#define INF 100000000
#define lson(x) ((x)<<1)//左子结点编号
#define rson(x) (((x)<<1)+1)//右子结点编号
typedef long long LL;
struct segment
{
	int l, r;//左右端点
	LL val;
	segment(int l = 0, int r = 0, LL val = 0){ this->set(l, r, val); }
	void set(int l, int r, LL val)
	{
		this->l = l;
		this->r = r;
		this->val = val;
	}
	segment operator +(const segment&u)//重载加号,获得合并后的区间及区间连续和
	{
		return segment(min(l, u.l), max(r, u.r), val + u.val);
	}
	bool operator <(const segment&u)const//由于下面要取最大值,因此要重载小于号
	{
		if (val != u.val)return val < u.val;//用max函数时取以下三种情况右边的值
		if (l != u.l)return l>u.l;//优先让左端点最小
		return r>u.r;
	}
};
struct Node
{
	int l, r;
	segment max_sub, max_prefix, max_suffix;
}node[N*4];

int n, m;
LL arr[N], s[N];

Node seg_push(Node a, Node b)//维护满足题意的区间
{
	Node ret;
	LL suml = s[a.r] - s[a.l - 1];//a结点的连续和
	LL sumr = s[b.r] - s[b.l - 1];//b结点的连续和
	ret.l = a.l;
	ret.r = b.r;
	ret.max_sub = max(a.max_suffix + b.max_prefix, max(a.max_sub, b.max_sub));//取三种情况的最大值:(1)起点在结点a,终点在结点b中;(2)起点和终点都在结点a中;(3)起点和终点都在结点b中;
	ret.max_prefix = max(a.max_prefix, segment(a.l, a.r, suml) + b.max_prefix);//取两种情况的最大值:(1)终点仍在结点a中;(2)终点在结点b中
	ret.max_suffix = max(b.max_suffix, a.max_suffix + segment(b.l, b.r, sumr));//取两种情况的最大值:(1)起点仍在结点b中;(2)起点在结点a中
	return ret;
}

void build_segtree(int root, int l, int r)
{
	if (l == r)//叶子结点
	{
		node[root].l = node[root].r = l;
		node[root].max_sub.set(l, r, (LL)arr[l]);
		node[root].max_prefix.set(l, r, (LL)arr[l]);
		node[root].max_suffix.set(l, r, (LL)arr[l]);
		return;
	}
	int mid = (l + r) / 2;
	build_segtree(lson(root), l, mid);//递归建树
	build_segtree(rson(root), mid + 1, r);
	node[root] = seg_push(node[lson(root)], node[rson(root)]);//node[root]为满足题意的区间
}
Node query(int root, int l, int r)
{
	if (l <= node[root].l&&node[root].r <= r)//待查询区间完全包含了编号为root的线段
		return node[root];
	int mid = (node[root].l + node[root].r) / 2;
	if (l <= mid&&r > mid)//起点在mid左侧,终点在mid右侧
		return seg_push(query(lson(root), l, r), query(rson(root), l, r));
	else if (r <= mid)//起点和终点都在mid左侧
		return query(lson(root), l, r);
	else//起点和终点都在mid右侧
		return query(rson(root), l, r);
}
int main()
{
	freopen("t.txt", "r", stdin);
	int rnd = 1;
	while (~scanf("%d%d", &n, &m))
	{
		s[0] = 0;
		for (int i = 1; i <= n; i++)
		{
			scanf("%lld", &arr[i]);
			s[i] = s[i - 1] + arr[i];//s[i]计算前i个的和
		}
		build_segtree(1, 1, n);//建立线段树,根结点编号为1
		printf("Case %d:\n", rnd++);
		int l, r;
		while (m--)
		{
			scanf("%d%d", &l, &r);
			Node u = query(1, l, r);//从根结点开始查询线段[l,r],返回满足题意的区间
			printf("%d %d\n", u.max_sub.l, u.max_sub.r);
		}
	}
	return 0;
}



时间: 2024-11-08 14:38:28

例题3.9 动态最大连续和 UVa1400的相关文章

UVALive 3938 Ray, Pass me the dishes! (动态最大连续和)

题意:求一个动态区间的最大连续和. 静态版本的O(n)算法显示不适用了,但是可以用线段树分治,因为一个连续和要在两边的区间,要么跨越两边,对于一个结点维护最大前缀和,后缀和,子区间连续和. 题目要求输出区间,所以还要保存连续和最大的区间,以及前缀和,后缀和的位置.为了维护最大前缀和以及后缀和还需要一个区间和. 写的时候稍微麻烦一点,更新写成一个函数会方便很多.还好一遍过了... #include<bits/stdc++.h> using namespace std; const int max

UVALive - 3938 分治,线段树,求动态最大连续和

UVALive - 3938 题意: 给出一个长度为n的整数序列D,你的任务是对m个询问作出回答.对于询问(a,b),需要找到两个下标x和y,使得a≤x≤y≤b,并且Dx+Dx+1+...+Dy尽量大.如果有多组满足条件的x和y,x应该尽量小.如果还有多解,y应该尽量小. tags: 分治思想,线段树       大白书201 区别于静态最大连续和,只能用分治算法: 最优解要么完全在左半序列,要么完全在右半序列,要么跨越中点. 我们构造一棵线段树,维护 3 个值:最大连续和 max_sub ,最

一个动态库连续注册的windows脚本regsvr32

cmd ->for %1 in (%windir%\system32\*.dll) do regsvr32.exe /s %1

动态内存分配与指向它的指针变量

1.动态内存分配的含义 c语言允许建立动态内存分配区域,以存放一些临时用的数据,这些数据不必再程序的声明部分定义,也不必等到函数结束时才释放,而是要随时开辟,不需要随时释放,这些数据是临时存放在一个特定的自由存储区(堆),可以根据需要向系统申请所需要大小的空间,由于未在声明部分定义它们为变量或数组,因此不能通过变量名或数组名去引用这些数据,只能通过指针来引用. 2.建立内存的动态分配 对内存的动态分配是通过系统提供的函数库来实现的,主要有malloc,calloc,free,realloc这四个

基于三维GIS技术的矢量地图动态LOD渲染方法研究现状

“地图是人类文化的杰作,它融科学.艺术于一体,作为描述.研究人类生存环境的一种信息载体是人类生产与生活中不可缺少的一种工具.”这是陈述彭院士为<中国地图学年鉴>作序的开场语.Taylor也曾指出“当涉及应用人脑来识别空间联系中的模式与相互关系时,地图学的认知方法是唯一的过程”.地图存在于我们生活的方方面面,应用广泛且己经产生了巨大的社会效应和经济效益,其重要性不言而喻.地图是地图可视化的结果,地图可视化作为现代地图学的核心一直是三维GIS的研究热点.地图可视化将电子设备的视觉传输能力和人类的视

九、c++容器

9.1 简介 容器库是类模板与算法的汇集,允许程序员简单地访问常见数据结构,例如队列.链表和栈. 有三类容器--顺序容器.关联容器和无序关联容器--每种都被设计为支持不同组的操作. 顺序容器:顺序容器实现能按顺序访问的数据结构. vector :向量,动态的连续数组 deque :双端队列 list :双链表 stack :栈,适配一个容器以提供栈(LIFO 数据结构) queue :队列,适配一个容器以提供队列(FIFO 数据结构) priority_queue :优先队列,适配一个容器以提供

实用数据结构---树状数组(二叉索引树)

树状数组适用于动态连续和查询问题,就是给定一个区间, 查询某一段的和或者修改某一位置的值. 关于树状数组的结构请去百度百科,否则将看不懂下面内容 我们看这个题 士兵杀敌(二) 时间限制:1000 ms  |  内存限制:65535 KB 难度:5 描述 南将军手下有N个士兵,分别编号1到N,这些士兵的杀敌数都是已知的. 小工是南将军手下的军师,南将军经常想知道第m号到第n号士兵的总杀敌数,请你帮助小工来回答南将军吧. 南将军的某次询问之后士兵i可能又杀敌q人,之后南将军再询问的时候,需要考虑到新

Vijos1881闪烁的繁星 [线段树]

P1881闪烁的繁星 背景 繁星闪烁着--深蓝的太空何曾听得见他们对语沉默中微光里他们深深的互相颂赞了 描述 繁星, 漫天的繁星.繁星排成一列, 我数一数呀, 一共有N只小星星呢. 星星们是听话的好孩子, 小岛在指挥它们跳舞呢.舞蹈开始前, 它们都亮了起来! 小岛指一指第i只小星星, 只见第i只小星星立刻改变了自己的状态.如果它之前是亮着的, 那么立刻就灭掉了.如果它之前是灭掉的, 现在就立刻亮了呀! 如果说, 可以有连续若干只小星星.其中任意相邻两只星星状态不同.那就是最美的了. 小岛希望知道

整体二分初步

部分内容引自myt论文:树状数组延伸和离线优化(CDQ.整体二分和莫队) 大致思路 1.确定答案范围[L,R],mid=L+R>>1;2.算出答案在[L,mid]内的操作对答案在[mid+1,R]内的操作的贡献;3.将答案在[L,mid]内的操作放入队列Q1,solve(Q1,L,mid)将答案在[mid+1,R]内的操作放入Q2,solve(Q2,mid+1,R) 伪代码 divide(hd,tl,l,r){ if(hd>tl) exit if(l==r){ for(i->hd