0x10 题目链接
0x20 题目
0x21 Tag
状态压缩,枚举
0x22 题目描述
0x23 翻译
给出一个数n,是否能将其拆解成多个**(共k个)**2的幂次与阶乘的和,求k的最小值。
0x30 思路与算法
首先肯定有解,因为整数一定能被二进制表示。其中数据范围是
1
e
12
(
<
15
!
)
1e12(<15!)
1e12(<15!),该数字最多是15个阶乘的和
(
1
!
+
2
!
+
.
.
.
15
!
)
(1!+2!+...15!)
(1!+2!+...15!),我们想到可以用状态压缩的方法去解是否该加和中选用了某些阶乘(例如0…11100说明只选用了3!,4!,5!)。
枚举每一种状态,在原数的基础上减去这些阶乘的加和,对于差再拆解成二进制的形式(有几个1)。这样答案就为选用阶乘的数量
c
n
t
cnt
cnt与差二进制1的个数加和的最小值。
a
n
s
=
M
i
n
(
a
n
s
,
c
n
t
+
g
e
t
_
o
n
e
(
i
)
)
ans=Min(ans,cnt+get\_one(i))
ans=Min(ans,cnt+get_one(i))
0x40 代码
0x41 实现细节
fact[3]=6;
for(int i=4;i<=15;++i)
fact[i]=fact[i-1]*i;
这里预处理了阶乘的结果,需要注意的是:1,2与二进制相冲突,我们只保留二进制中的即可(从fact[3]开始预处理)。
for(int i=0;i<MAXS;++i)
{
int cnt=0ll;
ll tmp=n;
for(int j=0;j<=15;++j)
{
if((i>>j)&1)
{
tmp-=fact[j];
cnt++;
}
}
if(tmp>=0)
{
ans=min(ans,cnt+get_one(tmp));
}
}
这里枚举了所有的状态,需要注意的是我们被减数的结果不能小于0。在大于0的情况下,接着拆解差二进制的1即可。
0x42 完整代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXS=1<<15;
ll fact[22];
ll get_one(ll x)
{
ll cnt=0ll;
while(x)
{
if(x&1)
cnt++;
x>>=1;
}
return cnt;
}
int main()
{
fact[3]=6;
for(int i=4;i<=15;++i)
fact[i]=fact[i-1]*i;
int t;
cin>>t;
while(t--)
{
ll n;
cin>>n;
ll ans=0x3f3f3f3f3f3f3f3f;
for(int i=0;i<MAXS;++i)
{
int cnt=0ll;
ll tmp=n;
for(int j=0;j<=15;++j)
{
if((i>>j)&1)
{
tmp-=fact[j];
cnt++;
}
}
if(tmp>=0)
{
ans=min(ans,cnt+get_one(tmp));
}
}
cout<<ans<<endl;
}
return 0;
}
0x50 另
代码仅代表个人答案。如有错误,请多指正。=)