三哼经(山海经) 线段树
关于这道题,网上的题解基本都是什么求最大连续子段和,还有什么最大前缀、最大后缀,看了半天也是实在看不明白,便找了个题解,开始给题解写注释(众所周知题解里基本没有注释 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坤天,身心俱疲...
小蒟蒻写题解,不喜勿喷,有问题可以直接私信我。