题目描述
对于计算机程序而言,验证一个表达式的括号是否匹配是简单的任务,但对于人眼来说却可能相当费力。为了保护眼睛,许多编辑器和电子表格应用程序使用颜色或加粗字体来显示括号的嵌套结构。然而,在我们的古老控制台应用程序中,我们既没有颜色,也没有不同的字体。我们的朴素建议是:我们可能没有颜色,但我们有不同的字母。如果我们用完了字母,我们还可以使用回文!
根据我们的建议,我们可以说 "abbaddcc" 是一个正确嵌套的括号结构,因为它可以解释为 "(ab ba) (d d) (c c)"。然而,很快就会发现,用这种方式表示的括号结构可能是模糊的。例如,"aaabba" 可以解释为 "(a (a a) (b b) a)" 或 "(a a) (ab ba)" ,而之前的表达式 "abbaddcc" 只有一种解释。
对于这个问题,你需要编写一个计算机程序,检查一个以回文方式定义的括号结构是否有效,并且是否具有唯一的解释。
输入格式
输入包含多个测试用例。第一行包含一个整数 TTT (1≤T≤201 \leq T \leq 201≤T≤20) 表示测试用例的数量。接下来是 TTT 个测试用例,每个测试用例包含一个仅由小写字母组成的字符串 SSS。可以假设没有字符串的长度会超过 100100100 个字符。
输出格式
每个测试用例的输出以测试用例编号开始,格式为 Case x: ,其中 1≤x≤T1 \leq x \leq T1≤x≤T。然后跟着以下三种字符串之一:
Invalid—— 括号结构不平衡Valid, Unique—— 括号结构平衡且具有唯一解释Valid, Multiple—— 括号结构平衡但具有多种解释
样例输入
3
aaabba
aabb
bbababba
样例输出
Case 1: Valid, Multiple
Case 2: Valid, Unique
Case 3: Invalid
题目分析
本题要求判断一个字符串是否能被解释为"回文括号结构",并判断解释是否唯一。题目中的"回文括号结构"定义如下:
- 每个括号对对应一个回文段,该回文段的第一个字符和最后一个字符相同
- 括号可以嵌套,也可以并列
- 整个字符串必须被完全划分为合法的括号结构
关键观察
-
字符配对约束:由于每个括号对的开头和结尾必须是相同字符,因此字符串中每个字符的出现次数必须是偶数次。这是解题的必要条件。
-
长度约束:由于括号是成对出现的,整个字符串的长度必须是偶数。同样,任何有效的括号子结构的长度也必须是偶数。
-
匹配约束:只有相同字符才能配对一个括号。这意味着如果位置 lll 的字符要与位置 rrr 的字符匹配,那么必须满足 s[l]=s[r]s[l] = s[r]s[l]=s[r] 。
-
结构约束:括号结构可以是嵌套的,也可以是并列的。这意味着我们需要考虑两种可能:
- 整个区间作为一个括号对(如果首尾字符相同)
- 在某个位置切分,形成两个独立的括号结构
解题思路
本题可以使用 区间动态规划 来解决。我们定义状态 dp[l][r]dp[l][r]dp[l][r] 表示子串 s[l...r]s[l...r]s[l...r] 的括号化可能性:
- dp[l][r]=0dp[l][r] = 0dp[l][r]=0 :子串 s[l...r]s[l...r]s[l...r] 无法被括号化
- dp[l][r]=1dp[l][r] = 1dp[l][r]=1 :子串 s[l...r]s[l...r]s[l...r] 可以被括号化且只有一种方式
- dp[l][r]=2dp[l][r] = 2dp[l][r]=2 :子串 s[l...r]s[l...r]s[l...r] 可以被括号化且有多种方式
状态转移需要考虑两种情况:
情况一:整个区间作为一个括号对
如果 s[l]=s[r]s[l] = s[r]s[l]=s[r] ,那么整个区间 [l,r][l, r][l,r] 可以作为一个括号对,此时:
dp[l][r]=dp[l+1][r−1]
dp[l][r] = dp[l+1][r-1]
dp[l][r]=dp[l+1][r−1]
前提是内部区间 [l+1,r−1][l+1, r-1][l+1,r−1] 必须是有效的括号结构。
情况二:在位置 kkk 切分
如果存在位置 kkk (l<k<rl < k < rl<k<r)使得 s[l]=s[k]s[l] = s[k]s[l]=s[k] ,那么区间 [l,r][l, r][l,r] 可以被切分为 [l,k][l, k][l,k] 和 [k+1,r][k+1, r][k+1,r] 两个独立的括号结构。此时:
dp[l][r]=dp[l][k]×dp[k+1][r]
dp[l][r] = dp[l][k] \times dp[k+1][r]
dp[l][r]=dp[l][k]×dp[k+1][r]
注意这里是乘法,因为如果左边有 xxx 种方式,右边有 yyy 种方式,那么整体就有 x×yx \times yx×y 种方式。
唯一性判断
在计算过程中,我们需要特别注意唯一性的判断:
- 如果两种情况都产生有效的括号化,那么一定存在多种解释
- 如果只有一种情况产生有效的括号化,但该情况内部有多个解释,那么整体也是多个解释
- 只有当恰好有一种方式,且该方式内部只有一种解释时,整体才是唯一的
算法步骤
-
预处理检查
- 检查字符串长度是否为偶数,如果不是,直接输出
Invalid - 检查每个字符出现次数是否为偶数,如果不是,直接输出
Invalid
- 检查字符串长度是否为偶数,如果不是,直接输出
-
动态规划计算
- 初始化 dpdpdp 数组为 −1-1−1 (未计算状态)
- 使用记忆化搜索计算 dp[0][n−1]dp[0][n-1]dp[0][n−1]
- 注意区间长度必须是偶数
-
结果输出
- 如果 dp[0][n−1]=0dp[0][n-1] = 0dp[0][n−1]=0 ,输出
Invalid - 如果 dp[0][n−1]=1dp[0][n-1] = 1dp[0][n−1]=1 ,输出
Valid, Unique - 如果 dp[0][n−1]=2dp[0][n-1] = 2dp[0][n−1]=2 ,输出
Valid, Multiple
- 如果 dp[0][n−1]=0dp[0][n-1] = 0dp[0][n−1]=0 ,输出
时间复杂度分析
- 状态数:O(n2)O(n^2)O(n2) ,其中 nnn 是字符串长度(n≤100n \leq 100n≤100)
- 状态转移:O(n)O(n)O(n) ,需要枚举切分位置 kkk
- 总时间复杂度:O(n3)O(n^3)O(n3) ,在 n=100n=100n=100 时完全可行
代码实现
// Parenthesizing Palindromes
// UVa ID: 10788
// Verdict: Accepted
// Submission Date: 2025-12-18
// UVa Run Time: 0.000s
//
// 版权所有(C)2025,邱秋。metaphysis # yeah dot net
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 105;
int dp[MAXN][MAXN];
string s;
int dfs(int l, int r) {
// 区间长度必须为偶数
if ((r - l + 1) % 2) return 0;
// 当为相邻两个字符,如果相等则表明可以组成一对括号,否则无法构成合法的括号
if (l + 1 == r) return s[l] == s[r];
// 返回备忘值
if (~dp[l][r]) return dp[l][r];
int &answer = dp[l][r];
answer = 0;
if (s[l] == s[r]) answer = dfs(l + 1, r - 1);
if (answer > 2) answer = 2;
for (int i = l + 1; i < r; i++) {
if (s[l] == s[i]) answer += dfs(l, i) * dfs(i + 1, r);
if (answer > 2) answer = 2;
}
return answer;
}
int main() {
int T;
cin >> T;
for (int t = 1; t <= T; ++t) {
cout << "Case " << t << ": ";
cin >> s;
int n = s.size();
if (n % 2) { cout << "Invalid\n"; continue; }
memset(dp, -1, sizeof(dp));
int answer = dfs(0, n - 1);
if (answer == 0) cout << "Invalid\n";
else if (answer == 1) cout << "Valid, Unique\n";
else cout << "Valid, Multiple\n";
}
return 0;
}
代码解释
核心函数 dfs(int l, int r)
这个函数使用记忆化搜索实现动态规划:
-
边界条件处理
- 如果区间长度为奇数,直接返回 000 (无效)
- 如果区间长度为 222 ,检查两个字符是否相同,相同则返回 111 ,否则返回 000
-
记忆化检查
- 如果 dp[l][r]dp[l][r]dp[l][r] 已经计算过(不为 −1-1−1 ),直接返回
-
状态转移
- 首先考虑整个区间作为一个括号对:如果 s[l]=s[r]s[l] = s[r]s[l]=s[r] ,则继承内部区间 [l+1,r−1][l+1, r-1][l+1,r−1] 的状态
- 然后枚举所有可能的切分点 iii ,如果 s[l]=s[i]s[l] = s[i]s[l]=s[i] ,则计算左右子区间的组合方案数
- 将方案数限制在 222 以内(因为我们只关心是否超过 111 )
主函数 main()
- 读取测试用例数量 TTT
- 对于每个测试用例:
- 输出测试用例编号
- 读取字符串 sss
- 检查长度是否为偶数,不是则直接输出
Invalid - 初始化 dpdpdp 数组为 −1-1−1
- 调用
dfs(0, n - 1)计算方案数 - 根据方案数输出相应结果
注意事项
-
题目定义的模糊性:本题的定义存在一定的模糊性,不同的合理实现可能得到不同的结果。上述代码是通过 UVA 在线评测系统的版本,但读者需要注意,其他合理的实现方式也可能被接受。
-
性能优化:代码中将方案数限制在 222 以内,这避免了可能的整数溢出,也减少了计算量。
-
记忆化搜索:使用
~dp[l][r]检查是否已计算,这是 C++\texttt{C++}C++ 中检查 −1-1−1 的简洁写法(~(-1) = 0)。
总结
本题是一个有趣的区间动态规划问题,结合了括号匹配和回文的特性。解题的关键在于理解题目中"回文括号结构"的定义,并设计合适的状态转移方程。虽然题目定义存在一定的模糊性,但通过合理的动态规划设计,我们可以有效地解决这个问题。
对于类似的括号匹配问题,动态规划通常是一个有效的解决方案。读者可以通过本题加深对区间动态规划的理解,并学习如何处理具有多种解释的结构化字符串问题。
285

被折叠的 条评论
为什么被折叠?



