题面
【题目描述】
给定一个序列
a
1
,
a
2
,
.
.
.
,
a
n
a_1,a_2,...,a_n
a1,a2,...,an,如果存在
i
<
j
i < j
i<j并且
a
i
>
a
j
a_i>a_j
ai>aj,那么我们称之为逆序对,求逆序对的数目。
【输入】
第一行为
n
n
n,表示序列长度,接下来的
n
n
n行,第
n
+
1
n+1
n+1行表示序列中的第
i
i
i个数。
【输出】
所有逆序对总数。
【样例输入】
4
3
2
3
2
【样例输出】
3
【数据范围】
n
<
=
1
0
5
,
a
i
<
=
1
0
5
。
n<=10^5,a_i<=10^5。
n<=105,ai<=105。
算法分析
归并对可以使用归并排序求解,在这里我们使用树状数组来实现。
(1)定义数组
s
s
s,
s
[
x
]
s[x]
s[x]表示数值为
x
x
x出现的次数,即桶计数。
再定义树状数组
c
c
c,
c
[
x
]
c[x]
c[x]表示数值在区间
[
x
−
l
o
b
i
t
(
x
)
+
1
,
x
]
[x-lobit(x)+1,x]
[x−lobit(x)+1,x]的个数。
(2)逆序访问
n
n
n个数(
a
[
n
]
,
a
[
n
−
1
]
,
.
.
.
a
[
1
]
a[n],a[n-1] ,...a[1]
a[n],a[n−1],...a[1]),对于
a
[
i
]
a[i]
a[i],统计前缀和
s
u
m
(
i
−
1
)
sum(i-1)
sum(i−1),表示值范围在
1
1
1 ~
i
−
1
i-1
i−1的个数,因为逆序访问,前缀和包含的数全部比
a
[
i
]
a[i]
a[i]小,且在
a
[
i
]
a[i]
a[i]后面,形成了逆序对
s
u
m
[
i
−
1
]
sum[i-1]
sum[i−1]个。
(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;
}