布尔括号化问题(统计真值表达式为true的方法数)
题目描述
给定一个布尔表达式,由字符 'T'(true)、'F'(false)、'&'(AND)、'|'(OR)和 '^'(XOR)组成,表达式中的符号(操作数)和操作符以交替顺序排列。例如,一个有效的表达式是 "T|F&T^F"。我们的目标是,通过给这个表达式添加括号来改变其运算顺序,计算出有多少种不同的括号化方式,使得整个表达式的结果为 true。
例如,对于表达式 "T|F":
- 不加括号:T | F -> true
- 只有一种方式,结果为 true,所以方式数为 1。
对于表达式 "T^F^F":
- 方式1: T ^ (F ^ F) -> T ^ (false) -> true
- 方式2: (T ^ F) ^ F -> (true) ^ F -> true
- 有两种方式,结果都为 true,所以方式数为 2。
解题过程
-
问题分析与状态定义
这是一个典型的区间动态规划问题。我们需要计算一个区间(即子表达式)在某种运算下得到 true 或 false 的方法数。
定义dp[i][j][0]表示在子表达式s[i:j](包含索引 i 和 j)之间,通过添加括号使得该子表达式计算结果为false的方法数。
定义dp[i][j][1]表示在子表达式s[i:j]之间,通过添加括号使得该子表达式计算结果为true的方法数。
这里,索引 i 和 j 必须都是代表操作数('T' 或 'F')的位置,并且 j - i 是偶数(因为操作数和操作符交替,子表达式的长度必须是奇数)。 -
确定区间长度与遍历顺序
我们按区间长度由小到大进行动态规划。最小的区间长度是 1,即单个操作数。- 如果
s[i] == 'T',那么dp[i][i][1] = 1,dp[i][i][0] = 0。 - 如果
s[i] == 'F',那么dp[i][i][1] = 0,dp[i][i][0] = 1。
然后,我们考虑长度为 3, 5, 7, ... 的区间。对于一个较长的区间[i, j],它最终总是通过一个操作符来连接左右两个子表达式。
- 如果
-
状态转移方程
对于区间[i, j],我们枚举区间内最后一个被运算的操作符的位置 k。k 必须是操作符的位置,并且 k 将区间[i, j]分割成左子区间[i, k-1]和右子区间[k+1, j]。
左子区间的结果可以是 true 或 false,右子区间的结果也可以是 true 或 false。根据操作符s[k]的真值表,我们可以计算出当前区间在最后进行s[k]运算时,得到 true 和 false 的方法数。
具体来说,对于每种操作符:- 与操作 &:结果为 true 当且仅当左右都为 true。
true_count = left_true * right_true
结果为 false 的情况有多种:左真右假、左假右真、左假右假。
false_count = left_true * right_false + left_false * right_true + left_false * right_false - 或操作 |:结果为 false 当且仅当左右都为 false。
false_count = left_false * right_false
结果为 true 的情况有多种:左真右假、左假右真、左真右真。
true_count = left_true * right_false + left_false * right_true + left_true * right_true - 异或操作 ^:结果为 true 当左右不同。
true_count = left_true * right_false + left_false * right_true
结果为 false 当左右相同。
false_count = left_true * right_true + left_false * right_false
然后,我们将所有可能的 k(即所有最后一个操作符的位置)对应的 true_count 和 false_count 分别累加,得到最终的
dp[i][j][1]和dp[i][j][0]。
状态转移方程可以概括为:
dp[i][j][1] += sum_over_k( 根据 s[k] 计算出的 true_count )
dp[i][j][0] += sum_over_k( 根据 s[k] 计算出的 false_count ) - 与操作 &:结果为 true 当且仅当左右都为 true。
-
算法步骤
a. 获取字符串长度 n。
b. 初始化一个三维数组dp,维度为n x n x 2,初始值设为 0。
c. 初始化:遍历所有单个操作数(i 从 0 到 n-1,步长为 2)。
- 如果s[i] == 'T':dp[i][i][1] = 1,dp[i][i][0] = 0
- 如果s[i] == 'F':dp[i][i][1] = 0,dp[i][i][0] = 1
d. 区间DP循环:
- 外层循环length从 3 开始,到 n 结束,每次步长为 2(因为区间长度必须是奇数)。
- 内层循环,区间的起始索引i从 0 开始,到n - length结束。
- 区间的结束索引j = i + length - 1。
- 初始化dp[i][j][0]和dp[i][j][1]为 0。
- 内层循环,枚举最后一个操作符的位置k。k从i+1开始,到j-1结束,步长为 2(因为操作符在奇数索引位置)。
- 获取左子区间的结果:left_true = dp[i][k-1][1],left_false = dp[i][k-1][0]
- 获取右子区间的结果:right_true = dp[k+1][j][1],right_false = dp[k+1][j][0]
- 根据s[k]是'&','|'还是'^',计算当前分割点 k 贡献的 true 和 false 的方法数。
- 将计算出的 true 方法数加到dp[i][j][1]上。
- 将计算出的 false 方法数加到dp[i][j][0]上。
e. 最终结果存储在dp[0][n-1][1]中,即整个表达式计算结果为 true 的方法数。 -
复杂度分析
- 时间复杂度:O(n³)。有三层循环,外层循环区间长度 O(n),中层循环起点 O(n),内层循环分割点 O(n)。
- 空间复杂度:O(n²)。用于存储 dp 表。
通过这个循序渐进的推导和计算,我们就能准确地统计出使得整个布尔表达式为 true 的所有括号化方式的数量。