数据结构(终极线段树篇)
摘要:
问题的提出:如何解决多样化的区间操作问题?
solve:线段树!!!
关键字:
线段树,可持久化线段树,权值线段树,线段树森林,动态开点线段树,区间操作,线段树应用。
前言:
区间操作问题的解决方法极多,如:树状数组,RMQ等。
所有这些数据结构都具有一定的局限性,都具有各自的优势和劣势。
而线段树无疑是这些数据结构中性价比最高的数据结构!(Ps:搞什么线段树,暴力出奇迹啊!)
线段树,线段树,线段树。
顾名思义:树上每一个节点表示一个线段(区间),每一次对一整个线段(区间)进行操作,棒棒。
第1节讲述了线段树的基本性质。
第2节实现了基本线段树。
第3节讲述动态开点线段树。
第4节讲述权值线段树。
//第5节讲述线段树森林。
//第6节讲述线段树的重要应用和例题。
1.线段树的基本性质:
1.0基本线段树的性质:
1.每个节点u表示一个区间[l,r]。若节点u非叶子节点,则u有两个儿子。设(mid=(l+r)/2)。 左儿子为[l,mid],节点编号为u*2。 右儿子为[mid+1,r],节点编号为u*2+1。
2.线段树的深度为O(lgn)
3.线段树的节点个数为O(n),常数约为4。
4.线段树上任意一个区间[l,r]都可以被O(logn)个线段覆盖。
思考:为何左儿子不为[l,mid+1],为何左儿子的节点编号为u*2。
思考:如何证明线段树的节点个数为O(n),且常数为4。
思考:如何证明线段树上任意一个区间[l,r]都可以被O(logn)个线段覆盖。
倘若我们只需要单点修改,我们只需要找到相应的叶子节点,更改叶子节点并沿路更新。
但倘若我们需要区间修改,我们却需要引入lazy tag(懒惰标记)。
每一次操作都先下放懒标记,清空标记,再进行相应的操作。
如上图:若将[1,5]全加上3。
需要标记tag[1,5]=3,下一次操作到[1,5]时下放。
tag[1,3]=3,tag[4,5]=3,tag[1,5]=0,sum[1,5]+=3*5;
2.基本线段树的实现:
2.1基本线段树的区间加,询问区间和:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1000005;
ll a[MAXN];
ll read()
{char c=getchar(); ll f=1,x=0;while (!isdigit(c)) {if (c=='-') f=-1; c=getchar();}while (isdigit(c)) {x=x*10+c-'0'; c=getchar();}return f*x;
}
struct Segment_tree
{struct node{int l,r;ll sum,tag;} tree[MAXN<<2];void up(int x){ tree[x].sum=tree[x<<1].sum+tree[(x<<1)|1].sum; }void down(int x){int num1=(tree[x<<1].r-tree[x<<1].l+1);int num2=(tree[(x<<1)|1].r-tree[(x<<1)|1].l+1);tree[(x<<1)|1].sum+=tree[x].tag*num2; tree[x<<1].tag+=tree[x].tag; tree[(x<<1)|1].tag+=tree[x].tag; tree[x<<1].sum+=tree[x].tag*num1; tree[x].tag=0;}void build(int x,int l,int r,ll *a){tree[x].l=l;tree[x].r=r;if (l==r){tree[x].sum=a[l];tree[x].tag=0;return;}int mid=(l+r)>>1;build(x<<1,l,mid,a);build((x<<1)|1,mid+1,r,a);up(x);}void change(int x,int l,int r,ll y){if (tree[x].l>=l&&tree[x].r<=r){tree[x].tag+=y;tree[x].sum+=y*(tree[x].r-tree[x].l+1);return;}down(x);int mid=(tree[x].l+tree[x].r)>>1;if (r<=mid) change(x<<1,l,r,y);else if (l>mid) change((x<<1)|1,l,r,y);else {change(x<<1,l,mid,y);change((x<<1)|1,mid+1,r,y);}up(x);}ll query(int x,int l,int r){if (tree[x].l>=l&&tree[x].r<=r) return tree[x].sum;down(x);int mid=(tree[x].l+tree[x].r)>>1;if (r<=mid) return query(x<<1,l,r);else if (l>mid) return query((x<<1)|1,l,r);else return query(x<<1,l,mid)+query((x<<1)|1,mid+1,r);}void print(int x){for (int i=1;i<=x;i++) printf("%d %d %d\n",tree[i].l,tree[i].r,tree[i].sum);printf("\n");}
} segment_tree;
int main()
{int n=read(),m=read();for (int i=1;i<=n;i++) a[i]=read();segment_tree.build(1,1,n,a);for (int i=1;i<=m;i++){int c=read();if (c==1){int x=read(),y=read(),z=read();segment_tree.change(1,x,y,z);}else{int x=read(),y=read();printf("%lld\n",segment_tree.query(1,x,y));}}return 0;
}
3.动态开点线段树
动态开点线段树,实现单点加,区间求和。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1000005; const int MAXS=1e9;
ll read()
{char c=getchar(); ll f=1,x=0;while (!isdigit(c)) {if (c=='-') f=-1; c=getchar();}while (isdigit(c)) {x=x*10+c-'0'; c=getchar();}return f*x;
}
struct Dynamic_segment_tree
{int nodenum=0;struct node{int l,r,leftnode,rightnode;ll sum;} tree[MAXN<<2];void change(int &x,int l,int r,int t,ll y){if (!x){nodenum++;x=nodenum;}tree[x].l=l;tree[x].r=r;tree[x].sum+=y;if (l==r) return;int mid=(tree[x].l+tree[x].r)>>1;if (t<=mid) change(tree[x].leftnode,l,mid,t,y);else change(tree[x].rightnode,mid+1,r,t,y);}ll query(int x,int l,int r){if (!x) return 0;if (tree[x].l>=l&&tree[x].r<=r) return tree[x].sum;int mid=(tree[x].l+tree[x].r)>>1;if (r<=mid) return query(tree[x].leftnode,l,r);else if (l>mid) return query(tree[x].rightnode,l,r);else return query(tree[x].leftnode,l,mid)+query(tree[x].rightnode,mid+1,r);}void print(int x){for (int i=1;i<=x;i++) printf("%d %d %d %d %d\n",tree[i].l,tree[i].r,tree[i].leftnode,tree[i].rightnode,tree[i].sum);printf("\n");}
} dynamic_segment_tree;
int main()
{int n=read(),m=read(),root=0;for (int i=1;i<=m;i++){int c=read(),x=read(),y=read();if (c==1) dynamic_segment_tree.change(root,1,MAXS,x,y);else printf("%d\n",dynamic_segment_tree.query(root,x,y));//dynamic_segment_tree.print(20);}return 0;
}
4.权值线段树
权值线段树,就是每一个线段树的叶子节点记录一种值的信息,常用于记录区间内某权值的出现个数。
这和一般的线段树差不多,只是节点的意义不同。
例题:求逆序对个数
未完待续