【计算机程序设计艺术 TAOCP】卷4·7.1.1 布尔基础:从真值表到范式、规则传播与2-SAT

5个4000比特大状态置换算法的设计与分析 下面是 5 个概念性、实验性的 4000 位可逆置换设计。它们都保证是双射,但未经过密码分析,不可直接用于生产系统。风格分别覆盖:三维 SPN、Feistel、可逆元胞自动机、ARX、宽轨迹/Keccak 式结构。 这 5 个设计分别代表了 SPN、Feistel、元胞自动机、ARX、宽轨迹 Keccak 五种不同风格,覆盖了主流大状态置换的构造思路。再次强调:它们只是实验性概念,若要用于实际协议,必须经过严格安全性分析。 阅读详情

如果一段程序只有三个布尔输入,我们可以把八种情况逐一跑完。但输入增加后,真正有用的问题就变成了:能否不列出整张真值表,仍然知道表达式表示什么、有哪些冗余,以及是否存在满足全部条件的输入?

7.1.1 给出了几条不同的路线:用代数改写函数,用子立方体理解化简,用闭包性质识别容易求解的约束,再用中数和阈值研究函数的结构。它们最后都会落到同一个问题上:输入的表示方式,究竟保留了哪些可以利用的信息?

阅读定位:本篇依据资料包中的《计算机程序设计艺术》第4卷第0册双语版《组合算法与布尔函数概论》,§7.1.1“布尔基础”。中文正文为书内274—302页,对应PDF第285—313页;本节习题为书内303—320页,对应PDF第314—331页。以下按正文主线重新组织推导,例子与Python实现为配套讲解,不逐题复述习题答案。原先的7.1篇是总览,本篇单独展开7.1.1。

1. 真值表是函数本身,表达式只是它的一种表示

一个 n n n元布尔函数是

f : { 0 , 1 } n → { 0 , 1 } . f:\{0,1\}^n\to\{0,1\}. f:{0,1}n→{0,1}.

输入有 2 n 2^n 2n种,各行的输出可独立选择0或1,所以函数共有

2 2 n 2^{2^n} 22n

个。两个输入时是16个,三个输入时是256个,四个输入时就有65536个。这里按固定变量顺序计数;交换变量后真值表改变,就算另一个函数。

规定两个变量的输入顺序为 00 , 01 , 10 , 11 00,01,10,11 00,01,10,11,常见运算的真值表如下。

运算表达式输出串
与 x ∧ y x\land y x∧y0001
或 x ∨ y x\lor y x∨y0111
异或 x ⊕ y x\oplus y x⊕y0110
蕴涵 x ⇒ y = ¬ x ∨ y x\Rightarrow y=\neg x\lor y x⇒y=¬x∨y1101
等价 ¬ ( x ⊕ y ) \neg(x\oplus y) ¬(x⊕y)1001
与非 ¬ ( x ∧ y ) \neg(x\land y) ¬(x∧y)1110
或非 ¬ ( x ∨ y ) \neg(x\lor y) ¬(x∨y)1000

蕴涵不是程序中的时间顺序,也不表示因果关系。它只排除“前件为真、后件为假”这一行。把自然语言条件翻译成公式时,先写清楚要排除哪些输入,通常比背运算符更可靠。

下面一直使用一个三输入选择函数: s s s是选择位, s = 0 s=0 s=0时返回 a a a, s = 1 s=1 s=1时返回 b b b。

F ( s , a , b ) = ( ¬ s ∧ a ) ∨ ( s ∧ b ) . F(s,a,b)=(\neg s\land a)\lor(s\land b). F(s,a,b)=(¬s∧a)∨(s∧b).

s s s a a a b b b F F F
0000
0010
0101
0111
1000
1011
1100
1111

输出串为00110101。后面每次改变表达式,都必须保留这八个输出。

2. 布尔代数里,两种“加法”不能混用

在整数算术中, 1 + 1 = 2 1+1=2 1+1=2;在逻辑或中, 1 ∨ 1 = 1 1\lor1=1 1∨1=1;在异或中, 1 ⊕ 1 = 0 1\oplus1=0 1⊕1=0。因此本文始终区分 ∨ \lor ∨、 ⊕ \oplus ⊕与普通加号。

最常用的几组规则是:

x ∧ x = x , x ∨ x = x , x ⊕ x = 0 , x ∨ ( x ∧ y ) = x , x ∧ ( x ∨ y ) = x , ¬ ( x ∧ y ) = ¬ x ∨ ¬ y , ¬ ( x ∨ y ) = ¬ x ∧ ¬ y . \begin{aligned} x\land x&=x,&x\lor x&=x,&x\oplus x&=0,\\ x\lor(x\land y)&=x,&x\land(x\lor y)&=x,\\ \neg(x\land y)&=\neg x\lor\neg y,& \neg(x\lor y)&=\neg x\land\neg y. \end{aligned} x∧xx∨(x∧y)¬(x∧y)​=x,=x,=¬x∨¬y,​x∨xx∧(x∨y)¬(x∨y)​=x,=x,=¬x∧¬y.​x⊕x=0,

吸收律说明,表达式中有一个条件已经保证输出为真时,再添加更严格的同类条件,不会增加新的真值点。例如 a ∨ ( a ∧ b ) = a a\lor(a\land b)=a a∨(a∧b)=a。

布尔代数还有两条分配律:

x ∧ ( y ∨ z ) = ( x ∧ y ) ∨ ( x ∧ z ) , x\land(y\lor z)=(x\land y)\lor(x\land z), x∧(y∨z)=(x∧y)∨(x∧z),

x ∨ ( y ∧ z ) = ( x ∨ y ) ∧ ( x ∨ z ) . x\lor(y\land z)=(x\lor y)\land(x\lor z). x∨(y∧z)=(x∨y)∧(x∨z).

第二条在普通加法、乘法里没有直接对应物。另一方面, ∧ \land ∧可以对 ⊕ \oplus ⊕分配,但不能随意把上式中的 ∨ \lor ∨都换成 ⊕ \oplus ⊕。

异或与与构成了模2算术:

x ⊕ y = ( x + y )   m o d   2 , x ∧ y = x y . x\oplus y=(x+y)\bmod2,\qquad x\land y=xy. x⊕y=(x+y)mod2,x∧y=xy.

因此异或特别适合处理奇偶性和“变化量”。若知道 x x x和 x ⊕ y x\oplus y x⊕y,再次异或 x x x即可恢复 y y y。

再看运算集合是否足够表达所有函数。定义与非 N ( x , y ) = ¬ ( x ∧ y ) N(x,y)=\neg(x\land y) N(x,y)=¬(x∧y),则

¬ x = N ( x , x ) , x ∧ y = N ( N ( x , y ) , N ( x , y ) ) , x ∨ y = N ( N ( x , x ) , N ( y , y ) ) . \begin{aligned} \neg x&=N(x,x),\\ x\land y&=N(N(x,y),N(x,y)),\\ x\lor y&=N(N(x,x),N(y,y)). \end{aligned} ¬xx∧yx∨y​=N(x,x),=N(N(x,y),N(x,y)),=N(N(x,x),N(y,y)).​

