发现 $k<=10^9<13!$ 所以只有最后几位会变
前面一大段都是固定的
考虑求前面一大段的贡献
$n$最大就 9 位,直接数位DP就好了(爆搜也是可以的)
考虑后面几位的贡献
用逆康托展开求出每一位的值然后暴力判断就好了
代码有一些细节:
#include<iostream> #include<cstdio> #include<algorithm> #include<cmath> #include<cstring> #include<vector> using namespace std; typedef long long ll; inline int read() { int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9') { if(ch=='-') f=-1; ch=getchar(); } while(ch>='0'&&ch<='9') { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); } return x*f; } const int N=233; int n,m,ans,a[N],f[N]; ll fac[N]; int dfs(int pos,bool lim,bool z)//lim表示前几位是否在上限,z前几位表示是否是前导零 { if(pos==-1) return 1; if(!lim&&!z&&f[pos]!=-1) return f[pos]; int res=0,mx=lim ? a[pos] : 9; for(int i=0;i<=mx;i++) { if(i==4||i==7||(z&&i==0)) res+=dfs(pos-1,lim&(i==mx),z&&i==0); } if(!lim&&!z) f[pos]=res; return res; } int solve1(int x)//求前面一段的贡献 { int tot=0; while(x) a[tot++]=x%10,x/=10; return dfs(tot-1,1,1); } inline bool pd(int x)//判断是否为幸运数 { while(x) { if(x%10!=4&&x%10!=7) return 0; x/=10; } return 1; } int solve2(int x,int rnk)//求后面一段贡献 { vector <int> v,b; for(int i=n-x+1;i<=n;i++) v.push_back(i); for(int i=x;i>=1;i--)//逆康托展开 { int t=rnk/fac[i-1]; rnk%=fac[i-1]; sort(v.begin(),v.end()); b.push_back(v[t]); v.erase(v.begin()+t); } int p=n-x+1,res=0,len=b.size(); for(int i=0;i<len;i++,p++) if(pd(p)&&pd(b[i])) res++;//暴力判断 return res; } int main() { n=read(),m=read(); fac[0]=1; for(int i=1;i<=15;i++) fac[i]=fac[i-1]*i; if(n<=12&&fac[n]<1ll*m) { printf("-1"); return 0; }//注意特判无解 memset(f,-1,sizeof(f)); int tot=0; while(m>fac[tot]) tot++; printf("%d",solve1(n-tot)-1/*-1是因为数位dp时全取0也会算到贡献*/+solve2(tot,m-1)); //注意m-1,因为康托展开求的是比一个排列小的排列数,即它的排名为比它小的排列数+1 return 0; }