solution by XiangXunYi
思路推导
step 1
首先题目中操作二同时删掉 A
,B
,C
的条件相当于同时将三者数量减一,操作一删掉两个相同字符等同于将某一字符的数量减二,那么我们可以发现只使用操作一不会改变奇偶,操作二则是同时反转奇偶,所以一个字符串是个好字符串的必要条件是其中三个字母数量的奇偶性相同,同时容易构造出一组解使其变为空字符串,故也是必要条件。
构造解:执行操作一直到不能执行为止,最后视情况执行操作二。
step2
由于题目的抽象数据范围较小且对于问号的抉择相互影响较大,联想到 dp。首先会想到把 \(K\) 放入 dp 状态中来 check 是否合法,但我们发现每次新增一个字符时算贡献是需要枚举之前前缀三个字符的奇偶性,但上一个状态又并不完全由所枚举的单个状态转移过来,dp 的转移就很扑朔迷离(至少我是这样)。所以要改变 dp 的定义,但又要计算好子段个数,所以状态必须能计算 \(K\) 值。
step 3
设一段区间 A
的数量对 \(2\) 取模为 \(a\),B
的数量对 \(2\) 取模为 \(b\),C
的数量对 \(2\) 取模为 \(c\)。
因为好字段必须要三个字符数量奇偶性相同,抽象的理解为 \(0/1\) 状态,即区间和为 \(0\),前缀和呀!只要两点的前缀和值相等,这个区间就是一个好子段。我们设计一个 dp 状态 \(dp_{i,j,k,l,0/1/2/3}\) 表示存在 \(i\) 个位置前缀区间满足情况 \(0\)[1],\(j\) 个位置前缀区间满足情况 \(1\)[2],\(k\) 个位置前缀区间满足情况 \(2\)[3],\(l\) 个位置前缀区间满足情况 \(3\)[4]且上一个位置前缀区间满足情况 \(0/1/2/3\) 的方案数。
在这种 dp 状态的设计下,我们可以发现,四种情况不重不漏的涵盖了所有区间,当一个区间满足右端点和左端点减一是同一种情况就是一个好子段,最后好子段的个数就是 \(\frac{i \times (i + 1) + j \times (j - 1) + k \times (k - 1) + l \times (l - 1)}{2}\) 答案即为所有该值大于 \(K\) 的 \(dp_{i,j,k,l,0/1/2/3}\) 累加起来。
step 4
定义有了,该题的转移并不难想,只要根据上一个位置的状态加上当前位置的字符即可转移(给个建议:用刷表)。
solution
定义 \(dp_{i,j,k,l,lst \in \{0/1/2/3\}}\) 表示有 \(i\) 个前缀满足情况 \(0\),\(j\) 个前缀满足情况 \(1\),\(k\) 个前缀满足情况 \(2\),\(l\) 个前缀满足情况 \(3\),上一个前缀的情况为 \(lst\)。
对于当前字符为 A
时,情况 \(0\) 与情况 \(3\) 互换,情况 \(1\) 与情况 \(2\) 互换。
对于当前字符为 B
时,情况 \(0\) 与情况 \(1\) 互换,情况 \(2\) 与情况 \(3\) 互换。
对于当前字符为 C
时,情况 \(0\) 与情况 \(2\) 互换,情况 \(1\) 与情况 \(3\) 互换。
int pos = i + j + k + l;
int tmp[4] = { i, j, k, l };
int x = lst ^ 3, y = lst ^ 1, z = lst ^ 2;
if ((s[pos] == '?' || s[pos] == 'A') && tmp[x] + 1 <= n) {tmp[x]++;trans(dp[tmp[0]][tmp[1]][tmp[2]][tmp[3]][x] , dp[i][j][k][l][lst]);tmp[x]--;
}
if ((s[pos] == '?' || s[pos] == 'B') && tmp[y] + 1 <= n) {tmp[y]++;trans(dp[tmp[0]][tmp[1]][tmp[2]][tmp[3]][y] ,dp[i][j][k][l][lst]);tmp[y]--;
}
if ((s[pos] == '?' || s[pos] == 'C') && tmp[z] + 1 <= n) {tmp[z]++;trans(dp[tmp[0]][tmp[1]][tmp[2]][tmp[3]][z] , dp[i][j][k][l][lst]);tmp[z]--;
}
代码
#include <bits/stdc++.h>
using namespace std;
const int N = 55;
const int mod = 998244353;
int n, least, dp[N][N][N][N][4];
char s[55];
inline void trans(int& x, int y) { x = (x + y) % mod; }
int main() {scanf("%d%d%s", &n, &least, s);dp[0][0][0][0][0] = 1;for (int i = 0; i < n; ++i)for (int j = 0; j < n; ++j)for (int k = 0; k < n; ++k)for (int l = 0; l < n; ++l)if (i + j + k + l < n)for (int lst = 0; lst < 4; ++lst) {int pos = i + j + k + l;int tmp[4] = { i, j, k, l };int x = lst ^ 3, y = lst ^ 1, z = lst ^ 2;if ((s[pos] == '?' || s[pos] == 'A') && tmp[x] + 1 <= n) {tmp[x]++;trans(dp[tmp[0]][tmp[1]][tmp[2]][tmp[3]][x] , dp[i][j][k][l][lst]);tmp[x]--;}if ((s[pos] == '?' || s[pos] == 'B') && tmp[y] + 1 <= n) {tmp[y]++;trans(dp[tmp[0]][tmp[1]][tmp[2]][tmp[3]][y] ,dp[i][j][k][l][lst]);tmp[y]--;}if ((s[pos] == '?' || s[pos] == 'C') && tmp[z] + 1 <= n) {tmp[z]++;trans(dp[tmp[0]][tmp[1]][tmp[2]][tmp[3]][z] , dp[i][j][k][l][lst]);tmp[z]--;}}int ans = 0;for (int i = 0; i <= n; ++i)for (int j = 0; j <= n; ++j)for (int k = 0; k <= n; ++k)for (int l = 0; l <= n; ++l)for (int lst = 0; lst < 4; ++lst)if (i + j + k + l == n && i * (i + 1) + j * (j - 1) + k * (k - 1) + l * (l - 1) >> 1 >= least)trans(ans, dp[i][j][k][l][lst]);printf("%d\n", ans);return 0;
}
\(a = b \ and \ a = c\) ↩︎
\(a = c \ and \ a \neq b\) ↩︎
\(a = b \ and \ a \neq c\) ↩︎
\(a \neq b \ and \ b = c\) ↩︎