只用与非就能构造与、或、非,再借助下一节的范式表示任意布尔函数。这说明了“能够表示”,还没有说明表达式短、门电路少或者求值快;这些代价要到7.1.2继续研究。

3. 固定一个变量,得到两种不同的展开

对任意函数,固定选择变量 s s s,记

f 0 ( x ) = f ( 0 , x ) , f 1 ( x ) = f ( 1 , x ) . f_0(\boldsymbol x)=f(0,\boldsymbol x),\qquad f_1(\boldsymbol x)=f(1,\boldsymbol x). f0​(x)=f(0,x),f1​(x)=f(1,x).

按 s s s的两种取值分类,有布尔展开式

f = ( ¬ s ∧ f 0 ) ∨ ( s ∧ f 1 ) . f=(\neg s\land f_0)\lor(s\land f_1). f=(¬s∧f0​)∨(s∧f1​).

因为两个分支不会同时为真,这个式子的外层 ∨ \lor ∨也可以替换成 ⊕ \oplus ⊕。但这个替换依赖“分支互斥”,对一般DNF不成立。

还可以把 f 0 f_0 f0​看成默认输出,只在 s = 1 s=1 s=1且两个分支不同时翻转它:

f = f 0 ⊕ ( s ∧ ( f 0 ⊕ f 1 ) ) . f=f_0\oplus\bigl(s\land(f_0\oplus f_1)\bigr). f=f0​⊕(s∧(f0​⊕f1​)).

证明只需代入: s = 0 s=0 s=0得到 f 0 f_0 f0​; s = 1 s=1 s=1得到 f 0 ⊕ f 0 ⊕ f 1 = f 1 f_0\oplus f_0\oplus f_1=f_1 f0​⊕f0​⊕f1​=f1​。两种展开使用相同的分支函数,但继续递归后会产生不同的表示。

3.1 满DNF:为每一个1写一项

给定一行输入 u = ( u 1 , … , u n ) u=(u_1,\ldots,u_n) u=(u1​,…,un​),构造一个只在该行取1的极小项: u i = 1 u_i=1 ui​=1就用 x i x_i xi​, u i = 0 u_i=0 ui​=0就用 ¬ x i \neg x_i ¬xi​,再把它们全部与起来。

把 f ( u ) = 1 f(u)=1 f(u)=1的所有行对应的极小项或起来,就是满析取范式,也常称主析取范式。对选择函数,四个真值点为 010 , 011 , 101 , 111 010,011,101,111 010,011,101,111,所以

F = ( ¬ s ∧ a ∧ ¬ b ) ∨ ( ¬ s ∧ a ∧ b ) ∨ ( s ∧ ¬ a ∧ b ) ∨ ( s ∧ a ∧ b ) . \begin{aligned} F={}&(\neg s\land a\land\neg b) \lor(\neg s\land a\land b)\\ &\lor(s\land\neg a\land b) \lor(s\land a\land b). \end{aligned} F=​(¬s∧a∧¬b)∨(¬s∧a∧b)∨(s∧¬a∧b)∨(s∧a∧b).​

前两项合并为 ¬ s ∧ a \neg s\land a ¬s∧a,后两项合并为 s ∧ b s\land b s∧b,恢复原式。一般DNF只要求“若干合取项的或”,不要求每项包含全部变量。

3.2 满CNF:为每一个0写一个禁止条件

对一行假输入 u u u,构造一个只在该行取0的子句。此时极性与极小项相反: u i = 0 u_i=0 ui​=0用 x i x_i xi​, u i = 1 u_i=1 ui​=1用 ¬ x i \neg x_i ¬xi​,再或起来。

选择函数的假值点为 000 , 001 , 100 , 110 000,001,100,110 000,001,100,110,因此

F = ( s ∨ a ∨ b ) ∧ ( s ∨ a ∨ ¬ b ) ∧ ( ¬ s ∨ a ∨ b ) ∧ ( ¬ s ∨ ¬ a ∨ b ) . \begin{aligned} F={}&(s\lor a\lor b) \land(s\lor a\lor\neg b)\\ &\land(\neg s\lor a\lor b) \land(\neg s\lor\neg a\lor b). \end{aligned} F=​(s∨a∨b)∧(s∨a∨¬b)∧(¬s∨a∨b)∧(¬s∨¬a∨b).​

按固定 s s s的两组分别化简,得到

F = ( s ∨ a ) ∧ ( ¬ s ∨ b ) . F=(s\lor a)\land(\neg s\lor b). F=(s∨a)∧(¬s∨b).

代入 s = 0 s=0 s=0,第一子句要求 a = 1 a=1 a=1,第二子句自动满足;代入 s = 1 s=1 s=1则相反。这也直接说明了CNF和选择行为的关系。

边界约定同样重要:空析取为0,空合取为1。因而恒假函数的满DNF没有项;恒真函数的满CNF没有子句。一个空子句本身则为假。

3.3 多重线性表示:用异或拼出函数

重复使用异或展开,所有变量的指数都可以压到1,因为 x i 2 = x i x_i^2=x_i xi2​=xi​。最终得到

f ( x ) = ⨁ S ⊆ { 1 , … , n } c S ∏ i ∈ S x i , c S ∈ { 0 , 1 } . f(\boldsymbol x)=\bigoplus_{S\subseteq\{1,\ldots,n\}} c_S\prod_{i\in S}x_i,\qquad c_S\in\{0,1\}. f(x)=S⊆{1,…,n}⨁​cS​i∈S∏​xi​,cS​∈{0,1}.

这是模2的多重线性表示,也称代数范式(ANF)。空乘积等于1,负责常数项。

令 1 T 1_T 1T​表示恰好在集合 T T T中的位置取1的输入。只有 S ⊆ T S\subseteq T S⊆T的单项式在该输入上为1,因此

f ( 1 T ) = ⨁ S ⊆ T c S . f(1_T)=\bigoplus_{S\subseteq T}c_S. f(1T​)=S⊆T⨁​cS​.

反过来,

c S = ⨁ T ⊆ S f ( 1 T ) . c_S=\bigoplus_{T\subseteq S}f(1_T). cS​=T⊆S⨁​f(1T​).

把前一个式子代入后, c U c_U cU​出现 2 ∣ S ∖ U ∣ 2^{|S\setminus U|} 2∣S∖U∣次;在模2意义下,除 U = S U=S U=S外全部消去。因此系数被真值表唯一确定。

对选择函数, f 0 = a , f 1 = b f_0=a,f_1=b f0​=a,f1​=b,于是

F = a ⊕ ( s ∧ a ) ⊕ ( s ∧ b ) . F=a\oplus(s\land a)\oplus(s\land b). F=a⊕(s∧a)⊕(s∧b).

这里三个乘积可以同时为1。输入 111 111 111时, 1 ⊕ 1 ⊕ 1 = 1 1\oplus1\oplus1=1 1⊕1⊕1=1。如果把这三个异或换成逻辑或,输入 110 110 110就会算错。

