树状数组笔记

代码源数据结构中级课

第一课:树状数组笔记

树状数组1

在这里插入图片描述

思路: l o w b i t lowbit lowbit:求二进制数最低位 1 1 1和尾端 0 0 0构成的二进制数, l o w b i t ( x ) = x & ( − x ) lowbit(x)=x\&(-x) lowbit(x)=x&(x)
树状数组 c i = ∑ j = i − l o w b i t ( i ) + 1 i a j c_i=\sum_{j=i-lowbit(i)+1}^ia_{j} ci=j=ilowbit(i)+1iaj
单点加:

void modify(int x,ll val){
    for(;x<=n;x+=x&(-x)) c[i]+=val;
}

前缀和查询:

ll query(int x){
    ll s=0;
    for(;x;x-=x&(-x)) s+=c[i];
    return s;
}

在此基础上修改 a x = d a_x=d ax=d,只需要: m o d i f y ( x , d − a [ x ] ) , a [ x ] = d modify(x,d-a[x]),a[x]=d modify(x,da[x]),a[x]=d即可

C o d e : Code: Code:

#include <bits/stdc++.h>
using namespace std;
#define endl '\n';
typedef long long ll;
typedef pair<string,int> PII;
const int N=2e5+10,mod=998244353;
template<class T>
void read(T &x){
	x=0; char c; int sign=1;
	do{ c=getchar(); if(c=='-') sign=-1;}while(!isdigit(c));
	do{ x=x*10+c-'0',c=getchar();}while(isdigit(c));
	x*=sign;
}
int a[N],n,q,x;
ll c[N];
void modify(int x,ll val){
	for(;x<=n;x+=x&(-x)) c[x]+=val;
}
ll query(int x){
	ll s=0;
	for(;x;x-=x&(-x)) s+=c[x];
	return s;
}
int main(){
	read(n),read(q);
	for(int i=1;i<=n;i++) {
		read(a[i]);
		modify(i,a[i]);
	}
	while(q--){
		int op;read(op);
		if(op==1){
			int x,d;
			read(x),read(d);
			modify(x,d-a[x]);
			a[x]=d;
		}else{
			int x;read(x);
			printf("%lld\n",query(x));
		}
	}
	return 0;
}

树状数组2

在这里插入图片描述

思路:考虑差分,对 [ l , r ] [l,r] [l,r]的区间都加上 d d d,只需要在差分数组 d [ l ] d[l] d[l] + d +d +d d [ r + 1 ] d[r+1] d[r+1] − d -d d
原数组和差分数组对应关系为: a i = ∑ k = 1 i d k a_i=\sum_{k=1}^{i} d_k ai=k=1idk
那么 ∑ i = 1 x a i = ∑ i = 1 x ( x + 1 − i ) ∗ d i = ( x + 1 ) ∑ i = 1 x d i − ∑ i = 1 x i ∗ d i \sum_{i=1}^x a_i=\sum_{i=1}^x (x+1-i)*d_i=(x+1)\sum_{i=1}^xd_i-\sum_{i=1}^x i*d_i i=1xai=i=1x(x+1i)di=(x+1)i=1xdii=1xidi
对于 ∑ i = 1 x d i \sum_{i=1}^xd_i i=1xdi ∑ i = 1 x i ∗ d i \sum_{i=1}^x i*d_i i=1xidi,维护两个树状数组即可

C o d e : Code: Code:

#include <bits/stdc++.h>
using namespace std;
#define endl '\n';
typedef long long ll;
typedef unsigned long long u64;
typedef pair<string,int> PII;
const int N=2e5+10,mod=998244353;
template<class T>
void read(T &x){
	x=0; char c; int sign=1;
	do{ c=getchar(); if(c=='-') sign=-1;}while(!isdigit(c));
	do{ x=x*10+c-'0',c=getchar();}while(isdigit(c));
	x*=sign;
}
int a[N],n,q,x;
template<class T>
struct BIT{
	T c[N];
	void modify(int x,T val){
		for(;x<=n;x+=x&(-x)) c[x]+=val;
	}
	T query(int x){
		T s=0;
		for(;x;x-=x&(-x)) s+=c[x];
		return s;
	}
};
BIT<u64> c1,c2;
int main(){
	read(n),read(q);
	while(q--){
		int op;read(op);
		if(op==1){
			int l,r;read(l),read(r);
			u64 d;read(d);
			c1.modify(l,d);
			c1.modify(r+1,-d);
			c2.modify(l,l*d);
			c2.modify(r+1,(r+1)*(-d));
		}else{
			int x;read(x);
			printf("%llu\n",(x+1)*c1.query(x)-c2.query(x));
		}
	}
	return 0;
}

逆序对2

在这里插入图片描述

