线段树

线段树的其他操作,一起使用等看本分类集合,这里就只放最基本的板子

板子

区间修改(+),区间查询(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)\) 的时间复杂度内实现单点修改、区间修改、区间查询(区间求和,求区间最大值,求区间最小值)等操作。

几点注意事项:

  1. 线段树记得开四倍空间!!!不然就RE
  2. 区间求和记得开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 线段树

单点修改 区间查询

  1. 区间和 hdu 1166 敌兵布阵
    详可见暑假训练摘记
  2. 区间最大值 hdu 1754 I Hate It
    详可见暑假训练摘记
  3. loj #130. 树状数组 1 :单点修改,区间查询
    线段树也可以做,这里就不放代码了,大同小异

区间修改 单点查询

其实单点查询和区间查询没有区别,可以直接写区间查询之后用来端点查询

  1. loj #131. 树状数组 2 :区间修改,单点查询

区间修改 区间查询

  1. 洛谷 p3372 P3372 【模板】线段树 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;
}
  1. loj #132. 树状数组 3 :区间修改,区间查询
    与上面的代码类似,此处不在放出

相关资料

https://oi-wiki.org/ds/seg/

posted on 2023-07-04 09:47  Qiansui  阅读(7)  评论(0编辑  收藏  举报