第十四届蓝桥杯大赛软件赛省赛Java 研究生组G题
·
题解:奇怪的数
源题目地址:https://www.lanqiao.cn/problems/3528/learning/
问题分析
我们需要找到满足以下条件的长度为 ( n ) 的数字:
- 奇数位是奇数,偶数位是偶数:即在奇数位置上只能是 ( 1, 3, 5, 7, 9 ),在偶数位置上只能是 ( 0, 2, 4, 6, 8 )。
- 任意连续 5 个数位的和不超过 ( m \times m ):这是一个滑动窗口限制。
最终目标是统计所有满足上述条件的数字个数,并对结果取模 ( 998244353 )。
解题思路
为了高效解决该问题,我们采用动态规划DP的方法。以下是详细的解题步骤:
动态规划设计
定义状态 ( dp[a][b][c][d] ) 表示当前数字的最后 4 个数位分别为 ( a, b, c, d ) 时,满足条件的数字个数。
-
状态转移:
- 当前数字的第 ( i ) 位可以由前一个状态的最后 4 位推导而来。
- 新的一位 ( x ) 必须满足:
- 奇偶性约束:如果 ( i ) 是奇数位,则 ( x ) 必须是奇数;如果是偶数位,则 ( x ) 必须是偶数。
- 和约束:新加入的 5 位数字之和不能超过 ( m \times m )。
-
边界初始化:
- 对于前 4 位数字,直接枚举所有可能的组合,满足奇偶性和和约束,将其初始化为 1。
-
优化:
- 利用滚动数组减少内存开销。
- 在状态转移过程中,及时清空无用的状态以节省空间。
代码实现
import java.util.Scanner;
public class Main {
static final int mod = 998244353;
static int[][][][] dp = new int[10][10][10][10];
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int m = scanner.nextInt();
int res = 0;
// 初始化边界
for (int i = 1; i <= 9; i += 2) {
for (int j = 0; j <= 9 && j <= (m - i); j += 2) {
for (int k = 1; k <= 9 && k <= (m - i - j); k += 2) {
for (int l = 0; l <= 9 && l <= (m - i - j - k); l += 2) {
dp[i][j][k][l] = 1;// 边界初始化为1
}
}
}
}
// 开始动态规划
for (int i = 5; i <= n; i++) {// 遍历数字的位数
for (int p = i % 2; p <= 9; p += 2) {// 遍历当前数字的奇数位数
for (int j = (i + 1) % 2; j <= 9 && (j <= m - p); j += 2) {// 遍历当前数字的偶数位数
for (int k = i % 2; k <= 9 && (k <= m - p - j); k += 2) {// 遍历奇数
for (int l = (i + 1) % 2; l <= 9 && l <= (m - p - j - k); l += 2) {// 遍历偶数
for (int q = i % 2; q <= 9 && q <= (m - p - j - k - l); q += 2) {// 遍历最后一个奇数位
dp[j][k][l][q] += dp[p][j][k][l];// 状态转移
dp[j][k][l][q] %= mod;
}
dp[p][j][k][l] = 0;// 清空原有的值
}
}
}
}
}
// 计算结果
for (int j = (n + 1) % 2; j <= 9 && (j <= m); j += 2) {
for (int k = n % 2; k <= 9 && (k <= m - j); k += 2) {
for (int l = (n + 1) % 2; l <= 9 && l <= (m - j - k); l += 2) {
for (int q = n % 2; q <= 9 && q <= (m - j - k - l); q += 2) {
res += dp[j][k][l][q];// 累加结果
res %= mod;
}
}
}
}
System.out.println(res);
}
}
算法实现
代码实现中,主要分为以下几个部分:
-
初始化边界:
for (int i = 1; i <= 9; i += 2) { for (int j = 0; j <= 9 && j <= (m - i); j += 2) { for (int k = 1; k <= 9 && k <= (m - i - j); k += 2) { for (int l = 0; l <= 9 && l <= (m - i - j - k); l += 2) { dp[i][j][k][l] = 1; } } } }这里枚举了前 4 位的所有可能组合,并初始化符合条件的状态。
-
动态规划状态转移:
for (int i = 5; i <= n; i++) { for (int p = i % 2; p <= 9; p += 2) { for (int j = (i + 1) % 2; j <= 9 && (j <= m - p); j += 2) { for (int k = i % 2; k <= 9 && (k <= m - p - j); k += 2) { for (int l = (i + 1) % 2; l <= 9 && l <= (m - p - j - k); l += 2) { for (int q = i % 2; q <= 9 && q <= (m - p - j - k - l); q += 2) { dp[j][k][l][q] += dp[p][j][k][l]; dp[j][k][l][q] %= mod; } dp[p][j][k][l] = 0; } } } } }从第 5 位开始逐步计算新的状态,并更新 DP 数组。
-
结果统计:
for (int j = (n + 1) % 2; j <= 9 && (j <= m); j += 2) { for (int k = n % 2; k <= 9 && (k <= m - j); k += 2) { for (int l = (n + 1) % 2; l <= 9 && l <= (m - j - k); l += 2) { for (int q = n % 2; q <= 9 && q <= (m - j - k - l); q += 2) { res += dp[j][k][l][q]; res %= mod; } } } }最后累加所有可能的状态,得到最终结果。
复杂度分析
-
时间复杂度:
- 初始化边界的时间复杂度为 ( O(10^4) )。
- 动态规划状态转移的时间复杂度为 ( O(n \cdot 10^4) ),其中 ( n ) 是数字长度。
- 总体时间复杂度为 ( O(n \cdot 10^4) ),在 ( n \leq 2 \times 10^5 ) 的范围内可接受。
-
空间复杂度:
- 使用了一个大小为 ( 10 \times 10 \times 10 \times 10 ) 的 DP 数组,空间复杂度为 ( O(10^4) )。
示例验证
输入:
5 5
输出:
6
解释:
当 ( n = 5 ) 且 ( m = 5 ) 时,满足条件的数字有 6 个。
总结
通过动态规划巧妙地解决了复杂的约束问题,充分利用了奇偶性和滑动窗口的性质。代码逻辑清晰、实现正确,能够高效处理大规模数据。
更多推荐


所有评论(0)