动态规划深度训练经典问题
问题一: 给你一个长度为 50 的数字串,问你有多少个子序列构成的数字可以被 3 整除,答案对 1e9+7 取模。
输入描述: 输入一个字符串,由数字构成,长度小于等于 50
输出描述: 输出一个整数
不难发现就是要判断是否对某一位数选还是不选 的问题,应该是用 DP 解决。我们从三种情况来考虑这个问题。
一维DP:
(1)一维 DP 很多同学觉得这个用一维是不可能的,但是如果大家学的通透,完全可以实现,且不难理解。 其实无论数字多大,对 3 取余只有三个结果 0,1,2。我们用 dp[0]、dp[1]、dp[2]分别存储 除 3 得到的余数为 0,1,2 的数量,能不能每增加一个数就更新一遍数组。 下面的式子是否能看懂:
一.余数为 0:
dp[0]=dp[0]+dp[0]+1;
dp[1]=dp[1]+dp[1];
dp[2]=dp[2]+dp[2];
二.余数为 1:
dp[0]=dp[0]+dp[2];
//如果新的余数为 1,原来余数为 2 的就变成了能被 3 整除了, 就要把 dp[2]的加到原来 dp[0]里面,能理解了吧?
dp[1]=dp[1]+dp[0]+1;
dp[2]=dp[2]+dp[1];
三.余数为 2:
dp[0]=dp[0]+dp[1];
dp[1]=dp[1]+dp[2];
dp[2]=dp[2]+dp[0]+1;
二维 DP
如果是二维 dp[i][j],i 很容易理解,应该是选前 i 个数。
仍然以 132 举例,当取第 1 个数字 1 的时候,其实余数为 1,这个 1 需要保存,因为后面有用
所以 j 应该是对 3 取模后的余数, 那么 0 <= j <= 2。
剩下的就是递推公式了。
(1)如果第 i 个数不取,那么
dp[i][j]=dp[i-1][j];
(2)如果取第 i 个数,首先是包含 dp[i-1][j]个,132 肯定包含 13 的所有个数。
再就要看看 这位数对 3 求模和前面的余数加起来的情况了。
若求得余数为 1,那么当前这个余数,与前 i-1 位余数为 1 的情况,可以组成 2,与前面余数为 0 的情况又可以组成 1.怎么表达呢?
dp[i][j] = (dp[i - 1][j] + dp[i - 1][(j + 3 - 当前余数) % 3]) % Mod;
三维 DP
三维肯定是 dp[i][j][k]:表示第 i 位取/不取时余数为 k 时的子序列个数,j 取 0 或 1,0 <= k<= 2。
(1)不取 i 位,那应该是前面取或不取的和
dp[i][0][k] = dp[i-1][0][k] + dp[i-1][1][k];
(2)取第 i 位,这位的余数情况,要根据自身位数和前面余数的关系确定。
dp[i][1][k] = dp[i-1][0][p] + dp[i-1][1][p] ;
p 应该满足:
(p * 10 % 3 + a[i])% 3 = k;
暴力 p 的范围[0,2],满足条件时就转移 。
你们明白了吗?