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
}
}
格式2
数据类型[][] 变量名 = new 数据类型[m][];
m表示这个二维数组有多少个一维数组
这一次没有直接给出一维数组的元素个数,可以动态的给出。
举例:
int[][] arr = new int[3][];
arr[0] = new int[2];
arr[1] = new int[3]
arr[2] = new int[1];
格式3
数据类型[][] 变量名 = new 数据类型[][]{{元素…},{元素…},{元素…}};
简化版格式:
数据类型[][] 变量名 = {{元素…},{元素…},{元素…}};
举例:
int[][] arr = {{1,2,3},{4,6},{6}};
4.动态规划
动态规划是将待求解的问题分解为若干个子阶段,按顺序求解子阶段,前一子问题的解,为后一子问题的求解提供了有用的信息。
问题解析
问题要求解的是L位K进制数中K好数的个数,按照动态规划分析,将该问题拆解为若干个相似的子问题,只需要求解其中的第一个子问题,再通过迭代的方式就可以求解出整个问题的解。
关键步骤:
- 划分子问题
- 循环设计
循环设计
这里因为已经知道循环的确切的次数,所有直接用嵌套的for循环就可以实现