唯一性属于这个固定的ANF表示,不属于一般DNF。满DNF虽也由真值表唯一确定,但化简后的DNF可能不唯一,长度也可能相差很大。

4. 化简的几何意义:从单个点扩大成子立方体

用*表示不关心某个变量,模式01*代表 010 , 011 010,011 010,011两个输入,对应 ¬ s ∧ a \neg s\land a ¬s∧a。模式中每删掉一个文字,就把覆盖的输入数扩大一倍。

一个合取项是 f f f的蕴涵项,意思是:只要该项为真, f f f就为真。几何上,它代表的整个子立方体必须落在 f f f的真值点集合内。

若再也不能把某个固定的0或1改成*而仍保持这个性质,该项就是基本蕴涵项,也常称素蕴涵项。这里是按包含关系“无法继续扩大”,不同基本蕴涵项的大小可以不同。

选择函数有三个基本蕴涵项:

模式表达式覆盖的真值点
01* ¬ s ∧ a \neg s\land a ¬s∧a010、011
1*1 s ∧ b s\land b s∧b101、111
*11 a ∧ b a\land b a∧b011、111

第三项无法单独扩大,却可以从整个表达式里删掉,因为它覆盖的点已经由前两项覆盖。

( ¬ s ∧ a ) ∨ ( s ∧ b ) ∨ ( a ∧ b ) = ( ¬ s ∧ a ) ∨ ( s ∧ b ) . (\neg s\land a)\lor(s\land b)\lor(a\land b) =(\neg s\land a)\lor(s\land b). (¬s∧a)∨(s∧b)∨(a∧b)=(¬s∧a)∨(s∧b).

所以“每一项都已经是基本的”,并不意味着“整个DNF已经最短”。前者是局部扩大问题,后者还要选择哪些子立方体共同覆盖全部真值点。

在这个例子里, 010 010 010只能由01*覆盖, 101 101 101只能由1*1覆盖,因此这两项都必须保留,也已经覆盖全部真值点。这给出了两项最优的证明。一般情况下不能靠同样简单的目测解决。

单调函数为什么特殊

若逐位有 x i ≤ y i x_i\le y_i xi​≤yi​,总有 f ( x ) ≤ f ( y ) f(\boldsymbol x)\le f(\boldsymbol y) f(x)≤f(y),则 f f f是单调函数。增加输入中的1不会让输出从1降到0。

对单调函数,一个基本蕴涵项不可能含有 ¬ x i \neg x_i ¬xi​:去掉这个负文字后,新增加的输入只是把 x i x_i xi​从0改成1,单调性保证输出仍为1。这样原项就不是基本的了。

再取一个基本蕴涵项,把它要求的变量设为1,其余全设为0。任何其他基本蕴涵项若也覆盖这个输入,其正文字集合就包含于前者;除非两项相同,否则会破坏基本性。因此每个基本蕴涵项都有自己不可替代的见证输入。原书定理Q据此说明,单调函数的全部基本蕴涵项给出其最短DNF。

选择函数并不单调: 010 ≤ 110 010\le110 010≤110,却有 F ( 010 ) = 1 , F ( 110 ) = 0 F(010)=1,F(110)=0 F(010)=1,F(110)=0。它正好展示了为何一般函数会出现多余的基本蕴涵项。

5. 第一组Python:转换表示,枚举基本蕴涵项

下面采用二进制整数索引真值表:i的最高位对应第一个变量。coeff[m]表示由掩码m选中的变量乘积的ANF系数。特别是m=0对应常数1。

from itertools import product


def anf(table):
    """真值表到ANF系数;输入长度必须是2的幂。"""
    size = len(table)
    if size == 0 or size & (size - 1):
        raise ValueError("table length must be a power of two")
    if any(v not in (0, 1) for v in table):
        raise ValueError("table values must be 0 or 1")
    c = list(table)
    for k in range(size.bit_length() - 1):
        bit = 1 << k
        for mask in range(size):
            if mask & bit:
                c[mask] ^= c[mask ^ bit]
    return c


def eval_anf(coeff, assignment):
    value = 0
    for mask, c in enumerate(coeff):
        if c and (assignment & mask) == mask:
            value ^= 1
    return value


def cube_points(pattern):
    n = len(pattern)
    return {
        i for i in range(1 << n)
        if all(v == "*" or int(v) == ((i >> (n - 1 - j)) & 1)
               for j, v in enumerate(pattern))
    }


def prime_cubes(table):
    n = len(table).bit_length() - 1
    if len(table) != 1 << n or any(v not in (0, 1) for v in table):
        raise ValueError("invalid truth table")
    ones = {i for i, v in enumerate(table) if v}
    valid = {
        "".join(p): cube_points(p)
        for p in product("01*", repeat=n)
        if cube_points(p) <= ones
    }
    primes = []
    for p in valid:
        # 一次删掉一个文字即可检验是否还能扩大。
        if not any(p[:j] + "*" + p[j + 1:] in valid
                   for j, v in enumerate(p) if v != "*"):
            primes.append(p)
    return sorted(primes)


def eval_full_dnf(table, i):
    return int(any(value and i == row for row, value in enumerate(table)))


def eval_full_cnf(table, i):
    return int(all(i != row for row, value in enumerate(table) if not value))


choice = [a if s == 0 else b for s, a, b in product((0, 1), repeat=3)]
assert "".join(map(str, choice)) == "00110101"
assert [m for m, c in enumerate(anf(choice)) if c] == [2, 5, 6]
assert prime_cubes(choice) == ["*11", "01*", "1*1"]

checked = 0
for n in range(4):
    size = 1 << n
    for code in range(1 << size):
        table = [(code >> i) & 1 for i in range(size)]
        coeff = anf(table)
        covered = set()
        for p in prime_cubes(table):
            covered.update(cube_points(p))
        assert covered == {i for i, v in enumerate(table) if v}
        for i in range(size):
            assert eval_anf(coeff, i) == table[i]
            assert eval_full_dnf(table, i) == table[i]
            assert eval_full_cnf(table, i) == table[i]
        checked += 1
print("representation checks:", checked)  # 278

ANF转换对每个变量扫描一次长度 2 n 2^n 2n的表,时间为 O ( n 2 n ) O(n2^n) O(n2n),额外空间为 O ( 2 n ) O(2^n) O(2n)。循环每处理一个坐标,就把“是否包含该坐标”的两组系数做一次异或,逐步实现前面的子集求和公式。

prime_cubes故意枚举全部 3 n 3^n 3n个模式,再检查每个模式覆盖的点,适合小规模观察与核验;它不是大规模逻辑最小化工具,也没有执行最小覆盖搜索。输出基本蕴涵项后,还要另行选择覆盖。

6. Horn子句:从真假枚举变成必然结论的传播

一个Horn子句至多含一个正文字。例如

¬ a ∨ ¬ b ∨ c \neg a\lor\neg b\lor c ¬a∨¬b∨c

可以写成规则

a ∧ b ⇒ c . a\land b\Rightarrow c. a∧b⇒c.

