前言

线性代数中对于一段数字序列的排列情况有这样一个定义:在一个排列中,如果一对数的前后位置与大小顺序相反,即前面的数大于后面的数,那么它们就称为一个逆序一个排列中逆序的总数就称为这个排列的逆序数。一个排列中所有逆序总数叫做这个排列的逆序数。也就是说,对于n个不同的元素,先规定各元素之间有一个标准次序(例如n个 不同的自然数,可规定从小到大为标准次序),于是在这n个元素的任一排列中,当某两个元素的先后次序与标准次序不同时,就说有1个逆序。一个排列中所有逆序总数叫做这个排列的逆序数。(摘自 百度百科)

对于逆序数通俗的理解:对于序列中每个位置的的数,其之前比他的值大的个数之和,或者在其之后比他的值小的个数之和,如此称为逆序数


实现

实现手段:线段树、树状数组、离散化、归并排序、枚举

  • 我们容易根据逆序数的理解写出O(n^2)的模拟算法,是一个普通冒泡排序类似物:
int ans = 0;  //逆序数个数
int num[maxn];
for (int i = 1; i < n; i++) {for (int j = n - i; j > 0; j--) {if (num[j + 1] < num[j]) {swap(num[j + 1], num[j]);ans++;    }}
}
  • 如果当一段数字集中在某个范围中,还可以利用hash的特性,复杂度O(n*num(max)),但这个仍然是个暴力算法:
#include <iostream>
using namespace std;
int  a[100005];//存储有多少比它大的数字在它之前
int main()
{int n, m, i, j, k;cin >> n;long long int sum = 0;for (i = 1; i <= n; i++){scanf("%d", &m);sum = sum + a[m];// 有多少比它大的数字在他之前,就要加上多少组for (j = 0; j<m; j++)//在它之前的数字都+1{a[j]++;}}cout << sum << endl;
}
  • 上面那步的思想其实就是每次将数字压入数据集时,查询比当前数字排名来得大得数字数量,这个数字就是当前下标数字得逆序数。但是这个写法不足地方有两点:1,数据分散时空间复杂度高;2,每次都要执行一个O(num)大小的前缀操作。我们这是就可以利用线段树,树状数组的树形结构将前缀操作以及查询操作都均摊到O(logn)级别,从而提高效率。
细节

关于sum的含义是求得1~idx下标得前缀和,在这里根据方法2得思路就是rank <= idx的前缀数量,这里就要利用到一点容斥的思想:我们的目标是考虑当前有多少个比当前数排名大的数,当前全集为i,rank <= idx的数量为sum(idx),则当前 rank > idx 的数量为i-sum(idx)。其实c维护的就是rank的数量。举例:{7,4,3,8,6}

目前数据集 目前下标得到的逆序数 说明
{ } 初始化 逆序数为0,数据集为空
{7} 0 加入7,排名为4,逆序数为0,前面没有排名比他高的数字
{7,4} 1 加入4,排名为2,逆序数为1,前面排名有1个比他高的数字,分别为7
{7,4,3} 2 加入3,排名为1,逆序数为2,前面排名有2个比他高的数字,分别为7,4
{7,4,3,8} 0 加入8,排名为5,逆序数为0,前面排名有0个比他高的数字
{7,4,3,8,6} 2 加入6,排名为3,逆序数为2,前面排名有2个比他高的数字,分别为7,8
观察C数组的变化

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;
int n,c[100010];
int lowbit(int x)
{return x&(-x);
}
void add(int k,int num)
{while(k<=n){c[k]+=num;k+=lowbit(k);}
}int query(int k)
{int sum=0;while(k){sum+=c[k];k-=lowbit(k);}return sum;
}
typedef struct nodee
{int x,i;
}node;
node maze[100010];
bool cmp(node u,node v)
{if(u.x==v.x)return u.i>v.i;return u.x<v.x;
}
int b[100010];int main(void)
{int i,j,x,y;while(~scanf("%d",&n)){ll sum = 0;memset(c,0,sizeof(c));for(i=1;i<=n;i++){scanf("%d",&maze[i].x);maze[i].i = i;}sort(maze+1,maze+1+n,cmp);int cnt = 1;for(i=1;i<=n;i++){if(i!=1&&maze[i].x!=maze[i-1].x)cnt++;b[maze[i].i] = cnt;}for(i=1;i<=n;i++){add(b[i],1);ll tmp = (i-query(b[i]));sum += tmp;cout << "\n逆序数 = " << tmp << "  排名 = " << b[i] << '\n';cout << "C数组:\n";for (int j = 1; j <= n; j++) {cout << c[j] << " ";}}printf("\n\n排列的逆序数为 = %lld\n",sum);}return 0;
}
  • 对于逆序数还有归并排序的求法,注意归并排序是稳定的排序算法,写法有细节要注意。
#include <iostream>
#include <algorithm>using namespace std;
const int maxn = (int)1e5+5;
typedef long long ll;
typedef double db;
typedef long double ldb;int val[maxn],tmp[maxn];
ll cnt;void Merge (int l, int m, int r) {int i = l;int j = m + 1;int k = l;//归并排序为稳定排序,稳定的关键是mid后面那部分只有在小于前面的时候才往前提,相等不提!!!while (i <= m && j <= r) {if (val[i] > val[j]) {cnt += j-k; // 每当后段的数组元素提前时,记录提前的距离tmp[k++] = val[j++];}else {tmp[k++] = val[i++];}}while (i <= m) {tmp[k++] = val[i++];}while (j <= r) {tmp[k++] = val[j++];}for (int i = l; i <= r; i++) {val[i] = tmp[i];}
}void MergeSort(int l, int r) {if (l < r) {int mid = l + ((r - l) >> 1);MergeSort(l, mid);MergeSort(mid + 1, r);Merge(l, mid, r);}return ;
}int main() {int n;cin >> n;for (int i = 1; i <= n; i++) {cin >> val[i];}cnt = 0;MergeSort(1, n);cout << cnt << '\n';return 0;
}

题目链接

  • NowCoder
  • PKU2299
  • HDU1394

逆序数介绍以及算法实现相关推荐

  1. 归并算法经典应用——求解逆序数

    本文始发于个人公众号:TechFlow,原创不易,求个关注 在之前介绍线性代数行列式计算公式的时候,我们曾经介绍过逆序数:我们在列举出行列式的每一项之后,需要通过逆序数来确定这一项符号的正负性.如果有 ...

  2. 算法笔记-归并算法面试题、逆序数问题

    1. 题目 逆序对问题:在一个数组中,左边的数如果比右边大,则这两个数构成一个逆序对,请打印所有逆序对 比如说: 数组: 5, 1, 3,4,2 第一个元素是5,其右边有小于5的数,即1,3,4,2, ...

  3. 45.分支算法练习:  7622:求排列的逆序数

    总时间限制: 1000ms 内存限制: 65536kB 描述 在Internet上的搜索引擎经常需要对信息进行比较,比如可以通过某个人对一些事物的排名来估计他(或她)对各种不同信息的兴趣,从而实现个性 ...

  4. 【归并排序】-求逆序数算法

    1.归并排序 归并排序是分治法的一种典型应用,应用递归思想,自顶向下思考:先假定MergeSort()可以将一个乱序数组排好序,因此可以开始分(将一个数组平均分成两部分),再治(分别调用MergeSo ...

  5. 【归并排序】求逆序数算法

    在一个排列中,如果一对数的前后位置与大小顺序相反,即前面的数大于后面的数,那么它们就称为一个逆序. 一个排列中逆序的总数就称为这个排列的逆序数.逆序数为偶数的排列称为偶排列:逆序数为奇数的排列称为奇排 ...

  6. 【排序】归并类排序—归并排序(逆序数问题)

    文章目录 前言 归并排序(merge sort) 逆序数 结语 微信公众号:bigsai 数据结构与算法专栏 前言 在排序中,我们可能大部分更熟悉冒泡排序.快排之类.对归并排序可能比较陌生.然而事实上 ...

  7. 递归/归并:count of smaller numbers求逆序数

    已知数组nums,求新数组count,count[i]代表了在nums[i]右侧且比 nums[i]小的元素个数. 例如: nums = [5, 2, 6, 1], count = [2, 1, 1, ...

  8. 逆序数2 HDOJ 1394 Minimum Inversion Number

    题目传送门 1 /* 2 求逆序数的四种方法 3 */ 1 /* 2 1. O(n^2) 暴力+递推 法:如果求出第一种情况的逆序列,其他的可以通过递推来搞出来,一开始是t[1],t[2],t[3]. ...

  9. 八十二、归并排序求取复杂的逆序数

    @Author:Runsen 逆序数,我在很多的面试题都见过,本质上来说难度是比较大,因为如果使用暴力法当数据量一大,必然就会爆掉.你现在就要记住逆序数就是考归并排序. 逆序数 给定一个数组array ...

最新文章

  1. 基于手势识别的鼠标控制实现
  2. golang import 导入包语法介绍 点 别名 下划线
  3. 网络配置之ifconfig及Ip命令详解
  4. 认识与入门:Markdown
  5. Html5不允许修改但允许赋值,JavaScript | 引用类型变量的赋值问题
  6. left join on、where后面的条件的区别
  7. python-网易云音乐搜索下载脚本
  8. DevOps原则,听伍道长细细道来
  9. Linux中安装苹果系统
  10. api系列聚美优品的知识点
  11. 总结低代码海报平台编辑器难点
  12. debug基本命令及全称
  13. Word UVA - 517 状态压缩 暴力搜索
  14. 如何判断一个文件的编码格式是gb2312还是gbk等
  15. LED 控制技术将日间行车灯的优势发挥到极致
  16. push_back 和 emplace_back 剖析
  17. Android自带图标大全
  18. 浅谈EditorConfig、Prettier以及Eslint的使用
  19. mysql 5.7 xbackup_mysql5.7备份
  20. 是时候入场投资数字货币了吗?

热门文章

  1. 实例分析对关键词堆砌以及长尾关键词的判断
  2. tp5 时间间隔查询问题
  3. 用python画函数的梯度图_只需45秒,用Python给故宫画一组雪景手绘图
  4. 2022-05-18——视频拍摄小记 ——三段式思维让短片更具有氛围感
  5. 不会接口测试?用Postman轻松入门(四)——Post请求url格式请求体
  6. 使用modprobe加载驱动
  7. FreeSWITCH中lua实例:实现呼叫中心中电话接通前播放坐席号码的效果
  8. 茶的分类计算机基础知识,【收藏】六大茶类的本质区别,看这篇就够了!(附茶叶分类图)...
  9. 4、MySQL 逻辑架构
  10. b站pink老师JavaScript的PC端网页特效 案例代码——仿淘宝返回顶部(带有动画的返回顶部)