树状数组LDUOJ积累本
首先我们应该知道树状数组的基本结构:
1.lowbit函数:此处不做解释( 视频讲的更好)
2.add函数:修改(单点||区间)
3.getsum函数:查询(单点||区间)
往下看的前提是看过上述视频并了解了lowbit函数
A.单点修改,区间查询
洛谷
A. 单点修改,区间查询 [ 讨论 ]
Description
给定数列a[1],a[2],…,a[n],你需要依次进行 q个操作,操作有两类:
1 i x:给定i,x,将a[i]加上x;
2 l r:给定l,r,求 ∑ri=la[i]的值(换言之,求a[l]+a[l+1]+⋯+a[r]的值)
Input
第一行包含2个正整数n,q,表示数列长度个数,保证a≤n,q≤106
第二行n个整数a[1],a[2],…,a[n],表示初始数列,保证|a[i]|≤106
接下来q行,每行一个操作,为下列两种之一:
1 i x:给定i,x,将a[i]加上x;
2 l r:给定l,r,求 ∑ri=1a[i]的值;
保证1≤l≤r≤n, |x|≤106
Output
对于每个 2 l r 操作输出一行,每行有一个整数,表示所求的结果。
Samples
Input 复制
3 2
1 2 3
1 2 0
2 1 3
Output
6
Hint
对于所有数据,1≤n,q≤106,|a[i]|≤106,1≤l≤r≤n,|x|≤106
首先从单点修改开始:
假如初始数组为a[1]=1,a[2]=2,a[3]=3,a[4]=4,a[5]=5;
那么我们构建树状数组tree;
是不是 插入 1 有
tree[1]+=1, tree[1]=1;
tree[2]+=1,tree[2]=1;
tree[4]+=1,tree[4]=1;
插入2 有
tree[2]+=2, tree[2]=3;
tree[4]+=2,tree[4]=3;
插入3 有
tree[3]+=3,tree[3]=3;
tree[4]+=3,tree[4]=6;
插入4 有
tree[4]+=4,tree[4]=10;
插入5 有
tree[5]+=5,tree[5]=5;
那么我们此时来看 初始数列为 1 2 3 4 5;
前1项和 为1;
前2项和 为3;
前3项和 为6;
前4项和 为10;
前5项和 为15;
是不是发现前n项和的数值 是树状数组getsum(n)的数值?
比如5 getsum(5){
ans+=tree[5];
5-=lowbit(5)
}
ans=5;
↓
getsum(4){
ans+=tree[4];
4-=lowbit(4)
}
ans=5+10=15;
4-lowbit(4)=0;
getsum(0) 错误退出返回ans;
getsum(5)=15;
所以此时你应该明白 getsum(n)求的是初始数列a[]的前n项和;
至于单点修改 add函数 :
初始数组为a[1]=1,a[2]=2,a[3]=3,a[4]=4,a[5]=5;
如果我给数组2 加上3 a[2]=5;
数组为a[1]=1,a[2]=5,a[3]=3,a[4]=4,a[5]=5;
那么我是不是tree[2]也要改变 tree[2]+=3,tree[2]=6;
既然我getsum函数求的是前n项和,是不是跟tree[2]数组有关的tree数组我也要改变 2+=lowbit(2)=4;
这不直接是一条龙服务吗
tree[4]+=3,tree[4]=10+3=13;
总结:
add(n)函数是修改前n项的和||单点修改;
getsum(n)是求前n的和;
此处前n项和均不是树状数组前n项和,而是初始数组! 模拟一下getsum的过程你就会发现了;
那么我求区间【l,r】的和 是不是 getsum®-getsum(l-1);
#include<bits/stdc++.h>
using namespace std;
#define PI 3.1415926535897932384
typedef long long ll;
const int UNINF=-0x3f3f3f3f;
const int INF=0x3f3f3f3f;
const int maxn=1e6+7;
const int mod=1000;
inline ll read()
{
ll x=0,zf=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')
zf=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return zf*x;
}
#define read read()
#define size size()
//priority_queue<int,vector<int>,greater<int> >v;
//map<char,int>s;
//string s[maxn];
ll tree[maxn];
ll n,q;
ll lowbit(ll x){
return x&-x;
}
void add(ll x,ll k){
while(x<=n){
tree[x]+=k;
x+=lowbit(x);
//cout<<x<<" "<<lowbit(x)<<endl;
}
return ;
}
ll getsum(ll x){
ll ans=0;
while(x){
ans+=tree[x];
x-=lowbit(x);
}
return ans;
}
int main()
{
int t;
//cin>>n>>q;
scanf("%lld %lld",&n,&q);
memset(tree,0,sizeof(tree));
for(int i=1;i<=n;i++) {
ll k;
scanf("%lld",&k);
add(i,k);
}
for(int i=1;i<=q;i++){
ll id,x,y;
//cin>>id>>x>>y;
scanf("%lld %lld %lld",&id,&x,&y);
if(id==1){
add(x,y);
//cout<<tree[x]<<endl;
}
if(id==2){
printf("%lld\n",getsum(y)-getsum(x-1));
//cout<<getsum(y)-getsum(x-1)<<endl;
}
}
return 0;
}
B.区间修改,单点查询
洛谷
B. 区间修改,单点查询 [ 讨论 ]
Description
给定数列a[1],a[2],…,a[n] ,你需要依次进行 q 个操作,操作有两类:
1 l r x:给定 l,r,x,对于所有i∈[l,r],将 a[i] 加上 x(换言之,将 a[l],a[l+1],…,a[r] 分别加上 x);
2 i:给定 i ,求 a[i] 的值。
Input
第一行包含 2 个正整数 n,q,表示数列长度和询问个数。保证1≤n,q≤106 。
第二行 n 个整数 a[1],a[2],…,a[n],表示初始数列。保证|a[i]|≤106 。
接下来 q 行,每行一个操作,为以下两种之一:
1 l r x:对于所有 i∈[l,r],将a[i]加上x;
2 i:给定 i,求 a[i] 的值。
保证 1≤l≤r≤106, |x|≤106。
Output
对于每个 2 i 操作,输出一行,每行有一个整数,表示所求的结果。
Samples
Input 复制
3 2
1 2 3
1 1 3 0
2 2
Output
2
Hint
对于所有数据,1≤n,q≤106 , |a[i]|≤106, 1≤l≤r≤n, |x|≤106
至于区间修改,单点查询:
我们要引入差分数组c[i]=a[i]-a[i-1];;
比如
a[0]=0;
c[1]=a[1]-a[0];
c[2]=a[2]-a[1];
那么我a[2]=c[2]+c[1]=a[2]-a[1]+a[1]-a[0]=a[2];
所以你会发现 我们求c数组(getsum(n)) 就是求的要查询的第n项的值;
所以我们就用tree储存差分值;
计算getsum(n) 不就是查询第n项的值了吗,所以和A题很相似,对比着去学;
至于区间修改:
比如对[l,r]区间进行修改加上k:
是不是我要对初始数组a在区间[l,r]内的全部元素加上k;
初始数组:5 4 2 3 1
差分 5 -1 -2 1 -2
初始差分数组:0 0 0 0 0
插入第1个差分 5:5 5 0 5 0
插入第2个差分 -1:5 5+(-1) 0 5+(-1) 0 →5 4 0 4 0
插入第3个差分 -2:5 4 0+(-2) 4+(-2) 0 →5 4 -2 2 0
插入第4个差分 1:5 4 -2 2+(1) 0→ 5 4 -2 3 0
插入第5个差分 -2:5 4 -2 3 0+(-2)→ 5 4 -2 3 -2
那么我们再用getsum(n) 是不是就是询问到第n项的值了
加上k=3之后 5 7 5 6 1
差分 5 2 -2 1 -5
聪明的你是不是发现 差分只有 开始的一项+k,还有区间外接下来的一项-k;(其实这个一想也知道,我把第l项的差分加上k,那我后面用差分求和的时候也是加了这一个k呀,为了不影响区间外的求和,我们要把区间外的紧挨着的一项-k呀)
所以就有
add(l,k)
add(r+1,-k);
#include<bits/stdc++.h>
using namespace std;
#define PI 3.1415926535897932384
typedef long long ll;
const int UNINF=-0x3f3f3f3f;
const int INF=0x3f3f3f3f;
const int maxn=1e6+7;
const int mod=1000;
inline ll read()
{
ll x=0,zf=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')
zf=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return zf*x;
}
#define read read()
#define size size()
//priority_queue<int,vector<int>,greater<int> >v;
//map<char,int>s;
//string s[maxn];
ll tree[1000006];
ll n,m,q;
ll lowbit(ll x){
return x&-x;
}
void add(ll x,ll k){
//cout<<n<<endl;
while(x<=n){
tree[x]+=k;
x+=lowbit(x);
}
return ;
}
ll getsum(ll x){
ll ans=0;
while(x){
ans+=tree[x];
x-=lowbit(x);
}
return ans;
}
int main()
{
//ios::sync_with_stdio(false);
memset(tree,0,sizeof(tree));
scanf("%lld %lld",&n,&m);
ll temp=0;
ll kk;
for(int i=1;i<=n;i++){
scanf("%lld",&kk);
add(i,kk-temp);
temp=kk;
}
while(m--){
ll id,x,y,k;
scanf("%lld",&id);
if(id==1){
scanf("%lld %lld %lld",&x,&y,&k);
add(x,k);
add(y+1,-k);
}
if(id==2){
scanf("%lld",&x);
printf("%lld\n",getsum(x));
//cout<<getsum(x)<<endl;
}
}
return 0;
}
C.区间修改,区间查询
C. 区间修改,区间查询 [ 讨论 ]
Description
给定数列 a[1],a[2],…,a[n],你需要依次进行q个操作,操作有两类:
1 l r x:给定l,r,x,对于所有的i∈[l,r],将a[i]加上x(换言之,将a[l],a[l+1],…,a[r] 分别加上x)
2 l r:给定l,r,求∑ri=la[i]的值(换言之,求a[l]+a[l+1]+…+a[r]的值)
Input
第一行包含2个正整数n,q,表示数列长度和询问个数。保证1≤n,q≤106;
第二行n个整数a[1],a[2],…,a[n],表示初始数列。保证|a[i]|≤106。
接下来q行,每行一个操作,为以下两种之一:
1 l r x:对于所有的i∈[l,r],将a[i]加上x;
2 l r:输出∑ri=la[i]的值;
Output
对于每个 2 l r 操作,输出一行,每行有一个整数,表示所求的结果。
Samples
Input 复制
5 10
2 6 6 1 1
2 1 4
1 2 5 10
2 1 3
2 2 3
1 2 2 8
1 2 3 7
1 4 4 10
2 1 2
1 4 5 6
2 3 4
Output
15
34
32
33
50
Hint
对于所有数据,1≤n,q≤106,|a[i]|≤106 ,1≤l≤r≤n,|x[i]|≤106 。
ok,把这个图片搞懂&&AB题能理解了 这个你就会做了 (借鉴大佬)
累了不想写了
#include<bits/stdc++.h>
using namespace std;
#define PI 3.1415926535897932384
typedef long long ll;
const int UNINF=-0x3f3f3f3f;
const int INF=0x3f3f3f3f;
const int maxn=1e6+7;
const int mod=1000;
inline ll read()
{
ll x=0,zf=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')
zf=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return zf*x;
}
#define read read()
#define size size()
ll n,q;
ll c[maxn];
ll c1[maxn];
ll lowbit(ll x){
return x&-x;
}
void addc(ll x,ll k){
while(x<=n){
c[x]+=k;
x+=lowbit(x);
}
return ;
}
ll askc(ll x){
ll ans=0;
while(x){
ans+=c[x];
x-=lowbit(x);
}
return ans;
}
void addc1(ll x,ll k){
while(x<=n){
c1[x]+=k;
x+=lowbit(x);
}
return ;
}
ll askc1(ll x){
ll ans=0;
while(x){
ans+=c1[x];
x-=lowbit(x);
}
return ans;
}
int main()
{
scanf("%lld %lld",&n,&q);
int temp=0;
for(int i=1;i<=n;i++){
ll x;
scanf("%lld",&x);
addc(i,x-temp);
addc1(i,(i-1)*(x-temp));//我跟大佬代码的区别也就在这了
temp=x;
}
while(q--){
ll id,l,r,xx;
scanf("%lld",&id);
if(id==1){
scanf("%lld %lld %lld",&l,&r,&xx);
addc(l,xx);
addc(r+1,-xx);
addc1(l,(l-1)*xx);
addc1(r+1,-(r+1-1)*xx);
}
if(id==2){
scanf("%lld %lld",&l,&r);
ll ans;
ans=(r*askc(r)-askc1(r))-((l-1)*askc(l-1)-askc1(l-1));
printf("%lld\n",ans);
}
}
return 0;
}
本人小白,避免不了出现错误,还请各位大牛指正!