线段树及其应用
线段树的几个基础操作:建树,单点查询,单点修改,区间查询,区间修改。其代码的主要思想为二分。参考博客:https://blog.csdn.net/qq_39826163/article/details/81436440
数据结构:
struct node { int l; //左端点 int r; //右端点 int sum; //区间和,因题目而异 int f; //懒标记 }tree[4*maxn+1];
1.建树
建树的过程分为三步:1:给定左右端点的确定范围;2:如果是叶子结点,储存需要维护的信息;3:状态合并。下面是实现代码:
void build(int l,int r,int cur) { tree[cur].l = l,tree[cur].r = r; if(tree[cur].l == tree[cur].r){ tree[cur].sum = arr[l]; return; } int mid = (l + r) >> 1; build(l, mid, cur << 1); build(mid + 1, r, cur << 1 | 1); pushup(cur); //状态合并 }
2.单点查询
单点查询与二分查询法基本一致,如果当前枚举的点左右端点相等,即叶子节点,就是目标节点。如果不是,因为这是二分法,所以设查询位置为x,当前结点区间范围为了l,r,中点为mid,则如果x<=mid,则递归它的左孩子,否则递归它的右孩子。下面是实现代码:
int ask(int pos,int cur) //cur为当前结点,x为待查位置 { if(tree[cur].l==tree[cur].r) return tree[k].value; int mid=(tree[cur].l+tree[cur].r)>>1; if(pos<=mid) ask(pos,cur<<1); else ask(pos,cur<<1|1); //递归左右孩子 }
3.单点修改
和单点查询原理类似,结合建树过程,我们以增加某一个区间的长度代码为例,下面是实现代码:
void modify(int pos,int x,int cur) //在pos位置修改x(增加x),cur为当前结点编号 { if(tree[cur].l == tree[cur].r){ tree[cur].sum += x; return; } if(tree[cur].f) pushdown(cur); int mid = (tree[cur].l + tree[cur].r) >> 1; if(pos <= mid) modify(pos,x,cur<<1); else modify(pos,x,cur<<1|1); pushup(cur); }
4.区间查询
区间查询分为三种状态,1、当前结点区间的值全部为答案的一部分,;2、当前结点区间只有一部分是答案,3、当前结点区间包含了待查询的区间,根据x,y与mid的情况往下走。
即mid=(l+r)/2
y<=mid ,即 查询区间全在,当前区间的左子区间,往左孩子走;x>mid 即 查询区间全在,当前区间的右子区间,往右孩子走否则,两个子区间都走。
下面是实现代码:
void query(int l,int r,int cur) //l,r为待查询区间 { if(l <= tree[cur].l && tree[cur].r <=r ){ ans += tree[cur].sum; return; } if(tree[cur].f) pushdown(cur); //将更新信息传递给左右子树 int mid = (tree[cur].l + tree[cur].r) >> 1; if(l <= mid) query(l,r,cur<<1); if(mid < r) query(l,r,cur<<1|1); }
5.区间修改
如果要修改一个区间的值,给一个区间内的每个数都加或减或修改时,如果我们只想查询某一个子区间的值,如修改[1,100]而只查询[1,2]的值,如果给所有区间都改得画,在树的深度很高的情况下会很浪费。所以这里引入了一个新状态——懒标记,其作用是存储到这个节点的修改信息,暂时不把修改信息传到子节点。就像家长扣零花钱,你用的时候才给你,不用不给你。下面是懒标记的具体实现过程:
懒标记下移:
void pushdown(int cur) { tree[cur<<1].f+=tree[cur].f; tree[cur<<1|1].f+=tree[cur].f; tree[cur<<1].sum+=tree[cur].f*(tree[cur<<1].r-tree[cur<<1].l+1); tree[cur<<1|1].sum+=tree[cur].f*(tree[cur<<1|1].r-tree[cur<<1|1].l+1); tree[cur].f=0; }
还有上面提到的pushup函数,我认为它和oushdown函数一起,是线段树的核心,其他的不过是模板而已,而这个是线段树真正灵活多变的地方,对于任意给定一个题目,你要依据题意,题目需要维护什么,你就维护什么,比如上面一直再说的oushup函数和这里的Pushdown,在这起到的是维护一个区间和的作用。不同题目真正不一样的代码应该就是这两个了。
//以维护区间和为例: inline void pushup(int cur) { tree[cur].sum=tree[cur<<1].sum+tree[cur<<1|1].sum; }
区间修改代码:
void modify_interval(int l,int r,int x,int cur) //[a,b]为待修改的区间,x为区间修改的值 { if(tree[cur].l>=l&&tree[cur].r<=r) //当前区间全部对要修改的区间有用 { tree[cur].value+=(tree[cur].r-tree[cur].l+1)*x; //(r-1+1)区间点的总数 tree[cur].f+=x; return; } if(tree[cur].f) pushdown(cur); //懒标记下移 int mid=(tree[cur].l+tree[cur].r)>>1; if(l<=mid) modify_interval(l,r,x,cur<<1); if(r>mid) modify_interval(l,r,x,cur<<1|1); pushup(cur); }
例题AC代码:poj-2528:题意,每次在[l,r]区间贴广告,最多能看见多少个广告牌?
思路:离散化+线段树区间染色
#include<cstdio> #include<algorithm> #include<cstring> #include<cstdlib> using namespace std; const int maxn=1e5+50; struct node { int l,r,num; }tree[maxn<<2]; int n,T,cnt,tot,li[maxn],ri[maxn]; int point[maxn<<1]; int ans; bool vis[maxn]; inline void pushdown(int cur) { tree[cur<<1].num=tree[cur].num; tree[cur<<1|1].num=tree[cur].num; tree[cur].num=0; } inline void build(int l,int r,int cur) //initialization { tree[cur].l=l,tree[cur].r=r; if(l==r){ tree[cur].num=0; return; } int mid=(l+r)>>1; build(l,mid,cur<<1); build(mid+1,r,cur<<1|1); tree[cur].num=0; } inline void modify(int l,int r,int x,int cur) { if(tree[cur].l>=l&&tree[cur].r<=r){ tree[cur].num=x; return; } if(tree[cur].num) pushdown(cur); int mid=(tree[cur].l+tree[cur].r)>>1; if(l<=mid) modify(l,r,x,cur<<1); if(mid<r) modify(l,r,x,cur<<1|1); } inline void query(int l,int r,int cur) { if(tree[cur].num&&!vis[tree[cur].num]){ vis[tree[cur].num]=1; ans++; return; } if(l==r) return; if(tree[cur].num) pushdown(cur); int mid=(l+r)>>1; query(l,mid,cur<<1); query(mid+1,r,cur<<1|1); } int main() { scanf("%d",&T); while(T--) { memset(vis,false,sizeof(vis)); scanf("%d",&n); cnt=ans=0; for(int i=1;i<=n;++i) scanf("%d %d",&li[i],&ri[i]); for(int i=1;i<=n;++i){ point[++cnt]=li[i], point[++cnt]=ri[i]; } sort(point+1,point+cnt+1); int now=unique(point+1,point+cnt+1)-(point+1); tot=now; for(int i=2;i<=now;++i){ if(point[i]-point[i-1]>1) point[++tot]=point[i-1]+1; } sort(point+1,point+tot+1); build(1,tot,1); for(int i=1;i<=n;++i){ int l=lower_bound(point+1,point+tot+1,li[i])-point; int r=lower_bound(point+1,point+tot+1,ri[i])-point; //O(n*logn) algorithm modify(l,r,i,1); } query(1,tot,1); printf("%d\n",ans); } system("pause"); }
POJ-3468 题意:裸线段树区间修改+区间查询,注意区间查询的时候别忘了pushdown就好了。
#include<cstdio> #include<cstdlib> #include<iostream> using namespace std; const int maxn=1e5+50; typedef long long LL; int arr[maxn],n,q; LL ans; struct node { int l,r,f; LL sum; }tree[maxn<<2]; inline void pushup(int cur) { tree[cur].sum=tree[cur<<1].sum+tree[cur<<1|1].sum; } inline void build(int l,int r,int cur) { tree[cur].l=l,tree[cur].r=r; tree[cur].sum=tree[cur].f=0; if(tree[cur].l==tree[cur].r){ tree[cur].sum=1LL*arr[l]; return; } int mid=(l+r)>>1; build(l,mid,cur<<1); build(mid+1,r,cur<<1|1); pushup(cur); } inline void pushdown(int cur) { tree[cur<<1].f+=tree[cur].f; tree[cur<<1|1].f+=tree[cur].f; tree[cur<<1].sum+=1LL*tree[cur].f*(tree[cur<<1].r-tree[cur<<1].l+1); tree[cur<<1|1].sum+=1LL*tree[cur].f*(tree[cur<<1|1].r-tree[cur<<1|1].l+1); tree[cur].f=0; } inline void query(int l,int r,int cur) { if(l<=tree[cur].l&&tree[cur].r<=r){ ans+=tree[cur].sum; return; } if(tree[cur].f) pushdown(cur); int mid=(tree[cur].l+tree[cur].r)>>1; if(mid>=l) query(l,r,cur<<1); if(mid<r) query(l,r,cur<<1|1); } inline void modify(int l,int r,int x,int cur) { if(l<=tree[cur].l&&tree[cur].r<=r){ tree[cur].f+=x; tree[cur].sum+=1LL*x*(tree[cur].r-tree[cur].l+1); return; } if(tree[cur].f) pushdown(cur); int mid=(tree[cur].l+tree[cur].r)>>1; if(mid>=l) modify(l,r,x,cur<<1); if(mid<r) modify(l,r,x,cur<<1|1); pushup(cur); } int main() { scanf("%d%d",&n,&q); for(int i=1;i<=n;++i) scanf("%d",&arr[i]); build(1,n,1); while(q--) { char t; getchar(); scanf("%c",&t); if(t=='Q'){ ans=0; int a,b; scanf("%d %d",&a,&b); query(a,b,1); printf("%lld\n",ans); } else{ int a,b,c; scanf("%d %d %d",&a,&b,&c); modify(a,b,c,1); } } system("pause"); }
ps:pushdown可以写在函数最上方,玄学写法我也不懂,先pushdown,wa了可以试试qwq
来源:https://www.cnblogs.com/StungYep/p/12254041.html