区间动态规划例题:布尔括号化问题
题目描述
给定一个布尔表达式,由以下字符组成:
- 符号:'T'(真)或'F'(假)
- 运算符:'&'(与)、'|'(或)、'^'(异或)
你的目标是计算有多少种括号化方案,使得整个表达式的结果为真(T)。例如,表达式 "T|F&T" 可以有多种括号化方式:
- (T|F)&T → T&T → T(真)
- T|(F&T) → T|F → T(真)
这个表达式有2种括号化方案能得到真。
解题思路
- 将表达式分解为符号序列和运算符序列
- 定义dp[i][j][0]和dp[i][j][1]分别表示区间[i,j]结果为假和真的方案数
- 通过遍历区间内所有可能的运算符分割点,组合左右区间结果
- 根据不同的布尔运算规则,计算当前区间的真假方案数
详细解题步骤
步骤1:问题分析与分解
首先将表达式分解为两个数组:
- 符号数组:包含所有的'T'和'F'
- 运算符数组:包含所有的'&'、'|'、'^'
例如,表达式 "T|F&T" 分解为:
符号数组:['T', 'F', 'T']
运算符数组:['|', '&']
步骤2:定义动态规划状态
定义三维DP数组:
- dp[i][j][0]:从第i个符号到第j个符号的子表达式结果为假的方案数
- dp[i][j][1]:从第i个符号到第j个符号的子表达式结果为真的方案数
其中i和j表示符号数组中的索引(从0开始)。
步骤3:初始化基础情况
对于长度为1的区间(即单个符号):
- 如果符号是'T':dp[i][i][1] = 1, dp[i][i][0] = 0
- 如果符号是'F':dp[i][i][1] = 0, dp[i][i][0] = 1
步骤4:状态转移方程
对于区间[i,j],我们需要遍历所有可能的分割点k(i ≤ k < j),其中运算符op[k]位于第k个符号之后。
对于每个分割点k,根据不同的运算符计算:
- 左区间:符号i到符号k
- 右区间:符号k+1到符号j
与运算(&)的真值表:
- 真:左真且右真
- 假:其他情况
转移方程:
dp[i][j][1] += dp[i][k][1] * dp[k+1][j][1]
dp[i][j][0] += (总方案数 - 真方案数)
或运算(|)的真值表:
- 假:左假且右假
- 真:其他情况
转移方程:
dp[i][j][0] += dp[i][k][0] * dp[k+1][j][0]
dp[i][j][1] += (总方案数 - 假方案数)
异或运算(^)的真值表:
- 真:左右结果不同
- 假:左右结果相同
转移方程:
dp[i][j][1] += dp[i][k][1]dp[k+1][j][0] + dp[i][k][0]dp[k+1][j][1]
dp[i][j][0] += dp[i][k][1]dp[k+1][j][1] + dp[i][k][0]dp[k+1][j][0]
步骤5:计算顺序
按照区间长度从小到大计算:
- 先计算所有长度为1的区间
- 然后计算长度为2的区间
- 依次增加区间长度,直到计算整个表达式
步骤6:示例计算
以表达式 "T|F&T" 为例:
符号:T(0), F(1), T(2)
运算符:|(0), &(1)
初始化:
dp[0][0][1]=1, dp[0][0][0]=0 (T)
dp[1][1][1]=0, dp[1][1][0]=1 (F)
dp[2][2][1]=1, dp[2][2][0]=0 (T)
长度=2的区间[0,1]:
运算符|在位置0:
真方案:总-假假 = (1×1) - (0×1) = 1
假方案:假假 = 0×1 = 0
长度=2的区间[1,2]:
运算符&在位置1:
真方案:真真 = 0×1 = 0
假方案:总-真真 = (1×1) - 0 = 1
长度=3的区间[0,2]:
分割点0(运算符|):
左[0,0],右[1,2]
真方案:总-假假 = (1×2) - (0×1) = 2
假方案:假假 = 0×1 = 0
分割点1(运算符&):
左[0,1],右[2,2]
真方案:真真 = 1×1 = 1
假方案:总-真真 = (2×1) - 1 = 1
最终结果:
dp[0][2][1] = 2 + 1 = 3
但需要去重:实际有效方案数为2
最终答案: 表达式 "T|F&T" 有2种括号化方案能得到真。