【SPOJ GSS3】Can you answer these queries III(动态区间最大子段和)——杨子曰题目

【SPOJ GSS3】Can you answer these queries III(动态区间最大段和)——杨子曰题目

传送门:线段树集合


题目描述
You are given a sequence A of N (N <= 50000) integers between -10000 and 10000. On this sequence you have to apply M (M <= 50000) operations:
modify the i-th element in the sequence or for given x y print max{Ai + Ai+1 + … + Aj | x<=i<=j<=y }.

输入格式:
The first line of input contains an integer N. The following line contains N integers, representing the sequence A1…AN.
The third line contains an integer M. The next M lines contain the operations in following form:
0 x y: modify Ax into y (|y|<=10000).
1 x y: print max{Ai + Ai+1 + … + Aj | x<=i<=j<=y }.

输出格式:
For each query, print an integer as the problem required.

输入样例:

4
1 2 3 4
4
1 1 3
0 3 -3
1 2 4
1 3 3

输出样例:

6
4
-3

给一个长度为n(n<=50000)的序列,再给出m个接着是m(m<=20000)个操作对于每个操作k,x,y,如果k=0那么第x个元素变为y,如果k=1就求出区间[x,y]里的子序列最大和


额……线段树我又来了


这次我们要维护的东西就有点多了,首先一个正常的人都能知道区间里的区间子序列最大和——ms肯定要维护(这不就是答案吗),现在我们要考虑想要算出ms,也就是pushup的时候要用到什么东西
我们来分类讨论一下:
这个区间的子序列最大和都在左区间:
在这里插入图片描述
嗯……不错,直接用ms[nod*2]更新上来

这个区间的子序列最大和都在右区间:
在这里插入图片描述
也直接用ms[nod*2+1]更新上来

但是最令人恼火的就是一半在左,一半在右,Look at the 图:
在这里插入图片描述
我们很明显地知道,蓝色和绿色部分很可能不是左右区间的子序列最大和,那到底是神马呢?
我觉得蓝色部分是左区间包括右端点的区间最大子序列和,同理绿色部分是右区间包括左端点的区间最大子序列和(←仔细阅读)
也就是我们还要维护每个区间从左边和右边开始的区间子序列最大和ls和rs
那我们就可以得到:

ms[nod]=max(ms[nos*2],ms[nod*2+1],rs[nod*2]+ls[nod*2+1]);

不要高兴太早,我们还要考虑ls和rs怎么pushup,我们以ls为例分类讨论一下:
这个区间的ls就是左区间的ls
在这里插入图片描述
二话不说,直接用ls[nod*2]更新上来

还有一种恶心的情况,叫做横跨了左右区间,look at the图:
在这里插入图片描述
我们会发现蓝色部分是左区间的和,而绿色部分是右区间的ls
这也就是说——我!们!还!要!维!护!一!个!sum
于是我们得出了这样的东西:

ls[nod]=max(ls[nod*2],sum[nod*2]+ls[nod*2+1]);
rs[nod]=max(rs[nod*2+1],sum[nod*2+1]+rs[nod*2]);

总结一波:到目前为止我们已经把我们要维护的东西维护完了——ms,ls,rs,sum
于是乎,我们得到了以下pushup:

void pushup(int nod){
	ms[nod]=max(max(ms[nod*2],ms[nod*2+1]),rs[nod*2]+ls[nod*2+1]);
	ls[nod]=max(ls[nod*2],sum[nod*2]+ls[nod*2+1]);
	rs[nod]=max(rs[nod*2+1],sum[nod*2+1]+rs[nod*2]);
	sum[nod]=sum[nod*2]+sum[nod*2+1];
}

这样一来build和update就变得非常常规:

void build(int l,int r,int nod){
	if (l==r){
		sum[nod]=ms[nod]=ls[nod]=rs[nod]=a[l];
		return;
	}
	int mid=(l+r)/2;
	build(l,mid,nod*2);
	build(mid+1,r,nod*2+1);
	pushup(nod);
}

void update(int l,int r,int k,int v,int nod){
	if (l==r){
		sum[nod]=ms[nod]=ls[nod]=rs[nod]=v;
		return;
	}
	int mid=(l+r)/2;
	if (k<=mid) update(l,mid,k,v,nod*2);
	else update(mid+1,r,k,v,nod*2+1);
	pushup(nod);
}

接下来,我们看——query


首先,如果查询区间在当前区间的左边,那我们就去左边找,如果查询区间在当前区间的右边,那我们就去右边找——这是地球人都知道的事情
我们现在要考虑的就是一般在左,一半在右的情况——其实与我们的pushup极像

我们要query的是ms,我们分三种情况讨论——把左边的ms给query出来,把右边的ms给query出来,然后我们还要把左边的rs给query,右边的ls给搞出来加在一起,三个东西取一个max

于是乎,我们又要写两个查ls和rs的query,比如查ls的query,当遇到一半在左一半在右的情况时,我们要把左边的ls给query出来,或者是query左边的sum 和右边的ls加一下,取个max

嗯,和pushup一样,很好理解
BBUUTT,你有没有注意到一个恐怖的事情,我!们!要!写!四!个!query!!!,分别求ms,ls,rs,sum
别急,这里有一个小技巧,我们可以把四个东西在一个query里求出——做法有两种

  1. 把四个东西放在一个结构体里,return一个结构体
  2. 把四个东西放进实参里,然后传出

我这里选择了第2种:

