ACM 逆序对(逆序数)总结

article/2025/9/22 5:09:56

 

最近做题遇到几次逆序数了,今天总结一下,以后遇到了再也不怕了。

 

首先说明一下什么是逆序数,下面是百度的定义

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

如2431中,21,43,41,31是逆序,逆序数是4。

 

①如何求逆序数

方法如下:

考察每一位,判断从这一位往后有多少小于该位的,结果累加,得到最后结果。

例如,2,4,3,1     先判断2,之后有1是小于2的,结果+1,再判断4,之后3,1都小于4,结果+2,判断3时,结果再+1,最后得到结果4.

至于为什么,我也不知道,反正我感觉挺好理解的。

②如何用算法实现

当然,对于上述简单的过程,我们可以用两个for循环直接暴力,但是,这种方法一般都太low了,复杂度太高。

高效的方法有 : 归并排序、线段树、树状数组。

个人建议使用线段树求解,因为线段树在很多地方都有应用。

对于归并排序和树状数组,我就不解释了,因为归并排序一搞就忘记了而且用处不大,想了解的这里给出一篇博客

https://blog.csdn.net/acm_jl/article/details/51147010

 

例题是 HDU 1394,然后做完这个可以再写一下POJ 2299

HDU 1394的解析如下

https://www.cnblogs.com/qlky/p/5693747.html

然后我写的是POJ 2299

下面开始解析,当然,大家一定要有一定线段树的基础

 

算法的思想还是最基础的思想,考察每个点,判断它以后有多少个小于它的,然后累加。

与之前不同的地方在于,求小于某个数的个数时用到了线段树,所以非常快

这里的线段树表示某个范围内的值个数query 1--3,就表示值在1--3的个数

然后对于1--n个数,我们每个数都进行两步操作

查询1--a[i],单点更新a[i],值+1

例如对于 2 4 3 1 序列,我们从右向左判断,首先查询1-1,没有,值为0,然后1点值更新

再查询1--3,值为1,(实质表示的意思是比3小的有1个),然后更新3,

再查询4,更新4

查询2,更新2,得到最后的结果

 

下面是POJ 2299 的代码,要注意的是要离散化一下,因为值比较大

