[UVALive - 3938] “Ray, Pass me the dishes!“ (线段树)

题目链接:“Ray, Pass me the dishes!”

对于刚接触线段树的我,一开始搞个这种难度的属实有点搞人心态,关键这题设的数据类型就很多,用线段树去维护三个值,然后还有很多细节要去处理,搞了一上午才搞出来,线段树还是做得少,得加把劲啊。

题意

给你一个长度为n的序列D,有m次查询。对于询问(l,r),需要我们找两个下标x和y,使a≤x≤y≤b,并且要求 D x + D x + 1 + . . . + D y {D_x+D_{x+1}+...+D_y} Dx+Dx+1+...+Dy的值尽可能大,如果多组满足,x和y应该尽可能小。

题解

由于这个题是带询问的,所以如果用动态规划解决每次查询的时间复杂度为O(r-l+1),那么总的时间复杂度为O(mn),很明显题目中n,m≤500000,用动·态规划是无法解决的。
此时就需要用一种数据结构来帮助我们解决问题——线段树。
首先我们来分析这个题的分治解法,将这个查询区间分为左右区间两部分,那么最优解无非就三种情况。

  1. 最优解在左区间
  2. 最优解在右区间
  3. 最优解在两个区间都有。

我们可以用线段树,每个结点来维护三个值,最大连续和(max_sub),最大前缀和(max_pre),最大后缀和(max_suf),同时由于本题要找最大和的区间,所以还得维护前缀的l和r,后缀的l和r,最优解的子区间l和r,以及当前区间的l和r。
所以在建树的过程中我们得对每一个值进行赋值处理。其中的细节我不多说,也并不复杂多想想就能想出来。
现在我们举个例子看线段树如何解决问题。n=64,查询区间为[20,50]。
那么查询区间为[20,50]的最优解子区间的左右端点有三种情况。

  1. 左右端点都在[20,32](线段树的左区间内),那么max_sub(20,50) = max _sub (20,32)。
  2. 左右端点都在[33,50](线段树的右区间内),那么max_sub(20,50) = max _sub (33,50)。
  3. 左端点在[20,32]内,右端点在[33,50]内,那么max_sub(20,50) = max_suf(20,32)+max_pre(33,50)。

建树的时间复杂度为O(n),查询时间复杂度为O(logn)。

代码
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<bitset>
#include<cassert>
#include<cctype>
#include<cmath>
#include<cstdlib>
#include<ctime>
#include<deque>
#include<iomanip>
#include<list>
#include<map>
#include<queue>
#include<set>
#include<stack>
#include<vector>
using namespace std;
//extern "C"{void *__dso_handle=0;}
typedef long long ll;
typedef long double ld;
#define fi first
#define se second
#define pb push_back
#define mp make_pair
#define pii pair<int,int>
#define lowbit(x) x&-x
const double PI=acos(-1.0);
const double eps=1e-6;
const ll mod=1e9+7;
const int inf=0x3f3f3f3f;
const int maxn=1e6+10;
const int maxm=2e6+10;
#define ios ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);

int n,a[maxn];
struct Tree
{
	int l,r;
	ll max_sub,max_pre,max_suf,sum;
	int sub_l,sub_r,pre_pos,suf_pos;
}tree[maxn];

void push_up(Tree& root,Tree& lchild,Tree& rchild)
{
	root.sum = lchild.sum + rchild.sum;
	if((lchild.sum+rchild.max_pre)>lchild.max_pre)
	{
		root.max_pre = (lchild.sum+rchild.max_pre);
		root.pre_pos = rchild.pre_pos;
	}
	else
	{
		root.max_pre = lchild.max_pre;
		root.pre_pos = lchild.pre_pos;
	}
	if((lchild.max_suf+rchild.sum)>=rchild.max_suf)
	{
		root.max_suf = (lchild.max_suf+rchild.sum);
		root.suf_pos = lchild.suf_pos;
	}
	else 
	{
		root.max_suf = rchild.max_suf;
		root.suf_pos = rchild.suf_pos;
	}

	root.max_sub = lchild.max_sub;
	root.sub_l = lchild.sub_l;
	root.sub_r = lchild.sub_r;
	if((lchild.max_suf+rchild.max_pre)>root.max_sub)
	{
		root.max_sub = (lchild.max_suf+rchild.max_pre);
		root.sub_l = lchild.suf_pos;
		root.sub_r = rchild.pre_pos;
	}
	if((lchild.max_suf+rchild.max_pre)==root.max_sub && root.sub_l>lchild.suf_pos)
	{
		root.sub_l = lchild.suf_pos;
		root.sub_r = rchild.pre_pos;
	}
	if(rchild.max_sub>root.max_sub)
	{
		root.max_sub = rchild.max_sub;
		root.sub_l = rchild.sub_l;
		root.sub_r = rchild.sub_r;
	}
}

void build(int root,int l,int r)
{
	tree[root].l=l;
	tree[root].r=r;
	if(l==r)
	{
		tree[root].sum = a[l];
		tree[root].max_sub=tree[root].max_pre=tree[root].max_suf=a[l];
		tree[root].sub_l=tree[root].sub_r=tree[root].pre_pos=tree[root].suf_pos=l;
		return;
	}
	int mid=l+(r-l)/2;
	build(root*2, l, mid);
	build(root*2+1, mid+1, r);
	push_up(tree[root],tree[root*2],tree[root*2+1]);
}
Tree query(int p,int l,int r)
{
	if((l==tree[p].l) && (r==tree[p].r)) return tree[p];
	int mid=tree[p].l+(tree[p].r-tree[p].l)/2;
	if(r<=mid) return query(p*2, l, r);
	if(l>mid) return query(p*2+1, l, r);
	Tree ans;
	Tree a1,a2;
	a1=query(p*2, l, mid); 
	a2=query(p*2+1, mid+1, r);
	ans.l = l,ans.r=r;
	push_up(ans, a1, a2);
	return ans;
}
int main()
{
	int n,q;
	int cas=0;
	while(~scanf("%d%d",&n,&q))
	{
		for(int i=1;i<=n;i++) scanf("%d",&a[i]);
		build(1, 1, n);
		printf("Case %d:\n",++cas);
		while(q--)
		{
			int l,r;
			scanf("%d%d",&l,&r);
			Tree ans=query(1, l, r);
			printf("%d %d\n",ans.sub_l,ans.sub_r);
		}
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值