当前位置: 首页 > news >正文

逆序对略解

逆序对

定义

在一个数列中,如果前面的数字大于后面的数字,那么这两个数字就构成了一个逆序对

求逆序对

有3种方法:暴力归并排序线段树

1.暴力算法

枚举i和j(i<j),并判断是否满足a[j]<a[i]

for(int i=1;i<n;i++){for(int j=i+1;j<=n;j++){if(a[i]>a[j]) ans++;}}

时间复杂度:O(n²)

2.归并排序

首先知道归并排序的原理:每次把数组平均分成两组,直至每一组只有一个元素,那这一部分一定有序,再用有序部分继续比较排序,进行合并。这样最后合并出来的数组就是有序的

例:

1 3 5 7 9        2 4 6 8 10

① 由于1<2,所以把1放入答案数组

1

② 因为3>2,所以把2放入答案数组

1 2

③ 因为3<4,所以把3放入答案数组

1 2 3

……

最终,答案数组变为

1 2 3 4 5 6 7 8 9 10

我们观察操作①②③,发现它是在比较两个部分的元素大小。

第②步中,发现3>2,由于每个部分都是有序的,因为前面组中3后面的数都比3大,所以前面组中3以及之后的数字都会与2组成逆序对,所以只需要再归并排序的基础上,当前面元素大于后面元素时计算逆序对个数即可

//a是当前数组,t是答案数组
void mergesort(int l,int r){if(l>=r) return;int mid=(l+r)>>1;mergesort(l,mid);mergesort(mid+1,r);int k=0,i=l,j=mid+1;while(i<=mid&&j<=r){if(a[i]<=a[j]) t[++k]=a[i++];else{t[++k]=a[j++];ans+=mid-i+1;}}while(i<=mid) t[++k]=a[i++];while(j<=r) t[++k]=a[j++];for(i=l,k=0;i<=r;i++) a[i]=t[++k];return;
}

时间复杂度:O(n long n)

3.树状数组

树状数组的作用是求前缀和,如何用树状数组表示逆序对数?

其实网上有2种思路,感觉都很神奇。

1.记录下标法

开struct存每一位的值(val)和下标(id),然后按照val降序对数组进行排序。针对每一位,让这一位在树状数组中的位置+1,然后求上一个位置的前缀和,就是当前数值所构成的一部分逆序对数。

原理

因为数组按照权值降序排序,所以处理当前元素时,树状数组中已存在的所有元素值都大于等于当前元素值,满足a[i]>a[j]的条件,接下来需要满足i<j,所以就找j上一位的前缀和,因为每一位只有一个权值,所以树状数组中的值只可能为0和1,即为这一位有数字/没数字,所以前缀和就是1的个数,即为比j小的下标的个数,满足i<j,所以上一位的前缀和就是这一位与前面数的逆序对数

当val值相等的时候,需要按id从大到小排序,这是为了防止将相同的数字错误的当作逆序对数。假设下标2,4存的数值都是10,如果先处理2下标,那么2下标就会被标记,接下来处理4下标的时候,由于2<4,所以计算前缀和会把2下标也计算上,这样是错误的。

(洛谷P1908)

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N=5e5+10;
ll n,ans;
struct node{ll id,val;
}a[N];
struct tree{ll c[N];void update(ll p,ll x){for(;p<=n;p+=(p&-p)) c[p]+=x;return ;}ll ask(ll p){ll ans=0;for(;p;p-=(p&-p)) ans+=c[p];return ans;}
}t;
bool cmp(node a1,node a2){if(a1.val!=a2.val) return a1.val>a2.val;return a1.id>a2.id;
}
int main(){scanf("%lld",&n);for(int i=1;i<=n;i++){scanf("%lld",&a[i].val);a[i].id=i;}sort(a+1,a+n+1,cmp);for(int i=1;i<=n;i++){t.update(a[i].id,1);ans+=t.ask(a[i].id-1);}printf("%lld",ans);return 0;
}

时间复杂度:O(n long n)

2.记录数值法

记录每个数值出现的次数,到处理当前位时,让当前位的数值在树状数组中的位置+1,然后求这个数值在树状数组中的前缀和,并用i减去它。

原理

由于记录的是每一个数值出现的次数,所以处理当前元素(i)时,求它的前缀和,就是所有小于等于它的数值的个数,此时满足j<i,所以还需要满足a[i]<a[j],由于前缀和计算的是小于等于,当前是第j个数,所以答案应为i-ask(a[i])

当数组数值过大时需要进行离散化

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N=5e5+10;
ll n,ans,a;
struct tree{ll c[N];void update(ll p,ll x){for(;p<=n;p+=(p&-p)) c[p]+=x;return ;}ll ask(ll p){ll ans=0;for(;p;p-=(p&-p)) ans+=c[p];return ans;}
}t;
int main(){scanf("%lld",&n);for(int i=1;i<=n;i++){scanf("%lld",&a);t.update(a,1);ans+=i-t.ask(a);}printf("%lld",ans);return 0;
}

时间复杂度:O(n long n)

http://www.jsqmd.com/news/26867/

相关文章:

  • 解码Shell 脚本编程
  • 第10天(中等题 滑动窗口)
  • 树形dp部分题目总结
  • 人工智能之编程基础 Python 入门:第三章 基础语法
  • 模块-文本
  • 偏微分方程数值解
  • oier的呻吟
  • 进销存软件和ERP是包含关系吗?
  • jenkins 权限控制(用户只能看指定的项目)
  • CF1784C Monsters (hard version)
  • [Programming Tips]Teach Yourself Programming in Ten Years by Peter Norvig
  • 世界上最牛逼的人—黄景行
  • X991CN-个人自制计算器
  • 非计算机专业,保姆级申请软著教程
  • F5重大安全事件:国家级黑客窃取BIG-IP源代码与技术漏洞
  • 2025年功效型洗发水品牌推荐榜:二硫化硒去屑洗发水/香氛洗发水/控油蓬松洗发水/MASIL玛丝兰以科技适配多元洗护需求​
  • 10.30(续)
  • Python字典 _ 创个秒查流行语的词典
  • 2025铝合金/工业/体育/机库/篷房推荐榜:华烨海特斯五星领跑!德国技术 + 多领域适配,3 家企业凭活动 / 仓储 / 特种场景显优势
  • B3612 【深进1.例1】求区间和
  • 2025智慧康养实训室/专业建设/虚拟仿真/仿真实训室机构推荐榜:北京教之道五星领跑!全场景 AI 服务 + 居家社区适配,3 家企业凭硬件 / 平台 / 改造显实力
  • 2025氮化硼陶瓷/高温绝缘体/坩埚/套管/基板/高温构件/耐腐蚀构件厂家综合推荐榜:福维科新材料以全产业链布局与高性能材料引领行业创新
  • 2025液冷/全液冷/浸没式液冷/大功率/超充/设备推荐榜:中碳创新五星领跑!AI 能碳管理 + 超充之城落地,3 家企业凭家用 / 商用 / 县域场景显优势
  • 网络参考模型与标准协议
  • 2025澳洲/新加坡/美国/新西兰/加拿大/英国/留学生辅导机构推荐榜:合肥辅无忧教育以定制化服务与全球师资引领学业支持
  • 2025年屏蔽机房设备厂家推荐榜:局放屏蔽机房/电磁屏蔽机房/组装式屏蔽机房/专注电磁安全,助力行业升级
  • 如何按部就班地打一场提高组比赛
  • 1030
  • 1030 2
  • Mac版Color Folder v3.8安装教程(附dmg文件安装步骤和搜索关键词)