对于线段树的研究

基本思想

线段树(Segment Tree)是一种特殊的二叉树,每个节点维护一个区间,可以用来处理区间更新、单点修改、单点查询、区间查询的数据结构。

建树(build)

建树使用分治的思想,将一个区间 \((l,r)\) 拆成两份,分别为 \((l,mid)\)\((mid+1,r)\),分别处理,直到遇到叶子结点(\(l=r\))为止,再上推数据(pushup)。完成建树。

上推数据(pushup)

线段树的每一个节点不止存储了区间信息,还存储了维护的值(maintain value)。在建树的过程中更新每个节点的数据。

单点查询(query)

单点查询使用二分的思想。

如果查询的点的下标是\(k\),那么先遍历节点 \((1,n)\)(根节点)。如果\(k\)小于\(mid\)那么往左半区间搜索,否则往右半区间搜索。

区间查询(query)

如果需要查询空间被当前空间包含,那么直接返回该空间的值。

如果没有,将查询空间分成两份,把当前区间也分成两份,如果满足条件,就继续搜索,直到被包含。

单点修改(update)

先用单点查询的思想找到叶子节点,更改他,然后逐级上推即可。

区间修改(update)

区间修改使用了懒标记(lazy tag)的思想。

如果直接遍历线段树进行暴力修改,那么有很大的可能性会超时,因此我们可以使用懒标记标记这个节点,等到需要使用的时候再进行下推标记(pushdown)

下推标记(pushdown)

遇到了标记,直接将储存的标记向下推,更改维护值,取消标记即可。

模板

线段树的应用过于广泛,我们在后面的应用再进行展示。

时空复杂度分析

空间复杂度

线段树整体空间复杂度比较难算,但是一定不会超过\(O(4n)\)。所以开数组的时候可以开n<<2的数组。

另外,标记数组也需要开四倍。

除此之外,线段树一般不需要其他辅助空间。

时间复杂度

  • 建树:时间复杂度:\(O(n\log n)\)
  • 其他操作:时间复杂度:\(O(\log n)\)

应用

维护区间和的问题

例题:洛谷P3372 【模板】线段树 1

题目传送门

如题,已知一个数列,你需要进行下面两种操作:

  • 将某区间每一个数加上 \(k\)
  • 求出某区间每一个数的和。

涉及操作:区间修改、区间查询、建树(这个可以忽略)。

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,m;
#define ls (now<<1)
#define rs (now<<1|1)
#define mid (l+r>>1)
int tree[100001<<2],tag[100001<<2];
void pushup(int now){
	tree[now]=tree[ls]+tree[rs];
}
void pushdown(int now,int l,int r){
	if(tag[now]){
		tag[ls]+=tag[now];
		tag[rs]+=tag[now];
		tree[ls]+=(mid-l+1)*tag[now];
		tree[rs]+=(r-mid)*tag[now];
		tag[now]=0;
	}
}
void build(int now,int l,int r){
	if(l==r){
		cin>>tree[now];
		return ;
	}
	build(ls,l,mid);
	build(rs,mid+1,r);
	pushup(now);
}
void add(int now,int l,int r,int x,int y,int k){
	if(l>=x&&r<=y){
		tree[now]+=(r-l+1)*k;
		tag[now]+=k;
		return ;
	}
	pushdown(now,l,r);
	if(mid>=x) add(ls,l,mid,x,y,k);
	if(mid<y) add(rs,mid+1,r,x,y,k);
	pushup(now);
}
int ask(int now,int l,int r,int x,int y){
	if(l>=x&&r<=y){
		return tree[now];
	}
	pushdown(now,l,r);
	int ret=0;
	if(mid>=x) ret+=ask(ls,l,mid,x,y);
	if(mid<y) ret+=ask(rs,mid+1,r,x,y);
	return ret;
}
signed main(){
	cin>>n>>m;
	build(1,1,n);
	while(m--){
		int op,x,y,k;
		cin>>op;
		if(op==1){
			cin>>x>>y>>k;
			add(1,1,n,x,y,k);
		}
		else{
			cin>>x>>y;
			cout<<ask(1,1,n,x,y)<<'\n';
		}
	}
}

总结:维护区间和类问题的pushup操作是\(tree[i]+=tag[now]*length\)

两个tag

例题:洛谷P3373 【模板】线段树 2

题目传送门

#include <bits/stdc++.h>
using namespace std;

long long c[500010];
long long p;