void query(int l,int r,int ll,int rr,int nod,int &s,int &ans,int &ans_l,int &ans_r){
	if (l==ll && r==rr){
		ans=ms[nod];
		ans_l=ls[nod];
		ans_r=rs[nod];
		s=sum[nod];
		return;
	}
	int mid=(l+r)/2;
	if (rr<=mid) query(l,mid,ll,rr,nod*2,s,ans,ans_l,ans_r);
	else if (ll>mid) query(mid+1,r,ll,rr,nod*2+1,s,ans,ans_l,ans_r);
	else{
		int l_ans,r_ans,l_ans_l,l_ans_r,r_ans_l,r_ans_r,l_s,r_s;
		query(l,mid,ll,mid,nod*2,l_s,l_ans,l_ans_l,l_ans_r);
		query(mid+1,r,mid+1,rr,nod*2+1,r_s,r_ans,r_ans_l,r_ans_r);
		ans=max(max(l_ans,r_ans),l_ans_r+r_ans_l);
		ans_l=max(l_ans_l,l_s+r_ans_l);
		ans_r=max(r_ans_r,r_s+l_ans_r);
		s=l_s+r_s;
	}
}

相信你看变量名看得已经头晕了,简单解释一下:

  • s:我们现在要求的区间的和(实参)
  • ans:我们现在要求的ms(实参)
  • ans_l:我们现在要求的ls(实参)
  • ans_r:我们现在要求的rs(实参)
  • l_XXX:左区间的XXX
  • r_XXX:右区间的XXX

OK,完事


搞定了这道题以后你可以去试试这道题:【SPOJ2916 GSS5】Can you answer these queries V


c++代码:

#include<cstdio>
#include<iostream>
using namespace std;

const int maxn=50005;
int a[maxn],sum[maxn*4],ms[maxn*4],ls[maxn*4],rs[maxn*4];

int max(int x,int y){
    return x>y?x:y;
}

void pushup(int nod){
    ms[nod]=max(max(ms[nod*2],ms[nod*2+1]),rs[nod*2]+ls[nod*2+1]);
    ls[nod]=max(ls[nod*2],sum[nod*2]+ls[nod*2+1]);
    rs[nod]=max(rs[nod*2+1],sum[nod*2+1]+rs[nod*2]);
    sum[nod]=sum[nod*2]+sum[nod*2+1];
}

void build(int l,int r,int nod){
    if (l==r){
        sum[nod]=ms[nod]=ls[nod]=rs[nod]=a[l];
        return;
    }
    int mid=(l+r)/2;
    build(l,mid,nod*2);
    build(mid+1,r,nod*2+1);
    pushup(nod);
}

void update(int l,int r,int k,int v,int nod){
    if (l==r){
        sum[nod]=ms[nod]=ls[nod]=rs[nod]=v;
        return;
    }
    int mid=(l+r)/2;
    if (k<=mid) update(l,mid,k,v,nod*2);
    else update(mid+1,r,k,v,nod*2+1);
    pushup(nod);
}

void query(int l,int r,int ll,int rr,int nod,int &s,int &ans,int &ans_l,int &ans_r){
    if (l==ll && r==rr){
        ans=ms[nod];
        ans_l=ls[nod];
        ans_r=rs[nod];
        s=sum[nod];
        return;
    }
    int mid=(l+r)/2;
    if (rr<=mid) query(l,mid,ll,rr,nod*2,s,ans,ans_l,ans_r);
    else if (ll>mid) query(mid+1,r,ll,rr,nod*2+1,s,ans,ans_l,ans_r);
    else{
        int l_ans,r_ans,l_ans_l,l_ans_r,r_ans_l,r_ans_r,l_s,r_s;
        query(l,mid,ll,mid,nod*2,l_s,l_ans,l_ans_l,l_ans_r);
        query(mid+1,r,mid+1,rr,nod*2+1,r_s,r_ans,r_ans_l,r_ans_r);
        ans=max(max(l_ans,r_ans),l_ans_r+r_ans_l);
        ans_l=max(l_ans_l,l_s+r_ans_l);
        ans_r=max(r_ans_r,r_s+l_ans_r);
        s=l_s+r_s;
    }
}

int main(){
    int n;
    scanf("%d",&n);
    for (int i=1;i<=n;i++){
        scanf("%d",&a[i]);
    }
    build(1,n,1);
    int m;
    scanf("%d",&m);
    while(m--){
        int k,x,y,ans=0,ansl=0,ansr=0,s=0;
        scanf("%d%d%d",&k,&x,&y);
        if (k==0) update(1,n,x,y,1);
        else{
            query(1,n,x,y,1,s,ans,ansl,ansr);
            printf("%d\n",ans);
        }
    }
    return 0;
}

于XJ机房607

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
洛谷的SPOJ需要注册一个SPOJ账号并进行绑定才能进行交题。您可以按照以下步骤进行注册: 1. 打开洛谷网站(https://www.luogu.com.cn/)并登录您的洛谷账号。 2. 在网站顶部导航栏中找到“题库”选项,将鼠标悬停在上面,然后选择“SPOJ”。 3. 在SPOJ页面上,您会看到一个提示,要您注册SPOJ账号并进行绑定。点击提示中的链接,将会跳转到SPOJ注册页面。 4. 在SPOJ注册页面上,按照要填写您的用户名、密码和邮箱等信息,并完成注册。 5. 注册完成后,返回洛谷网站,再次进入SPOJ页面。您会看到一个输入框,要您输入刚刚注册的SPOJ用户名。输入用户名后,点击“绑定”按钮即可完成绑定。 现在您已经成功注册并绑定了SPOJ账号,可以开始在洛谷的SPOJ题库上刷题了。祝您顺利完成编程练习!\[1\]\[2\] #### 引用[.reference_title] - *1* *3* [(洛谷入门系列,适合洛谷新用户)洛谷功能全解](https://blog.csdn.net/rrc12345/article/details/122500057)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [luogu p7492 序列](https://blog.csdn.net/zhu_yin233/article/details/122051384)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值