树状数组
树状数组的实现
使用场景
给定一个长度为
分析
对于操作 1,我们可以将
树状数组的实现
区间和,单点改
定义
lowbit 函数
得证。
:::
那么每次我们可以
void add(int x,int k)
{
while (x<=n)
{
c[x]+=k;
x+=lowbit(x);
}
}
:::
getsum 函数
每次我们从
int getsum(int x)
{
int sum=0;
while (x)
{
sum+=c[x];
x-=lowbit(x);
}
return sum;
}
:::
综合代码
:::info[代码]
#include<bits/stdc++.h>
using namespace std;
const int N=5e5+100;
int c[N];
int n,m;
int lowbit(int x){return x&-x;}
void add(int x,int k)
{
while (x<=n)
{
c[x]+=k;
x+=lowbit(x);
}
}
int getsum(int x)
{
int ans=0;
while (x>0)
{
ans+=c[x];
x-=lowbit(x);
}
return ans;
}
int main()
{
cin>>n>>m;
for (int i=1;i<=n;i++)
{
int x;
cin>>x;
add(i,x);
}
while (m--)
{
int op;
cin>>op;
if (op==1)
{
int x,k;
cin>>x>>k;
add(x,k);
}
if (op==2)
{
int x,y;
cin>>x>>y;
cout<<getsum(y)-getsum(x-1)<<endl;
}
}
}
:::
性质
我们可以发现对于 getsum 函数,这个前缀和是有若干个区间的和,所以需要具有结合律。而两个前缀和相减则需要具有差分性。
归纳一下,应具有:
- 结合律
- 差分性
与线段树相比的优劣
优势
- 树状数组的常数比线段树要写,效率更高。
- 树状数组的空间复杂度为
\mathcal{O}(n) ,而线段树要开4 被空间。 - 树状数组实现的代码要比线段树少的多。
劣势
- 树状数组无法处理不具有差分性的运算,而线段树可以,线段树的运用比树状数组要广。
常见的不具有差分性的几个运算:
单点查,区间改
众所周知,正常的树状数组可以处理单点改和区间和的操作,而现在要处理区间改该怎么办呢?
如果是正常的一个数组,我们要让它
:::info[代码]
#include<bits/stdc++.h>
using namespace std;
const int N=5e5+100;
int c[N];
int n,m;
int lowbit(int x){return x&-x;}
void add(int x,int k)
{
while (x<=n)
{
c[x]+=k;
x+=lowbit(x);
}
}
int getsum(int x)
{
int ans=0;
while (x>0)
{
ans+=c[x];
x-=lowbit(x);
}
return ans;
}
int main()
{
cin>>n>>m;
for (int i=1;i<=n;i++)
{
int x;
cin>>x;
add(i,x);
}
while (m--)
{
int op;
cin>>op;
if (op==1)
{
int l,r,k;
cin>>l>>r>>k;
add(l,k),add(r+1,-k);
}
if (op==2)
{
int x;
cin>>x;
cout<<getsum(x)<<endl;
}
}
}
:::
树状数组的分析
树状数组虽然含有树这个词,但却并不是是一棵标准意义上的树,它没有根节点,更像是个数组。树状数组采用二进制的方法,分成了若干个区间,与线段树从中间分割的方式不同,就使得效率要比线段树快但是有了其他的约束条件。
树状数组的应用
题目
题目描述
在完成了分配任务之后,西部 314 来到了楼兰古城的西部。相传很久以前这片土地上(比楼兰古城还早)生活着两个部落,一个部落崇拜尖刀(‘V’),一个部落崇拜铁锹(‘∧’),他们分别用V和∧的形状来代表各自部落的图腾。
西部 314 在楼兰古城的下面发现了一幅巨大的壁画,壁画上被标记出了
西部 314 打算研究这幅壁画中包含着多少个图腾,其中V图腾的定义如下(注意:图腾的形式只和这三个纵坐标的相对大小排列顺序有关)
西部 314 想知道,这
输入格式
第一行一个数
输出格式
两个数,中间用空格隔开,依次为V的个数和∧的个数
思路
这是要我们求V和∧的数量,V是上下横坐标都比中点大,∧是都比中点小。
我们可以用树状数组来求数量,每次将位置放到树状数组中,
:::info[代码]
#include<bits/stdc++.h>
using namespace std;
const int N=200100;
int y[N];
long long c[N];
long long s[N][4];
int n;
int lowbit(int x)
{
return x&-x;
}
void add(int x,int k)
{
while (x<=n)
{
c[x]+=k;
x+=lowbit(x);
}
}
long long getsum(int x)
{
long long sum=0;
while (x)
{
sum+=c[x];
x-=lowbit(x);
}
return sum;
}
int main()
{
cin>>n;
for (int i=1;i<=n;i++) cin>>y[i];
for (int i=1;i<=n;i++)
{
s[i][0]=getsum(y[i]-1);
s[i][1]=getsum(n)-getsum(y[i]);
add(y[i],1);
}
memset(c,0,sizeof(c));
for (int i=n;i>=1;i--)
{
s[i][2]=getsum(y[i]-1);
s[i][3]=getsum(n)-getsum(y[i]);
add(y[i],1);
}
long long ans1=0,ans2=0;
for (int i=1;i<=n;i++)
{
ans1+=s[i][1]*s[i][3];
ans2+=s[i][0]*s[i][2];
}
cout<<ans1<<" "<<ans2<<endl;
}
:::
题目
题目描述
破解了符文之语,小 FF 开启了通往地下的道路。当他走到最底层时,发现正前方有一扇巨石门,门上雕刻着一幅古代人进行某种活动的图案。而石门上方用古代文写着“神的殿堂”。小 FF 猜想里面应该就有王室的遗产了。但现在的问题是如何打开这扇门……。
仔细研究后,他发现门上的图案大概是说:古代人认为只有智者才是最容易接近神明的。而最聪明的人往往通过一种仪式选拔出来。仪式大概是指,即将隐退的智者为他的候选人写下一串无序的数字,并让他们进行一种操作,即交换序列中相邻的两个元素。而用最少的交换次数使原序列变成不下降序列的人即是下一任智者。
小 FF 发现门上同样有着
输入格式
第一行为一个整数
第二行为
输出格式
一个整数
输入输出样例 #1
输入 #1
4
2 8 0 3
输出 #1
3
说明/提示
数据范围及约定
- 对于
30\% 的数据1 \le n \le 10^4 。 - 对于
100\% 的数据1 \le n \le 5\times 10^5 ,A_i\in [-2^{31}, 2^{31}) 。
样例解释
开始序列为
- 交换
(8,0) ,序列变成[2,0,8,3] ; - 交换
(2,0) ,序列变成[0,2,8,3] ; - 交换
(8,3) ,序列变成[0,2,3,8] 。思路
本题问要交换多少次才能使得成为有序的序列。我们可以轻松的用冒泡排序了写暴力。
:::info[冒泡排序]
for (int i=1;i<=n-1;i++)
{
for (int j=1;j<=n-i;j++)
{
if (a[j]>a[j+1])
swap(a[j],a[j+1]),sum++;
}
}
cout<<sum;
:::
可以发现,这求出的就是逆序对的个数,如果我们在加入了
:::info[代码]
#include<bits/stdc++.h>
using namespace std;
const int N=5e5+100;
int a[N],c[N];
vector<int>v;
int n;
int lowbit(int x)
{
return x&-x;
}
void add(int x,int k)
{
while (x<=n)
{
c[x]+=k;
x+=lowbit(x);
}
}
int getsum(int x)
{
int sum=0;
while (x)
{
sum+=c[x];
x-=lowbit(x);
}
return sum;
}
int main()
{
cin>>n;
for (int i=1;i<=n;i++) cin>>a[i],v.push_back(a[i]);
sort(v.begin(),v.end());
v.erase(unique(v.begin(),v.end()),v.end());
for (int i=1;i<=n;i++) a[i]=lower_bound(v.begin(),v.end(),a[i])-v.begin()+1;
long long ans=0;
for (int i=1;i<=n;i++)
{
add(a[i],1);
ans+=i-getsum(a[i]);
}
cout<<ans;
}
:::