写在最前
写代码,就是不断地去模仿他人代码,总结出其中要点,再加上自己的体会。
线段树我一开始也是三段式:
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;
}
可能以后还会补上更典型的例子-