没有正文字时,写成 a ∧ b ⇒ ⊥ a\land b\Rightarrow\bot a∧b⇒⊥,表示这个前提组合不允许出现。没有前提而结论为 a a a的规则就是事实 a a a。

先看全都有正结论的确切Horn规则。取事实

⇒ a , a ⇒ b , a ∧ b ⇒ c , c ⇒ d . \Rightarrow a,\qquad a\Rightarrow b,\qquad a\land b\Rightarrow c,\qquad c\Rightarrow d. ⇒a,a⇒b,a∧b⇒c,c⇒d.

从空集合出发,依次必须加入 a , b , c , d a,b,c,d a,b,c,d。如果另有 d ⇒ ⊥ d\Rightarrow\bot d⇒⊥,就得到矛盾。

这里每一次加入的变量都在所有满足赋值中为真:事实必须成立;若一条规则的全部前提在所有模型中为真,它的结论也必须为真。传播结束后,把加入的变量设为真,其余设为假:未触发的规则至少有一个假前提,已触发的规则已有真结论。因此只要没有触发矛盾,就得到了最小满足赋值。

原书算法C使用“尚未成立的前提数”与反向出现表。下面用队列实现同样的思想,并允许None代表矛盾结论。

from collections import deque


def horn_model(n, rules):
    """变量编号0..n-1;规则为(前提列表, 结论或None)。"""
    rules = [(tuple(sorted(set(body))), head) for body, head in rules]
    waiting = [[] for _ in range(n)]
    remaining = [len(body) for body, _ in rules]
    true = set()
    queue = deque()

    def infer(head):
        if head is None:
            return False
        if head not in true:
            true.add(head)
            queue.append(head)
        return True

    for r, (body, head) in enumerate(rules):
        if any(v < 0 or v >= n for v in body):
            raise ValueError("invalid premise")
        if head is not None and not 0 <= head < n:
            raise ValueError("invalid conclusion")
        for v in body:
            waiting[v].append(r)
        if not body and not infer(head):
            return None

    while queue:
        v = queue.popleft()
        for r in waiting[v]:
            remaining[r] -= 1
            if remaining[r] == 0 and not infer(rules[r][1]):
                return None
    return true


rules = [((), 0), ((0,), 1), ((0, 1), 2), ((2,), 3)]
assert horn_model(4, rules) == {0, 1, 2, 3}
assert horn_model(4, rules + [((3,), None)]) is None
# 只有循环,没有事实,不会凭空推出任何真变量。
assert horn_model(2, [((0,), 1), ((1,), 0)]) == set()
print("Horn propagation: passed")

每个变量最多入队一次,每个前提出现在传播阶段最多处理一次。因此在前提已去重的输入上,建表与传播为 O ( n + L ) O(n+L) O(n+L), L L L是规则总长度。代码开头的排序去重属于额外预处理,不计入这个线性传播界。

Horn还有一个语义刻画:若 u , v u,v u,v都是满足赋值,则逐位与 u ∧ v u\land v u∧v仍然满足。原书定理H进一步证明,这个闭包性质也足以刻画能够写成Horn CNF的布尔函数。

要检查必要性,考虑一条规则。如果 u ∧ v u\land v u∧v使所有前提为真,则 u u u与 v v v分别都使前提为真,因而分别都使结论为真,交集中的结论仍为真。这个解释也说明了最小模型为何自然出现。

7. 2-SAT:把一个子句变成两条有向边

另一类容易处理的公式是每个子句至多有两个文字的CNF,也就是2-SAT;原书称这类子句为Krom子句。

对于 ( u ∨ v ) (u\lor v) (u∨v),有

¬ u ⇒ v , ¬ v ⇒ u . \neg u\Rightarrow v,\qquad \neg v\Rightarrow u. ¬u⇒v,¬v⇒u.

建图时两条边都要加入。图的顶点是每个变量及其否定,共 2 n 2n 2n个。单文字子句 ( u ) (u) (u)可以写成 ( u ∨ u ) (u\lor u) (u∨u);空子句立即导致无解。

如果某个 x x x与 ¬ x \neg x ¬x属于同一个强连通分量,就有 x ⇒ ¬ x x\Rightarrow\neg x x⇒¬x以及 ¬ x ⇒ x \neg x\Rightarrow x ¬x⇒x。无论把 x x x设成哪一种值都会矛盾,因此公式不可满足。

反过来,若没有这种分量,把强连通分量缩点后得到有向无环图。每条边都有相应的逆否边;选择一个汇分量设为真、其取补分量设为假,再删除这对分量并继续,就能构造满足赋值。这给出了原书定理K的两个方向。

例如

( a ∨ b ) ∧ ( ¬ a ∨ b ) ∧ ( a ∨ ¬ b ) (a\lor b)\land(\neg a\lor b)\land(a\lor\neg b) (a∨b)∧(¬a∨b)∧(a∨¬b)

只能取 a = b = 1 a=b=1 a=b=1;再加 ( ¬ a ∨ ¬ b ) (\neg a\lor\neg b) (¬a∨¬b)就变成无解。后面程序会验证这两个例子。

下面用Kosaraju的两遍图遍历实现强连通分量。它是对定理K的独立实现;原书正文提到的是Tarjan方法。为避免大图上的Python递归深度问题,两遍遍历都使用显式栈。

def two_sat(n, clauses):
    """文字用±1..±n表示;返回0/1赋值,或None。"""
    graph = [[] for _ in range(2 * n)]
    reverse = [[] for _ in range(2 * n)]

    def node(lit):
        if lit == 0 or abs(lit) > n:
            raise ValueError("invalid literal")
        return 2 * (abs(lit) - 1) + int(lit < 0)

    for clause in clauses:
        if len(clause) == 0:
            return None
        if len(clause) > 2:
            raise ValueError("not a 2-CNF clause")
        u = node(clause[0])
        v = node(clause[-1])
        for a, b in ((u ^ 1, v), (v ^ 1, u)):
            graph[a].append(b)
            reverse[b].append(a)

    seen = [False] * (2 * n)
    order = []
    for start in range(2 * n):
        if seen[start]:
            continue
        seen[start] = True
        stack = [(start, 0)]
        while stack:
            v, k = stack[-1]
            if k == len(graph[v]):
                order.append(v)
                stack.pop()
            else:
                stack[-1] = (v, k + 1)
                w = graph[v][k]
                if not seen[w]:
                    seen[w] = True
                    stack.append((w, 0))

    component = [-1] * (2 * n)
    cid = 0
    for start in reversed(order):
        if component[start] != -1:
            continue
        component[start] = cid
        stack = [start]
        while stack:
            v = stack.pop()
            for w in reverse[v]:
                if component[w] == -1:
                    component[w] = cid
                    stack.append(w)
        cid += 1

    if any(component[2 * i] == component[2 * i + 1] for i in range(n)):
        return None
    # 本实现的分量编号按原图的源到汇方向增长。
    return [int(component[2 * i] > component[2 * i + 1])
            for i in range(n)]


