树状数组的特点:可以动态地求前缀和或任何可合并操作的问题,当插入一个数字时,只需要logn的时间复杂度就可以更新所有的前缀和。
1.树状数组构建的函数:
change函数用于更新树状数组,每在x位置插入一个数字,就把所有大于x的前缀更新,这个函数也用于初始化树状数组。
query函数用于查询从1到x的区间和,通过二进制进行。
int s[510000];
int n;
int lowbit(int x){
return x&-x;
}
void change(int x,int k)
{
while(x<=n) s[x]+=k,x+=lowbit(x);
}
int query(int x){//查询区间和
int t=0;
while(x) t+=s[x],x-=lowbit(x);
return t;
}
2.应用:
P3374 【模板】树状数组 1 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
洛谷p3374:点修,区查,简单的树状数组函数操作可以完成。前缀和查询区间。
注意:树状数组s必须从1开始储存
#include <iostream>
#define int long long
using namespace std;
int s[510000];
int n;
int lowbit(int x){
return x&-x;
}
void change(int x,int k)
{
while(x<=n) s[x]+=k,x+=lowbit(x);
}
int query(int x){//查询区间和
int t=0;
while(x) t+=s[x],x-=lowbit(x);
return t;
}
signed main(){
int x,m;cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>x;change(i,x);
}
for(int i=1;i<=m;i++){
int t;cin>>t;
int x,y;
if(t==1){
cin>>x>>y;
change(x,y);
}else if(t==2){
cin>>x>>y;
cout<<query(y)-query(x-1)<<endl;
}
}
return 0;
}
P3368 【模板】树状数组 2 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
洛谷p3368:区修,点查,当进行区间操作的时候需要构建差分数组。此时树状数组求的前缀和变为差分数组的前缀和,差分数组可以具体求出某个点的坐标。
这里修改区间的时候只需要在左侧l加上要修改的值和右侧+1的位置减去修改的值,就可以改掉整个区间。
#include <iostream>
#define int long long
using namespace std;
int a[510000];
int s[510000];
int n;
int lowbit(int x){
return x&-x;
}
void change(int x,int k)
{
if(x==0) x=n+1;
while(x<=n) s[x]+=k,x+=lowbit(x);
}
int query(int x){
int t=0;
while(x) t+=s[x],x-=lowbit(x);
return t;
}
signed main(){
int x,m;cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=n;i>=1;i--){
a[i]-=a[i-1];
}
for(int i=1;i<=n;i++){
change(i,a[i]);
}
for(int i=1;i<=m;i++){
int t;cin>>t;
int x,y,z;c
if(t==1){
cin>>x>>y>>z;
change(x,z);
change(y+1,-z);
}else if(t==2){
cin>>x;
cout<<query(x)<<endl;
}
}
return 0;
}
P1908 逆序对 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
洛谷P1908:离散化+树状数组,天才的思路,即通过树状数组动态地查询前缀和,用前缀和表示出小于该数字且位置大于该数字的数量。
具体的流程是这样:
创建一个结构体,存数字的位置pos和值val。先按val的大小降序排序,如果val相同,再按pos的大小降序排序(对相同数字的处理)。
排序之后,先放大的数字(这样每次查询前缀和区间的时候,前缀和的结果就是大于放入数字的个数),即change(a[i].pos,1)。存入的是位置,因为大小通过排序已经确定了,但是位置必须要在该数字之前才能满足逆序的条件。
每放入一个数字就要计算大于它且位置在它之前的个数,sum+=query(a[i].pos-1)。
#include <iostream>
#include <algorithm>
#define int long long
using namespace std;
struct node{
int val,pos;
}a[510000];
int s[510000];
int n;
int lowbit(int x){
return x&-x;
}
void change(int x,int k)
{
while(x<=n) s[x]+=k,x+=lowbit(x);
}
bool cmp(node x,node y){
if(x.val==y.val){
return x.pos>y.pos;
}else return x.val>y.val;
}
int query(int x){//查询区间和
int t=0;
while(x) t+=s[x],x-=lowbit(x);
return t;
}
signed main(){
while(cin>>n&&n){
for(int i=1;i<=n;i++){
cin>>a[i].val;a[i].pos=i;
}
sort(a+1,a+n+1,cmp);
int sum=0;
for(int i=1;i<=n;i++){
change(a[i].pos,1);
sum+=query(a[i].pos-1);
}
cout<<sum<<endl;
}
return 0;
}
CF1042D:题意就是给一个数组,寻找一些子段满足子段和小于给定数,给出这样的子段总数(一个数也算子段)。
这个题没用离散化因为要用准确的数字来比较,树状数组求逆序数这类思路重点在于排序和放的顺序,对前缀和进行排序是因为放入的顺序影响结果,因为树状数组的位置在排序后赋予了大小的意义,即树状数组前面的数小,后面的数大。在放入当前数字时,便于query数组得到之前所有 a[i]<=a[j]-k这样不满足条件的数组,在他前面的一定不满足,算一遍前缀和即可。
#include <iostream>
#include <algorithm>
#define int long long
using namespace std;
int s[510000];
int a[510000];
int cnt[510000];
int n,k;
int lowbit(int x){
return x&-x;
}
//这是一个改的函数,默认原来数组全是0,所以改也是初始化的过程
void change(int x,int k)//x是新数组对应位,k是原数组对应位的数字
{
while(x<=n+1) s[x]+=k,x+=lowbit(x);
}
int query(int x){//查询区间和
int t=0;
while(x) t+=s[x],x-=lowbit(x);
return t;
}
signed main(){
cin>>n>>k;
for(int i=2;i<=n+1;i++){
cin>>a[i];
a[i]=a[i]+a[i-1];
}
int ans=0;
sort(cnt+1,cnt+2+n);
for(int i=1;i<=n+1;i++){//这个i代表的是j的滚动。
int pos=upper_bound(cnt+1,cnt+2+n,a[i]-k)-cnt;//找的是最小j。即到底有多少个pre[j]-t小于了a[i];
ans+=i-1-query(pos-1);//由于i<j,所以所有可能的i只出现在j的下面。
pos=lower_bound(cnt+1,cnt+2+n,a[i])-cnt;
change(pos,1);//存的是a[i]在cnt中的位置,由于cnt是递增的,在后面计算的时候,会通过树状数组动态记录已有的答案,减去重复的i。
}
cout<<ans<<endl;
return 0;
}