”山海经“--线段树 讲解

1、题面:

http://cogs.pro/cogs/problem/problem.php?pid=775

2、题目大意及分析:

i:大概就是说给了你一段[1,n]的区间,并给了每个区间的权值,下面会有m个问题,每个问题给你一段[1,n]的子区间[i,j],问你在这段区间上的任意一端子区间和最大是多少,并且要求输出这段区间,然后最恶心的点就是这段区间要保证是在sum最大的情况下,优先i最小,其次j最小(这真的是缺德至极了)。

ii:那既然是线段树的题我们一开始很容易就会想到用线段树来维护一段区间的sum以及maxn,只不过是把一个求和和一个最大值搓在一起了吗,(然后我吭哧吭哧打出来,发现连样例都过不了),为什么错了呢,再给大家看一下样例:

样例输入

5 3

5 -6 3 -1 4

1 3

1 5

5 5

样例输出

1 1 5

3 5 6

5 5 4

那么按照我们的思路,线段树画出来是这样的:



(红色标记为sum,黄色标记为maxn)

明显发现[1,5]是错的,可由[3,5]转移过来6.

我们这样的做法表明了每段区间的的sum值和maxn值都只是由我们给划分好的区间转移过来,就像是区间[1,5]只能由[1,3]和[4,5]转移过来,却没有想到我们划分好的区间其实是可以打通的,就像区间[1,5],被分成了[1,3]和[4,5],但实际答案是[3,5]。这就警示了我们去深入思考,像[1,5]中的[3,5]这样是从中间打通的,但一定要注意是以中间为基准的,必须由中间向两边扩展,(不能找出左右两边的maxn再加和,因为题目中明确说了i只能到达i+1,所以道路必须是连续不断的),那么怎么算呢,遍历一遍?

NO,TLE终止了我的美梦。

第一次交的代码:

面对TLE的$\huge{pu-tao↑zher↑↓}$
#include<bits/stdc++.h>
#define ps push_back
#define mk make_pair
using namespace std;
typedef long long ll;
const int MAXN=0x7fffffff;
const int N=1e5+10;
int a[N],n,m,pre[N];
struct jj{
int sum=0,max=-MAXN;
int l,r;
pair<int,int> ans;
}tr[N<<2];
//int sum(int L,int R,int l,int r,int k){
// if(L<=l&&r<=R)return tr[k].sum;
// else{
// int ans=0;
// int mid=l+r>>1;
// if(L<=mid)ans+=sum(L,R,l,mid,k<<1);
// if(R>mid)ans+=sum(L,R,mid+1,r,k<<1|1);
// return ans;
// }
//}
inline void geng(int x){
tr[x].sum=tr[x<<1].sum+tr[x<<1|1].sum;
int j1=-MAXN,j2=-MAXN,mid=tr[x].l+tr[x].r>>1,l,r,p;
if(tr[x].max<tr[x].sum)tr[x].max=tr[x].sum,tr[x].ans=mk(tr[x].l,tr[x].r);
if(tr[x].max<tr[x<<1].max)tr[x].max=tr[x<<1].max,tr[x].ans=tr[x<<1].ans;
if(tr[x].max<tr[x<<1|1].max)tr[x].max=tr[x<<1|1].max,tr[x].ans=tr[x<<1|1].ans;
for(int i=tr[x].l;i<=mid;i++){
p=pre[mid]-pre[i-1];
if(j1<p)j1=p,l=i;
}
for(int i=mid+1;i<=tr[x].r;i++){
p=pre[i]-pre[mid];
if(j2<p)j2=p,r=i;
}
if(tr[x].max<j1+j2)tr[x].max=j1+j2,tr[x].ans=mk(l,r);
}
void jian(int k,int l,int r){
tr[k].l=l,tr[k].r=r;
if(l==r){
tr[k].sum=a[l];
tr[k].max=a[l];
tr[k].ans=mk(l,r);
return;
}
int mid=(l+r)>>1;
jian(k<<1,l,mid);
jian(k<<1|1,mid+1,r);
geng(k);
}
struct ret{
int ans;
pair<int,int> key;
void out(){
printf("%d %d %d\n",key.first,key.second,ans);
}
};
ret ask(int L,int R,int l,int r,int k){
if(L<=l&&r<=R)return ret{tr[k].max,tr[k].ans};
else{
int mid=l+r>>1;
ret anss=ret{-MAXN};
if(L<=mid){
ret pp=ask(L,R,l,mid,k<<1);
if(anss.ans<pp.ans)anss=pp;
}
if(L<=mid&&R>mid){
int l1,r1,p,j1=-MAXN,j2=-MAXN;
for(int i=L;i<=mid;i++){
p=pre[mid]-pre[i-1];
if(j1<p)j1=p,l1=i;
}
for(int i=mid+1;i<=R;i++){
p=pre[i]-pre[mid];
if(j2<p)j2=p,r1=i;
}
if(anss.ans<j1+j2)anss.ans=j1+j2,anss.key=mk(l1,r1);
}
if(R>mid){
ret pp=ask(L,R,mid+1,r,k<<1|1);
if(anss.ans<pp.ans)anss=pp;
}
return anss;
}
}
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++)scanf("%d",&a[i]),pre[i]=pre[i-1]+a[i];
jian(1,1,n);
// for(int i=1;i<=n*4;i++)cout<<tr[i].sum<<' '<<tr[i].max<<' '<<i<<' '<<tr[i].l<<' '<<tr[i].r<<' '<<tr[i].ans.first<<' '<<tr[i].ans.second<<endl;
int x,y;
for(int i=1;i<=m;i++){
scanf("%d%d",&x,&y);
ret op=ask(x,y,1,n,1);
op.out();
}
}

