[CSP-J 2022] 逻辑表达式

题目描述:

逻辑表达式是计算机科学中的重要概念和工具,包含逻辑值、逻辑运算、逻辑运算优先级等内容。

在一个逻辑表达式中,元素的值只有两种可能:0(表示假)和 1(表示真)。元素之间有多种可能的逻辑运算,本题中只需考虑如下两种:“与”(符号为 &)和“或”(符号为 |)。其运算规则如下:

0&0=0&1=1&0=0,1&1=1;
0 | 0=0,0∣1=1∣0=1∣1=1。

在一个逻辑表达式中还可能有括号。规定在运算时,括号内的部分先运算;两种运算并列时,& 运算优先于 | 运算;同种运算并列时,从左向右运算。

比如,表达式 0|1&0 的运算顺序等同于 0|(1&0);表达式 0&1&0|1 的运算顺序等同于 ((0&1)&0)|1

此外,在 C++ 等语言的有些编译器中,对逻辑表达式的计算会采用一种“短路”的策略:在形如 a&b 的逻辑表达式中,会先计算 a 部分的值,如果 a = 0,那么整个逻辑表达式的值就一定为 0,故无需再计算 b 部分的值;同理,在形如 a|b 的逻辑表达式中,会先计算 a 部分的值,如果 a = 1,那么整个逻辑表达式的值就一定为 1,无需再计算 b 部分的值。

现在给你一个逻辑表达式,你需要计算出它的值,并且统计出在计算过程中,两种类型的“短路”各出现了多少次。需要注意的是,如果某处“短路”包含在更外层被“短路”的部分内则不被统计,如表达式 1|(0&1) 中,尽管 0&1 是一处“短路”,但由于外层的 1|(0&1) 本身就是一处“短路”,无需再计算 0&1 部分的值,因此不应当把这里的 0&1 计入一处“短路”。

输入格式

输入共一行,一个非空字符串 ss 表示待计算的逻辑表达式。

输出格式

输出共两行,第一行输出一个字符 0 或 1,表示这个逻辑表达式的值;第二行输出两个非负整数,分别表示计算上述逻辑表达式的过程中,形如 a&b 和 a|b 的“短路”各出现了多少次。

输入输出样例

输入 #1复制

0&(1|0)|(1|1|1&0)

输出 #1复制

1
1 2

输入 #2复制

(0|1&0|1|1|(1|1))&(0&1&(1|0)|0|1|0)&0

输出 #2复制

0
2 3

说明/提示

【样例解释 #1】

该逻辑表达式的计算过程如下,每一行的注释表示上一行计算的过程:

0&(1|0)|(1|1|1&0)
=(0&(1|0))|((1|1)|(1&0)) //用括号标明计算顺序
=0|((1|1)|(1&0))   //先计算最左侧的 &,是一次形如 a&b 的“短路”
=0|(1|(1&0))       //再计算中间的 |,是一次形如 a|b 的“短路”
=0|1               //再计算中间的 |,是一次形如 a|b 的“短路”
=1

【样例 #3】

见附件中的 expr/expr3.in 与 expr/expr3.ans

【样例 #4】

见附件中的 expr/expr4.in 与 expr/expr4.ans

【数据范围】

设∣s∣ 为字符串 s 的长度。

对于所有数据,1≤∣s∣≤10^6。保证 s 中仅含有字符 01&|() 且是一个符合规范的逻辑表达式。保证输入字符串的开头、中间和结尾均无额外的空格。保证 ss 中没有重复的括号嵌套(即没有形如 ((a)) 形式的子串,其中 a 是符合规范的逻辑表 达式)。

测试点编号∣s∣≤特殊条件
1∼23
3∼45
520001
620002
720003
8∼102000
11∼1210^61
13∼1410^62
15∼1710^63
18∼2010^6

其中:
特殊性质 1 为:保证 s 中没有字符 &
特殊性质 2 为:保证 s 中没有字符 |
特殊性质 3 为:保证 s 中没有字符 ( 和 )

【提示】

以下给出一个“符合规范的逻辑表达式”的形式化定义:

  • 字符串 0 和 1 是符合规范的;
  • 如果字符串 s 是符合规范的,且 s 不是形如 (t) 的字符串(其中 t 是符合规范的),那么字符串 (s) 也是符合规范的;
  • 如果字符串 a 和 b 均是符合规范的,那么字符串 a&ba|b 均是符合规范的;
  • 所有符合规范的逻辑表达式均可由以上方法生成。

