CF17C Balance (字符串 dp)
upd 2024.11.13
字符串 dp
可以看到答案要求的是个数,我们的计数工具有 dp,组合计数等。这题中的操作可以说是“覆盖”,偏简单,又看到数据范围偏小,可以想想用朴素的 dp 怎么做。
计数平衡串,关心字母的数量,字符集又很小,所以可以想直接表示在状态里就好了。
想想操作的本质,它并不改变相对顺序,于是可以看成选出原串中的若干字符进行扩展。这就是从左到右线性 dp。
由于相邻相同字符会算重怎么办?压缩一下就好了。
发送式转移比较简单,预处理每个位置的转移位置就好了。
考虑 dp。观察题目的操作,可以发现一些性质:
- 不改变字符出现的相对顺序
- 相当于覆盖操作,可以盖掉某字符
那么我们就可以考虑将字符串”压缩“,即将相同字母的区间压缩为一个。那么就可以 dp 了,考虑设 \(f_{i,a,b,c}\) 表示匹配到压缩串第 \(i\) 个位置用了 \(a\) 个 a
,\(b\) 个 b
,\(c\) 个 c
的方案数。
转移枚举下一个字符,预处理 \(nxt_{i,j}\) 表示压缩串中第 \(i\) 个字符后第一个字符 \(j\) 的位置。
复杂度 \(O(n^4)\),后面三维最大才 \(\frac{n}{3}\),所以只会更快。
#include <bits/stdc++.h>
#define pii std::pair<int, int>
#define fi first
#define se second
#define pb push_back
using i64 = long long;
using ull = unsigned long long;
const i64 iinf = 0x3f3f3f3f, linf = 0x3f3f3f3f3f3f3f3f;
const int N = 152, mod = 51123987;
int n, m;
char s[N], A[N];
int nxt[N][3];
int f[N][52][52][52], ans;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cin >> n >> s + 1;
int lim = n / 3 + 1;
for(int i = 1; i <= n; i++) {
if(i == n || s[i] != s[i + 1]) {
A[++m] = s[i];
}
}
nxt[m + 1][0] = nxt[m + 1][1] = nxt[m + 1][2] = m + 1;
for(int i = m; i >= 0; i--) {
for(int j = 0; j < 3; j++) {
nxt[i][j] = nxt[i + 1][j];
}
nxt[i][A[i] - 'a'] = i;
}
f[0][0][0][0] = 1;
for(int i = 0; i <= m; i++) {
for(int a = 0; a <= lim; a++) {
for(int b = 0; b <= lim; b++) {
for(int c = 0; c <= lim; c++) {
(f[nxt[i][0]][a + 1][b][c] += f[i][a][b][c]) % mod;
(f[nxt[i][1]][a][b + 1][c] += f[i][a][b][c]) % mod;
(f[nxt[i][2]][a][b][c + 1] += f[i][a][b][c]) % mod;
}
}
}
}
for(int i = 1; i <= m; i++) {
for(int a = 0; a <= lim; a++) {
for(int b = 0; b <= lim; b++) {
for(int c = 0; c <= lim; c++) {
f[i][a][b][c] = (f[i][a][b][c] + f[i - 1][a][b][c]) % mod;
}
}
}
}
for(int i = 0; i <= lim; i++) {
for(int j = 0; j <= lim; j++) {
for(int k = 0; k <= lim; k++) {
if(i + j + k == n && abs(i - j) <= 1 && abs(j - k) <= 1 && abs(i - k) <= 1) (ans += f[m][i][j][k]) % mod;
}
}
}
std::cout << ans << "\n";
return 0;
}