题解:学而思编程 我们不喜欢WA

【题目来源】

学而思编程:我们不喜欢WA

【题目描述】

给出正整数 \(N\),求满足以下条件的长为 \(N\) 的字符串数量。

只含'A','C','W'三种字符。

不含"WA"子串,也就是说字符'A'不能紧接在字符'W'后面。

任选一对相邻字符交换位置后,也不含"WA"子串。

满足条件的字符串数量很大,只需要输出答案除以 \(10^9+7\) 的余数。

【输入】

一个正整数 \(N\)

【输出】

输出满足条件的字符串个数,除以 \(10^9+7\) 的余数。

【输入样例】

3

【输出样例】

16

【核心思想】

  1. 问题分析:给定长度 \(N\),求由 'A'、'C'、'W' 三种字符组成的长度为 \(N\) 的字符串数量,要求:

    • 原串不含 "WA" 子串('A' 不能紧跟在 'W' 后面)
    • 任意交换一对相邻字符后,新串也不含 "WA" 子串
      这是一个线性动态规划问题,状态需要记录最后两位字符以判断转移合法性。
  2. 算法选择

    • 线性 DP(一维扩展)\(dp[i][a][b]\) 表示长度为 \(i\) 的序列,最后两位分别为 \(a\)\(b\) 的合法序列数量
    • 状态转移合法性矩阵:预处理所有三位组合 \((a, b, c)\) 是否合法
    • 取模运算:答案对 \(10^9+7\) 取模
  3. 关键步骤

    • 字符编码:将 'A'、'C'、'W' 分别编码为 \(0\)\(1\)\(2\)
    • 预处理合法转移矩阵 \(v[a][b][c]\)
      • 原串不能含 "WA":即不能有 \((2, 0)\) 连续出现
      • 交换相邻字符后不能含 "WA":即对于三位 \((a, b, c)\),交换后 \((b, a, c)\)\((a, c, b)\) 都不能产生 "WA"
      • 通过分析得出合法的三位组合,初始化 \(v\) 矩阵
    • 初始化\(dp[2][a][b]\) 表示长度为 \(2\) 的合法序列,共 \(8\) 种合法组合
    • DP 递推\(i\)\(3\)\(N\)):
      • 枚举前两位 \(a, b\) 和新增字符 \(c\)
      • \(v[a][b][c] = 1\),则 \(dp[i][b][c] += dp[i-1][a][b]\)
      • 所有运算对 \(mod\) 取模
    • 统计答案\(ans = \sum_{b=0}^{2} \sum_{c=0}^{2} dp[N][b][c] \mod mod\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(N \times 3^3) = O(27N) = O(N)\),DP 递推每层枚举 \(27\) 种状态转移
    • 空间复杂度:\(O(N \times 3^2) = O(9N) = O(N)\),DP 数组存储
  5. 线性 DP 与状态设计的核心思想

    • 状态设计原则:DP 状态需要包含足够的信息以判断未来转移的合法性,本题记录最后两位字符
    • 约束转化:将"交换后仍合法"的复杂约束转化为对三位组合的合法性判断
    • 矩阵预处理:将复杂的条件判断预处理为合法性矩阵,DP 过程中 \(O(1)\) 查表
    • 取模技巧:在每次加法运算后立即取模,防止整数溢出
    • 适用于字符串计数、序列构造、约束满足类问题

【解题思路】

![[备注 2026年3月11日(55)_00.png]]

【算法标签】

线性DP-一维

【代码详解】

#include <bits/stdc++.h>
using namespace std;

// ================= 常量与全局变量 =================

const int N = 100005;    // 最大长度
const int mod = 1e9 + 7; // 模数

int n;                   // 序列长度
int ans;                 // 最终答案

int v[3][3][3];          // 状态转移合法性矩阵
                         // v[a][b][c] = 1 表示允许从 (a,b) 转移到 (b,c)

int dp[N][3][3];         // 动态规划数组
                         // dp[i][a][b] 表示长度为 i 的序列,
                         // 最后两位分别为 a 和 b 的合法序列数量

// ================= 主函数 =================
int main()
{
    // 读取序列长度
    cin >> n;

    // ========= 初始化状态转移矩阵 v =========
    // 定义哪些三位数组合 (a, b, c) 是合法的
    v[0][0][0] = v[0][0][1] = v[0][1][0] = v[0][1][1] = 1;
    v[0][1][2] = v[1][0][0] = v[1][0][1] = v[1][1][0] = 1;
    v[1][1][1] = v[1][1][2] = v[1][2][1] = v[1][2][2] = 1;
    v[2][1][1] = v[2][1][2] = v[2][2][1] = v[2][2][2] = 1;

    // ========= 初始化长度为 2 的序列 =========
    dp[2][0][0] = dp[2][0][1] = dp[2][1][0] = dp[2][1][1] = 1;
    dp[2][1][2] = dp[2][2][1] = dp[2][2][2] = 1;

    // ========= 动态规划递推 =========
    // 从长度为 3 开始,逐步递推到长度为 n
    for (int i = 3; i <= n; i++)
    {
        // 枚举前三个状态 a, b, c
        for (int a = 0; a < 3; a++)
            for (int b = 0; b < 3; b++)
                for (int c = 0; c < 3; c++)
                {
                    // 如果组合 (a, b, c) 是合法的
                    if (v[a][b][c] == 1)
                    {
                        // 将前 i-1 位以 (a, b) 结尾的序列
                        // 扩展为以 (b, c) 结尾的长度为 i 的序列
                        dp[i][b][c] = (dp[i][b][c] + dp[i - 1][a][b]) % mod;
                    }
                }
    }

    // ========= 统计答案 =========
    // 累加所有以任意两位 (b, c) 结尾的长度为 n 的合法序列数量
    for (int b = 0; b < 3; b++)
        for (int c = 0; c < 3; c++)
            ans = (ans + dp[n][b][c]) % mod;

    // 输出结果
    cout << ans << endl;

    return 0;
}

【运行结果】

3
16
posted @ 2026-06-17 17:24  团爸讲算法  阅读(10)  评论(0)    收藏  举报