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

题目连接

参考:学无止境的题解


题目大意:

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

2 逆序对:i>j的时候,a[i]<a[j],这样就算一个逆序数对;


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

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

暴力代码(25分):

#include<cstdio>

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;j<i;j++)
		{
			if(a[j]>a[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<bits/stdc++.h>

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]<a[x]) //右边的 y小于左边的 x 
		{
			ans+=mid-x+1;//计数:左边x之后的数字,都大于y
			b[t++]=a[y++]; 
		}
		else b[t++]=a[x++];//左边的比较小,直接合并 
	}
	//剩余一边的合并(另一边已经结束了): 
	while(x<=mid) b[t++]=a[x++];
	while(y<=r)   b[t++]=a[y++];
	
	//复制回原数组
	for(int i=l;i<=r;i++) a[i]=b[i]; 
}

int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	
	px(1,n);//归并排序:过程中进行了计数 
	
	printf("%lld",ans);
	
	return 0;
} 

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

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

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

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

上代码:

#include<bits/stdc++.h> 
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.i<y.i;
	return x.x<y.x;	
}

int lowb(int x) { return x&-x; }

void ins(int x)//第x大的值
{
	while(x<=n)
	{
		tr[x]++;
		x+=lowb(x);
	}
}

int su(int x)//前缀和 
{
	int s=0;
	while(x>0)
	{
		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;
}
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值