关于线段树的一个模板
没错只有一个模板
对了,声明一下本文所用的线段树均为结构体式
1.首先这个是线段树的简单naive操作
他支持以下几种操作:
1.建树(大雾
2.单点修改
3.单点赋值
4.区间修改(加)
5.区间修改(乘)
6.单点查询
7.区间求和
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int MAXN =100001; struct Segtree { int l,r,mid; int maxn,minn; int w; int tag,tag_mul; }seg[MAXN<<2]; int a[MAXN],ans; void build(int l,int r,int rt) { seg[rt].l = l; seg[rt].r = r; seg[rt].mid = (l+r) >> 1; seg[rt].tag_mul = 1; if(l == r) { seg[rt].w = a[seg[rt].l]; seg[rt].maxn = seg[rt].minn = a[seg[rt].l]; return ; } build(l,seg[rt].mid,rt<<1); build(seg[rt].mid+1,r,rt<<1|1); seg[rt].w = seg[rt<<1].w + seg[rt<<1|1].w; seg[rt].maxn = max(seg[rt<<1].maxn,seg[rt<<1|1].maxn); seg[rt].minn = min(seg[rt<<1].minn,seg[rt<<1|1].minn); return ; } void pushdown_plus(int rt)//标记下传(加) { seg[rt<<1].tag += seg[rt].tag; seg[rt<<1|1].tag += seg[rt].tag; seg[rt<<1].w += (seg[rt<<1].r - seg[rt<<1].l + 1) * seg[rt].tag; seg[rt<<1|1].w += (seg[rt<<1|1].r - seg[rt<<1|1].l + 1) * seg[rt].tag; seg[rt].tag = 0; return ; } void pushdown_mul(int rt)//标记下传(乘) { seg[rt<<1].tag += seg[rt].tag; seg[rt<<1|1].tag += seg[rt].tag; seg[rt<<1].tag_mul = seg[rt<<1].tag_mul * seg[rt].tag_mul; seg[rt<<1|1].tag_mul = seg[rt<<1].tag_mul * seg[rt].tag_mul; seg[rt<<1].w = (seg[rt<<1].w * seg[rt].tag_mul + (seg[rt<<1].r - seg[rt<<1].l + 1) * seg[rt].tag); seg[rt<<1|1].w = (seg[rt<<1|1].w * seg[rt].tag_mul + (seg[rt<<1|1].r - seg[rt<<1|1].l + 1) * seg[rt].tag); seg[rt].tag = 0; seg[rt].tag_mul = 1; return ; } void update_plus(int l,int r,int val,int rt)//区间修改(加) { if(seg[rt].l >= l && seg[rt].r <= r) { seg[rt].tag += val; seg[rt].w += (seg[rt].r - seg[rt].l + 1) * val; return ; } if(l <= seg[rt].mid) update_plus(l,seg[rt].mid,val,(rt<<1)); if(r > seg[rt].mid) update_plus(seg[rt].mid+1,r,val,(rt<<1|1)); seg[rt].w = seg[rt<<1].w + seg[rt<<1|1].w; return ; } void update_mul(int l,int r,int val,int rt)//区间修改(乘) { if(seg[rt].r < l || seg[rt].l > r) return ; if(seg[rt].l <= l && seg[rt].r >= r) { seg[rt].w = seg[rt].w * val; seg[rt].tag_mul = seg[rt].tag_mul * val; seg[rt].tag = seg[rt].tag * val; return ; } pushdown_mul(rt); update_mul(l,seg[rt].mid,val,rt<<1); update_mul(seg[rt].mid+1,r,val,rt<<1|1); seg[rt].w = seg[rt<<1].w + seg[rt<<1|1].w; return ; } void query(int l,int r,int rt)//区间求和 { if(seg[rt].l >= l && seg[rt].r <= r) { ans += seg[rt].w; //注意每一次在main内询问完毕之后要将ans重置 return ; } if(seg[rt].tag) pushdown_plus(rt); if(seg[rt].tag_mul) pushdown_mul(rt); if(l <= seg[rt].mid) query(l,r,(rt<<1)); if(r > seg[rt].mid) query(l,r,(rt<<1|1)); return ; } void modify(int x,int val,int rt)//单点修改x位置为val { if(seg[rt].l == seg[rt].r == x) { seg[rt].maxn = seg[rt].w = val; return ; } if(x <= seg[rt].mid) modify(x,val,rt<<1); else modify(x,val,rt<<1|1); seg[rt].w = seg[rt<<1].w + seg[rt<<1|1].w; seg[rt].maxn = max(seg[rt<<1].maxn,seg[rt<<1|1].maxn); return ; } int main() { cout << "这是线段树的naive操作"; return 0; }
2.区间求最大子段和
这个其实也只是线段树功能的一部分rwr
考虑每一个区间的最大子段和一共有三种存在方式:全部被包含在左侧,全部被包含在右侧,被左侧和右侧各包含一部分
对于前两种情况就是向左边和右边的子区间递归就行了,
对于第三种情况:我们对每一个线段树的"区间点"维护一个最大前缀和和最大后缀和,(在这里最大前缀和就是包含最左边的点的最大子段和,最大后缀和就是包含最右边的点的最大子段和)
那么第三种情况就是该区间的左侧区间最大后缀和加上右侧区间的最大前缀和
以该图为例,则第一、二、三种情况分别对应了红色、黄色、绿色的区间
最大前缀和就是蓝色的区间、最大后缀和就是橙色的区间
会转移了就好写了把,贴代码了:
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int MAXN = 50001; struct Segtree { int l; int r; int mid; int w; int prel; int prer; int res; }seg[MAXN<<2]; int a[MAXN],n,m; void pushup(int rt) { seg[rt].w = seg[rt<<1].w + seg[rt<<1|1].w; seg[rt].prel = max(seg[rt<<1].prel,seg[rt].w + seg[rt<<1|1].prel); seg[rt].prer = max(seg[rt<<1|1].prer,seg[rt<<1|1].w + seg[rt<<1].prer); seg[rt].res = max(seg[rt<<1].prer + seg[rt<<1|1].prel,max(seg[rt<<1].res,seg[rt<<1|1].res)); } void build(int l,int r,int rt) { seg[rt].l = l; seg[rt].r = r; if(l == r) { seg[rt].res = seg[rt].prel = seg[rt].prer = seg[rt].w = a[seg[rt].l]; return ; } seg[rt].mid = (seg[rt].l + seg[rt].r) >> 1; build(seg[rt].l,seg[rt].mid,rt<<1); build(seg[rt].mid + 1,seg[rt].r,rt<<1|1); pushup(rt); return ; } void modify(int x,int rt,int val) { if(seg[rt].l == seg[rt].r) { seg[rt].prel = seg[rt].prer = seg[rt].res = seg[rt].w = val; return ; } int mid = (seg[rt].l + seg[rt].r) >> 1; if(x <= mid) modify(x,rt<<1,val); else modify(x,rt<<1|1,val); pushup(rt); } /*Segtree query(int l,int r,int rt)//这个是窝写炸了的东西 { if(l <= seg[rt].l && seg[rt].r <= r) return seg[rt]; int mid = (l + r) >> 1; if(r <= mid) return query(l,r,rt<<1); if(mid < l) return query(l,r,rt<<1|1); Segtree L = query(l,seg[rt].mid,rt<<1); Segtree R = query(seg[rt].mid + 1,r,rt<<1|1); Segtree res; res.w = L.w + R.w; res.prel = max(L.prel,L.w + R.prel); res.prer = max(R.prer,R.w + L.prer); res.res = max(L.prer + R.prel,max(L.res,R.res)); return res; }*/ Segtree query(int x,int y,int rt,int l,int r) { if(x <= l && r <= y) return seg[rt]; int mid = (l + r) >> 1; if(y <= mid) return query(x,y,rt<<1,l,mid); if(mid<x) return query(x,y,rt<<1|1,mid+1,r); Segtree L = query(x,mid,rt<<1,l,mid); Segtree R = query(mid+1,y,rt<<1|1,mid+1,r); Segtree res; res.w = L.w + R.w; res.prel = max(L.prel,L.w + R.prel); res.prer = max(R.prer,R.w + L.prer); res.res = max(L.prer + R.prel,max(L.res,R.res)); return res; } int main() { scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&a[i]); build(1,n,1); scanf("%d",&m); while(m--) { int opt,x,y; scanf("%d%d%d",&opt,&x,&y); if(opt == 0) { modify(x,1,y); } else { int a6954717 = query(x,y,1,1,n).res; printf("%d\n",a6954717); } } return 0; }
3.这个东西我不太会,瞎胡了
题目要求是询问区间和,同时在修改的时候是修改等差数列的
比如说修改[1,4] 4 2 ,则其实际是将a[1]+=4,a[2]+=6,a[3]+=8,a[4]+=10;
这里我们讨论对着一个题的线段树解法:
普通的线段树是将其设置为lazy_tag的下传
所以我们仍然考虑如何变形lazy_tag从而使得其仍满足
答案是显然的,我们可以保存该等差数列的a1和d从而使得其可以通过等差数列求和公式而得出结论
a1可以直接下传,d=(seg[rt].r-seg[rt].l+1),这样就行了
懒得写代码了rwr
4.这个和上一个差不多
题目要求是询问区间和,同时在修改的时候是修改斐波那契数列的
这个题是以长者的p138 T4 为原型的
同上一个题,可以发现
a (1,0) (1,0)
b (0,1) (1,1)
a+b (1,1) (2,2)
a+2b (1,2) (3,4)
2a+3b (2,3) (5,7)
3a+5b (3,5) (8,12)
不管是什么数列,要求满足一个条件就可以用线段树了:两个序列的和仍然为同一性质的序列
以上