数据结构(终极线段树篇)
摘要:
问题的提出:如何解决多样化的区间操作问题?
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.权值线段树
权值线段树,就是每一个线段树的叶子节点记录一种值的信息,常用于记录区间内某权值的出现个数。
这和一般的线段树差不多,只是节点的意义不同。
例题:求逆序对个数
未完待续