那怎么办,要不存一下每段区间从mid向两边拓展的最大值?刚要动手打,发现每次还是得求一边,并且这个参数只能用于比较,没什么别的用,于是我就打开了题解,于是我就学到了这道题的精髓:每个区间的mid拓展最大值是由他的—————— 左二分区间的以右为基础向左拓展的最大值与—————— 右二分区间的以左为基础向右拓展的最大值的加和。

好,来总结一下:

1、优先级:

i.子区间和最大。

ii.子区间左端点i最小。

iii.子区间右端点j最小。

2、结构体中参数及其转移:

  • i.左端点l,右端点r,建树时直接赋值。
  • ii.sum求和,由两个二分区间的sum值加和转移过来。
  • iii.以左为基础的向右拓展的最大值lman,以及这段区间的终点lmr(l-sum-r),可以由左二分区间的lman、左二分区间的sum+右二分区间的lman转移过来,且左二分区间的lman优先(因为左二分区间的lman的终点lmr一定小于右二分区间的lmr)。以右为基础的向左拓展的最大值rman,以及这段区间的终点rml(r-sum-l),可以由右二分区间的rman、右二分区间的sum+左二分区间的rman转移过来,且右二分区间的sum+左二分区间的rman优先,不知道为什么网上的题解大都按第一个右二分区间的rman优先,大概是没多想直接和上面一样了吧。
  • iiii.该区间的子区间求和最大值man,man所对应的区间的两个端点ansl,ansr,man可以由左二分区间的man、右二分区间的man以及左二分区间的rman+右二分区间的lman转移过来,如果在值相等的情况下,左二分区间 优先于 中间加和 优先于 右二分区间。

注意:ii--iiii的转移不仅要出现在pushup函数中,而且也要在询问的ask函数中出现。

pushup函数:

