这些天来测试的时候遇到了几次逆序对的题目,但没有太在意,已经跪了好几次了,所以今天打算学习总结一下逆序对的两种做法。
首先是归并排序
归并排序的模板
#include<iostream>
using namespace std;
int a[100000],temp[100000];
void merge_i(int l,int mid,int r)
{
int i=l,j=mid+1,k=l;//i是从l到mid枚举,j从mid+1到r枚举加入中介数组
while(i<=mid&&j<=r)
{
if (a[i]>a[j])
temp[k++]=a[j++];
else
temp[k++]=a[i++];//按从小到大的顺序加入中介数组
}
while(i<=mid) temp[k++]=a[i++];//把左边区间剩余的加入中介数组
while(j<=r) temp[k++]=a[j++];//把右边区间剩余的加入中介数组
for(i=l;i<=r;i++)
a[i]=temp[i];//返回排列顺序
}
void merge(int l,int r)
{
if (l<r)//这个很重要 不然会运行错误
{
int mid=(l+r)>>1;//找中间位置
merge(l,mid);//左区间排列
merge(mid+1,r);//右区间排列
merge_i(l,mid,r);//总的排列
}
}
int main()
{
int n;
cin>>n;
int i;
for(i=1;i<=n;i++)
cin>>a[i];
merge(1,n);
for(i=1;i<=n;i++) cout<<a[i]<<" ";
return 0;
}
接下来是关于归并排序对于逆序对的作用
转载于:http://blog.csdn.net/acdreamers/article/details/16849761(今天看的感觉还好理解,感谢ACdreamers大大)
我们可以这样考虑:
归并排序是将数列a[l,h]分成两半a[l,mid]和a[mid+1,h]分别进行归并排序,然后再将这两半合并起来。
在合并的过程中(设l<=i<=mid,mid+1<=j<=h),当a[i]<=a[j]时,并不产生逆序数;当a[i]>a[j]时,在
前半部分中比a[i]大的数都比a[j]大,将a[j]放在a[i]前面的话,逆序数要加上mid+1-i。因此,可以在归并
排序中的合并过程中计算逆序数.
接下来上代码
#include<iostream>
using namespace std;
int a[100000],temp[100000];long long ans=0;
void merge_i(int l,int mid,int r)
{
int i=l,j=mid+1,k=l;
while(i<=mid&&j<=r)
{
if (a[i]>a[j])
{
temp[k++]=a[j++];
ans+=mid-i+1;
}
else
temp[k++]=a[i++];
}
while(i<=mid) temp[k++]=a[i++];
while(j<=r) temp[k++]=a[j++];
for(i=l;i<=r;i++)
a[i]=temp[i];
}
void merge(int l,int r)
{
if (l<r)
{
int mid=(l+r)>>1;
merge(l,mid);
merge(mid+1,r);
merge_i(l,mid,r);
}
}
int main()
{
int n;
cin>>n;
int i;
for(i=1;i<=n;i++)
cin>>a[i];
merge(1,n);
for(i=1;i<=n;i++) cout<<a[i]<<" ";cout<<endl;
cout<<ans;
return 0;
}
接下来我们总结树状数组求逆序对
转载于http://blog.csdn.net/alongela/article/details/8142965(感谢Onlyan大大)
给定n个数,要求这些数构成的逆序对的个数。除了用归并排序来求逆序对个数,还可以使用树状数组来求解。
树状数组求解的思路:开一个能大小为这些数的最大值的树状数组。从头到尾读入这些数,每读入一个数就更新树状数组,查看它前面比它小的已出现过的有多少个数sum,然后用当前位置减去该sum,就可以得到当前数导致的逆序对数了。把所有的加起来就是总的逆序对数。
题目中的数都是独一无二的,这些数最大值不超过999999999,但n最大只是500000。如果采用上面的思想,必然会导致空间的巨大浪费,而且由于内存的限制,我们也不可能开辟这么大的数组。因此可以采用一种称为“离散化”的方式,把原始的数映射为1-n一共n个数,这样就只需要500000个int类型的空间。
离散化的方式:
struct Node
{
int val;
int pos;
};
Node node[500005];
int reflect[500005];
val存放原数组的元素,pos存放原始位置,即node[i].pos = i。
把这些结构体按照val的大小排序。
reflect数组存放离散化后的值,即reflect[node[i].pos] = i。
这样从头到尾读入reflect数组中的元素,即可以保持原来的大小关系,又可以节省大部分空间。
上代码
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
struct tree
{
int v,x;
}node[100000];
int n,a[100000],c[100000],reflect[100000];
bool cmp(tree a,tree b)
{
return a.v<b.v;
}
int lowbit(int i)
{
return i&(-i);//假如i是奇数,返回1,否则返回i的因数当中2的最大次方 例如 lowbit(8)返回8,lowbit(6)返回2 lowbit(20)返回4
}
void update(int i)//更新树状数组
{
while(i<=n)
{
c[i]++;
i+=lowbit(i);
}
}
int sum(int i)//查看它前面比它小的已出现过的有多少个数sum
{
int t=0;
while(i>0)
{
t+=c[i];
i-=lowbit(i);
}
return t;
}
int main()
{
int i;long long ans=0;
cin>>n;
for(i=1;i<=n;i++)
{
cin>>node[i].v;
node[i].x=i;//记录输入位置
}
sort(node+1,node+1+n,cmp);//排序
for(i=1;i<=n;i++) reflect[node[i].x]=i;//离散化,优化空间,并记录当前位置
for(i=1;i<=n;i++)
{
update(reflect[i]);
ans+=i-sum(reflect[i]);//求逆序对的个数
}
cout<<ans;
return 0;
}