几天前我编码了一个DP问题,我想将递归解决方案减少为更优化的解决方案,即一次又一次减少同一函数的多次调用
n <10 ^ 9和k <10 ^ 3
long long fibo(long long n,long long k){
if(n==k){
return 2;
}
else if(k==1){
return 2*n;
}
else if(k==0){
return 1;
}
else if(k==2){
return 2*(n-1)*(n-1);
}
else {
return (fibo(n-1,k) + fibo(n-2,k-1) + fibo(n-1,k-1));
}
}
迭代方法
long long fibo[1010][1010]={-1};
long long fib(long long n,long long k){
if(n==k){
return 2;
}
else if(k==1){
return 2*n;
}
else if(k==0){
return 0;
}
else if(k==2){
return 2*(n-1)*(n-1);
}
if(fibo[n][k]!=-1){
return fibo[n][k];
}
else {
return fibo[n][k]= (fib(n-1,k) + fib(n-2,k-1) + fib(n-1,k-1));
}
}
答案 0 :(得分:3)
编辑:我添加了性能测试的结果和fibo(n, k)
的粗略数学估计。
我想将递归解决方案简化为更优化的解决方案
以下代码是我的答案。
该代码由动态编程实现。
该算法的细节在下面说明。
与原始代码的匹配测试为here,而性能测试为here。性能得到了很好的改善。 fibo(40, 10)
的计算时间如下:
原始:24691879微秒(〜24秒)
DP。版本:33微秒
long long fibo_DP(long long n, long long k)
{
if(k<0 || n<k){
throw std::logic_error("Value of fibo is ill-defined for (k<0 || n<k).");
}
// boundary
if(n==k){
return 2;
}
else if(k==1){
return 2*n;
}
else if(k==0){
return 1;
}
else if(k==2){
return 2*(n-1)*(n-1);
}
std::vector<long long> memo(k+2, 2); // diagonal line.
const auto n_last = (n-k);
for(auto i = 1U; i<=n_last; ++i)
{
// modifying first two values
memo[0] = 1; // (i ,0)
memo[1] = 2*(i+1); // (i+1,1)
// throwing away the value of (k, k) by sliding
for(auto l = memo.size()-2; l>3U; --l){
memo[l] = memo[l-1];
}
// interrupting the value of (i+2, 2)
memo[3] = 2*(i+1)*(i+1);
// do local updates.
for(auto j = 4U; j<memo.size(); ++j)
{
const auto sum = memo[j-2] + memo[j-1] + memo[j];
memo[j-2] = memo[j-1];
memo[j-1] = memo[j];
memo[j] = sum;
}
}
return memo.back();
}
首先,我认为只有并且k>=0 && n>=k
时,这个问题才是明确定义的。
为了明确起见,请考虑2维矩阵,其中(n, k)
处的值等于fibo(n, k)
。
然后是您的顺序总和
return (fibo(n-1,k) + fibo(n-2,k-1) + fibo(n-1,k-1));
由下图表示:
要终止该和,我们需要一个上边界和一个左边界,其中给出初始值。
OTOH,仅为n==k
,k==0, 1, 2
,下图上的蓝色框定义了初始值:
如果(n, k)
位于n<k
的区域(1)上,则由于没有给出上限,上述顺序求和将成为无限循环。
并且,如果(n, k)
位于k<0 && n>=k
的区域(2)上,则由于没有给出左边界,它再次成为无限循环。
因此,当且仅当fibo(n, k)
并且
我们在下面的讨论中假设k>=0 && n>=k
。
回到第一个求和图,我们可以在计算k>=0 && n>=k
时找到以下算法。
首先,我们构造一个包含fibo(n, k)
元素(1,0),(2,1),(2,2),(3,2)的数组>,(3,3),(4,4),(5,5)…(k,k),下图上的黑框:
接下来,我们通过以下方式开始递归求和。
在每个步骤中,黑盒还是k+2
元素,它们存储在数组中。
此过程是本地过程,从内存访问的角度来看将是有效的:
如果我们已经计算出(k + 1,k),则我们得到的值为(1,0),(2,1),(3,2),(4 ,3),(5,4),(6,5),...,(k,k-1) ,(k,k),(k + 1,k)。 然后我们转到下一条对角线平行线。 修改数组的前两个值,在第四个元素处中断(4,2)的值,丢弃(k,k)的值,我们构造下一个初始数组(2,0),(3、1),(3、2),(4、2),(4、3),(5、4),...,( k-1,k-2),(k,k-1),(k + 1,k)。
我们再次以以前的方式开始顺序求和。
重复此过程,最后得到k+2
的值。
对于fibo(n, k)
与fibo(k+1, k)
,我们得到以下递归方程:
我们可以求解该方程式并得到以下结果:
接下来,对于k>=3
和fibo(k+2, k)
,我们得到以下递归方程:
我们可以再次求解该方程式,并获得以下结果:
通过这种方式,我们可以将k>=3
的顺序估计为fibo(k+p,k)
,
即k^p
。
不幸的是,fibo(n,k) ~ k^(n-k)
对于n~10^9 and k~10^3
来说太大了,将返回无意义的值。