洛谷 P7469 [NOI Online 2021 提高组] 积木小赛(民间数据) 题解
一、题目:
二、思路:
简化题意,求出在\(t\)串中有多少个本质不同的子串与\(s\)串中的某个子序列相同。
看到子串,当然先想后缀自动机。
考虑在SAM上动态规划。
设\(dp(i, x)\)表示考虑了\(s\)串的前\(i\)个字符,在后缀自动机的\(substrings(x)\)中所能与\(s\)串子序列匹配的最大长度。
例如\(substrings(x)=\{"abcbc","bcbc","cbc","bc"\}\),\(s[1\sim 8]="ebcbcbda"\)。则\(dp(8, x) = 4\)。
对于任意一个状态\(st\),很容易发现一个性质:若\(substrings(st)\)中有长度为\(len\)的一个后缀可以被匹配,则长度为\(len-1,len-2,...,minlen(st)\)这些后缀都可以被匹配。
所以最后的答案\(ans = \sum_{st}(dp(n,st)-minlen(st) + 1)\)。
哦,忘了讲怎么转移了。
\(dp(i,x)=\max_{node[y].ch[s_i]=x}\{dp(i-1,y)\}+1\)。
当然第一维可以滚动掉。
三、代码:
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int maxn = 3005;
int n, las = 1, tot = 1;
int dp[maxn << 1], tmp[maxn << 1];
char s[maxn], t[maxn];
struct Node {
int len, fa;
int ch[27];
}node[maxn << 1];
inline void extend(int c) {
int v = las, z = ++ tot; las = tot;
node[z].len = node[v].len + 1;
for (; v && node[v].ch[c] == 0; v = node[v].fa) node[v].ch[c] = z;
if (!v) node[z].fa = 1;
else {
int x = node[v].ch[c];
if (node[x].len == node[v].len + 1) node[z].fa = x;
else {
int y = ++ tot;
node[y] = node[x];
node[y].len = node[v].len + 1;
node[x].fa = node[z].fa = y;
for (; v && node[v].ch[c] == x; v = node[v].fa) node[v].ch[c] = y;
}
}
}
inline void chkmax(int &x, int y) {
x = y > x ? y : x;
}
int main() {
scanf("%d", &n);
scanf("%s", s + 1);
scanf("%s", t + 1);
for (int i = 1; i <= n; ++ i) extend(t[i] - 'a' + 1);
for (int i = 1; i <= n; ++ i) {
int c = s[i] - 'a' + 1;
for (int j = 1; j <= tot; ++ j) tmp[j] = dp[j];
for (int j = 1; j <= tot; ++ j) {
if (node[j].ch[c] != 0) {
chkmax(dp[node[j].ch[c]], tmp[j] + 1);
}
}
}
int ans = 0;
for (int i = 2; i <= tot; ++ i) {
ans += max(0, dp[i] - node[node[i].fa].len);
}
printf("%d\n", ans);
return 0;
}