学习笔记——树状数组应用扩展
林则徐
·
2017-12-10 22:28:48
·
个人记录
知识回顾
树状数组,是在每个位置i 存储长度为lowbit(i) 的区间之和,从而实现logN 时间复杂度内单点修改、单点前缀和查询。附上代码(感谢@sjkmost神犇帮忙码的代码):
#define lowbit(x) (x) & -(x)
template < typename T, int N >
struct BIT {
T c[N];
int n;
void modify(int i, const T &rhs) { //单点修改
for (; i <= n; i += lowbit(i))
c[i] = c[i] + rhs;
}
T query(int i) { //单点前缀和查询
T k = T();
for (; i; i -= lowbit(i))
k = k + c[i];
return k;
}
};
模板:洛谷P3374
扩展#1:区间修改,单点查询
树状数组的基本功能是单点修改、单点前缀和查询,如何利用此性质呢?其实并不复杂,可以存储差分数组dif ,这样单点的前缀和便是该点的值,修改区间[l..r] 的话就转化成修改l 和r+1 两点了。附上代码:
if( opt == 1){ //区间修改
a = read() , b = read() , c = read();
dif.modify(a , c) ;
dif.modify( b + 1 , -c) ;
}
else{ //单点查询
pint( dif.query( read() ) );
pchar('\n');
}
模板:洛谷P3368
扩展#2: 区间修改,区间查询
乍一看这不可能用树状数组实现,我也想了好长时间,最后还是@sjkmost神犇给我指点迷津才想到。
首先我们想一想:若用树状数组存储差分,点i 的前缀和有什么性质?
a_1$ = $dif_1
a_2$ = $dif_1$ + $dif_2
a_3$ = $dif_1$ + $dif_2$ + $dif_3
......
a_i$ = $dif_1$ + $dif_2$ + $dif_3$ + ... + $dif_i
仔细观察,就会发现点1被加了i 次、点2被加了i-1 次、点3被加了i-2 次......点i 被加了1次,呈一个金字塔状。这样看似可以维护了。
不过还是不能直接维护,我们再来观察一下,不难发现:若点i 再加上i-1 次、点i-1 再加上i-2 次、点i-2 再加上i-3 次...
a_1$ = $dif_1$ + $dif_2$ + $dif_3$ + ... + $dif_i
a_2$ = $dif_1$ + $dif_2$ + $dif_3$ + ... + $dif_i
a_3$ = $dif_1$ + $dif_2$ + $dif_3$ + ... + $dif_i
......
a_i$ = $dif_1$ + $dif_2$ + $dif_3$ + ... + $dif_i
就恰好成为i{a_i} 了。i{a_i} 很好求,现在问题就转化成维护
附上代码:
```cpp
if( opt == 1 ) { //区间修改
k = read() ;
dif1.modify( x , k ) ;
dif1.modify( y + 1 , -k ) ;
dif2.modify( x , k * x ) ;
dif2.modify( y + 1 , ( -k ) * ( y + 1 ) ) ;
}
else { //区间查询
pint( ( dif1.query( y ) * ( y + 1 ) - dif2.query( y ) ) - ( dif1.query( x - 1 ) * x - dif2.query( x - 1 ) ) ) ;
pchar('\n') ;
}
```
模板: 洛谷P3372(线段树模板1)
##### 总结
树状数组是一个很实用的数据结构,具有常数小、码量小、不易写挂等优点。虽然其功能看似局限性较大,但只要肯思考、肯创新,就会发现很多很多的扩展应用。学OI,就得勤思考,勤创新。