P1384 幸运数与排列 题解
Harry_Hedwig · · 题解
先看题。
0x00 思路
一个数是幸运数当且仅当这个数仅由
4 和7 构成,比如47 ,744 ,4747 。询问在
1 到n 的全排列中字典序第k 小的排列中,有多少个幸运数在排列中的位置编号也是幸运数。
我们注意到,在完成对幸运数记数的问题前,我们首先对这个数列有一个逆康托展开,所以这个问题可以分成两步走。
0x01 Step 1:逆康托展开
很明显,逆康托展开就是个板子,所以直接开开森森打代码就好了。
但是,逆康托展开需要知道
!这…… unsigned long long 都存不下啊……高精度?也感觉不对。
现在我们的思路卡住了,所以我们可以碰碰运气,看在 unsigned long long 范围下能逆康托展开多少个数(骗多少分)。
| 阶乘结果 | |
|---|---|
诶诶诶停停停,根据数据范围,
而关于 偷懒不搞特殊,可以将 可以少写一个 。if 语句
至此,第一步顺利解决了。
0x02 Step 2:幸运数
既然我们已经发现除开最后
接着,你会发现只有一位的幸运数有
这个事情解释起来很简单。我们可以把
由于整个数列只有后
注意!
当
当
当
但是千万不要忘了
关于和
特别地,
最后还有被全排列过的
Code
#include<bits/stdc++.h>
using namespace std;
long long val_fac[20],val_pow[10];
long long step_1[20];
void decontor(int n,int p)//逆康托展开
{
vector<int>mus,ans;
int now=p-1,i;
for(i=1;i<=n;i++)
mus.push_back(i);
for(i=n;i>=1;i--)
{
int l=now/val_fac[i-1];
now=now%val_fac[i-1];
ans.push_back(mus[l]);
mus.erase(mus.begin()+l);
}
for(i=0;i<n;i++)
step_1[i+1]=ans[i];
}
int Digit(int num)//找位数
{
int sum=0;
while(num)
{
num/=10;
sum++;
}
return sum;
}
void change(long long &n)//改成下一个幸运数
{
long long ans=n;
int sum=0;
while(ans%10==7)//即上文说的找法
{
ans/=10;
n-=3*(int)pow(10,sum);
sum++;
}
n+=3*(int)pow(10,sum);
}
bool check(int val)//检查是否满足幸运数的条件
{
while(val)
{
if(val%10!=4&&val%10!=7)
return 0;
val/=10;
}
return 1;
}
int main()
{
int p,i,n;
long long ans=0;
scanf("%d %d",&n,&p);
val_fac[0]=1;
val_pow[0]=1;
for(i=1;i<=15;i++)//逆康托展开前置
val_fac[i]=val_fac[i-1]*i;
int dig=Digit(n);//求位数
for(i=1;i<=dig;i++)//预处理
val_pow[i]=val_pow[i-1]*2;
if(p>val_fac[min(n,13)])//判断-1
{
printf("-1");
return 0;
}
decontor(min(n,13)/*最多13位,否则就不行了*/,p);
if(n>13)
{
if(n>19)//不会影响到任何低于dig位的幸运数
for(i=1;i<dig;i++)
ans+=val_pow[i];
if(n>16&&n<20)//会影响到7
ans++;
for(i=1;i<=13;i++)//在逆康托展开中使用的是1~13,需要加回来
step_1[i]=step_1[i]+n-13;
long long now=0,biggest=0;
for(i=1;i<=dig;i++)//找到dig位的幸运数的最小值和最大值
now*=10,now+=4,biggest*=10,biggest+=7;
if(n-13>=biggest)//不会影响到dig位的所有幸运数
ans+=val_pow[dig],now=n-12/*防止进入循环*/;
while(now<=n-13&&now!=biggest)
{
ans++;
change(now);
}
if(now<=n-13&&now==biggest)
ans++;
for(i=1;i<=13;i++)//暴力查找
{
if(check(n+i-13)&&check(step_1[i]))
ans++;
}
}
else
{
if(step_1[4]==4||step_1[4]==7)
ans++;
if(step_1[7]==4||step_1[7]==7)
ans++;
}
printf("%lld",ans);
return 0;
}
话说康托展开太容易打错了QaQ