- 今天刚刚开学提高课(什么,你怎么知道我的基础课还有好多不会的)
- 咳咳,实在是因为只刷题太枯燥了,所以想着刷累了看会儿课,这几天老是听别人讲树状数组和线段树,发现自己也可以学,虽然我很菜,所以下面就对树状数组做了一个小结
- 树状数组是干什么的,简而言之就是平均复杂度的前缀和
- 前缀和应该都知道吧,树状数组能在o(logn)的复杂度内求到前缀和,但是换回的是查询更慢(也需要o(logn))前缀和的查询只需要o1
- 但众所周知,logn几乎是常数,所以这波树状数组完胜
- 首先你得维护一个tree数组,他代表的含义就是树状数组里面的值
- 每一个tree[x]代表[x - lowbit(x) + 1,x]的前缀和,干说有些难懂
- 像下面这样
x | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
---|
x的二进制 | 1 | 10 | 11 | 100 | 101 | 110 | 111 | 1000 | 1001 |
lowbit(x) | 1 | 2 | 1 | 4 | 1 | 2 | 1 | 8 | 1 |
tree[x] | a1 | a1+a2 | a3 | a1+…+a4 | a5 | a5+a6 | a7 | a1+…+a8 | a9 |
- 如何求前缀和
- sum[8] = tree[8]
- sum[7] = tree[7]+tree[6]+tree[4]
- sum[9] = tree[9]+tree[8]
- 首先从7开始先加上tree[7]
- 之后7-lowbit[7]=6加上tree[6]
- 之后6-lowbit[6]=4加上tree[4]
- 最后4-lowbit(4)=0结束
- tree数组的更新是同样的
- 二进制解释比如说x=10000,则x-1=01111
- 第一段就是(01110~01111],a[15]
- 第二段就是(01100~01110],a[13] ~ a[14]
- 第三段就是(01000~01100],a[9] ~ a[12]
- 第四段就是(00000~01000],a[1] ~ a[8]
- 加起来再加a[16]就是tree[16]
- 查询操作: 观察发现tree[x]=a[x]+tree[x-1]+tree[x-lowbit(x)-1]+…
- 所以对应的循环遍历是
int res=0;
for(int i=x;i;i-=lowbit(i)) res+=tree[i];
return res;
- 修改操作: 再有就是修改了,我们发现000011100…直接影响到的点有且只有一个00010000…也就是x+lowbit(x)
- 所以对应的循环遍历是(如果a[x]要加c)
for(int i=x;i<=n;i+=lowbit(i)) tree[i]+=c;
- 实现很简单,应用也简单,下面用一个例题来终结这篇博客
- link<-----楼兰图腾
#include<iostream>
#include<cstring>
using namespace std;
const int N=2e5+10;
int n,a[N],low[N],great[N];
typedef long long ll;
int tree[N];
int lowbit(int x)
{
return x&-x;
}
void add(int x)
{
for(int i=x;i<=n;i+=lowbit(i)) tree[i]+=1;
}
int sum(int x)
{
int res=0;
for(int i=x;i;i-=lowbit(i)) res+=tree[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];
low[i]=sum(y-1);
great[i]=sum(n)-sum(y);
add(y);
}
memset(tree,0,sizeof tree);
ll ans1=0,ans2=0;
for(int i=n;i>=1;i--)
{
int y=a[i];
ans1+=1ll*low[i]*(sum(y-1));
ans2+=1ll*great[i]*(sum(n)-sum(y));
add(y);
}
cout<<ans2<<' '<<ans1<<endl;
return 0;
}