1. 程式人生 > 實用技巧 >[康託展開]luogu P1384 幸運數與排列

[康託展開]luogu P1384 幸運數與排列

題面

https://www.luogu.com.cn/problem/P1384

分析

康託展開,即$k=a_n*(n-1)!+a_{n-1}*(n-2)!+\dot \dot \dot +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); }
View Code