20250829 LG_P2679 [NOIP 2015 提高组] 子串
题意
有两个字符串 \(s, t\) 求有多少种方案从 \(s\) 中取出不相交的 \(w\) 段,使他们拼起来为 \(t\)。
思路
由于求方案数,所以考虑 DP。
对于 DP 计数题,我们可以将每一个方案看作是多阶段决策的结果,那么我们一般先考虑一部分的决策(即,将决策集合的扩大作为拓扑序)。因为这样做就可以直接考虑下一步决策(将下一步决策加入当前决策集合)。而在此题中,我们也可以如上考虑。首先,我们可以将「选出一段」看作为决策,并将选出的段数的增多作为拓扑序,同时,因为我们不在乎选择段的顺序(即,先选 \(s[1][2]\) (\(s[l][r]\) 表示 s 从 l 到 r 的子段) 再选 \(s[3][4]\) 和 先选 \(s[3][4]\) 再选 \(s[1][2]\) 是一样的方案),所以钦定我们选的段是越来越靠后的。我们发现,在转移(决策,即「选出一段」)中,为了使方案满足每段之间不相交,我们需要关注「最后一个被字符段覆盖的位置」,为了新的一段与 \(t\) 相符,我们需要关注「当前匹配到了 \(t\) 的第几个字符」。所以我们可以设状态 \((i, j, k)\),表示当前选了 \(i\) 段,最后一段的末尾为 \(j\),匹配到了 \(t\) 的第 \(k\) 个字符。转移就是选出下一段,形式化的表述为 \((i - 1, x, y) \to (i, j, k)\),其中 \(x < j, y < k,y < x, x < j - k + y + 1, s[j - k + y + 1][j] == t[y + 1][k]\)。用哈希来判断字段是否相同。
枚举状态 \(O(nmw)\),转移 \(O(nm)\),总共 \(O(n^2m^2w)\)。\(40\) pts。
#include<bits/stdc++.h>
using namespace std;
using ull = unsigned long long;
const ull N = 1e3 + 5, M = 2e2 + 5, P = 131, MOD = 1e9 + 7;
int n, m, k, ans;
int dp[M][N][M];
string s, t;
ull hs[N], ht[N], pw[N];
ull Cs(int l, int r) {
return hs[r] - hs[l - 1] * pw[r - l + 1];
}
ull Ct(int l, int r) {
return ht[r] - ht[l - 1] * pw[r - l + 1];
}
int main() {
cin >> n >> m >> k >> s >> t;
s = '#' + s, t = '#' + t;
for (int i = 1; i <= n; i++) {
hs[i] = hs[i - 1] * P + s[i];
}
for (int i = 1; i <= m; i++) {
ht[i] = ht[i - 1] * P + t[i];
}
pw[0] = 1;
for (int i = 1; i <= max(n, m); i++) {
pw[i] = pw[i - 1] * P;
}
dp[0][0][0] = 1;
for (int i = 1; i <= k; i++) {
for (int j = 1; j <= n; j++) {
for (int l = 1; l <= min(m, j); l++) {
for (int x = 0; x < j; x++) {
for (int y = max(0, x + l - j); y < min(l, x + 1); y++) {
if (Cs(j - l + y + 1, j) == Ct(y + 1, l)) {
dp[i][j][l] = (dp[i][j][l] + dp[i - 1][x][y]) % MOD;
}
}
}
}
}
}
for (int i = 1; i <= n; i++) {
ans = (ans + dp[k][i][m]) % MOD;
}
cout << ans;
return 0;
}
对于一个状态,可以发现对于同一个 \(y\) 合法的 \(x\) 是一个前缀,所以改变枚举顺序,在转移时先枚举 \(y\) 再枚举 \(x\),并用前缀和优化即可。其实仅改变枚举顺序就有 60 pts
枚举状态 \(O(nmw)\),转移 \(O(m)\),总共 \(O(nm^2w)\)。\(70\) pts。
#include<bits/stdc++.h>
using namespace std;
using ull = unsigned long long;
const ull N = 1e3 + 5, M = 2e2 + 5, P = 131, MOD = 1e9 + 7;
int n, m, k, ans;
int dp[M][N][M];
string s, t;
ull hs[N], ht[N], pw[N];
ull Cs(int l, int r) {
return hs[r] - hs[l - 1] * pw[r - l + 1];
}
ull Ct(int l, int r) {
return ht[r] - ht[l - 1] * pw[r - l + 1];
}
int main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin >> n >> m >> k >> s >> t;
s = '#' + s, t = '#' + t;
for (int i = 1; i <= n; i++) {
hs[i] = hs[i - 1] * P + s[i];
}
for (int i = 1; i <= m; i++) {
ht[i] = ht[i - 1] * P + t[i];
}
pw[0] = 1;
for (int i = 1; i <= max(n, m); i++) {
pw[i] = pw[i - 1] * P;
}
dp[0][0][0] = 1;
for (int i = 1; i <= k; i++) {
for (int y = 0; y <= m; y++) {
for (int x = 0; x <= n; x++) {
dp[i - 1][x + 1][y] = (dp[i - 1][x + 1][y] + dp[i - 1][x][y]) % MOD;
}
}
for (int l = 1; l <= m; l++) {
for (int j = l; j <= n; j++) {
for (int y = 0; y < l; y++) {
if (Cs(j - l + y + 1, j) == Ct(y + 1, l) && j - l + y >= 0) {
dp[i][j][l] = (dp[i][j][l] + dp[i - 1][j - l + y][y]) % MOD;
// for (int x = 0; x < j - l + y + 1; x++) {
// dp[i][j][l] = (dp[i][j][l] + dp[i - 1][x][y]) % MOD;
// }
}
}
}
}
}
for (int i = 1; i <= n; i++) {
ans = (ans + dp[k][i][m]) % MOD;
}
cout << ans;
return 0;
}
此时,对于一个状态,可以发现合法的 \(y\) 是一个后缀,类似的,用前缀和解决。于是,我们可以对于每个状态二分加哈希找到后缀长度,但是这样要带 \(\log\),观察到 \(y\) 的后缀的长度与 \(i\) 这一位状态无关,所以可以枚举 \(j, k\) 再二分加哈希预处理。然后可以在原来的基础上再套一层前缀和,并且要注意由于原来 \(x\) 的前缀的长度是随而 \(y\) 变化的,所以在二次前缀和时也要是随 \(y\) 变化的。最后滚动一下即可。
预处理 \(O(nm \log m)\),枚举状态 \(O(nmw)\),前缀和 \(O(nmw)\),转移 \(O(1)\),总共 \(O(nmw)\)。\(100\) pts。
/**
dp[i][j][k] i 段 n 到 j m 到 k
dp[i][j][k] += dp[i - 1][x][y]
y < k && x < j - len + 1 && s[j - len + 1][j] == t[y + 1][k]
*/
#include<bits/stdc++.h>
using namespace std;
using ull = unsigned long long;
const ull N = 1e3 + 5, M = 2e2 + 5, P = 131, MOD = 1e9 + 7;
int n, m, k, ans;
int dp[M][N][M], len[N][M], res[N][M];
string s, t;
ull hs[N], ht[N], pw[N];
ull Cs(int l, int r) {
return hs[r] - hs[l - 1] * pw[r - l + 1];
}
ull Ct(int l, int r) {
return ht[r] - ht[l - 1] * pw[r - l + 1];
}
int main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin >> n >> m >> k >> s >> t;
s = '#' + s, t = '#' + t;
for (int i = 1; i <= n; i++) {
hs[i] = hs[i - 1] * P + s[i];
}
for (int i = 1; i <= m; i++) {
ht[i] = ht[i - 1] * P + t[i];
}
pw[0] = 1;
for (int i = 1; i <= max(n, m); i++) {
pw[i] = pw[i - 1] * P;
}
for (int i = 1; i <= n; i++) {//预处理
for (int j = 1; j <= min(m, i); j++) {
int l = 0, r = j;
while (l < r) {//二分 + 哈希
int mid = l + r + 1 >> 1;
if (Cs(i - mid + 1, i) == Ct(j - mid + 1, j)) {
l = mid;
} else {
r = mid - 1;
}
}
len[i][j] = l;
}
}
dp[0][0][0] = 1;
for (int i = 1; i <= k; i++) {
int id = i & 1, _id = 1 - id;
for (int y = 0; y <= m; y++) {
for (int x = 0; x <= n; x++) {//x的前缀和
dp[_id][x + 1][y] = (dp[_id][x + 1][y] + dp[_id][x][y]) % MOD;
}
}
for (int w = 0; w <= n; w++) {
res[w][0] = dp[_id][w][0];
}
for (int y = 0; y <= m; y++) {//y 的前缀和
for (int w = 0; w <= n; w++) {//w + y + 1:x 随 y 变化
res[w][y + 1] = (res[w][y] + dp[_id][w + y + 1][y + 1]) % MOD;
}
}
for (int l = 1; l <= m; l++) {
for (int j = l; j <= n; j++) {
int u = l - len[j][l];
dp[id][j][l] = (res[j - l][l - 1] - (u == 0 ? 0 : res[j - l][u - 1]) + MOD) % MOD;
}
}
for (int j = 0; j <= n; j++) {//清空一下
for (int l = 0; l <= m; l++) {
dp[_id][j][l] = 0;
}
}
}
for (int i = 1; i <= n; i++) {
ans = (ans + dp[k & 1][i][m]) % MOD;
}
cout << ans;
return 0;
}

浙公网安备 33010602011771号