比得喜欢幸运数字。这里所说的幸运数字是由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;
}