SCAU------18746 逆序数

时间限制:1000MS 代码长度限制:10KB
题型: 编程题 语言: 不限定

Description
在一个排列中,如果一对数的前后位置与大小顺序相反,即前面的数大于后面的数,那么它们就称为一个逆序。
一个排列中逆序的总数就称为这个排列的逆序数。逆序数是课程线性代数的一个知识点。
现在给定一个排列a1,a2,…,an,如果存在i<j并且ai>aj,那么我们称ai和aj为一个逆序,请求出排列的逆序数。

输入格式
第一行为n,表示排列长度。(1=<n<=100000)
第二行有n个整数,依次为排列中的a1,a2,…,an。所有整数均在int范围内。

输出格式
一个整数代表排列的逆序数。

输入样例
4
3 2 3 2

输出样例
3

提示
注意答案的数据范围。

解题思路:(归并排序法)
1.将数组分成两半,分别求出左半边的逆序数和右半边的逆序数
2.再算有多少逆序是由左半边取一个数和右半边取一个数构成的
3.2的关键:左半边和右半边都是排好序的。这样,左右半边只需要从头到尾各扫一遍,就可以找出由两边各取一个数构成的逆序个数
(改进归并排序即可 )
注意:count有可能超出int

代码如下:

#include <iostream>//1.将数组分成两半,分别求出左半边的逆序数和右半边的逆序数 
using namespace std;//2.再算有多少逆序是由左半边取一个数和右半边取一个数构成的 
int a[100005];//分治  O(nlogn) 
int b[100005];
long long count=0;
 //2的关键:左半边和右半边都是排好序的。
 //这样,左右半边只需要从头到尾各扫一遍,就可以找出由两边各取一个数构成的逆序个数 
 //改进归并排序即可 
void Merge(int a[],int s,int m,int e,int tmp[])
{
	int pb=s;
	int p1=s,p2=m+1;
	while(p1<=m&&p2<=e)
	{
		if(a[p1]>a[p2])
		{
			tmp[pb++]=a[p2++];
			count+=m-p1+1;
		}
		else tmp[pb++]=a[p1++];
	}
	while(p1<=m) tmp[pb++]=a[p1++];
	while(p2<=e) tmp[pb++]=a[p2++];
	for(int i=s;i<=e;i++)
	a[i]=tmp[i];
} 
void MergeSort(int a[],int s,int e,int tmp[])
{
	if(s<e)
	{
		int m=(e+s)/2;
		MergeSort(a,s,m,tmp);
		MergeSort(a,m+1,e,tmp);
		Merge(a,s,m,e,tmp);
	}	
}

int main()
{
	int n;
	cin>>n;
	for(int i=0;i<n;i++) cin>>a[i];
	MergeSort(a,0,n-1,b);
	cout<<count<<endl;
	return 0;
} 

还有一种解法:(树状数组)
(第一次接触,搬了大佬的解题思路,莫怪莫怪!)

大致如下:
对于样例n=5,a[1]~a[n]={9,1,0,5,4},不妨设d[j]表示j是否出现过,若是,则标记为1;
getsum( a[i])为比 a[i] 小的数的个数,其中 i 为当前已经插入的数的个数,
i- getsum( a[i] ) 即比 a[i] 大的个数, 即逆序的个数

当i=1时,d[9]=1,对0~9求和,1-getsum(9)=0
当i=2时,d[1]=1,对0 ~ 1求和,2-getsum(1)=1
当i=3,d[0]=1,对0 ~ 0求和,3-getsum(0)=2
以此类推。。。
将上面的i-getsum(a[i])加起来,就是答案了
但是,如果题目所给的数范围是0 ~ 999,999,999,根本没办法开一个d数组那么大!但是,观察到n最大才500,000,其实每个a[i]都可以映射成1~~500,000的每个数!
(高能:数组离散化!!!)
对于样例n=5,a[1]~a[n]={9,1,0,5,4},用一个结构体(或pair)来记录输入的值的大小和下标:
{(x,y)|(9,1), (1,2) , (0,3) , (5,4), (4,5) ,x为值,y为下标}
对以上按x值从小到大排序:
{(x,y)|(0,3), (1,2) , (4,5) , (5,4), (9,0) ,x为值,y为下标}
用一个数组b来存储离散化的数组,即b[x[i]]=i 结果为{5,2,1,4,3}
(高能:离散化!!!)
(改变x的值,把x的范围缩减到1~500,000的范围,然后变回原来的顺序而已。离散化后,数组中所有元素的大小关系是完全没有变化的!)

