UVALive - 3938 "Ray, Pass me the dishes!" 线段树

题目大意:给你一个N个数字的序列和M个问题,问题的内容是给出一个区间[x,y],然后求出这个区间的满足x <= a <= b <= y,且Da+ Da+1 + ...+ Db的和尽量大,如果有多组满足条件的,a,b就尽量小

解题思路:这题的线段树比较复杂,以前的线段树只维护一个值而已,现在的线段树维护的是三个值,前缀和pre,连续和sub,后缀和suf,要求区间[x,y]的满足条件的a,b无非有三种情况,假设现在的区间时的[l,r],他的两个子区间分别为,左子区间[l,mid],右子区间[mid+1,r]

1.所给区间[x,y]在左子区间,满足条件的a,b就等于max_sub(l,mid)

2.所给区间[x,y]在右子区间,满足条件的a,b就等于max_sub(mid+1,r)

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn = 500010 << 2;
long long sum[maxn], sub[maxn], pre[maxn], suf[maxn];
int sub_s[maxn], sub_e[maxn];
int pre_s[maxn], pre_e[maxn];
int suf_s[maxn], suf_e[maxn];

struct tree_node{
	long long pre_v, sub_v, suf_v, sum;
	int pr_s, pr_e, sb_s, sb_e, sf_s, sf_e;
};

void push_up(int u) {

	int l = u * 2;
	int r = u * 2 + 1;
	sum[u] = sum[l] + sum[r];
	long long MAX = pre[l];
	//pre 如果左边的和加上右边的前缀大于以前的前缀
	int start = pre_s[l], end = pre_e[l];
	if(pre[l] < sum[l] + pre[r]) {
		MAX = sum[l] + pre[r];
		start = pre_s[l];
		end = pre_e[r];
	}
	pre[u] = MAX; pre_s[u] = start; pre_e[u] = end;
//后缀,如果右边的和加上左边的后缀大于等于以前的后缀
	MAX = suf[r]; start = suf_s[r] ; end = suf_e[r];
	if( suf[r] <= sum[r] + suf[l]) {
		MAX = sum[r] + suf[l];
		start = suf_s[l];
		end = suf_e[r];	
	}
	suf[u] = MAX; suf_s[u] = start; suf_e[u] = end;
//连续和,不是左边或右边的连续和,就是中间的
	MAX = sub[l]; start = sub_s[l]; end = sub_e[l];
	if(MAX < suf[l] + pre[r]) {
		MAX = suf[l] + pre[r];
		start = suf_s[l];
		end = pre_e[r];	
	}

	if(MAX < sub[r])  {
		MAX = sub[r];
		start = sub_s[r];
		end = sub_e[r];	
	}
	sub[u] = MAX; sub_s[u] = start; sub_e[u] = end;
}

void build(int u, int l , int r) {
	if(l == r) {
		scanf("%lld",&sum[u]);
		sub[u] = pre[u] = suf[u] = sum[u];
		sub_s[u] = sub_e[u] = l;
		pre_s[u] = pre_e[u] = l;
		suf_s[u] = suf_e[u] = l;
		return ;
	}
	int mid = (l + r) / 2;
	build(2 * u, l, mid);
	build(2 * u + 1, mid + 1, r);
	push_up(u);
}

tree_node query(int l, int r,int u, int L, int R) {
	if(L <= l && r <= R) {
		tree_node tn;
		tn.pre_v = pre[u]; tn.sub_v = sub[u]; tn.suf_v = suf[u];
		tn.pr_s = pre_s[u]; tn.pr_e = pre_e[u];
		tn.sb_s = sub_s[u]; tn.sb_e = sub_e[u];
		tn.sf_s = suf_s[u]; tn.sf_e = suf_e[u];
		tn.sum = sum[u];
		return tn;	
	}
	int left = 0, right = 0;
	int mid = (l + r) / 2;
	tree_node t1, t2;
	if(L <= mid) {
		t1 = query(l,mid,2*u,L,R);	
		left = 1;
	}
	if(R > mid) {
		t2 = query(mid+1,r,2*u+1,L,R);	
		right = 1;
	}
	tree_node tn;
	if(left && !right) 
		tn = t1;
	if(!left && right)
		tn = t2;
	if(left && right) {
		//前缀和
		tn.pre_v = t1.pre_v; tn.pr_s = t1.pr_s; tn.pr_e = t1.pr_e;
		if(tn.pre_v < t1.sum + t2.pre_v) {
			tn.pre_v = t1.sum + t2.pre_v;
			tn.pr_s = t1.pr_s;
			tn.pr_e = t2.pr_e;	
		}

		tn.suf_v = t2.suf_v; tn.sf_s = t2.sf_s; tn.sf_e = t2.sf_e;
		if(tn.suf_v <= t2.sum + t1.suf_v) {
			tn.suf_v = t2.sum + t1.suf_v;
			tn.sf_s = t1.sf_s;
			tn.sf_e = t2.sf_e;	
		}	

		tn.sub_v = t1.sub_v; tn.sb_s = t1.sb_s; tn.sb_e = t1.sb_e;
		if(tn.sub_v < t1.suf_v + t2.pre_v) {
			tn.sub_v = t1.suf_v + t2.pre_v;
			tn.sb_s = t1.sf_s;
			tn.sb_e = t2.pr_e;	
		}
		if(tn.sub_v < t2.sub_v) {
			tn.sub_v = t2.sub_v;
			tn.sb_s = t2.sb_s;
			tn.sb_e = t2.sb_e;	
		}
		tn.sum = t1.sum + t2.sum;
	}
	return tn;
}

int main() {
	int N, M, mark = 1;
	while(scanf("%d%d",&N, &M) == 2) {
		build(1,1,N);
		printf("Case %d:\n",mark++);
		int t1, t2;
		for(int i = 0; i < M; i++) {
			scanf("%d%d",&t1,&t2);
			tree_node t3 = query(1,N,1,t1,t2);
			printf("%d %d\n",t3.sb_s, t3.sb_e);
		}
	}
	return 0;
}


3.x在区间[l,mid]中,y在区间[mid+1,r]中,那么a,b就等于(max_suf(l,mid),max_pre(mid+1,r)),左子区间的后缀和右子区间的前缀


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值