题面
https://www.luogu.com.cn/problem/P1384
分析
康托展开,即$k=a_n*(n-1)!+a_{n-1}*(n-2)!+cdot cdot cdot +a_1*0!$,$a_i$ 表示第i位上的数在尚未出现的元素中的排名
这题对k做逆康托展开还原序列前13位即可(13!已经大于最大值了,剩余部分不会变)然后统计不变部分的幸运数个数,对于变化的部分单独求在幸运数位置上的幸运数个数。
由于康托展开是一种类似数位DP的定理,所以也可以归类这题为数位DP
代码
#include <iostream> #include <cstdio> #include <algorithm> using namespace std; typedef long long ll; const int N=2e6+10; int cnt,ncnt,c; ll n,k,fact[21],pow[10],l[N],need[21],num[21],rev[21],ans; void Get_Lucky(int dep,ll x) { if (x+pow[dep]*4<=n) Get_Lucky(dep+1,l[++cnt]=x+pow[dep]*4); if (x+pow[dep]*7<=n) Get_Lucky(dep+1,l[++cnt]=x+pow[dep]*7); } bool Lucky(ll x) {for (;x;x/=10) if (x%10!=4&&x%10!=7) return 0;return 1;} int main() { scanf("%lld%lld",&n,&k);k--; fact[0]=1;for (int i=1;i<=20;i++) fact[i]=fact[i-1]*i; pow[0]=1;for (int i=1;i<10;i++) pow[i]=pow[i-1]*10; if (n<=12&&fact[n]<=k) return printf("-1"),0; Get_Lucky(0,0);sort(l+1,l+cnt+1); if (cnt==0) return printf("0"),0; for (c=20;c&&fact[c]>k;c--); for (int i=cnt;l[i]>=n-c;i--) need[++ncnt]=l[i]; for (int i=n-c;i<=n;i++) num[i-n+c+1]=i; for (int i=1,j=c;j;j--,i++) { rev[i]=num[k/fact[j]+1]; for (int r=k/fact[j]+1;r<=j;r++) num[r]=num[r+1]; k%=fact[j]; } rev[c+1]=num[1]; for (int i=1;i<=ncnt;i++) if (Lucky(rev[need[i]-n+c+1])) ans++; printf("%lld",ans+cnt-ncnt); }