lowbit函数:x&-x
- 思想:lowbit函数找的是二进制最右边的1。计算机机器语言中,-x实际上是x的补码,补码是x取反加1。
- 举个例子 x = 00001100, -x = 11110011 + 1 = 11110100。通过按位取余这样就可以愉快的找到最右边的1了。思想和巧妙,看看例子应该好理解。
- 找到最右边的1有什么用?x+lowbit(x)可以使最后一位1向前进。还是有点晕吧。看看下面的例子就明白了。
最经典的栗子:
- C1 = A1
- C2 = A1 + A2
- C3 = A3
- C4 = A1 + A2 + A3 + A4
- C5 = A5
- C6 = A5 + A6
- C7 = A7
- C8 = A1 + A2 + A3 + A4 + A5 + A6 + A7 + A8
- 设节点编号为x,那么这个节点管辖的区间为2^k(其中k为x二进制末尾0的个数)个元素。
举个例子,有几个数可以通过lowbit产生0100呢?0001--lowbit-->0010-->lowbit-->0011-->lowbit-->0100,有三个数可以通过lowbit产生0100,加上它本身,总共维护了2^2个数的和。比如5(0101)没有数可以通过lowbit产生,所以维护它本身一个。6(0110)可以有5(0101)lowbit产生,所以维护两个。
大家有没有发现要通过lowbit产生X1XX(X代表未知数),只能通过X0XX+lowbit产生,而后面的XX可以是01,10,11,2^k怎么得出来该明白了吧!以下就是add(i,x)函数了
void add(int i,int x){ //第i个位置加上x
while(i <= n){
d[i] += x;
i += lowbit(i);
}
}
我们可以看到每个以Ci维护的只是一段区间和,那我们求前缀和只需找到对应的几个Ci即可。
比如我们要求前6(0110)个数的前缀和为C6 + C4。C6维护的是a[5]+a[6]。现在只需x -= lowbit(x)。0110-0010=0100。而0100永远也不可能lowbit到0110(上面说过了),所以0110和0100维护的区间是完全没有关系的。0100维护的是a[1]+a[2]+a[3]+a[4]。正好找到前缀和。
为什么这样可行呢?0110后面维护2^1个数,0100维护的是2^2个数。加起来正好是6 = 0010 + 0100。再比如11100 = 00100 + 01000 + 10000,而这些数都可以通过lowbit产生,通过维护的个数和,我们很容易能够验证这样的方法可行的。一下是求和函数。
int sum(int i){
int s = 0;
while(i > 0){
s += d[i];
i -= lowbit(i);
}
return s;
}
单点更新单点查询
luogu3374
代码:
#include <cstdio>
using namespace std;
typedef long long ll;
int const N = 500000 + 10;
int d[N],n,m;
int lowbit(int x){return x&-x;}
ll sum(int i){ //区间询问
ll sum = 0;
while(i){
sum += d[i];
i -= lowbit(i);
}
return sum;
}
void add(int i,int x){ //构造线段树,在第i个位置上加x
while(i<=n){
d[i] += x;
i += lowbit(i);
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
int a; scanf("%d",&a);
add(i,a);
}
while(m--){
int flag,x,y;
scanf("%d%d%d",&flag,&x,&y);
if(flag == 1) add(x,y);
else printf("%lld\n",sum(y) - sum(x-1));
}
}
区间更新单点查询
luogu3368
题解:
- 利用差分的思想,新建一个数组c[i] = a[i] - a[i-1],那么a[i] = c[i] + a[i-1]。
- 利用迭代得:a[i] = c[i] + a[i-1] = c[i] + c[i-1] + a[i-2] = c[i] + c[i-1] + c[i-2] + c[1] + a[0]。所以树状数组维护c[i]即可
- 如何更新区间[x,y]。直接add(x,val),add(y+1,-val)。
- 可以这样理解:a[n]=c[1]+c[2]+……+c[n] = (a[1]-a[0])+(a[2]-a[1])+……+(a[n]-a[n-1]+val) = a[n]+val。只要在x处加val,区间[x,y]内的每一个数都加了val。而y+1处减val,那么y后面的又恢复正常。
代码:
#include <bits/stdc++.h>
using namespace std;
int const N = 500000 + 10;
int n,m,d[N],a[N];
int lowbit(int x){return x&-x;}
void add(int i,int x){
while(i <= n){
d[i] += x;
i += lowbit(i);
}
}
int sum(int i){
int s = 0;
while(i > 0){
s += d[i];
i -= lowbit(i);
}
return s;
}
int main(){
scanf("%d%d",&n,&m);
int flag,k,x,y;
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=n;i++) add(i,a[i]-a[i-1]);
for(int i=1;i<=m;i++){
scanf("%d",&flag);
if(flag == 1){
scanf("%d%d%d",&x,&y,&k);
add(x,k); add(y+1,-k);
}else{
scanf("%d",&x);
printf("%d\n",sum(x));
}
}
return 0;
}
区间修改区间查询
Code1082
题解:
-
区间查询,所以维护a[1]+a[2]+……+a[n]的前缀和
-
a[1]+a[2]+……+a[n] = c[1]+(c[1]+c[2])+(c[1]+c[2]+c[3])+……+(c[1]+c[2]+……+c[n]) = n*(c[1]+c[2]+……+c[n])-(0*c[1]+1*c[2]+2*c[3]+……+(n-1)*c[n])
-
前面c[1]+c[2]+……+c[n]这一段的区间和可以维护,为了维护后面这一段,我们新建数组c2[i] = (i-1)*c[i]即可。
-
每次c[i]修改量为val,那么c2[i]=(i-1)(c[i]+val) = (i-1)*c[i]+(i-1)*val。所以修改偏移量为(i-1)*val
代码:
#include <bits/stdc++.h>
using namespace std;
int const N = 200000 + 10;
typedef long long ll;
ll c1[N],c2[N],a[N];
int n,m,k;
ll lowbit(ll x){return x&-x;}
void add(ll c[N],ll i,ll x){
while(i <= n){
c[i] += x;
i += lowbit(i);
}
}
ll getsum(ll c[N],ll i){
ll s = 0;
while(i > 0){
s += c[i];
i -= lowbit(i);
}
return s;
}
int main(){
ll x,y,v;
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
for(int i=1;i<=n;i++) add(c1,i,a[i]-a[i-1]), add(c2,i,(i-1)*(a[i]-a[i-1]));
scanf("%d",&m);
for(int i=1;i<=m;i++){
scanf("%d%lld%lld",&k,&x,&y);
if(k == 1) scanf("%lld",&v), add(c1,x,v), add(c1,y+1,-v), add(c2,x,v*(x-1)), add(c2,y+1,-y*v);
else printf("%lld\n",(y*getsum(c1,y)-getsum(c2,y)) - ((x-1)*getsum(c1,x-1)-getsum(c2,x-1)));
}
}
loj10114
题解:
左右括号的方法。在一个区间内种树,相当于加一对括号。用树状数组维护从起始到这个位置的左右括号的数量。区间内有左括号那么一定有这一种类型的树,只有离开了对应的右括号这种树才没有了。所以为了统计区间[x,y]内的树种类,只需把y左边左括号的个数-(x-1)左边右括号的个数即可。
代码:
#include <bits/stdc++.h>
using namespace std;
int const N = 50000 + 10;
int c1[N],c2[N];
int n,m;
int lowbit(int x){return x&-x;}
void add(int c[N],int i){
while(i <= n){
c[i]++;
i += lowbit(i);
}
}
int getsum(int c[N],int i){
int s = 0;
while(i > 0){
s += c[i];
i -= lowbit(i);
}
return s;
}
int main(){
int k,x,y;
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d%d%d",&k,&x,&y);
if(k == 1){
add(c1,x), add(c2,y); //c1记录左括号的个数,c2记录右括号的个数
}else{
printf("%d\n",getsum(c1,y) - getsum(c2,x-1));
}
}
}
POJ2155
题解:
- 这是一道二维树状数组模板题。二维和一维套路是一样的。看看代码就明白了。
- 翻转一个区间相当于区间的每个数加1,最后是0还是1就模2即可。
- 树状数组维护区间和,(x1,y1)(x2+1,y2+1)(x1,y2+1) (x2+1,y1)都要加1。画一个矩形框框选一下就明白了。
懒得画图。
代码:
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <vector>
#include <iostream>
using namespace std;
int const N = 1000 + 10;
int n,m,c[N][N];
int lowbit(int x){return x&-x;}
void add(int x,int y){
for(int i=x;i<=n;i+=lowbit(i))
for(int j=y;j<=n;j+=lowbit(j))
c[i][j]++;
}
int sum(int x,int y){
int s = 0;
for(int i=x;i>0;i-=lowbit(i))
for(int j=y;j>0;j-=lowbit(j))
s += c[i][j];
return s;
}
int main(){
int T;
scanf("%d",&T);
bool first = true;;
while(T--){
if(first) first = false;
else printf("\n");
memset(c,0,sizeof(c));
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
char p;
scanf(" %c",&p);
if(p == 'C'){
int x1,y1,x2,y2;
scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
add(x1,y1);
add(x2+1,y1);
add(x1,y2+1);
add(x2+1,y2+1);
}else{
int x,y;
scanf("%d%d",&x,&y);
printf("%d\n",sum(x,y)%2);
}
}
}
return 0;
}
POJ2299
题解:
这是一道树状数组求逆序数的经典题目。注意要离散化,最后要开long long。
代码
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <vector>
#include <iostream>
using namespace std;
typedef long long ll;
int const N = 500000 + 10;
int a[N],n,d[N];
ll ans;
vector<int>v;
int lowbit(int x){return x&-x;}
void add(int i,int x){
while(i <= n){
d[i] += x;
i += lowbit(i);
}
}
int sum(int i){
int s = 0;
while(i){
s += d[i];
i -=lowbit(i);
}
return s;
}
int main(){
while(cin>>n&&n){
v.clear();
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
v.push_back(a[i]);
}
sort(v.begin(),v.end());
v.erase(unique(v.begin(),v.end()),v.end());
memset(d,0,sizeof(d));
ans = 0;
for(int i=1;i<=n;i++){
int k = lower_bound(v.begin(),v.end(),a[i]) - v.begin();
ans += (i-1-sum(++k));
add(k,1);
}
printf("%lld\n",ans);
}
return 0;
}
POJ3067
题解:
又是一道求逆序数的题目。先排序,x升序,x相同y升序。注意要long long,数据范围不准确。
代码:
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <vector>
#include <iostream>
using namespace std;
int const N = 1000 + 10;
typedef long long ll;
int n,m,k,d[N];
ll ans;
struct Road
{
int x,y;
bool operator < (const Road &e) const{
return e.x == x ? y < e.y : x < e.x;
}
}road[N*N];
int lowbit(int x){return x&-x;}
void add(int i,int x){
while(i <= m){
d[i] += x;
i += lowbit(i);
}
}
int sum(int i){
int s = 0;
while(i){
s += d[i];
i -= lowbit(i);
}
return s;
}
int main(){
int T;
scanf("%d",&T);
int caser = 0;
while(T--){
scanf("%d%d%d",&n,&m,&k);
for(int i=0;i<k;i++) scanf("%d%d",&road[i].x,&road[i].y);
sort(road,road+k);
memset(d,0,sizeof(d));
ans = 0;
for(int i=0;i<k;i++){
ans += (i - sum(road[i].y));
add(road[i].y,1);
}
printf("Test case %d: %lld\n",++caser,ans);
}
return 0;
}
POJ2352
题解:
又是一道求逆序数的题目。题目顺序都处理好了。注意向右平移一个单位,因为0号位置也有star
代码:
#include <bits/stdc++.h>
using namespace std;
int const N = 15000 + 10;
int const M = 32000 + 10;
int n,d[M],num[N];
int lowbit(int x){return x&-x;}
void add(int i,int x){
while(i <= M){
d[i] += x;
i += lowbit(i);
}
}
int sum(int i){
int s = 0;
while(i > 0){
s += d[i];
i -= lowbit(i);
}
return s;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){ //0好位置有
int x,y;
scanf("%d%d",&x,&y);
++x;
num[sum(x)]++;
add(x,1);
}
for(int i=0;i<=n-1;i++) printf("%d\n",num[i]);
return 0;
}