第十四届蓝桥杯c++研究生组省赛

问题描述

小蓝最近在找一些奇怪的数,其奇数数位上是奇数,而偶数数位上是偶数。

同时,这些数的任意 5个连续数位的和都不大于 m。

例如当 m=9时,10101 和 12303 就是奇怪的数,而 12345 和 11111 则不是。

小蓝想知道一共有多少个长度为 n 的上述的奇怪的数。你只需要输出答案对 998244353取模的结果。

输入格式

输入一行包含两个整数 n, m,用一个空格分隔。

输出格式

输出一行包含一个整数表示答案。

0奇怪的数 - 蓝桥云课

代码的核心思想是通过动态规划来枚举所有可能的数,并统计满足条件的数的个数。

  • dp[10][10][10][10]:DP状态数组,dp[a][b][c][d] 表示当前数的最后四位是 a, b, c, d 的数的个数。

  • 初始化长度为4的数(即前四位),确保满足以下条件:

    • 奇数位是奇数,偶数位是偶数。

    • 连续4位数的和不超过 m

  • 通过嵌套循环枚举所有可能的四位组合,并将满足条件的组合的 dp 值设为1。

  • dp递推

  • 从长度为5开始,逐步递推到长度为 n

  • 对于每个长度 i,枚举当前数的最后四位 p, j, k, t,并尝试添加一个新的数字 q

  • 检查新添加的数字 q 是否满足以下条件:

    • 奇数位是奇数,偶数位是偶数。

    • 连续5位数的和不超过 m

  • 如果满足条件,则更新新的状态 dp[j][k][t][q],并将旧状态 dp[p][j][k][t] 清零。

#include 
using namespace std;

int dp[10][10][10][10];
const int MOD = 998244353;

int main() 
{
    int n, m;
    cin >> n >> m;
    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 t = 0;t <= 9 && t <= (m - i - j - k);t += 2) 
                    dp[i][j][k][t] = 1;
 
    //dp[i][j][k][t] -> p j k t q -> dp[j][k][t][q](将之前的结果存在这里)
    //动态规划递推
    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 t = (i + 1) % 2;t <= 9 && t <= (m - p - j -k);t += 2) 
                    {
                        for (int q = i % 2;q <= 9 && q <= (m - p - j - k - t);q += 2) 
                            dp[j][k][t][q] = (dp[j][k][t][q]+dp[p][j][k][t])%MOD;
                        //归零
                        dp[p][j][k][t] = 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 t = (n + 1) % 2;t <= 9 && t <= (m - j -k);t += 2) 
                for (int q = n % 2;q <= 9 && q <= (m - j - k - t);q += 2) 
                    res = (res+dp[j][k][t][q])%MOD;
 
    cout << res << endl;
    return 0;
}

代码逻辑比较复杂,如果优化,优化状态转移,减少不必要的循环嵌套

,将奇偶性检查的逻辑提取出来,避免在每个循环中重复判断,并使用滚动数组优化空间复杂度,减少内存占用。

如果直接用滚动数组优化的话,很好理解,但是测试用例只能过12个。(代码可以看看)

#include 
#include 
using namespace std;

const int MOD = 998244353;

int n, m;
int dp[2][10][10][10][10]; // dp[flag][a][b][c][d] 使用滚动数组优化

int main() {
    scanf("%d%d", &n, &m);
    int flag = 0;

    // 初始化长度为1的情况
    if (n >= 1) { // 确保n至少为1
        for (int d = 0; d <= 9; d++) {
            if ((n % 2 == 1 && d % 2 == 1) || (n % 2 == 0 && d % 2 == 0)) {
                dp[flag][0][0][0][d] = 1;
            }
        }
    }

    // DP递推
    for (int i = 2; i <= n; i++) {
        int new_flag = flag ^ 1;
        memset(dp[new_flag], 0, sizeof(dp[new_flag])); // 清空新状态

        bool is_odd_pos = (i % 2 == 1); // 当前位是否为奇数位

        for (int a = 0; a <= 9; a++) {
            for (int b = 0; b <= 9; b++) {
                for (int c = 0; c <= 9; c++) {
                    for (int d = 0; d <= 9; d++) {
                        if (dp[flag][a][b][c][d] == 0) continue;

                        int sum_abcd = a + b + c + d; // 前四位和

                        for (int e = 0; e <= 9; e++) {
                            // 奇偶性检查
                            if (is_odd_pos && e % 2 != 1) continue;
                            if (!is_odd_pos && e % 2 != 0) continue;

                            // 连续五位和检查(仅当i>=5时)
                            if (i >= 5 && (sum_abcd + e > m)) continue;

                            // 状态转移并取模
                            dp[new_flag][b][c][d][e] = (dp[new_flag][b][c][d][e] + dp[flag][a][b][c][d]) % MOD;
                        }
                    }
                }
            }
        }
        flag = new_flag; // 切换状态
    }

    // 计算结果
    int res = 0;
    for (int a = 0; a <= 9; a++) {
        for (int b = 0; b <= 9; b++) {
            for (int c = 0; c <= 9; c++) {
                for (int d = 0; d <= 9; d++) {
                    res = (res + dp[flag][a][b][c][d]) % MOD; // 累加时取模
                }
            }
        }
    }

    printf("%d\n", res);
    return 0;
}

1.为什么明明递推的时候从前往后,而初始化的时候却先初始化最后一位d呢?

       因为状态设计是保存最后四位数字,每次添加新的数字e到末尾,状态变化为 a b c d->b c d e。所以初始化对于长度为1的数字,只用关注d位就好,前面的三位用0占位。

    假设长度为 n,动态规划从长度1开始逐步构建到长度 n。初始化长度为1时:

  • 例如,dp[0][0][0][1] = 1 表示长度为1的数 1

  • 随着长度增加,新数字被添加到末尾,状态逐步更新为:

  • 长度为2时:[0, 0, 1, x]x 是偶数)。

  • 长度为3时:[0, 1, x, y]y 是奇数)。

  • 以此类推,直到长度为 n

2.初始那里为什么用0占位?

        当处理第一个数字时,状态是四个位置中的最后一个,而前三个位置没有意义,所以用0填充。在状态转移过程中,占位符不会参与有效计算,直到有足够的位数填满这几个位置。

   占位符的作用

  • 保持状态结构一致:占位符 0 在初始化时填充前三位,使得状态始终是四维的。

  • 避免边界条件处理:当长度不足4时,占位符 0 不会影响奇偶性检查(因为它们不是实际数字的有效位)。

  • 简化状态转移:每次添加新数字时,只需将新数字附加到末尾,并滚动更新前三位。

3.什么是滚动数组?

     通常在动态规划中,如果状态转移只依赖于前一个或几个状态,而不是所有的历史状态,就可以用滚动数组来替代完整的DP数组,从而节省空间。

        而在这道题中,dp是四维的,每次状态转移的时候,新状态只依赖前一个状态某些值。并不需要所有历史状态。,因此可以使用滚动数组来交替使用两个数组,一个表示当前状态,另一个表示下一个状态。

        滚动优化:使用两个四维数组(当前和下一步),空间复杂度为 2 * 10^4 = 20,000,与 n 无关。

你可能感兴趣的:(蓝桥杯,职场和发展)