树状数组前置知识:二进制 lowbit的求法
((x^(x-1))&x) = lowbit(x)
可以写成宏,也就是#define lowbit(a) ((a^(a-1))&a)
或者这样也可以找到lowbit(x) = (~x+1)
lowbit的作用:提取最后一个数转换为二进制后的从后往前找第一个出现的1的位置
//
// main.cpp
// lowbit
//
// Created by 陈冉飞 on 2019/8/6.
// Copyright © 2019 陈冉飞. All rights reserved.
//
#include <iostream>
using namespace std;
#define lowbit(a) ((a^(a-1))&a)
int main(int argc, const char * argv[]) {
for (int i = 1; i <= 64; i ++) {
//输出时转换为二进制看从后往前第一个1出现的位置
cout <<i<<" "<< bitset<numeric_limits<unsigned int>::digits>(lowbit(i)) <<" "<< bitset<numeric_limits<unsigned int>::digits>((~i+1))<<endl;
}
return 0;
}
然后就是lowbit在树状数组中的应用的理解
给定一个奇数长度为9的数组
{4,3,7,5,8,2,6,1,9},然后用树状数组储存,
c
1
=
a
1
c_1 = a_1
c1=a1
c
2
=
a
1
+
a
2
=
∑
a
1
.
.
.
a
2
c_2 = a_1+a_2 = \sum{a_1...a_2}
c2=a1+a2=∑a1...a2
c
3
=
a
3
c_3 = a_3
c3=a3
c
4
=
a
1
+
a
2
+
a
3
+
a
4
=
∑
a
1
.
.
.
a
4
c_4 = a_1+a_2+a_3+a_4= \sum{a_1...a_4}
c4=a1+a2+a3+a4=∑a1...a4
c
5
=
a
5
c_5 = a_5
c5=a5
c
6
=
a
5
+
a
6
=
∑
a
5
.
.
.
a
6
c_6 = a_5+a_6= \sum{a_5...a_6}
c6=a5+a6=∑a5...a6
c
7
=
a
7
c_7 = a_7
c7=a7
c
8
=
a
1
+
a
2
+
a
3
+
a
4
+
a
5
+
a
6
+
a
7
+
a
8
=
∑
a
1
.
.
.
a
8
c_8 = a_1+a_2+a_3+a_4+a_5+a_6+a_7+a_8= \sum{a_1...a_8}
c8=a1+a2+a3+a4+a5+a6+a7+a8=∑a1...a8
c
9
=
a
9
c_9 = a_9
c9=a9
…
理解:可以理解为
c
2
c_2
c2管理
a
1
.
.
.
a
2
a_1...a_2
a1...a2,
c
4
c_4
c4管理
a
1
.
.
.
a
4
a_1...a_4
a1...a4,,
c
6
c_6
c6管理
a
5
.
.
.
a
6
a_5...a_6
a5...a6,而
c
8
c_8
c8管理前8个数。
然后结合lowbit发现这个在树形结构中的深度就等于这个数lowbit。
- 规律表示就是
x = l o w b i t ( i ) x = lowbit(i) x=lowbit(i)
c[i] = a[i-x]+a[i-x+1]+……+a[i]
。
因而可以用来构造c[i]
这个数组
树状数组解题总结:
- 通常在建树的过程中会有往树形结构中存值,体现在c[i]数组的赋值,c[i]数组的值就是树形结构,在存值过程中通过一个循环遍历
//创建树形结构的过程
while(x <= maxn){
/*
* 对c[x]进行操作
*c[x]用来储存某个区间的和:c[x] += num;
*c[x]用来储存某个区间的最值:c[x] = max(c[x],num);
*/
x += lowbit(x);
}
- 从树形结构中读取数据,同样利用树状数组c[i]的值来进行赋值。
//利用树形结构查找过程
//传进来的x,是指找到x之前的和
int sum = 0;
while(x>0){
/*
*查找区间的和 sum += c[x];//注意此时是利用树形结构的数据,所以要用c[i]数组要往前遍历,x-=lowbit(x),此时算的就是前缀和
*/
x -= lowbit(x);
}
/*---------------------*/
//传进来的是区间l,r
int ans = 0;
while(l != r){
//从右边界往左边找,通过比较区间长度与当前l,r的关系
for(r -= 1; l - r >= lowbit(r); r -= lowbit(r)){
//比较树状数组储存的最值与当前的这个值的大小
//刷新最值的操作 ans = max(ans,c[r]);
}
}
CSU1770
转换思维,对区间进行加一操作。可以利用lowbit对后面的所有区间进行加一,然后还要对不应该加的进行减一操作。最后是否开关灯体现在最后的数组的值来判断是否开闭。
//
// main.cpp
// 区间修改_csu1770
//
// Created by 陈冉飞 on 2019/8/6.
// Copyright © 2019 陈冉飞. All rights reserved.
//
#include <iostream>
using namespace std;
#include <cstring>
#define cl(a,b) memset(a,b,sizeof(a))
#define lowbit(a) (a&(-a))
#define maxn 100050
int a[maxn];
int cnt[maxn];
int n,m;
//单点查找,区间修改,n*logn 的区间修改复杂度
void change(int len,int d){
while (len <= maxn) {
// cout<<len<<endl;
cnt[len] += d;
len += lowbit(len);
}
}
int getsum(int x){
int sum = 0;
while (x>0) {
sum += cnt[x];
x -= lowbit(x);
}
return sum;
}
int main(int argc, const char * argv[]) {
int r,l,tem,i,temcnt;
while (~scanf("%d%d",&n,&m)) {
cl(cnt, 0);
cl(a,0);
for (i = 1; i <= n; i++) {
scanf("%d",&tem);
if (tem == 1) {
a[i] = 1;
}else{
a[i] = 0;
}
}
// cout<<"1"<<endl;
while (m--) {
// cout<<"2"<<endl;
scanf("%d%d",&l,&r);
//对l到n进行+1修改,对r+1到n进行-1修改
change(r+1, -1);
change(l, 1);
// for (i = 1; i <= n; i++) {
// printf("%d ",cnt[i]);
// }
}
// for (int i = 1; i <= n; i++) {
// cout<<i<<" "<<cnt[i]<<endl;
// }
//做完所有的操作后,再输出
for (i = 1; i <= n; i++) {
temcnt = getsum(i);
if (temcnt&1) {
cout<<(a[i]^1);
}
else cout<<a[i];
if (i != n) {
cout<<" ";
}
else cout<<endl;
// if (temcnt%2 == 1||temcnt%2 == -1) {
// cout<<"1 ";
// }else cout<<"0 ";
// printf("%d ",cnt[i]);
}
}
return 0;
}
//5 4 1 0 1 1 1 1 3 1 2 2 3 1 5
//0 0 0 0 0
hdu1754
通过一个m[]数组来记录子区间长度为lowbit®的区间范围的最大值,在改值的时候也要更新最大值。但注意原数组要一直保持是原来的数,cnt用来记录区间的最值。
//
// main.cpp
// 树状数组_板题_hdu1754
//
// Created by 陈冉飞 on 2019/8/6.
// Copyright © 2019 陈冉飞. All rights reserved.
//
#include <iostream>
//#include <bits/stdc++.h>
using namespace std;
#include <cstring>
#define cl(a,b) memset(a,b,sizeof(a))
typedef long long ll;
#define lowbit(a) (a&(a^(a-1)))
#define maxn 200050
int a[maxn]; //记录原来的
int m[maxn]; //记录最大值
int n,mm; //共有多少个元素 m为所有请求的个数
int getmax(int l,int r){
//范围最值是通过比较时时的区间长度,而在树状数组中自区间的长度是有lowbit决定的,所以一定要在又端点去变化
int ans = a[r];
while(l!=r){
for (r-=1; r-l >= lowbit(r); r -= lowbit(r)) {
ans = max(ans,m[r]);
}
ans = max(ans,a[r]);
}
return ans;
}
void add(int x,int y){
//更新值
a[x] = y;
while (x <= maxn) {
//然后通过lowbit 遍历到的都刷新最值
m[x] = max(y,m[x]);
x += lowbit(x);
}
}
int main(int argc, const char * argv[]) {
int T,i,tem,tema,temb,kase = 1;
// scanf("%d",&T);
char str[10];
while (~scanf("%d%d",&n,&mm)) {
// cout<<"Case "<<kase++<<":"<<endl;
cl(a, 0);
cl(m, 0);
for (i = 1; i <= n; i++) {
scanf("%d",&tem);
//通过add 数组将树状数组c放好
add(i, tem);
}
// //检验输出
// for (i = 2; i <= n; i++) {
// cout<<i<<" "<<a[i]<<" "<<sum(i)-sum(i-1)<<endl;
// }
while(mm--){
scanf("%s",str);
if (str[0] == 'Q') { //查询区间最大值
scanf("%d%d",&tema,&temb);
cout<<getmax(tema, temb)<<endl;
}else{
scanf("%d%d",&tema,&temb);
add(tema, temb);
}
// //每次都检验输出
// cout<<"check "<<n<<endl;
// for (int j = 1; j <= n; j ++) {
// cout<<j<<" "<<a[j]<<" "<<m[j]<<endl;
// }
}
}
return 0;
}