emmm……最近在讲分治讲到了逆序对,写篇博客纪念一下
【题目】
题目描述:
猫猫TOM和小老鼠JERRY最近又较量上了,但是毕竟都是成年人,他们已经不喜欢再玩那种你追我赶的游戏,现在他们喜欢玩统计。最近,TOM老猫查阅到一个人类称之为“逆序对”的东西,这东西是这样定义的:对于给定的一段正整数序列,逆序对就是序列中 ai > aj 且 i < j 的有序对。知道这概念后,他们就比赛谁先算出给定的一段正整数序列中逆序对的数目。
输入格式:
第一行,一个数n,表示序列中有n个数。
第二行n个数,表示给定的序列。
输出格式:
给定序列中逆序对的数目。
样例数据:
输入
6 5 4 2 6 3 1
输出
11
备注:
对于 50% 的数据,n ≤ 2500
对于 100% 的数据,n ≤ 40000。
【分析】
首先是暴力,先枚举 i,再枚举 j(j > i),如果 ai > aj,答案就加一,很显然,这样做是 复杂度,肯定会超时
这里介绍一下分治+归并排序求逆序对的方法
若我们要求数列 A 中的逆序对个数,不妨把它分成两半,数列 A 中的逆序对(i,j)必然是下面三种情况之一:
(1)i,j 都属于数列 B
(2)i,j 都属于数列 C
(3)i 属于数列 B,j 属于数列 C
对于(1),(2)都很好做,直接递归下去就行了,麻烦一点的是(3)
解决(3)大体的思路就是枚举 C 中的每一个数,然后在 B 中找大于它的数,然后累加进答案就行了
不过直接枚举的话,时间可能会炸掉,我们要想办法优化
由于分治的时候是用的递归,在递归回来的时候,我们都做一次归并排序,保证 B,C 都是有序的
还是按照上面暴力的那种思路求逆序对,不过由于此时 B,C 是有序的,复杂度就从原来 降到了
这样的话,分治的复杂度是O(log n),归并排序的复杂度是O(n),总复杂度就是O(n * log n)
【代码】
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 100005
using namespace std;
int a[N],b[N],c[N];
long long merge(int l,int r,int mid)
{
long long ans=0;
int i,b1=0,c1=0,b2=1,c2=1;
for(i=l;i<=mid;++i) b[++b1]=a[i];
for(i=mid+1;i<=r;++i) c[++c1]=a[i];
for(i=l;i<=r;++i)
{
if(b1>=b2&&(c1+1==c2||b[b2]<=c[c2]))
a[i]=b[b2++];
else
{
a[i]=c[c2++];
ans+=b1-b2+1;
}
}
return ans;
}
long long solve(int l,int r)
{
if(l==r) return 0;
int mid=(l+r)>>1;
long long ans=0;
ans+=solve(l,mid);
ans+=solve(mid+1,r);
ans+=merge(l,r,mid);
return ans;
}
int main()
{
int n,i;
long long ans;
scanf("%d",&n);
for(i=1;i<=n;++i)
scanf("%d",&a[i]);
ans=solve(1,n);
printf("%lld",ans);
return 0;
}