思路:把静态问题转化为动态问题+权值开树状数组
对于 j j j [ 1 , n ] [1,n] [1,n]循环的时候,统计 a [ 1 , j − 1 ] a_{[1,j-1]} a[1,j1]中大于 a j a_j aj的个数,对于 a [ 1 , j − 1 ] a_{[1,j-1]} a[1,j1]维护一个数据结构 D D D,统计完个数之后再将 a j a_j aj加入 D D D
那么 D D D满足的操作为: 1. D [ a x ] + = 1 1.D[a_x]+=1 1.D[ax]+=1 2. 2. 2.后缀和查询
理解为把 a x a_x ax当作下标加入 D D D中,那么对于 a [ 1 , x − 1 ] a_{[1,x-1]} a[1,x1]中在 D D D中在 a x a_x ax后面的和即为 a [ 1 , x − 1 ] a_{[1,x-1]} a[1,x1]中比 a x a_x ax大的数的个数,对于这个后缀和查询,只要预处理 a i = n + 1 − a i a_i=n+1-a_i ai=n+1ai即可转化为前缀和查询

C o d e : Code: Code

#include <bits/stdc++.h>
using namespace std;
#define endl '\n';
typedef long long ll;
typedef unsigned long long u64;
typedef pair<string,int> PII;
const int N=2e5+10,mod=998244353;
template<class T>
void read(T &x){
	x=0; char c; int sign=1;
	do{ c=getchar(); if(c=='-') sign=-1;}while(!isdigit(c));
	do{ x=x*10+c-'0',c=getchar();}while(isdigit(c));
	x*=sign;
}
int n,a[N];
ll c[N];
void modify(int x,ll val){
	for(;x<=n;x+=x&(-x)) c[x]+=val;
}
ll query(int x){
	ll s=0;
	for(;x;x-=x&(-x)) s+=c[x];
	return s;
}
int main(){
	read(n);
	for(int i=1;i<=n;i++) {
		read(a[i]);
		a[i]=n+1-a[i];
	}
	ll ans=0;
	for(int i=1;i<=n;i++){
		ans+=query(a[i]);
		modify(a[i],1);
	}
	printf("%lld",ans);
	return 0;
}

树状数组二分

在这里插入图片描述

思路:直接二分的复杂度为 O ( l o g 2 n ) O(log^2n) O(log2n),如果在 q u e r y query query时,从最高位开始向最低位循环,如果满足 c [ p o s + ( 1 < < j ) ] < = s c[pos+(1<<j)]<=s c[pos+(1<<j)]<=s,那么 p o s + = ( 1 < < j ) pos+=(1<<j) pos+=(1<<j),这样复杂度就可以优化掉一个 l o g log log

C o d e : Code: Code:

#include <bits/stdc++.h>
using namespace std;
#define endl '\n';
typedef long long ll;
typedef unsigned long long u64;
typedef pair<string,int> PII;
const int N=2e5+10,mod=998244353;
template<class T>
void read(T &x){
	x=0; char c; int sign=1;
	do{ c=getchar(); if(c=='-') sign=-1;}while(!isdigit(c));
	do{ x=x*10+c-'0',c=getchar();}while(isdigit(c));
	x*=sign;
}
int n,a[N],q;
ll c[N];
void modify(int x,ll val){
	for(;x<=n;x+=x&(-x)) c[x]+=val;
}
ll query(ll s){
	ll pos=0;
	for(int j=18;j>=0;j--){
		if(pos+(1<<j)<=n&&c[pos+(1<<j)]<=s){
			pos+=(1<<j);
			s-=c[pos];
		}
	}
	return pos;
}
int main(){
	read(n),read(q);
	for(int i=1;i<=n;i++) {
		read(a[i]);
		modify(i,a[i]);
	}
	while(q--){
		int op;read(op);
		if(op==1){
			int x,d;read(x),read(d);
			modify(x,d-a[x]);
			a[x]=d;
		}else{
			ll s;read(s);
			printf("%lld\n",query(s));
		}
	}
	return 0;
}

二维树状数组

在这里插入图片描述

思路: k k k维树状数组的复杂度为 O ( l o g k n ) O(log^kn) O(logkn)

C o d e : Code: Code:

#include <bits/stdc++.h>
using namespace std;
#define endl '\n';
typedef long long ll;
typedef unsigned long long u64;
typedef pair<string,int> PII;
const int N=500+10,mod=998244353;
template<class T>
void read(T &x){
	x=0; char c; int sign=1;
	do{ c=getchar(); if(c=='-') sign=-1;}while(!isdigit(c));
	do{ x=x*10+c-'0',c=getchar();}while(isdigit(c));
	x*=sign;
}
int n,m,a[N][N],q;
ll c[N][N];
void modify(int x,int y,ll val){
	for(int i=x;i<=n;i+=i&(-i)) {
		for(int j=y;j<=m;j+=j&(-j)){
			c[i][j]+=val;
		}
	}
}
ll query(int x,int y){
	ll s=0;
	for(int i=x;i;i-=i&(-i)) {
		for(int j=y;j;j-=j&(-j)){
			s+=c[i][j];
		}
	}
	return s;
}
int main(){
	read(n),read(m),read(q);
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			read(a[i][j]);
			modify(i,j,a[i][j]);
		}
	}
	while(q--){
		int op;read(op);
		if(op==1){
			int x,y;read(x),read(y);
			ll d;read(d);
			modify(x,y,d-a[x][y]);
			a[x][y]=d;
		}else{
			int x,y;read(x),read(y);
			printf("%lld\n",query(x,y));
		}
	}
	return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

学不会数据库

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值