经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » C » 查看文章
解析·优化 ZKW线段树
来源:cnblogs  作者:迷失の风之旅人  时间:2018/10/31 9:05:23  对本文有异议

德鲁伊!大自然已经听命于我了!
死亡之翼长子奈法利安

ZKW天下第一!
摘自某群聊天记录

ZKW线段树,即非递归形式的线段树,出自终极大犇ZKW的论文《统计的力量》。与普通的线段树相比,ZKW线段树由于是非递归形式,效率极高,代码也极短,成为了OI比赛中极为实用的优化算法之一。虽然ZKW线段树无法处理有运算优先级的线段树问题,但是在一般的问题上和常数偏大的问题上总能带来极强的游戏体验。

ZKW线段树的建树

  1. 普通线段树
  2. 1
  3. / 2 3 <---------------"弱小,可伶又无助"
  4. / \
  5. 4 5
  1. ZKW线段树
  2. 1
  3. / 10 11 <---------------"天下第一!"
  4. / \ / 100 101 110 111

那么接下来进入我们的分析环节:小学生找规律
通过观察,我们可以发现:线段树对应叶子节点的下标和原数组的下标的差值是恒定的
事实上,这个值几乎恒等于线段树数组里叶子节点的数量。
事实上,该值\(num\)满足:\[num=2^{[log_2(n+1)]} \]
于是我们可以先将线段树建为一棵满二叉树,然后我们从叶子节点开始回溯即可。
定义

  1. #define maxn 10000
  2. int n,num;
  3. int minv[maxn<<2];

其中,minv为线段树数组,n为总节点数量,N即为上文提到的N。
那么完整的建树代码如下

  1. inline int ksm(int x,int y){
  2. int res=1;
  3. while(y){
  4. if(y&1)res*=x%p;
  5. x*=x%p;
  6. y>>=1;
  7. }
  8. return res;
  9. }
  10. inline void build(){
  11. scanf("%d", &n);
  12. N=ksm(2,log2(n+1));
  13. for(register int i=N+1;i<=N+n;i++)cin>>minv[i];
  14. for(register int i=N-1;i>=1;i--)minv[i]=minv[i<<1]+minv[i<<1|1];
  15. }

ZKW线段树的修改&查询

单点修改与单点查询

代码量很少,背模板即可

单点更新
  1. inline void update(int x,int k){
  2. for(register int i=x+N;i;i>>=1)tree[i]+=k;
  3. }
区间(单点)查询
  1. inline int query(int l,int r){
  2. int ans=0;
  3. for(l=N+l-1,r=N+r+1;s ^ r ^ 1;s>>=1,r>>=1){
  4. if(~s&1)ans+=tree[s ^ 1];
  5. if(r&1)ans+=tree[r ^ 1];
  6. }
  7. return ans;
  8. }
ZKW线段树单点操作
  1. #include<bits/stdc++.h>
  2. #define maxn 10000
  3. using namespace std;
  4. int n,N;
  5. int a[maxn];
  6. int ansv[maxn<<2];
  7. inline int ksm(int x,int y){
  8. int res=1;
  9. while(y){
  10. if(y&1)res*=x%p;
  11. x*=x%p;
  12. y>>=1;
  13. }
  14. return res;
  15. }
  16. inline void build(int n){
  17. N=ksm(2,log2(n+1));
  18. for(register int i=N+1;i<=N+n;i++)ansv[i]=a[i];
  19. for(register int i=N-1;i>=1;i--)ansv[i]=ansv[i<<1]+ansv[i<<1|1];
  20. }
  21. inline void update(int x,int k){
  22. for(register int i=x+N;i;i>>=1)ansv[i]+=k;
  23. }
  24. inline int query(int l,int r){
  25. int ans=0;
  26. for(l=N+l-1,r=N+r+1;s ^ r ^ 1;s>>=1,r>>=1){
  27. if(~s&1)ans+=tree[s ^ 1];
  28. if(r&1)ans+=tree[r ^ 1];
  29. }
  30. return ans;
  31. }

区间修改与区间查询

