题目大意
给你一个字符串s,问长度为2n并且s为其子串的字符串有多少个。
解题思路
一开始我想了几种状态方案,都有重复的情况,后来看了题解才知道还能这样做。我们设dp数组为dp[i][j][k],分别表示在长度为2n的字符串的第i位,括号序列的匹配度为j(遇到左括号+1,遇到右括号-1),并且这个字符串的后缀(其实不是严格的后缀,也可以包含首位)与s的前缀匹配了k个字符时的方案数。这样设计状态之后,我们在添加括号的过程中,不仅j会改变,k的值也可以改变,我们可以预处理出来在匹配了k个字符之后再在这个字符串末尾添加左括号或者右括号时它的后缀与s的前缀的最大匹配位数,设这个函数为f(k, 左括号 or 右括号),那么状态转移方程就可以写成:
dp[i+1][j+1][f[k][0]] += dp[i][j][k];
dp[i+1][j-1][f[k][1]] += dp[i][j][k];
注意如果之前的后缀匹配的长度和s的长度相等了,即使再往后添加括号,也不用再改变匹配的长度了。
这个方案很好的避免了同一方案被重复计算的问题,比如字符串s为"(("对于方案"(((())))"来说,如果会重复计算的话,可能会被记成4种方案,但是如果用上面的方法,这一方案只能是两个字符串前两个字符匹配的情况,就不会再被重复算进去了。
代码
#include<bits/stdc++.h>
#define endl '
'
#define x first
#define y second
#define clr(arr,a) memset(arr, a, sizeof(arr))
#define IOS ios::sync_with_stdio(false)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<ll, ll> P;
typedef pair<int, int> Pll;
const double pi = acos(-1.0);
const double eps = 1e-8;
const int MOD = 1e9+7;
const int INF = 0x3f3f3f3f;
const int maxn = 2e2+10;
const int maxm = 2e6+10;
char s[maxn];
int nxt[maxn], f[maxn][2];
ll dp[maxn][maxn][maxn];
void kmp() {
int len = strlen(s+1);
int i = 1, j = 0; nxt[1] = 0;
while(i<=len) {
while(j && s[i]!=s[j]) j = nxt[j];
//cout << s[i] << ' ' << s[j+1] << endl;
nxt[++i] = ++j;
}
for (int i = 0; i<len; ++i) {
int p = i+1;
int q = i+1;
while(p && s[p]!='(') p = nxt[p];
f[i][0] = p;
while(q && s[q]!=')') q = nxt[q];
f[i][1] = q;
}
f[len][0] = f[len][1] = len;
}
int main() {
int n; cin >> n >> s+1;
kmp();
n <<= 1;
dp[0][0][0] = 1;
int len = strlen(s+1);
for (int i = 0; i<n; ++i)
for (int j = 0; j<=n; ++j)
for (int k = 0; k<=len; ++k) {
if (j+1<=n) dp[i+1][j+1][f[k][0]] = (dp[i+1][j+1][f[k][0]]+dp[i][j][k])%MOD;
if (j) dp[i+1][j-1][f[k][1]] = (dp[i+1][j-1][f[k][1]]+dp[i][j][k])%MOD;
}
cout << dp[n][0][len] << endl;
return 0;
}