树状数组按照一定的规律来存储原数组的一些区间的和,以此可将查询和修改的复杂度都优化到O(logn),规律如下:
从表面上看,树状数组C[i](1<=i<=n)中,当i为奇数的时候C[i]存的是A[i];当i为偶数时,如果i能表示为2的次幂则C[i]=A[1]+A[2]+···+A[i],如果i不能表示为2的次幂则C[i]=A[i-1]+A[i]。但还有更深层次的规律,其实C[i]=A[i-k+1]+A[i-k+2]+···+A[i](1<=i<=n);k=1<<x,x是i的二进制表示下右边0的个数,即
int lowbit(int i) { return i&(-i); }lowbit()函数的返回值就是k。例如,当i=5时, int类型,4字节,占32位:
5的二进制为: 00000000 00000000 00000000 00000101
5的反码为: 11111111 11111111 11111111 11111010
-5的二进制为:11111111 11111111 11111111 11111011
lowbit(5)=1;所以,C[5]=A[5];
PS:
在计算机中,负数以其正值的补码形式表达。
反码表示法规定:正数的反码与原码相同,负数的反码为对该数的原码除符号位外各位取反。
补码表示法规定:正数的补码与原码相同,负数的补码为对该数的原码除符号位外各位取反,然后在最后一位加1。
例题:NYOJ_士兵杀敌(二)
题意:插点问线
思路:向后修改,向前求和
CODE:
/** **author :Or_me ** ╭︿︿︿╮ {/ a c /} ( (oo) ) ︶︶︶ ** ** **NYOJ_116题** ** 2014 年 7月 31日** **/ #include <set> #include <map> #include <cmath> #include <queue> #include <stack> #include <vector> #include <cstdio> #include <string> #include <cctype> #include <climits> #include <cstring> #include <cstdlib> #include <iostream> #include <algorithm> using namespace std; const int MAX=1000001; int c[MAX]; int N,M; int lowbit(int t)//lowbit(0)=0,会形成死循环,所以数组下标从1开始 { return t&-t; } void Modify(int m,int n)//向后修改 { for (int k=m; k<=N; k+=lowbit(k)) c[k]+=n; } int sum(int x)//向前求和 { int ans=0; for (int i=x; i>=1; i-=lowbit(i)) ans+=c[i]; return ans; } int getsum(int i,int j) { return sum(j)-sum(i-1); } int main() { int m,n,v; char ch[7]; scanf("%d%d",&N,&M); for (int i=1; i<=N; i++) { scanf("%d",&v); Modify(i,v); } for (int i=1; i<=M; i++) { scanf("%s%d%d",ch,&m,&n); if (ch[0]=='A') Modify(m,n); else if (ch[0]=='Q') printf("%d\n",getsum(m,n)); } return 0; }
例题:NYOJ_士兵杀敌(四)
题意:插线问点
思路:向前修改,向后求和
CODE:
/** **author :Or_me ** ╭︿︿︿╮ {/ a c /} ( (oo) ) ︶︶︶ ** ** ** 题** ** 2014 年 月 日** **/ #include <set> #include <map> #include <cmath> #include <queue> #include <stack> #include <vector> #include <cstdio> #include <string> #include <cctype> #include <climits> #include <cstring> #include <cstdlib> #include <iostream> #include <algorithm> using namespace std; const int MAX=1000001; int c[MAX]; int N,M,T; int lowbit(int t) { return t&-t; } void Modify(int m,int n)//向前修改 { for (int k=m; k>0; k-=lowbit(k)) c[k]+=n; } int sum(int x)//向后求和 { int ans=0; for (int i=x; i<=M; i+=lowbit(i)) ans+=c[i]; return ans; } int main() { int a,b,t,x; char ch[7]; memset(c,0,sizeof(c)); scanf("%d%d",&T,&M); for (int i=1; i<=T; i++) { scanf("%s",ch); if (ch[0]=='A') { scanf("%d%d%d",&a,&b,&t); Modify(a-1,-t); Modify(b,t); } else { scanf("%d",&x); printf("%d\n",sum(x)); } } return 0; }例题: NYOJ_士兵杀敌(五)
题意:插线问线
思路:树状数组离线版,查询在修改之后
CODE:
/** **author :Or_me ** ╭︿︿︿╮ {/ a c /} ( (oo) ) ︶︶︶ ** ** ** 题** ** 2014 年 月 日** **/ #include <set> #include <map> #include <cmath> #include <queue> #include <stack> #include <vector> #include <cstdio> #include <string> #include <cctype> #include <climits> #include <cstring> #include <cstdlib> #include <iostream> #include <algorithm> using namespace std; const int MAX=1000001; int c[MAX]; int main() { int N,C,Q; int a,b,x; scanf("%d%d%d",&N,&C,&Q); while (C--) { scanf("%d%d%d",&a,&b,&x); c[a]+=x; c[b+1]-=x; } for (int i=1;i<=N;i++)//得到每个士兵的战功 { c[i]=c[i-1]+c[i]; } for (int i=1;i<=N;i++)//得到前缀和 { c[i]=(c[i-1]+c[i])%10003;//保存的是余数 } while (Q--) { scanf("%d%d",&a,&b); printf("%d\n",(c[b]-c[a-1]+10003)%10003);//+10003,防止结果小于0 } return 0; }