思路:

分治/伪表达式树:

vp 完的那个夜晚,大家讨论做法时都一致认定这道题在考表达式树,于是我就看了看他们发的代码。清一色的要么是建表达式树,最后跑一遍遍历,要么是用一个栈弹来弹去,还有厉害的则先将中缀表达式转成前/后缀表达式,然后再求值。

我还是太弱了,看着大佬们一百多行的代码陷入了沉思。

我的评价是:太抽象了!有没有一种方法既不用建表达式树,不用栈弹来弹去,也不用转成前/后缀表达式,实现直接在中缀表达式求值呢?

我们可以先来思考一下人类是怎么算中缀表达式的:

就先拿样例 1 举例:

0&(1|0)|(1|1|1&0)

人类会先看向第一层的 | 左边的 0&(1|0),再看向 & 左边的 0,此时形成了一个短路,所以这部分的值为 0。

此时式子为 0|(1|1|1&0)

此时 | 左边的值计算完毕,我们该看 第一层的 | 右边的 (1|1|1&0)

此时我们会先看到 1|1|1&0,发现第二层的 | 左边为 1,构成短路,此部分的值为 1,后面的操作都不用算。

此时式子变为 0|(1|1&0)

我们会看到 (1|1&0),发现又构成一次短路,此部分的值为 1。

此时式子会变成 0|1,最后算出答案为 1。

为什么要分析的那么仔细?因为我们等会就要实现通过模拟人脑计算中缀表达式的过程计算中缀表达式。

我们先来分析一下我们做的过程。我们会先找到第一层优先级最高的,先算这个符号的左半边,判断存不存在短路,如果不存在,继续找找到第二层优先级最高的,算这个符号的左半边,判断存不存在短路……以此类推。当不存在比他优先级更高的时候,计算这一层的值并将当前算式替换成这部分的值继续求解。

对于刚才样例的人脑操作,流程图如下。

我们会先找到第一层优先级最高的,也就是第一层的 | 左半边,然后我们进入左半边,找这个左半边里优先级最高的,自然是第二层的 &。看他的左半边,是一个 0,造成了短路,所以这一部分的值就是 0。然后我们回到第一层,第一层的左半边我们算完了,值为 0,但因为第一层是 |,所以不造成短路。我们继续找  号的右半边优先级最高的,发现是第二层的 | ,于是我们再去左半边找优先级最高的运算符,此时我们会找到第三层的 |,看他的左半边。发现没有运算符,只有一个数字,为 1!并且他右边的运算符为 |,构成一个短路,于是这部分的值就是 1。第三层算完了,我们回到第二层,发现是一个 |,并且我们刚才的计算结果为 1,于是又构成一次短路。此时回到第一层,我们只需要计算出 0|1 的结果就行,答案为 1。

我们来回顾一下刚才的过程。我们每次都会重复寻找当前层优先级最高的符号,并先到符号左边继续寻找当前层优先级最高的符号……直到当前层没有符号,即只有数字为止,此时我们当前层的结果就是这个数字,返回到上一层。此时我们通过刚才计算的结果和这一层的符号判断是否存在短路,如果没有短路就计算出符号右边的值后再计算在这一层的值,并回到上层。否则我们不用计算右边的值,直接回到上一层利用已算出的值继续计算……

把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并,这个算法叫什么?

对了!分治。

那我们该怎么分解问题呢?

