入门博文:http://www.cnblogs.com/acgoto/p/8583952.html
共同点:同时一个节点维护多个叶子节点的信息
不同点:线段树节点采用二分的规则,而树状数组节点利用了bit位的性质来锁定管理的叶子节点,且树状数组只能直接查询1~i区间的信息,而线段树可以直接查询 [ l , r ] [l,r] [l,r] 区间信息,
树状数组可以解决问题的思路推广到线段树都可以用线段树解决。不过树状数组代码量是真的小!
树状数组核心函数:
这样的代码要求其叶子节点必须从1开始(因为此处维护的都是1~i的信息,可以改下代码使维护0到i的信息,不过代码就不简洁了)
int lowbit(ll val){ //获得val的1的最低位的二进制权值
return val&(-val);
}
int getsum(ll k)//遍历1~k区间的管理节点。
{
ll ans=0;
while(k>0)
{
ans+=bt[k];
k-=lowbit(k);
}
return ans;
}
void modify(ll k,ll val)//向上更新管理叶子节点k的节点信息。
{
while(k<=n)
{
bt[k]+=val;
k+=lowbit(k);
}
}
洛谷模板题,改点查段:https://www.luogu.org/problemnew/show/P3374
#include
#define mset(a,b) memset(a,b,sizeof(a))
using namespace std;
typedef long long ll;
const int maxn=5e5+100;
ll qwe[maxn],n,q;
int lowbit(ll val){
return val&(-val);
}
int getsum(ll k)
{
ll ans=0;
while(k>0)
{
ans+=qwe[k];
k-=lowbit(k);
}
return ans;
}
int modify(ll k,ll val)
{
while(k<=n)
{
qwe[k]+=val;
k+=lowbit(k);
}
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cin>>n>>q;
for(int i=1;i<=n;++i){
ll val;
cin>>val;
modify(i,val);
}
while(q--)
{
ll fa,x,y;
cin>>fa>>x>>y;
if(fa==1)
{
modify(x,y);
}
else{
cout<<getsum(y)-getsum(x-1)<<endl;
}
}
return 0;
}
比如:LIS(最长递增子序列),求逆序对,改点查段,改段查点的问题都可以用树状数组解决。
LIS(最长递增子序列)思想:
f [ i ] f[i] f[i] 表示当前以值 i i i 为递增子序列末尾的序列最长长度。每个叶子 i i i 记录当前 f [ i ] f[i] f[i], 最大值,那么用树状数组节点维护其叶子节点的最大值即可。如果该此时序列中数字值为val,那么 f [ v a l ] = m a x ( f [ i ] ) i ∈ [ 1 , v a l ] + 1 = g e t m a x ( i ) + 1 f[val]=max(f[i])_{i\in[1,val]}+1 =\ \ ~getmax(i)+1 f[val]=max(f[i])i∈[1,val]+1= getmax(i)+1 ps:有时候需要离散化,所以还是二分求LIS比较好用
求逆序对思想:
每个叶子 i i i 记录当前值i的出现次数,那么用树状数组节点维护其管理的叶子节点出现次数和即可,假设当前第k个数其值为val,那么前面大于val的个数= k − 1 − g e t s u m ( v a l ) k-1-getsum(val) k−1−getsum(val)
#include
#define mset(a,b) memset(a,b,sizeof(a))
using namespace std;
typedef long long ll;
const int maxn=5e5+100;
int tot,n;
int unq[maxn],qwe[maxn],bt[maxn];
int getind(int val)
{
return lower_bound(unq,unq+tot,val)-unq;
}
int lowbit(int val)
{
return val&-val;
}
ll getsum(int k)
{
ll ans=0ll;
while(k>0)
{
ans+=bt[k];
k-=lowbit(k);
}
return ans;
}
void modify(int k)//k叶子+1
{
while(k<=n)
{
++bt[k];
k+=lowbit(k);
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cin>>n;
for(int i=0;i<n;++i)
cin>>unq[i],qwe[i]=unq[i];
sort(unq,unq+n);
tot=unique(unq,unq+n)-unq;
ll ans=0ll;
mset(bt,0);
for(int i=0;i<n;++i)
{
int ind=getind(qwe[i])+1;
// cout<<"ind:"<
ans+=i-getsum(ind);
modify(ind);
/*先求逆序数i- 1~val的个数 然后插入*/
}
cout<<ans<<endl;
}
改段查点:
修改:将区间 $[l,r] $ 的每个数加val,查询:求点k处的值
假设原数组为 N u m [ ] Num[] Num[],我们用 d [ ] d[] d[] 记录相邻元素的差值,比如 d [ i ] = n u m [ i ] − n u m [ i − 1 ] d[i]=num[i]-num[i-1] d[i]=num[i]−num[i−1],且我们让 d [ 1 ] = n u m [ 1 ] d[1]=num[1] d[1]=num[1] ,那么 n u m [ i ] = d [ 1 ] + d [ 2 ] . . . . . + d [ i ] num[i]=d[1]+d[2].....+d[i] num[i]=d[1]+d[2].....+d[i]
至于修改操作,我们只需将 d [ l ] d[l] d[l]加上 v a l val val, d [ r + 1 ] d[r+1] d[r+1]减去 v a l val val即可,
我们维护d数组即可。
洛谷模板:https://www.luogu.org/problemnew/show/P3368
代码:
// luogu-judger-enable-o2
#include
#define mset(a,b) memset(a,b,sizeof(a))
using namespace std;
typedef long long ll;
const int maxn=5e5+100;
ll qwe[maxn],n,q;
int lowbit(ll val){
return val&(-val);
}
int getsum(ll k)
{
ll ans=0;
while(k>0)
{
ans+=qwe[k];
k-=lowbit(k);
}
return ans;
}
void modify(ll k,ll val)
{
while(k<=n)
{
qwe[k]+=val;
k+=lowbit(k);
}
}
ll va[maxn];
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cin>>n>>q;
for(int i=1;i<=n;++i){
cin>>va[i];
}
modify(1,va[1]);
for(int i=2;i<=n;++i)
modify(i,va[i]-va[i-1]);
while(q--)
{
ll fa,x,y,z;
cin>>fa;
if(fa==1)
{
cin>>x>>y>>z;
modify(y+1,-z);
modify(x,z);
}
else{
cin>>x;
cout<<getsum(x)<<endl;
}
}
return 0;
}