struct sgt{
    long long sum[2000010];
    long long addv[2000010];
    long long mulv[2000010];
    void build(int o,int l,int r){
        addv[o]=0;
        mulv[o]=1;
        if(l==r)sum[o]=c[l];
        else{
            int mid=(l+r)>>1;
            int lson=o<<1;
            int rson=lson|1;
            build(lson,l,mid);
            build(rson,mid+1,r);
            sum[o]=(sum[lson]+sum[rson])%p;
        }
    }    
    void push_down(int o,int l,int r,int mid,int lson,int rson){
        mulv[lson]=(mulv[lson]*mulv[o])%p;
        mulv[rson]=(mulv[rson]*mulv[o])%p;
        addv[lson]=(addv[lson]*mulv[o])%p;
        addv[rson]=(addv[rson]*mulv[o])%p;
        sum[lson]=(sum[lson]*mulv[o])%p;
        sum[rson]=(sum[rson]*mulv[o])%p;
        mulv[o]=1;
        addv[lson]=(addv[lson]+addv[o])%p;
        addv[rson]=(addv[rson]+addv[o])%p;
        sum[lson]=(sum[lson]+(mid-l+1)*addv[o])%p;
        sum[rson]=(sum[rson]+(r-mid)*addv[o])%p;
        addv[o]=0;
    }
    void addall(int o,int l,int r,int a,int b,int x){
        if(l>=a && r<=b){
            addv[o]=(addv[o]+x)%p;
            sum[o]=(sum[o]+(r-l+1)*x)%p;
            return;
        }
        else{
            int mid=(l+r)>>1;
            int lson=o<<1;
            int rson=lson|1;
            if(mulv[o]!=1 || addv[o])push_down(o,l,r,mid,lson,rson);
            if(a<=mid)addall(lson,l,mid,a,b,x);
            if(b>mid)addall(rson,mid+1,r,a,b,x);
            sum[o]=(sum[lson]+sum[rson])%p;
        }
    }
    void mulall(int o,int l,int r,int a,int b,int x){
        if(l>=a && r<=b){
            mulv[o]=(mulv[o]*x)%p;
            addv[o]=(addv[o]*x)%p;
            sum[o]=(sum[o]*x)%p;
            return;
        }
        else{
            int mid=(l+r)>>1;
            int lson=o<<1;
            int rson=lson|1;
            if(mulv[o]!=1 || addv[o])push_down(o,l,r,mid,lson,rson);
            if(a<=mid)mulall(lson,l,mid,a,b,x);
            if(b>mid)mulall(rson,mid+1,r,a,b,x);
            sum[o]=(sum[lson]+sum[rson])%p;
        }
    }
    long long query(int o,int l,int r,int a,int b){
        if(l>=a && r<=b)return sum[o]%p;
        else{
            int mid=(l+r)>>1;
            int lson=o<<1;
            int rson=lson|1;
            long long ans=0;
            if(mulv[o]!=1 || addv[o])push_down(o,l,r,mid,lson,rson);
            if(a<=mid)ans+=query(lson,l,mid,a,b);
            if(b>mid)ans+=query(rson,mid+1,r,a,b);
            return ans%p;
        }
    }
} tree;

signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	int n,m;
	cin>>n>>m>>p;
	for(int i=1;i<=n;i++){
		cin>>c[i];
	}
	tree.build(1,1,n);
	for(int i=1;i<=m;i++){
		int f;
		long long x,y,k;
		cin>>f;
		if(f==1){
			cin>>x>>y>>k;
			tree.mulall(1,1,n,x,y,k);
		}
		else if(f==2){
			cin>>x>>y>>k;
			tree.addall(1,1,n,x,y,k);
		}
		else{
			cin>>x>>y;
			cout<<tree.query(1,1,n,x,y)<<endl;
		}
	}
	return 0;
}

复杂的维护问题

例题:洛谷P4041 [AHOI2014/JSOI2014]奇怪的计算器

题目传送门

#include <bits/stdc++.h>
#define int long long
#define soi sort
using namespace std;

const signed N = 100005;
const signed T = 4 * N + 5;

signed n, mn, mx, q;
int minv[T], maxv[T], tagadd[T], tangmul[T], tat[T], tagset[T];

struct Option {
	char s;
	int a;
}op[N];
struct Number {
	int id, val, ans;
}num[N];