void pushup(int k){//k是线段树的下标。
int lid=k<<1,rid=k<<1|1;
tr[k].sum=tr[lid].sum+tr[rid].sum;
if(tr[k].lman<tr[lid].lman)tr[k].lman=tr[lid].lman,tr[k].lmr=tr[lid].lmr;
if(tr[k].lman<tr[lid].sum+tr[rid].lman)tr[k].lman=tr[lid].sum+tr[rid].lman,tr[k].lmr=tr[rid].lmr;
if(tr[k].rman<tr[rid].sum+tr[lid].rman)tr[k].rman=tr[rid].sum+tr[lid].rman,tr[k].rml=tr[lid].rml;
if(tr[k].rman<tr[rid].rman)tr[k].rman=tr[rid].rman,tr[k].rml=tr[rid].rml;
if(tr[k].man<tr[lid].man)tr[k].man=tr[lid].man,tr[k].ansl=tr[lid].ansl,tr[k].ansr=tr[lid].ansr;
if(tr[k].man<tr[lid].rman+tr[rid].lman)tr[k].man=tr[lid].rman+tr[rid].lman,tr[k].ansl=tr[lid].rml,tr[k].ansr=tr[rid].lmr;
if(tr[k].man<tr[rid].man) tr[k].man=tr[rid].man,tr[k].ansl=tr[rid].ansl,tr[k].ansr=tr[rid].ansr;
}

ask函数:

jj ask(int l,int r,int L,int R,int k){
if(L<=l&&r<=R)return tr[k];
else{
int mid=l+r>>1,lid=k<<1,rid=k<<1|1;
if(R<=mid)return ask(l,mid,L,R,lid);
if(L>mid)return ask(mid+1,r,L,R,rid);
jj ll,rr,z;
ll=ask(l,mid,L,R,lid),rr=ask(mid+1,r,L,R,rid);
if(z.lman<ll.lman)z.lman=ll.lman,z.lmr=ll.lmr;
if(z.lman<ll.sum+rr.lman)z.lman=ll.sum+rr.lman,z.lmr=rr.lmr;
if(z.rman<rr.sum+ll.rman)z.rman=rr.sum+ll.rman,z.rml=ll.rml;
if(z.rman<rr.rman)z.rman=rr.rman,z.rml=rr.rml;
if(z.man<ll.man)z.man=ll.man,z.ansl=ll.ansl,z.ansr=ll.ansr;
if(z.man<ll.rman+rr.lman)z.man=ll.rman+rr.lman,z.ansl=ll.rml,z.ansr=rr.lmr;
if(z.man<rr.man) z.man=rr.man,z.ansl=rr.ansl,z.ansr=rr.ansr;
return z;
}
}

3、初始化

注意sum=0,lman=rman=man=-0x7fffffff;

建树时遇到l==r的情况后要注意所有参数全部赋值:

build函数:

void build(int l,int r,int k){
tr[k].l=l,tr[k].r=r;
int mid=l+r>>1;
if(l==r){
tr[k].lmr=tr[k].rml=tr[k].ansl=tr[k].ansr=l;
tr[k].lman=tr[k].rman=tr[k].man=tr[k].sum=a[l];
return;
}
build(l,mid,k<<1);build(mid+1,r,k<<1|1);
pushup(k);
}
附上最后代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
const int MAXN=0x7fffffff;
struct jj{
int l,r,sum=0,lman=-MAXN,rman=-MAXN,ansl,ansr,lmr,rml,man=-MAXN;
}tr[N<<2];
int n,m,a[N];
void pushup(int k){
int lid=k<<1,rid=k<<1|1;
tr[k].sum=tr[lid].sum+tr[rid].sum;
if(tr[k].lman<tr[lid].lman)tr[k].lman=tr[lid].lman,tr[k].lmr=tr[lid].lmr;
if(tr[k].lman<tr[lid].sum+tr[rid].lman)tr[k].lman=tr[lid].sum+tr[rid].lman,tr[k].lmr=tr[rid].lmr;
if(tr[k].rman<tr[rid].sum+tr[lid].rman)tr[k].rman=tr[rid].sum+tr[lid].rman,tr[k].rml=tr[lid].rml;
if(tr[k].rman<tr[rid].rman)tr[k].rman=tr[rid].rman,tr[k].rml=tr[rid].rml;
if(tr[k].man<tr[lid].man)tr[k].man=tr[lid].man,tr[k].ansl=tr[lid].ansl,tr[k].ansr=tr[lid].ansr;
if(tr[k].man<tr[lid].rman+tr[rid].lman)tr[k].man=tr[lid].rman+tr[rid].lman,tr[k].ansl=tr[lid].rml,tr[k].ansr=tr[rid].lmr;
if(tr[k].man<tr[rid].man) tr[k].man=tr[rid].man,tr[k].ansl=tr[rid].ansl,tr[k].ansr=tr[rid].ansr;
}
void build(int l,int r,int k){
tr[k].l=l,tr[k].r=r;
int mid=l+r>>1;
if(l==r){
tr[k].lmr=tr[k].rml=tr[k].ansl=tr[k].ansr=l;
tr[k].lman=tr[k].rman=tr[k].man=tr[k].sum=a[l];
return;
}
build(l,mid,k<<1);build(mid+1,r,k<<1|1);
pushup(k);
}
jj ask(int l,int r,int L,int R,int k){
if(L<=l&&r<=R)return tr[k];
else{
int mid=l+r>>1,lid=k<<1,rid=k<<1|1;
if(R<=mid)return ask(l,mid,L,R,lid);
if(L>mid)return ask(mid+1,r,L,R,rid);
jj ll,rr,z;
ll=ask(l,mid,L,R,lid),rr=ask(mid+1,r,L,R,rid);
if(z.lman<ll.lman)z.lman=ll.lman,z.lmr=ll.lmr;
if(z.lman<ll.sum+rr.lman)z.lman=ll.sum+rr.lman,z.lmr=rr.lmr;
if(z.rman<rr.sum+ll.rman)z.rman=rr.sum+ll.rman,z.rml=ll.rml;
if(z.rman<rr.rman)z.rman=rr.rman,z.rml=rr.rml;
if(z.man<ll.man)z.man=ll.man,z.ansl=ll.ansl,z.ansr=ll.ansr;
if(z.man<ll.rman+rr.lman)z.man=ll.rman+rr.lman,z.ansl=ll.rml,z.ansr=rr.lmr;
if(z.man<rr.man) z.man=rr.man,z.ansl=rr.ansl,z.ansr=rr.ansr;
return z;
}
}
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
build(1,n,1);
int x,y;
for(int i=1;i<=m;i++){
scanf("%d%d",&x,&y);
jj ans=ask(1,n,x,y,1);
printf("%d %d %d\n",ans.ansl,ans.ansr,ans.man);
}
}

[==============================================]

记得我说的网上的题解的bug吧?

给出hack代码:

hack.in:

8 1

1 2 0 0 398 -398 398 -37

3 8

hack.out:

3 5 398

"山海经“ 讲解----线段树的更多相关文章

  1. [COGS 755]山海经:线段树

    网上似乎这道题的题解很少?写一个吧 我跟这道题的渊源追溯到了上个学期刚刚学线段树的那一天... 当时线段树专题前边的题都是一些板子就不一会就水过了,然后就看到了最后一题的它:山海经 那一个上午,我竭尽 ...

  2. [转载]完全版线段树 by notonlysuccess大牛

    原文出处:http://www.notonlysuccess.com/ (好像现在这个博客已经挂掉了,在网上找到的全部都是转载) 今天在清北学堂听课,听到了一些很令人吃惊的消息.至于这消息具体是啥,等 ...

  3. 【转】线段树完全版~by NotOnlySuccess

    线段树完全版  ~by NotOnlySuccess 很早前写的那篇线段树专辑至今一直是本博客阅读点击量最大的一片文章,当时觉得挺自豪的,还去pku打广告,但是现在我自己都不太好意思去看那篇文章了,觉 ...

  4. 《完全版线段树》——notonlysuccess

    转载自:NotOnlySuccess的博客 [完全版]线段树 很早前写的那篇线段树专辑至今一直是本博客阅读点击量最大的一片文章,当时觉得挺自豪的,还去pku打广告,但是现在我自己都不太好意思去看那篇文 ...

  5. 【转】 线段树完全版 ~by NotOnlySuccess

    载自:NotOnlySuccess的博客 [完全版]线段树 很早前写的那篇线段树专辑至今一直是本博客阅读点击量最大的一片文章,当时觉得挺自豪的,还去pku打广告,但是现在我自己都不太好意思去看那篇文章 ...

  6. 【转载】完全版线段树 by notonlysuccess大牛

    原文出处:http://www.notonlysuccess.com/ 今晚上比赛就考到了 排兵布阵啊,难受. [完全版]线段树 很早前写的那篇线段树专辑至今一直是本博客阅读点击量最大的一片文章,当时 ...

  7. POJ2182题解——线段树

    POJ2182题解——线段树 2019-12-20 by juruoOIer 1.线段树简介(来源:百度百科) 线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线 ...

  8. 线段树讲解(数据结构、C++)

    声明    : 仅一张图片转载于http://www.cnblogs.com/shuaiwhu/archive/2012/04/22/2464583.html,自己画太麻烦了...那个博客的讲解也很好 ...

  9. Tido c++线段树知识讲解(转载)

    线段树知识讲解 定义.建树.单点修改.区间查询         特别声明:如上的讲解说的是区间最大值 如果想要查询区间和 只需要改变一下建树和查询的代码就行了,如下 其他根据自己的需要进行修改即可

  10. poj 3468 A Simple Problem with Integers 线段树第一次 + 讲解

    A Simple Problem with Integers Description You have N integers, A1, A2, ... , AN. You need to deal w ...