example = [(1, 2), (-1, 2), (1, -2)]
assert two_sat(2, example) == [1, 1]
assert two_sat(2, example + [(-1, -2)]) is None
assert two_sat(0, []) == []
assert two_sat(0, [()]) is None
print("2-SAT examples: passed")

两个遍历都是 O ( n + m ) O(n+m) O(n+m), m m m是子句数。代码最后的分量大小比较依赖编号方向:换一个SCC实现时,不能直接照搬比较符号。

这两类可满足性算法利用的是不同结构。Horn允许很长的子句,但限制正文字数量;2-SAT允许任意正负搭配,但限制每个子句的长度。把任意长子句直接拆成多个二元子句,通常会改变满足赋值,不能据此把一般SAT变成2-SAT。

8. 中数把布尔公式与几何结构连起来

三个比特的中数就是多数值:

μ ( x , y , z ) = ( x ∧ y ) ∨ ( x ∧ z ) ∨ ( y ∧ z ) . \mu(x,y,z)=(x\land y)\lor(x\land z)\lor(y\land z). μ(x,y,z)=(x∧y)∨(x∧z)∨(y∧z).

它也等于

( x ∨ y ) ∧ ( x ∨ z ) ∧ ( y ∨ z ) . (x\lor y)\land(x\lor z)\land(y\lor z). (x∨y)∧(x∨z)∧(y∨z).

固定一个参数,可以恢复与、或:

μ ( x , 0 , y ) = x ∧ y , μ ( x , 1 , y ) = x ∨ y . \mu(x,0,y)=x\land y,\qquad \mu(x,1,y)=x\lor y. μ(x,0,y)=x∧y,μ(x,1,y)=x∨y.

中数既单调,又满足自对偶性:

μ ( ¬ x , ¬ y , ¬ z ) = ¬ μ ( x , y , z ) . \mu(\neg x,\neg y,\neg z)=\neg\mu(x,y,z). μ(¬x,¬y,¬z)=¬μ(x,y,z).

因此只用变量和中数复合、不引入常数或取反,得到的函数仍然单调且自对偶。原书定理P说明反向也成立:所有单调且自对偶的布尔函数都能这样表示。允许常数0、1后,则可以表示所有单调函数。

8.1 为什么2-SAT的解对逐位中数封闭

取同一个2-CNF的三个满足赋值 A , B , C A,B,C A,B,C,对每个变量取多数,得到 M M M。假设某个二元子句 ( u ∨ v ) (u\lor v) (u∨v)在 M M M上为假,则:

  • 至少两个原赋值使 u u u为假;
  • 至少两个原赋值使 v v v为假。

在三个赋值里,这两组必然相交,于是至少一个原赋值同时使 u , v u,v u,v为假,与它满足该子句矛盾。否定文字同样适用,因为三个值的多数运算与逐位取反可交换。

所以2-CNF的满足赋值集合对逐位中数封闭。原书定理S证明这个条件也充分。它与Horn解集对逐位与封闭形成了有用的对照:可以通过解集的运算性质研究公式的类型。

8.2 从比特立方体到中数图

在二进制立方体中,相差一位的两个点相连。距离就是汉明距离。记 I ( u , v ) I(u,v) I(u,v)为位于 u u u到 v v v某条最短路径上的全部顶点,则对任意三点

I ( x , y ) ∩ I ( y , z ) ∩ I ( z , x ) = { μ ( x , y , z ) } , I(x,y)\cap I(y,z)\cap I(z,x)=\{\mu(x,y,z)\}, I(x,y)∩I(y,z)∩I(z,x)={μ(x,y,z)},

右边的中数按坐标计算。例如 x = 000 , y = 110 , z = 101 x=000,y=110,z=101 x=000,y=110,z=101,三个区间的唯一公共点为 100 100 100。

原因可以逐位解释:若某个坐标在两个端点中相同,最短路径不能先改掉它再改回来;若不同,则可以在两值之间选择。三个端点里总有两个坐标相同,公共点只能选多数值。

树也有类似结构:任意三点之间的三条唯一路径交于一个点。原书由此推广到中数代数和中数图,并研究怎样给图的顶点分配比特标记,使图距离对应标记的汉明距离。

在树上,删除一条边会把顶点分成两侧;选择一个根,把根一侧标0,另一侧标1。对每条边都做一次,就得到一个直接的标记方法。两点的标记在哪些位置不同,恰好对应连接它们的路径经过了哪些边。

一般中数图有四边形:同一四边形的相对边对应同一个坐标方向。原书算法H通过按距离访问顶点、识别四边形诱导的边等价类、合并这些类,再沿选定的边给顶点翻转相应标记位。忽略显式输出所有标记的代价,其讨论的运行时间界为 O ( V ( log ⁡ V ) 2 ) O(V(\log V)^2) O(V(logV)2)。这里介绍其结构用途,不把上面的SCC代码当作算法H的实现。

还要区分“中数封闭集合”和“在原坐标下直接连边得到的中数图”。集合 { 00 , 11 } \{00,11\} {00,11}对逐位中数封闭,但按“只差一位才相邻”连边时,两点不连通。原书引入精简标记来处理这类冗余坐标,不能跳过这个条件。

正文随后把这种表示与比较器、反相器网络联系起来。比较器把 ( x , y ) (x,y) (x,y)变成 ( x ∧ y , x ∨ y ) (x\land y,x\lor y) (x∧y,x∨y),反相器把 x x x变成 ¬ x \neg x ¬x。在相应中数标记条件下,可以构造一个网络,把整个比特立方体映入目标集合,并让目标集合中的点保持不变。这种“映过去以后不再移动”的性质称为回缩。它说明中数结构也能落实成有限操作序列。

9. 阈函数:一条加权不等式能表示什么

用Iverson括号 [ P ] [P] [P]表示命题 P P P成立时取1,否则取0。阈函数写成

f ( x ) = [ ∑ i = 1 n w i x i ≥ t ] . f(\boldsymbol x)=\left[\sum_{i=1}^n w_ix_i\ge t\right]. f(x)=[i=1∑n​wi​xi​≥t].

与、或、奇数个输入的多数函数,分别对应“至少 n n n个1”“至少1个1”“超过半数为1”。有负权重时,把对应输入改写成它的补,可以转成非负方向的权重,同时调整阈值。

但一个阈值门不能表示所有布尔函数。例如二元异或要求

0 < t , w 1 ≥ t , w 2 ≥ t , w 1 + w 2 < t . 0<t,\quad w_1\ge t,\quad w_2\ge t,\quad w_1+w_2<t. 0<t,w1​≥t,w2​≥t,w1​+w2​<t.

中间两式推出 w 1 + w 2 ≥ 2 t > t w_1+w_2\ge2t>t w1​+w2​≥2t>t,与最后一式矛盾。多层阈值组合可能表示异或,这与“单个阈函数能否表示”是不同问题。

原书定理T给出一个精巧结论。对布尔函数 f f f,令

