线段树
目录
线段树的其他操作,一起使用等看本分类集合,这里就只放最基本的板子
板子
区间修改(+),区间查询(sum) - struct聚合
struct Segment_Tree{//update - range_add + query - range_sum
int n;
vector<ll> seg, tag, val;
Segment_Tree(int cnt) : n(cnt), seg(cnt << 2, 0), tag(cnt << 2, 0), val(cnt + 1, 0){}
int ls(int p) { return p << 1; }
int rs(int p) { return p << 1 | 1; }
void push_up(int p){ seg[p] = seg[ls(p)] + seg[rs(p)]; return ; }
void build(int p, int pl, int pr){
tag[p] = 0;
if(pl == pr){
seg[p] = val[pl]; return ;
}
int mid = pl + pr >> 1;
build(ls(p), pl, mid);
build(rs(p), mid + 1, pr);
push_up(p);
return ;
}
void addtag(int p, int pl, int pr, ll k){
tag[p] += k;
seg[p] += (pr - pl + 1) * k;
return ;
}
void push_down(int p, int pl, int pr){
if(tag[p]){
int mid = pl + pr >> 1;
addtag(ls(p), pl, mid, tag[p]);
addtag(rs(p), mid + 1, pr, tag[p]);
tag[p] = 0;
}
return ;
}
void update(int p, int pl, int pr, int l, int r, ll k){
if(l <= pl && pr <= r){
addtag(p, pl, pr, k);
return ;
}
push_down(p, pl, pr);
int mid = pl + pr >> 1;
if(l <= mid) update(ls(p), pl, mid, l, r, k);
if(mid < r) update(rs(p), mid + 1, pr, l, r, k);
push_up(p);
return ;
}
ll query(int p, int pl, int pr, int l, int r){
if(l <= pl && pr <= r){
return seg[p];
}
push_down(p, pl, pr);
ll res = 0;
int mid = pl + pr >> 1;
if(l <= mid) res += query(ls(p), pl, mid, l, r);
if(mid < r) res += query(rs(p), mid + 1, pr, l, r);
return res;
}
};
线段树
线段树是算法竞赛中常用的用来维护 区间信息 的数据结构。
线段树可以在 \(O(\log N)\) 的时间复杂度内实现单点修改、区间修改、区间查询(区间求和,求区间最大值,求区间最小值)等操作。
几点注意事项:
- 线段树记得开四倍空间!!!不然就RE
- 区间求和记得开long long,不然数字大的会容易溢出
具体的代码实现需要视题目要求而定!下面是一个例子
建树
利用递归建树,开数组seg[maxm]
下面以hdu 1166 敌兵布阵为例
int ls(int p){return p<<1;}
int rs(int p){return p<<1|1;}
void push_up(int p){//更新
seg[p]=seg[ls(p)]+seg[rs(p)];
return ;
}
void build(int p,int pl,int pr){//建树
if(pl==pr){
seg[p]=a[pl];
a[pl]=p;
return ;
}
int mid=(pl+pr)>>1;
build(ls(p),pl,mid);//左边递归建树
build(rs(p),mid+1,pr);//右边递归建树
push_up(p);
return ;
}
//main函数输入数据
for(int i=1;i<=n;++i){
cin>>a[i];
}
build(1,1,n);
区间修改
利用Lazy-Tag的方法,统一记录区间 i 的修改
增加addtag函数和push_down函数,用来处理tag的变化
下面以洛谷 p3372 P3372 【模板】线段树 1为例
建树:
int ls(int p){return p<<1;}
int rs(int p){return p<<1|1;}
void push_up(int p){//更新seg
seg[p]=seg[ls(p)]+seg[rs(p)];
return ;
}
void build(int p,int pl,int pr){//建树
tag[p]=0;//初始化tag
if(pl==pr){
seg[p]=a[pl];
a[pl]=p;
return ;
}
int mid=(pl+pr)>>1;
build(ls(p),pl,mid);
build(rs(p),mid+1,pr);
push_up(p);
return ;
}
区间修改:
void addtag(int p,int pl,int pr,ll k){//修改tag
tag[p]+=k;
seg[p]+=(pr-pl+1)*k;//已经对seg的值进行了修改
return ;
}
void push_down(int p,int pl,int pr){//下传tag
if(tag[p]!=0){
int mid=(pl+pr)>>1;
addtag(ls(p),pl,mid,tag[p]);
addtag(rs(p),mid+1,pr,tag[p]);
tag[p]=0;
}
return ;
}
void update(int l,int r,ll k,int p,int pl,int pr){//区间修改
if(l<=pl&&pr<=r){
addtag(p,pl,pr,k);
return ;
}
push_down(p,pl,pr);//tag不能继续维持,需要下传
int mid=(pl+pr)>>1;
if(l<=mid) update(l,r,k,ls(p),pl,mid);
if(mid<r) update(l,r,k,rs(p),mid+1,pr);
push_up(p);
return ;
}
区间查询
对于[l,r]区间,递归查询到某个节点 p ,其对应的区间为[pl,pr]时,存在两种情况
- [l,r]完全覆盖[pl,pr],那么当前的节点 p 是所查询区间的组成部分,返回seg[p]即可
- [l,r]与[pl,pr]部分重叠,利用mid=(pl+pr)/2划分区间,继续递归,那么分别判断左右子节点,l<=mid 是需要继续递归左子节点,r>mid是需要继续递归右子节点。
单点修改时:
下面以hdu 1166 敌兵布阵为例
int query(int l,int r,int p,int pl,int pr){
if(l<=pl&&r>=pr) return seg[p];
int mid=(pl+pr)>>1,res=0;
if(l<=mid) res+=query(l,r,ls(p),pl,mid);
if(r>mid) res+=query(l,r,rs(p),mid+1,pr);
return res;
}
//main
ans=query(x,y,1,1,n);
区间修改时:
下面以洛谷 p3372 P3372 【模板】线段树 1为例
ll query(int l,int r,int p,int pl,int pr){
if(l<=pl&&pr<=r) return seg[p];
push_down(p,pl,pr);//tag不能继续维持,需要下传
int mid=(pl+pr)>>1;
ll res=0;
if(l<=mid) res+=query(l,r,ls(p),pl,mid);
if(mid<r) res+=query(l,r,rs(p),mid+1,pr);
return res;
}
例题
2023ACM暑假训练day 8-9 线段树
单点修改 区间查询
- 区间和 hdu 1166 敌兵布阵
详可见暑假训练摘记 - 区间最大值 hdu 1754 I Hate It
详可见暑假训练摘记 - loj #130. 树状数组 1 :单点修改,区间查询
线段树也可以做,这里就不放代码了,大同小异
区间修改 单点查询
其实单点查询和区间查询没有区别,可以直接写区间查询之后用来端点查询
区间修改 区间查询
//>>>Qiansui
#include<map>
#include<set>
#include<list>
#include<stack>
#include<cmath>
#include<queue>
#include<deque>
#include<cstdio>
#include<string>
#include<vector>
#include<utility>
#include<iomanip>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<functional>
#define ll long long
#define ull unsigned long long
#define mem(x,y) memset(x,y,sizeof(x))
#define debug(x) cout << #x << " = " << x << endl
#define debug2(x,y) cout << #x << " = " << x << " " << #y << " = "<< y << endl
//#define int long long
inline ll read()
{
ll x=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-48;ch=getchar();}
return x*f;
}
using namespace std;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
typedef pair<ull,ull> pull;
typedef pair<double,double> pdd;
/*
P3372 【模板】线段树 1
区间修改+区间查询
*/
const int maxm=1e5+5,inf=0x3f3f3f3f,mod=998244353;
ll n,m,a[maxm],seg[maxm<<2],tag[maxm<<2];
int ls(int p){return p<<1;}
int rs(int p){return p<<1|1;}
void push_up(int p){//更新seg
seg[p]=seg[ls(p)]+seg[rs(p)];
return ;
}
void build(int p,int pl,int pr){//建树
tag[p]=0;
if(pl==pr){
seg[p]=a[pl];
a[pl]=p;
return ;
}
int mid=(pl+pr)>>1;
build(ls(p),pl,mid);
build(rs(p),mid+1,pr);
push_up(p);
return ;
}
void addtag(int p,int pl,int pr,ll k){//修改tag
tag[p]+=k;
seg[p]+=(pr-pl+1)*k;//已经对seg的值进行了修改
return ;
}
void push_down(int p,int pl,int pr){//下传tag
if(tag[p]!=0){
int mid=(pl+pr)>>1;
addtag(ls(p),pl,mid,tag[p]);
addtag(rs(p),mid+1,pr,tag[p]);
tag[p]=0;
}
return ;
}
void update(int l,int r,ll k,int p,int pl,int pr){//区间修改
if(l<=pl&&pr<=r){
addtag(p,pl,pr,k);
return ;
}
push_down(p,pl,pr);
int mid=(pl+pr)>>1;
if(l<=mid) update(l,r,k,ls(p),pl,mid);
if(mid<r) update(l,r,k,rs(p),mid+1,pr);
push_up(p);
return ;
}
ll query(int l,int r,int p,int pl,int pr){
if(l<=pl&&pr<=r) return seg[p];
push_down(p,pl,pr);
int mid=(pl+pr)>>1;
ll res=0;
if(l<=mid) res+=query(l,r,ls(p),pl,mid);
if(mid<r) res+=query(l,r,rs(p),mid+1,pr);
return res;
}
void solve(){
cin>>n>>m;
for(int i=1;i<=n;++i){
cin>>a[i];
}
build(1,1,n);
ll c,x,y,k;
for(int i=0;i<m;++i){
cin>>c;
if(c==1){//区间修改
cin>>x>>y>>k;
update(x,y,k,1,1,n);
}else{//区间查询
cin>>x>>y;
cout<<query(x,y,1,1,n)<<'\n';
}
}
return ;
}
signed main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int _=1;
// cin>>_;
while(_--){
solve();
}
return 0;
}
- loj #132. 树状数组 3 :区间修改,区间查询
与上面的代码类似,此处不在放出
相关资料
本文来自博客园,作者:Qiansui,转载请注明原文链接:https://www.cnblogs.com/Qiansui/p/17524865.html