动态规划 学习笔记

动态规划

通过把原问题分解成若干个子问题(所有这些问题在一定意 义上答案固定),再利用已知的子问题答案依次计算未知问题的答案,最终得到原问题答案的方式,称之为动态规划(Dynamic Programming,简称 DP)。

动态规划利用若干条信息来标识一个状态,按照一定的顺序 依次计算这些状态对应的答案,每一个状态的计算都利用上之前 计算过的状态(类似递推的由已知得到未知),由此降低问题本身的复杂程度。

动态规划的状态可以笼统的解释为“问题所在的局面”。状态的定义是明确的,具有无后效性以及最优子结构性质。

Luogu P2842 纸币问题 1

fif_i 表示总金额为 ii 时最少用多少张纸币。

如果 iaj0i-a_j \ge 0,则可以先用 fiajf_{i-a_j} 张纸币凑齐 iaji-a_j 元,再用一张 aja_j 元的纸币,即可凑齐 ii 元。

可以得到状态转移方程:

1
if(i-a[j]>=0) f[i]=min(f[i],f[i-a[j]]+1);

Luogu P2840 纸币问题 2

fif_i 表示总金额为 ii 时有多少种方式。

如果 iaj0i-a_j \ge 0,则有 fiajf_{i-a_j} 种方式凑齐 iaji-a_j 元,再用一张 aja_j 元的纸币来凑齐 ii 元。

可以得到状态转移方程:

1
2
3
for(int i=1;i<=w;i++)
for(int j=1;j<=n;j++)
if(i-a[j]>=0) f[i]+=f[i-a[j]],f[i]%=mod;

Luogu P2834 纸币问题 3

与上一道题相似,但是要求硬币组合的数量。

对于上一道题,我们要保证 fiajf_{i-a_j} 是最终答案,因此我们先枚举 ii 再枚举 jj

但是这道题,我们要保证 fiajf_{i-a_j} 是由编号为 1j11 \sim j-1 这几种纸币拼出来的方案数,不能有其它纸币参与。

因此我们先枚举 jj 再枚举 ii 即可。

1
2
3
for(int j=1;j<=n;j++)
for(int i=1;i<=w;i++)
if(i-a[j]>=0) f[i]+=f[i-a[j]],f[i]%=mod;

Luogu P1216 数字三角形

fi,jf_{i,j} 记录到达点 (i,j)(i,j) 处的最大路径。

而点 (i,j)(i,j) 可能由 (i1,j)(i-1,j)(i1,j1)(i-1,j-1) 直接向下一步走到,于是推出转移方程:

1
2
3
4
dp[1][1]=a[1][1];
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++)
f[i][j]=a[i][j]+max(f[i-1][j-1],f[i-1][j]);

Luogu P1048 采药

01 背包模板题。

假设 viv_i 为物体的体积,cic_i 为物体的价值。将这 nn 个物体放入一个体积为 VV 的背包中。

假设 fi,jf_{i,j} 表示将前 ii 个物品放入容量为 jj 的背包中,最大价值是多少。

则有转移方程:

fi,j={max{fi1,jvi+ci,fi1,j}(jvi)fi1,jotherwisef_{i,j}=\left \{ \begin{array}{ll} \max\{f_{i-1,j-v_i}+c_i,f_{i-1,j}\} & (j\ge v_i)\\ f_{i-1,j} & otherwise \end{array} \right.

因为有两种选择:选或不选。

若选第 ii 个物体,则前 i1i-1 个物体体积要小于 jwij-w_i,得出 fi1,jvi+cif_{i-1,j-v_i}+c_i。若不选第 ii 个物体,则等价于只选前 i1i-1 个物体,得出 fi1,jf_{i-1,j}

看转移方程。第一维都是 i1i-1 ,于是我们想办法将第一维消掉。

再看第二维,计算 fi,jf_{i,j} 时,第二维的值都小于等于 jj。要想将第一维消掉,则需要保证,在更新 fi,jf_{i,j} 时,fi,jvif_{i,j-v_i} 没有被更新过。

于是我们可以省掉第一维,然后从大到小遍历 jj

1
2
3
for(int i=1;i<=n;i++)
for(int j=V;k>=v[i];j--)
f[j]=max(f[j],f[j-v[i]]+c[i]);

Luogu P1616 疯狂的采药

完全背包模板题。

与上一题相比,这一题每个物品都有无限个。

上一题倒着遍历,其实还为了保证,更新 fif_i 时,fjvif_{j-v_i} 没有将第 ii 个物品算进去。因为上一题每件物品只有一个。

而这道题每个物品有无数个,我们恰好要保证:更新 fif_i 时,fjvif_{j-v_i} 已经将第 ii 个物品算进去。

因此正着遍历即可。

1
2
3
for(int i=1;i<=n;i++)
for(int j=v[i];k<=V;j++)
f[j]=max(f[j],f[j-v[i]]+c[i]);
------待更新------