逆序对

题目描述

猫猫 TOM 和小老鼠 JERRY 最近又较量上了,但是毕竟都是成年人,他们已经不喜欢再玩那种你追我赶的游戏,现在他们喜欢玩统计。

最近,TOM 老猫查阅到一个人类称之为“逆序对”的东西,这东西是这样定义的:对于给定的一段正整数序列,逆序对就是序列中 $a_i>a_j$ 且 $i<j$ 的有序对。知道这概念后,他们就比赛谁先算出给定的一段正整数序列中逆序对的数目。注意序列中可能有重复数字。

Update:数据已加强。

输入格式

第一行,一个数 $n$,表示序列中有 $n$个数。

第二行 $n$ 个数,表示给定的序列。序列中每个数字不超过 $10^9$。

输出格式

输出序列中逆序对的数目。

样例 #1

样例输入 #1

6
5 4 2 6 3 1

样例输出 #1

11

提示

对于 $25\%$ 的数据,$n \leq 2500$

对于 $50\%$ 的数据,$n \leq 4 \times 10^4$。

对于所有数据,$n \leq 5 \times 10^5$

请使用较快的输入输出

应该不会 $O(n^2)$ 过 50 万吧 by chen_zhe

题目分析

目的:求出序列中逆序对的数目。

首先确定逆序对的特征:

  1. 位置: $i<j$
  2. 大小: $a_i>a_j$

问题可抽象为求满足条件的二元组数量。能较快地想到暴力的解法:双重循环枚举所有的二元组,筛选所有符合条件的元素即可,复杂度为 $O(n^2)$。

for(int i=1;i<=n;i++){
    for(int j=i+1;j<=n;j++){
        if(a[i]>a[j]) cnt++;
    }
}

根据数据范围可以获得 $50\%$ 的分数。对于剩下的分数需要考虑优化方法。

当前速度慢在,需要一组组的找过去,每次也只能确定一组逆序对,若能一次性确定多组逆序对,自然就能加快统计的速度的。如何才能一次性就确定多组逆序对了呢?

假设序列存在升序的一段 $[L,R]$ , $i$ 在升序区间内, $j$ 在升序区间右侧。若 $a_i>a_j$ 则 $a_i$ 与 $a_j$ 可以构成一组逆序对,且 $a_i \sim a_R$ 的元素也能与 $a_j$ 构成逆序对,因为从位置上来看,它们位于 $j$ 的左侧,数值上他们又是大于 $a_j$ 的所以符合逆序对特征。

在这样的比较过程中,我们需要序列呈现有序的状态,比较的位置的相对位置需要与原序列一致,我们可以利用归并排序来实现这一效果。

先看归并排序的模板:

#include <bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int a[N],b[N];
void merge(int l1,int r1,int l2,int r2){
    int i=l1,j=l2,k=l1;
    while(i<=r1 && j<=r2){
        if(a[i]<a[j]){
            b[k++]=a[i++];
        }else{
            b[k++]=a[j++];
        }
    }
    while(i<=r1) b[k++]=a[i++];
    while(j<=r2) b[k++]=a[j++];
    for(int i=l1;i<=r2;i++){
        a[i]=b[i];
    }
}
void mergeSort(int l,int r){
    if(l>=r) return ;
    int mid=(l+r)/2;
    //[l,mid] [mid+1,r]
    //先使得左半部分有序、右半部分有序
    mergeSort(l,mid);
    mergeSort(mid+1,r);
    //合并两个有序的数列
    merge(l,mid,mid+1,r);
}

观察merge合并过程中的 $i$ 和 $j$ 。首先从位置关系上可判断出 $i<j$,若 $a_i>a_j$ 我们便能推出 $a_i$ 与 $a_j$ 构成逆序对,并且由于合并的两段序列都是升序的,所以 $a_i \sim a_{r_1}$ 都能与 $a_j$ 构成逆序对,因为位置上 $i\le r_1 <j$ ;数值上 $a_j<a_i\le a_{r_1}$ 。那么一下子就能确定 $r1-i+1$ 组逆序对。

逆序对.png

此时我们只需要在归并排序的过程中进行处理,就能统计出所有的逆序对数量了,复杂度为 $O(n\log n)$。

代码实现

#include <bits/stdc++.h>
using namespace std;
const int N=5e5+5;
typedef long long ll;
int a[N],b[N];
ll sum;
void merge(int l1,int r1,int l2,int r2){
   int i=l1,j=l2,k=l1;
   while(i<=r1 && j<=r2){
       if(a[i]>a[j]){
           sum+=(r1-i+1);//a[i] ~ a[r1] 都与 a[j]形成逆序对
           b[k++]=a[j++];
       }else{
           b[k++]=a[i++];
       }
   }
   while(i<=r1) b[k++]=a[i++];
   while(j<=r2) b[k++]=a[j++];
   for(int i=l1;i<=r2;i++){
       a[i]=b[i];
   }
}
void mergeSort(int l,int r){
   if(l>=r) return ;
   int mid=(l+r)/2;
   //[l,mid] [mid+1,r]
   //先使得左半部分有序、右半部分有序
   mergeSort(l,mid);
   mergeSort(mid+1,r);
   //合并两个有序的数列
   merge(l,mid,mid+1,r);
}
int main()
{
   int n;
   cin>>n;
   for(int i=1;i<=n;i++){
       cin>>a[i];
   }
   mergeSort(1,n);
   cout<<sum;
   return 0;
}
最后修改:2024 年 01 月 04 日
如果觉得我的文章对你有用,请随意赞赏