luogu1908:逆序对(暴力思维+归并+树状数组)

题目连接

参考:学无止境的题解


题目大意:

1 求一组数字的逆序数对的个数;

2 逆序对:i>j的时候,a[i]


解题思路1:暴力枚举(n平方)

1 对于每个 i,询问 j ,j 属于[1,i-1],只要a[j]>a[i],则有一对逆序数对;

暴力代码(25分):

#include

int n,ans=0;
int a[50005];

int main()
{
	scanf("%d",&n);
	
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	
	for(int i=2;i<=n;i++)
	{
		for(int j=1;ja[i]) ans++;
		}
	}
	
	printf("%d",ans);
	
	return 0;
}

解题思路2:在归并排序的过程完成统计(nlogn)(先了解归并排序的基础)

1 归并的意思:在递归的"归"的时候,进行合并;

2 设当前“”的状态是:

2.1 因为左边和右边本来已经有序,所以只要考虑左右合并的问题

2.2 对于 2 而言,左边的全部 3 个数字都比他大,所以 ans+=3,统计完成之后,进行正常的合并操作

2.3 对于 5 而言,左边的 6和7比他大,所以ans+=2,统计完之后,进行正常的合并操作

2.4 对于 8 而言,左边并没有比他大的数字,所以直接进行正常的合并操作

上代码:

#include

int n,a[500005],b[500005];
long long ans=0;

void px(int l,int r)
{
	if(l==r) return ;
	int mid=(l+r)/2;
	px(l,mid); px(mid+1,r);
	//以上是二分的思维:递
	
	//以下是归来的时候:合并 
	int x=l,y=mid+1,t=l;
	//x,y分别是两边的游标,t是复制到b数组的游标
	 
	//合并的过程: 
	while(x<=mid&&y<=r)
	{
		if(a[y]

解题思路3:树状数组(或者线段树)

1 用桶的思维:当前是第 i 个数字,值是 a[i],可知:在 (a[i],INF) 区间的桶里,有多少个数字,则新增这么多个逆序对;

2 如果用树状数组来进行统计“桶”的前缀和,就搞定了时间复杂度的问题;

3 再机上排序和离散化,就解决了间距的问题;

上代码:

#include 
using namespace std;

int n,rk[500005],tr[500005];
long long ans=0;
struct nod{int x,i;}a[500005];

bool cmp(nod x,nod y)//值是第一关键字,序号是第二关键字 
{
	if(x.x==y.x) return x.i0)
	{
		s+=tr[x];
		x-=lowb(x);
	}
	return s;
}

int main()
{
	scanf("%d",&n);
	memset(tr,0,sizeof(tr));
	for(int i=1;i<=n;i++) scanf("%d",&a[i].x),a[i].i=i;
	
	sort(a+1,a+1+n,cmp);//双关键字排序 
	
	for(int i=1;i<=n;i++) rk[a[i].i]=i;//获取新排名 
	
	for(int i=1;i<=n;i++)
	{
		ins(rk[i]);//进树 
		ans+=i-su(rk[i]);//当前树里有i个点,减去比i小的点,就是逆序对的数量;
	} 
	
	printf("%lld",ans);
	
	return 0;
}

你可能感兴趣的:(归并,树状数组,知识讲解)