问题描述
如果一个自然数N的K进制表示中任意的相邻的两位都不是相邻的数字,那么我们就说这个数是K好数。求L位K进制数中K好数的数目。例如K = 4,L = 2的时候,所有K好数为11、13、20、22、30、31、33 共7个。由于这个数目很大,请你输出它对1000000007取模后的值。输入格式
输入包含两个正整数,K和L。
输出格式
输出一个整数,表示答案对1000000007取模后的值。
样例输入
4 2
样例输出
7
数据规模与约定
对于30%的数据,KL <= 106;
对于50%的数据,K <= 16, L <= 10;
对于100%的数据,1 <= K,L <= 100。
题目分析:
这是一道典型的动态规划入门题,相比于算法设计,这道题首先需要的是理解题意,同时需要一定的进制转换知识基础。看了许多版本的博客题解,虽然对算法的核心都有解释或备注,但总觉得对入门者,尤其是动态规划入门者来说不够友好。所以决定自己写一篇题解。
我们以题目所给的样例数据K=4,L=2入手,也就是求2位的4进制数中,任意相邻两位不是相邻数字的4进制数的个数。
首先我们来回顾一下十进制数转换为四进制数的方法—除4取余法,即十进制数除4,余数为权位上的数,得到的商值继续除4,依此步骤继续向下运算直到商为0为止。基础知识,不再赘述,简单列出1-15以便接下来的分析。
0 —— 1 8 —— 20 √
1 —— 1 9 —— 21
2 —— 2 10 —— 22 √
3 —— 3 11 —— 23
4 —— 10 12 —— 30 √
5 —— 11 √ 13 —— 31
6 —— 12 14 —— 32 √
7 —— 13 √ 15 —— 33 √
算法分析:
观察位数为2的4进制数字,根据题意,我们要找出 任意相邻两位不是相邻数字的,也就是上方√出的数字,共有7个。我们知道,四进制是以4为底数的进位制,以 0,1,2,3 四个数字表示任何实数。假定从高位开始寻找,最高位数字必定也是0,1,2,3,要找到满足题意的数字,比如十位数为1时,就要去寻找个位数不为0或1的那些。
这里引进一个二维数组dp[ i ][ j ],1<i<=L,0<j<K, i 表示位数,j 表示最高位的数字。在这里的样例中具化成1<i<=2,0<j<4。根据观察可以知道,当位数为1时,除了0之外的1,2,3都满足题意。于是可以将i=1时的j都初始化为1,表示满足题意的情况数,方便下面的计算。那么当十位数为1时,个位数可以是1,3两种情况;十位数为2时,个位数可以是0,2两种情况;十位数为3时,个位数可以是0,1,3三种情况。将这些结论填入下表中。
0 | 1 | 2 | 3 | |
1 | 1 | 1 | 1 | 1 |
2 | 3 | 2 | 2 | 3 |
此时用sum计数,由于j是最高位的数字,不能为0,那么从1开始循环遍历到3,满足题意的情况共有2+2+3=7种,与我们题目分析时得出的结论一致。
这里可能有人有疑问,为什么将i=1时的j都初始化为1呢,当位数为1时,满足题意的不是j=1,2,3三种情况吗,0不是不满足吗,为什么j=0也初始化为1呢。其实细想一下,位数为1时,我们计数时j仍然是从1开始循环遍历到3的,满足题意的1,2,3都进行了计数,也就是说i=1时,j=0的值对于结果是没有影响的。但是再仔细思考,当位数为2的时候呢?如果我们把i=1时,j=0的值初始化为了0,那么比如当十位数为2时,个位数可以是0,2两种情况,最终的计数却只有1种,这对于后面所有的计算都是有偏差的,所以我们把i=1时的j都初始化为1。也就是为了上面所说过的“表示满足题意的情况数,方便下面的计算”。
到这里的算法,应该都很清晰明了了。那么我们抛开样例,延伸地去考虑,当位数为3时,同样的,从高位开始寻找,出现一个问题是,当百位为2时,十位数可以是0,2两种情况,但是当十位数为0时的情况,我们刚刚并没有计算,因为我们人为地从样例具化分析知道十位数不能为0。这个问题和刚刚的疑问是一个道理,既然十位数为0的情况在刚刚的计算中对最终结果没有影响,但在现在的计算中又需要用到,那么为什么不进行计算呢?所以我们这里把i=2,j=0的情况数3也补充回刚刚的表格中去。同理,当位数为L时,我们也需要用到i=2,j=0的情况数,所以干脆都进行计算。
在这样的过程中,其实我们已经引出了动态规划的重要概念,也就是—每次决策依赖于当前状态,又随即引起状态的转移。现在再来理解动态规划就比较容易了。一般来说,能采用动态规划求解的问题的一般要具有3个性质:
1、最优化原理:如果问题的最优解所包含的子问题的解也是最优的,就称该问题具有最优子结构,即满足最优化原理。
在本题中,我们按照从最高位到最低位不断判断直到找出所有情况数的过程,也就是利用了问题具有最优子结构。
2、 无后效性:即某阶段状态一旦确定,就不受这个状态以后决策的影响。也就是说,某状态以后的过程不会影响以前的状态,只与当前状态有关。
在本题中,当最高位确定后,我们只需要不断向下判断,不会再改变已经确定了的位上的情况数,这就是无后效性。
3、有重叠子问题:即子问题之间是不独立的,一个子问题在下一阶段决策中可能被多次使用到。(该性质并不是动态规划适用的必要条件,但是如果没有这条性质,动态规划算法同其他算法相比就不具备优势)。
这一点应该很好理解,不再赘述。
算法设计:
有了以上的理解,我们再来对这一题进行算法设计就水到渠成了。附上我用java实现的代码。核心的算法思想都在上面进行了详细的解释,备注就不多写了。
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int K = sc.nextInt();
int L = sc.nextInt();
sc.close();
int [][]dp = new int[L+1][K+1]; //为了防止循环中数组下标溢出
for(int j=0;j<K;j++) {
dp[1][j] = 1;
}
for(int i=2;i<=L;i++) {
for(int j=0;j<K;j++) {
for(int temp=0;temp<K;temp++) {
if(temp!=(j-1) && temp!=(j+1)) {
dp[i][j] += dp[i-1][temp];
dp[i][j] = dp[i][j] % 1000000007;
}
}
}
}
int sum=0;
for(int j=1;j<K;j++) {
sum += dp[L][j];
sum = sum % 1000000007;
}
System.out.println(sum);
}
}