P1908 逆序对(归并排序)
题目描述:
解题思路:
对于这个问题,我们显然能通过冒泡或选择排序来累计交换次数达到所求目的。但很显然, Θ ( n 2 ) \Theta(n^2) Θ(n2) 的排序无法承担 5 ∗ 1 0 5 5*10^5 5∗105 的数据,因此我们需要用到 Θ ( n l o g n ) \Theta(n~log_n) Θ(n logn) 的归并排序。
归并排序:
首先我们考虑要排序一段
(
l
,
r
)
(l,r)
(l,r) 的区间,归并排序需要考虑分裂和合并两个步骤,我们先看看分裂。
如图,对于一个 ( 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),这样原问题就分解成了等价的两个子问题,然后子问题再进一步分裂,分成更小的问题,知道分成每个区间只有一个元素 a i a_i ai,为止。此时,当区间长度为 1 1 1,我们发现区间本身已经是有序的了,因此我们要把这种子区间的有序带到父区间中,也就是合并。
将左右各自有序的区间合并成跟大的区间,这样的话父区间也将有序,然后继续合并,等最后的根区间合并完了,那么 ( l , r ) (l,r) (l,r) 区间就变得有序了。
合并步骤 Θ ( n ) \Theta(n) Θ(n),分裂是 Θ ( l o g n ) \Theta(log_n) Θ(logn),归并排序的复杂度就是 Θ ( n l o g n ) \Theta(n~log_n) Θ(n logn)。
这里有必要讲讲
Θ
(
n
)
\Theta(n)
Θ(n) 合并的过程,如图:
我们需要一个一开始 指向区间
A
A
A 头部的指针
i
i
i ,还需要一个指向区间
B
B
B 头部的指针
j
j
j,开始考虑比较
a
i
a_i
ai 以及
b
j
b_j
bj,若
a
i
>
b
j
a_i>b_j
ai>bj 则将
b
j
b_j
bj 插入
i
i
i 前,也就是
i
−
1
i-1
i−1 的位置,并将
j
j
j 指针往后移动一位;若
a
i
<
b
j
a_i<b_j
ai<bj 则将
i
i
i 指针往后移动一位, 以此往复,区间将成功有序合并。
在程序中我们需要额外的一个数组作为空间存放这个合并后的区间,最后还得把这个数组重新放入区间 A A A 或 B B B 。
以下是完整的归并排序代码:
int t[100010]={0}; //额外空间
void msort(int l,int r)
{
if(l-r==0) return ;
int mid;
mid=l+(r-l)/2;
msort(l,mid); //左区间排序
msort(mid+1,r); //右区间排序
int i=l,j=mid+1,k=l;
while(i<=mid&&j<=r)
{
if(a[i]>a[j])
{
t[k]=a[j];
j++;
k++;
}
else
{
t[k]=a[i];
i++;
k++;
}
}
while(i<=mid) //若某一段区间插入完毕了,剩下的直接放进去就行了
{
t[k]=a[i];
i++;
k++;
}
while(j<=r)
{
t[k]=a[j];
j++;
k++;
}
for(i=l;i<=r;i++)
a[i]=t[i];
return ;
}
那么对于题目中的逆序对个数问题,我们可以找到一些合并过程中 B B B 区间的数小于 A A A 区间的数的数对 ,简单推推就能发现,一个包含 a i a_i ai 的这样的数对就能带来 m i d − i + 1 mid-i+1 mid−i+1 个逆序对。原因也很简单,由于 A A A 区间中的数是有小到大有序排列的,可知,若 a i > b j a_i>b_j ai>bj, i i i以后的所有 a a a 也都必定大于 b j b_j bj ,即都是逆序对,个数为 m i d − i + 1 mid-i+1 mid−i+1。
CODE:
#include <bits/stdc++.h>
using namespace std;
int n,a[500010]={0},t[500010]={0};
long long ans=0;
void msort(int l,int r)
{
if(l-r==0) return ;
int mid;
mid=l+(r-l)/2;
msort(l,mid);
msort(mid+1,r);
int i=l,j=mid+1,k=l;
while(i<=mid&&j<=r)
{
if(a[i]>a[j])
{
t[k]=a[j];
j++;
k++;
ans+=(mid-i+1); //统计逆序对
}
else
{
t[k]=a[i];
i++;
k++;
}
}
while(i<=mid)
{
t[k]=a[i];
i++;
k++;
}
while(j<=r)
{
t[k]=a[j];
j++;
k++;
}
for(i=l;i<=r;i++)
a[i]=t[i];
return ;
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i];
msort(1,n);
cout<<ans;
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!