咱们在这篇博客里将具体介绍一种超级毒瘤超级高效的算法
线段树html
首先来认识一下线段树
什么是线段树呢:
线段树是一种二叉树,也就是对于一个线段,咱们会用一个二叉树来表示。好比说一个长度为6的线段,咱们能够表示成这样
这个图是什么意思呢?
node
tree[rt].sum = tree[l].sum + tree[r].sum
struct node{ int l,r,sum;//l表示左儿子 r表示右儿子 sum表示当前节点存储的权值 }tree[maxn*4]; void build(int i,int l,int r){ tree[i].l = l;tree[i].r = r; if(l == r){ tree[i].sum = a[l];//a数组存储给出的数组初始值 return; } int mid = (l+r)/2; build(i*2,l,mid); build(i*2+1,mid+1,r); tree[i].sum = tree[i*2].sum+tree[i*2+1].sum; return; }
这就是线段树的建树方法 若是你要问为何咱们要花好几倍的内存去建树来完成一个数组就能完成的事情 那就是由于咱们须要让这个超级大的数组去干一些比较困难的事情
那什么是比较困难的事情呢 让咱们进入下个专题算法
for(int i = 1;i<=5;++i){ans+=a[i]};
所以用代码怎么实现呢 也很简单
先让咱们总结一下线段树的查询方式:数组
int search(int rt,int l,int r){ if(tree[rt].r < l ||tree[rt].l > r)return 0; if(tree[rt].l >= l && tree[rt].r <= r)return tree[rt].sum; int ans = 0; if(tree[rt*2].r >= l)ans += search(2*rt,l,r); if(tree[rt*2+1].l <= r)ans += search(2*rt+1,l,r); return ans; }
区间修改和单点查询的方法有不少
为了一会对pushdown的讲解 咱们这里说一种比较便于下面理解的方法
区间修改和区间查询很像函数
void build(int l,int r,int rt){ tree[rt].num=0; tree[rt].l=l; tree[rt].r=r; if(l==r) return ; int mid=(r+l)/2; build(l,mid,rt*2); build(mid+1,r,rt*2+1); } void add(int rt,int l,int r,int k){ if(tree[rt].l>=l && tree[rt].r<=r){ tree[rt].num+=k; return ; } if(tree[rt*2].r>=l) add(rt*2,l,r,k); if(tree[rt*2+1].l<=r) add(rt*2+1,l,r,k); }
前方高难
看到这样的题你或许会想 不就是上边那两种放在一块儿吗
可是若是你真的这样写完了代码你会发现 WA
为何呢
先来回想一下刚才的操做:将区间加上标记 最终查询的时候去从上往下找 将标记累加最后再加上初始值
可是这样真的能够吗?
答案是否认的 缘由很简单:若是你要求1~3区间的和 而你刚刚将3~5的区间加上标记 由于1~3并不包含3~5的标记 因此咱们计算后的结果并非加k以后的和 而是初始值的和
那如何解决这个问题呢? 也很简单:只要将咱们的标记k下放到i的儿子不就行了吗
因此咱们的算法雏形就出来了(这也是线段树最毒瘤并且难调最具备魅力的地方)ui
tree[rt].sum += k*(tree[rt].r - tree[rt].l + 1)
void pushdown(long long rt){ if(tree[rt].lazy != 0){//若是当前区间已经被标记 tree[rt*2].lazy += tree[rt].lazy;//下放到左儿子 tree[rt*2+1].lazy += tree[rt].lazy;//下放到右儿子 long long mid = (tree[rt].l + tree[rt].r)/2; tree[rt*2].sum += tree[rt].lazy*(mid - tree[rt*2].l + 1);//更新左儿子的值 tree[rt*2+1].sum += tree[rt].lazy*(tree[rt*2+1].r - mid);//更新右儿子的值 tree[rt].lazy = 0;//清空当前节点的懒惰标记 } return; } void add(long long rt,long long l,long long r,long long k){ if(tree[rt].l >= l && tree[rt].r <= r){//若是当前区间彻底包含在目标区间直接更新而且标记懒惰标记 tree[rt].sum += k*(tree[rt].r-tree[rt].l+1);//更新当前区间的权值 tree[rt].lazy += k;//增长懒惰标记 return; } pushdown(rt);//下放懒惰标记 if(tree[rt*2].r >= l)add(rt*2,l,r,k);//递归更新左儿子 if(tree[rt*2+1].l <= r)add(rt*2+1,l,r,k);//递归更新右儿子 tree[rt].sum = tree[rt*2].sum+tree[rt*2+1].sum;//更新当前节点的权值 return; }
区间查询的时候和以前几乎同样 不一样的是要进行懒惰标记的下放以后在累加spa
long long search(long long rt,long long l,long long r){ if(tree[rt].l >= l && tree[rt].r <= r)return tree[rt].sum;//若是当前区间彻底包含在目标区间内 直接返回当前区间的权值 if(tree[rt].r < l || tree[rt].l > r)return 0;//若是当前区间和目标区间彻底没有关系 直接返回0 pushdown(rt);//下放懒惰标记 long long s = 0; if(tree[rt*2].r >= l)s += search(rt*2,l,r); if(tree[rt*2+1].l <= r)s += search(rt*2+1,l,r); return s;//最后返回这个区间的和 }
线段树模型大概就是这个样子(线段树仍是比较受出题人青睐的难道是由于难调??)
附上练习攻略:
简单线段树建议用洛谷P3374【模板】树状数组1
洛谷P3368【模板】树状数组2练习板子
若是简单线段树没有问题了请食用进阶线段树
通关攻略:洛谷P3372【模板】线段树1
洛谷P3373【模板】线段树2
洛谷P6242【模板】线段树3code
谢谢观看
点个关注>_<htm