【题解】逆序对(树状数组)

本文介绍了一种高效求解逆序对数量的方法,利用树状数组进行优化,通过逆序访问序列并统计逆序对,适用于序列长度和数值范围较大的情况。文章提供了详细的算法分析和C++代码实现。

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

题面

【题目描述】
给定一个序列a1,a2,...,ana_1,a_2,...,a_na1,a2,...,an,如果存在i<ji < ji<j并且ai>aja_i>a_jai>aj,那么我们称之为逆序对,求逆序对的数目。
【输入】
第一行为nnn,表示序列长度,接下来的nnn行,第n+1n+1n+1行表示序列中的第iii个数。
【输出】
所有逆序对总数。
【样例输入】

4
3
2
3
2

【样例输出】

3

【数据范围】
n<=105,ai<=105。n<=10^5,a_i<=10^5。n<=105,ai<=105

算法分析

归并对可以使用归并排序求解,在这里我们使用树状数组来实现。
(1)定义数组ssss[x]s[x]s[x]表示数值为xxx出现的次数,即桶计数。
再定义树状数组cccc[x]c[x]c[x]表示数值在区间[x−lobit(x)+1,x][x-lobit(x)+1,x][xlobit(x)+1,x]的个数。
(2)逆序访问nnn个数(a[n],a[n−1],...a[1]a[n],a[n-1] ,...a[1]a[n],a[n1],...a[1]),对于a[i]a[i]a[i],统计前缀和sum(i−1)sum(i-1)sum(i1),表示值范围在111 ~ i−1i-1i1的个数,因为逆序访问,前缀和包含的数全部比a[i]a[i]a[i]小,且在a[i]a[i]a[i]后面,形成了逆序对sum[i−1]sum[i-1]sum[i1]个。
(3)将每次前缀和相加,就是最后的答案。
(4)访问完a[i],就执行单点增加,数值为a[i]的个数+1,即s[a[i]]+1。
如果数值太大,桶装不下怎么办呢?可以使用离散化,所谓离散化就是把无限空间中有限的个体映射到有限的空间中去,以此提高算法的时空效率。通俗的说,离散化是在不改变数据相对大小的条件下,对数据进行相应的缩小。
例如:我需要求解序列:99999999 199999999 88888888的逆序对,实际可以看作是求序列:1 3 2的逆序对,因为逆序对跟大小关系有关,和具体的值无关。
【程序实现】:
这里的s数组,可以不使用,方便大家理解。

#include<bits/stdc++.h>
#define N 500100
using namespace std;
int n,c[N],a[N],maxn;
int lowbit(int x)
{
    return x&(-x);
}
void update(int x,int val)
{
    for(int i=x;i<=maxn;i+=lowbit(i))	
        c[i]+=val;
}
int sum(int x)
{
    int ans=0;
   for(int i=x;i>0;i-=lowbit(i))
        ans+=c[i];
    return ans;
}
int main()
{
    scanf("%d",&n);
    long long ans=0;
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        maxn=max(maxn,a[i]);	//最大值 
    }
    for(int i=n;i>=1;i--)
    {
        ans+=(long long)sum(a[i]-1);
        update(a[i],1);
    }
    cout<<ans<<endl;
    return 0;
 } 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值