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

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. 为什么阿波罗机器人(Apollo)是外观最帅的机器人 ?

    资料: https://www.youtube.com/watch?v=3CdwPGC9nyk 答案很简单,那就是这个公司单独找了一个外观设计团队,单独设计的外观. 看来啥事情要想搞的好,那就得多花钱 ...

  2. PowerBI_一分钟学会计算门店开业前3天销售金额_计算列及度量值方法

    在某些特殊场景,我们往往需要去计算一些特定的组别的聚合数据 今天,就以计算门店开业前3天的销售情况,来学习一下,利用计算列和DAX度量值,两种快捷计算此类问题的方案. 一:XMIND 二:示例数据 2 ...

  3. AI阅读助手ChatDOC:基于 AI 与文档对话、重新定义阅读方式的AI文献阅读和文档处理工具

    让 AI 真正成为你的生产力超级助手 AI 时代降临,我们需要积极拥抱 AI 工具 在过去的 2 个多月里,以 ChatGPT 为代表的 AI 风靡全球.随着 GPT 模型的不断优化,ChatGPT ...

  4. CKS考试心得分享

    CKS证书 考试相关 考试报名准备 CKS考试和CKA考试一样,已经开放中国大陆的考试.但区别是CKS目前没有中文题目,考试都是英文题目,唯一区别是CKS中文考试是中文老师监考,仅此而已.因此,建议C ...

  5. [HCTF 2018]admin 1

    [HCTF 2018]admin 1 < 文章中有有关flask session 文章 需要认真读一下> 1.信息搜集 由题意,注册admin 用户,回显 The username has ...

  6. 使用 nuxi analyze 命令分析 Nuxt 应用的生产包

    title: 使用 nuxi analyze 命令分析 Nuxt 应用的生产包 date: 2024/8/29 updated: 2024/8/29 author: cmdragon excerpt: ...

  7. 在虚拟机CentOS中安装jdk

    公众号本文地址:在虚拟机CentOS中安装jdk 本文主要是记录在CentOS中安装新的JDK的过程. 在虚拟机的centos中安装Jdk主要分为三步,第一步上传jdk文件到centos中,第二步解压 ...

  8. 全网最适合入门的面向对象编程教程:45 Python 实现常见数据结构-链表、树、哈希表、图和堆

    全网最适合入门的面向对象编程教程:45 Python 实现常见数据结构-链表.树.哈希表.图和堆 摘要: 数据结构是计算机科学中的一种组织和存储数据的方式,它决定了数据的访问方式和操作效率,数据结构的 ...

  9. 解决elementUI 表格el-table设置高度之后合计行不显示

    问题:elementUI el-table 设置了高度height 和合计行 show-summary 之后,合计行无法正常显示 一.问题分析 根据调试,发现该问题应该属于组件bug,表格主体中给 f ...

  10. 一文轻松搞定 tarjan 算法(二)(附带 tarjan 题单)

    完结篇:tarjan 求割点.点双连通分量.割边(桥)(附 40 道很好的 tarjan 题目). 上一篇(tarjan 求强连通分量,缩点,求边双) tarjan 求割点 还是求强联通分量的大致思路 ...