[康托展开]luogu P1384 幸运数与排列

题面

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);
}
View Code

 

posted @ 2020-10-21 22:05  Vagari  阅读(172)  评论(0编辑  收藏  举报