首页 > 其他分享 >三哼经(山海经) 线段树

三哼经(山海经) 线段树

时间:2024-02-20 17:26:22浏览次数:22  
标签:山海经 左子 线段 右子 ansm 端点 ans 区间 三哼经

关于这道题,网上的题解基本都是什么求最大连续子段和,还有什么最大前缀、最大后缀,看了半天也是实在看不明白,便找了个题解,开始给题解写注释(众所周知题解里基本没有注释 doge),写了一下午加一晚上,倒是差不多明白了,又肝了0.3坤天终于把三哼经拿下了。

题目巴拉巴拉说了一堆没用的的话,让人头大。但就是给你一个序列,再给你m个区间,问你这每个区间中子区间的最大和以及最小的区间的两个端点。

首先是定义一个结构体,来表示一个一个节点,肯定要有的是区间节点、当前区间的和,题目问的是某个区间的最大值,所以还要定义一个ansm来表示这个最大值,剩下的东西在分析问题的时候加上即可。

在建树的函数中,最后一步是用处理完的左右子树更新其父节点,也就是下面代码中出现的
函数hebing(就是拼音....),那么就可以想一下当把两个子区间合并到一起时会出现什么问题。

很容易想错的是,在合并时简单地取两个区间中的ansm的最大值当做其父节点的ansm,因为在两个区间中间(也就是一部分在左子区间,一部分在右子区间),可能会存在一个子区间的子区间最大值比左右区间子区间最大值还要大,这就很麻烦了。

最朴素好想的方法就是遍历一下各个区间,但这个方法显然不行。还可以运用动规的思想(就比如我的同桌,某位孟姓Oler),但他已经试过了,会TLE,所以应该是只能寄希望于线段树了(其实可以用莫队,但我还没学习,就不多介绍了)。
这时候就可以画一个图:

这里我画了两层(虽然看起来像是卦象),就是为了方便取中间区间。

现在开始分析情况:

1.

左子区间的子区间最大值大于或等于右子区间的区间最大值(这里取等是因为题目要求输出最小的l和r),在这种情况中假设中间不存在子区间的子区间最大值大于这个值。于是问题的规模就可以缩小到左子区间,也就是父节点的子区间最大值以及其端点完全继承左子区间的(虽然父亲继承儿子的东西很奇怪,但确实是这样)

2.

第二种情况就是右子区间的区间最大值大于(注意是大于)左子区间的子区间最大值,这样父节点就可以继承右子区间的子区间最大值及端点。这种情况假设与情况1相同,

以下是中间区间要大的情况,但应细分为三种

3.

第一种是严格的中间即该子区间的任一端点均不与其爷爷节点(暂且这样称呼)的两端点重合。
这个时候就要记录一下子节点的两个子节点的所有端点位置,最左、右的端点和其父节点一致,所以只需要记录左子区间的右端点和右子区间的左端点。(分别是下面代码里的lp,rp)。
这种情况就要让左端点继承左子区间的右子区间,右端点继承右子区间的左子区间了。

4.

第二种是整个左子区间和右子区间的左子区间合起来大。
这种情况让左端点继承左子区间,右端点继承右子区间的左子区间。

5。

第三种是整个右子区间和左子区间的右子区间大。
左端点继承左子区间的右子区间,右端点继承右子区间。

(记得更新ansm!)

到这里就可以总结一下结构体里的变量了:
当前区间的两端点l、r及区间和mx,左右子区间和lmx、rmx
子区间的左子区间的右端点lp、子区间的右子区间的左端点rp
表示答案两个端点的ansl、ansr和最大子区间和ansm

还有不要开long long,虽然说十年OI一场空,不开long long见祖宗,但开了long long可能会爆内存,还是看看有没有必要再开long long吧。

