题目链接
比赛的时候第一反应是一个背包dp,但很显然直接dp的话会寄掉。
比赛完了以后仔细想了想,发现这个题就是递归式的递推(自己起的名字)。
我们定义
d
f
s
(
i
,
s
u
m
)
dfs(i,sum)
dfs(i,sum)为下标为
0
−
i
0-i
0−i的数中,总和为
s
u
m
sum
sum的数的最小个数,则状态转移为:
d
f
s
(
i
,
s
u
m
)
=
m
i
n
(
d
f
s
(
k
−
1
,
s
u
m
−
a
r
r
[
k
]
)
+
1
)
dfs(i,sum)=min(dfs(k-1,sum-arr[k])+1)
dfs(i,sum)=min(dfs(k−1,sum−arr[k])+1),其中
k
k
k的上界通过右边界二分确定,下界通过前缀和确定。若不存在,则设为
i
n
f
inf
inf。
#include<iostream>
#include<vector>
#include<cmath>
#include<algorithm>
using namespace std;
typedef long long ll;
const ll N = 1e12 + 10;
const int inf = 0x3f3f3f3f;
int t, cnt;
ll n;
vector<ll>arr;
ll pre[60];
void m_init()
{
ll i , j;
i = 0;
while (1)
{
j = pow(2, i);
if (j <= N)
arr.push_back(j);
else
break;
++i;
}
i = 1, j = 1;
while (1)
{
i *= j;
if (i <= N)
arr.push_back(i);
else
break;
++j;
}
sort(arr.begin(), arr.end());
arr.erase(unique(arr.begin(), arr.end()), arr.end());
cnt = arr.size();
pre[0] = arr[0];
for (i = 1; i < cnt; ++i)
pre[i] = pre[i - 1] + arr[i];
}
int dfs(int i, ll sum) //0-i的数,总和为sum的最小数量
{
if (sum == 0)
return 0;
int left = 0, right = i, mid, j, k,res = inf;
while (left <= right)
{
mid = (left + right) >> 1;
if (arr[mid] <= sum)
left = mid + 1;
else
right = mid - 1;
}
for (j = right; j>=0&&j<=cnt-1&&pre[j] >= sum; --j)
res = min(res, dfs(j - 1, sum - arr[j]) + 1);
return res;
}
int main()
{
m_init();
int ans;
scanf("%d", &t);
while (t--)
{
scanf("%lld", &n);
ans = dfs(cnt-1, n);
if (ans == inf)
printf("%d\n", -1);
else
printf("%d\n", ans);
}
return 0;
}