随机推荐

  1. Jax框架的显存分析已经不支持gperftools,而是支持go语言下的新版本pprof

    官方: https://jax.readthedocs.io/en/latest/device_memory_profiling.html

  2. 深度学习中使用TensorFlow或Pytorch框架时到底是应该使用CPU还是GPU来进行运算???

    本文实验环境为Python3.7, TensorFlow-gpu=1.14, CPU为i7-9700k,锁频4.9Ghz, GPU为2060super显卡 ====================== ...

  3. mpi4py 官方使用手册

    一直好奇mpi4py的使用手册在哪,找了好久最后在anaconda上发现了线索: https://anaconda.org/conda-forge/mpi4py Home: https://mpi4p ...

  4. from joblib.pool import MemmapingPool 报错

    修改方法: 将 from joblib.pool import MemmappingPool   修改为: from joblib.pool import MemmappingPool     === ...

  5. DeepMind Lab的一些python例子—————(Ubuntu22.04系统安装DeepMind Lab)后续

    相关资料: Ubuntu22.04系统安装DeepMind Lab ====================================================== 关于DeepMind ...

  6. Apache DolphinScheduler如何开启开机自启动功能?

    转载自东华果汁哥 Apache DolphinScheduler 是一个分布式.去中心化的大数据工作流调度系统,支持大数据任务调度.若要设置 DolphinScheduler 开机自启动,通常需要将其 ...

  7. 3.2.0 版本预告!远程日志解决 Worker 故障获取不到日志的问题

    Apache DolphinScheduler 3.2.0 版本已经呼之欲出,8 月 中下旬,这个大版本就要和用户见面了.为了让大家提前了解到此版本更新的主要内容,我们已经制作了几期视频和内容做了大致 ...

  8. Apache DolphinScheduler 3.1.8 版本发布,修复 SeaTunnel 相关 Bug

    近日,Apache DolphinScheduler 发布了 3.1.8 版本.此版本主要基于 3.1.7 版本进行了 bug 修复,共计修复 16 个 bug, 1 个 doc, 2 个 chore ...

  9. 使用jquery的tmpl构建复杂表格

    Tmpl提供了几种tag:${}:等同于{{=}},是输出变量,通过了html编码的.{{html}}:输出变量html,但是没有html编码,适合输出html代码.{{if }} {{else}}: ...

  10. docker高级篇第三章-dockerfile案例之制作自己的centos镜像

    在上一篇文章中<Dockerfile介绍及常用保留指令>,我们介绍了Dockerfile是什么以及Dockerfile常用的保留字段.熟悉了这些之后,有没有想自己动手写一个Dockerfi ...