求逆序对(树状数组和归并)

树状数组求逆序对,归并排序求逆序对。

一 树状数组

http://www.cnblogs.com/wdvxdr/p/7300717.html

我看了这篇博客,才逐渐明白的,树状数组求逆序对的一种思路。

①桶排序,把数组a[]的元素放在一个很大的数组b里面,(确保a的元素最大值不超过b的大小)

挨个把a中的元素放到b中去,,就是令b[a[i]]=1,每次求已经放入的元素个数减去a[i]前面的元素个数(包括a[i])

用树状数组的单点修改,区间求和可以很好地完成。(手动写代码ing)

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
using namespace std;
int a[500005],b[500005],n;
int lowbit(int x){
	return x&(-x);
}
void add(int x){
	while(x<500005){
		b[x]++;
		x+=lowbit(x);
	}
}
int  getsum(int x){
	long long sum=0;
	while(x>0){
		sum+=b[x];
		x-=lowbit(x);
	}
	return sum;
}
int main()
{
	long long n,i,ans;
	while(cin>>n){//n为数的总数.
		ans=0;
		memset(b,0,sizeof(b));
		for(i=1;i<=n;i++){
			cin>>a[i];
			add(a[i]);
			ans+=i-(getsum(a[i]));//当前加入b的元素个数减去a[i]及其前面的元素个数
		}
		cout<<ans<<endl;
	}
}

比如5个数,9 ,1,2,7,5。先把b[9]置为1,此时9前面没有数,再把b[1]置为1,此时1前面没有数(比一小的数),并且已经插入了两个数,那么就说明a[]这个序列在1的前面有一个比一大的数,即有一对逆序数。

再把b[2]置为1,二的前面有一个比自己小的数1,此时已经插入了三个数,那么就说明a[]这个序列中,有一个比2大的数,即有一对逆序数。以此类推,就全部求出来了。

②前面的方法很简单,但是限制实在太多,比如只能求正整数的逆序对(b的下标不能为负数),如果a中的元素最大值很大,就会超时。

那么就需要进行离散化。这玩意实在有点难理解。

首先,需要把a设置为结构体数组,结构体保存的值为元素大小,及下标。

然后将a按照元素大小用sort函数排序,记得写自定义的排序方式,还有就是因为a的下标从1开始,所以sort(a+1,a+n+1,cmp)。

不是sort(a,a+n,cmp)。

这里有两种方法去做,虽然代码差不多,但是我个人认为有很大的不同。

第一种

https://blog.csdn.net/ssimple_y/article/details/53744096我看这博客做的。

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
using namespace std;
int b[500005],n,t[500005];
struct node{
	int pos,v;
}a[500005];
bool cmp(node x,node y){
	return x.v<y.v;
}
int lowbit(int x){
	return x&(-x);
}
void add(int x){
	while(x<500005){
		b[x]++;
		x+=lowbit(x);
	}
}
int  getsum(int x){
	long long sum=0;
	while(x>0){
		sum+=b[x];
		x-=lowbit(x);
	}
	return sum;
}
int main()
{
	long long n,i,ans,cnt;
	while(cin>>n){//n为数的总数.
		ans=0;
		memset(b,0,sizeof(b));
		for(i=1;i<=n;i++){
			cin>>a[i].v;
			a[i].pos=i;
		}
		sort(a+1,a+n+1,cmp);
		cnt=1;
		for(i=1;i<=n;i++){
			if(i!=1&&a[i].v!=a[i-1].v)
				cnt++;
			t[a[i].pos]=cnt;
		}
		for(i=1;i<=n;i++){
			add(t[i]);
			ans+=(i-getsum(t[i]));
		}
		cout<<ans<<endl;
	}
}

这种方法的思想和前面的是一样的,按照顺序依次插入,并且求前面有多少个数,再用已插入的数的个数减去。

cnt的作用就是避免有大小相同的元素。pos代表的是a中元素插入的顺序,比如还是那五个数9,1,2,7,5。

