时间限制: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;
}