康托展开

  • 显然的,给出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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值