9的pos是1,1的pos是2,5的pos是5。按照a中元素大小排序之后,9在最后一位,1在第一位,5在第三位。

此时cnt代表的是元素大小关系,9对应的cnt为5,1对应的cnt为1,5对应的cnt为3。那么此时的pos所对应的数的相对大小依然没有变(pos是原来的序列的顺序)比如pos为1的数是9,是这个序列中最大的数。排序后,a的序列v值为1 2 5 7 9;(最重要的是pos没有变,1这个数对应的pos依然是2,因为它是原来的序列的第二个数,9对应的pos依然是1,也就是说,pos为2的数是1,

pos为1的数是9)t[a[i].pos]=cnt;在这个操作后,pos所对应的数的相对大小没有变化。pos为1的cnt为5(9是最大的),pos为2cnt为1(1是最小的)pos为5的cnt为3(5是第三大的)

求逆序对(树状数组和归并)

最后,在调用add()函数的时候,i为1实际上是a[i].pos为1,也就是说,这样是按照原来的顺序插入的。这是模拟前面所讲的方法。

第二种

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
using namespace std;
int b[500005],n,t[500005];
struct node{
	int pos,v;
}a[500005];
bool cmp(node x,node y){
	if(x.v==y.v)return x.pos<y.pos;
	return x.v<y.v;//当值相等的时候,按照输入先后排序
}
int lowbit(int x){
	return x&(-x);
}
void add(int x){
	while(x<500005){
		b[x]++;
		x+=lowbit(x);
	}
}
int  getsum(int x){
	long long sum=0;
	while(x>0){
		sum+=b[x];
		x-=lowbit(x);
	}
	return sum;
}
int main()
{
	long long n,i,ans,cnt;
	while(cin>>n){//n为数的总数.
		ans=0;
		memset(b,0,sizeof(b));
		for(i=1;i<=n;i++){
			cin>>a[i].v;
			a[i].pos=i;
		}
		sort(a+1,a+n+1,cmp);
		/*cnt=1;
		for(i=1;i<=n;i++){
			if(i!=1&&a[i].v!=a[i-1].v)
				cnt++;
			t[a[i].pos]=cnt;
		} 将这一段删去*/
		for(i=1;i<=n;i++){
			add(a[i].pos);//直接add排序后的pos。
			ans+=(i-getsum(a[i].pos));
		}
		cout<<ans<<endl;
	}
}

这样做的意思是先排序,然后按照从小到大的顺序插入pos。这样的话,每次插入的数一定比之前插入的大,把b[a[i].pos]置为1,a[i].pos之前的数就是原序列中比a[i].v小的数,用已经插入的数的个数减去,就是逆序对的个数。

二 归并排序

归并排序求逆序对需要注意的就是因为两部分已经拍好戏了,每次加的应该是mid-i-1;手动模拟一遍就懂了。

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<set>
#include<cstdio>
using namespace std;
long long a[100005],cnt;
void merge(int L,int mid,int R){
	int temp[100005],i=L,j=mid+1,k=0;
	while(i<=mid&&j<=R){
		if(a[i]>a[j]){
			temp[k]=a[j];
			k++;j++;
			cnt+=(mid-i+1);//对于左边,因为i后面的数都比i这个位置的数大
		}//直到mid肯定都比a[j]大
		else{
			temp[k]=a[i];
			k++;i++;
		}
	}
	while(i<=mid){
		temp[k]=a[i];
		k++;i++;
	}
	while(j<=R){
		temp[k]=a[j];
		k++;j++;
	}
	for(i=L,k=0;i<=R;i++){
		a[i]=temp[k];
		k++;
	}
}
void mergeup(int L,int R){
	if(L==R)return;
	int mid=(L+R)/2;
	mergeup(L,mid);
	mergeup(mid+1,R);
	merge(L,mid,R);
}
int main()
{
	int n,i;
	while(cin>>n){
		cnt=0;
		for(i=0;i<n;i++){
			cin>>a[i];
		}
		mergeup(0,n-1);
		cout<<cnt<<endl;
	}
	return 0;
}