51nod:第K个幸运排列(数位dp & 逆康托展开)

题目来源:  CodeForces
基准时间限制:1 秒 空间限制:131072 KB 分值: 80  难度:5级算法题
 收藏
 关注

比得喜欢幸运数字。这里所说的幸运数字是由4和7组成的正整数。比如,整数47,744,4是幸运数字,而5,17,467就不是。

 

一天比得梦到由数字1到n组成的第K个字典序排列。要求计算在这个排列中有多少个幸运数所在的位置的编号也是幸运数。

 

举例如下:

比如排列[1,2,3,4],其中4为幸运数,它所在的位置的下标为4(幸运数),所在此排列中,结果为1.

又如,[1,2,4,3],4所在位置为3。3不是幸运数,所以,结果为0.


样例解释:

排列是由n个元素组成的一个序列,在这个序列中,整数1到n都要有且仅出现一次。

在排列中,第i个元素定义为 ai (1≤i≤n)。

假定有排列a,和排列b。长度均为n。即都是由1到n组成。如果存在i(1≤i≤n)和对于任意j(1≤j<i)使得 ai  bi 且 aj  bj 。我们就说,a的字典序比b的字典序小。

对1到n组成的所有排列进行字典序升序排列。然后取其中的第K个排列,就是第K个字典序排列。

 

在样例中,最终排列如下:

1 2 3 4 6 7 5

只有第4个位置是幸运数。


Input
单组测试数据
共一行,包含两个整数n和k(1≤n,k≤10^9)表示排列中的元素个数,和第K个字典序排列。
Output
如果k超过出了1到n所有排列数的种数,那么输出“-1”(没有引号)。
否则,输出题目要求结果。
Input示例
7 4
Output示例
1
System Message  (题目提供者)


题意:求N个数的排列A的第K小排列里,满足i和A[i]都仅有4和7组成的个数。

思路:1e9范围所以至多有13个数排列,其余的数都没有动过。那么用数位dp算出不动的部分有几个符合,再用逆康托展开算出动的部分有几个符合。前者计算时注意下前导0就行了。

# include <bits/stdc++.h>
using namespace std;
typedef long long LL;
LL f[]={1,1,2,6,24,120,720,5040,40320,362880,3628800,39916800,479001600,6227020800,87178291200};
int dp[11], a[11];
int dfs(int pos, int lim, int zero)
{
    if(pos == -1) return 1;
    if(!lim && !zero && dp[pos] != -1) return dp[pos];
    int up = lim?a[pos]:9, sum=0;
    for(int i=0; i<=up; ++i)
    {
        if(i!=4 && i!=7 && !(zero && i==0)) continue;
        sum += dfs(pos-1, lim&&i==up, zero &&i==0);
    }
    if(!lim && !zero) dp[pos] = sum;
    return sum;
}
int solve1(int x)
{
    int K=0;
    while(x)
    {
        a[K++] = x%10;
        x /= 10;
    }
    return dfs(K-1, 1, 1);
}
bool ok(int x)
{
    bool flag = true;
    while(x)
    {
        flag &= ((x%10==4)||(x%10==7));
        x /= 10;
    }
    return flag;
}
int solve2(int n, int x, int k)
{
    vector<int> v, a;
    for(int i=n-x+1; i<=n; ++i)
        v.push_back(i);
    for(int i=x; i>=1; --i)
    {
        int r = k%f[i-1];
        int t = k/f[i-1];
        k = r;
        sort(v.begin(),v.end());
        a.push_back(v[t]);
        v.erase(v.begin()+t);
    }
    int j=n-x+1, res=0;
    for(int i:a)
    {
        if(ok(j) && ok(i)) ++res;
        ++j;
    }
    return res;
}
int main()
{
    int n, k;
    scanf("%d%d",&n,&k);
    if(n<=12 && 1LL*k>f[n]) return 0*puts("-1");
    memset(dp, -1, sizeof(dp));
    int x=1;
    while(f[x]<k) ++x;
    int ans = solve1(n-x)-1;
    ans += solve2(n, x, k-1);
    printf("%d\n",ans);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值