【洛谷 1908】逆序对

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,答案就加一,很显然,这样做是n^{2} 复杂度,肯定会超时

这里介绍一下分治+归并排序求逆序对的方法

若我们要求数列 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 是有序的,复杂度就从原来 n^{2} 降到了 n

这样的话,分治的复杂度是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;
}

猜你喜欢

转载自blog.csdn.net/forever_dreams/article/details/81276806