线段树-代码实现细节与技巧

来源:互联网 发布:淘宝怎么开店流程 编辑:程序博客网 时间: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;}

可能以后还会补上更典型的例子-

3 0