bool cmp1(Number a, Number b) {
	return a.val < b.val;
}
bool cmp2(Number a, Number b)  {
	return a.id < b.id;
}
void build(signed i, signed l, signed r) {
	if (l == r) {
		minv[i] = num[l].val;
		maxv[i] = num[l].val;
		return ;
	}
	tangmul[i] = 1;
	signed mid = l + r >> 1;
	build(i << 1, l, mid);
	build(i << 1 | 1, mid + 1, r);
	minv[i] = min(minv[i << 1], minv[i << 1 | 1]);
	maxv[i] = max(maxv[i << 1], maxv[i << 1 | 1]);
}
void pushadd(signed i, signed l, signed r, int x) {
	minv[i] += x;
	maxv[i] += x;
	tagadd[i] += x;
}
void pushmul(signed i, signed l, signed r, int x) {
	minv[i] *= x;
	maxv[i] *= x;
	tangmul[i] *= x;
	tagadd[i] *= x;
	tat[i] *= x;
}
void pushad(signed i, signed l, signed r, int x)  {
	minv[i] += num[l].val * x;
	maxv[i] += num[r].val * x;
	tat[i]+=x;
}
void pushset(signed i, signed l, signed r, int x) {
	minv[i] = maxv[i] = x;
	tagset[i] = x;
	tagadd[i] = 0;
	tangmul[i] = 1;
	tat[i] = 0;
}
void pushdown(signed i, signed l, signed r) {
	signed mid = l + r >> 1;
	if (tagset[i] != 0) {
		pushset(i << 1, l, mid, tagset[i]);
		pushset(i << 1 | 1, mid + 1, r, tagset[i]);
		tagset[i] = 0;
	}
	if (tangmul[i] != 1) {
		pushmul(i << 1, l, mid, tangmul[i]);
		pushmul(i << 1 | 1, mid + 1, r, tangmul[i]);
		tangmul[i] = 1;
	}
	if (tagadd[i] != 0) {
		pushadd(i << 1, l, mid, tagadd[i]);
		pushadd(i << 1 | 1, mid + 1, r, tagadd[i]);
		tagadd[i] = 0;
	}
	if (tat[i] != 0) {
		pushad(i << 1, l, mid, tat[i]);
		pushad(i << 1 | 1, mid + 1, r, tat[i]);
		tat[i] = 0;
	}
}
void setmin(signed i, signed l, signed r) {
	if (maxv[i] < mn) {
		pushset(i, l, r, mn);
		return ;
	}
	if (l == r)
	        return ;
	pushdown(i, l, r);
	signed mid = l + r >> 1;
	setmin(i << 1, l, mid);
	if (minv[i << 1 | 1] < mn)
	        setmin(i << 1 | 1, mid + 1, r);
	minv[i] = min(minv[i << 1], minv[i << 1 | 1]);
	maxv[i] = max(maxv[i << 1], maxv[i << 1 | 1]);
}
void setmax(signed i, signed l, signed r) {
	if (minv[i] > mx) {
		pushset(i, l, r, mx);
		return ;
	}
	if (l == r)
	        return ;
	pushdown(i, l, r);
	signed mid = l + r >> 1;
	setmax(i << 1 | 1, mid + 1, r);
	if (maxv[i << 1] > mx)
	        setmax(i << 1, l, mid);
	minv[i] = min(minv[i << 1], minv[i << 1 | 1]);
	maxv[i] = max(maxv[i << 1], maxv[i << 1 | 1]);
}
void getAnswer(signed i, signed l, signed r) {
	if (l == r) {
		num[l].ans = minv[i];
		return ;
	}
	pushdown(i, l, r);
	signed mid = l + r >> 1;
	getAnswer(i << 1, l, mid);
	getAnswer(i << 1 | 1, mid + 1, r);
}
signed main() {
	scanf("%d%d%d", &n, &mn, &mx);
	for (signed i = 1; i <= n; i++) 
	        scanf("%s%lld", &op[i].s, &op[i].a);
	scanf("%d", &q);
	for (signed i = 1; i <= q; i++) {
		scanf("%lld", &num[i].val);
		num[i].id = i;
	}
	soi(num + 1, num + q + 1, cmp1);
	build(1, 1, q);
	for (signed i = 1; i <= n; i++) {
		if (op[i].s == '+') 
		            pushadd(1, 1, q, op[i].a); else if (op[i].s == '-')
		            pushadd(1, 1, q, -op[i].a); else if (op[i].s == '*')
		            pushmul(1, 1, q, op[i].a); else if (op[i].s == '@')
		            pushad(1, 1, q, op[i].a);
		setmin(1, 1, q);
		setmax(1, 1, q);
	}
	getAnswer(1, 1, q);
	soi(num + 1, num + q + 1, cmp2);
	for (signed i = 1; i <= q; i++){
		cout << num[i].ans << endl;
	}
	return 0;
}
posted @ 2022-01-27 11:06  蒟蒻xiezheyuan  阅读(35)  评论(0编辑  收藏  举报