51nod 1635 第K个幸运排列 逆康托展开+DFS

题目链接:

http://www.51nod.com/Challenge/Problem.html#problemId=1635

康托展开和逆康托展开解释:

https://blog.csdn.net/u011815404/article/details/99412989

1635 第K个幸运排列

比得喜欢幸运数字。这里所说的幸运数字是由4和7组成的正整数。比如,整数47,744,4是幸运数字,而5,17,467就不是。

 

一天比得梦到由数字1到n组成的第K个字典序排列。要求计算在这个排列中有多少个幸运数所在的位置的编号也是幸运数。

 

举例如下:

比如排列[1,2,3,4],其中4为幸运数,它所在的位置的下标为4(幸运数),所在此排列中,结果为1.

又如,[1,2,4,3],4所在位置为3。3不是幸运数,所以,结果为0.

 

样例解释:

排列是由n个元素组成的一个序列,在这个序列中,整数1到n都要有且仅出现一次。

在排列中,第i个元素定义为 ai

 (1≤i≤n)。

假定有排列a,和排列b。长度均为n。即都是由1到n组成。如果存在i(1≤i≤n)和对于任意j(1≤j<i)使得 ai < bi

 且 aj = bj

 。我们就说,a的字典序比b的字典序小。

对1到n组成的所有排列进行字典序升序排列。然后取其中的第K个排列,就是第K个字典序排列。

 

在样例中,最终排列如下:

1 2 3 4 6 7 5

只有第4个位置是幸运数。

 

输入

单组测试数据
共一行,包含两个整数n和k(1≤n,k≤10^9)表示排列中的元素个数,和第K个字典序排列。

输出

如果k超过出了1到n所有排列数的种数,那么输出“-1”(没有引号)。
否则,输出题目要求结果。

输入样例

7 4

输出样例

1

 思路:

因为13!就已经超过10^9,所以输出-1的情况只有n<13的情况,

当n>=13时,第k个全排列只是后面的某些个数排列就能够组成,所以需要找到某个点pos,前面的1-pos不需要动,只需要DFS判断就可以,后面的pos-n使用逆康托展开求后面的排列,然后判断是否序列和位置都是幸运数

 

This is the code:

#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<iostream>
#include<iomanip>
#include<list>
#include<map>
#include<queue>
#include<sstream>
#include<stack>
#include<string>
#include<set>
#include<vector>
using namespace std;
#define PI acos(-1.0)
#define EXP exp(1)
#define pppp cout<<endl;
#define EPS 1e-8
#define LL long long
#define ULL unsigned long long     //1844674407370955161
#define INT_INF 0x3f3f3f3f      //1061109567
#define LL_INF 0x3f3f3f3f3f3f3f3f //4557430888798830399
// ios::sync_with_stdio(false);
// 那么cin, 就不能跟C的 scanf,sscanf, getchar, fgets之类的一起使用了。
const int dr[]={0, 0, -1, 1, -1, -1, 1, 1};
const int dc[]={-1, 1, 0, 0, -1, 1, -1, 1};
inline int read()//输入外挂
{
    int ret=0, flag=0;
    char ch;
    if((ch=getchar())=='-')
        flag=1;
    else if(ch>='0'&&ch<='9')
        ret = ch - '0';
    while((ch=getchar())>='0'&&ch<='9')
        ret=ret*10+(ch-'0');
    return flag ? -ret : ret;
}
LL fac[20];
LL num[20];
int vis[20];
LL ans;
void init()//阶乘
{
    fac[0]=1LL;
    for(LL i=1;i<=14LL;++i)
        fac[i]=fac[i-1]*i;
}
bool check(LL x)//判断是不是幸运数
{
    while(x)
    {
        LL ans=x%10;
        if(ans!=4 && ans!=7)
            return false;
        x/=10LL;
    }
    return true;
}
void reverse_cantor(LL n,LL k, int base)//逆康托展开
{
    k--;
    for(LL i=1;i<=n;++i)
    {
        LL cnt=k/fac[n-i];
        k=k%fac[n-i];
        for(LL j=1;j<=n;++j)
        {
            if(!vis[j])
            {
                if(!cnt)
                {
                    vis[j]=1;
                    num[i]=j+base;
                    break;
                }
                cnt--;
            }
        }
    }
    for(LL i=1;i<=n;++i)//判断展开式中多少幸运数并且位置也是幸运数
        if(check(i+base) && check(num[i]))
            ans++;
}
void DFS(LL x,LL n)//DFS寻找到n有多少个幸运数
{
    if(x>n)
        return ;
    if(x)
        ans++;
    DFS(x*10+4,n);
    DFS(x*10+7,n);
}
int main()
{
    init();
    LL n,k;
    scanf("%lld%lld",&n,&k);
    if(n<13 && fac[n]<k )//表示没有第k个排列
    {
        printf("-1\n");
        return 0;
    }
    LL pos=1;
    for(LL i=1; ; ++i)//找变动多少个数排列就可以组成第k个排列,从后面数多少个数
    {
        if(fac[i]>=k)
        {
            pos=i;
            break;
        }
    }
    reverse_cantor(pos,k,n-pos);
    DFS(0LL,n-pos);
    printf("%lld\n",ans);
    return 0;
}

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值