【题目链接】
ybt 1311:【例2.5】求逆序对
ybt 1237:求排列的逆序数
OpenJudge NOI 2.4 7622:求排列的逆序数
洛谷 P1908 逆序对
ybt 1311,1237 OpenJudge 2.4 7622几题输入数据的最大个数为
10
5
10^5
105
洛谷 P1908 输入数据的最大个数为
5
∗
10
5
5*10^5
5∗105
【题目考点】
1. 归并排序 求逆序对
【解题思路】
如果直接写两层循环寻找逆序对,时间复杂度是
O
(
n
2
)
O(n^2)
O(n2),该题问题规模为
10
5
10^5
105,会超时。
可以用归并排序解决逆序对问题
例:某一次归并排序,两个数组分别为a1,a2,临时数组为t,逆序对数量s:
a1:1 6 8 a2:2 3 7 t: s:0
第一步:a1的1进入到临时数组t中,1和a2中的数字不产生逆序对
a1:6 8 a2:2 3 7 t:1 s:0
第二步:a2的2比a1的6小,2进入t中。2和a1中的所有元素都形成逆序对,逆序对数量增加2
a1:6 8 a2:3 7 t:1 2 s:2
第三步:a2的3比a1的6小,3进入t中。3和a1中的所有元素都形成逆序对,逆序对数量增加2
a1:6 8 a2:7 t:1 2 3 s:4
第四步:a1的6比a2的7小,6进入t中。不产生逆序对
a1:8 a2:7 t:1 2 3 6 s:4
第五步:a2的7比a1的8小,7进入t中。产生逆序对1个
a1:8 a2: t:1 2 3 6 7 s:5
最后把8 填到t中
a1: a2: t:1 2 3 6 7 8 s:5
此次归并一共找到5个逆序对
总结规律:在合并有序序列时,当a2数组的最小值比a1数组最小值小的时候,逆序对增加的数量为:此时a1数组剩余元素的个数。其他时刻,逆序对不增加。
一般情况下:
a
a
a序列整个区间
[
l
,
r
]
[l, r]
[l,r]先被分为两个子区间
[
l
,
m
i
d
]
[l, mid]
[l,mid]与
[
m
i
d
+
1
,
r
]
[mid+1,r]
[mid+1,r],而后对这两个子区间分别进行归并排序。在合并有序序列时,两个区间中的元素都构成了升序序列。
使用双指针方法,区间
[
l
,
m
i
d
]
[l, mid]
[l,mid]中访问到第
i
i
i元素
a
i
a_i
ai,区间
[
m
i
d
+
1
,
r
]
[mid+1,r]
[mid+1,r]中访问到第
j
j
j元素
a
j
a_j
aj,如果此时
a
i
>
a
j
a_i>a_j
ai>aj,那么由于区间
[
l
,
m
i
d
]
[l, mid]
[l,mid]是升序的,因此区间
[
i
,
m
i
d
]
[i, mid]
[i,mid]中的元素都满足大于
a
j
a_j
aj,即区间
[
i
,
m
i
d
]
[i, mid]
[i,mid]中的元素都能与
a
j
a_j
aj构成逆序对,共产生
m
i
d
−
i
+
1
mid-i+1
mid−i+1个逆序对。区间
[
l
,
i
−
1
]
[l, i-1]
[l,i−1]中的元素都小于等于
a
j
a_j
aj,无法与
a
j
a_j
aj构成逆序对。
关注较小数为
x
x
x的所有逆序对,在归并排序中使用该方法可以统计到所有较小数为
x
x
x的逆序对。
使用数学归纳法
当区间 [ l , r ] [l, r] [l,r]长度为 2 2 2时, x x x是其中一个数,使用上述方法显然可以统计出较小数为 x x x逆序对的数量。
假设当区间 [ l , r ] [l, r] [l,r]长度小于 k k k时,使用上述方法可以统计出较小数为 x x x逆序对的数量。当区间长度等于 k k k时,区间被分为 [ l , m i d ] [l, mid] [l,mid], [ m i d + 1 , r ] [mid+1,r] [mid+1,r]。
如果关注的数 x x x在区间 [ l , m i d ] [l, mid] [l,mid]中,那么较小数为 x x x的逆序对都存在于区间 [ l , m i d ] [l, mid] [l,mid]中,该区间长度小于k,该区间中较小数为 x x x的逆序对数量已经统计过了。
如果关注的数 x x x在区间 [ m i d + 1 , r ] [mid+1, r] [mid+1,r]:
如果较大数也在区间 [ m i d + 1 , r ] [mid+1, r] [mid+1,r]中,那么这样的逆序对是区间 [ m i d + 1 , r ] [mid+1, r] [mid+1,r]中的逆序对,该区间长度小于k,该区间中较小数为 x x x的逆序对都已经统计过了。
如果较大数在区间 [ l , m i d ] [l, mid] [l,mid]中,那么这样的逆序对会通过上述方法统计出来。
因此在归并排序中使用该方法可以统计到所有较小数为 x x x的逆序对。
x x x可以是序列中的任意值,序列中以任意值为较小数的逆序对数量通过上述方法都可以通过上述方法统计出来。因此该方法可以求整个序列的逆序对数量。
根据这一原理编写程序,归并排序的时间复杂度是 O ( n l o g n ) O(nlogn) O(nlogn),可以解决对长为 10 5 10^5 105的序列求逆序对的问题。
n个元素,当整个数组是严格降序序列时,有最多的逆序对:
第1元素和后面n-1个元素构成n-1个逆序对;
第2元素和后面n-2个元素构成n-2个逆序对;
。。。
第n-1元素和第n元素构成1个逆序对。
共有逆序对:
n
−
1
+
n
−
2
+
.
.
.
+
1
=
n
(
n
−
1
)
2
n-1+n-2+...+1 = \frac{n(n-1)}{2}
n−1+n−2+...+1=2n(n−1)
当n为
10
5
10^5
105时,逆序对最多有约为
10
10
10^{10}
1010个,超出了int可以表示的范围,所以保存逆序对的变量应该设为long long类型。
【题解代码】
解法1:
ybt 1311,1237 OpenJudge 2.4 7622几题 数组长度N可以设为100005
洛谷 P1908 数组长度N必须设为500005
#include<bits/stdc++.h>
using namespace std;
#define N 500005//数组长度
int n, a[N], t[N];
long long cnt;//逆序数
void mergeSort(int l, int r)
{
if(l >= r)
return;
int mid = (l+r)/2;
mergeSort(l, mid);
mergeSort(mid+1, r);
int i = l, j = mid+1, k = l;//i第一个段数组中的下标 j第二段数组中的下标 k临时数组中的下标
while(i <= mid && j <= r)
{
if(a[i] <= a[j])//必须使用<=不能使用<,数值相等时填充较小下标的数,保证排序的稳定性
t[k++] = a[i++];
else//如果a[i] < a[j] 此时左侧数组剩余的mid-i+1个数与a[j]构成逆序对
{
t[k++] = a[j++];
cnt += mid-i+1;
}
}
while(i <= mid)
t[k++] = a[i++];
while(j <= r)
t[k++] = a[j++];
for(int i = l; i <= r; ++i)
a[i] = t[i];
}
int main()
{
cin >> n;
for(int i = 1; i <= n; ++i)
cin >> a[i];
mergeSort(1, n);
cout << cnt;
return 0;
}