题目描述
一个 $n$ 行的代码出了bug,每行都可能会产生这个bug。你要通过输出调试,在其中加入printf来判断bug出现的位置。运行一次程序的时间为 $r$ ,加入一条printf的时间为 $p$ ,求最坏情况下调出程序的最短时间。
输入
输入包括一行三个整数:
n(1≤n≤10^6),代码行的数目;
r(1≤r≤10^9),编译和运行程序直到它崩溃的时间量;
p(1≤p≤10^9),增加单个的printf行所花费的时间。
输出
输出的最坏情况使用最优策略找到崩溃行的时间。
样例输入
16 1 10
样例输出
44
题解
数论+记忆化搜索
看题第一眼dp,设 $f[i]$ 表示 $i$ 行代码最坏情况下的最短时间。那么枚举添加的printf语句数,可以得出dp方程:$f[i]=f[\lceil\frac i{j+1}\rceil]+jp+r$ 。
考虑优化:$\lceil\frac ni\rceil$ 和下取整一样最多只有 $O(\sqrt n)$ 个值。方法:从大到小枚举 $i$ ,令 $last=\lceil\frac n{\lceil\frac ni\rceil}\rceil$ ,则 $last$ 就是最后一个满足 $\lceil\frac nj\rceil=\lceil\frac ni\rceil$ 的 $j$ 。由于要让方程中的 $j$ 尽量小,因此使用 $last$ 转移。下一次令 $i=last-1$ 即可。
但是这样 $O(n\sqrt n)$ 的时间复杂度还是过不了,考虑进一步优化:只有一个询问,因此无需知道大多数无用的 $f$ 值。使用记忆化搜索,这样更新的结果就只有 $f[\lceil\frac ni\rceil]$ 了。
使用微积分知识可以证得时间复杂度为 $O(n^{\frac 34})$ (和杜教筛相同)
#include <cstdio> #include <algorithm> using namespace std; typedef long long ll; ll f[1000010] , r , p; inline int cdiv(int x , int y) { return (x + y - 1) / y; } ll solve(int n) { if(n == 1) return 0; if(f[n]) return f[n]; int i , j; f[n] = 1ll << 62; for(i = n ; i != 1 ; i = j - 1) j = cdiv(n , cdiv(n , i)) , f[n] = min(f[n] , solve(cdiv(n , i)) + (j - 1) * p + r); return f[n]; } int main() { int n; scanf("%d%lld%lld" , &n , &r , &p); printf("%lld\n" , solve(n)); return 0; }
原文地址:https://www.cnblogs.com/GXZlegend/p/8135724.html