首页云计算数据结构:树状数组

数据结构:树状数组

时间2024-07-31 02:15:52发布ongwu分类云计算浏览69

树状数组

基本操作:1.快速求前缀和 2.修改一个数。

基本图示:

 lowbit:求出一个数字二进制最后一个1的位置

原理:

我们发现,除了最后一个1,以及其后面的0,其余位置都是反,则要求最后一位1的位置,可以将原码和补码按位&;而计算机中-x有取补码,则lowbit(x)=x&-x;

#define lowbit (x&(-x));

例子:数组:1,2,3,4,5,6,7

1.如果想要修改6=0110, 

 

则会影响0110对应的:用lowbit+操作就可以修改,即6处修改,6+2(lowbit(6)=2)=8,对8处修改

2.如果想要查询【2,5】之间的和,可以求sum(5)-sum(1)

 sum(5),5=0011,则用lowbit-就可以求出,sum加上5处的值,lowbit(5)=1,5-1=4,sum再加上4处的值,刚好就是sum(5),求出【2,5】,类似于前缀和,求出sum(5)-sum(1)。

综上所述:

1.单点修改

void upload(int x,int d){
while(x<=n){
tree[x]+=d;
x+=lowbit(x);
}
}

2.区间查询

int sum(int x){
int ans=0;
while(x>0){
ans+=tree[x];
x-=lowbit(x);
}
return ans;
}

 一个典型题:

我们不难知道,如果想要求解此题,想要知道V(题中)只要知道第个数,左边有个比它大,右边有个比它大,然后就是V的个数,就是总的V图腾数,∧同理(左边,右边比它小)

然后利用树状数组就可以求解逆序对:

lower指比它大的数,Greater比它小的数。

第一遍求左边比它大(小)的数,第二遍(清空第一遍的tr,并逆序求一遍)求右边比它大(小)的数。

注:为啥会求出来,理由是每次更新树状数组, upload(y,1),则+1,之后遍历下一个数,就知道左边那群数比这个数大还是小的个数是多少(具体例子如下:)

1for(int i=0;i<n;i++){
int y=a[i];
Greater[i]=sum(n)-sum(y);
lower[i]=sum(y-1);
upload(y,1);
}
2memset(tr, 0, sizeof tr);
LL res1 = 0, res2 = 0;
for (int i = n; i; i -- )
{
int y=a[i];
res1+=great[i]*(LL)(sum(n)-sum(y));
res2+=lower[i]*(LL)(sum(y-1));
upload(y,1);
}

 左边是第一次,右边是逆序第二次(上面代码1与代码2对应)

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N=200010;
int tr[N];
typedef long long LL;
#define lowbit (x&(-x));
int n;
void upload(int x,int d){
while(x<=n){
tr[x]+=d;
x+=lowbit(x);
}
}
int sum(int x){
int ans=0;
while(x>0){
ans+=tr[x];
x-=lowbit(x);
}
return ans;
}
int a[N],great[N],lower[N];
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i ++ ) scanf("%d", &a[i]);
for(int i=1;i<=n;i++){
int y=a[i];
great[i]=sum(n)-sum(y);//right
lower[i]=sum(y-1);//left
upload(y,1);
}
memset(tr, 0, sizeof tr);
LL res1 = 0, res2 = 0;
for (int i = n; i; i -- )
{
int y=a[i];
res1+=great[i]*(LL)(sum(n)-sum(y));
res2+=lower[i]*(LL)(sum(y-1));
upload(y,1);
}
printf("%lld %lld\n", res1, res2);
return 0;
}

3.区间修改,单点查询

利用差分数组:

 

详见差分性质 

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 100010;
int n, m;
int a[N];
LL tr[N];
#define lowbit (x&(-x));
void upload(int x,int d){
while(x<=n){
tr[x]+=d;
x+=lowbit(x);
}
}
LL sum(int x){
int ans=0;
while(x>0){
ans+=tr[x];
x-=lowbit(x);
}
return ans;
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i ++ ) scanf("%d", &a[i]);
for (int i = 1; i <= n; i ++ ) upload(i, a[i] - a[i - 1]);
while (m -- )
{
char op[2];
int l, r, d;
scanf("%s%d", op, &l);
if (*op == C)
{
scanf("%d%d", &r, &d);
upload(l, d), upload(r + 1, -d);
}
else
{
printf("%lld\n", sum(l));
}
}
return 0;
}

4.区间修改,区间查询

1.记录差分

int b = a[i] - a[i - 1];

2.大致思路

如果要求区间修改,利用差分,同上。

但是,区间查询则需要利用一些数学推导:

其中,a为原数组,b为差分数组:

 总面积:,减去黄色部分:,则白色区域:

LL prefix_sum(int x)
{
return sum(tr1, x) * (x + 1) - sum(tr2, x);
}

LL tr1[N];  // 维护b[i]的前缀和

LL tr2[N];  // 维护b[i] * i的前缀和

区间查询prefix_sum(r)-prefix_sum(l-1))

区间修改

add(tr1, l, d), add(tr2, l, l * d);
add(tr1, r + 1, -d), add(tr2, r + 1, (r + 1) * -d);

 总代码

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 100010;
int n, m;
int a[N];
LL tr1[N]; // 维护b[i]的前缀和
LL tr2[N]; // 维护b[i] * i的前缀和
int lowbit(int x)
{
return x & -x;
}
void add(LL tr[], int x, LL c)
{
for (int i = x; i <= n; i += lowbit(i)) tr[i] += c;
}
LL sum(LL tr[], int x)
{
LL res = 0;
for (int i = x; i; i -= lowbit(i)) res += tr[i];
return res;
}
LL prefix_sum(int x)
{
return sum(tr1, x) * (x + 1) - sum(tr2, x);
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i ++ ) scanf("%d", &a[i]);
for(int i=1;i<=n;i++){
int b=a[i]-a[i-1];
add(tr1,i,b);
add(tr2,i,(LL)b*i);
}
while(m--){
char op[2];
int l,r,d;
scanf("%s%d%d", op, &l, &r);
if (*op == Q) {
printf("%lld\n",prefix_sum(r)-prefix_sum(l-1));
}
else{
scanf("%d", &d);
add(tr1, l, d), add(tr2, l, l * d);
add(tr1, r + 1, -d), add(tr2, r + 1, (r + 1) * -d);
}
}
return 0;
}

5.应用:谜一样的牛

 这个题从正序入手不容易,可以从逆序入手。如何入手,可以举个例子:

 从h【4】=0开始,发现前面没有一个比它低,那么它最低,则是1。然后tree更新

则发现,只要从最后一个开始,找到第h[i]+1个数就是它的位置,同时更新tree。

为了找到h[i]+1(其实就是求第k小的数,而tr数组记录了数组的前缀和,也就表示了数字到底是在数组中第几小),可以利用二分法。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N=100010;
int n;
int h[N];
int tr[N];
int ans[N];
#define lowbit x&(-x);
void update(int x,int d){
while(x<=n){
tr[x]+=d;
x+=lowbit(x);
}
}
int sum(int x){
int ans=0;
while(x>0){
ans+=tr[x];
x-=lowbit(x);
}
return ans;
}
int main()
{
cin>>n;
for(int i=2;i<=n;i++) cin>>h[i];
for(int i=1;i<=n;i++) update(i,1);
for(int i=n;i;i--){
int k=h[i]+1;
int l=1,r=n;
while(l<r){
int mid=(l+r)>>1;
if(sum(mid)>=k) r=mid;
else l=mid+1;
}
ans[i]=r;
update(r,-1);
}
for (int i = 1; i <= n; i ++ ) printf("%d\n", ans[i]);
return 0;
}

Ongwu博客 版权声明:以上内容未经允许不得转载!授权事宜或对内容有异议或投诉,请联系站长,将尽快回复您,谢谢合作!

展开全文READ MORE
最新AI智能聊天对话问答系统源码(详细图文搭建部署教程)+AI绘画系统,DALL-E3文生图, Whisper & TTS 语音识别,文档分析 【笔记】先求修改没保存的文本文件-在虚拟机中输入 yum makecache报错

游客 回复需填写必要信息