求逆序数的板子(归并排序&树状数组)

首先是归并排序的板子

int n,a[100000],b[100000];
void msort(int l,int r)
{
	if(l==r) return;
	int mid=l+r>>1;
	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]) b[k++]=a[i++];
		else b[k++]=a[j++];
	}
	
	while(i<=mid) b[k++]=a[i++];
	while(j<=r)   b[k++]=a[j++];
	
	for(i=l;i<=r;i++) a[i]=b[i]; 
}

求逆序对只需要添加一个计数器,当合并的时候把右边区间的数字放到了左边,说明这个时候左边区间剩余的数字是和这个数字逆序的。count+=mid-1+1;

这里需要注意的一点是,当出现了重复的数字,我们的处理是a[i]<=a[j]的先放,也就是说如果出现了两个一样的数字,我们会先放左面的,再放右边的。也就不会增加多余的逆序对。

归并排序求逆序对的板子:

long long int count;
int n,a[100000],b[100000];
void msort(int l,int r)
{
	if(l==r) return;
	int mid=l+r>>1;
	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]) b[k++]=a[i++];
		else b[k++]=a[j++],count+=mid-1+1;
	}
	
	while(i<=mid) b[k++]=a[i++];
	while(j<=r)   b[k++]=a[j++];
	
	for(i=l;i<=r;i++) a[i]=b[i]; 
}

学长讲到归并排序求逆序对,改变了a本身的顺序,没有保序性,所以不太常用。

而且求逆序数的时候,其实是在归并排序的各个子数组有序的前提下才能在O(1)的条件下实现。

如果遇到某些题求a[i],a[j]的大小关系,而且可以证明子区间是单调的,那么可以直接套用模板。

动态求逆序对,寻找保序可以用树状数组。

P1908 逆序对 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

 洛谷P1908:离散化+树状数组,天才的思路,即通过树状数组动态地查询前缀和,用前缀和表示出小于该数字且位置大于该数字的数量。

具体的流程是这样:

创建一个结构体,存数字的位置pos和值val。先按val的大小降序排序,如果val相同,再按pos的大小降序排序(对相同数字的处理)。

排序之后,先放大的数字(这样每次查询前缀和区间的时候,前缀和的结果就是大于放入数字的个数),即change(a[i].pos,1)。存入的是位置,因为大小通过排序已经确定了,但是位置必须要在该数字之前才能满足逆序的条件。

每放入一个数字就要计算大于它且位置在它之前的个数,sum+=query(a[i].pos-1)。

#include <iostream>
#include <algorithm>
#define int long long
using namespace std;
struct node{
	int val,pos;
}a[510000];
int s[510000];
int n;
int lowbit(int x){
	return x&-x;
}
void change(int x,int k)
{
	while(x<=n) s[x]+=k,x+=lowbit(x);
}
bool cmp(node x,node y){
	if(x.val==y.val){
		return x.pos>y.pos;
	}else return x.val>y.val;
}
int query(int x){//查询区间和 
	int t=0;
	while(x) t+=s[x],x-=lowbit(x);
	return t;
}	 
signed main(){
	while(cin>>n&&n){
		for(int i=1;i<=n;i++){
			cin>>a[i].val;a[i].pos=i;
		}
		sort(a+1,a+n+1,cmp);
		int sum=0;
		for(int i=1;i<=n;i++){
			change(a[i].pos,1);
			sum+=query(a[i].pos-1);
		}
		cout<<sum<<endl;
	}
	
	return 0;
}

 

  • 10
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值