树状数组
树状数组完成以下两种操作:
- 将第i个数加上k
- 输出区间[i,j]内每个数的和
lowbit(x):返回x在二进制表示下最低位1
int lowbit(int x)
{
return x & -x;
}
树状数组结构:
使用树形结构维护前缀和,时间复杂度为O(logn)
- 每个结点
t[x]
保存以x为根的子树中叶结点值的和 - 每个结点覆盖的长度为
lowbit(x)
t[x]
结点的父结点为t[x + lowbit(x)]
- 树的深度为
log2n+1
加数操作:
void add(int x, int k)
{
for(int i = x; i <= n; i += lowbit(i))
t[i] += k;
}
在整棵树上维护这个值,需要一层一层向上找到父结点,并将这些结点上的t[x]值都加上k,这样保证计算区间和时的结果正确
查询操作
int ask(int x)
{
int sum = 0;
for(int i = x; i; i -= lowbit(i))
sum += t[i];
return sum;
}
查询这个点的前缀和,需要从这个点向左上找到上一个结点,将加上其结点的值。向左上找到上一个结点,只需要将下标 x -= lowbit(x)
AcWing 241. 楼兰图腾
题目
https://www.acwing.com/problem/content/243/
在完成了分配任务之后,西部 314 来到了楼兰古城的西部。
相传很久以前这片土地上(比楼兰古城还早)生活着两个部落,一个部落崇拜尖刀(V),一个部落崇拜铁锹(∧),他们分别用 V 和 ∧ 的形状来代表各自部落的图腾。
西部 314 在楼兰古城的下面发现了一幅巨大的壁画,壁画上被标记出了 n 个点,经测量发现这 n 个点的水平位置和竖直位置是两两不同的。
西部 314 认为这幅壁画所包含的信息与这 n 个点的相对位置有关,因此不妨设坐 标分别为 (1,y1),(2,y2),…,(n,yn)
,其中y1∼yn
是 1 到 n 的一个排列。
西部 314 打算研究这幅壁画中包含着多少个图腾。
如果三个点 (i,yi),(j,yj),(k,yk) 满足 1≤i<j<k≤n 且 yi>yj,yj<yk
,则称这三个点构成 V 图腾;
如果三个点 (i,yi),(j,yj),(k,yk) 满足 1≤i<j<k≤n 且 yi<yj,yj>yk
,则称这三个点构成 ∧ 图腾;
西部 314 想知道,这 n 个点中两个部落图腾的数目。
因此,你需要编写一个程序来求出 V 的个数和 ∧ 的个数。
思路
- 遍历数组中每个数a[i],使用树状数组统计在位置之前所有比a[i]大的数,以及所有比a[i]小的数的个数
- 倒叙遍历每个数a[i],使用树状数组统计在i之后所有比a[i]大的数,以及小的数的个数
- 统计完后,将a[i]加入到树状数组中
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
const int N=200010;
int n;
int a[N];
int tr[N];//tr[i]表示树状数组i结点覆盖的范围和
int Greater[N],lower[N];//Lower[i]表示左边比第i个位置小的数的个数
int lowbit(int x){return x&-x;}
//将序列中第x个数加上k。
void add(int x,int c){
for(int i=x;i<=n;i+= lowbit(i)){
tr[i]+=c;
}
}
//查询序列前x个数的和
int sum(int x){
int res=0;
for(int i=x;i;i-= lowbit(i)){
res+=tr[i];
}
return res;
}
int main() {
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=n;i++)
{
int y=a[i];
Greater[i]= sum(n)- sum(y);
lower[i]= sum(y-1);
add(y,1);//将y加入树状数组,即数字y出现1次
}
memset(tr,0,sizeof tr);
LL res1=0,res2=0;
for(int i=n;i;i--){
int y=a[i];
res1+=Greater[i]*(LL)(sum(n)- sum(y));
res2+=lower[i]*(LL)(sum(y-1));
add(y,1);
}
cout<<res1<<" "<<res2<<endl;
return 0;
}
AcWing 242. 一个简单的整数问题
题目
https://www.acwing.com/problem/content/248/ 给定长度为 N 的数列 A,然后输入 M 行操作指令。 第一类指令形如 C l r d,表示把数列中第 l∼r 个数都加 d。 第二类指令形如 Q x,表示询问数列中第 x 个数的值。 对于每个询问,输出一个整数表示答案。
思路
树状数组,区间修改,单点查询
差分操作
- b[l]+=d;
- b[r+1]-=d;
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
const int N=100010;
int n,m;
int a[N];
LL 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;
}
LL sum(int x){
LL res=0;
for(int i=x;i;i-= lowbit(i)) res+=tr[i];
return res;
}
int main() {
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=n;i++) add(i,a[i]-a[i-1]);
while (m--){
char op[2];
int l,r,d;
cin>>op>>l;
if (op[0]=='C'){
cin>>r>>d;
add(l,d), add(r+1,-d);
}else {
cout<<sum(l)<<endl;
}
}
return 0;
}