#include <iostream>
#include <cstdio>
#include<algorithm>
#include <cstring>
#include <stack>
#define fori(l,r) for( int i = l ; i <= r ; i++ )
#define forj(l,r) for( int j = 1 ; j <= r ; j++ )
#define lef rt<<1
#define rig rt<<1|1
#define mid (l+r)>>1
#define mem(a,val) memset(a,val,sizeof a)
typedef long long ll;
using namespace std;struct spe
{int val,pos;
};
const int maxn = 5e5+5;
spe p[maxn];
int n;
int pos[maxn];
int tree[maxn<<2];
int L,R;
bool cmp( spe a,spe b )
{return a.val < b.val;
}
void change( int rt )
{tree[rt] = tree[lef]+tree[rig];
}
void update( int l,int r,int rt,int pos )       //在pos位置值加1
{if( l == r ){tree[rt]++;return;}int m = mid;if( m >= pos )update(l,m,lef,pos);else update(m+1,r,rig,pos);change(rt);
}
ll query( int l,int r,int rt )
{if( l >= L && r <= R )return tree[rt];int m = mid;ll ans = 0;if( m >= L )ans += query(l,m,lef);if( m < R )ans += query(m+1,r,rig);return ans;
}
int main()
{while( scanf("%d",&n) == 1 && n ){mem(tree,0);fori(1,n){scanf("%d",&p[i].val);p[i].pos = i;}sort(p+1,p+1+n,cmp);int cnt = 1;fori(1,n)       //进行离散化{p[i].val = cnt++;pos[ p[i].pos ] = i;}ll ans = 0;for( int i = n ; i >= 1 ; i-- ){L = 1;R = p[ pos[i] ].val;ans += query(1,n,1);update(1,n,1,p[ pos[i] ].val );}printf("%lld\n",ans);}return 0;
}
/*
3
(([()])))*/

 

其实上面都只是基础,真正的比赛不会出上面的题

如果想更深入的了解,可以看看这篇文章,虽然我是看的是有点晕

https://www.cnblogs.com/saltless/archive/2011/06/01/2065619.html

不过最后还是听我讲解吧

 

我觉得ACM中遇到逆序数最多的就是  全排列+逆序数

就是给你一个数,问它的全排列有多少个逆序数

例题忘记是在哪了,大家可以自己搜一下

 

由于它是一个全排列,所以就很有规律性

我们列举一下

n = 2

全排列:  1   2,  2   1             逆序数是0+1 = 1

n = 3

全排列: 1  2  3,   1   3   2,  2   1   3,  2  3  1 ,   3  1  2 ,  3  2  1     逆序数是0+1+1+2+2+3

当然,最好的是能够自己去找规律,这样记忆最深刻。

对于n = 3时,其中的每一种排列情况都是 n = 2中的一个变种

也就是说,求出了n,我们就可以求n+1,这是一个递推的过程

例如,1 2 3, 2  1  3,  3  1  2, 它们其实就是n=2 中1 2的变种,之所以把他们归为一类,是因为,如果把首位忽略,然后求后两位的逆序数,发现是相等的,然后他们只是分别在前面插入1  , 2 , 3 。

所以1 2 3中的 2  3,前面插入1,结果是不变的,  2  1   3中的1  3,插入2,结果+1,  3   1   2中的 1  2,插入3,结果+2;

请大家再仔细琢磨下,就能发现递推规律。

下面是我自己推的一个公式,如果大家有通项公式,可以互相交流下

 

dp[i] = \frac{ ( dp[i-1]+dp[i-1]+(i-1)*q ) }{2}*i

其中q = !( i-1 ),q为i-1的阶乘

其实这是一个等差数列。

例如n = 2是,答案是1,n = 3时,答案就是1+3+5 = 9

n = 4时,答案就是 9+15+21+27=72

每一个dp[i]都是等差数列的和,有i项,首项是dp[i-1],公差为i-1的阶乘。

代码实现:

 

 

#include <iostream>
typedef long long ll;
using namespace std;
const ll mod = 1e9+7;
ll dp[104];
ll factorial[105];      //储存阶乘
const int maxn = 105;ll fastpow( ll base,int x )    //快速幂
{ll ans = 1;while( x ){if( x&1 )ans = (ans*base)%mod;base = ( base*base )%mod;x = x>>1;}return ans;
}
void init()
{ll total = 1;for( int i = 1 ; i <= maxn ; i++ ){total = (total*i)%mod;factorial[i] = total;}dp[1] = 0;dp[2] = 1;ll up;ll ans = 0;for( int i = 3 ; i <= maxn ; i++ ){ll q = factorial[i-1];up = ( dp[i-1]*2+( ( i-1 )*q )%mod )%mod;up = (up*i)%mod;ans = ( up*fastpow(2,mod-2) )%mod;dp[i] = ans;}}
int main()
{init();int x;while( cin>>x ){cout<<dp[x]<<endl;}return 0;
}
/**/

 

 

下面是一道更难一点的题,是计蒜客上面的

JSZKC is the captain of the lala team.

There are NN girls in the lala team. And their height is [1,N][1,N] and distinct. So it means there are no two girls with a same height.

JSZKC has to arrange them as an array from left to right and let h_ihi​ be the height of the i^{th}ith girl counting from the left. After that, he can calculate the sum of the inversion pairs. A inversion pair counts if h_i>h_jhi​>hj​ with i<ji<j.

Now JSZKC wants to know how many different arranging plans with the sum of the inversion pairs equaling KK. Two plans are considered different if and only if there exists an ii with h_ihi​ different in these two plans.

As the answer may be very large, you should output the answer mod 10000000071000000007.

Input Format

The input file contains several test cases, each of them as described below.

  • The first line of the input contains two integers NN and KK (1 \le N \le 5000,0 \le K \le 5000)(1≤N≤5000,0≤K≤5000), giving the number of girls and the pairs that JSZKC asked.

There are no more than 50005000 test cases.

Output Format

An integer in one line for each test case, which is the number of the plans mod 10000000071000000007.

样例输入

3 2
3 3

样例输出

2
1

题目来源

The 2018 ACM-ICPC China JiangSu Provincial Programming Contest

 

这个题很坑,不能开二维数组,只能先把答案储存下来,然后用滚动数组(当然我不太会用,用两个数组复制的)

很重要的规律就是递推,有一个递推公式

下面的是官方的递推公式

递推公式:用N(n,k)表示1~n排列中逆序数为k者的个数,则有
N(n,k+1)=N(n,k)+N(n-1,k+1)-N(n-1,k+1-n)

 

我推出来的有些不一样

我用pre记录前缀和,dp[i][j]记录i的全排列时,逆序数为j的个数

dp[i][j] = pre[i-1][j-1]+dp[i-1][j]

当然,由于不能开二维数组,代码看起来比较难懂。

AC代码:

#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<list>
#include<map>
#define fori(l,r) for( int i = l ; i <= r ; i++ )
#define forj(l,r) for( int j = 1 ; j <= r ; j++ )
#define mem(a,val) memset(a,val,sizeof a)
#define inf 0x3f3f3f3f
using namespace std;
int n,m;
typedef long long ll;
const int maxn = 5004;
const ll mod =  1000000007;
ll dp[maxn];
ll pre[maxn],cur[maxn];    //两个前缀和数组
ll ans[maxn];
struct spe
{int first,second;
};
int cnt = 1;
spe query[maxn];
void init()
{mem(dp,0);mem(pre,0);mem(cur,0);fori(2,5000){int goal = i*(i-1)>>1;int nextgoal = i*(i+1)>>1;goal = min(goal,maxn);nextgoal = min(nextgoal,maxn);pre[0] = dp[0] = cur[0] = 1;forj(1,goal){dp[j] = ( pre[j-1]+dp[j] )%mod;if( j >= i )dp[j] = ( dp[j]-pre[j-i]+mod )%mod;cur[j] = ( cur[j-1]+dp[j] )%mod;      //前缀和累加}forj(1,cnt-1)       //如果更新的当前行有包含答案if( query[j].first == i ){if( query[j].second <= goal )ans[j] = dp[ query[j].second ];else ans[j] = 0;}forj( 1,nextgoal ){if( j > goal )pre[j] = cur[goal];else pre[j] = cur[j];}}
}
int main()
{mem(ans,0);while( scanf("%d %d",&query[cnt].first,&query[cnt].second) == 2 && query[cnt].first ){if( query[cnt].first == 1 ){if( query[cnt].second == 0 )ans[cnt] = 1;else ans[cnt] = 0;}cnt++;}init();fori(1,cnt-1)printf("%lld\n",ans[i]);return 0;
}
/*1 1
1 2
2 1
2 2
3 1
3 2
3 3
3 4
3 5
3 6
4 1
4 2
4 3
4 4
6 5
6 6*/

 

下面是标程代码:

 

#include <map>
#include <cmath>
#include <cstdio>
#include <ctime>
#include <string>
#include <vector>
#include <cstring>
#include <cstdlib>
#include <utility>
#include <iostream>
#include <algorithm>
#define LL long long
#define pi 3.1415926535897932384626433
#define sqr(a) ((a)*(a))using namespace std;typedef pair<int,int> PII;
typedef pair<PII,int> PIII;
const int N=5002,P=1000000007;
int f[2][N];
int ans[N];
vector<PIII> d;void data_maker()
{srand(time(0));freopen("B.in", "w", stdout);printf("1 0\n1 1\n1 5000\n5000 5000\n");for (int Case=1;Case<=4996;Case++){printf("%d %d\n",rand()%5000+1,rand()%5001);}fclose(stdout);
}
int main()
{//data_maker();//freopen("B.in", "r", stdin);//freopen("B.out", "w", stdout);int i,j,k,x,y,Case=0,cur=0;while (scanf("%d%d",&x,&y)!=EOF) d.push_back({{x,y},++Case});sort(d.begin(),d.end());f[1][0]=1;f[1][1]=-1;k=1;for (i=1;i<=5000;i++,k^=1){for (j=0;j<=5000;j++){if (j>0) f[k][j]=(f[k][j]+f[k][j-1])%P;f[k^1][j]=(f[k^1][j]+f[k][j])%P;if (i+j+1<=5000)f[k^1][i+j+1]=((f[k^1][i+j+1]-f[k][j])%P+P)%P;}while (cur!=Case&&i==d[cur].first.first)ans[d[cur].second]=f[k][d[cur].first.second],++cur;memset(f[k],0,sizeof(f[k]));}for (i=1;i<=Case;i++) printf("%d\n",ans[i]);return 0;
}

 

 

最后给自己打个广告吧,自己做了一个网站,大家可以访问访问

 https://www.bowenyang666.com

 


http://chatgpt.dhexx.cn/article/swA6nv5G.shtml

相关文章

排列的逆序数

百度百科&#xff1a; 在一个排列中&#xff0c;如果一对数的前后位置与大小顺序相反&#xff0c;即前面的数大于后面的数&#xff0c;那么它们就称为一个逆序。一个排列中逆序的总数就称为这个排列的逆序数。也就是说&#xff0c;对于n个不同的元素&#xff0c;先规定各元素之…

逆序数算法

原题 在一个排列中&#xff0c;如果一对数的前后位置与大小顺序相反&#xff0c;即前面的数大于后面的数&#xff0c;那么它们就称为一个逆序。一个排列中逆序的总数就称为这个排列的逆序数。 如2 4 3 1中&#xff0c;2 1&#xff0c;4 3&#xff0c;4 1&#xff0c;3 1是逆序…

C语言计算逆序数

从键盘任意输入一个3为整数&#xff0c;编程计算并输出它的逆序数&#xff08;忽略整数前的正负号&#xff09;。例如&#xff0c;输入-123&#xff0c;则忽略负号&#xff0c;由其百位1、十位2、个位3&#xff0c;然后计算3*1002*101 321&#xff0c;并输出321。 输入格式要…

迁移率随载流子浓度变化

载流子迁移率随载流子浓度变化&#xff0c;弱场下几乎保持恒定&#xff0c;然而随着载流子浓度变大&#xff0c;迁移率开始下降 从上面的公式可以得出&#xff0c;在浓度很小的时候&#xff0c;迁移率保持在最大值&#xff0c;当浓度比参考浓度大很多的时候&#xff0c;迁移率…

半导体器件物理 2022.10.13

漂移电流由两部分组成 扩散电流 扩散电流漂移电流就是总的电流&#xff0c;在实际问题中漂移电流远远大于扩散电流 空间电荷限制电流&#xff0c;对于本征半导体和一些绝缘体里面的电流&#xff0c;我们的作业 我们首先忽略我们的扩散电流&#xff0c;只考虑扩散电流 电流密度…

半导体材料参数介绍-很有用

上期文章我们最后提到了半导体参数&#xff0c;之所以专门挑一篇文章来说&#xff0c;因为它确实比较重要&#xff0c;可以让我们明白当前各种半导体材料的优势与劣势的原因。 不仅如此&#xff0c;还可以让我们明白一些东西&#xff0c;特别是二极管和三极管的一些特性。 其实…

silvaco-mobility models(1)

1.前一阶段的问题 大概接触了一段时间的silvaco&#xff0c;根据《InP基PIN开关二极管结构设计与制备》这篇文章提供的结构和一些简单的参数进行仿真。因为已经工作&#xff0c;没有老师在自己摸索&#xff0c;学习期间看到很多人写的心得或理解&#xff0c;或多或少都对我有所…

研究蛋白和DNA的相互作用—EMSA(凝胶迁移或电泳迁移率实验),可用于DAP-seq后续验证

技术简介 凝胶迁移或电泳迁移率实验&#xff08;EMSA,Electrophoretic Mobility Shift Assay&#xff09;是研究DNA结合蛋白和其相关的DNA结合序列相互作用的技术&#xff0c;可用于定性和定量分析。可用于DAP-seq后续验证实验。 EMSA实验&#xff0c;基于生物素标记探针与对应…

网络迁移学习率调整思路

在将HRNet从PyTorch框架向MindSpore迁移的过程中&#xff0c;由于初始学习率的选择不好&#xff0c;导致了最终精度没有达到预期要求。 文末有总结。 具体实验过程如下&#xff1a; 实验过程 优化器&#xff1a;SGD 初始学习率&#xff1a;0.01 学习率调整策略&#xff1a;p…

【迁移攻击笔记】数据集の变化→提高迁移率!Improving Transferability of Adversarial Examples with Input Diversity

1.作案动机 已知&#xff1a; 迭代攻击&#xff08;eg.I-FGSM&#xff09;过拟合且易陷入局部最优&#xff0c;不适合迁移。 单步攻击&#xff08;eg.FGSM&#xff09;欠拟合&#xff0c;不适合迁移。 对输入进行图像处理可以有效抵抗对抗攻击。 推测&#xff1a; 图像处理之后…

为什么NMOS管比PMOS管用得多--电子迁移率-宽禁带-半导体材料参数介绍

上期文章我们最后提到了半导体参数&#xff0c;之所以专门挑一篇文章来说&#xff0c;因为它确实比较重要&#xff0c;可以让我们明白当前各种半导体材料的优势与劣势的原因。 不仅如此&#xff0c;还可以让我们明白一些东西&#xff0c;特别是二极管和三极管的一些特性。 其实…

silvaco 第三章迁移率模型

记录模型都是什么 都用了什么 低场迁移率&#xff1a; 1 MUN and MUP parameters to set constant values for electron and hole mobilities and optionally specify temperature dependence. 2 using a look-up table model (CONMOB) to relate the low-field mobility at…

基于形变势理论计算载流子迁移率

载流子迁移率通常指半导体内部电子和空穴整体的运动快慢情况&#xff0c;是衡量半导体器件性能的重要物理量&#xff0c;例如对石墨烯、黑磷等二维材料展现出的高载流子迁移率的研究。由于电子在运动过程中不仅受到外电场力的作用&#xff0c;还会不断的与晶格、杂质、缺陷等发…

Silvaco 学习笔记 3——物理模型:迁移率模型

迁移率模型一般可以分为一下四种&#xff1a; 1.低场行为&#xff1a;此时载流子与晶格几乎处于平衡&#xff0c;其迁移率具有典型的低场值&#xff0c;一般用来表示。 低场载流子的迁移率可以采用5种不同的方式进行定义&#xff1b; 第一种方法使用MUN和MUP参数设置电子和空穴…

手把手地实操迁移率计算|附代码

迁移率可以用来分析资产变化情况&#xff0c;能够形象的展示客户贷款账户在整个生命周期的变化轨迹&#xff0c;也是预测未来坏账损失的常用指标。 迁移率计算步骤&#xff1a;&#xff08;以M0-M1为例&#xff09; 1、在月末或者&#xff08;账单结算完成日&#xff09;&#…

迁移率 计算方法及用途 风控建模系列 02

迁移率 计算方法及用途 风控建模系列 02 在上一篇博客中&#xff0c;我们讲解了vintage分析的原理及方法&#xff08;https://blog.csdn.net/weixin_44239904/article/details/99745084&#xff09;。而迁移率经常与vintage分析一同被人提到&#xff0c;不少人对这两者傻傻分不…

go 类型断言

switch 语句 switch k {case 0:println("fallthrough")fallthrough/*Go的switch非常灵活&#xff0c;表达式不必是常量或整数&#xff0c;执行的过程从上至下&#xff0c;直到找到匹配项&#xff1b;而如果switch没有表达式&#xff0c;它会匹配true。Go里面switch默…

java断言是什么_Java断言

断言的概念 断言用于证明和测试程序的假设&#xff0c;比如“这里的值大于 5”。 断言可以在运行时从代码中完全删除&#xff0c;所以对代码的运行速度没有影响。 断言的使用 断言有两种方法&#xff1a;一种是 assert<> &#xff1b; 另一种是 assert<> &#xff…

C++ 断言

文章目录 前言assertstatic_assert 前言 断言(Assertion)是一种常用的编程手段&#xff0c;用于排除程序中不应该出现的逻辑错误。它是一种很好的Debug工具。其作用是判断表达式是否为真。C提供了assert和static_assert来进行断言。在C库中也有断言&#xff0c;其中断言与C的相…

SVA断言

目录 Assertion介绍什么是assertion&#xff1f;断言覆盖率断言语言的发展与进步类型划分立即断言并行断言并行断言的执行阶段assertion&#xff0c;property&#xff0c;sequencesequences sequence定义基本操作符号and操作符号intersect操作符号or操作符号first_match操作符号…