51nod 1635 第K个幸运排列

题意:求1-N的全排列中的第K种排列,其中有多少幸运数字处在幸运位置上。幸运数字以及位置的定义为只包含4和7

思路:我们知道13!大于1e9,根据全排列的顺序显然最多只有13个位置在发生重排,那么我们可以根据输入的K将N个位置分成两个部分:前半部分为固定不动的部分(即1,2,3,4,……),后半部分为实际发生了重排的部分,我们对这两个部分分别计算满足要求的个数。

   我们首先找到第一个数X,它的阶乘大于K,这个数就是发生重排的数的个数,通过这个数我们将N个位置分为左右两部分。前半部分即从1到N-X的数列,问题就变成了1到N-X有多少个数只包含4和7,这里可以数位dp或者直接搞,直接搞的话首先算出N-X有多少位(假设有Len位),对于1位数到Len-1位数,随意放4或7即可,也就是2的1次方加到2的Len-1次方,对于恰好Len位的数,我们从高位开始拆解,如果当前位大于7,那么这一位4和7都能放,后面随便放;如果当前位等于7,那么这一位4一定能放,7能不能放继续看下一位数;如果当前位是5和6,那么这一位放4,后面随便放;如果当前位为4,接着看后面;当前位小于4那么这一位没法放数了,结束计数。

   后半部分因为数的个数非常少,我们将K进行逆康托展开,还原出后半部分原来的排列,对位置和数都check一下是否只包含4和7,统计一下满足的个数。

   最后两部分计数加起来就是答案了。K大于N!的输出-1。

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
LL fac[20],p[40];
int a[20];
LL solve(LL x) {
    if (x==0) return 0;
    int pos=0;
    while (x) a[++pos]=x%10,x/=10;
    LL res=0;
    for (int i=pos;i>=1;i--) {
        if (a[i]>7) {
            res+=2*p[i-1];
            break;
        }else if (a[i]==7) {
            res+=p[i-1];
            if (i==1) res++;
        }else if (a[i]>4) {
            res+=p[i-1];
            break;
        }else if (a[i]==4) {
            if (i==1) res++;
        }else if (a[i]<4) {
            break;
        }
    }
    for (int i=1;i<=pos-1;i++) res+=p[i];
    return res;
}
bool vis[20];
int s[20];
void reverse_cantor(int n,LL k,int base) {
    int i,j,t;
    --k;
    for (i=0;i<n;i++) {
        t=k/fac[n-i-1];
        for (j=1;j<=n;j++) {
            if (!vis[j]) {
                if (t==0) break;
                --t;
            }
        }
        s[i]=j+base;
        vis[j]=true;
        k%=fac[n-i-1];
    }
}
bool check(int n) {
    if (n==0) return false;
    while (n>0) {
        if (n%10!=4 && n%10!=7) return false;
        n/=10;
    }
    return true;
}
int main() {
    fac[0]=1;p[0]=1;
    for (int i=1;i<=13;i++) {
        fac[i]=fac[i-1]*i;
    }
    for (int i=1;i<=32;i++) p[i]=p[i-1]*2;
    LL n,k;
    scanf("%lld%lld",&n,&k);
    if (n<13 && fac[n]<k) {
        puts("-1");
        return 0;
    }
    int len=1;
    while (fac[len]<k) len++;
    int start=n-len+1,pos=n-len;
    len=0;
    int tmp=n;
    while (tmp>=10) tmp/=10,len++;
    LL ans=solve(pos);
    reverse_cantor(n-pos,k,pos);
    for (int i=start;i<=n;i++) {
        if (check(i) && check(s[i-start])) ans++;
    }
    printf("%lld\n",ans);
    return 0;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值