P1384 幸运数与排列

传送门

发现 $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;
}
原文地址:https://www.cnblogs.com/LLTYYC/p/10342881.html