前言
1、什么是逆序数?
在一个排列中,如果一对数的前后位置与大小顺序相反,即前面的数大于后面的数,那么它们就称为一个逆序。一个排列中逆序数的总数就是这个排列的逆序数。
2、用树状数组求逆序数的总数
2.1该背景下树状数组的含义
我们假设一个数组A[n],当A[n]=0时表示数字n在序列中没有出现过,A[n]=1表示数字n在序列中出现过。A对应的树状数组为c[n],则c[n]对应维护的是数组A[n]的内容,即树状数组c可用于求A中某个区间的值的和。
树状数组的插入函数(假设为 void insert(int i,int x) )的含义:在求逆序数这个问题中,我们的插入函数通常使用为insert( i , 1 ),即将数组A[i]的值加1 (A数组开始应该初始化为0,所以也可以理解为设置A[ i ]的值为1,即将数字i 加入到序列的意思 )。,同时维护c数组的值。
树状数组中区间求和函数(假设函数定义为: int getsun(int i ) )的含义:该函数的作用是用于求序列中小于等于数字 i 的元素的个数。这个是显而易见的,因为树状数组c 维护的是数组A的值,则该求和函数即是用于求下标小于等于 i 的数组A的和,而数组A中元素的值要么是0要么是1,所以最后求出来的就是小于等于i的元素的个数。
所以要求序列中比元素a大的数的个数,可以用i - getsum(a)即可( i 表示此时序列中元素的个数)。
离散化+树状数组
算法的大体流程就是:
1.先对输入的数组离散化,使得各个元素比较接近,而不是离散的,
2.接着,运用树状数组的标准操作来累计数组的逆序数。
算法详细解释:
1.解释为什么要有离散的这么一个过程?
刚开始以为999.999.999这么一个数字,对于int存储类型来说是足够了。
还有只有500000个数字,何必要离散化呢?
刚开始一直想不通,后来明白了,后面在运用树状数组操作的时候,
用到的树状数组C[i]是建立在一个有点像位存储的数组的基础之上的,
不是单纯的建立在输入数组之上。
比如输入一个9 1 0 5 4,那么C[i]树状数组的建立是在,
下标 0 1 2 3 4 5 6 7 8 9
数组 1 1 0 0 1 1 0 0 0 1
现在由于999999999这个数字相对于500000这个数字来说是很大的,
所以如果用数组位存储的话,那么需要999999999的空间来存储输入的数据。
这样是很浪费空间的,题目也是不允许的,所以这里想通过离散化操作,
使得离散化的结果可以更加的密集。
第一种离散化。(包含重复元素,并且相同元素离散化后也要相同,推荐使用)
离散化以前一直搞不懂是怎么实现的,看了一个代码才明白。
const int maxn=1e5+10;
int a[maxn], t[maxn];
int n;
scanf("%d",&n);
for(int i=1; i<=n; i++)
scanf("%d",a[i]),t[i]=a[i];
sort(t+1,t+n+1);
m=unique(t+1,t+1+n)-t-1;//m为不重复的元素的个数
for(int i=1; i<=n; i++)
a[i]=lower_bound(t+1,t+1+m,a[i])-t;
原来的a[i]离散化后成了后来的a[i];
离散化后的a[i]范围是(1-m);
举个栗子:
原序列:6 9 4 6 4
排序后:4 4 6 6 9
unique(元素去掉重复的)后:4 6 9 6 9 (前m位数字无重复,(自己瞎猜的:其他数字跟sort排序后的序列相比不改变 )
#include <cstdio>
#include <algorithm>
using namespace std;
int a[10]={6,9,4,6,4};
int main()
{
int n=5;
sort(a,a+n);//排序后4 4 6 6 9
n=unique(a,a+n)-a;
for(int i=0;i<5;i++)
printf("%d ",a[i]);
printf("\n");
//最后输出4 6 9 6 9, 我猜是重复的只留一个,后面的数组没变,保留原来的数字
}
unique有一个返回值,例如有十个有序的数列3 3 5 5 6 6 6 7 7 8,不重复的数字有五个,使用unique去重之后数列变成了3 5 6 7 8 6 6 7 7 8,它只改变了前五个数字后边的不变,返回值是 最后一个改变的数字的地址。so:m=unique(t+1,t+1+n)-t-1;一般要减去首地址(t+1),m为不重复的数字的个数
第二种离散化(复杂度低,1.包含重复元素,并且相同元素离散化后不相同,2.不包含重复元素,并且不同元素离散化后不同,符合这两种的其中一个,推荐使用
struct A
{
int x, idx;
bool operator < (const A &rhs) const
{
return x < rhs.x;
}//也可以写个cmp函数排序
};
A a[MAXN];
int rank[MAXN];
int n;
scanf("%d",&n);
for(int i = 1; i <= n; ++i)
{
scanf("%d", &a[i].x);
a[i].idx = i;
}
//for(int i=1; i<=n; i++)
// printf("%d %d\n",a[i].idx,a[i].x);
//printf("\n");
sort(a + 1, a + n + 1);
//for(int i=1; i<=n; i++)
// printf("%d %d\n",a[i].idx,a[i].x);
//printf("\n");
for(int i = 1; i <= n; ++i)
{
rank[a[i].idx] = i;
// printf("rank[%d] = %d\n",a[i].idx,i);
}
给你们个例子:
i 1 2 3 4
x 6 8 9 4
idx 1 2 3 4
排序后:
i 1 2 3 4 //离散化后的数字
x 4 6 8 9
idx 4 1 2 3 //数字原来的所在的位置编号
将上面两行黑体数字对应起来 即是:rank[4]=1,rank[1]=2,rank[2]=3,rank[3]=4; //rank[i]=j表示将原来在第i个位置上的数字离散化成j
so:
rank[1]=2;
rank[2]=3;
rank[3]=4;
rank[4]=1;
so: 6 8 9 4
就离散化为2,3,4,1
如果你想用原来的数字,就用排过序的结构体a[2]=6,a[3]=8,a[4]=9,a[1]=4; //a[i]=j表示排过序后现在的a数组里第i个数字的是j。j也就是第i小。
a[i]是原来的数字;
rank是离散化后的数字;
下面给出三个求逆序数的代码两个是树状数组(第一种离散化和第二种离散化的变形),一个是归并排序
树状数组(第一种离散化)
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define LL long long
using namespace std;
int n,tree[100010];
void add(int k,int num)
{
while(k<=n)
{
tree[k]+=num;
k+=k&-k;
}
}
int read(int k)
{
int sum=0;
while(k)
{
sum+=tree[k];
k-=k&-k;
}
return sum;
}
int main()
{
int i,j,x,y;
int b[100010];
int a[100010];
while(scanf("%d%d%d",&n)!=EOF)
{
LL sum = 0;
memset(tree,0,sizeof(tree));
for(i=1;i<=n;i++)
{
scanf("%d",&a[i]);
b[i]=a[i];
}
sort(b+1,b+n+1);
int cnt = 1;
int m=unique(b+1,b+n+1)-b-1;//m为不重复的元素的个数
for(int i=1; i<=n; i++)
a[i]=lower_bound(b+1,b+m+1,a[i])-b;
for(i=1;i<=n;i++)
{
add(a[i],1);
sum += (LL)(i-read(a[i]));//统计当前序列中大于a[i]的元素的个数
}
printf("%lld\n",sum);
}
return 0;
}
树状数组(第二种离散化的变形)
因为求逆序数时,存在相同元素,并且相同元素离散化后也要相同
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define LL long long
using namespace std;
int n,tree[100010];
void add(int k,int num)
{
while(k<=n)
{
tree[k]+=num;
k+=k&-k;
}
}
int read(int k)
{
int sum=0;
while(k)
{
sum+=tree[k];
k-=k&-k;
}
return sum;
}
struct node
{
int val,pos;
}a[100010];
bool cmp(node a,node b)
{
return a.val < b.val;
}
int main()
{
int i,j,x,y;
int b[100010];
while(scanf("%d",&n)!=EOF)
{
LL sum = 0;
memset(tree,0,sizeof(tree));
for(i=1;i<=n;i++)
{
scanf("%d",&a[i].val);
a[i].pos = i;
}
sort(a+1,a+1+n,cmp);
int cnt = 1;
for(i=1;i<=n;i++)//离散化
{
if(i != 1 && a[i].val != a[i-1].val)
cnt++;
b[a[i].pos] = cnt;
}
for(i=1;i<=n;i++)
{
add(b[i],1);
sum += (LL)(i-read(b[i]));//统计当前序列中大于b[i]的元素的个数
}
printf("%lld\n",sum);
}
return 0;
}
归并排序求逆序数
给出一个序列,只交换相邻两数,使得序列升序排列,求出最少交换次数。
思路:
如果说只是交换相邻两个数字。那么就是这个序列的逆序数。
1.假设序列个数为n,我们先把最大的数换到最后,因为是相邻数字交换,所以把最大数交换到最后,需要交换的次数为最大数后的数字个数。
2.当完成最大数的交换后,可以将最大数从序列中划去不管了,即此时序列个数为n-1了,我们再在该序列中找到一个最大数,进行相同操作。
3.所以使整个序列有序的交换次数为,这个序列的所有逆序总数。
比如4,3,2,1。
(4,3) (4,2) (4,1),有3个逆序,交换后 3,2,1,4
(3,2) (3,1),有2个逆序,交换后2,1,3,4
(2,1),有1个逆序,交换后1,2,3,4
用归并的方法写了个。
#include<iostream>
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
using namespace std;
int cnt;
int arr[1005];
void Merge(int* arr,int* tmp,int left,int right,int rightEnd){
int leftEnd = right - 1;
int start = left;
while (left <= leftEnd && right <= rightEnd){
if (arr[left] > arr[right]){
tmp[start++] = arr[right++];
cnt += (leftEnd - left+1); //如果当前left位置上的数大于right位置上的数,那么从left到leftEnd所有的数都大于
}
else{
tmp[start++] = arr[left++];
}
}
while (left <= leftEnd){
tmp[start++] = arr[left++];
}
while (right <= rightEnd){
tmp[start++] = arr[right++];
}
}
void MergeSort(int* arr,int* tmp,int n,int length)//length当前有序子列的长度
{
int i;
for (i = 0; i <= n - 2 * length; i += 2 * length){
Merge(arr,tmp,i,i+length,i+2*length-1);
}
//最后剩下两个子列,进行归并
if (i + length < n){
Merge(arr,tmp,i,i+length,n-1);
}
else{//只剩最后一个子列,不能成对
for (int j = i; j < n; j++){
tmp[j] = arr[j];
}
}
}
void Merge_Sort(int* arr,int n){
int lenght = 1;
int* tmp = (int *)malloc(sizeof(int)*n);
while (lenght < n){
MergeSort(arr,tmp,n,lenght);
lenght *= 2;
MergeSort(tmp,arr,n,lenght);
lenght *= 2;
}
free(tmp);
}
int main()
{
int n;
while(scanf("%d",&n)!=EOF){
memset(arr,0,sizeof(arr));
for(int i=0;i<n;i++){
scanf("%d",&arr[i]);
}
cnt=0;
Merge_Sort(arr,n);
printf("%d\n",cnt);
}
return 0;
}
还有一个队友写的树状数组求逆序数
先将价值大到小排序并且如果价值相同按坐标从大到小排序
最后只需要求query(ccc)即可(query(ccc)表示统计当前序列中大于ccc的元素个数)
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define rep(i,l,r) for(int i=l;i<=r;i++)
#define minn(x,y,z) min(min(x,y),z)
struct node
{
int x,i;
bool operator < (const node &a) const
{
if(x!=a.x)
return x>a.x;
return i>a.i;
}
}p[100005];
int c[100005];
int lowbit(int k)
{
return k&(-k);
}
void add(int k)
{
while(k < 100005)
{
c[k] += 1;
k += lowbit(k);
}
}
int query(int k)
{
int sum = 0;
while(k)
{
sum += c[k];
k -= lowbit(k);
}
return sum;
}
int main(){
int n;
while(~scanf("%d",&n))
{
memset(c,0,sizeof(c));
rep(i,1,n) scanf("%d",&p[i].x),p[i].i=i;
sort(p+1,p+1+n);
ll ans = 0x3f3f3f3f,cnt = 0, cn = 0;
for(int i=1;i<=n;i++)
{
int ccc =p[i].i;
cnt = cnt + query(ccc);
add(ccc);
}
printf("%d\n",cnt);
}
return 0;
}