树状数组
解决的问题
- 基本问题:单点修改,区间查询
- 利用差分:区间修改,区间查询
总的来说就是:频繁修改+区间查询
与线段树区别
树状数组可以解决的问题都可以用线段树解决。
两者的区别
树状数组的优点:
- 相比线段树系数系数要少很多
- 容易写,代码量小
线段树的优点:
- 可以解决复杂问题。
理解
代码
lowbit
int lowbit(int x) {return x & (-x);}
更新/建立
一般来说建立就是直接将树状数组更新n次
//x为更新的位置,y为更新后的数,n为数组最大值
void update(int x,int y,int n){
for(int i=x;i<=n;i+=lowbit(i))
c[i] += y;
}
查询
查询1~x之间的数的和。
很明显查询一段区间的就是sum(x)-sum(y)
int sum(int x){
int ans = 0;
for(int i=x;i;i-=lowbit(i))
ans += c[i];
return ans;
}
应用
单点修改,区间查询
思路:
对于某个点a[i]的V
的数量就是:
a[i]前面大于a[i]的数的数量 × a[i]后面大于a[i]的数的数量
∧
也同理,就是换成小于a[i]的数的 数量
#include <bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
typedef pair<int, int> pii;
const int N = 2000010;
const int INF = 0x3f3f3f3f3f;
int n;
int a[N];
int tr[N];
int upper[N],lower[N];
int lowbit(int x){
return x & -x;
}
void add(int x,int c){
for(int i=x;i<=n;i+=lowbit(i))
tr[i]+=c;
}
int sum(int x){
int res=0;
for(int i=x;i;i-=lowbit(i))
res+=tr[i];
return res;
}
void slove()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
//统计i前面 大于和小于 i的数 的数量
for(int i=1;i<=n;i++){
int x=a[i];
upper[i]=sum(n)-sum(x);
lower[i]=sum(x-1);
add(x,1);
}
memset(tr,0,sizeof tr);
int ans1=0,ans2=0;
//统计i后面 大于和小于 i的数 的数量,并求出答案
for(int i=n;i>=1;i--){
int y=a[i];
ans1+=upper[i]*(sum(n)-sum(y));
ans2+=lower[i]*sum(y-1);
add(y,1);
}
printf("%lld %lld\n",ans1,ans2);
}
signed main()
{
slove();
return 0;
}
区间修改,单点查询
242. 一个简单的整数问题
题意:
有两类指令:
- 第一类指令形如 C l r d,表示把数列中第 l∼r 个数都加 d。
- 第二类指令形如 Q x,表示询问数列中第 x 个数的值。
思路: 裸题, 利用树状数组维护差分数组 即可,看代码吧
#include <bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
typedef pair<int, int> pii;
const int N = 1e5+10;
const int INF = 0x3f3f3f3f3f;
int n,m;
int a[N];
int tr[N];
int lowbit(int x){
return x & -x;
}
void add(int x,int c){
for(int i=x;i<=n;i+=lowbit(i))
tr[i]+=c;
}
int sum(int x){
int res=0;
for(int i=x;i;i-=lowbit(i))
res+=tr[i];
return res;
}
void slove()
{
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
}
while(m--){
string s;cin>>s;
if(s=="Q"){
int x;cin>>x;
cout<<sum(x)+a[x]<<endl;
}
else{
int l,r,d;
cin>>l>>r>>d;
add(l,d);
add(r+1,-d);
}
}
}
signed main()
{
slove();
return 0;
}
区间修改,区间查询
维护两个树状数组即可:
- 一个是差分数组d[i]的树状数组tr[i]
- 一个是原始数组的差分数组乘以i即i*d[i]的树状数组tri[i]
#include <bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
typedef pair<int, int> pii;
const int N = 1e5 + 10;
const int INF = 0x3f3f3f3f3f;
int n, m;
int a[N];
int tr[N];
int tr2[N];
int lowbit(int x)
{
return x & -x;
}
void add(int x, int c)
{
for (int i = x; i <= n; i += lowbit(i))
{
tr[i] += c;
tr2[i] += x * c;
}
}
int sum(int x)
{
int res = 0;
for (int i = x; i; i -= lowbit(i))
res += (x + 1) * tr[i] - tr2[i];
return res;
}
void slove()
{
cin >> n >> m;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
add(i,a[i]-a[i-1]);
}
while (m--)
{
string s;
cin >> s;
if (s == "Q")
{
int l, r;
cin >> l >> r;
cout << sum(r) - sum(l - 1) << endl;
}
else
{
int l, r, d;
cin >> l >> r >> d;
add(l, d);
add(r + 1, -d);
}
}
}
signed main()
{
slove();
return 0;
}