与普通线段树类似的,我们在ZKW线段树上也不能使用暴力的方式进行区间修改。在ZKW线段树上做暴力修改的复杂度甚至比普通线段树更高。同时,在ZKW线段树中我们仍然需要使用到lazy标记。然而不同的是,在ZKW线段树中我们会将lazy标记永久固化,即永远不对标记做pushdown操作。
我们假定现在指定了一个区间\([l,r]\),使得区间的每一个值全部加上\(k\),并使得\(l=2,r=10\)
\(l\)到了\([2,2]\)节点时,显然\([3,3]\)节点需要被标记上\(k\),那么接下来我们走到的\([2,3]、[0,3]\)节点都会被标记上\(k*1\),等我们到达\([0,7]\)节点时,因为\([4,7]\)已经被标记了\(k\),所以\([0,7]\)节点的值要加上\(k*(1+4)=k*5\),自然我们需要一个变量来存储\(k\)的系数。
需要注意的是,这次的修改要上推到根节点

  1. inline void update(int l,int r,int k) {
  2. int lNum=0,rNum=0,nNum=1;
  3. for(l=N+l-1,r=N+r+1;l ^ r ^ 1;l>>=1,r>>=1,nNum<<=1){
  4. ansv[l]+=k*lNum;
  5. ansv[r]+=k*rNum;
  6. if(~s&1){
  7. lazy[s ^ 1]+=k;
  8. ansv[s ^ 1]+=k*nNum;
  9. lNum += nNum;
  10. }
  11. if(t&1){
  12. lazy[t ^ 1]+=k;
  13. ansv[t ^ 1]+=k*nNum;
  14. rNum+=nNum;
  15. }
  16. }
  17. for(;l;l>>=1,r>>=1){
  18. ansv[l]+=k*lNum;
  19. ansv[r]+=k*rNum;
  20. }
  21. }

区间查询的过程类似。

  1. inline int query(int l, int r){
  2. int lNum=0,rNum=0,nNum=1;
  3. int ans=0;
  4. for(l=N+l-1,r=N+r+1;l ^ r ^ 1;l>>=1,r>>=1,nNum<<=1){
  5. if(lazy[l])ans+=lazy[l]*lNum;
  6. if(lazy[r])ans+=lazy[r]*rNum;
  7. if(~l&1){
  8. ans+=ansv[l ^ 1];
  9. lNum+=nNum;
  10. }
  11. if(r&1){
  12. ans+=ansv[r ^ 1];
  13. rNum+=nNum;
  14. }
  15. }
  16. for(;l;l>>=1,r>>=1) {
  17. ans+=lazy[l]*lNum;
  18. ans+=lazy[r]*rNum;
  19. }
  20. return ans;
  21. }
线段树区间操作代码
  1. #include<bits/stdc++.h>
  2. #define maxn 10000
  3. using namespace std;
  4. int n,N;
  5. int a[maxn];
  6. int ansv[maxn<<2],lazy[maxn<<2];
  7. inline int ksm(int x,int y){
  8. int res=1;
  9. while(y){
  10. if(y&1)res*=x%p;
  11. x*=x%p;
  12. y>>=1;
  13. }
  14. return res;
  15. }
  16. inline void build(int n){
  17. N=ksm(2,log2(n+1));
  18. for(register int i=N+1;i<=N+n;i++)ansv[i]=a[i];
  19. for(register int i=N-1;i>=1;i--)ansv[i]=ansv[i<<1]+ansv[i<<1|1];
  20. }
  21. inline void update(int l,int r,int k) {
  22. int lNum=0,rNum=0,nNum=1;
  23. for(l=N+l-1,r=N+r+1;l ^ r ^ 1;l>>=1,r>>=1,nNum<<=1){
  24. ansv[l]+=k*lNum;
  25. ansv[r]+=k*rNum;
  26. if(~l&1){
  27. lazy[l ^ 1]+=k;
  28. ansv[l ^ 1]+=k*nNum;
  29. lNum += nNum;
  30. }
  31. if(r&1){
  32. lazy[r ^ 1]+=k;
  33. ansv[r ^ 1]+=k*nNum;
  34. rNum+=nNum;
  35. }
  36. }
  37. for(;l;l>>=1,r>>=1){
  38. ansv[l]+=k*lNum;
  39. ansv[r]+=k*rNum;
  40. }
  41. }
  42. inline int query(int l, int r){
  43. int lNum=0,rNum=0,nNum=1;
  44. int ans=0;
  45. for(l=N+l-1,r=N+r+1;l ^ r ^ 1;l>>=1,r>>=1,nNum<<=1){
  46. if(lazy[l])ans+=lazy[l]*lNum;
  47. if(lazy[r])ans+=lazy[r]*rNum;
  48. if(~l&1){
  49. ans+=ansv[l ^ 1];
  50. lNum+=nNum;
  51. }
  52. if(r&1){
  53. ans+=ansv[r ^ 1];
  54. rNum+=nNum;
  55. }
  56. }
  57. for(;l;l>>=1,r>>=1) {
  58. ans+=lazy[l]*lNum;
  59. ans+=lazy[r]*rNum;
  60. }
  61. return ans;
  62. }
 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号