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

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

一 树状数组

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;
}

	

三:线段树

和树状数组一样,但是是靠线段树来单点修改,区间求和

#include<iostream>
#include<cmath>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<map>
using namespace std;
int sum;
struct node{
	int L,R,v;
};
node tree[400005];
struct te{
	int pos,v;
};
te a[100005];
void build(int left,int right,int k){
	tree[k].L=left;tree[k].R=right;
	if(tree[k].L==tree[k].R){
		tree[k].v=0;
		return;
	}
	int mid=(tree[k].L+tree[k].R)/2;
	build(left,mid,2*k);
	build(mid+1,right,2*k+1);
}
void getsum(int LL,int RR,int k){
	if(tree[k].L>=LL&&tree[k].R<=RR){
		sum+=tree[k].v;
		return;
	}
	int mid=(tree[k].L+tree[k].R)/2;
	if(mid>=LL)getsum(LL,RR,2*k);//>=LL
	if(mid<RR)getsum(LL,RR,2*k+1);
}
void change(int k,int x){
	if(tree[k].L==tree[k].R){
		tree[k].v++;
		return;
	}
	int mid=(tree[k].L+tree[k].R)/2;
	if(x<=mid)change(2*k,x);//x<=mid,要写'='
	else if(x>mid)change(2*k+1,x);
	tree[k].v=tree[2*k].v+tree[2*k+1].v;
}
bool cmp(te x,te y){
	if(x.v==y.v)return x.pos<y.pos;
	return x.v<y.v;
}
int main()
{
	long long n,i,j,ans;
	while(scanf("%lld",&n)==1){
		memset(tree,0,sizeof(tree));
		memset(a,0,sizeof(a));
		build(1,n,1);
		for(i=1;i<=n;i++){
			scanf("%d",&a[i].v);
			a[i].pos=i;
		}
		ans=0;
		sort(a+1,a+n+1,cmp);
		for(i=1;i<=n;i++){
			sum=0;
			change(1,a[i].pos);//加入
			getsum(1,a[i].pos,1);//求和
			ans+=(i-sum);
		}
		cout<<ans<<endl;
	}
	return 0;
}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值