题意:求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;
}