使用C++编写代码,找到具有K个逆序对的排列数量

背景

对于一个排列P,逆序对指的是满足iP[j]的(i,j)这对位置,相对应的,升序对则是P[i]

我们的任务便是寻找具有K个逆序对的排列数量,注意这里我指的是数量而非具体的排列,例如长度为3且具有2个逆序对的排列有2个,分别为231和312,但它们都只算一种排列。

解题思路

1.暴力法

最简单的方法就是使用暴力法,先生成所有排列,对于每个排列计算其逆序对并统计数量,最终输出符合条件的排列数量。代码如下:

#include <bits/stdc++.h>

#define maxn 10

using namespace std;

int a[maxn];

int N,K,Ans;//N表示排列长度,K表示逆序对数量,Ans表示方案数

//计算逆序对数量

int calc(){

int res=0;

for(int i=1;i<N;i++)

for(int j=i+1;j<=N;j++)

if(a[i]>a[j])res++;

return res;

}

//搜索

void dfs(int dep){

if(dep==N+1){//搜索完毕,计算逆序对数量

int sum=calc();

if(sum==K)Ans++;

return;

}

for(int i=1;i<=N;i++){

if(!a[i]){

a[i]=dep;

dfs(dep+1);

a[i]=0;

}

}

}

int main(){

scanf("%d%d",&N,&K);

dfs(1);

printf("%d\n",Ans);

return 0;

}

该算法的时间复杂度为O(n*n!),对于较大的n显然求解速度非常缓慢,因此我们需要考虑更快的方法。

2.动态规划

我们试图找到一个公式,用来快速计算具有K个逆序对的排列数量,考虑动态规划。假设dp[i][j]表示长度为i且含有j个逆序对的排列数量,那么这个状态应该是可以转移的,最后我们用dp[n][K]求解即可。现在我们来考虑dp[i][j]如何通过转移得到dp[i+1][j]。

考虑将数a插入一个长度为i的排列中,我们可以想到两种插入方法:将a插入到前面或者将a插入到后面。为了方便,我们要将a赋值成i+1,这样就可以将a插入到排列末尾,然后枚举a前面的一个数b,如果b < i+1,则将a插入b后面,这样就在原来的排列基础上产生了一些新的逆序对。

考虑dp[i][j]如何转移到dp[i+1][j]。对于dp[i+1][j]来说,它应该由dp[i][j'] + new_pair'组成,其中j’表示一个小于等于j的数,显然j'越大新生成的逆序对越少,因此我们枚举j',然后加上新的逆序对的数量,就可以得到dp[i+1][j]的值。

接下来,我们来分析一下新生成的逆序对数量如何计算。对于一个新加入的数i+1,假设它插入到位置k上,则会在原有的排列中比它大的位置k~i上产生新的逆序对,这些逆序对的数量为i+1-k,我们将这个数累加到dp[i][j']后就可以得到dp[i+1][j]的值。

最终的代码会相对复杂,这里只展示dp转移部分,具体实现可以参见:https://www.acwing.com/solution/content/61747/

ll dp[maxn][maxn];

ll C[maxn][maxn];//组合数(n,k)

void init(){

fill(dp[0],dp[0]+maxn*maxn,0);

fill(C[0],C[0]+maxn*maxn,0);

for(int i=0;i<maxn;i++){//计算组合数

C[i][0]=1;

for(int j=1;j<=i;j++){

C[i][j]=C[i-1][j]+C[i-1][j-1];

}

}

dp[1][0]=1;

}

void solve(){

for(int i=2;i<=n;i++){//枚举排列长度

for(int j=0;j<=k;j++){//枚举逆序对数量

for(int k=0;k<i;k++){//枚举新加入的数插入到位置k上

int pair=i-1-k;

if(j-pair<0)continue;

dp[i][j]+=dp[i-1][j-pair]*C[i-1][pair];

}

}

}

}

总结

本节所讲的就是找到具有K个逆序对的排列数量,正文中介绍了两种解法:暴力法和动态规划。暴力法非常直接,先生成所有排列,然后暴力计算逆序对数量,最终统计符合条件的排列数量。这种方法的时间复杂度为O(n*n!),对于较大的n显然不太适用。而动态规划比较复杂,需要分析逆序对的产生方式,然后使用动态规划转移逆序对数量。动态规划的时间复杂度是O(n*n*k),可以处理较大的n。

参考资料:

https://www.acwing.com/problem/content/146/

后端开发标签