排列的编码【康托展开】

题目大意:
这里写图片描述

Input I n p u t

(4,(3,2,1,4))
(5,(3,5,1,2,4))
-1

Output O u t p u t

15,67

思路:
考试时居然把这道题推出来了。。。

然后还AK了。。。


正题:

这道题要我们求出这个排列是字典序第几大的,也就是让我们求 有多少个排列比该排列小,再加1即是答案。
那么应该如何考虑呢?
举个例子

  • (5,(3,5,1,2,4)) ( 5 , ( 3 , 5 , 1 , 2 , 4 ) )

这个排列是

  • 35124 3 5 1 2 4

考虑万位数,有多少个数只填万位数就肯定比它小呢?
很明显,如果必须比它小,那么就只有万位数为 1 1 2才行。那么万位数为 1 1 2的又有几个数呢?
如果把数字 1 1 放入万位

  • 1_ _ _ _

无论后面放入什么数字,都满足这个数字小于35124所以说,后面的数字可以随便填,方案数为 4 4 ! 表示阶乘,后面相同)
那么把2填进万位也有 4 4 ! 种方法,所以仅看万位就有 2×4!=48 2 × 4 ! = 48 种填法。
那么接下来看千位

  • 3 _ _ _ _

填千位使得这个数必然小于 35124 3 5 1 2 4 一共有几种方案呢?
按照原来的方法,应该有 4×3!=24 4 × 3 ! = 24 种方法。
但是,这样错了!
我们来把这个过程模拟一遍。
这个数千位必须小于5,那么就有 1 1 2 3 3 4四个数可填。
但是!
我们在填万位数的时候就已经把 1 1 2填进去了,所以如果万位填 1 1 ,那么千位就不能填1(每个数只能填一遍),那就只有 2 2 3 4 4 三个数可填。同理,如果将2填入万位,就只有 1 1 2 3 3 三个数可填。
所以无论用哪种填法,千位都只能填3个数!
但是如果这个样例是这样的

  • 43152

    那么千位就能填 1 1 2两个数字,还是千位数减一。
    那是为什么呢?
    不难发现,两组数据的区别就在于一个万位数比千位数大,一个万位数比千位数小
    如果万位数比千位数小,那么这个数就不能填在千位数(以及百位数,十位数,个位数)。同理,若果一个数万位数比百位数小,那么这个数就不能填在百位数(以及十位数和个位数)上。
    所以,回到样例,由于 3<5 3 < 5 ,所以千位数就少了一个候选数,也就变成了 3×3!=18 3 × 3 ! = 18

    按照这种方法求下去,就得到

    • 0×2!=0 0 × 2 ! = 0
    • 0×1!=0 0 × 1 ! = 0
    • 0×0!=0 0 × 0 ! = 0

    (真有趣)
    那么总共就是 48+18+0+0+0=66 48 + 18 + 0 + 0 + 0 = 66 个数字比 35124 3 5 1 2 4 小。
    那么 35124 3 5 1 2 4 就是第67大的数字啦!

    我们再用这种方法把另一个样例试一遍。

    • 原数 =3214 = 3214
    • 2×3!=12 2 × 3 ! = 12
    • 1×2!=2 1 × 2 ! = 2
    • 0×1!=0 0 × 1 ! = 0
    • 0×0!=0 0 × 0 ! = 0

    =12+2+0+0=14 = 12 + 2 + 0 + 0 = 14
    所以, 3214 3214 是字典序第15的数字。

    总结:

    我们设 num[i] n u m [ i ] 表示第 i i 个数字(从左往右数),a[i] num[1] n u m [ 1 ] num[i1] n u m [ i − 1 ] 中,比 num[i] n u m [ i ] 大的数字个数,则

    ans=((i=1n(num[i]a[i]1)×(n1)!))+1 a n s = ( ( ∑ i = 1 n ( n u m [ i ] − a [ i ] − 1 ) × ( n − 1 ) ! ) ) + 1

    本题要用高精度!


    
    #include <cstdio>
    #include <iostream>
    #include <string>
    #include <cstring>
    using namespace std;
    
    const int maxn=100;
    int n,x,num[101],a[101][maxn+1],size,k,b[101],t,c[maxn+1],o,kk,tot;
    string s;
    
    int main()
    {
        a[1][maxn]=1;
        for (int i=2;i<=50;i++)  //初始化,求阶乘
        {
            t=0;
            for (int j=maxn;j>=1;j--)
            {
                a[i][j]=a[i-1][j]*i+t;
                t=a[i][j]/10;
                a[i][j]%=10;
            }
        } 
        while (++tot)
        {
            cin>>s;
            memset(num,0,sizeof(num));
            memset(b,0,sizeof(b));
            memset(c,0,sizeof(c));  
            if (s=="-1") break;
            n=(int)s[1]-'0';
            if (s[2]>='0'&&s[2]<='9') 
             n=n*10+((int)s[2]-'0');
            if (n==1)  //特判
            {
                if (tot>1) printf(",");
                printf("1");
                continue;
            }
            if (tot>1) printf(",");
            size=s.size();
            k=1;
            for (int i=1;i<size;i++)  //读入,分离数字
             if (s[i]=='(')
             {
                for (int j=i+1;j<size;j++)
                {
                    if (s[j]>='0'&&s[j]<='9')
                     b[k]=b[k]*10+((int)s[j]-'0');
                    if (s[j]==',') 
                      k++;
                    if (s[j]==')') break;
                }
                break;
            } 
           for (int i=1;i<=n;i++)  //求出答案
           {
               o=b[i];
               for (int j=b[i]+1;j<=n;j++)
                num[j]++;
               b[i]--;
               b[i]-=num[o];
               t=0;
               for (int j=maxn;j>=1;j--) 
               {
                    c[j]+=b[i]*a[n-i][j]+t;
                    t=c[j]/10;
                    c[j]%=10;
               }
           }
           c[maxn]++;  //加1
           t=0;
           for (int i=maxn;i>=1;i--)
           {
                c[i]+=t;
                t=c[i]/10;
                c[i]%=10;
           }
           kk=1;
           while (!c[kk]) kk++;
           for (int i=kk;i<=maxn;i++) printf("%d",c[i]);
        }
        return 0;
    }
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值