ALGO-3 K好数 — 动态规划(java)

ALGO—3 K好数

问题描述

如果一个自然数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三种情况。将这些结论填入下表中。


0123
11111
23223

此时用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);
	}
}





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值