N ( f ) = ∣ { x : f ( x ) = 1 } ∣ , Σ ( f ) = ∑ f ( x ) = 1 x . N(f)=|\{x:f(x)=1\}|,\qquad \Sigma(f)=\sum_{f(x)=1}x. N(f)=∣{x:f(x)=1}∣,Σ(f)=f(x)=1∑​x.

若 f f f是阈函数,而任意布尔函数 g g g满足 N ( f ) = N ( g ) N(f)=N(g) N(f)=N(g)且 Σ ( f ) = Σ ( g ) \Sigma(f)=\Sigma(g) Σ(f)=Σ(g),就一定有 f = g f=g f=g。这里只要求 f f f是阈函数。

证明可以看两个函数不同意的地方:设 A = { x : f ( x ) = 1 , g ( x ) = 0 } A=\{x:f(x)=1,g(x)=0\} A={x:f(x)=1,g(x)=0}, B = { x : f ( x ) = 0 , g ( x ) = 1 } B=\{x:f(x)=0,g(x)=1\} B={x:f(x)=0,g(x)=1}。计数相等说明 ∣ A ∣ = ∣ B ∣ = k |A|=|B|=k ∣A∣=∣B∣=k,坐标和相等说明

∑ x ∈ A x = ∑ x ∈ B x . \sum_{x\in A}x=\sum_{x\in B}x. x∈A∑​x=x∈B∑​x.

若 k > 0 k>0 k>0,阈函数的定义又给出

∑ x ∈ A w ⋅ x ≥ k t , ∑ x ∈ B w ⋅ x < k t , \sum_{x\in A}w\cdot x\ge kt, \qquad \sum_{x\in B}w\cdot x<kt, x∈A∑​w⋅x≥kt,x∈B∑​w⋅x<kt,

与前面的向量和相等矛盾。因此 k = 0 k=0 k=0。

这个结论不能无条件推广到一般布尔函数。二元异或与二元等价函数都有两个真值点,真值点的坐标和都是 ( 1 , 1 ) (1,1) (1,1),却是两个不同函数;它们都不是阈函数。

10. 对称、渠限化与计数:压缩函数的另一种方式

对称函数在任意变量置换下不变。对布尔输入而言,这等价于只依赖输入中1的个数。重量可能是 0 , 1 , … , n 0,1,\ldots,n 0,1,…,n,每个重量层独立选0或1,因此对称函数共有

2 n + 1 2^{n+1} 2n+1

个。“恰有 k k k个1”可以写成两个阈条件的组合:

[ ∑ i x i = k ] = [ ∑ i x i ≥ k ] ∧ ¬ [ ∑ i x i ≥ k + 1 ] . \left[\sum_i x_i=k\right] =\left[\sum_i x_i\ge k\right] \land\neg\left[\sum_i x_i\ge k+1\right]. [i∑​xi​=k]=[i∑​xi​≥k]∧¬[i∑​xi​≥k+1].

**渠限化函数(canalizing function)**则存在某个输入位置及某个取值,固定它就能使整个函数成为常数。例如 f ( x , y , z ) = ¬ x ∨ ( y ⊕ z ) f(x,y,z)=\neg x\lor(y\oplus z) f(x,y,z)=¬x∨(y⊕z),只要 x = 0 x=0 x=0,输出就必为1; x = 1 x=1 x=1时还需继续看 y , z y,z y,z。这是一种可以支持提前返回的结构。

选择函数没有这种性质。固定 s s s后仍需读取一个数据位;固定 a a a或 b b b后,也不能让所有剩余输入都产生相同输出。

自对偶函数还可以直接计数。对 n ≥ 1 n\ge1 n≥1,输入按 x x x与 ¬ x \neg x ¬x组成 2 n − 1 2^{n-1} 2n−1对;每对选择一个输出,另一个输出被迫取补。因此自对偶函数共有

2 2 n − 1 2^{2^{n-1}} 22n−1

个。这种成对约束使真值表能保存一半,但它并不保证函数单调、可写成短公式或容易求解。

原书最后分别按“固定变量名”“允许变量置换”“再允许输入输出取反”统计函数。三种计数回答的是不同问题,不应把它们混在一起。本文程序始终采用固定变量顺序。

对三个变量逐一枚举256张真值表,可以复核以下计数。这些类别相互交叠,不能把各行相加。

三元函数类别数量
全部布尔函数256
自对偶16
单调20
同时单调且自对偶4
满足赋值对逐位与封闭,即可表示为Horn CNF122
满足赋值对逐位中数封闭,即可表示为2-CNF166
对称16
渠限化120

11. 本篇的核验范围与接下来的阅读

将文中的三个Python代码块按顺序放进一个文件,可直接运行,标准库即可。输出为:

representation checks: 278
Horn propagation: passed
2-SAT examples: passed

278包含零元至三元的全部布尔函数: 2 + 4 + 16 + 256 2+4+16+256 2+4+16+256。零元函数只有常数0、1,也用于检查空合取、空析取和常数ANF的边界。

配套扩展核验还独立穷举了零至两个变量的266组规范Horn规则集合、2066组规范2-CNF子句集合,以枚举满足赋值作为对照,检查有解/无解、Horn最小模型以及2-SAT返回赋值。规范化时去掉重复文字,Horn集合不计前提已经包含结论的恒真规则;2-CNF集合保留空子句与恒真子句。因此这些数字计数的是被测试的公式集合,不是不同函数的数量。

扩展核验也检查了2500个变量的蕴涵链、三维立方体的全部512组三点区间,以及上表的三元函数分类计数。它验证的是这些有限范围,不把小规模穷举代替一般证明。

回到本节的几个实用区别:

想解决的问题本节给出的表示或结构
精确识别一个小函数固定输入顺序的真值表
从真假输入系统地构造公式满DNF、满CNF
利用奇偶性与模2代数唯一的多重线性表示/ANF
寻找可以删掉的文字与项子立方体、基本蕴涵项、覆盖
传播必须成立的结论Horn规则、计数器与出现表
求解二元子句约束蕴涵图、强连通分量
理解解集和图的几何结构逐位中数、标记与回缩
利用加权比较或交换对称性阈函数、重量层表示

下一篇进入§7.1.2“布尔求值”:当多个表达式都表示同一个函数时,怎样比较计算步骤、复用中间结果,以及判断一个实现到底有多好。

原书阅读索引

  • 书内274—281页:基本运算、恒等式、展开、范式、基本蕴涵项与单调性。
  • 书内282—288页:可满足性、Horn规则及算法C、2-SAT与定理K。
  • 书内288—298页:中数、自对偶性、中数图与标记、算法H、比较器—反相器网络。
  • 书内298—302页:阈函数、定理T、对称函数、渠限化函数及分类计数。

以上页码属于所提供的第0册双语版中文部分;其他语言或后续合订版的页码可能不同。

免费搞定DWG转DXF:ODA File Converter保姆级配置指南(附常见报错解决) 本文详细介绍了如何使用ODA File Converter免费工具实现DWG到DXF的格式转换,包括环境配置、命令行参数解析、编程集成方案及常见问题解决。特别针对网络访问异常、参数配置等实际痛点提供应对策略,帮助用户高效完成工程设计文件的格式转换。 阅读详情

