线性分治——求逆序对数


前言

求逆序对数之前我们在讲树状数组的时候讲过,那个时候我们采用的方法是先对数据进行离散化处理,缩减数据范围,再将数据依次放入数组中,在这个过程中通过计算区间和统计对应逆序对个数;
此时我们将采用另一个思路——线性分治。
分治的思想我们学习排序的时候接触过一些,比如归并排序,将一个难以直接解决的大问题,分割成一些规模较小的相同问题,以便各个击破,分而治之。


一、分治思路

计数类题目最重要的原则是不重不漏

在一段线性区间中取一个平衡点,一个逆序对要么位于点的同一侧,要么分别位于两侧;
如果我们对一侧按照相同的策略进行处理,也就是进行分治,那么最后可以得到同一种情况:
逆序对位于对应平衡点的两侧

这个分治的思路就需要跟我们之前的归并排序联系起来了。
归并排序同样是使用分治思想,在平衡点左右两侧的区段均已被排好序,
那么此时我们有两个条件了:
1.逆序对位于左右两侧;
2.左右两侧已经排好序了;

此时想要求逆序对就比较简单了
遇到逆序对的情况,就可以得出一类逆序对的数量,直接加上就可以了

while(i<=mid&&j<=r){
		if(a[i]<=a[j]){
			tmp[k++]=a[i++];
		}
		else {
			ans+=mid-i+1;
			tmp[k++]=a[j++];
		}
	}

二、代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define fir(i, a, b) for (int i = (a); i <= (b); i++)
#define rif(i, a, b) for (int i = (a); i >= (b); i--)
char buf[1<<20],*P1=buf,*P2=buf;
#define gc() (P1==P2&&(P2=(P1=buf)+fread(buf,1,1<<20,stdin),P1==P2)?EOF:*P1++)
#define TT template<class T>inline
TT bool read(T &x){
    x=0;char c=gc();bool f=0;
    while(c<48||c>57){if(c==EOF)return 0;f^=(c=='-'),c=gc();}
    while(47<c&&c<58)x=(x<<3)+(x<<1)+(c^48),c=gc();
    if(f)x=-x;return 1;
}
const ll N=5e5+5;
ll n,a[N],b[N],cnt;
void merge(ll l,ll r){
    ll mid=l+r>>1;
    if(l>=r)return;
    merge(l,mid);
    merge(mid+1,r);
	 ll i=l,j=mid+1;
	 fir(k,l,r){
	 	if(j>r||i<=mid&&a[i]<=a[j])b[k]=a[i++];
	 	else b[k]=a[j++],cnt+=mid-i+1;
	 }
	 fir(k,l,r)a[k]=b[k];
} 
int main(){
	while(read(n)&&n){
		cnt=0;
		fir(i,1,n)read(a[i]);
		merge(1,n);
		cout<<cnt<<endl;
	}
	return 0;
}


#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=500005;
int a[N];
int tmp[N];
int n;
ll mort(int l,int r){
	int mid = (l+r)/2;
	if(l>=r)return 0;
	ll ans=mort(l,mid)+mort(mid+1,r);
	int i=l,k=l,j=mid+1;
	while(i<=mid&&j<=r){
		if(a[i]<=a[j]){
			tmp[k++]=a[i++];
		}
		else {
			ans+=mid-i+1;
			tmp[k++]=a[j++];
		}
	}
	while(i<=mid)tmp[k++]=a[i++];
	while(j<=r)tmp[k++]=a[j++];
	for(int i=l;i<=r;i++)a[i]=tmp[i];
	return ans;
}
int main(){
	cin>>n;
	for(int i=0;i<n;i++){
		cin>>a[i];
	}
	cout<<mort(0,n-1)<<endl;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值