update(x),即把第x位设为1
例:输入5 ,调用update(5)
1 2 3 4 5
0 0 0 0 1
然后看比5小的数是否存在,用到上述的树状数组的i-getsum(a[i])操作计算逆序数,1-getsum(5)=0

输入2 ,调用 update(2)
1 2 3 4 5
0 1 0 0 1
2-getsum(2)=1

输入1,调用 update(1)
1 2 3 4 5
1 1 0 0 1
3-getsum(1)=2

输入4,调用 update(4)
1 2 3 4 5
1 1 0 1 1
4-getsum(1)=1

输入3,调用 update(3)
1 2 3 4 5
1 1 1 1 1
5-getsum(3)=2

最后的逆序数为0+1+2+1+2=6

lowbit(x)
顾名思义,lowbit这个函数的功能就是求某一个数的二进制表示中最低的一位1,举个例子,x = 6,它的二进制为110,那么lowbit(x)就返回2,因为最后一位1表示2。

整数运算 x&(-x),当x为0时结果为0;x为奇数时,结果为1;x为偶数时,结果为x中2的最大次方的因子。

因为:x &(-x) 就是整数x与其相反数(负号取反)的按位与:1&1=1,0&1 =0, 0&0 =1。具体分析如下:
当x为0时,x&(-x) 即 0 & 0,结果为0;
当x不为0时,x和-x必有一个为正。不失一般性,设x为正。
●当x为奇数时,最后一个比特为1,取反加1没有进位,故x和-x除最后一位外前面的位正好相反,按位与结果为0。最后一位都为1,故结果为1。
●当x为偶数,且为2的m次方(m>0)时,x的二进制表示中只有一位是1(从右往左的第m+1位),其右边有m位0,左边也都是0(个数由表示x的字节数决定),故x取反加1后,从右到左第有m个0,第m+1位及其左边全是1。这样,x& (-x) 得到的就是x。
●当x为偶数,却不为2的m次方的形式时,可以写作x= y * (2 ^ k)。其中,y的最低位为1。实际上就是把x用一个奇数左移k位来表示。这时,x的二进制表示最右边有k个0,从右往左第k+1位为1。当对x取反时,最右边的k位0变成1,第k+1位变为0;再加1,最右边的k位就又变成了0,第k+1位因为进位的关系变成了1。左边的位因为没有进位,正好和x原来对应的位上的值相反。二者按位与,得到:第k+1位上为1,左边右边都为0。结果为2^k,即x中包含的2的最大次方的因子。
总结一下:x&(-x),当x为0时结果为0;x为奇数时,结果为1;x为偶数时,结果为x中2的最大次方的因子。 比如x=32,其中2的最大次方因子为2^5,故x&(-x)结果为32;当x=28,其中2的最大次方因子为4,故x & (-x)结果为4。当x=24,其中2的最大次方因子为8,故 x&(-x)结果为8。

上述作理解

代码如下:

#include<iostream>
#include <algorithm>
#define N 500005
using namespace std;
struct Node
{
	int value;
	int pos;
};

Node node[N];
int c[N],reflect[N],n;

bool cmp(const Node &a,const Node &b)
{
	if(a.value==b.value) return a.pos<b.pos;
	return a.value<b.value;
}
int lowbit(int x) 
{
	return x&(-x);
}
void update(int x)
{
	while(x<=n)
	{
		c[x]+=1;
		x+=lowbit(x);
	}
}
int getsum(int x) 
{
	int sum=0;
	while(x>0)
	{
		sum+=c[x];
		x-=lowbit(x);
	}
	return sum;
}
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>node[i].value;
		node[i].pos=i;
	}
	sort(node+1,node+n+1,cmp);
	for(int i=1;i<=n;i++) reflect[i]=node[i].pos;//离散化
	for(int i=1;i<=n;i++) c[i]=0;//初始化树状数组
	long long ans=0;
	for(int i=1;i<=n;i++)
	{
		update(reflect[i]);
	 	ans+=i-getsum(reflect[i]);
	} 
	cout<<ans<<endl;
	return 0;
}

猜你喜欢

转载自blog.csdn.net/BitcoinR/article/details/106532005