- 显然的,给出n个数,范围在1-n间,每个数各不相同,即为全排列问题,方案数为n!。
- 那么现在给出一个n个数的全排列,求在n个数的全排列按字典序排序以后,给出的全排列排第几。
- 例子:
- 3个数的全排列,按字典序排序后为
- 123
- 132
- 213
- 231
- 312
- 321
“213”是第3名。
- 此时我们需要用到一个叫“康托展开”的东西。
- 是这样计算的,一开始ans=1:
- 设当前在计算第i个数,则x=比a[i]小且在第1-(i-1)中没有出现过的数的数目。
- 那么ans=ans+x*(n-i)!。
- 这样就可以简单的算出排名。
- 注意一开始ans=1是因为我们这里算出的是比这个排列字典序小的数目,所以要得到这个排列的排名,还需要+1。
- 那么如果我们有排名,我们也可以进行康托展开的逆运算求出原排列。求出方法就相当于康托展开反过来,看看代码很容易理解。
题意(原题):
给出全排列长度n,给出一个排列,输出它的排名;再给出一个排名,输出原排列。
代码:
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#define LL long long
using namespace std;
LL n,x,y;
LL a[16],jc[16];
bool useable[16];
LL Kt()
{
memset(useable,true,sizeof(useable));
LL ans=0,cnt;
for(LL i=1;i<=n;i++)
{
cnt=0;
for(LL j=1;j<a[i];j++)
if(useable[j])
cnt++;
ans+=cnt*jc[n-i];
useable[a[i]]=false;
}
return ans+1;
}
void reKt(LL ans)
{
ans--;
memset(useable,true,sizeof(useable));
for(LL i=1;i<=n;i++)
{
LL k=ans/jc[n-i];
ans-=k*jc[n-i];
LL j;
for(j=1;j<=n;j++)
if(useable[j])
{
if(k==0)break;
k--;
}
useable[j]=false;
a[i]=j;
}
for(LL i=1;i<=n;i++)
printf("%lld ",a[i]);
printf("\n");
}
int main()
{
scanf("%d",&n);
jc[0]=1;
for(LL i=1;i<=n;i++)
{
scanf("%d",&a[i]);
jc[i]=jc[i-1]*i;
}
printf("%lld\n",Kt());
scanf("%lld",&y);
reKt(y);
return 0;
}