相关推荐

别再死磕U-Net了!用PyTorch手把手教你搭建ResUnet,搞定医学图像分割(附完整代码)

本文详细介绍了如何利用PyTorch搭建ResUnet模型,通过引入残差连接解决医学图像分割中的梯度消失、特征退化和细节丢失问题。与传统U-Net相比,ResUnet在Dice系数和收敛速度上均有显著提升,特别适合处理脑部MRI等复杂医学图像。文章还提供了完整的代码实现和关键训练技巧,帮助开发者快速掌握这一先进技术。

weixin_42552637的博客 326

算法日常・每日刷题--<动态规划>42

本题为“不相交的线”,与最长公共子序列问题相似。使用动态规划求解:定义 dp[i][j] 表示 nums1[0:i] 与 nums2[0:j] 的最大不相交连线数。若元素相等,则继承左上值加1;否则取上方或左方最大值。最终返回 dp[m][n],即两数组的最大不相交连线数。时间复杂度 O(mn),空间复杂度 O(mn)。

2503_92261635的博客 12

python 中心线提取_二值分析 | OpenCV + skimage如何提取中心线

点击上方蓝字关注我们微信公众号:OpenCV学堂关注获取更多计算机视觉与深度学习知识问题前几天有个人问了我一个问题,问题是这样的,他有如下的一张二值图像:怎么得到白色Blob中心线,他希望的效果如下:显然OpenCV中常见的轮廓分析无法获得上面的中心红色线段,本质上这个问题是如何提取二值对象的骨架,提取骨架的方法在OpenCV的扩展模块中,另外skimage包也支持图像的骨架提取。这里就...

weixin_42204453的博客 4290

C语言程序设计题|有序数组插入数字冒泡排序

题目要求将一个数插入有序数组末尾,并用冒泡排序实现整体升序。代码先将输入数存入数组最后位置,再通过双重循环进行冒泡排序,每轮将较大元素向后移动,最终输出11个升序元素。该方法虽时间复杂度为O(n²),但符合题目要求,逻辑清晰,适合初学者理解排序原理。

2301_81429079的博客 436

安装破解IAR EWARM 8.2,提供注册机

IAR Embedded Workbench for ARM(IAR for ARM)是一款微处理器的集成开发环境软件,该集成开发环境中包含了IAR的C/C++编译器、 汇编工具、链接器、库管理器、文本编辑器、工程管理器和C-SPY调试器,支持ARM,AVR,MSP430等芯片。   一、安装 双击运行安装程序,点击Install IAR Embedded Workbench for ARM...

睿思派克 4万+

零基础学习大气污染模式(WRF、WRF-chem、smoke、camx等)

零基础学习大气污染模式(WRF、WRF-chem、smoke、camx等)

weixin_56135535的博客 652

【算法拾光】模拟|外观数列 + 数青蛙

本文总结两道模拟题解法:外观数列通过迭代统计连续数字个数生成下一项,关键在于处理末尾字符;数青蛙则模拟青蛙叫声“croak”状态转移,维护各阶段青蛙数量,记录最大并发数并确保全部完成。共性在于严格遵循流程、细致处理边界与非法情况,锻炼字符串遍历与状态管理能力。

wuqingyigexiaoba的博客 361

使用Python与OpenCV实现基于霍夫变换的圆形缺陷检测与定位详细教程

本文详细探讨了如何使用Python和OpenCV库,通过图像边缘检测和霍夫变换实现圆形对象的检测与缺陷定位。我们不仅介绍了算法的基本原理,还通过具体的代码示例展示了如何在实际应用中实现这些技术。对于工业检测和医学影像分析,本文的方法提供了高效且易于实现的解决方案。随着图像处理技术的不断发展,未来我们可以期待更多新算法和新技术的应用,如深度学习与传统方法的结合,以及多传感器融合技术的引入,这将进一步提升缺陷检测与定位的效率和准确性。

qq_38334677的博客 1295

【数据结构-3】算法美学之降复杂度

在while循环的循环体内,首先用临时变量保存最后一个位置的数据,再通过for循环把所有元素往后移动一位,最后把tmp中保存的值放到下标为0的位置上去。方法二的思路是:创建新数组,让原数组中后k个数据移动到新数组中前面的位置,让原数组中前numsSize-k个数据移动到新数组中后面的位置,这。这段代码的思路是:三次逆置,第一次逆置前numsSize-k个数据,第二次逆置后k个数据,最后整体逆置。这篇博文分享一道解法不一的算法题,求解过程的目的在于降低算法的时间复杂度,题源力扣。,属于空间换时间的操作。

weixin_44218929的博客 77

USB_TYPE-C 母座电路设计

本文详细介绍了USB Type-C接口(C2765186)的电路设计要点,重点解析了电源、识别和数据三部分的关键连接方案。电源部分需并联VBUS和GND引脚并加宽走线以承载3A电流;识别部分必须为CC1/CC2引脚配置5.1kΩ±1%精度的下拉电阻,这是触发PD充电的核心条件;数据部分通过短接正反插引脚实现盲插功能。文档还提供了完整的引脚对照表,并强调PCB布局需注意ESD防护、差分走线阻抗控制等要点,为Type-C接口设计提供了实用指导。

在读研究生,FPGA电路设计与功能开发。 579

使用 MATLAB/Simulink 构建一个基于PID控制的直流电机速度调节系统模型

直流电机(DC Motor)因其结构简单、成本低廉、易于控制等特点,广泛应用于各种工业自动化和家用电器中。PID控制器作为最常用的控制策略之一,在直流电机的速度调节系统中具有重要应用价值。通过合理调整比例(P)、积分(I)、微分(D)三个参数,可以实现对电机速度的精确控制。本教程将带你使用构建一个基于PID控制的直流电机速度调节系统模型,涵盖电机模型构建、PID控制器设计、系统仿真与调试等多个方面。本文介绍了如何使用构建一个基于PID控制的直流电机速度调节系统模型。

amy_mhd的博客 217

AI和大模型—从克里金算法说开去

克里金算法源于矿业勘探,通过空间自相关性实现稀疏数据下的最优预测,核心优势在于不确定性量化。在AI大模型中,其等价于高斯过程,广泛应用于超参数优化、小样本建模与推理调度。尽管面临计算复杂度高、核函数选择难等问题,通过稀疏化、分治法与低秩近似可有效优化。该方法在降低训练成本、提升泛化能力及减少幻觉方面表现突出,尤其适用于高维、高成本的智能系统优化,具有重要借鉴价值。

fpcc的专栏 602

独家开源|RK3588 平台 YOLOv5 Android 源码级解析:NPU 量化、多线程与目标跟踪实战

本文将带你完成一个完整的AI视觉项目:在搭载RK3588芯片的Android开发板上运行实时目标检测与跟踪应用。实战目标:成功运行官方YOLOV5摄像头实时检测案例技术深度:深入理解RKNN开发框架和源码实现核心技能:掌握NPU推理、多线程并发、目标跟踪等关键技术。