注释版代码
#include<bits/stdc++.h>
using namespace std;
#define rid (x<<1|1)
#define lid (x<<1)
//#define int long long
#define inf 0x7ffffff
const int N=1e7;
int n,m,a[N],i,l,r;
struct stu{
	int l,r,mx,lmx,rmx;//当前区间左右端点、当前区间最大区间和、左区间最大区间和、右区间最大区间和 
	int ansl,ansr,ansm;//答案左右端点、最大区间和 
	int lp,rp; //左子树的右端点和右子树的左端点 
}s[N<<2];
stu hebing(stu a,stu b){
	stu ans;
	ans.ansm=-inf;//因为有负数,所以要赋值为极小值,要不然就会像我一样WA了而且找了半天还没发现错。 
	ans.l=a.l;
	ans.r=b.r;
	ans.lp=a.lp;
	ans.rp=b.rp;
	ans.lmx=a.lmx;
	ans.rmx=b.rmx;
	ans.mx=a.mx+b.mx;
	//以上初始化该节点。
	//先更新左最大区间和,再确保答案端点最小,再更新右区间和,更新的同时更新答案,最后看新的左右区间和能否更新答案。 
	if(a.ansm>=b.ansm){
		//左子树的区间最大值大于右子树,就把答案缩小到左子树。
		ans.ansl=a.ansl;
		ans.ansr=a.ansr;
		ans.ansm=a.ansm;
		//继承左子树答案 ,并非简单地把区间缩为左子树区间 
	} 
	if(a.rmx+b.lmx>ans.ansm){
		//左子树的右子树的区间最大和加上右子树的左子树的区间最大和大于当前答案区间和。 
		ans.ansm=a.rmx+b.lmx;
		ans.ansl=a.rp;
		ans.ansr=b.lp;
		//相当于把左子树的右区间和右子树的左区间合并 
	}
	if(a.mx+b.lmx>ans.lmx){
		//左区间和右区间的左区间合并可以更新当前区间的的左最大区间和 
		ans.lmx=a.mx+b.lmx;
		ans.lp=b.lp;
	}
	if(b.mx+a.rmx>ans.rmx){
		//右区间和左区间的右区间合并可以更新当前区间的右最大区间和 
		ans.rmx=b.mx+a.rmx;
		ans.rp=a.rp;
	}
//	---------------------------------------------------------------------------------------------
	if(ans.ansm==a.rmx+b.lmx){
		//若有相等区间和,取靠前的区间端点。
		if((a.rp<ans.ansl)||(ans.ansl==a.rp&&ans.ansr>b.lp)){
			ans.ansl=a.rp;
			ans.ansr=b.lp;
		}
	}
//	---------------------------------------------------------------------------------------------
	if(ans.ansm<b.ansm){
		//如果右子树的区间最大和大于当前答案,则继承右子树答案。 
		ans.ansm=b.ansm;
		ans.ansl=b.ansl;
		ans.ansr=b.ansr;
	}
	if(ans.ansm<=ans.lmx){
		//如果左最大区间和可以更新答案
		//更新答案的左右端点 
		ans.ansl=ans.l;
		ans.ansr=ans.lp;
	}
	if(ans.ansm<ans.rmx){
		//如果右最大区间和可以更新答案
		//更新答案左右端点 
		ans.ansl=ans.rp;
		ans.ansr=ans.r;
	}
	return ans;
}
void build(int x,int l,int r){
	s[x].l=l;
	s[x].r=r;
	if(l==r){
		s[x].ansl =s[x].ansr =s[x].lp=s[x].rp=l;
		s[x].ansm=s[x].lmx=s[x].rmx=s[x].mx =a[l];
		return; 
	}
	int mid=(l+r)>>1;
	build(lid,l,mid);
	build(rid,mid+1,r);
	s[x]=hebing(s[lid],s[rid]);
}
stu find(int x,int l,int r){
	if(l<=s[x].l&&s[x].r<=r)return s[x];
	stu ans;
	if(l<=s[lid].r&&r>=s[rid].l)ans=hebing(find(lid,l,r),find(rid ,l,r));
	else if(r<=s[lid].r)ans=find(lid,l,r);
	else if(l>=s[rid].l)ans=find(rid,l,r);
	return ans;
}
int main(){
	cin>>n>>m;
	for(i=1;i<=n;i++)scanf("%d",&a[i]);
	build(1,1,n);
	for(i=1;i<=m;i++){
		scanf("%d%d",&l,&r);
		stu ans=find(1,l,r);
		printf("%d %d %d\n",ans.ansl,ans.ansr,ans.ansm);
	}
	return 0; 
纯享版代码
#include<bits/stdc++.h>
using namespace std;
#define rid (x<<1|1)
#define lid (x<<1)
#define inf 0x7ffffff
const int N=1e7;
int n,m,a[N],i,l,r;
struct stu{
	int l,r,mx,lmx,rmx;
	int ansl,ansr,ansm,lp,rp;
}s[N<<2];
stu hebing(stu a,stu b){
	stu ans;
	ans.ansm=-inf;
	ans.l=a.l;
	ans.r=b.r;
	ans.lp=a.lp;
	ans.rp=b.rp;
	ans.lmx=a.lmx;
	ans.rmx=b.rmx;
	ans.mx=a.mx+b.mx;
	if(a.ansm>=b.ansm){
		ans.ansl=a.ansl;
		ans.ansr=a.ansr;
		ans.ansm=a.ansm;
	} 
	if(a.rmx+b.lmx>ans.ansm){
		ans.ansm=a.rmx+b.lmx;
		ans.ansl=a.rp;
		ans.ansr=b.lp;
	}
	if(a.mx+b.lmx>ans.lmx){
		ans.lmx=a.mx+b.lmx;
		ans.lp=b.lp;
	}
	if(b.mx+a.rmx>ans.rmx){
		ans.rmx=b.mx+a.rmx;
		ans.rp=a.rp;
	}
	if(ans.ansm==a.rmx+b.lmx){
		if((a.rp<ans.ansl)||(ans.ansl==a.rp&&ans.ansr>b.lp)){
			ans.ansl=a.rp;
			ans.ansr=b.lp;
		}
	}
	if(ans.ansm<b.ansm){
		ans.ansm=b.ansm;
		ans.ansl=b.ansl;
		ans.ansr=b.ansr;
	}
	if(ans.ansm<=ans.lmx){
		ans.ansl=ans.l;
		ans.ansr=ans.lp;
	}
	if(ans.ansm<ans.rmx){
		ans.ansl=ans.rp;
		ans.ansr=ans.r;
	}
	return ans;
}
void build(int x,int l,int r){
	s[x].l=l;
	s[x].r=r;
	if(l==r){
		s[x].ansl =s[x].ansr =s[x].lp=s[x].rp=l;
		s[x].ansm=s[x].lmx=s[x].rmx=s[x].mx =a[l];
		return; 
	}
	int mid=(l+r)>>1;
	build(lid,l,mid);
	build(rid,mid+1,r);
	s[x]=hebing(s[lid],s[rid]);
}
stu find(int x,int l,int r){
	if(l<=s[x].l&&s[x].r<=r)return s[x];
	stu ans;
	if(l<=s[lid].r&&r>=s[rid].l)ans=hebing(find(lid,l,r),find(rid ,l,r));
	else if(r<=s[lid].r)ans=find(lid,l,r);
	else if(l>=s[rid].l)ans=find(rid,l,r);
	return ans;
}
int main(){
	cin>>n>>m;
	for(i=1;i<=n;i++)scanf("%d",&a[i]);
	build(1,1,n);
	for(i=1;i<=m;i++){
		scanf("%d%d",&l,&r);
		stu ans=find(1,l,r);
		printf("%d %d %d\n",ans.ansl,ans.ansr,ans.ansm);
	}
	return 0;
}

后记

(关于做这道题的胡扯)

做这题先是对着题相面,想了半天(可能并不是夸张)还是没什么结果,就找了学长的题解,不过写的东西太少了,基本没什么注释,解释也不多(虽然我的也不是很多),就拿学长代码过来写注释,又写了一个下午加晚上(晚上其实有活动,没写多长时间),到了第二天又码了一上午代码,期间Huge还gank了一下偷偷把自己手机拿走的同学,下午就是各种出错,不是WA就是RE(基本都是忘写了return,无穷递归导致栈堆溢出....),到了下午终于是AC了,前后大概经历了0.6坤天,身心俱疲...

标签:山海经,左子,线段,右子,ansm,端点,ans,区间,三哼经
From: https://www.cnblogs.com/0shadow0/p/18023587

相关文章

  • 线段树-山海经 题解
    《最痛苦的一集》从开始的找维护变量到依据i比较依据y比较最后发现问题出在没初始化(如果不将答案赋值为极小值那么它最小值就是0因此wa了无数遍下面是思路首先要维护的变量有:\(区间的左右边界\,l,\,r\)\(区间的答案\,ans\)\(含左端点最大值\,lans\,和含右端点最......
  • 线段树
    线段树算是树状数组的进阶版了,树状数组能做的,线段树也肯定能做,它做不了的,线段树也能做。但确实线段树的代码量也很让人挠头,基本原理不再解释。先看一下基础的模板吧单点修改和区间查询点击查看代码#include<bits/stdc++.h>usingnamespacestd;constintN=300000;intn......
  • "山海经“ 讲解----线段树
    ”山海经“--线段树讲解1、题面:http://cogs.pro/cogs/problem/problem.php?pid=7752、题目大意及分析:i:大概就是说给了你一段[1,n]的区间,并给了每个区间的权值,下面会有m个问题,每个问题给你一段[1,n]的子区间[i,j],问你在这段区间上的任意一端子区间和最大是多少,并且要求输出这......
  • 线段树的各种板板~(*^▽^*)
    $\color{purple}{板板}$#include<bits/stdc++.h>usingnamespacestd;#defineintlonglong#defineinf0x3f#defineINF0x3f3f3f3f#definemst(a,b)memset(a,b,sizeof(a))#defineElaina0#definelid(id<<1)#definerid(id<<1|1)//即ridco......
  • 线段树模板
    开局宏定义:#include<bits/stdc++.h>#defineintlonglong#definelson(now<<1)//现结点的左孩子#definerson(now<<1|1)//右孩子usingnamespacestd;结构体定义:structNode{intl,r;//表示左右区间intmax,sum;//其他数据域}tree[N<<2]//=N*......
  • 线段树(板子)
    线段树单点修改,单点,区间查询区间修改,单点,区间查询单点修改普通线段树code#definels(rt<<1)#definers(rt<<1|1)#definellllonglongconstintN=1000001;intn,m;inta[N];structT{ intl,r,data;}tr[N<<2];voidpushup(intrt){ tr[rt].da......
  • 线段树-基础模版
    线段树模版一埋头扎进线段树一上午感觉很神奇几个函数就能把它完整的复现下来这里有几张基础概念的图对着代码来想还是很好理解的最后是整理了能够处理总和最大值和最小值的线段树模版code:$\LARGE\color{purple}{代码在这里}$#include<bits/stdc++.h>usingnames......
  • 树状数组及线段树详解
    本文章是作者调了3个多小时后,感触颇深,历经1个多小时写出来的,不喜勿喷相关内容可参考模板总结中的树状数组及线段树;进入正题树状数组所谓树状数组,即像树的数组,一般应用于求多次前缀和以及区间前缀和的问题中;其根据节点编号的二进制数的末尾0的个数划分层次,每个节点的管辖......
  • 区间最大子区间和——山海经题解
    区间最大子区间和规定:ls:区间靠左部分子区间最大和rs:区间靠右部分子区间最大和ms:区间子区间最大和s:区间和方程与数量关系如图所示,可以用动态规划解决山海经题解这题是上述方法在线段树中的应用solution#include<bits/stdc++.h>usingnamespacestd;constintmax......
  • 线段树进阶
    线段树进阶动态开点定义动态存储数据的线段树,可以优化空间复杂度实现为了避免\(N<<1\),不再使用完全二叉树存储,而记录左右儿子\(ls,rs\)此外需要\(tot\)记录已经开了多少点在递归时要记录点的左右区间,确保开点时能知道区间大小voidmodify(int&p,intL,intR,intl,i......