k好数

k好数

package practice;

import java.util.Scanner;

/*
 * 如果一个自然数N的K进制表示中任意的相邻的两位都不是相邻的数字,
 * 那么我们就说这个数是K好数。

求L位K进制数中K好数的数目。例如K = 4,L = 2的时候,
所有K好数为11、13、20、22、30、31、33 共7个。

由于这个数目很大,请你输出它对1000000007取模后的值。
 * */
public class _13K好数 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Scanner sc = new Scanner(System.in);
		int k = sc.nextInt();// k进制
		int l = sc.nextInt();// l位数
		long count = 0;// 有几个数
		long mod = 1000000007;
		// f[i][j]表示长度为i,首数字为j的k好数的个数
		long f[][] = new long[100][100];
		/*
		 * L位K进制中K好数的个数划分为i位首数字为j的K好数的个数,其中i取值[1,L],j取值[0,K],
		 * 将整个问题分为L*(K+1)个子问题。创建一个二维数组f[i][j]表示i位首数字为j的K好数的个数,
		 * 每个f[i][j]的值只和f[i-1][]的值有关。
		 * 
		 */

		// 长度为1的所有数由于没有相邻数,所有都是k好数,之所以把这个长度分出来是因为个位很特殊
		for (int j = 0; j < k; j++) {
			f[1][j] = 1;
		}

		// 从长度为2的数开始查找
		// 当j数字和i-1位的首数字m不相邻时
		for (int i = 2; i <= l; i++) {
			for (int j = 0; j < k; j++) {
				for (int m = 0; m < k; m++) {// i-1位的首数字m
					// 便可以将 j数字和该i-1位的数字组成新的i位k好数
					// 该i位的k好数的个数增加了该i-1位k好数的个数
					if (m != j - 1 && m != j + 1) {
						f[i][j] += f[i - 1][m];
//									// 当数量超过的时候就取余
						if (f[i][j] >= mod) {
							f[i][j] %= mod;
						}
					}

				}
			}

		}
		for (int i = 1; i < k; i++) {
            //把长度为l的所有位数相加
			count += f[l][i];
			if (count >= mod) {
				count %= mod;
			}
		}
		System.out.println(count);
	}
}

总结:

1.题目中为啥给出“由于数很大……输出取模后的值”这一条件?

在正数的范围内的运算 ,取模和取余是一样的。也可以这样理解当除数与被除数的符号是同号时,取模和取余运算结果是相同的,当异号时则相反。 取模是以负无穷为趋近点的运算。

最后,在数据规范与约定中,我们知道1K100,1 L100。我们假设K取100,L也取100。那么在100位100进制数中不考虑100好数的情况下,粗略一算,它的数就有100的100次方。肯定超过了int,long long。这时题目告诉我们取模来减小我们的负担。(感谢啊)

2.划分子问题

L位K进制中K好数的个数划分为i位首数字为j的K好数的个数,其中i取值[1,L],j取值[0,K],将整个问题分为L*(K+1)个子问题。创建一个二维数组f[i][j]表示i位首数字为j的K好数的个数,每个f[i][j]的值只和f[i-1][]的值有关。

动态规划思路:设置状态数组dp[L][K]表示长度为L且以K结束的k好数的个数,状态数组为L*K的二维数组。

初始状态:长度为1的以任何数结束的k好数都只有一个:dp[1][k] = 1,k=1,2,3…,K

递推方程:长度为i以j结尾的k好数个数为 dp[i][j] = sum(dp[i-1][m], m=1,2,3,…K且abs(m-j)!=1)

最后要求K进制长度为L的k好数,只需要把数组dp中第L行所有元素加起来即可,但是考虑到k好数的第一个位置的数不能为0,所以最后相加时去除dp[L][0](虽然dp[L][0]表示的是长度为L,以0结尾的k好数个数,但是因为这是一种可能的组合个数,dp[L][0]的数量与长度为L以0开头的k好数个数一样)。

3.二维数组

格式1
数据类型[][] 变量名 = new 数据类型[m][n];
m表示这个二维数组有多少个一维数组
n表示每一个一维数组的元素个数
举例:
int[][] arr = new int[3][2];
定义了一个二维数组arr
这个二维数组有3个一维数组,名称是arr[0],arr[1],arr[2]
每个一维数组有2个元素,可以通过arr[m][n]来获取
表示获取第m+1个一维数组的第n+1个元素

针对格式1其实还可以
	int arr[][];
	int[] arr[];
	但是都不建议。
	
	这个时候提醒大家注意一个问题
	int[] x,y[];
	这种定义x是一个一维数组。
	y是一个二维数组。

class Array2Demo {
	public static void main(String[] args) {
		//用格式1定义一个二维数组
		int[][] arr1 = new int[3][2];
		
		//输出二维数组名称
		System.out.println(arr1);	//地址值:[[I@7676438d
	
		//输出二维数组的第一个元素一维数组的名称
		System.out.println(arr1[0]);	//地址值:[I@4e4d1abd
		
		//输出二维数组的元素
		System.out.println(arr1[0][0]);	//元素值:0
	}
}

image-20210307203623456
格式2
数据类型[][] 变量名 = new 数据类型[m][];
m表示这个二维数组有多少个一维数组
这一次没有直接给出一维数组的元素个数,可以动态的给出。
举例:
int[][] arr = new int[3][];
arr[0] = new int[2];
arr[1] = new int[3]
arr[2] = new int[1];
image-20210307203716413
格式3
数据类型[][] 变量名 = new 数据类型[][]{{元素…},{元素…},{元素…}};
简化版格式:
数据类型[][] 变量名 = {{元素…},{元素…},{元素…}};
举例:
int[][] arr =  {{1,2,3},{4,6},{6}};
image-20210307203746921 image-20210307205824207

4.动态规划

动态规划是将待求解的问题分解为若干个子阶段,按顺序求解子阶段,前一子问题的解,为后一子问题的求解提供了有用的信息。

问题解析

问题要求解的是L位K进制数中K好数的个数,按照动态规划分析,将该问题拆解为若干个相似的子问题,只需要求解其中的第一个子问题,再通过迭代的方式就可以求解出整个问题的解。

关键步骤:

  • 划分子问题
  • 循环设计

循环设计

这里因为已经知道循环的确切的次数,所有直接用嵌套的for循环就可以实现

posted @ 2021-03-09 15:18  记录学习Blog  阅读(265)  评论(0编辑  收藏  举报