目录
还记得上次,我用"P3374 【模板】树状数组 1 题解"作为题目,而这次,我将用"树状数组总结"作为题目。不废话了,本文就是一个总结。
首先,我将对这几个题目进行总结:这个&这个
第一个这个:

第二个这个:

(排名不分先后)
那么首先,就是最典型的一个例题:P3374
因为上次讲过了,所以就不讲了,具体就看这个。
这次,先讲第二题:P3368
P3368
假设你看不见上面这行字(也看不见这行字)(看到的就点个赞)
那么,首先,题目就不发了,总结一下题意:
如题,已知一个数列,你需要进行下面两种操作:
将某区间每一个数加上 x;
求出某一个数的值。
因此,我们可以先写出第一个操作:
cpp
if(op==1){
int x,y;
long long k;
cin>>x>>y>>k;
add(x,k);
add(y+1,-k);
}
当然,很符合题意:
操作 1: 格式:
1 x y k含义:将区间 x,y 内每个数加上 k;
然后,我们再写第二个操作:(上一个操作貌似包含容斥原理的成分🤭)
cpp
else{
int x;
cin>>x;
cout<<query(x)<<"\n";
}
当然,很符合题意:
操作 2: 格式:
2 x含义:输出第 x 个数的值。
其实输入也不能缺:
cpp
for(int i=1;i<=n;i++){
cin>>b;
add(i,b-a);
a=b;
}
ok,就剩函数了:
cpp
void add(int x,int y){for(;x<=n;x+=(x&-x))c[x]+=y;}
int query(int x){int sum=0;for(;x;x-=(x&-x))sum+=c[x];return sum;}
接下来,就没什么可讲的了。
全篇完(那是不可能的)
AC code:
cpp
#include<bits/stdc++.h>
using namespace std;
int n,m;
long long c[500005];
void add(int x,int y){for(;x<=n;x+=(x&-x))c[x]+=y;}
int query(int x){int sum=0;for(;x;x-=(x&-x))sum+=c[x];return sum;}
int main(){
cin>>n>>m;
long long a=0,b;
for(int i=1;i<=n;i++){
cin>>b;
add(i,b-a);
a=b;
}
while(m--){
int op;
cin>>op;
if(op==1){
int x,y;
long long k;
cin>>x>>y>>k;
add(x,k);
add(y+1,-k);
}
else{
int x;
cin>>x;
cout<<query(x)<<"\n";
}
}
return 0;
}
接下来,我们来看一下:P1908
P1908
题意:

因此,依旧add和query:
cpp
void add(int x,int y){
for(;x<=n;x+=(x&-x))c[x]+=y;
}
int query(int x){
int sum=0;
for(;x;x-=(x&-x))sum+=c[x];
return sum;
}
输入:
cpp
for(int i=1;i<=n;i++){
cin>>a[i];
b[i]=a[i];
}
离散化:
cpp
sort(b+1,b+n+1);
for(int i=1;i<=n;i++){
a[i]=lower_bound(b+1,b+n+1,a[i])-b;
}
统计求答案:
cpp
int ans=0;
for(int i=1;i<=n;i++){
int tmp=0;
tmp=query(a[i]);
add(a[i],1);
ans+=i-1-tmp;
}
cout<<ans;
然后,也没什么可讲的。
过:
cpp
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,a[500005],b[500005],c[500005];
void add(int x,int y){
for(;x<=n;x+=(x&-x))c[x]+=y;
}
int query(int x){
int sum=0;
for(;x;x-=(x&-x))sum+=c[x];
return sum;
}
signed main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
b[i]=a[i];
}
sort(b+1,b+n+1);
for(int i=1;i<=n;i++){
a[i]=lower_bound(b+1,b+n+1,a[i])-b;
}
int ans=0;
for(int i=1;i<=n;i++){
int tmp=0;
tmp=query(a[i]);
add(a[i],1);
ans+=i-1-tmp;
}
cout<<ans;
return 0;
}
P1637
P1637:

首先:函数:
cpp
void add(int x,int y){for(;x<100005;x+=(x&-x))d[x]+=y;}
int query(int x){int sum=0;for(;x;x-=(x&-x))sum+=d[x];return sum;}
主函数:
cpp
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
memset(d,0,sizeof(d));
for(int i=1;i<=n;i++){b[i]=query(a[i]-1);add(a[i],1);}// 计算 b [i]:a [i] 左边比它小的数字数量
memset(d,0,sizeof(d));
for(int i=n;i>=1;i--){c[i]=(n-i)-query(a[i]);add(a[i],1);}//计算 c [i]:a [i] 右边比它大的数字数量
long long ans=0;
for(int i=1;i<=n;i++)ans+=1LL*b[i]*c[i];//(累加答案)
cout<<ans;
然后,没了。
#include<bits/stdc++.h>
using namespace std;
int a[30005],b[30005],c[30005],d[100005];
int n;
void add(int x,int y){for(;x<100005;x+=(x&-x))d[x]+=y;}
int query(int x){int sum=0;for(;x;x-=(x&-x))sum+=d[x];return sum;}
int main(){
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
memset(d,0,sizeof(d));
for(int i=1;i<=n;i++){b[i]=query(a[i]-1);add(a[i],1);}
memset(d,0,sizeof(d));
for(int i=n;i>=1;i--){c[i]=(n-i)-query(a[i]);add(a[i],1);}
long long ans=0;
for(int i=1;i<=n;i++)ans+=1LL*b[i]*c[i];
cout<<ans;
return 0;
}
P1531
P1531:

build:
void build(int o,int l,int r){
if(l==r){
tree[o]=a[l];
return;
}
int mid=(l+r)/2;
build(o*2,l,mid);
build(o*2+1,mid+1,r);
tree[o]=max(tree[o*2],tree[o*2+1]);
}
功能
线段树建树,维护区间最大值。
逻辑
l==r叶子节点:存原数组a[l];- 分左右子树递归建;
- 当前节点值 = 左、右子树最大值。
update:
void update(int o,int l,int r,int p,int k){
if(l==r){
tree[o]=max(tree[o],k);
return;
}
int mid=(l+r)/2;
if(p<=mid)update(o*2,l,mid,p,k);
else update(o*2+1,mid+1,r,p,k);
tree[o]=max(tree[o*2],tree[o*2+1]);
}
作用
线段树单点更新,只有新值 k 比原成绩大时才修改(匹配题目 U 操作规则)
逻辑
l==r到达叶子节点,节点值取自身和 k 的较大值;- 算 mid,判断要修改的位置 p 在左 / 右子树,递归更新;
- 回溯后用左右子树最大值更新当前节点。
query:
int query(int o,int l,int r,int x,int y){
if(x<=l&&r<=y){
return tree[o];
}
int mid=(l+r)/2;
int ans=0;
if(x<=mid)ans=max(ans,query(o*2,l,mid,x,y));
if(y>mid)ans=max(ans,query(o*2+1,mid+1,r,x,y));
return ans;
}
作用
线段树区间查询,求 \(x,y\) 区间最大值。
流程
- 当前区间 l,r 完全在查询范围 x,y 内,直接返回该节点最大值;
- 取中点 mid,分别递归查左、右子树;
- 把左右查到的结果取最大返回。
主函数:
int main(){
int n,m;
cin>>n>>m;
for(int i=1;i<=n;++i){
cin>>a[i];
}
build(1,1,n);
char op;
int x,y;
while(m--){
cin>>op>>x>>y;
if(op=='U'){
update(1,1,n,x,y);
}else{
cout<<query(1,1,n,x,y)<<'\n';
}
}
return 0;
}
- 读入学生数 n、操作数 m;
- 读入数组 a 1\~n,调用线段树建树 build;
- 循环 m 次处理操作:
U x y:单点更新 x 位置,只变大不缩小;Q x y:查询区间 x,y 最大值并输出;
然后,真的无了。
#include<bits/stdc++.h>
using namespace std;
int a[200005];
int tree[20005*10*4];
void build(int o,int l,int r){
if(l==r){
tree[o]=a[l];
return;
}
int mid=(l+r)/2;
build(o*2,l,mid);
build(o*2+1,mid+1,r);
tree[o]=max(tree[o*2],tree[o*2+1]);
}
void update(int o,int l,int r,int p,int k){
if(l==r){
tree[o]=max(tree[o],k);
return;
}
int mid=(l+r)/2;
if(p<=mid)update(o*2,l,mid,p,k);
else update(o*2+1,mid+1,r,p,k);
tree[o]=max(tree[o*2],tree[o*2+1]);
}
int query(int o,int l,int r,int x,int y){
if(x<=l&&r<=y){
return tree[o];
}
int mid=(l+r)/2;
int ans=0;
if(x<=mid)ans=max(ans,query(o*2,l,mid,x,y));
if(y>mid)ans=max(ans,query(o*2+1,mid+1,r,x,y));
return ans;
}
int main(){
int n,m;
cin>>n>>m;
for(int i=1;i<=n;++i){
cin>>a[i];
}
build(1,1,n);
char op;
int x,y;
while(m--){
cin>>op>>x>>y;
if(op=='U'){
update(1,1,n,x,y);
}else{
cout<<query(1,1,n,x,y)<<'\n';
}
}
return 0;
}
这个博客,讲解了
- P3368
- P1908
- P1637
- P1531
最后,为了凑字数: