树状数组介绍(蓝书详细讲解)+ AcWing 241. 楼兰图腾(树状数组模板题 区间查询 单点修改 逆序对思想 )

该博客介绍了如何使用树状数组解决平面中‘V’形和‘^’形图腾计数问题。通过正序和倒序扫描,计算每个点前后比它大和小的数,利用树状数组的区间查询和单点修改,最终求得逆序对,从而得出图腾的数量。核心代码包括树状数组的初始化、查询和修改操作,实现了O(nlogn)的时间复杂度解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

树状数组介绍以及如何用于求逆序对(蓝书讲解):
微信图片_20220212235747.png
微信图片_20220212235812.png
微信图片_20220212235842.png
微信图片_20220212235916.png
微信图片_20220213000529.png
微信图片_20220213002040.png

经典例题AcWing 241. 楼兰图腾

在这里插入图片描述
在这里插入图片描述

本题只要求实现树状数组基本操作 区间查询单点修改

空白代码

#include<bits/stdc++.h>

using namespace std;
#define int long long
const int N = 2e5+10;
int a[N], c[N], up[N], down[N];

inline int lowbit(int x) {return x & (-x);}

int ask(int x)
{
    int ans = 0;
    while(x) {ans+=c[x], x-=lowbit(x);}
    return ans;
}

void add(int x, int y)
{
    for(; x<=n; x+=lowbit(x)) c[x]+=y;
}

signed main()
{
    cin>>n;
    for(int i=1;i<=n;++i)
    {
        scanf("%d",&a[i]);
        add(a[i], 1), up[i] = ask(n)-ask(a[i]), down[i] = ask(a[i]-1);
    }
    
    memset(c, 0, sizeof c);
    int res1 = 0, res2 = 0;
    for(int i=n; i>=1; --i)
    {
        add(a[i], 1);
        res1+=up[i]*(ask(n)-ask(a[i]));
        res2+=down[i]*ask(a[i]-1);
    }
    cout<<res1<<' '<<res2<<"\n";
    
    return 0;
}

讲解

题意

平面上有N个点(N<=2e5,这就提示我们只能用O(n)或者O(nlogn)的算法了),每个点横、纵坐标范围都是1~N,任意两个点横、纵坐标不同。

若三个点(x1, y1)(x2, y2)(x3, y3)满足x1 < x2 < x3y1 > y2y3 > y2,则这三个点构成“v”形图腾。

若三个点(x1, y1)(x2, y2)(x3, y3)满足x1 < x2 < x3y1 < y2y3 < y2,则这三个点构成“^”形图腾。

题目让我们求给定平面上两种图腾的个数。

思路

如果把这N个点按照横坐标排序,那么它们的纵坐标则是1~N的一个排列,我们将这个排列存入a数组。

在刚刚讲述的树状数组求逆序对中,我们知道了如何在一个序列中计算每个数的后面有多少个数比它小,类似这个做法,我们可以:

  • 正序扫描序列,利用树状数组将每一个a[i]前面有几个数比它大(ask(n)-ask(a[i])),有几个数比它小(ask(a[i]-1))求出。并将这些值先分别存入up[i]down[i],方便后续运算。

  • 倒序扫描序列,利用树状数组将每一个a[i]后面有几个数比它大(ask(n)-ask(a[i])),有几个数比它小(ask(a[i]-1))求出。

在倒序的过程中,我们可以依次次枚举每个点作为中间点,以这个点为中心的“v”图腾个数根据乘法原理显然是up[i]*(ask(n)-ask(a[i])),所以总的“v”图腾个数就是将n个点算出来的值累加即可。同理,在此过程中也可求出“^”图腾的总个数。

up[i]是正序扫描时求得的当前点前面比它大的元素个数,ask(n)-ask(a[i])是倒序扫描时当前点后面比它大的元素个数,相乘即为答案)

重点代码片段

树状数组c初始化O(nlogn)

比如我们想对包含n个元素的序列a建立一个树状数组,
建立一个全0的数组c,对每个位置x执行add(x, a[x])

memset(c, 0, sizeof c);
for(int i=1;i<=n;++i) add(i, a[i]);

倒序查询1~x前缀和O(logn)

int ask(int x)
{
    int ans = 0;
    while(x) {ans+=c[x], x-=lowbit(x);}
    return ans;
}

正序单点修改O(logn)

void add(int x, int y)
{
    for(; x<=n; x+=lowbit(x)) c[x]+=y;
}

时间复杂度

nlogn

代码

#include<bits/stdc++.h>

using namespace std;
#define int long long
const int N = 2e5+10;
int a[N], c[N], up[N], down[N];
//a存储n个点的纵坐标,c表示树状数组i结点覆盖的范围和,up表示左边比第i个位置大的数的个数,down表示左边比第i个位置小的数的个数
int n;

inline int lowbit(int x) {return x & (-x);}

int ask(int x)//查询序列1~x前缀和
{
    int ans = 0;
    while(x) {ans+=c[x], x-=lowbit(x);}
    return ans;
}

void add(int x, int y)//将第x个数加上y,构建树状数组c
{
    for(; x<=n; x+=lowbit(x)) c[x]+=y;
}

signed main()
{
    cin>>n;
    for(int i=1;i<=n;++i)//正序依次统计每个位置左边比第i个数纵坐标y小的数的个数、以及大的数的个数
    {
        scanf("%d",&a[i]);
        add(a[i], 1), up[i] = ask(n)-ask(a[i]), down[i] = ask(a[i]-1);
//add(a[i], 1);---将y加入树状数组,即数字y出现1次
//up[i] = ask(n)-ask(a[i]);---在前面已加入树状数组的所有数中统计在区间[y + 1, n]的数字的出现次数
//down[i] = ask(a[i]-1);---在前面已加入树状数组的所有数中统计在区间[1, y - 1]的数字的出现次数
    }
    
    memset(c, 0, sizeof c);//清空树状数组,
    int res1 = 0, res2 = 0;
    for(int i=n; i>=1; --i)//倒序统计每个位置右边比第i个数y小的数的个数、以及大的数的个数
    {
        add(a[i], 1);
        res1+=up[i]*(ask(n)-ask(a[i]));//乘法原理
        res2+=down[i]*ask(a[i]-1);
    }
    cout<<res1<<' '<<res2<<"\n";
    
    return 0;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值