目录
1.题目
题目描述
现在给出一个正整数 n ,找到最小值 k 使得 n 可以表示为k个不同的强大的数字的和,或者说没有这样的数字 k.
强大的数字:2的幂或者是任意数阶乘。即2d 或 x! (x表示任意的非负数)
例:1,4,6 都是强大的数字,因为1=1!,4=22 和 6 =3! ,而7,10,18不是。
输入
每个测试包括多个测试样例,每一行包括测试用例的数量 T (1≤T≤100)
一个测试用例只包括一行,包括一个整数 n (1≤n≤1012)
输出
对于每个测试用例,将最小值 k 输出。
如果不存在k个强大的数字的和为n,那么输出-1.
样例输入
4 7 11 240 17179869184
样例输出
2 3 4 1
2.解题思路
题意:给定你一个n,问你最少用几个不同的强大的数加起来的和等于n,输出最少使用的个数k。
解题思路:
既然是用数个强大的数加起来,而强大的数分为两类,一类是阶乘数,一类是2的幂,那我们需要从这两类数里面选出合适的数,那是先从阶乘数那里选,还是先从2的幂那里选,还是说两类里随机选取?
我们的思路是先选阶乘数。
因为每个阶乘数对应的一个十进制数的大小是一定的,且是不规律的。而2的幂,因为任何十进制数都可以转化为一个二进制数,即转化为一组2的幂,所以反之可用一组2的幂去组成任意一个十进制数。那就是说,先选了任意个阶乘数,我都能用一组2的幂去构成(n-任意个阶乘数的和),且保证了每一个强大的数都不同。
如果先从2的幂那里选,然后再从阶乘数那里选出一组数来构成(n-2的幂的和),因为每个阶乘数对应的十进制数不规律,所组成的和不一定能刚好构成(n-2的幂的和),这就不合题意了,若再从2的幂那里挑则不如先选阶乘数方便,故不用这种选法。
如果在这两类里随机选择,则可能还会出现第二种选法的问题,且不方便,故也不用这种选法。
确定了先选阶乘数,那下一步我们如何确保,每一种选择阶乘数的情况我们都考虑到呢?
我们采用二进制枚举的方式来枚举所有可能的选择阶乘数的情况。(1代表选择对应的阶乘,0代表不选,一串01序列即可表示每种对应阶乘选没选的情况,n个阶乘就用n个二进制来枚举,枚举从都不选到都选,即全为0到全为1)
在每一次确定选哪些阶乘数后,再将剩下的数转换为二进制,并将其使用的2的幂分离出来,记录每一次选择。
在将所有可能的选择阶乘数的情况枚举的时候,每一次比较更新选用强大的数的个数。
最后得到k值并输出。
3.代码思路
cnt:用于存储每次枚举时所用到的强大的数字的个数
ans:用于比较每次枚举时得到的cnt,从而获得最终最小的k值
sum:用于存储该次枚举的阶乘数的总和
jcs:一个用于存储每个阶乘对应阶乘数的数组
t:用n减去阶乘数总和后剩下的值
先将需要用到的阶乘数进行一个预处理,存入一个数组中。在完成输入后,将阶乘数(本次最大为14)可能的情况,从0到-1枚举枚举1遍,将每次选择的阶乘数都分辨出来,并加到sum上,同时,cnt++,如果sum大于n就break(够大就没必要接着枚举了),让t=n-sum,再将t对应的各个2的幂分离出来,同时cnt++,然后同ans比较,若cnt比ans小,则更新ans.将所有情况枚举完后,此时的ans为最小的k值,将其输出即可。
4.代码实现
#include<iostream>
using namespace std;
const int N=1e5;
long long sum,n,t;
int cnt,ans;
long long jcs[20];
void jc()
{
jcs[1]=1;
for(int i=2;i<=14;i++) jcs[i]=jcs[i-1]*i;
}
int main()
{
int T;
cin >>T;
jc();///阶乘数预处理
while(T--)
{
cin >>n;
ans=N;
for(int i=0;i<(1<<14);i++)
{
sum=0;
cnt=0;
for(int j=0;j<14;j++)
{
if((i>>j)&1) cnt++,sum+=jcs[j+1];
}
if(sum>n) break;
t=n-sum;
for(int j=0;j<64;j++)
{
if((t>>j)&1) cnt++;
}
ans=min(ans,cnt);
}
cout <<ans <<endl;
}
return 0;
}