题解:学而思编程 我们不喜欢WA
【题目来源】
学而思编程:我们不喜欢WA
【题目描述】
给出正整数 \(N\),求满足以下条件的长为 \(N\) 的字符串数量。
只含'A','C','W'三种字符。
不含"WA"子串,也就是说字符'A'不能紧接在字符'W'后面。
任选一对相邻字符交换位置后,也不含"WA"子串。
满足条件的字符串数量很大,只需要输出答案除以 \(10^9+7\) 的余数。
【输入】
一个正整数 \(N\)
【输出】
输出满足条件的字符串个数,除以 \(10^9+7\) 的余数。
【输入样例】
3
【输出样例】
16
【核心思想】
-
问题分析:给定长度 \(N\),求由 'A'、'C'、'W' 三种字符组成的长度为 \(N\) 的字符串数量,要求:
- 原串不含 "WA" 子串('A' 不能紧跟在 'W' 后面)
- 任意交换一对相邻字符后,新串也不含 "WA" 子串
这是一个线性动态规划问题,状态需要记录最后两位字符以判断转移合法性。
-
算法选择:
- 线性 DP(一维扩展):\(dp[i][a][b]\) 表示长度为 \(i\) 的序列,最后两位分别为 \(a\) 和 \(b\) 的合法序列数量
- 状态转移合法性矩阵:预处理所有三位组合 \((a, b, c)\) 是否合法
- 取模运算:答案对 \(10^9+7\) 取模
-
关键步骤:
- 字符编码:将 '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\)
-
时间/空间复杂度:
- 时间复杂度:\(O(N \times 3^3) = O(27N) = O(N)\),DP 递推每层枚举 \(27\) 种状态转移
- 空间复杂度:\(O(N \times 3^2) = O(9N) = O(N)\),DP 数组存储
-
线性 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
浙公网安备 33010602011771号