逆序对的定义:对于给定的一段正整数序列,逆序对就是序列中 ,让统计逆序对的个数。
本题的数据加强,允许存在重复的数字。这并不影响下面分析的正确性。
思路
对于a[ i ] ,我们要求的是它前面大于a [ i ]的个数。
采用的思路是: i减去 小于等于a[i]的个数,剩下的就是大于a[i] 的个数
使用树状数组来求前缀和(query),也就是来计算小于等于a[i]的个数,对于第i个数a[ i ],其逆序对的个数是i-query(a[i])
我是看了这篇博客会的https://blog.csdn.net/ssimple_y/article/details/53744096
这里需要注意的是位置的理解,即a数组中的数值a[i],作为另一个数组中的下标。
比如 数组a={5,2,3,4,1},假设数组下标是1~5,a[1]==5,5就是在数组 t 中的下标,5出现一次5的位置就加1,t数组初始化全0.
遍历a数组
a[1]=5,数组t中;
这个时候对数组t求前缀和(从第一项加到t[5]),sum=1,意思是在前面包括自身,≤5的个数=1,此时i=1,表示总共一个数,则其中大于5的个数=0;
遍历到a[2]=2,数组t中;
此时对数组t关于t[2] 求前缀和,发现等于1,意思是a数组2前面包括2本身有1个数字≤2。此时i=2,总共有2个数,其中比2大的个数:2-1=1。也就是逆序的个数。
逆序对个数 ans+=i-前缀和(a[i]).
此时逆序对个数:ans=1;
遍历到a[3]=3
发现t[3]前面的前缀和=2,意思是3前面包括本身≤3的个数是2,此时i=3,总共三个数,前面比3大的个数是:3-2=1;
此时逆序对个数 ans+=1 ,所以 ans=2;
遍历到a[4]=4,
发现t[4]前面的前缀和=3,意思是4前面有3个数小于等于4。换句话说,有1个大于4,所以逆序对个数 加1
逆序对个数:ans+=1,所以 ans=3
遍历到a[5]=1,
发现t[1]的前缀和为1,则1前面比1小的数的个数为1.此时i=5,5-1=4为逆序对的个数
所以逆序对的个数 ans+=4;
最终ans=7;
ac代码
#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
typedef long long ll;
const int maxn=5e5+10;
ll n,a[maxn],b[maxn];//离散化用数组
ll t[maxn];//树状数组
//快读
inline ll read(){
ll k=0,f=1;
char c=getchar();
while(c<'0'||c>'9'){
if(c=='-') f=-1;
c=getchar();
}
while(c>='0'&&c<='9'){
k=k*10+c-'0';
c=getchar();
}
return f*k;
}
//计算前驱和后继
ll lowbit(ll x){
return x&-x;
}
// 树状数组更新
void update(ll i,ll k){
while(i<=n){
t[i]+=k;
i+=lowbit(i);
}
}
//前缀和
ll query(ll x){
ll res=0;
while(x>0){
res+=t[x];
x-=lowbit(x);
}
return res;
}
int main(){
n=read();
for(ll i=1;i<=n;i++){
a[i]=read();
b[i]=a[i];
}
sort(b+1,b+n+1);
ll m=unique(b+1,b+1+n)-b;
for(ll i=1;i<=n;i++){
a[i]=lower_bound(b+1,b+1+n,a[i])-b;//用来离散化
}
ll ans=0;
for(ll i=1;i<=n;i++){
update(a[i],1);//树状数更新,出现的话,给位置+1
ans+=i-query(a[i]);//维护的是比a[i]大的个数,累加就是答案
}
printf("%lld",ans);
}