树状数组例题
树状数组例题
1.【模板】树状数组1
题意:序列单点修改,区间查询
思路:模板
#include<bits/stdc++.h>
#define lowbit(x) -x&x
using namespace std;
const int N = 5e5+5;
int n,m;
int opt,x,k;
int sum[N];
void add(int x,int k){
for(;x<=n;x+=lowbit(x))
sum[x]+=k;
}
int query(int x){
int res=0;
for(;x;x-=lowbit(x))
res+=sum[x];
return res;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1,t;i<=n;i++)
scanf("%d",&t),add(i,t);
while(m--){
scanf("%d%d%d",&opt,&x,&k);
if(opt==1)add(x,k);
else printf("%d\n",query(k)-query(x-1));
}
return 0;
}
树状数组的核心其实是那个 #define lowbit(x) -x&x
2.[POJ3468]A Simple Problem with Intergers
题意:序列区间修改,区间查询
思路:线段树秒杀(!!注意常数),以下摘自《算法竞赛进阶指南》
首先简化题目:区间修改,单点查询怎么做?
我们知道树状数组的本质是前缀和,设原序列 \(a[]\) 不妨建立一个差分数组 \(b[i]=a[i]-a[i-1]\) 。
这时候如果我们要将区间 \([l,r]\) 增加 \(d\) ,转化成了这样: \(b[l]+=d;b[r+1]-=d\)
考虑这时候的 \(b\) 的前缀和:
- 对于 \(1\leq x<l\) ,前缀和不变
- 对于 \(l\leq x\leq r\) ,前缀和增加 \(d\)
- 对于 \(r<x\leq n\) ,前缀和不变
我们发现,b 数组的前缀和 \(b[1...x]\) 就是区间 \([l,r]\) 增加 \(d\) 对 \(a\) 的影响,于是可以用树状数组维护 \(b\)
容易发现,修改后 \(a[x]\) 的实际值 \(=query(x)+a[x]\)
好的,现在回到原问题。
我们现在知道 \(\sum_{i=1}^xb[i]\) 就是区间修改后 \(a[x]\) 增加的值。
那么序列 \(a\) 的前缀和 \(a[1..x]\) 整体增加的值就是:
所以我们可以再用一个树状数组维护 \(i*b[i]\) 的值。
于是,回答询问 \([l,r]=(sum[r]+(r+1)*query(c_0,r)-query(c_1,r))-\\(sum[l-1],(r-1)*query(c_0,l-1)-query(c_1,l-1))\)
其中树状数组 \(c_0\) 维护 \(i*b[i]\) ,树状数组 \(c_1\) 维护 \(b[i]\)
//[POJ3468]A_Simple_Problem_with_Integers
#include<bits/stdc++.h>
#define lowbit(x) -x&x
using namespace std;
typedef long long ll;
const int N = 1e5+5;
int a[N],n,m;
ll sum[N],c[2][N];
void add(int which,int x,int val){
for(;x<=n;x+=lowbit(x))
c[which][x]+=val;
}
ll query(int which,int x){
ll res=0;
for(;x;x-=lowbit(x))
res+=c[which][x];
return res;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
sum[i]=sum[i-1]+a[i];
}
while(m--){
char op[2];
int l,r,d;
scanf("%s%d%d",op,&l,&r);
if(op[0]=='C'){
scanf("%d",&d);
add(0,l,d);
add(1,l,d*l);
add(0,r+1,-d);
add(1,r+1,-d*(r+1));
}else{
ll ans=sum[r]+(r+1)*query(0,r)-query(1,r);
ans-=sum[l-1]+l*query(0,l-1)-query(1,l-1);
printf("%lld\n",ans);
}
}
}
3.[POJ2352]Stars
题意:链接
分析:这里 https://blog.csdn.net/qq_42367703/article/details/98093999 说得很清楚
4.「POJ2299」Ultra-QuickSort
题意:请分析,对于一串数列,至少要交换多少次(相邻两个数交换)才能使该数列有序(从小到大)?
分析:逆序对模板
for(int i=n;i;i--){
ans+=query(a[i]-1);
add(a[i],1);
}
5.「NOIP2013」火柴排队
题意:现有两列每列个数为 \(n\) 的火柴,且每列中火柴棒的高度均不相同,求得到 \(\sum[(a_i-b_i)^2]\) 的最小值的时候,最少需要交换火柴的次数,其中 \(i\) 表示 \(a、b\) 两列火柴棒中第i根火柴
分析:由数据范围,我们首先知道要离散化,然后我们可以对式子进行变型:
所以也就是求 \(\sum(a_ib_i)\) 最大值,
容易证明, 对于数列 \(k_1\dots k_n,p_1\dots p_n,\sum(k_i*p_i)\) 的最小值要求两个数列有序的分别从小到大(或从大到小)排列
也即是:顺序之乘>=乱序之乘
所以也就是逆序对了。
6.楼兰图腾
题意:给定一串序列,分别找到满足 \(1<=i<j<k<=n\) 且 \(yi>yj,yj<yk\) (称为‘V’图腾); 满足 \(1<=i<j<k<=n\) 且 \(y_i<y_j,y_j>y_k\) (称为' ∧ ' 图腾)的数量
分析: 树状数组求逆序对,让我们知道了如何在一个序列中计算每个数后面有多少个数比它小,因此我们可以通过这个性质来做一些事情
‘v’图腾求法
正序扫描序列 \(a \) ,利用树状数组求出每个 \(a[i]\) 前面有几个数比它大,记录为 \(left[i]\):
for(int i=1;i<=n;add(a[i++],1))l[i]=query(mx)-query(a[i]);
倒序扫描序列 \(a\),利用树状数组求出每个 \(a[i]\) 后面有几个数比它大,记录为 \(right[i]\) :
for(int i=n;i>=1;add(a[i--],1))r[i]=query(mx)-query(a[i]);
然后我们就可以枚举每一个点为中间点,那么这个点为中心的’v’图腾的个数就是\(\sum_{i=1}^n=left[i]\times right[i]\)
’^’图腾求法
正序扫描序列 \(a\),利用树状数组求出每个 \(a[i]\) 前面有几个数比它小,记录为 \(left[i]\)
for(int i=1;i<=n;add(a[i++],1))l[i]=query(a[i]-1);
倒序扫描序列 \(a\),利用树状数组求出每个 \(a[i]\) 后面有几个数比它小,记录为 \(right[i]\)
for(int i=n;i>=1;add(a[i--],1))r[i]=query(a[i]-1);
(这波是压行大法好)
然后我们就可以枚举每一个点为中间点,那么这个点为中心的’^’图腾的个数就是\(\sum_{i=1}^1=left[i]×right[i]\)
同时注意数组清空
这段分析摘自(并良心地加上了 \(Latex\) ):
作者:秦淮岸灯火阑珊
链接:https://www.acwing.com/solution/content/1008/
来源:AcWing
7.「IOI2001」Mobile Phones
题意:矩阵内单点修改,区域询问
分析:二维树状数组
8.[CQOI2006]简单题
题意:有一个 \(n\) 个元素的数组,每个元素初始均为 \(0\)。有 \(m\) 条指令,有两种操作:
- 形如\(1 l r\),将区间 \([l,r]\) 之间的数反转,反转规则是:0 变为 1,1 变为 0。
- 形如 \(2 x\),输出数组下标为 \(x\) 的值
分析: 由1变0,0变1这一波操作你们想到了什么? 异或
区间修改,考虑异或差分序列,每个元素的真实值是异或前缀和,修改的时候改 \(l\) 和 \(r+1\) 两个点,树状数组维护。