逆序对的三种求法(归并排序,树状数组,线段树)
Posted galaxy-yr
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了逆序对的三种求法(归并排序,树状数组,线段树)相关的知识,希望对你有一定的参考价值。
求逆序对个数的三种方法
逆序对: 对于一个序列 $a_1$,$a_2$,$a_3$..$a_n$,如果存在$a_i$>$a_j$且i<j,则$a_i$和$a_j$为一个逆序对。
这里将介绍3种求逆序对对数的方法。
在此之前,默认为你已经会了归并排序,树状数组和线段树。(不会的可以百度学习一下)
学会后可以试着做一下这道题求逆序对
1.归并排序求逆序对
这应该是几乎人人都会的方法,原理是利用归并排序时,对于区间l~r
,在mid
左边和mid
右边都已经是单调的序列来求逆序对,这种方法就不多讲了。时间复杂度约为O(nlogn)
代码如下:个人习惯打快读
#include<iostream>
using namespace std;
const int maxn=1e6+10;
template<typename T>
void read(T&res)
{
char ch=getchar();
T q=1;
while(ch<‘0‘ or ch>‘9‘)
{
if(ch==‘-‘)q=-1;
ch=getchar();
}
res=(ch^48);
while((ch=getchar())>=‘0‘ and ch<=‘9‘)
res=(res<<1)+(res<<3)+(ch^48);
res*=q;
return;
}
int n,a[maxn];
long long ans;
inline void init()
{
read(n);
for(int i=1;i<=n;i++)
read(a[i]);
}
//归并排序
int temp[maxn];
void merge_sort(int l,int r)
{
if(l==r)return;
int mid=l+r>>1;
merge_sort(l,mid);
merge_sort(mid+1,r);
int ll=l,st=l,rr=mid+1;
while(l<=mid and rr<=r)
{
if(a[l]<=a[rr])
temp[ll++]=a[l++];
else
temp[ll++]=a[rr++],ans+=mid-l+1;
}
while(l<=mid)temp[ll++]=a[l++];
while(rr<=r)temp[ll++]=a[rr++];
for(int i=st;i<=r;i++)
a[i]=temp[i];
return;
}
int main()
{
init();
merge_sort(1,n);
cout<<ans<<endl;
return 0;
}
2.树状数组求逆序对
树状数组求逆序对也是一种很好的方法,它在求整个序列的逆序对的同时也可以求出每一个$a_i$的逆序对个数,
并且代码也比较短。 时间复杂度约为 O(nlogn)
具体方法:
我们知道树状数组可以快速的进行区间修改和区间查询,那么我们就要充分利用它的优点。
我的做法是:
用数组tree表示$a_i$的逆序对对数,倒过来扫一遍,边扫边把答案求出,并把比$a_i$大的每一个数字的逆序对对数都添加1。
树状数组我就不压行了
具体做法如下:
#include<iostream>
using namespace std;
const int maxn=1e6+10;
template<typename T>
void read(T&res)
{
char ch=getchar();
T q=1;
while(ch<‘0‘ or ch>‘9‘)
{
if(ch==‘-‘)q=-1;
ch=getchar();
}
res=(ch^48);
while((ch=getchar())>=‘0‘ and ch<=‘9‘)
res=(res<<1)+(res<<3)+(ch^48);
res*=q;
return;
}
int n,a[maxn];
long long ans;
inline void init()
{
read(n);
for(int i=1;i<=n;i++)
read(a[i]);
}
//树状数组
int tree[maxn];
inline int lowbit(int x){return x&-x;}
inline void add(int x,int v)
{
while(x<=n)
{
tree[x]+=v;
x+=lowbit(x);
}
return;
}
inline int sum(int x)
{
int res=0;
while(x>=1)
{
res+=tree[x];
x-=lowbit(x);
}
return res;
}
void solve()
{
for(int i=n;i>=1;i--)
ans+=sum(a[i]), add(a[i],1);//可以同时记录下每个数字的逆序对
cout<<ans<<endl;
}
int main()
{
init();
solve();
return 0;
}
3.权值线段树
我们可以把线段树中的l,r
表示为l~r
中的每一个数字出现了的总数(l=r时,就是单个数字的个数),在建树时,不添加任何值,做一颗空树,扫一遍的时候添加数字进去。也可以计算出每个数字的逆序对个数
时间复杂度为 O(2nlogn)
具体做法如下:
#include<iostream>
using namespace std;
const int maxn=1e6+10;
template<typename T>
void read(T&res)
{
char ch=getchar();
T q=1;
while(ch<‘0‘ or ch>‘9‘)
{
if(ch==‘-‘)q=-1;
ch=getchar();
}
res=(ch^48);
while((ch=getchar())>=‘0‘ and ch<=‘9‘)
res=(res<<1)+(res<<3)+(ch^48);
res*=q;
return;
}
int n,a[maxn];
long long ans;
inline void init()
{
read(n);
for(int i=1;i<=n;i++)
read(a[i]);
}
//权值线段树
struct{
int l,r,tot;
}e[maxn*3];
void build(int k,int l,int r)
{
e[k].l=l,e[k].r=r;
if(l==r)return;
int mid=(l+r)>>1;
build(k<<1,l,mid);
build(k<<1|1,mid+1,r);
}
void update(int k,int num)
{
if(e[k].l==num and e[k].r==num)
{
e[k].tot++;
return;
}
int mid=(e[k].l+e[k].r)>>1;
if(num<=mid)update(k<<1,num);
if(num>mid)update(k<<1|1,num);
e[k].tot=e[k<<1].tot+e[k<<1|1].tot;
return;
}
long long sum(int k,int l,int r)
{
if(e[k].l>r or e[k].r<l)return 0;
if(e[k].l==l and e[k].r==r)return e[k].tot;
int mid=e[k].l+e[k].r>>1;
if(mid>=r)return sum(k<<1,l,r);
if(mid<l)return sum(k<<1|1,l,r);
return sum(k<<1,l,mid)+sum(k<<1|1,mid+1,r);
}
void solve()
{
build(1,1,maxn);
for(int i=1;i<=n;i++)
{
ans+=sum(1,a[i]+1,maxn);
update(1,a[i]);
}
cout<<ans<<endl;
}
int main()
{
init();
solve();
return 0;
}
对于我个人而言还是比较喜欢树状数组的,因为比较短。当然这也只是个人意见。
以上是关于逆序对的三种求法(归并排序,树状数组,线段树)的主要内容,如果未能解决你的问题,请参考以下文章