dp优化--连续递推的时间和空间优化

复习到k阶fibonacci数的时候,老师讲这是比较难的,然而不就是dp优化吗。
通过化简合并式子减少时间复杂度。
通过对前面无用的状态的覆盖来减少空间复杂度。(若要查询任意小于等于m的k阶fibonacci数这样的覆盖是不行的,必须全部记录下来。同01背包的一维的滚动数组。)
顺便复习一下模板计数dp.

题目

k阶斐波那契数列

题意

  • 若m\(\gt\)k-1, \(f_m = f_{m-1}+f_{m-2}+\ldots+f_{m-k}\)
  • 若m == k-1, \(f_m\) = 1
  • 若m < k-1, \(f_m\) = 0
  • 若k < 2 || m < 0, 不存在

题解

  • 方法一:
    就是按照递推式写就行了

  • 方法二:
    递归,边界条件为m\(\le\)k-1.

代码

直接把老师的代码复制过来了

1
2
3
4
5
6
7
8
9
10
11
12
int Fib ( int m, int k)
{
if ( k < 2 || m< 0) return -1;
if ( m < k-1) return 0;
else if ( m == k-1 ) return 1;
else {
sum = 0;
for( i = 1; i <= k; i++)
sum += Fib( m - i, k);
return sum;
}
}

优化

时间复杂度的优化:
当m\(\ge\)k-1时:
\(f_m = f_{m-1}+f_{m-2}+\ldots+f_{m-k}\),发现后面的k-1项是\(f_{m-1}\)的展开的前k-1项。于是从后面借一项\(f_{m-(k+1)}\).
可化简为:
\(f_m = 2*f_{m-1}-f_{m-(k+1)}\)

空间复杂度的优化:
由于递推式,第m项只和前k项有关,因此k项前面的可以覆盖。使用循环的数组来管理(模数)。

代码

递归版:

1
2
3
4
5
6
7
8
int Fib ( int m, int k)
{
if ( k < 2 || m< 0) return -1;
if ( m < k-1) return 0;
else if( m == k-1 || m == k ) return 1;
else
return 2*Fib( m-1, k) – Fib( m-k-1, k);
}

递推版:通项公式未化简,仅优化空间的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int Fib (int k, int m)
{
if (k<2 || m<0) return -1;
if (m<k-1) return 0;
else if (m == k-1) return 1;
else{
int temp[k];
for(i=0; i<=k-2; i++) temp[i] = 0;
temp[k-1] = 1;
for(i=k; i<=m; i++){
sum = 0;
for(j=0; j<k; j++) sum += temp[j];
temp[i%k] = sum;
}
r=temp[m%k];
return r;
}
}

优化了时间和空间的递推版:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int Fib(int k, int m)
{
if (k<2 || m<0) return -1;
if (m<k-1) return 0;
else if ((m == k-1) || (m == k)) return 1;
else {
int temp[k];
for(i=1; i<=k-2; i++) temp[i] = 0;
temp[0] = temp[k-1] = 1; s = 0;
for(i=k+1; i<=m; i++){
sum = 2*temp[(i-1)%k]-s;
s = temp[i%k];//下一个需要减去的项
temp[i%k] = sum;
}
r=temp[m%k];
return r;
}
}

题目

有n种物品,第i种物品有\(a_i\)个。不同的物品同类不可区分,不同类的可区分。若在这些物品中取m个。有多少种取法?答案模M

题意

例如:
n = m = 3
{1, 2, 3}
3 = 0+0+3 = 0+1+2 = 0+2+1 = 1+0+2 = 1+1+1 = 1+2+0

题解

  • 设计状态:dp[i+1][j]:从前面i种物品中取j件的方案数
  • 状态转移方程:dp[i+1][j] = \(\sum_{k=0}^{min(a[i], j)} dp[i][j-k]\).表示从i种物品中取出k件,其他的从前面的i-1种物品中选j-k件
  • 边界: dp[i][0] = 1;
  • 结果:dp[n][m], 下标从0开始

优化状态转移方程

若按照前面的方程进行转移,那么时间复杂度是O\((n*m^2)\).

在j-1\(\ge\)a[i]的条件下
\(\sum_{k=0}^{min(a[i], j)} dp[i][j-k]\) = \(\sum_{k=0}^{min(j-1, a[i])} dp[i+1][j-1]+dp[i][j]-dp[i][j-1-a_i]\)
时间复杂度降为O(nm).

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <bits/stdc++.h>
using namespace std;
int n, m;
int a[MAX_N];
int dp[MAX_N][MAX_M];
void solve(){
for(int i=0; i<=n; i++) dp[i][0] = 1;
for(int i=0; i<n; i++){
for(int j=0; j<=m; j++){
if(j-1>=a[i]){
dp[i+1][j] = (dp[i+1][j-1]+dp[i][j]-dp[i][j-1-a[i]]+M)%M;
}
else {
dp[i+1][j] = (dp[i+1][j-1]+dp[i][j])%M;
}
}
}
printf("%d\n", dp[n][m]);
}
int main()
{
cout << "Hello world!" << endl;
return 0;
}

题目

划分数,计数dp的入门题,无优化

题意

  • 给定一个数n, 将其划分成m个非负整数,求方案数,最后模M
  • 例如:n = 4, m=3:4 = 1+1+2 = 1+3 = 2+2

题解

  • 状态设计:dp[i][j]:j划分成i的方案数
  • 状态转移: dp[i][j] = dp[i][j-i]+dp[i-1][j].实际的意义:若将n划分成m, 如果对于每个\(a_i\)>0,那么{\(a_i-1\)}就对应n-m的m划分
    若存在\(a_i == 0\),那么就对应了n-1的m划分
  • 边界:dp[0][0] = 1;
  • 结果:dp[m][n]

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int n, m;
int dp[MAX_M][MAX_N];
void solve(){
dp[0][0] = 1;
for(int i=1; i<=m; i++){
for(int j=0; j<=n; j++){
if(j-i>=0){
dp[i][j] = (dp[i-1][j]+dp[i][j-i])%M;
}
else{
dp[i][j] = dp[i-1][j];
}
}
}
}

未解决的问题

困成傻逼,明天再校验吧emmmmm
2017年11月6日01:13:07
果然是有错误的,
另外突然还想到了大霸计算超几何分布其实也是一种计算的优化。
还有树状数组可以优化的例子。
待更新吧。。。。。。

文章目录
  1. 1. 题目
    1. 1.1. 题意
    2. 1.2. 题解
    3. 1.3. 代码
    4. 1.4. 优化
    5. 1.5. 代码
  2. 2. 题目
    1. 2.1. 题意
    2. 2.2. 题解
    3. 2.3. 优化状态转移方程
    4. 2.4. 代码
  3. 3. 题目
    1. 3.1. 题意
    2. 3.2. 题解
    3. 3.3. 代码
  4. 4. 未解决的问题
{{ live2d() }}