m0_71589036的博客 193

算法日常・每日刷题--<动态规划>32

给定严格递增的正整数数组,求最长斐波那契子序列长度。使用二维DP,dp[i][j]表示以arr[i]、arr[j]为最后两项的序列长度,初始化均为2。通过哈希表快速查找前一项target = arr[j] - arr[i],若存在且下标小于i,则状态转移:dp[i][j] = dp[k][i] + 1。遍历更新全局最大长度,最终若结果小于3则返回0。时间复杂度O(n²),空间复杂度O(n²)。

2503_92261635的博客 174

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

算法札记:几何分布在算法题UVA1262的运用

集卡问题求解核心:将集齐n种优惠券的过程分为n个阶段,每个阶段期望购买次数为 $ \frac{n}{k} $(k为剩余种类数),总期望为 $ n \cdot H_n $(调和数)。通过自洽方程推导单阶段期望,无需几何分布或无穷级数,逻辑清晰且适用于大一水平。例如,n=5时期望约11.42,非5。题目要求以带分数形式输出精确结果,算法上需累加分数并化简。

Keven_11的博客 301

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

不同的二叉搜索树

互不相同的二叉搜索树的种数,以 j (1<=j<=i)为头节点,左子树是 j-1 个数,右子树就是 i - (j - 1) -1 = i - j个数。所以dp[3] = dp[0] * dp[2] + dp[1] * dp[1] + dp[2] * dp[0]头1 = 左子树0节点 * 右子树2节点,二叉搜索树就是排序树,左子树<根节点<右子树,左右子树也满足左子树<根节点<右子树。头2 = 左子树1节点 * 右子树1节点。所以dp[i] += dp[j-1] * dp[i-j]个节点组成且节点值从。

qq_44721738的博客 150

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

SSL证书选RSA还是ECDSA:公钥算法、CA签名与兼容性怎么核对

申请SSL证书时,RSA和ECDSA该怎么选?先分清站点公钥算法、CA签名算法和TLS实际兼容性,再用隔离OpenSSL实验读取两类材料,核对申请、续期和部署交接,避免只看Signature Algorithm就选错。

谁呀!的博客 378

固件开发岗算法

背景:嵌入式固件岗,偏爱手写库函数、位运算、数组基础算法。

2502_90352018的博客 108

从曲线到储层:Techlog/KAPPA 全流程算法、UltraLAB 选型与本地 AI 部署实战

以高频 CPU 保证交互,以充足内存承载多井与多任务,以 NVMe 打通项目和知识库 I/O,再按本地模型、上下文与并发配置 GPU。对 UltraLAB 用户而言,更合理的采购方式是带着真实项目做选型:一组典型井、一套完整解释模板、一个目标大模型、一次多人并发测试。用实测结果确定配置,才能把预算转化为解释效率,而不是闲置算力。软件模块、操作系统与许可条件以厂商当前正式说明为准;硬件建议需结合项目实测。AI 输出只能作为辅助证据,不能替代岩石物理与油藏工程专业审核。

wdf8088的博客 159

【Deep-OC-SORT 01】多轨迹算法Deep-OC-SORT源码分析

本工程基于Deep-OC-SORT核心模块,重构并适配了EmbeddingComputer(外观特征提取)、CMCComputer(相机运动补偿)与OCSort(主跟踪器)三类。前者实现整框/网格模式的特征提取与L2归一化;后者通过稀疏光流或SIFT匹配估计仿射变换,补偿相机运动;主跟踪器融合IoU、外观相似度与方向一致性,支持模块开关与自定义组件,实现高效多目标跟踪。

Jayson 博客 330

Consistent-Point半监督人群计数算法详解

本文首次指出并量化了点定位半监督人群计数中伪点的位置不一致与类别不一致问题(Mean-Teacher baseline 甚至不如只用标注数据),并给出两个极简正交模块——PA 用"被匹配的伪点与其邻近提议点定位相似"的洞察做空间平均,以 1/K 方差收缩稳住位置;IUC 用分类得分做实例级加权,压低决策边界附近模糊伪点的监督强度。两模块零推理开销、只引入一个超参 λ,在 5 数据集 × 3 标注比例的 15 个定位设置上全部 SOTA,并在 CPS 框架上验证泛化性,说明"伪标签一致性"是点定位半监督范式中

Keep_Trying_Go的博客 319

算法日常・每日刷题--<动态规划>41

给定两个字符串text1和text2,求其最长公共子序列长度。使用动态规划,定义dp[i][j]为text1前i个字符与text2前j个字符的最长公共子序列长度。若字符相等,则dp[i][j] = dp[i-1][j-1] + 1;否则取舍弃一方的最大值:dp[i][j] = max(dp[i-1][j], dp[i][j-1])。最终返回dp[m][n]。时间复杂度O(mn),空间复杂度O(mn)。

2503_92261635的博客 116

C++ 标准库算法全景指南

本文系统梳理C++标准库<algorithm>与<numeric>中100+算法,按功能分为非修改、修改、排序、二分查找、归并、堆、极值、字典序、排列及数值操作十大类,涵盖典型用法与时间复杂度。结合C++20范围(ranges)与投影特性,实现更简洁高效的代码。

sss123ddd的博客 214

高速公路无人机巡检系统:场景、算法与部署方案

高速公路巡检的工程矛盾,不在“能不能飞到”,而在“端侧算力、时延约束与精度要求三者之间如何分配”。路面病害要求毫米级识别,但高速动态成像与运动模糊压制了单帧精度;交通事件要求秒级推送,但隧道与峡谷的通信盲区切断了云端链路;桥梁梁底的无卫星信号环境,则把自主定位的难度推到了GNSS失效的边界。本文从“端—边—云”三级协同的工程逻辑出发,拆解路面病害、交通事件、桥梁结构三类场景下算法与部署的实际取舍,结合重庆绕城高速、广西六宾高速、南京6G低空巡检等公开数据,给出可复用的系统设计参考。

THJS优快云的博客 186

算法1. 双指针法-移动零

【代码】算法1. 双指针法-移动零。

2603_95728536的博客 228

嵌入式 AI 智能相机怎么选?从算力、算法架构、部署方式深度对比

在工业自动化视觉检测场景中,嵌入式 AI 智能相机正在逐步替代传统 “工业相机 + 上位机工控机” 的方案。但市面上产品参数繁杂,很多工程师选型时容易被纸面算力、宣传卖点误导。本文从**持续算力性能、算法架构设计、现场部署模式**三个核心维度,梳理嵌入式视觉设备不同技术路线的差异、适用边界与落地痛点,为机器视觉工程师、自动化集成商、产线技改负责人提供可落地的选型参考。

VisionDataLab的博客 289
上一篇: 【计算机程序设计艺术 TAOCP】卷3·6.3 数字查找:把键码拆成字符,怎样沿前缀找到记录
下一篇: 【计算机程序设计艺术 TAOCP】卷4·7.1.2 布尔求值:从最短计算链到共享、分解与复杂度下界
Js.Tx
博客等级 码龄8年 332粉丝 59原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值