首先观察数据范围,一眼 \(\mathcal O(n^3)\),然后再观察题目,你感觉它是个区间 dp,那么恭喜你,你的感觉是对的。
然后你直接一个区间 dp 板子拍上去,设 \(dp_{i, j}\) 表示区间 \([i, j]\) 的方案数,那么转移很显然,若 \(i,j\) 能够匹配,则可以将 \([i + 1, j - 1]\) 包起来,然后计算由两个串拼起来的。
然后你测一下样例,发现你错了。接着你模拟了一下样例,发现你算重了,例如样例 1:()()()
,你输出了 2
。
你发现 \(\texttt{()()()} = \texttt{()} + \texttt{()()}\) 或 \(\texttt{()()} + \texttt{()}\),于是你会统计两遍。也就是对于由两个匹配串拼起来的串,若其中的某一个串也是由某些子串拼起来的,就会算重。
于是你开始寻找原因。设 \(s_{[i,j]} = s_{[i,k_1]} + s_{[k_1+1,j]},s_{[k_1+1,j]}=s_{[k_1+1,k_2]}+s_{[k_2+1,j]}\),其中 \(s_{[i,k_1]},s_{[k_1+1,j]},s_{[k_1+1,k_2]},s_{[k_2+1,j]}\) 都是括号匹配串,那么也一定有 \(s_{[i,j]} = s_{[i,k_2]} + s_{[k_2+1,j]},s_{[i,k_2]}=s_{[i,k_1]}+s_{[k_1+1,k_2]}\),于是 \(s_{[k_1+1, k_2]}\) 就算重了。
然后你开始想办法让它只算一次。可以钦定 \(s_{[k_1+1,k_2]}\) 只在 \(s_{[k_1+1,r]}\) 中出现,那么 \(s_{[i,k]}\) 就一定不是拼接而成的括号串。于是拼接的方式形如"不拼接串+拼接串"。
现在你知道如何去重了,于是你开始修改状态。设 \(dp_{i,j,0/1}\) 表示 \([i,j]\) 不是/是拼接串的方案数。然后你将转移修改。
其中 \(check(i, j)\) 表示 \(i,j\) 是否能匹配。若 \(s_i = s_j = \texttt{?}\),则 \(check(i, j) = 3\)。
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 505, Mod = 1e5;
int n, dp[N][N][2];
bool f;
string s;
bool check(int i, int j)
{return (s[i] == '(' && s[j] == ')' || s[i] == '[' && s[j] == ']' || s[i] == '{' && s[j] == '}' || (s[i] == '?' && (s[j] == ')' || s[j] == ']' || s[j] == '}') || ((s[i] == '(' || s[i] == '[' || s[i] == '{') && s[j] == '?')));
}
int add(int x, int y)
{if(x + y >= Mod)f = 1;return x + y >= Mod ? x + y - Mod : x + y;
}
signed main()
{cin >> n >> s;s = ' ' + s;for(int i = 1; i < n; i++)if(s[i] == s[i + 1] && s[i] == '?')dp[i][i + 1][0] = 3;else if(check(i, i + 1)) dp[i][i + 1][0] = 1;for(int len = 4; len <= n; len += 2)for(int i = 1; i + len - 1 <= n; i++){int j = i + len - 1;if(s[i] == s[j] && s[i] == '?')dp[i][j][0] = add(dp[i + 1][j - 1][0], dp[i + 1][j - 1][1]) * 3 % Mod;else if(check(i, j))dp[i][j][0] = add(dp[i + 1][j - 1][0], dp[i + 1][j - 1][1]);for(int k = i + 1; k < j; k += 2)dp[i][j][1] = add(dp[i][j][1], dp[i][k][0] * add(dp[k + 1][j][1], dp[k + 1][j][0]) % Mod);}if(f)return printf("%05lld", add(dp[1][n][0], dp[1][n][1])), 0;cout << add(dp[1][n][0], dp[1][n][1]);return 0;
}
P.S:这是我在考场时的心路历程,且我认为大部分人在第一次做此题时都是如此,于是我使用了第二人称。