题目描述
难度分:1400
输入n,k,a,b(1≤n,k,a,b≤2×109)。
有两种操作:
- 花费a,把n减一。
- 如果n是k的倍数,花费b,把n变成nk。
输出把n变成1的最小总花费。
输入样例1
9
2
3
1
输出样例1
6
输入样例2
5
5
2
20
输出样例2
8
输入样例3
19
3
4
2
输出样例3
12
算法
记忆化搜索
首先我们过滤一个情况,如果k=1,肯定不会选择除法操作,因为nk=n,没有任何意义。所以答案就是将n每次减1得到1的代价,为(n−1)a。
状态定义
dp[rest]表示将rest变为1的最小代价,按这个定义,答案就应该是dp[n]。
状态转移
分为两种情况:
- 如果rest是k的倍数,要么通过减1的方式让n变成restk,要么通过除以k的方式让rest变成restk。状态转移方程为dp[rest]=min((rest−restk)a+dp[restk],b+dp[restk])。
- 否则就只有减1操作,如果rest≤k,就减去k−1变成1。否则减去余数rest%k,让rest变成k的倍数了再做下一步决策。
复杂度分析
时间复杂度
单次转移的时间复杂度是O(1)的,虽然在DP
的过程中会存在减法操作,但是经过一次减法操作后立马就会使rest变成k的倍数,这样就可以进行除法操作,状态数量应该是O(logkn)级别。因此,整个算法的时间复杂度为O(logkn)。
空间复杂度
额外空间复杂度就是DP
过程中状态的存储消耗,为O(logkn)。
python代码
from functools import lru_cache
n = int(input())
k = int(input())
a = int(input())
b = int(input())
if k == 1:
print((n - 1)*a)
exit(0)
inf = int(1e18)
@lru_cache(None)
def dfs(rest: int) -> int:
if rest == 1:
return 0
res = inf
if rest % k == 0:
res = min(res, min(a*(rest - rest//k) + dfs(rest//k), b + dfs(rest//k)))
else:
r = rest % k
if rest > k:
res = min(res, a*r + dfs(rest - r))
else:
res = min(res, (rest - 1)*a)
return res
ans = dfs(n)
dfs.cache_clear()
print(ans)