我们每次会找到这一层不在括号里的最后一个运算级最低的运算符,然后递归到他的左半边和右半边分别求解,这一层的值就等于左半边的值和右半边的值做这个运算符的操作。(如果是 | 那么就是左半边的值 | 右半边的值,& 同理。

那问题来了,我们不是优先级高的先算吗?为什么要“找到这一层不在括号里的最后一个运算级最低的运算符”呢?

好问题!恰恰因为先找,所以才后算。递归是用类似于栈的先进后出机制,我们每次把运算级最低的先进栈,最后回溯的时候反而运算级最低的在最底下了。此时我们回溯就会先从运算级最高的开始算并返回到上一层了。大家可以在纸上画个表达式,自己玩一玩。

那如果这一层没有“不在括号里的最后一个运算级最低的运算符”怎么办?那就要分两种情况了。一种情况是整个算式都被括号包起来了,此时我们把括号去掉就行了。还有一种是只有数字,此时我们只要返回这个数字就行了,也就是我们分治的边界条件。

那我为什么要叫它伪表达式树呢?

来看这张图:

通过中缀表达式树的建立也可以用上面的代码实现——找到最后一个在括号外的符号,让他作为根节点,符号的左边就是他的左子树,右边就是他的右子树。但有没有必要把它存下来再遍历呢?多此一举!因为我们在遍历的时候就相当于在通过计算机能理解的顺序访问这棵树了,可以边访问边计算。

思路部分完结撒花!

错误代码:

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
#define N (int)1e6 + 1
char str[N];
int sum1;
int sum2;
int dfs(int l,int r) {
    int x = 0,orpos = 0, andpos = 0;//记录括号层数、最后一个 | 出现的位置、最后一个 & 出现的位置
    for (int i = l; i<=r; ++i) {//遍历左右区间查找运算符
        if (str[i] == '(') {
            ++x;//增加一层括号
        }else {
            if (str[i] == ')') {
                --x;//减少一层括号
            }else {
                if (!x) {//不在括号中
                    if (str[i] == '|') {
                        orpos = i;
                    }else {
                        if (str[i] == '&') {
                            andpos = i;
                        }
                    }
                }
            }
        }
    }
    if (orpos) {//注意,因为 | 比 &优先级低,要先判断存不存在 |
        if (str[orpos] == '|') {
            int tmp1 = dfs(l, orpos - 1);//遍历左区间
            if (tmp1 == 1) {//如果是 1,触发了 | 短路
                ++sum1;
                return 1;//不需要计算右区间,直接返回 1
            }else {
                int tmp2 = dfs(orpos + 1, r);//计算右区间
                return (tmp1 | tmp2);
            }
        }
    }
    if (andpos) {
        if (str[andpos] == '&') {
            int tmp1 = dfs(l, andpos - 1);
            if (tmp1 == 0) {//如果是 0,触发了 & 短路
                ++sum2;
                return 0;//不需要计算右区间,直接返回 0
            }else {
                int tmp2 = dfs(andpos + 1, r);
                return (tmp1 & tmp2);
            }
        }
    }
    //不在括号内的运算符不存在
    if (str[l] == '(' && str[r] == ')') {//如果都被括号包裹着
        return dfs(l + 1, r - 1);//去掉括号
    }else {
        return str[l] - '0';//否则左右区间一定重合,返回数字就行了
    }
}
int main(int argc, const char * argv[]) {
    scanf("%s",str + 1);
    int len = strlen(str + 1);
    int sum = dfs(1, len);
    printf("%d\n%d %d\n",sum, sum2, sum1);//注意!一定要开个 sum 把dfs结果先记下来,否则直接输出会导致 UB
    return 0;
}

 为什么会这样呢?

原因很简单——刚才的程序时间复杂度为 O(n^2)O(n2)。因为我们在每次分治求解的时候都遍历一遍字符串,时间效率太低。如果我们能预处理出来字符串,那时间复杂度将降低为 O(n)O(n)。

那我们怎么预处理呢?好办,我们对每个位置,处理出这个位置左侧和它同层的最后一个(离它最近的)运算符。这样一来,我们在递归时,想看这一层的最后一个运算符,也就只需访问与右边界同层的最后一个运算符就行了,省掉了每次都遍历一遍的过程。注意,别忘了判断一下这个运算符的位置是否在左边界的右边,否则就说明这一层没有运算符。

依然是结合代码讲解。

优化版:

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
#define N (int)1e6 + 1
char str[N];
int c1[N];
int c2[N];
int l1[N];
int l2[N];
int cnt1;
int cnt2;
int dfs(int l,int r) {//记得先看主函数的预处理
    if (c1[r] >= l) {//如果最后一个和 r 同层的 | 在 l 和 r 的范围内
        int ans = dfs(l, c1[r] - 1);
        if (ans == 1) {
            ++cnt1;
            return 1;
        }
        return (ans | dfs(c1[r] + 1, r));
    }
    if (c2[r] >= l) {//如果最后一个和 r 同层的 & 在 l 和 r 的范围内
        int ans = dfs(l, c2[r] - 1);
        if (ans == 0) {
            ++cnt2;
            return 0;
        }
        return (ans & dfs(c2[r] + 1, r));
    }
    if (str[l] == '(' && str[r] == ')') {
        return dfs(l + 1, r - 1);
    }
    return str[l] - '0';
}
int main(int argc, const char * argv[]) {
    scanf("%s",str + 1);
    int len = strlen(str + 1);
    int x = 0;//括号层数
    //l1[x] 代表目前最后一个在 x 层括号的 | 运算符
    //l2[x] 代表目前最后一个在 x 层括号的 & 运算符
    //c1[i] 代表目前和 i 同层的最后一个 | 运算符
    //c2[i] 代表目前和 i 同层的最后一个 & 运算符
    for (int i = 1; i<=len; ++i) {
        if (str[i] == '(') {
            ++x;
        }else if (str[i] == ')') {
            --x;
        }else if (str[i] == '|') {
            l1[x] = i;
        }else if (str[i] == '&') {
            l2[x] = i;
        }
        c1[i] = l1[x];//最后一个在 i 这个位置前且与 i 同层的 | 运算符
        c2[i] = l2[x];//最后一个在 i 这个位置前且与 i 同层的 & 运算符
    }
    int ans = dfs(1, len);
    printf("%d\n%d %d\n",ans,cnt2,cnt1);
    return 0;
}

总结:

  本题解亮点:不用建表达式树或者用栈,这道题没有那么难,大家都想复杂了

题目链接:

[CSP-J 2022] 逻辑表达式 - 洛谷https://www.luogu.com.cn/problem/P8815

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
### 回答1: 题目描述: 给定一个逻辑表达式,包含变量和运算符,其中变量用大写字母表示,运算符包括与(&)、或(|)、非(!)和异或(^),请输出该表达式的真值。 输入格式: 输入共一行,为一个字符串,表示逻辑表达式,字符串长度不超过 100。 输出格式: 输出共一行,为一个整数,表示该表达式的真值,1 表示真, 表示假。 输入样例: A&B&C|!D^E 输出样例: 1 解题思路: 本题可以使用栈来解决,具体思路如下: 1.首先定义两个栈,一个用来存储运算符,一个用来存储操作数。 2.遍历表达式中的每一个字符,如果是运算符,则将其压入运算符栈中,如果是操作数,则将其压入操作数栈中。 3.当遇到一个右括号时,从操作数栈中弹出两个操作数,从运算符栈中弹出一个运算符,进行运算,并将结果压入操作数栈中。 4.最终操作数栈中只剩下一个元素,即为表达式的真值。 代码实现: ### 回答2: 题目链接:https://www.luogu.com.cn/problem/CSP-J-2022 本题需要我们求得不含变量 `x` 的逻辑表达式的真值表。 首先分析题目中给出的逻辑运算符:`NOT`、`AND`、`OR`、`XOR`。这些逻辑运算符在计算机科学中很常见,它们分别表示取反、且、或、异或的运算。我们可以通过一个简单的真值表来表示它们的运算规则: |p|q|NOT p|p AND q|p OR q|p XOR q| |-|-|-----|------|------|-------| |0|0| 1 | 0 | 0 | 0 | |0|1| 1 | 0 | 1 | 1 | |1|0| 0 | 0 | 1 | 1 | |1|1| 0 | 1 | 1 | 0 | 通过这个真值表,我们可以很容易地推导出不同逻辑表达式的结果。例如,当 `p` 为真且 `q` 为假时,`p AND q` 的结果就是假;`p OR q` 的结果就是真。同理,当 `p` 和 `q` 相同的时候,它们的异或结果就是假,否则就是真。 而本题中的逻辑表达式是由多个运算符和 `x` 变量组成的,我们需要计算出真值表。为了避免直接枚举每一种情况,并逐个计算结果,我们可以采用二叉的思路,即每个节点代表一个逻辑运算符,左右子分别为该运算符的两个参数,直到叶节点为止,叶节点即为一个变量(可能是 `x` 或者是常量)。 具体操作方法可以采用递归实现。递归函数需要传入一个字符串表达式,返回该表达式是真还是假。具体实现细节可见代码。最终我们可以得到一个 1 行 2 的 n 次方 列的真值表。 代码: ### 回答3: 题目描述 给定 $m$ 个逻辑变量 $a_1, a_2, \cdots, a_m$,以及 $k$ 个逻辑表达式 $f_1, f_2, \cdots, f_k$,每个逻辑表达式都是由 $m$ 个逻辑变量和 $\land, \lor, \oplus, \neg$ 构成的合法逻辑表达式。 定义 $\operatorname{sat}(S)$ 为集合 $S$ 中的逻辑变量对应的真值赋值下,存在至少一个使得所有的逻辑表达式都为真。 例如,对于逻辑变量 $a, b, c$ 和两个逻辑表达式 $(a \lor b)$ 和 $(b \Rightarrow \neg c)$,$\operatorname{sat}(\{a=\mathrm{true}, b=\mathrm{false}, c=\mathrm{true}\})= false$。 给定一个逻辑表达式 $f$,定义其等效表示为 $\operatorname{simpl}(f)$。具体过程如下: - 根据与运算和或运算的结合律联想律,将逻辑表达式化为形如 $\ell_1 \land \ell_2 \land \cdots \land \ell_p$ 的简单合取范式,其中每个 $\ell_i$ 都是形如 $x_1 \lor x_2 \lor \cdots \lor x_q$ 的简单析取项; - 对于每个简单析取项 $x_1 \lor x_2 \lor \cdots \lor x_q$,在线性时间内计算一个包含了 $S$ 中逻辑变量对应的所有真值赋值中满足其为真的赋值的指示函数 $\phi_{x_1 \lor x_2 \lor \cdots \lor x_q}$。具体地,使用一个变量 $f=\mathrm{false}$ 并进行以下操作 $q$ 次:若 $S$ 中某个逻辑变量对应的值可以使得 $x_i$ 为 true,则让 $f$ 取为 $\mathrm{true}$,最终令 $\phi_{x_1 \lor x_2 \lor \cdots \lor x_q}=f$; - 最终得到的 $\operatorname{simpl}(f)$ 是 $\ell_1', \ell_2', \cdots, \ell_p'$ 的形式,其中每个 $\ell_i'$ 是二元组 $(\phi_{x_{i,1}}, \phi_{x_{i,2}})$,表示存在一组完全赋值 $v$ 使得 $\operatorname{sat}(\{a_i=v_i\})=\mathrm{true}$ 当且仅当 $x_{i,1}(v) \lor x_{i,2}(v)=\mathrm{true}$。 例如,对于逻辑表达式 $(a \land b) \Rightarrow ((a \lor b) \land (\neg a \lor \neg b))$,其 $\operatorname{simpl}$ 值为 $((a, b), ((a,b), (\neg a, \neg b)))$。 现在,请你构造一个逻辑表达式 $g$,使得 $\operatorname{simpl}(g)$ 中只有 $O(k+2^m)$ 个二元组,且对于所有的 $S$,$\operatorname{sat}(S)$ 在 $\operatorname{simpl}(g)$ 中的判定时间复杂度为 $O(k)$。 数据范围 对于所有测试点,保证 $1 \leqslant m \leqslant 10, 1 \leqslant k \leqslant 3m$。 本题满分为 $100$ 分。对于在此公开测试数据中得分为 $0$ 分的提交,我们将根据提交的相似度情况视情况给出提示,请注意查看提交结果页面。 算法1 (打表找规律) $O(m 2^{3m})$ 我们需要解决两个问题:如何根据 $\operatorname{simpl}(f)$ 判断 $\operatorname{sat}(S)$ 是否为 $\mathrm{true}$,以及如何构造出一个表达式 $g$,使得对于所有 $S$ 都有 $\operatorname{simpl}(g)$ 可以在 $O(k)$ 时间内判断 $\operatorname{sat}(S)$ 是否为 $\mathrm{true}$。 先考虑第一个问题。容易发现,对于一个二元组 $(\phi_{x_1}, \phi_{x_2})$,如果 $\operatorname{simpl}(f)$ 中存在一个简单析取项 $x_1 \lor x_2$,且 $\phi_{x_1}$ 和 $\phi_{x_2}$ 均为真函数,则 $\operatorname{simpl}(f)$ 在 $S$ 中的真值即为 $\mathrm{true}$。注意到 $\phi_{x_1}, \phi_{x_2}$ 都是只由 $S$ 中的逻辑变量决定的 01 函数,因此我们可以使用一个 $3^m \times 3^m$ 的矩阵 $mat_x$ 表示 $x$ 对 $S$ 的真值的影响。$mat_x(i,j)=1$ 表示当 $x$ 中逻辑变量取值的指示函数为 $i$ 和 $j$ 时,$\operatorname{sat}(S)$ 是否为 $\mathrm{true}$。注意到 $x$ 有 $2^m$ 种取值,因此我们需要计算 $O(2^m)$ 个矩阵 $mat_x$,每个矩阵的计算可以使用一组对应的二元组 $(\phi_{x_1}, \phi_{x_2})$ 和矩阵加法计算得到。接着我们可以把 $\operatorname{simpl}(f)$ 中所有的简单析取项的对应矩阵加起来,最终得到 $k$ 个 $3^m \times 3^m$ 的矩阵 $mat_1, mat_2, \cdots, mat_k$。判断 $\operatorname{simpl}(f)$ 在 $S$ 中的真值即可以通过计算 $\prod_{i=1}^k mat_i$ 得到。 接着考虑第二个问题。我们需要构造出一个表达式 $g$,使得 $\operatorname{simpl}(g)$ 在 $O(k)$ 时间内能判断 $\operatorname{sat}(S)$ 是否为 $\mathrm{true}$。首先考虑简单情况即 $m=1$。设 $a$ 为 $S$ 中唯一的变量。如果 $k=1$,令 $g=a$ 即可;如果 $k=2$,令 $g=\neg a \land f_2 \lor a\land \neg f_2$ 即可;如果 $k=3$,令 $g=\neg a (\neg f_2 \land \neg f_3) \lor a(\neg f_2 \land f_3) \lor f_2 \land f_3$ 即可;如果 $k=4$,令 $g=a(\neg f_2 \land f_4) \lor \neg a(f_3 \land f_4)$ 即可。可发现,对于给定的 $m,k$,这些表达式都不超过常数个。接着考虑如何推广到一般情况。 设 $a_1, a_2, \cdots, a_m$ 为 $S$ 中的 $m$ 个变量。考虑令 $g_i$ 表示 $S$ 中变量 $a_i$ 的值($0$ 或 $1$)。首先考虑对一个简单析取项 $x_1 \lor x_2 \lor \cdots \lor x_q$ 进行处理。对于 $i \in [1,q]$,设 $x_i$ 的指示函数为 $(\phi_{x_i,1}, \phi_{x_i,2})$,考虑令 $y_i$ 表示“至少有一个 $x_1, x_2, \cdots, x_i$ 在 $S$ 中是真的”,即 $y_1=x_1, y_i=y_{i-1} \lor x_i$。注意到 $y_i$ 极易通过 $\mathrm{or}$ 的结合律计算得到,因此我们可以直接把所有的二元组加入表达式 $g$ 中。此外,为了判断 $\operatorname{sat}(S)$ 是否为 $\mathrm{true}$,我们只需要计算 $y_q$ 即可。 接着考虑如何计算 $g_i$。我们构建一个由 $2^m$ 个节点组成的满二叉,每个节点代表一个赋值 $v_1, v_2, \cdots, v_m$。根节点对应所有变量的值都是未定的赋值。为了方便起见,我们设根节点所在的深度为 $1$(节点深度定义为从根节点到该节点的从上到下路径上穿过的边数目加一)。除了根节点外,每个节点 $u$ 都对应一个深度为 $i_u$ 的变量 $a_{u, i_u}$ 的值。仅当 $a_{u, i_u}$ 的值已经被确定时,才可以计算 $g_{i_u}(u)$(为了防止重复计算,不考虑已经被计算过的 $g$ 值)。$g_{i_u}(u)$ 的值也是二元组 $(\phi_{x_{i_u,1}}, \phi_{x_{i_u,2}})$ 的形式,表示当前 $\operatorname{sat}(S)$ 在 $u$ 对应的赋值下为真的充要条件为 $\phi_{x_{i_u,1}}(u)=\mathrm{true}$ 或 $\phi_{x_{i_u,2}}(u)=\mathrm{true}$。这些值可以通过矩阵乘法计算得到(注意到构成 $g_{i_u}(u)$ 的所有简单析取项的矩阵已经计算得到)。最后根据 $g_1(\mathrm{root}) \lor g_2(\mathrm{root}) \lor \cdots \lor g_m(\mathrm{root})$ 判断 $\operatorname{sat}(S)$ 是否为 $\mathrm{true}$ 即可。 时间复杂度 计算 $\operatorname{simpl}(f)$ 中所有 二元组需 $O(k \cdot 2^m \cdot 3^{2m})$ 的时间,处理 $g$ 的过程中遍历了一个深度为 $m+1$ 的二叉,节点数为 $2^{m+1}-1$。每个节点计算 $g_{i_u}(u)$ 的时间复杂度为 $O(2^{3m})$,因此总时间复杂度为 $O(k \cdot 2^m \cdot 3^{2m} + (2^{m+1}-1)2^{3m}) \leqslant O(m 2^{3m})$。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

龙星尘

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值