线段树-代码实现细节与技巧
来源:互联网 发布:淘宝怎么开店流程 编辑:程序博客网 时间:2024/05/21 17:30
写在最前
写代码,就是不断地去模仿他人代码,总结出其中要点,再加上自己的体会。
线段树我一开始也是三段式:
if (r<=mid) query(x+x, l, r);else if (l>mid) query(x+x+1, l, r);else query(x+x, l, mid), query(x+x+1, mid+1, r);
冗长且复杂,遇到多操作多询问的问题必定只能复制,开始打错了一点更正都要很久的时间。
其实虽然算法是固定的,但是打法就是多种多样的了。一开始只能跟着书上的标程来模仿,自然是很臃肿的。后来跟学长沟通交流之后,模仿了他的写法,再做了自己的一些修改。
从K-D树网上的模板我学到了指针的打法;LCT的模板也是网上的找的。善于模仿他人的代码并加为己用,是一种很好的学习码农题的一种方式。
线段树的两种实现方法
写线段树一般有两种方法:自顶向下和自底向上。
一般会使用自顶向下来编写:优点-易于理解,方便调试,打标记很方便。
自底向上这种方法参见zkw的统计的力量。优点:常数小,代码短。缺点:不易调试,打标记麻烦。(一般在简单的线段树题目中会很方便,但是原理并不好记,所以我放弃了这种方法)。
自顶向下我的编写是:
void update(int x, int l, int r){ if(opl<=l && r<=opr) { operate(x); return; } int mid = (l+r) >> 1; if(opl<=mid) update(x+x, l, mid); if(opr>mid) update(x+x+1, mid+1, r);}
其中主程序是这样写的:
opl = L, opr = R, update(1, 1, n);
例如我们想要操作的区间是(L,R),那就定义两个全局变量opl,opr,记录操作区间。
当(l,r)区间完全被包含就代表我可以进行操作了;否则就判断左右两个区间是否有被包含,递归操作。(比起写三个if要方便很多。)
多操作
而面对很多题目中,有各种的区间操作(区间加,区间乘),和区间查询(区间和,区间MIN),我们要写超多段类似的东西。虽然可以复制,但很容易写错。
运用全局变量,我们可以只写一个过程,来完成各种操作:
用opx来记录当前操作是什么:例如,如果有四种操作(两修改两查询),可以用1,2,3,4来分别代表它们:1代表1类修改,2代表2类修改,3代表1类查询,4代表2类询问。
同时用全局变量opans来记录当前的答案,来避免返回值造成的程序混乱(修改操作是没有返回值的,而询问操作是有返回值的,通过记录ans来规避询问操作有返回值这个问题,使得修改操作与询问操作能用一个程序搞定)。
void update(int x, int l, int r){ if(opl<=l && r<=opr) { if(opx==1) modify1(x); else if (opx==2) modify2(x); else if (opx==3) query1(x); else query2(x); return; } int mid = (l+r) >> 1; if(opl<=mid) update(x+x, l, mid); if(opr>mid) update(x+x+1, mid+1, r);}
区间操作、懒标记
对于区间操作,无论是线段树还是平衡树都避免不了这个问题。其中懒标记更是处理的重点。
我一般编三个小过程:
void pup(int x) //合并x的两个子区间中的信息(put-up)void modify(int x, int y) //对x的区间加上y的修改void pdw(int x) //将x区间的信息下传(put-down)
那我的程序可以变成这样:
void update(int x, int l, int r){ if(opl<=l && r<=opr) { operate(x); return; } int mid = (l+r) >> 1; pdw(x); if(opl<=mid) update(x+x, l, mid); if(opr>mid) update(x+x+1, mid+1, r); if(opx==修改操作) pup(x);}
Codeforces266E - More Queries to Array
#include <cstdio>#include <cstring>#include <algorithm>using namespace std;typedef long long LL;const int N = 1e5 + 5;const int T = 6;const int MD = 1e9 + 7;int n, m, a[N], tag[N<<2];int opl, opr, op;int c[T][T];int tax[T][N], sum[T][N];int pt[T];struct rc{ int a[T]; int siz; rc(){memset(a, 0, sizeof a); siz=0;}}f[N<<2];rc merge(rc p, rc q) // 合并的过程{ if(!p.siz) return q; rc r; r.siz = p.siz + q.siz; int s = p.siz; for(int i=0; i<T; i++) r.a[i] = p.a[i]; for(int i=0; i<T; i++) for(int j=0; j<=i; j++) (r.a[i] += ((LL(c[i][j]) * tax[i-j][s]) % MD * q.a[j]) % MD) %= MD; return r;}void put(int x, int p) // 修改的过程{ int s = f[x].siz; for(int i=0; i<T; i++) f[x].a[i] = (LL(sum[i][s]) * p) % MD; tag[x] = p;}void lay(int x) // 下传标记,会用到修改的过程{ if(tag[x]>=0) put(x+x, tag[x]), put(x+x+1, tag[x]), tag[x]=-1;}rc query(int x, int l, int r) // 我这里把查询和修改分开来写,其实是可以合并起来的:使用全局变量opx{ if(opl<=l && r<=opr) return f[x]; int md = (l+r) >> 1; rc ret; lay(x); if(opl<=md) ret = merge(ret, query(x+x, l, md)); if(opr>md) ret = merge(ret, query(x+x+1, md+1, r)); return ret;}void update(int x, int l, int r){ if(opl<=l && r<=opr) {put(x, op); return;} int md = (l+r) >> 1; lay(x); if(opl<=md) update(x+x, l, md); if(opr>md) update(x+x+1, md+1, r); f[x] = merge(f[x+x], f[x+x+1]);}void bud(int x, int l, int r) // 建立线段树的操作{ if(l==r) {f[x].siz=1, put(x, a[l]); return;} int md = (l+r) >> 1; tag[x] = -1; bud(x+x, l, md), bud(x+x+1, md+1, r); f[x] = merge(f[x+x], f[x+x+1]);}int rd(){ int c; while(c=getchar(), c<=' '); return c;}int main(){ scanf("%d%d", &n, &m); for(int i=1; i<=n; i++) scanf("%d", &a[i]); for(int i=1; i<=n; i++) tax[0][i] = 1; for(int i=1; i<T; i++) for(int j=1; j<=n; j++) tax[i][j] = (LL(tax[i-1][j]) * j) % MD; for(int i=0; i<T; i++) for(int j=1; j<=n; j++) sum[i][j] = (sum[i][j-1] + tax[i][j]) % MD; for(int i=0; i<T; i++) { c[i][0] = c[i][i] = 1; for(int j=1; j<i; j++) c[i][j] = (c[i-1][j] + c[i-1][j-1]) % MD; } bud(1, 1, n); for(int i=1; i<=m; i++) { if(rd()=='?') { scanf("%d%d%d", &opl, &opr, &op); rc ret = query(1, 1, n); printf("%d\n", ret.a[op]); } else { scanf("%d%d%d", &opl, &opr, &op); update(1, 1, n); } } return 0;}
可能以后还会补上更典型的例子-
- 线段树-代码实现细节与技巧
- 线段树-代码实现细节与技巧
- java代码实现线段树
- caffe代码阅读1:Layer的介绍与实现细节
- matlab 可视化技巧与细节
- 【Java】编程细节与技巧
- UItableViewCell一些实用细节与技巧
- 算法细节系列(28):线段树
- 细节技巧
- 深度学习中的数学与技巧(4): BatchNormalization 代码实现
- 线段树实现区间最值查询代码
- 滚动条 实现的细节代码 SCROLLINFO
- 滚动条 实现的细节代码 SCROLLINFO
- 滚动条 实现的细节代码 SCROLLINFO
- UINavigationController纯代码实现细节提示
- Java 代码细节与优化(一)
- 线段树代码整理
- 代码细节
- 网络变压器在以太网中的作用
- ZZULIOJ 1913: 小火山的计算能力(栈模拟)
- 系统标签控制器的使用——UITabBarController
- Bad Request(Invalid Hostname)错误分析
- 金融行业的BI应用分析
- 线段树-代码实现细节与技巧
- linux下bin安装mysql的问题
- 掌握好这几点 你的软文不愁没流量!
- solr_4.5.0_04:配置 tomcat 服务器
- 100天土鸡饲养计划(22)
- 欢迎使用CSDN-markdown编辑器
- Oracle NULL操作
- RSYNC
- 音视频(H264+G711)打包AVI文件