<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>exdoubled&#39;blog</title>
  
  
  <link href="https://exdoubled.github.io/atom.xml" rel="self"/>
  
  <link href="https://exdoubled.github.io/"/>
  <updated>2026-07-10T08:12:54.119Z</updated>
  <id>https://exdoubled.github.io/</id>
  
  <author>
    <name>exdoubled</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>Reverse题记</title>
    <link href="https://exdoubled.github.io/reverse/ReverseCTF/"/>
    <id>https://exdoubled.github.io/reverse/ReverseCTF/</id>
    <published>2026-07-09T02:00:00.000Z</published>
    <updated>2026-07-10T08:12:54.119Z</updated>
    
    <content type="html"><![CDATA[<p>有段时间没有写 CTF Reverse 题目了，在 AI盛行的时代，手搓题目只能说是自己的兴趣了，在此记录一下以后手搓的 Reverse题目，作为纪念吧</p><h2 id="attachments_505">Attachments_505</h2><p>找到 <code>main</code> 函数</p><div class="code-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">scanf</span>(<span class="string">&quot;%s&quot;</span>, Str);</span><br><span class="line"> <span class="keyword">if</span> ( <span class="built_in">strlen</span>(Str) != <span class="number">49</span> )</span><br><span class="line"> &#123;</span><br><span class="line">   puts_0(<span class="string">&quot;It&#x27;s not enough.&quot;</span>);</span><br><span class="line">   system_0(<span class="string">&quot;pause&quot;</span>);</span><br><span class="line">   <span class="built_in">exit</span>(<span class="number">0</span>);</span><br><span class="line"> &#125;</span><br><span class="line"> v10 = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">for</span> ( i = <span class="number">0</span>; i &lt;= <span class="number">8</span>; ++i )</span><br><span class="line"> &#123;</span><br><span class="line">   <span class="keyword">for</span> ( j = <span class="number">0</span>; j &lt;= <span class="number">8</span>; ++j )</span><br><span class="line">   &#123;</span><br><span class="line">     <span class="keyword">if</span> ( !box[<span class="number">9</span> * i + j] )</span><br><span class="line">     &#123;</span><br><span class="line">       v3 = v10++;</span><br><span class="line">       box[<span class="number">9</span> * i + j] = Str[v3] - <span class="number">48</span>;</span><br><span class="line">     &#125;</span><br><span class="line">   &#125;</span><br><span class="line"> &#125;</span><br><span class="line"> check1();</span><br><span class="line"> check2();</span><br><span class="line"> check3();</span><br></pre></td></tr></table></figure></div><p>判断输入字符串是否为 49</p><p>此处 <code>v3</code> 和 <code>v10</code> 其实是一个变量，将对应的<code>Str[i]</code> 减去 48 储存到 <code>box</code> 中，0 ASCII 码值为48，实则为字符转数字，49长度的字符串转为每9个一行</p><p><code>check1</code></p><div class="code-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> ( i = <span class="number">0</span>; i &lt;= <span class="number">8</span>; ++i )</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="keyword">for</span> ( j = <span class="number">1</span>; j &lt;= <span class="number">9</span>; ++j )</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="keyword">for</span> ( k = <span class="number">0</span>; ; ++k )</span><br><span class="line">      &#123;</span><br><span class="line">        result = (<span class="type">unsigned</span> <span class="type">int</span>)(<span class="type">char</span>)box[<span class="number">9</span> * i + k];</span><br><span class="line">        <span class="keyword">if</span> ( j == (_DWORD)result )</span><br><span class="line">          <span class="keyword">break</span>;</span><br><span class="line">        <span class="keyword">if</span> ( k == <span class="number">8</span> )</span><br><span class="line">        &#123;</span><br><span class="line">          printf_0(<span class="string">&quot;Wrong!!!Try again!!!&quot;</span>);</span><br><span class="line">          system_0(<span class="string">&quot;pause&quot;</span>);</span><br><span class="line">          <span class="built_in">exit</span>(<span class="number">0</span>);</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure></div><p>每一行，需要满足 1-9 的数字都出现一次</p><p><code>check2</code></p><div class="code-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> ( i = <span class="number">0</span>; i &lt;= <span class="number">8</span>; ++i )</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="keyword">for</span> ( j = <span class="number">1</span>; j &lt;= <span class="number">9</span>; ++j )</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="keyword">for</span> ( k = <span class="number">0</span>; ; ++k )</span><br><span class="line">      &#123;</span><br><span class="line">        result = (<span class="type">unsigned</span> <span class="type">int</span>)(<span class="type">char</span>)box[<span class="number">9</span> * k + i];</span><br><span class="line">        <span class="keyword">if</span> ( j == (_DWORD)result )</span><br><span class="line">          <span class="keyword">break</span>;</span><br><span class="line">        <span class="keyword">if</span> ( k == <span class="number">8</span> )</span><br><span class="line">        &#123;</span><br><span class="line">          printf_0(<span class="string">&quot;Wrong!!!Try again!!!&quot;</span>);</span><br><span class="line">          system_0(<span class="string">&quot;pause&quot;</span>);</span><br><span class="line">          <span class="built_in">exit</span>(<span class="number">0</span>);</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure></div><p>同样的，每一列，需要满足 1-9 的数字都出现一次</p><p><code>check3</code></p><div class="code-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> ( i = <span class="number">0</span>; i &lt;= <span class="number">8</span>; i += <span class="number">3</span> )</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="keyword">for</span> ( j = <span class="number">0</span>; j &lt;= <span class="number">8</span>; j += <span class="number">3</span> )</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="keyword">for</span> ( k = <span class="number">1</span>; k &lt;= <span class="number">9</span>; ++k )</span><br><span class="line">      &#123;</span><br><span class="line">        v5 = <span class="number">0</span>;</span><br><span class="line">        v4 = <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">while</span> ( <span class="number">1</span> )</span><br><span class="line">        &#123;</span><br><span class="line">          result = (<span class="type">unsigned</span> <span class="type">int</span>)(<span class="type">char</span>)box[<span class="number">9</span> * i + <span class="number">9</span> * v5 + j + v4];</span><br><span class="line">          <span class="keyword">if</span> ( k == (_DWORD)result )</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">          <span class="keyword">if</span> ( v5 == <span class="number">2</span> &amp;&amp; v4 == <span class="number">2</span> )</span><br><span class="line">          &#123;</span><br><span class="line">            printf_0(<span class="string">&quot;Wrong!!!Try again!!!&quot;</span>);</span><br><span class="line">            system_0(<span class="string">&quot;pause&quot;</span>);</span><br><span class="line">            <span class="built_in">exit</span>(<span class="number">0</span>);</span><br><span class="line">          &#125;</span><br><span class="line">          <span class="keyword">if</span> ( ++v4 == <span class="number">3</span> )</span><br><span class="line">          &#123;</span><br><span class="line">            ++v5;</span><br><span class="line">            v4 = <span class="number">0</span>;</span><br><span class="line">          &#125;</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure></div><p>为了检查每个 3x3 的小方格是否包含 1-9 的数字</p><p>那么 box 实际为一个满足数独要求的 9x9 的二维数组，输入的字符串长度为49</p><p>实际上可以从<code>puts_0("Enjoy the beauty of reverse and sudoku!");</code>看出这里是数独</p><p>最后输出：</p><div class="code-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> ( k = <span class="number">0</span>; k &lt; <span class="built_in">strlen</span>(Str); ++k )</span><br><span class="line">    putchar_0(Str[k] ^ magic[k]);</span><br><span class="line">  putchar_0(<span class="number">125</span>)</span><br></pre></td></tr></table></figure></div><p><code>magic</code> 数组：</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"> .data:0000000000404080 magic           db 6Bh, 2, 66h, 70h, 44h, 69h, 7Eh, 6Eh, 43h, 4Ah, 78h</span><br><span class="line">.data:0000000000404080                                         ; DATA XREF: main+196↑o</span><br><span class="line">.data:000000000040408B                 db 4Ah, 6Dh, 60h, 56h, 0, 51h, 59h, 50h, 43h, 50h, 51h</span><br><span class="line">.data:0000000000404096                 db 6Dh, 74h, 2, 55h, 50h, 52h, 6Eh, 6Fh, 79h, 40h, 5Dh</span><br><span class="line">.data:00000000004040A1                 db 4Bh, 1Eh, 19h, 1Ch, 74h, 3, 54h, 7, 4Ch, 52h, 6Ah, 60h</span><br><span class="line">.data:00000000004040AD                 db 50h, 58h, 40h, 58h, 0Fh dup(0)</span><br></pre></td></tr></table></figure></div><p>64 位，即</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">6B 02 66 70 44 69 7E 6E 43 4A 78 4A 6D 60 56 00 51 59 50 43 50 51 6D 74 02 55 50 52 6E 6F 79 40 5D 4B 1E 19 1C 74 03 54 07 4C 52 6A 60 50 58 40 58 0F 00</span><br></pre></td></tr></table></figure></div><p>同时可查询到 <code>box</code> 数组默认值</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">.data:0000000000404020 box             db 2 dup(0), 5, 2 dup(0), 4, 3, 6, 5 dup(0), 5, 2 dup(0)</span><br><span class="line">.data:0000000000404020                                         ; DATA XREF: check1+69↑o</span><br><span class="line">.data:0000000000404020                                         ; check2+69↑o ...</span><br><span class="line">.data:0000000000404030                 db 2, 4, 0, 4, 9, 6, 7, 4 dup(0), 1, 0, 6, 0, 2, 2 dup(0)</span><br><span class="line">.data:0000000000404042                 db 3, 0, 9, 2 dup(0), 7, 2 dup(0), 1, 0, 8, 0, 3, 3 dup(0)</span><br><span class="line">.data:0000000000404052                 db 5, 0, 9, 0, 2, 2 dup(0), 5, 0, 7, 2 dup(0), 9, 7, 0</span><br><span class="line">.data:0000000000404061                 db 4, 3 dup(0), 8, 3 dup(0), 9, 2 dup(0), 4, 3 dup(0)</span><br><span class="line">.data:0000000000404070                 db 6, 0Fh dup(0)</span><br></pre></td></tr></table></figure></div><p>即</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">0 0 5 | 0 0 4 | 3 6 0</span><br><span class="line">0 0 0 | 0 5 0 | 0 2 4</span><br><span class="line">0 4 9 | 6 7 0 | 0 0 0</span><br><span class="line">------+-------+------</span><br><span class="line">1 0 6 | 0 2 0 | 0 3 0</span><br><span class="line">9 0 0 | 7 0 0 | 1 0 8</span><br><span class="line">0 3 0 | 0 0 5 | 0 9 0</span><br><span class="line">------+-------+------</span><br><span class="line">2 0 0 | 5 0 7 | 0 0 9</span><br><span class="line">7 0 4 | 0 0 0 | 8 0 0</span><br><span class="line">0 9 0 | 0 4 0 | 0 0 6</span><br></pre></td></tr></table></figure></div><p>解数独</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">8 2 5 | 9 1 4 | 3 6 7</span><br><span class="line">6 7 1 | 3 5 8 | 9 2 4</span><br><span class="line">3 4 9 | 6 7 2 | 5 8 1</span><br><span class="line">------+-------+------</span><br><span class="line">1 8 6 | 4 2 9 | 7 3 5</span><br><span class="line">9 5 2 | 7 6 3 | 1 4 8</span><br><span class="line">4 3 7 | 1 8 5 | 6 9 2</span><br><span class="line">------+-------+------</span><br><span class="line">2 6 8 | 5 3 7 | 4 1 9</span><br><span class="line">7 1 4 | 2 9 6 | 8 5 3</span><br><span class="line">5 9 3 | 8 4 1 | 2 7 6</span><br></pre></td></tr></table></figure></div><p>对应应该输入的字符串为：</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">8291767138932581849755263447186268341129653538127</span><br></pre></td></tr></table></figure></div><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"># ./chall.exe</span><br><span class="line">&lt;---  moectf2021  ---&gt;</span><br><span class="line"> [A_game] Welcome to moectf2021.</span><br><span class="line">Let&#x27;s play a game!</span><br><span class="line">Now input your answer, and if you are right, I will give you flag</span><br><span class="line">input : 8291767138932581849755263447186268341129653538127</span><br><span class="line">Congratulations!!!!</span><br><span class="line">Enjoy the beauty of reverse and sudoku!</span><br><span class="line">And here is your flag : moectf&#123;S0_As_I_prAy_Un1imited_B1ade_WOrks---E1m1ya_Shiro&#125;</span><br></pre></td></tr></table></figure></div><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">moectf&#123;S0_As_I_prAy_Un1imited_B1ade_WOrks---E1m1ya_Shiro&#125;</span><br></pre></td></tr></table></figure></div><h2 id="attachments_493">Attachments_493</h2><p>静态链接，C++ 代码编写</p><div class="code-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">std::<span class="keyword">operator</span>&lt;&lt;&lt;std::char_traits&lt;<span class="type">char</span>&gt;&gt;(&amp;_TMC_END__, <span class="string">&quot;Welcome to MoeCTF 2020!\nPlease input your flag here &gt;&gt;&quot;</span>, envp);</span><br><span class="line">  __isoc99_scanf(<span class="string">&quot;%22s&quot;</span>, flag);</span><br><span class="line">  <span class="built_in">sub_0</span>();</span><br><span class="line">  <span class="keyword">for</span> ( i = <span class="number">0</span>; i &lt;= <span class="number">21</span>; ++i )</span><br><span class="line">  &#123;</span><br><span class="line">    v3 = (<span class="type">unsigned</span> __int8)flag[i];</span><br><span class="line">    <span class="keyword">if</span> ( (_BYTE)v3 != key[i] )</span><br><span class="line">    &#123;</span><br><span class="line">      v4 = std::<span class="keyword">operator</span>&lt;&lt;&lt;std::char_traits&lt;<span class="type">char</span>&gt;&gt;(&amp;_TMC_END__, <span class="string">&quot;Ruaaaaa~Wrong!&quot;</span>, v3);</span><br><span class="line">      std::ostream::<span class="keyword">operator</span>&lt;&lt;(v4, &amp;std::endl&lt;<span class="type">char</span>,std::char_traits&lt;<span class="type">char</span>&gt;&gt;);</span><br><span class="line">      <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  v6 = std::<span class="keyword">operator</span>&lt;&lt;&lt;std::char_traits&lt;<span class="type">char</span>&gt;&gt;(</span><br><span class="line">         &amp;_TMC_END__,</span><br><span class="line">         <span class="string">&quot;Congratulations!!!\nWhat you input here is the true flag!!&quot;</span>,</span><br><span class="line">         v3);</span><br><span class="line">  std::ostream::<span class="keyword">operator</span>&lt;&lt;(v6, &amp;std::endl&lt;<span class="type">char</span>,std::char_traits&lt;<span class="type">char</span>&gt;&gt;);</span><br></pre></td></tr></table></figure></div><p>输入了一个长度为 22 的字符串</p><div class="code-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">__int64 __fastcall <span class="title">sub_0</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">  x = (byte_741B1 | y) ^ x &amp; byte_741B1 ^ (<span class="number">2</span> * x);</span><br><span class="line">  y = (byte_741B1 | x) ^ y &amp; byte_741B1 ^ (<span class="number">2</span> * y);</span><br><span class="line">  byte_741A9 = ~(<span class="number">6</span> - byte_741A9);</span><br><span class="line">  <span class="built_in">sub_1</span>();</span><br><span class="line">  <span class="built_in">sub_1998</span>();</span><br><span class="line">  <span class="keyword">return</span> <span class="built_in">sub_1999</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function">__int64 __fastcall <span class="title">sub_1</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">  x = (byte_741A9 | y) ^ x &amp; byte_741A9 ^ (<span class="number">2</span> * x);</span><br><span class="line">  y = (byte_741A9 | x) ^ y &amp; byte_741A9 ^ (<span class="number">2</span> * y);</span><br><span class="line">  byte_741A9 = ~(<span class="number">-8</span> - byte_741A9);</span><br><span class="line">  <span class="built_in">sub_2</span>();</span><br><span class="line">  <span class="keyword">if</span> ( <span class="number">2</span> * y + ((((_BYTE)x - <span class="number">1</span>) * (_BYTE)x) &amp; <span class="number">1</span>) == (((((((_BYTE)x - <span class="number">1</span>) * (_BYTE)x) &amp; <span class="number">1</span>) + <span class="number">2</span> * y) | <span class="number">1</span>) == <span class="number">1</span>) )</span><br><span class="line">    <span class="built_in">exit</span>(<span class="number">0</span>);</span><br><span class="line">  <span class="built_in">sub_1996</span>();</span><br><span class="line">  <span class="keyword">return</span> <span class="built_in">sub_1997</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">....</span><br></pre></td></tr></table></figure></div><p>进行了多次加密，顺序为</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sub_0 -&gt; sub_1 -&gt; sub_2 -&gt; ... -&gt;sub_1996 -&gt; sub_1997 -&gt; sub_1998 -&gt; sub_1999</span><br></pre></td></tr></table></figure></div><p>主函数主要是比较输入的字符串的和 <code>key</code>字符串的每个字符是否相同，<code>key</code> 字符串为：</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">.data:0000000000074060 key             db 2, 0Ah, 6, 16h, 13h, 1Ch, 2, 4Dh, 41h, 28h, 3Dh, 56h</span><br><span class="line">.data:0000000000074060                                         ; DATA XREF: main+5A↑o</span><br><span class="line">.data:000000000007406C                 db 52h, 39h, 19h, 70h, 51h, 1Bh, 5Ah, 59h, 7Ah, 22h, 2 dup(0)</span><br></pre></td></tr></table></figure></div><p>即</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">02 0A 06 16 13 1C 02 4D 41 28 3D 56 52 39 19 70 51 1B 5A 59 7A 22</span><br></pre></td></tr></table></figure></div><p>通过动态调试知道第 <code>k</code> 个输入只影响输出 <code>k</code> 和<code>k-1</code></p><p>我们知道 flag 字符串前面是固定的 <code>meoctf&#123;</code>，所以可以尝试写gdb_python 脚本爆破</p><div class="code-container" data-rel="Python"><figure class="iseeu highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> gdb, string</span><br><span class="line"></span><br><span class="line">flag_addr = <span class="built_in">int</span>(gdb.parse_and_eval(<span class="string">&#x27;(char*)&amp;flag&#x27;</span>))</span><br><span class="line">key_addr = <span class="built_in">int</span>(gdb.parse_and_eval(<span class="string">&#x27;(char*)&amp;key&#x27;</span>))</span><br><span class="line">x_addr = <span class="built_in">int</span>(gdb.parse_and_eval(<span class="string">&#x27;(int*)&amp;x&#x27;</span>))</span><br><span class="line">y_addr = <span class="built_in">int</span>(gdb.parse_and_eval(<span class="string">&#x27;(int*)&amp;y&#x27;</span>))</span><br><span class="line">inf = gdb.selected_inferior()</span><br><span class="line">key = <span class="built_in">bytes</span>(inf.read_memory(key_addr, <span class="number">22</span>))</span><br><span class="line"></span><br><span class="line">charset = string.ascii_letters + string.digits + <span class="string">&#x27;_&#123;&#125;-!@#$%^&amp;*()+=&#x27;</span></span><br><span class="line">charset += <span class="string">&#x27;&#x27;</span>.join(<span class="built_in">chr</span>(i) <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">33</span>, <span class="number">127</span>) <span class="keyword">if</span> <span class="built_in">chr</span>(i) <span class="keyword">not</span> <span class="keyword">in</span> charset)</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">transform</span>(<span class="params">s</span>):</span><br><span class="line">    s = s.ljust(<span class="number">22</span>, <span class="string">&#x27;\x00&#x27;</span>)[:<span class="number">22</span>]</span><br><span class="line">    inf.write_memory(x_addr, (<span class="number">0xdeadbeef</span>).to_bytes(<span class="number">4</span>, <span class="string">&#x27;little&#x27;</span>))</span><br><span class="line">    inf.write_memory(y_addr, (<span class="number">0xdeadbeef</span>).to_bytes(<span class="number">4</span>, <span class="string">&#x27;little&#x27;</span>))</span><br><span class="line">    inf.write_memory(flag_addr, s.encode(<span class="string">&#x27;latin1&#x27;</span>))</span><br><span class="line">    gdb.execute(<span class="string">&#x27;call (void) sub_0()&#x27;</span>, to_string=<span class="literal">True</span>)</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">bytes</span>(inf.read_memory(flag_addr, <span class="number">22</span>))</span><br><span class="line"></span><br><span class="line">paths = [<span class="string">&#x27;m&#x27;</span>]</span><br><span class="line"><span class="keyword">for</span> j <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">21</span>):</span><br><span class="line">    new = []</span><br><span class="line">    <span class="keyword">for</span> p <span class="keyword">in</span> paths:</span><br><span class="line">        <span class="keyword">if</span> j + <span class="number">1</span> &lt; <span class="built_in">len</span>(<span class="string">&#x27;moectf&#123;&#x27;</span>):</span><br><span class="line">            candidates = <span class="string">&#x27;moectf&#123;&#x27;</span>[j + <span class="number">1</span>]</span><br><span class="line">        <span class="keyword">elif</span> j + <span class="number">1</span> == <span class="number">21</span>:</span><br><span class="line">            candidates = <span class="string">&#x27;\x00&#x27;</span></span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            candidates = charset</span><br><span class="line">        <span class="keyword">for</span> c <span class="keyword">in</span> candidates:</span><br><span class="line">            s = p + c</span><br><span class="line">            <span class="keyword">if</span> transform(s)[j] == key[j]:</span><br><span class="line">                new.append(s)</span><br><span class="line">    paths = new</span><br><span class="line">    <span class="built_in">print</span>(j, paths[:<span class="number">5</span>])</span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(paths)</span><br></pre></td></tr></table></figure></div><p>上面这个代码的逻辑就是不断尝试调用 <code>sub0</code> 来爆破flag，如果下一位算出来的答案是 <code>key[j]</code>那么就说明这个字符是正确的</p><div class="code-container" data-rel="Bash"><figure class="iseeu highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">pwndbg&gt; <span class="built_in">source</span> solution.py</span><br><span class="line">0 [<span class="string">&#x27;mo&#x27;</span>]</span><br><span class="line">1 [<span class="string">&#x27;moe&#x27;</span>]</span><br><span class="line">2 [<span class="string">&#x27;moec&#x27;</span>]</span><br><span class="line">3 [<span class="string">&#x27;moect&#x27;</span>]</span><br><span class="line">4 [<span class="string">&#x27;moectf&#x27;</span>]</span><br><span class="line">5 [<span class="string">&#x27;moectf&#123;&#x27;</span>]</span><br><span class="line">6 [<span class="string">&#x27;moectf&#123;y&#x27;</span>]</span><br><span class="line">7 [<span class="string">&#x27;moectf&#123;y0&#x27;</span>]</span><br><span class="line">8 [<span class="string">&#x27;moectf&#123;y0u&#x27;</span>]</span><br><span class="line">9 [<span class="string">&#x27;moectf&#123;y0u_&#x27;</span>]</span><br><span class="line">10 [<span class="string">&#x27;moectf&#123;y0u_a&#x27;</span>]</span><br><span class="line">11 [<span class="string">&#x27;moectf&#123;y0u_a2&#x27;</span>]</span><br><span class="line">12 [<span class="string">&#x27;moectf&#123;y0u_a2e&#x27;</span>]</span><br><span class="line">13 [<span class="string">&#x27;moectf&#123;y0u_a2e_&#x27;</span>]</span><br><span class="line">14 [<span class="string">&#x27;moectf&#123;y0u_a2e_G&#x27;</span>]</span><br><span class="line">15 [<span class="string">&#x27;moectf&#123;y0u_a2e_G0&#x27;</span>]</span><br><span class="line">16 [<span class="string">&#x27;moectf&#123;y0u_a2e_G0d&#x27;</span>]</span><br><span class="line">17 [<span class="string">&#x27;moectf&#123;y0u_a2e_G0d~&#x27;</span>]</span><br><span class="line">18 [<span class="string">&#x27;moectf&#123;y0u_a2e_G0d~!&#x27;</span>]</span><br><span class="line">19 [<span class="string">&#x27;moectf&#123;y0u_a2e_G0d~!&#125;&#x27;</span>]</span><br><span class="line">20 [<span class="string">&#x27;moectf&#123;y0u_a2e_G0d~!&#125;\x00&#x27;</span>]</span><br><span class="line">[<span class="string">&#x27;moectf&#123;y0u_a2e_G0d~!&#125;\x00&#x27;</span>]</span><br></pre></td></tr></table></figure></div><p>得到 flag</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">moectf&#123;y0u_a2e_G0d~!&#125;</span><br></pre></td></tr></table></figure></div><h2 id="attachments_494">Attachments_494</h2><p>搜索字符串发现可疑字符串</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br></pre></td><td class="code"><pre><span class="line">.rdata:0000000140005470 unk_140005470   db  6Dh ; m             ; DATA XREF: main+163↑o</span><br><span class="line">.rdata:0000000140005471                 db  6Fh ; o</span><br><span class="line">.rdata:0000000140005472                 db  65h ; e</span><br><span class="line">.rdata:0000000140005473                 db  63h ; c</span><br><span class="line">.rdata:0000000140005474                 db  74h ; t</span><br><span class="line">.rdata:0000000140005475                 db  66h ; f</span><br><span class="line">.rdata:0000000140005476                 db  7Bh ; &#123;</span><br><span class="line">.rdata:0000000140005477                 db 0E4h</span><br><span class="line">.rdata:0000000140005478                 db 0B8h</span><br><span class="line">.rdata:0000000140005479                 db  96h</span><br><span class="line">.rdata:000000014000547A                 db 0E7h</span><br><span class="line">.rdata:000000014000547B                 db  95h</span><br><span class="line">.rdata:000000014000547C                 db  8Ch</span><br><span class="line">.rdata:000000014000547D                 db 0E3h</span><br><span class="line">.rdata:000000014000547E                 db  81h</span><br><span class="line">.rdata:000000014000547F                 db 0A7h</span><br><span class="line">.rdata:0000000140005480                 db 0E4h</span><br><span class="line">.rdata:0000000140005481                 db 0B8h</span><br><span class="line">.rdata:0000000140005482                 db  80h</span><br><span class="line">.rdata:0000000140005483                 db 0E7h</span><br><span class="line">.rdata:0000000140005484                 db  95h</span><br><span class="line">.rdata:0000000140005485                 db 0AAh</span><br><span class="line">.rdata:0000000140005486                 db 0E5h</span><br><span class="line">.rdata:0000000140005487                 db 0B9h</span><br><span class="line">.rdata:0000000140005488                 db 0B8h</span><br><span class="line">.rdata:0000000140005489                 db 0E3h</span><br><span class="line">.rdata:000000014000548A                 db  81h</span><br><span class="line">.rdata:000000014000548B                 db  9Bh</span><br><span class="line">.rdata:000000014000548C                 db 0E3h</span><br><span class="line">.rdata:000000014000548D                 db  81h</span><br><span class="line">.rdata:000000014000548E                 db 0AAh</span><br><span class="line">.rdata:000000014000548F                 db 0E5h</span><br><span class="line">.rdata:0000000140005490                 db 0A5h</span><br><span class="line">.rdata:0000000140005491                 db 0B3h</span><br><span class="line">.rdata:0000000140005492                 db 0E3h</span><br><span class="line">.rdata:0000000140005493                 db  81h</span><br><span class="line">.rdata:0000000140005494                 db 0AEh</span><br><span class="line">.rdata:0000000140005495                 db 0E5h</span><br><span class="line">.rdata:0000000140005496                 db 0ADh</span><br><span class="line">.rdata:0000000140005497                 db  90h</span><br><span class="line">.rdata:0000000140005498                 db 0E2h</span><br><span class="line">.rdata:0000000140005499                 db  80h</span><br><span class="line">.rdata:000000014000549A                 db  94h</span><br><span class="line">.rdata:000000014000549B                 db 0EFh</span><br><span class="line">.rdata:000000014000549C                 db 0BCh</span><br><span class="line">.rdata:000000014000549D                 db 0A3h</span><br><span class="line">.rdata:000000014000549E                 db 0EFh</span><br><span class="line">.rdata:000000014000549F                 db 0BCh</span><br><span class="line">.rdata:00000001400054A0                 db 0A8h</span><br><span class="line">.rdata:00000001400054A1                 db 0EFh</span><br><span class="line">.rdata:00000001400054A2                 db 0BCh</span><br><span class="line">.rdata:00000001400054A3                 db 0B4h</span><br><span class="line">.rdata:00000001400054A4                 db 0EFh</span><br><span class="line">.rdata:00000001400054A5                 db 0BCh</span><br><span class="line">.rdata:00000001400054A6                 db 0A8h</span><br><span class="line">.rdata:00000001400054A7                 db 0EFh</span><br><span class="line">.rdata:00000001400054A8                 db 0BCh</span><br><span class="line">.rdata:00000001400054A9                 db 0AFh</span><br><span class="line">.rdata:00000001400054AA                 db 0EFh</span><br><span class="line">.rdata:00000001400054AB                 db 0BCh</span><br><span class="line">.rdata:00000001400054AC                 db 0ACh</span><br><span class="line">.rdata:00000001400054AD                 db 0EFh</span><br><span class="line">.rdata:00000001400054AE                 db 0BCh</span><br><span class="line">.rdata:00000001400054AF                 db 0ACh</span><br><span class="line">.rdata:00000001400054B0                 db 0EFh</span><br><span class="line">.rdata:00000001400054B1                 db 0BCh</span><br><span class="line">.rdata:00000001400054B2                 db 0B9h</span><br><span class="line">.rdata:00000001400054B3                 db 0E2h</span><br><span class="line">.rdata:00000001400054B4                 db  80h</span><br><span class="line">.rdata:00000001400054B5                 db  94h</span><br><span class="line">.rdata:00000001400054B6                 db  7Dh ; &#125;</span><br></pre></td></tr></table></figure></div><p><code>main+163</code> 处有交叉引用，这边直接 UTF8 解码得到</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">moectf&#123;世界で一番幸せな女の子—ＣＨＴＨＯＬＬＹ—&#125;</span><br></pre></td></tr></table></figure></div><p><code>main</code> 函数有点复杂</p><p>先根据程序的输出对应一下</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"># ./CxxIntro.exe</span><br><span class="line"></span><br><span class="line">「だからきっと</span><br><span class="line">  今の私は、誰が何と言おうと</span><br><span class="line">  世界一幸せな女の子だ」</span><br><span class="line">                ——クトリ・ノタ・セニオリス</span><br><span class="line"></span><br><span class="line">Input your hex-encoded magic spell to help her romance.</span><br><span class="line">&gt;</span><br></pre></td></tr></table></figure></div><p>找到 <code>cout</code> 对象</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">sub_1400026E0(std::cout, &amp;unk_1400054E0);</span><br><span class="line">  sub_1400026E0(std::cout, &quot;\nInput your hex-encoded magic spell to help her romance.\n&gt; &quot;);</span><br></pre></td></tr></table></figure></div><p><code>&amp;unk_1400054E0</code> 就是那一串日文</p><p>最后 <code>v32 = sub_140002EA0(std::cout, v31, v30[2]);</code></p><p>预估是将输入进行一系列变换之后的结果</p><p>根据汇编代码，缺失的 <code>v21</code> 为 <code>cin</code>输入的字符串，<code>v22</code> 为字符串转为字节的字节串</p><p>修改相关变量：</p><div class="code-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><span class="line">sub_1400026E0(</span><br><span class="line">    <span class="built_in">std</span>::<span class="built_in">cout</span>,</span><br><span class="line">    <span class="string">&quot;\n&quot;</span></span><br><span class="line">    <span class="string">&quot;「だからきっと\n&quot;</span></span><br><span class="line">    <span class="string">&quot;  今の私は、誰が何と言おうと\n&quot;</span></span><br><span class="line">    <span class="string">&quot;  世界一幸せな女の子だ」\n&quot;</span></span><br><span class="line">    <span class="string">&quot;\t\t——クトリ・ノタ・セニオリス\n&quot;</span>);</span><br><span class="line">  sub_1400026E0(<span class="built_in">std</span>::<span class="built_in">cout</span>, <span class="string">&quot;\nInput your hex-encoded magic spell to help her romance.\n&gt; &quot;</span>);</span><br><span class="line">  v62[<span class="number">2</span>] = <span class="number">0</span>;</span><br><span class="line">  v63 = <span class="number">15</span>;</span><br><span class="line">  LOBYTE(v62[<span class="number">0</span>]) = <span class="number">0</span>;</span><br><span class="line">  sub_140002CB0(<span class="built_in">std</span>::<span class="built_in">cin</span>, v62);</span><br><span class="line">  cin1 = sub_140001C40(v21, &amp;v57, v62);</span><br><span class="line">  i1 = <span class="number">0</span>;</span><br><span class="line">  i2 = <span class="number">0</span>;</span><br><span class="line">  C15_ = <span class="number">15</span>;</span><br><span class="line">  C15 = <span class="number">15</span>;</span><br><span class="line">  LOBYTE(Src[<span class="number">0</span>]) = <span class="number">0</span>;</span><br><span class="line">  cincopy1 = (_BYTE *)cin1;</span><br><span class="line">  <span class="keyword">if</span> ( *(_QWORD *)(cin1 + <span class="number">24</span>) &gt;= <span class="number">0x10u</span> )</span><br><span class="line">    cincopy1 = *(_BYTE **)cin1;</span><br><span class="line">  cincopy2 = (_BYTE *)cin1;</span><br><span class="line">  <span class="keyword">if</span> ( *(_QWORD *)(cin1 + <span class="number">24</span>) &gt;= <span class="number">0x10u</span> )</span><br><span class="line">    cincopy2 = *(_BYTE **)cin1;</span><br><span class="line">  v27 = &amp;cincopy2[*(_QWORD *)(cin1 + <span class="number">16</span>)];</span><br><span class="line">  <span class="keyword">if</span> ( cincopy1 != v27 )</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="keyword">while</span> ( <span class="number">1</span> )</span><br><span class="line">    &#123;</span><br><span class="line">      xor = *cincopy1 ^ random(&amp;v67);</span><br><span class="line">      <span class="keyword">if</span> ( i1 &gt;= C15_ )</span><br><span class="line">      &#123;</span><br><span class="line">        push_back(Src, v29, v30, xor);</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">else</span></span><br><span class="line">      &#123;</span><br><span class="line">        i2 = i1 + <span class="number">1</span>;</span><br><span class="line">        Srcptr = Src;</span><br><span class="line">        <span class="keyword">if</span> ( C15_ &gt;= <span class="number">0x10</span> )</span><br><span class="line">          Srcptr = (<span class="type">void</span> **)Src[<span class="number">0</span>];</span><br><span class="line">        *((_BYTE *)Srcptr + i1) = xor;</span><br><span class="line">        *((_BYTE *)Srcptr + i1 + <span class="number">1</span>) = <span class="number">0</span>;</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">if</span> ( ++cincopy1 == v27 )</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">      C15_ = C15;</span><br><span class="line">      i1 = i2;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  v32 = sub_140001380((__int64)Block, v54, Src);</span><br><span class="line">  v33 = v32;</span><br><span class="line">  <span class="keyword">if</span> ( v32[<span class="number">3</span>] &gt;= <span class="number">0x10u</span> )</span><br><span class="line">    v33 = (_QWORD *)*v32;</span><br><span class="line">  v34 = sub_140002EA0(<span class="built_in">std</span>::<span class="built_in">cout</span>, v33, v32[<span class="number">2</span>]);</span><br></pre></td></tr></table></figure></div><p><code>random</code> 函数查看：</p><div class="code-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">__int64 __fastcall <span class="title">random</span><span class="params">(<span class="type">unsigned</span> <span class="type">int</span> *a1)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">  __int64 v2; <span class="comment">// r9</span></span><br><span class="line">  <span class="type">unsigned</span> <span class="type">int</span> v3; <span class="comment">// ecx</span></span><br><span class="line">  _DWORD *v4; <span class="comment">// r8</span></span><br><span class="line">  <span class="type">int</span> v5; <span class="comment">// edx</span></span><br><span class="line">  _DWORD *v6; <span class="comment">// r11</span></span><br><span class="line">  <span class="type">unsigned</span> <span class="type">int</span> v7; <span class="comment">// eax</span></span><br><span class="line">  __int64 v8; <span class="comment">// rbx</span></span><br><span class="line">  <span class="type">unsigned</span> <span class="type">int</span> v9; <span class="comment">// edx</span></span><br><span class="line">  _DWORD *v10; <span class="comment">// r11</span></span><br><span class="line">  __int64 v11; <span class="comment">// rbx</span></span><br><span class="line">  <span class="type">unsigned</span> <span class="type">int</span> v12; <span class="comment">// eax</span></span><br><span class="line">  <span class="type">unsigned</span> <span class="type">int</span> v13; <span class="comment">// edx</span></span><br><span class="line">  <span class="type">unsigned</span> <span class="type">int</span> v14; <span class="comment">// edx</span></span><br><span class="line">  <span class="type">unsigned</span> <span class="type">int</span> v15; <span class="comment">// edx</span></span><br><span class="line"></span><br><span class="line">  v2 = <span class="number">624</span>;</span><br><span class="line">  v3 = *a1;</span><br><span class="line">  <span class="keyword">if</span> ( v3 == <span class="number">624</span> )</span><br><span class="line">  &#123;</span><br><span class="line">    v4 = a1 + <span class="number">2</span>;</span><br><span class="line">    <span class="keyword">do</span></span><br><span class="line">    &#123;</span><br><span class="line">      v5 = *v4 ^ *(v4 - <span class="number">1</span>);</span><br><span class="line">      ++v4;</span><br><span class="line">      v4[<span class="number">622</span>] = ((*(v4 - <span class="number">2</span>) ^ v5 &amp; <span class="number">0x7FFFFFFFu</span>) &gt;&gt; <span class="number">1</span>)</span><br><span class="line">              ^ v4[<span class="number">395</span>]</span><br><span class="line">              ^ (((*((_BYTE *)v4 - <span class="number">8</span>) ^ (<span class="type">unsigned</span> __int8)v5) &amp; <span class="number">1</span>) != <span class="number">0</span> ? <span class="number">0x9908B0DF</span> : <span class="number">0</span>);</span><br><span class="line">      --v2;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">while</span> ( v2 );</span><br><span class="line">    v3 = *a1;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">else</span> <span class="keyword">if</span> ( v3 &gt;= <span class="number">0x4E0</span> )</span><br><span class="line">  &#123;</span><br><span class="line">    v6 = a1 + <span class="number">625</span>;</span><br><span class="line">    v7 = a1[<span class="number">625</span>];</span><br><span class="line">    v8 = <span class="number">227</span>;</span><br><span class="line">    <span class="keyword">do</span></span><br><span class="line">    &#123;</span><br><span class="line">      v9 = v7 ^ (v6[<span class="number">1</span>] ^ v7) &amp; <span class="number">0x7FFFFFFF</span>;</span><br><span class="line">      v7 = v6[<span class="number">1</span>];</span><br><span class="line">      *(v6 - <span class="number">624</span>) = (v9 &gt;&gt; <span class="number">1</span>) ^ v6[<span class="number">397</span>] ^ ((v6[<span class="number">1</span>] &amp; <span class="number">1</span>) != <span class="number">0</span> ? <span class="number">0x9908B0DF</span> : <span class="number">0</span>);</span><br><span class="line">      ++v6;</span><br><span class="line">      --v8;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">while</span> ( v8 );</span><br><span class="line">    v10 = a1 + <span class="number">852</span>;</span><br><span class="line">    v11 = <span class="number">396</span>;</span><br><span class="line">    v12 = a1[<span class="number">852</span>];</span><br><span class="line">    <span class="keyword">do</span></span><br><span class="line">    &#123;</span><br><span class="line">      v13 = v12 ^ (v10[<span class="number">1</span>] ^ v12) &amp; <span class="number">0x7FFFFFFF</span>;</span><br><span class="line">      v12 = v10[<span class="number">1</span>];</span><br><span class="line">      *(v10 - <span class="number">624</span>) = (v13 &gt;&gt; <span class="number">1</span>) ^ *(v10 - <span class="number">851</span>) ^ ((v10[<span class="number">1</span>] &amp; <span class="number">1</span>) != <span class="number">0</span> ? <span class="number">0x9908B0DF</span> : <span class="number">0</span>);</span><br><span class="line">      ++v10;</span><br><span class="line">      --v11;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">while</span> ( v11 );</span><br><span class="line">    a1[<span class="number">624</span>] = ((a1[<span class="number">1248</span>] ^ (a1[<span class="number">1</span>] ^ a1[<span class="number">1248</span>]) &amp; <span class="number">0x7FFFFFFF</span>) &gt;&gt; <span class="number">1</span>) ^ a1[<span class="number">397</span>] ^ ((a1[<span class="number">1</span>] &amp; <span class="number">1</span>) != <span class="number">0</span> ? <span class="number">0x9908B0DF</span> : <span class="number">0</span>);</span><br><span class="line">    v3 = <span class="number">0</span>;</span><br><span class="line">    *a1 = <span class="number">0</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  v14 = a1[v3 + <span class="number">1</span>];</span><br><span class="line">  *a1 = v3 + <span class="number">1</span>;</span><br><span class="line">  v15 = ((((v14 &gt;&gt; <span class="number">11</span>) &amp; a1[<span class="number">1249</span>] ^ v14) &amp; <span class="number">0xFF3A58AD</span>) &lt;&lt; <span class="number">7</span>) ^ (v14 &gt;&gt; <span class="number">11</span>) &amp; a1[<span class="number">1249</span>] ^ v14;</span><br><span class="line">  <span class="keyword">return</span> ((v15 &amp; <span class="number">0xFFFFDF8C</span>) &lt;&lt; <span class="number">15</span>) ^ v15 ^ ((((v15 &amp; <span class="number">0xFFFFDF8C</span>) &lt;&lt; <span class="number">15</span>) ^ v15) &gt;&gt; <span class="number">18</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><p>这个为 <code>MT19937</code> 实现的梅森旋转算法（随机数生成函数）</p><div class="code-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">_QWORD *__fastcall <span class="title">push_back</span><span class="params">(_QWORD *Src, __int64 a2, __int64 a3, <span class="type">char</span> a4)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">  <span class="type">size_t</span> v4; <span class="comment">// rbp</span></span><br><span class="line">  __int64 v5; <span class="comment">// rbx</span></span><br><span class="line">  <span class="type">unsigned</span> __int64 v8; <span class="comment">// r14</span></span><br><span class="line">  <span class="type">unsigned</span> __int64 v9; <span class="comment">// rcx</span></span><br><span class="line">  <span class="type">unsigned</span> __int64 v10; <span class="comment">// rdx</span></span><br><span class="line">  <span class="type">size_t</span> v11; <span class="comment">// rcx</span></span><br><span class="line">  <span class="type">void</span> *v12; <span class="comment">// rax</span></span><br><span class="line">  _QWORD *v13; <span class="comment">// rdi</span></span><br><span class="line">  _QWORD *v14; <span class="comment">// rbx</span></span><br><span class="line"></span><br><span class="line">  v4 = Src[<span class="number">2</span>];</span><br><span class="line">  v5 = <span class="number">0x7FFFFFFFFFFFFFFFLL</span>;</span><br><span class="line">  <span class="keyword">if</span> ( v4 == <span class="number">0x7FFFFFFFFFFFFFFFLL</span> )</span><br><span class="line">    <span class="built_in">sub_1400011B0</span>();</span><br><span class="line">  v8 = Src[<span class="number">3</span>];</span><br><span class="line">  v9 = (v4 + <span class="number">1</span>) | <span class="number">0xF</span>;</span><br><span class="line">  <span class="keyword">if</span> ( v9 &lt;= <span class="number">0x7FFFFFFFFFFFFFFFLL</span> )</span><br><span class="line">  &#123;</span><br><span class="line">    v10 = v8 &gt;&gt; <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">if</span> ( v8 &lt;= <span class="number">0x7FFFFFFFFFFFFFFFLL</span> - (v8 &gt;&gt; <span class="number">1</span>) )</span><br><span class="line">    &#123;</span><br><span class="line">      v5 = (v4 + <span class="number">1</span>) | <span class="number">0xF</span>;</span><br><span class="line">      <span class="keyword">if</span> ( v9 &lt; v10 + v8 )</span><br><span class="line">        v5 = v10 + v8;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  v11 = v5 + <span class="number">1</span>;</span><br><span class="line">  <span class="keyword">if</span> ( v5 == <span class="number">-1</span> )</span><br><span class="line">    v11 = <span class="number">-1</span>;</span><br><span class="line">  <span class="keyword">if</span> ( v11 &lt; <span class="number">0x1000</span> )</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="keyword">if</span> ( v11 )</span><br><span class="line">    &#123;</span><br><span class="line">      _mm_lfence();</span><br><span class="line">      v13 = <span class="keyword">operator</span> <span class="built_in">new</span>(v11);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">    &#123;</span><br><span class="line">      v13 = <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">else</span></span><br><span class="line">  &#123;</span><br><span class="line">    <span class="keyword">if</span> ( v11 + <span class="number">39</span> &lt; v11 )</span><br><span class="line">      <span class="built_in">sub_140001110</span>();</span><br><span class="line">    _mm_lfence();</span><br><span class="line">    v12 = <span class="keyword">operator</span> <span class="built_in">new</span>(v11 + <span class="number">39</span>);</span><br><span class="line">    <span class="keyword">if</span> ( !v12 )</span><br><span class="line">      <span class="keyword">goto</span> LABEL_20;</span><br><span class="line">    v13 = (_QWORD *)(((<span class="type">unsigned</span> __int64)v12 + <span class="number">39</span>) &amp; <span class="number">0xFFFFFFFFFFFFFFE0uLL</span>);</span><br><span class="line">    *(v13 - <span class="number">1</span>) = v12;</span><br><span class="line">  &#125;</span><br><span class="line">  Src[<span class="number">2</span>] = v4 + <span class="number">1</span>;</span><br><span class="line">  Src[<span class="number">3</span>] = v5;</span><br><span class="line">  <span class="keyword">if</span> ( v8 &lt; <span class="number">0x10</span> )</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="built_in">memcpy</span>(v13, Src, v4);</span><br><span class="line">    *((_BYTE *)v13 + v4) = a4;</span><br><span class="line">    *((_BYTE *)v13 + v4 + <span class="number">1</span>) = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">goto</span> LABEL_22;</span><br><span class="line">  &#125;</span><br><span class="line">  v14 = (_QWORD *)*Src;</span><br><span class="line">  <span class="built_in">memcpy</span>(v13, (<span class="type">const</span> <span class="type">void</span> *)*Src, v4);</span><br><span class="line">  *((_BYTE *)v13 + v4) = a4;</span><br><span class="line">  *((_BYTE *)v13 + v4 + <span class="number">1</span>) = <span class="number">0</span>;</span><br><span class="line">  <span class="keyword">if</span> ( v8 + <span class="number">1</span> &gt;= <span class="number">0x1000</span> )</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="keyword">if</span> ( (<span class="type">unsigned</span> __int64)v14 - *(v14 - <span class="number">1</span>) - <span class="number">8</span> &lt;= <span class="number">0x1F</span> )</span><br><span class="line">    &#123;</span><br><span class="line">      v14 = (_QWORD *)*(v14 - <span class="number">1</span>);</span><br><span class="line">      <span class="keyword">goto</span> LABEL_19;</span><br><span class="line">    &#125;</span><br><span class="line">LABEL_20:</span><br><span class="line">    <span class="built_in">invalid_parameter_noinfo_noreturn</span>();</span><br><span class="line">  &#125;</span><br><span class="line">LABEL_19:</span><br><span class="line">  _mm_lfence();</span><br><span class="line">  <span class="built_in">j_j_free</span>(v14);</span><br><span class="line">LABEL_22:</span><br><span class="line">  *Src = v13;</span><br><span class="line">  <span class="keyword">return</span> Src;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><p><code>v9 = (v4 + 1) | 0xF;</code> 为新容量计算，<code>v10 + v8</code>即容量+容量/2 为典型的动态数组扩容策略，则该函数为<code>std::vector</code> 的 <code>push_back</code> 实现</p><p>那么上述流程即位将输入的字符串转为字节串，然后每个字节与<code>random</code> 函数生成的随机数进行异或，暂存到 <code>Src</code>中，调用 <code>sub_140001380</code> 进行处理，最后输出到<code>cout</code> 中</p><p>这里的 Block 为上述构建的，<code>sub_140001380</code>为自定义算法</p><div class="code-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Hidden C++ exception states: #wind=1</span></span><br><span class="line">_QWORD *__fastcall <span class="title function_">sub_140001380</span><span class="params">(__int64 Block, _QWORD *a2, _QWORD *a3)</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="type">char</span> i; <span class="comment">// r15</span></span><br><span class="line">  <span class="type">unsigned</span> __int8 j; <span class="comment">// r13</span></span><br><span class="line">  _QWORD *v7; <span class="comment">// rcx</span></span><br><span class="line">  _QWORD *v8; <span class="comment">// rdx</span></span><br><span class="line">  _BYTE *num2_a3; <span class="comment">// rbp</span></span><br><span class="line">  _BYTE *len; <span class="comment">// r12</span></span><br><span class="line">  <span class="type">unsigned</span> __int64 row1; <span class="comment">// r9</span></span><br><span class="line">  __int64 v12; <span class="comment">// rdx</span></span><br><span class="line">  _BYTE *SBoxChar1; <span class="comment">// rax</span></span><br><span class="line">  <span class="type">unsigned</span> __int64 row2; <span class="comment">// r10</span></span><br><span class="line">  <span class="type">char</span> *SBoxChar2; <span class="comment">// r11</span></span><br><span class="line">  <span class="type">char</span> v16; <span class="comment">// al</span></span><br><span class="line">  <span class="type">char</span> *S1Other; <span class="comment">// rdx</span></span><br><span class="line">  <span class="type">char</span> v18; <span class="comment">// cl</span></span><br><span class="line">  __int64 v19; <span class="comment">// r14</span></span><br><span class="line">  __int64 v20; <span class="comment">// rcx</span></span><br><span class="line">  <span class="type">char</span> temp; <span class="comment">// dl</span></span><br><span class="line">  _BYTE *S2Other; <span class="comment">// r8</span></span><br><span class="line">  __int64 col3; <span class="comment">// rcx</span></span><br><span class="line">  <span class="type">unsigned</span> __int64 row3; <span class="comment">// rax</span></span><br><span class="line">  <span class="type">char</span> out; <span class="comment">// r9</span></span><br><span class="line">  <span class="type">unsigned</span> __int64 v26; <span class="comment">// rcx</span></span><br><span class="line">  <span class="type">unsigned</span> __int64 v27; <span class="comment">// rdx</span></span><br><span class="line">  _QWORD *v28; <span class="comment">// rax</span></span><br><span class="line"></span><br><span class="line">  a2[<span class="number">2</span>] = <span class="number">0</span>;</span><br><span class="line">  a2[<span class="number">3</span>] = <span class="number">15</span>;</span><br><span class="line">  *(_BYTE *)a2 = <span class="number">0</span>;</span><br><span class="line">  i = <span class="number">0</span>;</span><br><span class="line">  j = <span class="number">0</span>;</span><br><span class="line">  v7 = a3;</span><br><span class="line">  <span class="keyword">if</span> ( a3[<span class="number">3</span>] &lt; <span class="number">0x10u</span> )</span><br><span class="line">  &#123;</span><br><span class="line">    v8 = a3;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">else</span></span><br><span class="line">  &#123;</span><br><span class="line">    v7 = (_QWORD *)*a3;</span><br><span class="line">    v8 = (_QWORD *)*a3;</span><br><span class="line">  &#125;</span><br><span class="line">  num2_a3 = v7;</span><br><span class="line">  len = (<span class="type">char</span> *)v8 + a3[<span class="number">2</span>];</span><br><span class="line">  <span class="keyword">if</span> ( v7 != (_QWORD *)len )</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="keyword">do</span></span><br><span class="line">    &#123;</span><br><span class="line">      row1 = (<span class="type">unsigned</span> __int64)(<span class="type">unsigned</span> __int8)++i &gt;&gt; <span class="number">4</span>;</span><br><span class="line">      v12 = row1 + Block;</span><br><span class="line">      <span class="keyword">if</span> ( (i &amp; <span class="number">0xF</span>) != <span class="number">0</span> )</span><br><span class="line">        SBoxChar1 = (_BYTE *)((i &amp; <span class="number">0xF</span>) + *(_QWORD *)(Block + <span class="number">8</span> * row1));</span><br><span class="line">      <span class="keyword">else</span></span><br><span class="line">        SBoxChar1 = (_BYTE *)(v12 + <span class="number">128</span>);</span><br><span class="line">      j += *SBoxChar1;</span><br><span class="line">      row2 = (<span class="type">unsigned</span> __int64)j &gt;&gt; <span class="number">4</span>;</span><br><span class="line">      <span class="keyword">if</span> ( (j &amp; <span class="number">0xF</span>) != <span class="number">0</span> )</span><br><span class="line">        SBoxChar2 = (<span class="type">char</span> *)((j &amp; <span class="number">0xF</span>) + *(_QWORD *)(Block + <span class="number">8</span> * row2));</span><br><span class="line">      <span class="keyword">else</span></span><br><span class="line">        SBoxChar2 = (<span class="type">char</span> *)(row2 + Block + <span class="number">128</span>);</span><br><span class="line">      v16 = *SBoxChar2;</span><br><span class="line">      <span class="keyword">if</span> ( (i &amp; <span class="number">0xF</span>) != <span class="number">0</span> )</span><br><span class="line">      &#123;</span><br><span class="line">        v19 = i &amp; <span class="number">0xF</span>;</span><br><span class="line">        v20 = *(_QWORD *)(Block + <span class="number">8</span> * row1);</span><br><span class="line">        temp = *(_BYTE *)(v20 + v19);</span><br><span class="line">        *(_BYTE *)(v20 + v19) = v16;</span><br><span class="line">        *SBoxChar2 = temp;                      <span class="comment">// 交换 SBoxChar1 和 SBoxChar2</span></span><br><span class="line">        S1Other = (<span class="type">char</span> *)(v19 + *(_QWORD *)(Block + <span class="number">8</span> * row1));</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">else</span></span><br><span class="line">      &#123;</span><br><span class="line">        S1Other = (<span class="type">char</span> *)(v12 + <span class="number">128</span>);</span><br><span class="line">        v18 = *S1Other;</span><br><span class="line">        *S1Other = v16;</span><br><span class="line">        *SBoxChar2 = v18;</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">if</span> ( (j &amp; <span class="number">0xF</span>) != <span class="number">0</span> )</span><br><span class="line">        S2Other = (_BYTE *)(*(_QWORD *)(Block + <span class="number">8</span> * row2) + (j &amp; <span class="number">0xF</span>));</span><br><span class="line">      <span class="keyword">else</span></span><br><span class="line">        S2Other = (_BYTE *)(row2 + Block + <span class="number">128</span>);</span><br><span class="line">      col3 = (*S2Other + *S1Other) &amp; <span class="number">0xF</span>;</span><br><span class="line">      row3 = (<span class="type">unsigned</span> __int64)(<span class="type">unsigned</span> __int8)(*S2Other + *S1Other) &gt;&gt; <span class="number">4</span>;</span><br><span class="line">      <span class="keyword">if</span> ( ((*S2Other + *S1Other) &amp; <span class="number">0xF</span>) != <span class="number">0</span> )</span><br><span class="line">        row3 = *(_QWORD *)(Block + <span class="number">8</span> * row3);</span><br><span class="line">      <span class="keyword">else</span></span><br><span class="line">        col3 = Block + <span class="number">128</span>;</span><br><span class="line">      out = *num2_a3 ^ *(_BYTE *)(row3 + col3);</span><br><span class="line">      v26 = a2[<span class="number">2</span>];</span><br><span class="line">      v27 = a2[<span class="number">3</span>];</span><br><span class="line">      <span class="keyword">if</span> ( v26 &gt;= v27 )</span><br><span class="line">      &#123;</span><br><span class="line">        push_back(a2, v27, (__int64)S2Other, out);</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">else</span></span><br><span class="line">      &#123;</span><br><span class="line">        a2[<span class="number">2</span>] = v26 + <span class="number">1</span>;</span><br><span class="line">        v28 = a2;</span><br><span class="line">        <span class="keyword">if</span> ( v27 &gt;= <span class="number">0x10</span> )</span><br><span class="line">          v28 = (_QWORD *)*a2;</span><br><span class="line">        *((_BYTE *)v28 + v26) = out;</span><br><span class="line">        *((_BYTE *)v28 + v26 + <span class="number">1</span>) = <span class="number">0</span>;</span><br><span class="line">      &#125;</span><br><span class="line">      ++num2_a3;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">while</span> ( num2_a3 != len );</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> a2;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><p>记 <code>i</code> 为字节索引，<code>j</code>为累加器，每次加上上次取出的值 <code>SBoxChar1</code></p><p>这个函数为取出之前生成的 Block 里面的内容，取出两个值<code>SBoxChar1=Block[i &gt;&gt; 4][j &amp; 0xF]</code> 和<code>SBoxChar2[i &gt;&gt; 4][(j + SBoxChar1) &amp; 0xF]</code>，接着交换<code>SBoxChar1</code> 和<code>SBoxChar2</code>，然后计算<code>key = Block[(SBoxChar1 + SBoxChar2) &gt;&gt; 4][(SBoxChar1 + SBoxChar2) &amp; 0xF]</code>，与函数传入的 <code>a3</code> 进行逐字节异或，放到输出字符串中</p><p>接下来查看 Block 怎么初始化的</p><div class="code-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> ( i = <span class="number">0</span>; i &lt; <span class="number">0x10</span>; ++i )</span><br><span class="line">  &#123;</span><br><span class="line">    v4 = operator new(<span class="number">0x10u</span>);</span><br><span class="line">    v5 = Block[i];</span><br><span class="line">    Block[i] = v4;</span><br><span class="line">    <span class="keyword">if</span> ( v5 )</span><br><span class="line">    &#123;</span><br><span class="line">      j_j_free(v5);</span><br><span class="line">      v4 = Block[i];</span><br><span class="line">    &#125;</span><br><span class="line">    *v4 = <span class="number">0</span>;</span><br><span class="line">    *(_BYTE *)Block[i] = <span class="number">73</span> * (i + <span class="number">1</span>);</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure></div><p>每一行的第一个字符为 <code>73 * (i + 1)</code>，即<code>49 92 135 178 221 8 51 94 137 180 223 66 109 152 195 238</code>，然后经过了一系列变化，发现这个变化是固定的，动态拿到Block 的值</p><p>编写 IDA-python 脚本</p><div class="code-container" data-rel="Python"><figure class="iseeu highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> idc</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">dump_sbox</span>():</span><br><span class="line">    rsp = idc.get_reg_value(<span class="string">&quot;RSP&quot;</span>)</span><br><span class="line">    <span class="keyword">if</span> rsp <span class="keyword">is</span> <span class="literal">None</span>:</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">&quot;Cannot read RSP.&quot;</span>)</span><br><span class="line">        <span class="keyword">return</span></span><br><span class="line"></span><br><span class="line">    v65_offset = <span class="number">0x1A0</span></span><br><span class="line">    block_offset = <span class="number">0x120</span></span><br><span class="line"></span><br><span class="line">    v65_addr = rsp + v65_offset</span><br><span class="line">    block_addr = rsp + block_offset</span><br><span class="line"></span><br><span class="line">    v65_data = idc.get_bytes(v65_addr, <span class="number">16</span>)</span><br><span class="line">    <span class="keyword">if</span> <span class="keyword">not</span> v65_data:</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">&quot;Failed to read v65 at&quot;</span>, <span class="built_in">hex</span>(v65_addr))</span><br><span class="line">        <span class="keyword">return</span></span><br><span class="line"></span><br><span class="line">    sbox = []</span><br><span class="line">    <span class="keyword">for</span> row <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">16</span>):</span><br><span class="line">        ptr_addr = block_addr + row * <span class="number">8</span></span><br><span class="line">        row_ptr = idc.get_qword(ptr_addr)</span><br><span class="line">        <span class="keyword">if</span> row_ptr == <span class="number">0</span>:</span><br><span class="line">            <span class="built_in">print</span>(<span class="string">&quot;Row&quot;</span>, row, <span class="string">&quot;pointer is NULL&quot;</span>)</span><br><span class="line">            <span class="keyword">continue</span></span><br><span class="line">        row_data = idc.get_bytes(row_ptr, <span class="number">16</span>)</span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">not</span> row_data:</span><br><span class="line">            <span class="built_in">print</span>(<span class="string">&quot;Failed to read row&quot;</span>, row)</span><br><span class="line">            <span class="keyword">continue</span></span><br><span class="line">        correct_row = <span class="built_in">bytearray</span>(<span class="number">16</span>)</span><br><span class="line">        correct_row[<span class="number">0</span>] = v65_data[row]</span><br><span class="line">        correct_row[<span class="number">1</span>:] = row_data[<span class="number">1</span>:<span class="number">16</span>]</span><br><span class="line">        sbox.append(<span class="built_in">list</span>(correct_row))</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> <span class="built_in">len</span>(sbox) != <span class="number">16</span>:</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">&quot;Incomplete S-Box, only got&quot;</span>, <span class="built_in">len</span>(sbox), <span class="string">&quot;rows&quot;</span>)</span><br><span class="line">        <span class="keyword">return</span></span><br><span class="line"></span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot;const unsigned char SBOX[16][16] = &#123;&quot;</span>)</span><br><span class="line">    <span class="keyword">for</span> row <span class="keyword">in</span> sbox:</span><br><span class="line">        hex_vals = <span class="string">&quot;, &quot;</span>.join(<span class="string">&quot;0x&#123;:02X&#125;&quot;</span>.<span class="built_in">format</span>(x) <span class="keyword">for</span> x <span class="keyword">in</span> row)</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">&quot;    &#123;&quot;</span> + hex_vals + <span class="string">&quot;&#125;,&quot;</span>)</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot;&#125;;&quot;</span>)</span><br><span class="line"></span><br><span class="line">dump_sbox()</span><br></pre></td></tr></table></figure></div><p>得到 SBOX</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">const unsigned char SBOX[16][16] = &#123;</span><br><span class="line">    &#123;0x97, 0xE6, 0x29, 0x74, 0xD2, 0x11, 0x73, 0x29, 0x4A, 0x75, 0x13, 0x0E, 0x52, 0xF6, 0xE1, 0x54&#125;,</span><br><span class="line">    &#123;0xDE, 0xFF, 0x14, 0xC8, 0xC3, 0x25, 0xAD, 0xCE, 0x1A, 0x92, 0x7D, 0xE4, 0x79, 0x64, 0xDA, 0x62&#125;,</span><br><span class="line">    &#123;0x6F, 0xCA, 0x47, 0x32, 0xAC, 0x30, 0x45, 0x75, 0x14, 0xFF, 0x60, 0x08, 0x22, 0x56, 0xEF, 0x09&#125;,</span><br><span class="line">    &#123;0x42, 0xD6, 0xF0, 0x35, 0xBD, 0xD7, 0x10, 0xA4, 0xBE, 0xFE, 0x8B, 0xA5, 0xE2, 0x72, 0x8C, 0xC9&#125;,</span><br><span class="line">    &#123;0x59, 0xE6, 0x30, 0xA6, 0x91, 0xF2, 0x28, 0x65, 0xB4, 0xF7, 0x42, 0xA0, 0xDF, 0x41, 0xF7, 0x18&#125;,</span><br><span class="line">    &#123;0x43, 0xE1, 0xDC, 0x20, 0xC4, 0xAF, 0x22, 0xAC, 0xCD, 0xE2, 0x96, 0x91, 0xF3, 0x7B, 0x9C, 0xE8&#125;,</span><br><span class="line">    &#123;0x60, 0x4B, 0xB2, 0x47, 0x32, 0xA8, 0x30, 0x3D, 0x98, 0x15, 0x00, 0x7A, 0xFE, 0x13, 0x43, 0xE2&#125;,</span><br><span class="line">    &#123;0xCD, 0x2E, 0xD6, 0xF0, 0x24, 0xBD, 0xD7, 0x10, 0xA4, 0xBE, 0x03, 0x8B, 0xA5, 0xDE, 0x72, 0x8C&#125;,</span><br><span class="line">    &#123;0xCC, 0x59, 0xE6, 0x23, 0xB3, 0xCD, 0x0A, 0x9A, 0xB4, 0xFE, 0x74, 0x5F, 0xC0, 0xF6, 0x33, 0x82&#125;,</span><br><span class="line">    &#123;0xC5, 0x10, 0x6E, 0xAD, 0x0F, 0xC5, 0xE6, 0x11, 0xAF, 0xAA, 0xEE, 0x92, 0x7D, 0xF0, 0x7A, 0x9B&#125;,</span><br><span class="line">    &#123;0xB0, 0x64, 0x5F, 0xC1, 0x49, 0x6A, 0xB6, 0x2E, 0x19, 0x80, 0x15, 0x00, 0x76, 0xFE, 0x0B, 0x66&#125;,</span><br><span class="line">    &#123;0xE3, 0xCE, 0x48, 0xCC, 0xE1, 0x11, 0xB0, 0x9B, 0xFC, 0xA4, 0xBE, 0xF2, 0x8B, 0xA5, 0xDE, 0x72&#125;,</span><br><span class="line">    &#123;0x8C, 0xD1, 0x59, 0xE6, 0x1F, 0xB3, 0xCD, 0x0D, 0x9A, 0xB4, 0xF1, 0x81, 0x9B, 0xD8, 0x68, 0x82&#125;,</span><br><span class="line">    &#123;0xCC, 0x42, 0x2D, 0x8E, 0xC4, 0x01, 0x50, 0x93, 0xDE, 0x3C, 0x7B, 0xDD, 0x93, 0xB4, 0xDF, 0x7D&#125;,</span><br><span class="line">    &#123;0x78, 0xBC, 0x60, 0x4B, 0xBE, 0x48, 0x69, 0x7E, 0x32, 0x2D, 0x8F, 0x17, 0x38, 0x84, 0xFC, 0xE7&#125;,</span><br><span class="line">    &#123;0x4E, 0xE3, 0xCE, 0x44, 0xCC, 0xD9, 0x34, 0xB1, 0x9C, 0x16, 0x9A, 0xAF, 0xDF, 0x7E, 0x69, 0xCA&#125;,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure></div><p>从 <code>main</code> 函数来看，输入 -&gt; <code>random</code> 异或-&gt; <code>sub_140001380</code> 处理 -&gt;输出，这个序列是确定的，但是和 flag 似乎没有关系</p><p>但注意到在 <code>.rodata</code> 段下面有一个字符串：</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">magic&#123;75b1743a9200a5bc60c70b666eaad2911ddb2292eed4d3d2b7ebc7867b2aa5499949810031756dcdc1dc6500&#125;</span><br></pre></td></tr></table></figure></div><p>这个字符串比较奇怪，通过尝试可以知道：</p><p><code>75b1743a9200a5bc60c70b666eaad2911ddb2292eed4d3d2b7ebc7867b2aa5499949810031756dcdc1dc6500</code>通过逆<code>sub_140001380</code> 处理后得到结果</p><p>编写脚本</p><div class="code-container" data-rel="Python"><figure class="iseeu highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> pathlib <span class="keyword">import</span> Path</span><br><span class="line"></span><br><span class="line">data = Path(<span class="string">&quot;CxxIntro.exe&quot;</span>).read_bytes()</span><br><span class="line"></span><br><span class="line">flag_key = <span class="string">&#x27;moectf&#123;世界で一番幸せな女の子—ＣＨＴＨＯＬＬＹ—&#125;&#x27;</span>.encode(<span class="string">&#x27;utf-8&#x27;</span>)</span><br><span class="line"></span><br><span class="line">magic = <span class="built_in">bytes</span>.fromhex(</span><br><span class="line">    <span class="string">&#x27;75b1743a9200a5bc60c70b666eaad2911ddb2292eed4d3d2b7ebc7867b2aa5499949810031756dcdc1dc6500&#x27;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">period = <span class="built_in">bytes</span>((<span class="number">0x2a</span> + i * data[<span class="number">0</span>]) &amp; <span class="number">0xff</span> <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">0x41</span>))</span><br><span class="line">sbox = [(period[i % <span class="built_in">len</span>(period)] + flag_key[i % <span class="built_in">len</span>(flag_key)]) &amp; <span class="number">0xff</span> <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">256</span>)]</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">MT19937</span>:</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, seed</span>):</span><br><span class="line">        <span class="variable language_">self</span>.mt = [seed &amp; <span class="number">0xffffffff</span>]</span><br><span class="line">        <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">1</span>, <span class="number">624</span>):</span><br><span class="line">            x = <span class="variable language_">self</span>.mt[-<span class="number">1</span>]</span><br><span class="line">            <span class="variable language_">self</span>.mt.append((<span class="number">0x6c078965</span> * (x ^ (x &gt;&gt; <span class="number">30</span>)) + i) &amp; <span class="number">0xffffffff</span>)</span><br><span class="line">        <span class="variable language_">self</span>.index = <span class="number">624</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">rand</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="keyword">if</span> <span class="variable language_">self</span>.index &gt;= <span class="number">624</span>:</span><br><span class="line">            <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">624</span>):</span><br><span class="line">                y = (<span class="variable language_">self</span>.mt[i] &amp; <span class="number">0x80000000</span>) | (<span class="variable language_">self</span>.mt[(i + <span class="number">1</span>) % <span class="number">624</span>] &amp; <span class="number">0x7fffffff</span>)</span><br><span class="line">                <span class="variable language_">self</span>.mt[i] = <span class="variable language_">self</span>.mt[(i + <span class="number">397</span>) % <span class="number">624</span>] ^ (y &gt;&gt; <span class="number">1</span>)</span><br><span class="line">                <span class="keyword">if</span> y &amp; <span class="number">1</span>:</span><br><span class="line">                    <span class="variable language_">self</span>.mt[i] ^= <span class="number">0x9908b0df</span></span><br><span class="line">            <span class="variable language_">self</span>.index = <span class="number">0</span></span><br><span class="line">        y = <span class="variable language_">self</span>.mt[<span class="variable language_">self</span>.index]</span><br><span class="line">        <span class="variable language_">self</span>.index += <span class="number">1</span></span><br><span class="line">        y ^= y &gt;&gt; <span class="number">11</span></span><br><span class="line">        y ^= (y &lt;&lt; <span class="number">7</span>) &amp; <span class="number">0x9d2c5680</span></span><br><span class="line">        y ^= (y &lt;&lt; <span class="number">15</span>) &amp; <span class="number">0xefc60000</span></span><br><span class="line">        y ^= y &gt;&gt; <span class="number">18</span></span><br><span class="line">        <span class="keyword">return</span> y &amp; <span class="number">0xffffffff</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">crypt</span>(<span class="params">buf</span>):</span><br><span class="line">    s = sbox[:]</span><br><span class="line">    i = j = <span class="number">0</span></span><br><span class="line">    out = <span class="built_in">bytearray</span>()</span><br><span class="line">    <span class="keyword">for</span> b <span class="keyword">in</span> buf:</span><br><span class="line">        i = (i + <span class="number">1</span>) &amp; <span class="number">0xff</span></span><br><span class="line">        j = (j + s[i]) &amp; <span class="number">0xff</span></span><br><span class="line">        s[i], s[j] = s[j], s[i]</span><br><span class="line">        out.append(b ^ s[(s[i] + s[j]) &amp; <span class="number">0xff</span>])</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">bytes</span>(out)</span><br><span class="line"></span><br><span class="line">flag = crypt(magic)</span><br><span class="line"><span class="built_in">print</span>(flag.decode())</span><br><span class="line"></span><br><span class="line"><span class="comment"># 让原程序打印 flag，输入的是 middle 再异或 MT19937 低字节后的 hex</span></span><br><span class="line">mt = MT19937(<span class="number">0x44</span>)</span><br><span class="line">spell = <span class="built_in">bytes</span>(b ^ (mt.rand() &amp; <span class="number">0xff</span>) <span class="keyword">for</span> b <span class="keyword">in</span> magic)</span><br><span class="line"><span class="built_in">print</span>(spell.<span class="built_in">hex</span>())</span><br></pre></td></tr></table></figure></div><p>或者：</p><div class="code-container" data-rel="Python"><figure class="iseeu highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br></pre></td><td class="code"><pre><span class="line">magic = <span class="built_in">bytes</span>.fromhex(</span><br><span class="line">    <span class="string">&#x27;75b1743a9200a5bc60c70b666eaad2911ddb2292eed4d3d2b7ebc7867b2aa5499949810031756dcdc1dc6500&#x27;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">sbox = [ </span><br><span class="line">    <span class="number">0x97</span>, <span class="number">0xE6</span>, <span class="number">0x29</span>, <span class="number">0x74</span>, <span class="number">0xD2</span>, <span class="number">0x11</span>, <span class="number">0x73</span>, <span class="number">0x29</span>, <span class="number">0x4A</span>, <span class="number">0x75</span>, <span class="number">0x13</span>, <span class="number">0x0E</span>, <span class="number">0x52</span>, <span class="number">0xF6</span>, <span class="number">0xE1</span>, <span class="number">0x54</span>,</span><br><span class="line">    <span class="number">0xDE</span>, <span class="number">0xFF</span>, <span class="number">0x14</span>, <span class="number">0xC8</span>, <span class="number">0xC3</span>, <span class="number">0x25</span>, <span class="number">0xAD</span>, <span class="number">0xCE</span>, <span class="number">0x1A</span>, <span class="number">0x92</span>, <span class="number">0x7D</span>, <span class="number">0xE4</span>, <span class="number">0x79</span>, <span class="number">0x64</span>, <span class="number">0xDA</span>, <span class="number">0x62</span>,</span><br><span class="line">    <span class="number">0x6F</span>, <span class="number">0xCA</span>, <span class="number">0x47</span>, <span class="number">0x32</span>, <span class="number">0xAC</span>, <span class="number">0x30</span>, <span class="number">0x45</span>, <span class="number">0x75</span>, <span class="number">0x14</span>, <span class="number">0xFF</span>, <span class="number">0x60</span>, <span class="number">0x08</span>, <span class="number">0x22</span>, <span class="number">0x56</span>, <span class="number">0xEF</span>, <span class="number">0x09</span>,</span><br><span class="line">    <span class="number">0x42</span>, <span class="number">0xD6</span>, <span class="number">0xF0</span>, <span class="number">0x35</span>, <span class="number">0xBD</span>, <span class="number">0xD7</span>, <span class="number">0x10</span>, <span class="number">0xA4</span>, <span class="number">0xBE</span>, <span class="number">0xFE</span>, <span class="number">0x8B</span>, <span class="number">0xA5</span>, <span class="number">0xE2</span>, <span class="number">0x72</span>, <span class="number">0x8C</span>, <span class="number">0xC9</span>,</span><br><span class="line">    <span class="number">0x59</span>, <span class="number">0xE6</span>, <span class="number">0x30</span>, <span class="number">0xA6</span>, <span class="number">0x91</span>, <span class="number">0xF2</span>, <span class="number">0x28</span>, <span class="number">0x65</span>, <span class="number">0xB4</span>, <span class="number">0xF7</span>, <span class="number">0x42</span>, <span class="number">0xA0</span>, <span class="number">0xDF</span>, <span class="number">0x41</span>, <span class="number">0xF7</span>, <span class="number">0x18</span>,</span><br><span class="line">    <span class="number">0x43</span>, <span class="number">0xE1</span>, <span class="number">0xDC</span>, <span class="number">0x20</span>, <span class="number">0xC4</span>, <span class="number">0xAF</span>, <span class="number">0x22</span>, <span class="number">0xAC</span>, <span class="number">0xCD</span>, <span class="number">0xE2</span>, <span class="number">0x96</span>, <span class="number">0x91</span>, <span class="number">0xF3</span>, <span class="number">0x7B</span>, <span class="number">0x9C</span>, <span class="number">0xE8</span>,</span><br><span class="line">    <span class="number">0x60</span>, <span class="number">0x4B</span>, <span class="number">0xB2</span>, <span class="number">0x47</span>, <span class="number">0x32</span>, <span class="number">0xA8</span>, <span class="number">0x30</span>, <span class="number">0x3D</span>, <span class="number">0x98</span>, <span class="number">0x15</span>, <span class="number">0x00</span>, <span class="number">0x7A</span>, <span class="number">0xFE</span>, <span class="number">0x13</span>, <span class="number">0x43</span>, <span class="number">0xE2</span>,</span><br><span class="line">    <span class="number">0xCD</span>, <span class="number">0x2E</span>, <span class="number">0xD6</span>, <span class="number">0xF0</span>, <span class="number">0x24</span>, <span class="number">0xBD</span>, <span class="number">0xD7</span>, <span class="number">0x10</span>, <span class="number">0xA4</span>, <span class="number">0xBE</span>, <span class="number">0x03</span>, <span class="number">0x8B</span>, <span class="number">0xA5</span>, <span class="number">0xDE</span>, <span class="number">0x72</span>, <span class="number">0x8C</span>,</span><br><span class="line">    <span class="number">0xCC</span>, <span class="number">0x59</span>, <span class="number">0xE6</span>, <span class="number">0x23</span>, <span class="number">0xB3</span>, <span class="number">0xCD</span>, <span class="number">0x0A</span>, <span class="number">0x9A</span>, <span class="number">0xB4</span>, <span class="number">0xFE</span>, <span class="number">0x74</span>, <span class="number">0x5F</span>, <span class="number">0xC0</span>, <span class="number">0xF6</span>, <span class="number">0x33</span>, <span class="number">0x82</span>,</span><br><span class="line">    <span class="number">0xC5</span>, <span class="number">0x10</span>, <span class="number">0x6E</span>, <span class="number">0xAD</span>, <span class="number">0x0F</span>, <span class="number">0xC5</span>, <span class="number">0xE6</span>, <span class="number">0x11</span>, <span class="number">0xAF</span>, <span class="number">0xAA</span>, <span class="number">0xEE</span>, <span class="number">0x92</span>, <span class="number">0x7D</span>, <span class="number">0xF0</span>, <span class="number">0x7A</span>, <span class="number">0x9B</span>,</span><br><span class="line">    <span class="number">0xB0</span>, <span class="number">0x64</span>, <span class="number">0x5F</span>, <span class="number">0xC1</span>, <span class="number">0x49</span>, <span class="number">0x6A</span>, <span class="number">0xB6</span>, <span class="number">0x2E</span>, <span class="number">0x19</span>, <span class="number">0x80</span>, <span class="number">0x15</span>, <span class="number">0x00</span>, <span class="number">0x76</span>, <span class="number">0xFE</span>, <span class="number">0x0B</span>, <span class="number">0x66</span>,</span><br><span class="line">    <span class="number">0xE3</span>, <span class="number">0xCE</span>, <span class="number">0x48</span>, <span class="number">0xCC</span>, <span class="number">0xE1</span>, <span class="number">0x11</span>, <span class="number">0xB0</span>, <span class="number">0x9B</span>, <span class="number">0xFC</span>, <span class="number">0xA4</span>, <span class="number">0xBE</span>, <span class="number">0xF2</span>, <span class="number">0x8B</span>, <span class="number">0xA5</span>, <span class="number">0xDE</span>, <span class="number">0x72</span>,</span><br><span class="line">    <span class="number">0x8C</span>, <span class="number">0xD1</span>, <span class="number">0x59</span>, <span class="number">0xE6</span>, <span class="number">0x1F</span>, <span class="number">0xB3</span>, <span class="number">0xCD</span>, <span class="number">0x0D</span>, <span class="number">0x9A</span>, <span class="number">0xB4</span>, <span class="number">0xF1</span>, <span class="number">0x81</span>, <span class="number">0x9B</span>, <span class="number">0xD8</span>, <span class="number">0x68</span>, <span class="number">0x82</span>,</span><br><span class="line">    <span class="number">0xCC</span>, <span class="number">0x42</span>, <span class="number">0x2D</span>, <span class="number">0x8E</span>, <span class="number">0xC4</span>, <span class="number">0x01</span>, <span class="number">0x50</span>, <span class="number">0x93</span>, <span class="number">0xDE</span>, <span class="number">0x3C</span>, <span class="number">0x7B</span>, <span class="number">0xDD</span>, <span class="number">0x93</span>, <span class="number">0xB4</span>, <span class="number">0xDF</span>, <span class="number">0x7D</span>,</span><br><span class="line">    <span class="number">0x78</span>, <span class="number">0xBC</span>, <span class="number">0x60</span>, <span class="number">0x4B</span>, <span class="number">0xBE</span>, <span class="number">0x48</span>, <span class="number">0x69</span>, <span class="number">0x7E</span>, <span class="number">0x32</span>, <span class="number">0x2D</span>, <span class="number">0x8F</span>, <span class="number">0x17</span>, <span class="number">0x38</span>, <span class="number">0x84</span>, <span class="number">0xFC</span>, <span class="number">0xE7</span>,</span><br><span class="line">    <span class="number">0x4E</span>, <span class="number">0xE3</span>, <span class="number">0xCE</span>, <span class="number">0x44</span>, <span class="number">0xCC</span>, <span class="number">0xD9</span>, <span class="number">0x34</span>, <span class="number">0xB1</span>, <span class="number">0x9C</span>, <span class="number">0x16</span>, <span class="number">0x9A</span>, <span class="number">0xAF</span>, <span class="number">0xDF</span>, <span class="number">0x7E</span>, <span class="number">0x69</span>, <span class="number">0xCA</span></span><br><span class="line">        ]</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">MT19937</span>:</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, seed</span>):</span><br><span class="line">        <span class="variable language_">self</span>.mt = [seed &amp; <span class="number">0xffffffff</span>]</span><br><span class="line">        <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">1</span>, <span class="number">624</span>):</span><br><span class="line">            x = <span class="variable language_">self</span>.mt[-<span class="number">1</span>]</span><br><span class="line">            <span class="variable language_">self</span>.mt.append((<span class="number">0x6c078965</span> * (x ^ (x &gt;&gt; <span class="number">30</span>)) + i) &amp; <span class="number">0xffffffff</span>)</span><br><span class="line">        <span class="variable language_">self</span>.index = <span class="number">624</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">rand</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="keyword">if</span> <span class="variable language_">self</span>.index &gt;= <span class="number">624</span>:</span><br><span class="line">            <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">624</span>):</span><br><span class="line">                y = (<span class="variable language_">self</span>.mt[i] &amp; <span class="number">0x80000000</span>) | (<span class="variable language_">self</span>.mt[(i + <span class="number">1</span>) % <span class="number">624</span>] &amp; <span class="number">0x7fffffff</span>)</span><br><span class="line">                <span class="variable language_">self</span>.mt[i] = <span class="variable language_">self</span>.mt[(i + <span class="number">397</span>) % <span class="number">624</span>] ^ (y &gt;&gt; <span class="number">1</span>)</span><br><span class="line">                <span class="keyword">if</span> y &amp; <span class="number">1</span>:</span><br><span class="line">                    <span class="variable language_">self</span>.mt[i] ^= <span class="number">0x9908b0df</span></span><br><span class="line">            <span class="variable language_">self</span>.index = <span class="number">0</span></span><br><span class="line">        y = <span class="variable language_">self</span>.mt[<span class="variable language_">self</span>.index]</span><br><span class="line">        <span class="variable language_">self</span>.index += <span class="number">1</span></span><br><span class="line">        y ^= y &gt;&gt; <span class="number">11</span></span><br><span class="line">        y ^= (y &lt;&lt; <span class="number">7</span>) &amp; <span class="number">0x9d2c5680</span></span><br><span class="line">        y ^= (y &lt;&lt; <span class="number">15</span>) &amp; <span class="number">0xefc60000</span></span><br><span class="line">        y ^= y &gt;&gt; <span class="number">18</span></span><br><span class="line">        <span class="keyword">return</span> y &amp; <span class="number">0xffffffff</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">crypt</span>(<span class="params">buf</span>):</span><br><span class="line">    s = sbox[:]</span><br><span class="line">    i = j = <span class="number">0</span></span><br><span class="line">    out = <span class="built_in">bytearray</span>()</span><br><span class="line">    <span class="keyword">for</span> b <span class="keyword">in</span> buf:</span><br><span class="line">        i = (i + <span class="number">1</span>) &amp; <span class="number">0xff</span></span><br><span class="line">        j = (j + s[i]) &amp; <span class="number">0xff</span></span><br><span class="line">        s[i], s[j] = s[j], s[i]</span><br><span class="line">        out.append(b ^ s[(s[i] + s[j]) &amp; <span class="number">0xff</span>])</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">bytes</span>(out)</span><br><span class="line"></span><br><span class="line">flag = crypt(magic)</span><br><span class="line"><span class="built_in">print</span>(flag.decode())</span><br><span class="line"></span><br><span class="line"><span class="comment"># 让原程序打印 flag，输入的是 middle 再异或 MT19937 低字节后的 hex</span></span><br><span class="line">mt = MT19937(<span class="number">0x44</span>)</span><br><span class="line">spell = <span class="built_in">bytes</span>(b ^ (mt.rand() &amp; <span class="number">0xff</span>) <span class="keyword">for</span> b <span class="keyword">in</span> magic)</span><br><span class="line"><span class="built_in">print</span>(spell.<span class="built_in">hex</span>())</span><br></pre></td></tr></table></figure></div><p>输出：</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">moectf&#123;CHTHOLLY_the_best__sukasuka_the_best&#125;</span><br><span class="line">4a43d39e58ec55207850df2918a6fe023248395a656f66933ee37187217e2e1cd943ddd75f397e2885e0f2b3</span><br></pre></td></tr></table></figure></div><p>flag 为：</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">moectf&#123;CHTHOLLY_the_best__sukasuka_the_best&#125;</span><br></pre></td></tr></table></figure></div><h2 id="attachments_495">Attachments_495</h2><p>神秘 <code>.lisp</code> 文件，居然还有 <code>.lisp</code> 玩的</p><p>最后给了注释：</p><div class="code-container" data-rel="Lisp"><figure class="iseeu highlight lisp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">;;;;flagis&quot;&amp;Dx16Y!x3xYDlShWbQ5hmzWf3EZly6h8UwD#d-1-&amp;#WlDHJaxM5qAzlPP&quot;</span></span><br></pre></td></tr></table></figure></div><p>大致浏览一下，发现这是一个简单的 base57 编码器</p><p>定义 <code>alphabet</code> 为<code>AB#DEd@f&amp;hi!klmnLMw3^5678N&#125;PF|HIxyz012JKYZab%Q&#123;SUVWX-pqrs</code></p><p><code>len</code> 为长度，即 57</p><p>定义函数 <code>divmod</code> 返回 <code>values</code>包括商和余数</p><p>定义加密函数 <code>encode</code>：字符串倒序转化为 256进制大整数，再转为 57 进制数，最后输出密文</p><p>编写解密函数</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">alphabet = &quot;AB#DEd@f&amp;hi!klmnLMw3^5678N&#125;PF|HIxyz012JKYZab%Q&#123;SUVWX-pqrs&quot;</span><br><span class="line">enc = &quot;&amp;Dx16Y!x3xYDlShWbQ5hmzWf3EZly6h8UwD#d-1-&amp;#WlDHJaxM5qAzlPP&quot;</span><br><span class="line"></span><br><span class="line">value = 0</span><br><span class="line">for ch in enc:</span><br><span class="line">    value = value * len(alphabet) + alphabet.index(ch)</span><br><span class="line"></span><br><span class="line">raw = value.to_bytes((value.bit_length() + 7) // 8, &quot;big&quot;)</span><br><span class="line">print(raw)</span><br></pre></td></tr></table></figure></div><p>不对！</p><p>查看官方仓库发现题出错了</p><p>实际上注释应该是</p><div class="code-container" data-rel="Lisp"><figure class="iseeu highlight lisp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">;;;;flagis&quot;6dsJ6lbHyhBXHfKnYdEddMU@0JKA^hlW0MMyx^3rpMM&#123;&#123;&amp;Q-lnLrZBbbKJy&quot;</span></span><br></pre></td></tr></table></figure></div><p>无敌了</p><p>flag</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">moectf&#123;woO0Oow_Y0u-ar3_th3_g0D_0f_LIIIISP!&#125;</span><br></pre></td></tr></table></figure></div><h2 id="attachments_512">Attachments_512</h2><p>查壳</p><figure><imgsrc="https://img.pagehost.cn/autoupload/4_V_ddXuVkoyJIUs82v1o1aMnouKReN3spbQz7x5YEI/20260710/zT09/810X345/1.png"alt="810X345/1.png" /><figcaption aria-hidden="true">810X345/1.png</figcaption></figure><p>UPX 壳，去掉</p><div class="code-container" data-rel="Bash"><figure class="iseeu highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># ./upx.exe -d Art.exe</span></span><br><span class="line">                       Ultimate Packer <span class="keyword">for</span> eXecutables</span><br><span class="line">                          Copyright (C) 1996 - 2026</span><br><span class="line">UPX 5.1.1       Markus Oberhumer, Laszlo Molnar &amp; John Reiser    Mar 5th 2026</span><br><span class="line"></span><br><span class="line">        File size         Ratio      Format      Name</span><br><span class="line">   --------------------   ------   -----------   -----------</span><br><span class="line">     18944 &lt;-     11264   59.46%    win64/pe     Art.exe</span><br><span class="line"></span><br><span class="line">Unpacked 1 file</span><br></pre></td></tr></table></figure></div><div class="code-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">scanf</span>(<span class="string">&quot;%s&quot;</span>, Str1);</span><br><span class="line"><span class="keyword">for</span> ( i = <span class="number">0</span>; i &lt;= <span class="number">27</span>; ++i )</span><br><span class="line">  v4[i] = Str1[i];</span><br><span class="line"><span class="keyword">for</span> ( i = <span class="number">1</span>; i &lt;= <span class="number">27</span>; ++i )</span><br><span class="line">  Str1[i - <span class="number">1</span>] ^= (Str1[i - <span class="number">1</span>] % <span class="number">17</span> + Str1[i]) ^ <span class="number">0x19</span>;</span><br><span class="line"><span class="keyword">if</span> ( !<span class="built_in">strcmp</span>(Str1, &amp;Str2) &amp;&amp; (<span class="type">unsigned</span> <span class="type">int</span>)sub_401550(v4) )</span><br><span class="line">  <span class="built_in">puts</span>(<span class="string">&quot;\nGood job!!! You know UPX and hash!!!&quot;</span>);</span><br></pre></td></tr></table></figure></div><p><code>Str1</code> 为输入，然后诸位运算得到结果</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Str1[i - 1] ^= (Str1[i - 1] % 17 + Str1[i]) ^ 0x19;</span><br></pre></td></tr></table></figure></div><p>那么</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Str1[i] = (Str1[i - 1] ^ 0x19) - (Str1[i - 1] % 17)</span><br></pre></td></tr></table></figure></div><div class="code-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br></pre></td><td class="code"><pre><span class="line">_BOOL8 __fastcall <span class="title function_">sub_401550</span><span class="params">(<span class="type">const</span> <span class="type">char</span> *a1)</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="type">size_t</span> v1; <span class="comment">// rax</span></span><br><span class="line">  _BYTE v3[<span class="number">112</span>]; <span class="comment">// [rsp+20h] [rbp-A0h] BYREF</span></span><br><span class="line">  _BYTE Buf2[<span class="number">44</span>]; <span class="comment">// [rsp+90h] [rbp-30h] BYREF</span></span><br><span class="line">  <span class="type">int</span> v5; <span class="comment">// [rsp+BCh] [rbp-4h]</span></span><br><span class="line"></span><br><span class="line">  v5 = <span class="number">1</span>;</span><br><span class="line">  sub_401B66(v3);</span><br><span class="line">  v1 = <span class="built_in">strlen</span>(a1);</span><br><span class="line">  sub_401BE0(v3, a1, v1);</span><br><span class="line">  sub_401C7D(v3, Buf2);</span><br><span class="line">  <span class="keyword">return</span> <span class="built_in">memcmp</span>(&amp;unk_404040, Buf2, <span class="number">0x20u</span>) == <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 初始化</span></span><br><span class="line">__int64 __fastcall <span class="title function_">sub_401B66</span><span class="params">(__int64 a1)</span></span><br><span class="line">&#123;</span><br><span class="line">  *(_DWORD *)(a1 + <span class="number">64</span>) = <span class="number">0</span>;</span><br><span class="line">  *(_QWORD *)(a1 + <span class="number">72</span>) = <span class="number">0</span>;</span><br><span class="line">  *(_DWORD *)(a1 + <span class="number">80</span>) = <span class="number">1779033703</span>;</span><br><span class="line">  *(_DWORD *)(a1 + <span class="number">84</span>) = <span class="number">-1150833019</span>;</span><br><span class="line">  *(_DWORD *)(a1 + <span class="number">88</span>) = <span class="number">1013904242</span>;</span><br><span class="line">  *(_DWORD *)(a1 + <span class="number">92</span>) = <span class="number">-1521486534</span>;</span><br><span class="line">  *(_DWORD *)(a1 + <span class="number">96</span>) = <span class="number">1359893119</span>;</span><br><span class="line">  *(_DWORD *)(a1 + <span class="number">100</span>) = <span class="number">-1694144372</span>;</span><br><span class="line">  *(_DWORD *)(a1 + <span class="number">104</span>) = <span class="number">528734635</span>;</span><br><span class="line">  *(_DWORD *)(a1 + <span class="number">108</span>) = <span class="number">1541459225</span>;</span><br><span class="line">  <span class="keyword">return</span> a1;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">__int64 __fastcall <span class="title function_">sub_401BE0</span><span class="params">(__int64 init_hash, __int64 str, <span class="type">unsigned</span> __int64 len)</span></span><br><span class="line">&#123;</span><br><span class="line">  __int64 result; <span class="comment">// rax</span></span><br><span class="line">  <span class="type">unsigned</span> <span class="type">int</span> i; <span class="comment">// [rsp+2Ch] [rbp-4h]</span></span><br><span class="line"></span><br><span class="line">  <span class="keyword">for</span> ( i = <span class="number">0</span>; ; ++i )</span><br><span class="line">  &#123;</span><br><span class="line">    result = i;</span><br><span class="line">    <span class="keyword">if</span> ( len &lt;= i )</span><br><span class="line">      <span class="keyword">break</span>;</span><br><span class="line">    *(_BYTE *)(init_hash + (<span class="type">unsigned</span> <span class="type">int</span>)(*(_DWORD *)(init_hash + <span class="number">64</span>))++) = *(_BYTE *)(str + i);</span><br><span class="line">    <span class="keyword">if</span> ( *(_DWORD *)(init_hash + <span class="number">64</span>) == <span class="number">64</span> )</span><br><span class="line">    &#123;</span><br><span class="line">      sub_401710(init_hash, init_hash);</span><br><span class="line">      *(_QWORD *)(init_hash + <span class="number">72</span>) += <span class="number">512LL</span>;</span><br><span class="line">      *(_DWORD *)(init_hash + <span class="number">64</span>) = <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> result;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">_BYTE *__fastcall <span class="title function_">sub_401C7D</span><span class="params">(__int64 a1, __int64 a2)</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="type">unsigned</span> <span class="type">int</span> v2; <span class="comment">// eax</span></span><br><span class="line">  <span class="type">unsigned</span> <span class="type">int</span> v3; <span class="comment">// eax</span></span><br><span class="line">  _BYTE *result; <span class="comment">// rax</span></span><br><span class="line">  <span class="type">unsigned</span> <span class="type">int</span> v5; <span class="comment">// [rsp+2Ch] [rbp-4h]</span></span><br><span class="line">  <span class="type">unsigned</span> <span class="type">int</span> v6; <span class="comment">// [rsp+2Ch] [rbp-4h]</span></span><br><span class="line">  <span class="type">unsigned</span> <span class="type">int</span> v7; <span class="comment">// [rsp+2Ch] [rbp-4h]</span></span><br><span class="line">  <span class="type">unsigned</span> <span class="type">int</span> i; <span class="comment">// [rsp+2Ch] [rbp-4h]</span></span><br><span class="line"></span><br><span class="line">  v5 = *(_DWORD *)(a1 + <span class="number">64</span>);</span><br><span class="line">  <span class="keyword">if</span> ( v5 &gt; <span class="number">0x37</span> )</span><br><span class="line">  &#123;</span><br><span class="line">    v7 = v5 + <span class="number">1</span>;</span><br><span class="line">    *(_BYTE *)(a1 + *(<span class="type">unsigned</span> <span class="type">int</span> *)(a1 + <span class="number">64</span>)) = <span class="number">0x80</span>;</span><br><span class="line">    <span class="keyword">while</span> ( v7 &lt;= <span class="number">0x3F</span> )</span><br><span class="line">    &#123;</span><br><span class="line">      v3 = v7++;</span><br><span class="line">      *(_BYTE *)(a1 + v3) = <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    sub_401710(a1, a1);</span><br><span class="line">    <span class="built_in">memset</span>((<span class="type">void</span> *)a1, <span class="number">0</span>, <span class="number">0x38u</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">else</span></span><br><span class="line">  &#123;</span><br><span class="line">    v6 = v5 + <span class="number">1</span>;</span><br><span class="line">    *(_BYTE *)(a1 + *(<span class="type">unsigned</span> <span class="type">int</span> *)(a1 + <span class="number">64</span>)) = <span class="number">0x80</span>;</span><br><span class="line">    <span class="keyword">while</span> ( v6 &lt;= <span class="number">0x37</span> )</span><br><span class="line">    &#123;</span><br><span class="line">      v2 = v6++;</span><br><span class="line">      *(_BYTE *)(a1 + v2) = <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  *(_QWORD *)(a1 + <span class="number">72</span>) += (<span class="type">unsigned</span> <span class="type">int</span>)(<span class="number">8</span> * *(_DWORD *)(a1 + <span class="number">64</span>));</span><br><span class="line">  *(_BYTE *)(a1 + <span class="number">63</span>) = *(_QWORD *)(a1 + <span class="number">72</span>);</span><br><span class="line">  *(_BYTE *)(a1 + <span class="number">62</span>) = BYTE1(*(_QWORD *)(a1 + <span class="number">72</span>));</span><br><span class="line">  *(_BYTE *)(a1 + <span class="number">61</span>) = BYTE2(*(_QWORD *)(a1 + <span class="number">72</span>));</span><br><span class="line">  *(_BYTE *)(a1 + <span class="number">60</span>) = BYTE3(*(_QWORD *)(a1 + <span class="number">72</span>));</span><br><span class="line">  *(_BYTE *)(a1 + <span class="number">59</span>) = BYTE4(*(_QWORD *)(a1 + <span class="number">72</span>));</span><br><span class="line">  *(_BYTE *)(a1 + <span class="number">58</span>) = (<span class="type">unsigned</span> __int16)WORD2(*(_QWORD *)(a1 + <span class="number">72</span>)) &gt;&gt; <span class="number">8</span>;</span><br><span class="line">  *(_BYTE *)(a1 + <span class="number">57</span>) = BYTE6(*(_QWORD *)(a1 + <span class="number">72</span>));</span><br><span class="line">  *(_BYTE *)(a1 + <span class="number">56</span>) = HIBYTE(*(_QWORD *)(a1 + <span class="number">72</span>));</span><br><span class="line">  result = (_BYTE *)sub_401710(a1, a1);</span><br><span class="line">  <span class="keyword">for</span> ( i = <span class="number">0</span>; i &lt;= <span class="number">3</span>; ++i )</span><br><span class="line">  &#123;</span><br><span class="line">    *(_BYTE *)(a2 + i) = *(_DWORD *)(a1 + <span class="number">80</span>) &gt;&gt; (<span class="number">-8</span> * i + <span class="number">24</span>);</span><br><span class="line">    *(_BYTE *)(i + <span class="number">4</span> + a2) = *(_DWORD *)(a1 + <span class="number">84</span>) &gt;&gt; (<span class="number">-8</span> * i + <span class="number">24</span>);</span><br><span class="line">    *(_BYTE *)(i + <span class="number">8</span> + a2) = *(_DWORD *)(a1 + <span class="number">88</span>) &gt;&gt; (<span class="number">-8</span> * i + <span class="number">24</span>);</span><br><span class="line">    *(_BYTE *)(i + <span class="number">12</span> + a2) = *(_DWORD *)(a1 + <span class="number">92</span>) &gt;&gt; (<span class="number">-8</span> * i + <span class="number">24</span>);</span><br><span class="line">    *(_BYTE *)(i + <span class="number">16</span> + a2) = *(_DWORD *)(a1 + <span class="number">96</span>) &gt;&gt; (<span class="number">-8</span> * i + <span class="number">24</span>);</span><br><span class="line">    *(_BYTE *)(i + <span class="number">20</span> + a2) = *(_DWORD *)(a1 + <span class="number">100</span>) &gt;&gt; (<span class="number">-8</span> * i + <span class="number">24</span>);</span><br><span class="line">    *(_BYTE *)(i + <span class="number">24</span> + a2) = *(_DWORD *)(a1 + <span class="number">104</span>) &gt;&gt; (<span class="number">-8</span> * i + <span class="number">24</span>);</span><br><span class="line">    result = (_BYTE *)(i + <span class="number">28</span> + a2);</span><br><span class="line">    *result = *(_DWORD *)(a1 + <span class="number">108</span>) &gt;&gt; (<span class="number">-8</span> * i + <span class="number">24</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> result;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><p>这个为计算 SHA-256 哈希</p><p>具体过程为：<code>sub_401B66</code> 初始化，<code>sub_401BE0</code>处理字符串每 64 个就执行 <code>sub_401710</code>压缩，具体压缩过程略过，最后调用 <code>sub_401C7D</code></p><p>和最终储存的 <code>&amp;unk_404040</code> 比较</p><p>储存为</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">40f53ff2b934da894ed3ff1f6e76c68a30ed92bcf764e839d5b31196d0899287</span><br></pre></td></tr></table></figure></div><p>那这个主函数主要是进行了一个逐字节的异或运算，然后计算 SHA-256哈希，分别和预设值比较，反过来只需要通过异或值反推答案再计算 SHA-256查看是否正确即可</p><div class="code-container" data-rel="Python"><figure class="iseeu highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> hashlib</span><br><span class="line"></span><br><span class="line">target = <span class="built_in">bytes</span>.fromhex(<span class="string">&quot;02180ff8190427d8eb0035484d2a456b592e4301185c09090909b57d&quot;</span>)</span><br><span class="line">sha = <span class="built_in">bytes</span>.fromhex(<span class="string">&quot;40f53ff2b934da894ed3ff1f6e76c68a30ed92bcf764e839d5b31196d0899287&quot;</span>)</span><br><span class="line"></span><br><span class="line">cands = [<span class="built_in">bytearray</span>(target)]</span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">27</span>, <span class="number">0</span>, -<span class="number">1</span>):</span><br><span class="line">    new = []</span><br><span class="line">    <span class="keyword">for</span> buf <span class="keyword">in</span> cands:</span><br><span class="line">        nxt = buf[i]</span><br><span class="line">        want = buf[i - <span class="number">1</span>]</span><br><span class="line">        <span class="keyword">for</span> x <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">256</span>):</span><br><span class="line">            <span class="keyword">if</span> (x ^ ((nxt + (x % <span class="number">0x11</span>)) &amp; <span class="number">0xff</span>) ^ <span class="number">0x19</span>) == want:</span><br><span class="line">                nb = <span class="built_in">bytearray</span>(buf)</span><br><span class="line">                nb[i - <span class="number">1</span>] = x</span><br><span class="line">                new.append(nb)</span><br><span class="line">    cands = new</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> buf <span class="keyword">in</span> cands:</span><br><span class="line">    flag = <span class="built_in">bytes</span>(buf)</span><br><span class="line">    <span class="keyword">if</span> hashlib.sha256(flag).digest() == sha:</span><br><span class="line">        <span class="built_in">print</span>(flag.decode())</span><br></pre></td></tr></table></figure></div><p>得到 flag</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">moectf&#123;Art_i5_b14s7ing!!!!!&#125;</span><br></pre></td></tr></table></figure></div><h2 id="attachments_511">Attachments_511</h2><p>apk 文件，解压看看，存在 <code>classes*.dex</code>，这是 Android 的Dalvik 字节码文件，使用 <code>jadx</code> 反编译工具可以将其反编译为Java 源代码。</p><p>直接搜索发现存在 flag</p><figure><imgsrc="https://img.pagehost.cn/autoupload/4_V_ddXuVkoyJIUs82v1o1aMnouKReN3spbQz7x5YEI/20260710/K4fL/1551X737/2.png"alt="1551X737/2.png" /><figcaption aria-hidden="true">1551X737/2.png</figcaption></figure><p>flag 为</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">moectf&#123;Andr01d_1s_so00oo_e@sy_t0_cr4ck!!!&#125;</span><br></pre></td></tr></table></figure></div><h2 id="attachments_513">Attachments_513</h2><p>自定义 base64</p><div class="code-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">strcpy</span>(base64, <span class="string">&quot;1wX/yRrA4RfR2wj72Qv52x3L5qa=&quot;</span>);</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">&quot;Welcome to moectf,plz input your flag!\n&quot;</span>);</span><br><span class="line">gets_0(inp);</span><br><span class="line">base64_decode(base64, de64);</span><br><span class="line"><span class="keyword">if</span> ( !<span class="built_in">strcmp</span>(de64, inp) )</span><br><span class="line">  <span class="built_in">printf</span>(<span class="string">&quot;great!&quot;</span>);</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">  <span class="built_in">printf</span>(<span class="string">&quot;wrong!&quot;</span>);</span><br><span class="line">gets_0(a);</span><br></pre></td></tr></table></figure></div><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">.rdata:0000000140009000 aAbcdefghijklmn db &#x27;abcdefghijklmnopqrstuvwxyz0123456789+/ABCDEFGHIJKLMNOPQRSTUVWXYZ&#x27;,0</span><br></pre></td></tr></table></figure></div><p>编写脚本</p><div class="code-container" data-rel="Python"><figure class="iseeu highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> base64</span><br><span class="line"></span><br><span class="line">custom = <span class="string">&quot;abcdefghijklmnopqrstuvwxyz0123456789+/ABCDEFGHIJKLMNOPQRSTUVWXYZ&quot;</span></span><br><span class="line">standard = <span class="string">&quot;ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/&quot;</span></span><br><span class="line">cipher = <span class="string">&quot;1wX/yRrA4RfR2wj72Qv52x3L5qa=&quot;</span></span><br><span class="line"></span><br><span class="line">translated = cipher.translate(<span class="built_in">str</span>.maketrans(custom, standard))</span><br><span class="line"><span class="built_in">print</span>(base64.b64decode(translated).rstrip(<span class="string">b&quot;\x00&quot;</span>).decode())</span><br></pre></td></tr></table></figure></div><p>得到 flag</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">moectf&#123;qwqbase_qwq&#125;</span><br></pre></td></tr></table></figure></div><h2 id="attachments_515">Attachments_515</h2><p>和上面那题 hash 差不多，不过这个是 SHA-1，还是自定义的</p><div class="code-container" data-rel="Python"><figure class="iseeu highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> pathlib <span class="keyword">import</span> Path</span><br><span class="line"><span class="keyword">import</span> struct</span><br><span class="line"></span><br><span class="line">MASK = <span class="number">0xffffffff</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">rol</span>(<span class="params">x, n</span>):</span><br><span class="line">    <span class="keyword">return</span> ((x &lt;&lt; n) &amp; MASK) | (x &gt;&gt; (<span class="number">32</span> - n))</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">broken_digest</span>(<span class="params">msg</span>):</span><br><span class="line">    h = [<span class="number">0x39063906</span>, <span class="number">0xefcda233</span>, <span class="number">0x74c74c74</span>, <span class="number">0x55555555</span>, <span class="number">0xd33b4700</span>]</span><br><span class="line">    k = [<span class="number">0xb1acb1dd</span>, <span class="number">0x23333333</span>, <span class="number">0x66666666</span>, <span class="number">0xca6272d6</span>]</span><br><span class="line">    data = <span class="built_in">bytearray</span>(msg) + <span class="string">b&quot;\x80&quot;</span></span><br><span class="line">    <span class="keyword">while</span> <span class="built_in">len</span>(data) % <span class="number">64</span> != <span class="number">56</span>:</span><br><span class="line">        data.append(<span class="number">0</span>)</span><br><span class="line">    data += (<span class="built_in">len</span>(msg) * <span class="number">8</span>).to_bytes(<span class="number">8</span>, <span class="string">&quot;big&quot;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> off <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">0</span>, <span class="built_in">len</span>(data), <span class="number">64</span>):</span><br><span class="line">        block = data[off:off + <span class="number">64</span>]</span><br><span class="line">        w = [<span class="built_in">int</span>.from_bytes(block[i:i + <span class="number">4</span>], <span class="string">&quot;big&quot;</span>) <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">0</span>, <span class="number">64</span>, <span class="number">4</span>)]</span><br><span class="line">        <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">16</span>, <span class="number">80</span>):</span><br><span class="line">            w.append(rol(w[i - <span class="number">3</span>] ^ w[i - <span class="number">8</span>] ^ w[i - <span class="number">14</span>] ^ w[i - <span class="number">16</span>], <span class="number">2</span>))</span><br><span class="line"></span><br><span class="line">        a, b, c, d, e = h</span><br><span class="line">        <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">80</span>):</span><br><span class="line">            <span class="keyword">if</span> i &lt; <span class="number">20</span>:</span><br><span class="line">                f, kk = (b &amp; c) ^ ((~b) &amp; d), k[<span class="number">0</span>]</span><br><span class="line">            <span class="keyword">elif</span> i &lt; <span class="number">40</span>:</span><br><span class="line">                f, kk = b ^ c ^ d, k[<span class="number">1</span>]</span><br><span class="line">            <span class="keyword">elif</span> i &lt; <span class="number">60</span>:</span><br><span class="line">                f, kk = (b &amp; c) ^ (b &amp; d) ^ (c &amp; d), k[<span class="number">2</span>]</span><br><span class="line">            <span class="keyword">else</span>:</span><br><span class="line">                f, kk = b ^ c ^ d, k[<span class="number">3</span>]</span><br><span class="line">            a, b, c, d, e = (f + e + rol(a, <span class="number">5</span>) + kk + w[i]) &amp; MASK, a, rol(b, <span class="number">30</span>), c, d</span><br><span class="line">        h = [(x + y) &amp; MASK <span class="keyword">for</span> x, y <span class="keyword">in</span> <span class="built_in">zip</span>(h, [a, b, c, d, e])]</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="string">b&quot;&quot;</span>.join(x.to_bytes(<span class="number">4</span>, <span class="string">&quot;big&quot;</span>) <span class="keyword">for</span> x <span class="keyword">in</span> h)</span><br><span class="line"></span><br><span class="line">data = Path(__file__).with_name(<span class="string">&quot;Broken_hash.exe&quot;</span>).read_bytes()</span><br><span class="line">targets = [struct.unpack_from(<span class="string">&quot;&lt;I&quot;</span>, data, <span class="number">0x3400</span> + <span class="number">4</span> * i)[<span class="number">0</span>] <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">0x58</span>)]</span><br><span class="line">charset = <span class="string">&quot;&quot;</span>.join(<span class="built_in">chr</span>(i) <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">0x21</span>, <span class="number">0x7f</span>))</span><br><span class="line"></span><br><span class="line">flag = <span class="string">&quot;&quot;</span></span><br><span class="line"><span class="keyword">for</span> target <span class="keyword">in</span> targets:</span><br><span class="line">    hits = [</span><br><span class="line">        ch <span class="keyword">for</span> ch <span class="keyword">in</span> charset</span><br><span class="line">        <span class="keyword">if</span> <span class="built_in">int</span>.from_bytes(broken_digest(ch.encode())[:<span class="number">4</span>], <span class="string">&quot;little&quot;</span>) == target</span><br><span class="line">    ]</span><br><span class="line">    <span class="keyword">assert</span> <span class="built_in">len</span>(hits) == <span class="number">1</span></span><br><span class="line">    flag += hits[<span class="number">0</span>]</span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(flag)</span><br><span class="line"></span><br></pre></td></tr></table></figure></div><p>得到 flag</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">moectf&#123;F1nd_th3_SEH_7hen_B1a5t_My_Fla9_and_Y0u_Can_Get_A_Cup_Of_Milk_Tea_From_YunZh1Jun&#125;</span><br></pre></td></tr></table></figure></div><h2 id="attachments_518">Attachments_518</h2><p>NET 6，反编译得到：</p><div class="code-container" data-rel="C#"><figure class="iseeu highlight c#"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">internal</span> <span class="keyword">class</span> <span class="title">D_flate</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="comment">// Token: 0x06000001 RID: 1 RVA: 0x00002050 File Offset: 0x00000250</span></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">Main</span>()</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">int</span> f = <span class="number">0</span>;</span><br><span class="line"><span class="built_in">int</span>[] flag = <span class="keyword">new</span> <span class="built_in">int</span>[]</span><br><span class="line">&#123;</span><br><span class="line"><span class="number">109</span>, <span class="number">111</span>, <span class="number">101</span>, <span class="number">99</span>, <span class="number">116</span>, <span class="number">102</span>, <span class="number">123</span>, <span class="number">68</span>, <span class="number">95</span>, <span class="number">102</span>,</span><br><span class="line"><span class="number">108</span>, <span class="number">97</span>, <span class="number">116</span>, <span class="number">101</span>, <span class="number">95</span>, <span class="number">105</span>, <span class="number">115</span>, <span class="number">95</span>, <span class="number">67</span>, <span class="number">95</span>,</span><br><span class="line"><span class="number">115</span>, <span class="number">104</span>, <span class="number">97</span>, <span class="number">114</span>, <span class="number">112</span>, <span class="number">33</span>, <span class="number">125</span></span><br><span class="line">&#125;;</span><br><span class="line">Console.WriteLine(<span class="string">&quot;In music theory, there is a note that has the same pitch as D flat.&quot;</span>);</span><br><span class="line">Console.WriteLine(<span class="string">&quot;Do you know it?\nNow plz input your flag:&quot;</span>);</span><br><span class="line"><span class="built_in">string</span> input = Console.ReadLine();</span><br><span class="line"><span class="built_in">byte</span>[] byteArray = Encoding.ASCII.GetBytes(input);</span><br><span class="line"><span class="keyword">for</span> (<span class="built_in">int</span> i = <span class="number">0</span>; i &lt; input.Length; i++)</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">if</span> (flag[i] == (<span class="built_in">int</span>)byteArray[i])</span><br><span class="line">&#123;</span><br><span class="line">f++;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> (f == flag.Length)</span><br><span class="line">&#123;</span><br><span class="line">Console.WriteLine(<span class="string">&quot;TTTTTQQQQQQLLLLLLL!!! This is your flag!&quot;</span>);</span><br><span class="line"><span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">Console.WriteLine(<span class="string">&quot;QwQ, plz try again.&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><p>这个 ASCII 储存 flag，编写脚本</p><div class="code-container" data-rel="Python"><figure class="iseeu highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">arr = [</span><br><span class="line">    <span class="number">109</span>, <span class="number">111</span>, <span class="number">101</span>, <span class="number">99</span>, <span class="number">116</span>, <span class="number">102</span>, <span class="number">123</span>, <span class="number">68</span>, <span class="number">95</span>, <span class="number">102</span>,</span><br><span class="line">    <span class="number">108</span>, <span class="number">97</span>, <span class="number">116</span>, <span class="number">101</span>, <span class="number">95</span>, <span class="number">105</span>, <span class="number">115</span>, <span class="number">95</span>, <span class="number">67</span>, <span class="number">95</span>,</span><br><span class="line">    <span class="number">115</span>, <span class="number">104</span>, <span class="number">97</span>, <span class="number">114</span>, <span class="number">112</span>, <span class="number">33</span>, <span class="number">125</span>,</span><br><span class="line">]</span><br><span class="line"><span class="built_in">print</span>(<span class="string">&quot;&quot;</span>.join(<span class="built_in">map</span>(<span class="built_in">chr</span>, arr)))</span><br></pre></td></tr></table></figure></div><p>得到 flag</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">moectf&#123;D_flate_is_C_sharp!&#125;</span><br></pre></td></tr></table></figure></div>]]></content>
    
    
    <summary type="html">CTF题记</summary>
    
    
    
    <category term="reverse" scheme="https://exdoubled.github.io/categories/reverse/"/>
    
    
    <category term="reverse" scheme="https://exdoubled.github.io/tags/reverse/"/>
    
  </entry>
  
  <entry>
    <title>代数结构与数理逻辑学习笔记8</title>
    <link href="https://exdoubled.github.io/lssx/ls18/"/>
    <id>https://exdoubled.github.io/lssx/ls18/</id>
    <published>2026-06-13T16:40:00.000Z</published>
    <updated>2026-06-14T15:43:46.687Z</updated>
    
    <content type="html"><![CDATA[<h2 id="定义-18.1-带等词的谓词演算">定义 18.1 带等词的谓词演算</h2><p>设谓词演算 <span class="math inline">\(K\)</span>的语言中有一个特殊的二元谓词符号 <spanclass="math inline">\(R_1^2\)</span>，把它写成 <spanclass="math display">\[\approx\]</span>并读作“等词”。在带等词的谓词演算中，除原来的逻辑公理和推理规则外，再加入以下等词公理</p><h3 id="定义-18.1.1-等词公理">定义 18.1.1 等词公理</h3><p>等词公理分为三类。</p><p>第一类是自反性： <span class="math display">\[\text{(E1)}\qquad t\approx t\]</span></p><p>第二类是函数符号中的替换性。若 <spanclass="math inline">\(f_i^n\)</span> 是 <spanclass="math inline">\(n\)</span> 元函数符号，则 <spanclass="math display">\[\text{(E2)}\qquadt_k\approx u\tof_i^n(t_1,\dots,t_k,\dots,t_n)\approxf_i^n(t_1,\dots,u,\dots,t_n)\]</span></p><p>第三类是谓词符号中的替换性。若 <spanclass="math inline">\(R_i^n\)</span> 是 <spanclass="math inline">\(n\)</span> 元谓词符号，则 <spanclass="math display">\[\text{(E3)}\qquadt_k\approx u\to\bigl(R_i^n(t_1,\dots,t_k,\dots,t_n)\toR_i^n(t_1,\dots,u,\dots,t_n)\bigr)\]</span></p><p>其中 <span class="math inline">\(t,t_1,\dots,t_n,u\)</span>都是任意项，<span class="math inline">\(k=1,\dots,n\)</span></p><p>这三类公理共同表达的是：相同的对象可以在函数项和谓词公式中互相替换。</p><p>注意，等词公理本身一般不是普通谓词逻辑的有效式。若解释域中把 <spanclass="math inline">\(\approx\)</span>解释成一个任意二元关系，它不一定满足自反性和替换性。只有当 <spanclass="math inline">\(\approx\)</span>被解释为真正的相等关系时，这些等词公理才都为真</p><h3 id="命题-18.1.2-等词解释为相等时等词公理为真">命题 18.1.2等词解释为相等时等词公理为真</h3><p>设解释域 <span class="math inline">\(M\)</span> 中 <spanclass="math inline">\(\approx\)</span> 被解释为真正的相等关系 <spanclass="math inline">\(=\)</span>，则 <spanclass="math inline">\(M\)</span> 是等词公理集 <spanclass="math inline">\(E\)</span> 的模型</p><details class="gray" data-header-exclude>    <summary><i class="fa-solid fa-chevron-right"></i>证明： 点击查看证明 </summary>    <div class='content markdown-body'>      <p>任取赋值 <span class="math inline">\(\varphi\)</span></p><p>对 (E1)，任意项 <span class="math inline">\(t\)</span> 的解释都是<span class="math inline">\(M\)</span> 中的一个对象，当然有 <spanclass="math display">\[\varphi(t)=\varphi(t)\]</span> 所以 <span class="math display">\[|t\approx t|(\varphi)=1\]</span></p><p>对 (E2)，若 <span class="math display">\[|t_k\approx u|(\varphi)=1\]</span> 则 <span class="math display">\[\varphi(t_k)=\varphi(u)\]</span> 函数解释保持输入相等时输出相等，于是 <spanclass="math display">\[\varphi\bigl(f_i^n(t_1,\dots,t_k,\dots,t_n)\bigr)=\varphi\bigl(f_i^n(t_1,\dots,u,\dots,t_n)\bigr)\]</span> 所以 (E2) 在 <span class="math inline">\(M\)</span>中为真。</p><p>对 (E3)，若 <span class="math inline">\(t_k\)</span> 与 <spanclass="math inline">\(u\)</span> 的解释相等，并且 <spanclass="math display">\[R_i^n(t_1,\dots,t_k,\dots,t_n)\]</span> 为真，那么把第 <span class="math inline">\(k\)</span>个解释对象换成相等对象后，元组不变，故 <span class="math display">\[R_i^n(t_1,\dots,u,\dots,t_n)\]</span> 仍为真。</p><p>因此所有等词公理在把 <span class="math inline">\(\approx\)</span>解释为真正相等关系的解释域中都为真</p>    </div>  </details><h3 id="命题-18.1.3-等词推出对称性和传递性">命题 18.1.3等词推出对称性和传递性</h3><p>在等词公理集 <span class="math inline">\(E\)</span> 中，对任意项<span class="math inline">\(t,u,v\)</span>，有： <spanclass="math display">\[E\vdash t\approx u\to u\approx t\]</span> <span class="math display">\[E\vdash t\approx u\to (u\approx v\to t\approx v)\]</span></p><p>也就是说，虽然等词公理只显式列出自反性和替换性，但对称性、传递性可以推出</p><details class="gray" data-header-exclude>    <summary><i class="fa-solid fa-chevron-right"></i>证明： 点击查看证明 </summary>    <div class='content markdown-body'>      <p>先证明对称性。</p><p>由 (E3) 用公式 <span class="math inline">\(x\approx t\)</span>作替换性质，可得 <span class="math display">\[t\approx u\to (t\approx t\to u\approx t)\]</span> 又由 (E1) 有 <span class="math display">\[t\approx t\]</span> 所以用 MP 得 <span class="math display">\[t\approx u\to u\approx t\]</span></p><p>再证明传递性</p><p>已知刚证明的对称性： <span class="math display">\[t\approx u\to u\approx t\]</span> 由 (E3) 对公式 <span class="math inline">\(u\approx v\)</span>替换，可得 <span class="math display">\[u\approx t\to (u\approx v\to t\approx v)\]</span> 把两式用假言三段论连接，即得 <span class="math display">\[t\approx u\to (u\approx v\to t\approx v)\]</span></p>    </div>  </details><h3 id="命题-18.1.4-等项替换">命题 18.1.4 等项替换</h3><p>若 <span class="math inline">\(t(u)\)</span> 是含有项 <spanclass="math inline">\(u\)</span> 的项，<spanclass="math inline">\(t(v)\)</span> 是把其中某一处 <spanclass="math inline">\(u\)</span> 替换为 <spanclass="math inline">\(v\)</span> 后得到的项，则 <spanclass="math display">\[E\vdash u\approx v\to t(u)\approx t(v)\]</span></p><p>若 <span class="math inline">\(p(u)\)</span> 是含有项 <spanclass="math inline">\(u\)</span> 的公式，<spanclass="math inline">\(p(v)\)</span> 是把其中某一处自由出现的 <spanclass="math inline">\(u\)</span> 替换为 <spanclass="math inline">\(v\)</span>后得到的公式，并且替换不导致变元受约束，则 <span class="math display">\[E\vdash u\approx v\to (p(u)\to p(v))\]</span></p><details class="gray" data-header-exclude>    <summary><i class="fa-solid fa-chevron-right"></i>证明： 点击查看证明 </summary>    <div class='content markdown-body'>      <p>第一式对项 <span class="math inline">\(t(u)\)</span>的构造层次归纳</p><p>若 <span class="math inline">\(t(u)\)</span> 本身就是 <spanclass="math inline">\(u\)</span>，则结论是 <span class="math display">\[u\approx v\to v\approx v\]</span> 或 <span class="math display">\[u\approx v\to u\approx v\]</span> 这些由等词公理和命题逻辑立即得到</p><p>若 <span class="math display">\[t(u)=f(t_1,\dots,t_k(u),\dots,t_n)\]</span> 则归纳假设给出 <span class="math display">\[u\approx v\to t_k(u)\approx t_k(v)\]</span> 再由 (E2) 得 <span class="math display">\[t_k(u)\approx t_k(v)\tof(t_1,\dots,t_k(u),\dots,t_n)\approxf(t_1,\dots,t_k(v),\dots,t_n)\]</span> 合并即得项替换</p><p>第二式对公式 <span class="math inline">\(p(u)\)</span> 的层次归纳</p><p>原子公式情形由 (E3)和第一部分的项替换得到。否定、蕴涵等联结词情形由命题逻辑规则处理。量词情形要注意：只有当被替换项中的自由变元不会被量词捕获时，才能把替换推进到量词内部</p><p>所以等词系统不仅能处理 <span class="math inline">\(u=v\)</span>的基本性质，也能处理“相等对象在任意上下文中可以替换”的形式化规则</p>    </div>  </details><p>这一节的作用在于给形式算术提供等式推理工具。后面证明 <spanclass="math display">\[\bar m+\bar n\approx \overline{m+n}\]</span> 或者从 <span class="math display">\[p(\bar n,t)\]</span> 推出 <span class="math display">\[t\approx \overline{f(n)}\]</span> 时，实际都在使用这些等词推理</p><h2 id="定义-18.2-形式算术-k_n">定义 18.2 形式算术 <spanclass="math inline">\(K_N\)</span></h2><p>把形式算术 <span class="math inline">\(K_N\)</span>看成一种特殊的带等词谓词演算。它的语言只有描述自然数算术所需的符号：</p><ul><li>个体常元：<span class="math inline">\(c_1\)</span>，以后写作 <spanclass="math inline">\(\bar 0\)</span></li><li>一元函数符号：<spanclass="math inline">\(f_1^1\)</span>，以后写作后继 <spanclass="math inline">\(&#39;\)</span></li><li>二元函数符号：<span class="math inline">\(f_1^2\)</span>，以后写作<span class="math inline">\(+\)</span></li><li>二元函数符号：<span class="math inline">\(f_2^2\)</span>，以后写作<span class="math inline">\(\times\)</span></li><li>等词：<span class="math inline">\(\approx\)</span></li></ul><p>因此项可以写成： <span class="math display">\[\bar 0,\quad x,\quad t&#39;,\quad t_1+t_2,\quad t_1\times t_2\]</span></p><p>在标准自然数解释域 <span class="math display">\[\mathbb N=\{0,1,2,\dots\}\]</span> 中，<span class="math inline">\(\bar 0\)</span> 解释为 <spanclass="math inline">\(0\)</span>，<spanclass="math inline">\(t&#39;\)</span> 解释为后继，<spanclass="math inline">\(+\)</span> 解释为加法，<spanclass="math inline">\(\times\)</span> 解释为乘法，<spanclass="math inline">\(\approx\)</span> 解释为相等</p><p>自然数 <span class="math inline">\(n\)</span> 对应的闭项称为 <spanclass="math inline">\(K_N\)</span>的<strong>数字</strong>或<strong>数码</strong>： <spanclass="math display">\[\bar n=\underbrace{\bar0&#39;&#39;\cdots&#39;}_{n\text{ 个 }&#39;}\]</span> 例如 <span class="math display">\[\bar1=\bar0&#39;,\qquad\bar2=\bar0&#39;&#39;,\qquad\bar3=\bar0&#39;&#39;&#39;\]</span></p><h3 id="定义-18.2.1-算术公理">定义 18.2.1 算术公理</h3><p><span class="math inline">\(K_N\)</span> 的算术公理记作公理集 <spanclass="math inline">\(N\)</span>。除等词公理外，核心算术公理模式如下</p><p>后继不为零： <span class="math display">\[\text{(N1)}\qquad t&#39;\not\approx \bar0\]</span></p><p>后继单射： <span class="math display">\[\text{(N2)}\qquad t_1&#39;\approx t_2&#39;\to t_1\approx t_2\]</span></p><p>加法对零的归约： <span class="math display">\[\text{(N3)}\qquad t_1+\bar0\approx t_1\]</span></p><p>加法对后继的归约： <span class="math display">\[\text{(N4)}\qquad t_1+t_2&#39;\approx (t_1+t_2)&#39;\]</span></p><p>乘法对零的归约： <span class="math display">\[\text{(N5)}\qquad t_1\times\bar0\approx \bar0\]</span></p><p>乘法对后继的归约： <span class="math display">\[\text{(N6)}\qquad t_1\times t_2&#39;\approx t_1\times t_2+t_1\]</span></p><p>归纳公理模式： <span class="math display">\[\text{(N7)}\qquadp(\bar0)\to\bigl(\forall x(p(x)\to p(x&#39;))\to \forall x\,p(x)\bigr)\]</span> 其中 <span class="math inline">\(p(x)\)</span> 是任意公式</p><p>这七类公理的含义分别对应 Peano 算术中关于 <spanclass="math inline">\(0\)</span>、后继、加法、乘法和归纳法的基本性质</p><p>注意，(N7) 不是一条公理，而是一个公理模式。每取一个公式 <spanclass="math inline">\(p(x)\)</span>，就得到一条归纳公理</p><h3 id="命题-18.2.2-标准解释使算术公理为真">命题 18.2.2标准解释使算术公理为真</h3><p>在自然数标准解释域 <span class="math inline">\(\mathbb N\)</span>中，<span class="math inline">\(N\)</span> 中所有算术公理都为真</p><details class="gray" data-header-exclude>    <summary><i class="fa-solid fa-chevron-right"></i>证明： 点击查看证明 </summary>    <div class='content markdown-body'>      <p>(N1) 表示任何自然数的后继都不是 <spanclass="math inline">\(0\)</span>，这在 <spanclass="math inline">\(\mathbb N\)</span> 中成立</p><p>(N2) 表示若 <span class="math display">\[n+1=m+1\]</span> 则 <span class="math display">\[n=m\]</span> 这就是后继函数的单射性</p><p>(N3)、(N4) 是加法的递归定义： <span class="math display">\[n+0=n\]</span> <span class="math display">\[n+(m+1)=(n+m)+1\]</span></p><p>(N5)、(N6) 是乘法的递归定义： <span class="math display">\[n\cdot0=0\]</span> <span class="math display">\[n\cdot(m+1)=n\cdot m+n\]</span></p><p>(N7) 是自然数归纳法的形式化。若某性质对 <spanclass="math inline">\(0\)</span> 成立，且对任意 <spanclass="math inline">\(x\)</span>，从 <spanclass="math inline">\(x\)</span> 成立能推出 <spanclass="math inline">\(x&#39;\)</span> 成立，则该性质对所有自然数成立</p><p>所以标准自然数解释是 <span class="math inline">\(K_N\)</span>算术公理的模型</p>    </div>  </details><h3 id="命题-18.2.3-数字加法可证">命题 18.2.3 数字加法可证</h3><p>对任意自然数 <span class="math inline">\(m,n\)</span>， <spanclass="math display">\[N\vdash \bar m+\bar n\approx \overline{m+n}\]</span></p><details class="gray" data-header-exclude>    <summary><i class="fa-solid fa-chevron-right"></i>证明： 点击查看证明 </summary>    <div class='content markdown-body'>      <p>对 <span class="math inline">\(n\)</span> 作普通自然数归纳</p><p>当 <span class="math inline">\(n=0\)</span> 时，由 (N3) 得 <spanclass="math display">\[\bar m+\bar0\approx \bar m\]</span> 而 <span class="math display">\[\overline{m+0}=\bar m\]</span> 所以结论成立</p><p>设 <span class="math display">\[N\vdash \bar m+\bar n\approx \overline{m+n}\]</span> 则由 (N4) 得 <span class="math display">\[\bar m+\bar n&#39;\approx (\bar m+\bar n)&#39;\]</span> 由归纳假设和等项替换得 <span class="math display">\[(\bar m+\bar n)&#39;\approx \overline{m+n}&#39;\]</span> 即 <span class="math display">\[(\bar m+\bar n)&#39;\approx \overline{m+n+1}\]</span> 用传递性合并： <span class="math display">\[N\vdash \bar m+\overline{n+1}\approx \overline{m+n+1}\]</span></p><p>所以对所有 <span class="math inline">\(n\)</span> 成立</p><p>这里的归纳是元语言中的归纳，即我们在证明关于所有自然数 <spanclass="math inline">\(n\)</span> 的命题，而不是在 <spanclass="math inline">\(K_N\)</span> 内部使用 (N7)</p>    </div>  </details><h3 id="命题-18.2.4-数字乘法可证">命题 18.2.4 数字乘法可证</h3><p>对任意自然数 <span class="math inline">\(m,n\)</span>， <spanclass="math display">\[N\vdash \bar m\times\bar n\approx \overline{mn}\]</span></p><details class="gray" data-header-exclude>    <summary><i class="fa-solid fa-chevron-right"></i>证明： 点击查看证明 </summary>    <div class='content markdown-body'>      <p>对 <span class="math inline">\(n\)</span> 作普通自然数归纳</p><p>当 <span class="math inline">\(n=0\)</span> 时，由 (N5) 得 <spanclass="math display">\[\bar m\times\bar0\approx \bar0\]</span> 而 <span class="math display">\[\overline{m\cdot0}=\bar0\]</span> 结论成立</p><p>设 <span class="math display">\[N\vdash \bar m\times\bar n\approx \overline{mn}\]</span> 由 (N6) 得 <span class="math display">\[\bar m\times\bar n&#39;\approx \bar m\times\bar n+\bar m\]</span> 由归纳假设、等项替换和命题 18.2.3 得 <spanclass="math display">\[\bar m\times\bar n+\bar m\approx\overline{mn}+\bar m\approx\overline{mn+m}\]</span> 而 <span class="math display">\[mn+m=m(n+1)\]</span> 所以 <span class="math display">\[N\vdash \bar m\times\overline{n+1}\approx \overline{m(n+1)}\]</span></p>    </div>  </details><h3 id="命题-18.2.5-左加零可证">命题 18.2.5 左加零可证</h3><p>虽然 (N3) 只给出 <span class="math display">\[t+\bar0\approx t\]</span> 但在 <span class="math inline">\(N\)</span> 中可以证明 <spanclass="math display">\[N\vdash \bar0+t\approx t\]</span></p><details class="gray" data-header-exclude>    <summary><i class="fa-solid fa-chevron-right"></i>证明： 点击查看证明 </summary>    <div class='content markdown-body'>      <p>对公式 <span class="math display">\[p(x)\equiv \bar0+x\approx x\]</span> 使用 (N7)。</p><p>基础步： <span class="math display">\[\bar0+\bar0\approx \bar0\]</span> 由 (N3) 得到</p><p>归纳步需要证明 <span class="math display">\[\bar0+x\approx x\to \bar0+x&#39;\approx x&#39;\]</span> 由 (N4) 有 <span class="math display">\[\bar0+x&#39;\approx(\bar0+x)&#39;\]</span> 若 <span class="math inline">\(\bar0+x\approxx\)</span>，则由等项替换得 <span class="math display">\[(\bar0+x)&#39;\approx x&#39;\]</span> 于是由传递性得 <span class="math display">\[\bar0+x&#39;\approx x&#39;\]</span></p><p>所以 <span class="math display">\[N\vdash \forall x(\bar0+x\approx x)\]</span> 再用全称实例化得到 <span class="math display">\[N\vdash \bar0+t\approx t\]</span></p>    </div>  </details><h3 id="命题-18.2.6-不同数字不等">命题 18.2.6 不同数字不等</h3><p>若 <span class="math inline">\(m\neq n\)</span>，则 <spanclass="math display">\[N\vdash \bar m\not\approx \bar n\]</span></p><details class="gray" data-header-exclude>    <summary><i class="fa-solid fa-chevron-right"></i>证明： 点击查看证明 </summary>    <div class='content markdown-body'>      <p>不妨设 <span class="math inline">\(m&gt;n\)</span>。则 <spanclass="math display">\[\bar m=\overline{m-n}\text{ 个后继作用在 }\bar n\text{ 上}\]</span> 若假设 <span class="math display">\[\bar m\approx \bar n\]</span> 连续使用 (N2)可以把两边共同的后继剥掉，最后得到某个项的后继等于 <spanclass="math inline">\(\bar0\)</span></p><p>例如 <span class="math display">\[\bar3\approx \bar1\]</span> 即 <span class="math display">\[\bar0&#39;&#39;&#39;\approx \bar0&#39;\]</span> 由 (N2) 得 <span class="math display">\[\bar0&#39;&#39;\approx \bar0\]</span> 但 <span class="math inline">\(\bar0&#39;&#39;\)</span>是某项的后继，由 (N1) 得矛盾</p><p>一般情形同理，所以 <span class="math display">\[N\vdash \bar m\not\approx\bar n\]</span></p>    </div>  </details><p>形式算术 <span class="math inline">\(K_N\)</span>的意义是为后面“用公式表达自然数函数和关系”提供了一个形式系统</p><h2 id="定义-18.3-可表示函数与可表示关系">定义 18.3可表示函数与可表示关系</h2><p>开始讨论 <span class="math inline">\(K_N\)</span>对自然数性质的表达能力，注意区分：</p><ul><li>数论函数和数论关系属于元语言中的自然数对象</li><li><span class="math inline">\(K_N\)</span> 公式属于形式语言</li></ul><p>“可表示”就是把前者翻译成后者</p><h3 id="定义-18.3.1-可表示函数">定义 18.3.1 可表示函数</h3><p>设 <span class="math display">\[f:\mathbb N^k\to\mathbb N\]</span> 是 <span class="math inline">\(k\)</span>元数论函数。若存在含有 <span class="math inline">\(k+1\)</span>个自由变元的 <span class="math inline">\(K_N\)</span> 公式 <spanclass="math display">\[p(x_1,\dots,x_k,y)\]</span> 使得对任意 <spanclass="math inline">\(n_1,\dots,n_k,m\in\mathbb N\)</span>：</p><p>若 <span class="math display">\[f(n_1,\dots,n_k)=m\]</span> 则 <span class="math display">\[N\vdash p(\bar n_1,\dots,\bar n_k,\bar m)\]</span></p><p>若 <span class="math display">\[f(n_1,\dots,n_k)\neq m\]</span> 则 <span class="math display">\[N\vdash \neg p(\bar n_1,\dots,\bar n_k,\bar m)\]</span></p><p>并且对任意在 <span class="math inline">\(p\)</span> 中代替 <spanclass="math inline">\(y\)</span> 的项 <spanclass="math inline">\(t\)</span>，有 <span class="math display">\[N\vdashp(\bar n_1,\dots,\bar n_k,t)\tot\approx \overline{f(n_1,\dots,n_k)}\]</span></p><p>则称 <span class="math inline">\(f\)</span> 在 <spanclass="math inline">\(K_N\)</span> 中可表示，或称 <spanclass="math inline">\(p\)</span> 表示 <spanclass="math inline">\(f\)</span></p><p>第三个条件表达“输出唯一性”：如果 <spanclass="math inline">\(p\)</span> 声称某个项 <spanclass="math inline">\(t\)</span> 是输出，那么 <spanclass="math inline">\(t\)</span> 必须等于实际输出</p><p>第二个条件在有第三个条件时可以推出，但为了后面使用方便，定义中仍把它列出来。</p><h3 id="命题-18.3.2-可表示函数的等价判定">命题 18.3.2可表示函数的等价判定</h3><p>函数 <span class="math inline">\(f\)</span> 可由公式 <spanclass="math inline">\(p(x_1,\dots,x_k,y)\)</span>表示的充分条件是：对任意 <span class="math inline">\(\vecn=(n_1,\dots,n_k)\)</span>， <span class="math display">\[N\vdash p(\bar n_1,\dots,\bar n_k,\overline{f(\vec n)})\]</span> 并且 <span class="math display">\[N\vdashp(\bar n_1,\dots,\bar n_k,t)\tot\approx \overline{f(\vec n)}\]</span></p><details class="gray" data-header-exclude>    <summary><i class="fa-solid fa-chevron-right"></i>证明： 点击查看证明 </summary>    <div class='content markdown-body'>      <p>只需要从这两个条件推出定义中的“错误值可证否定”</p><p>设 <span class="math display">\[f(\vec n)\neq m\]</span> 由命题 18.2.6， <span class="math display">\[N\vdash \bar m\not\approx\overline{f(\vec n)}\]</span></p><p>又由第二个条件，把 <span class="math inline">\(t\)</span> 取成 <spanclass="math inline">\(\bar m\)</span>，有 <span class="math display">\[N\vdashp(\bar n_1,\dots,\bar n_k,\bar m)\to\bar m\approx \overline{f(\vec n)}\]</span></p><p>由换位律和命题逻辑推理可得 <span class="math display">\[N\vdash\neg p(\bar n_1,\dots,\bar n_k,\bar m)\]</span></p><p>所以定义中的错误值条件成立</p>    </div>  </details><h3 id="注记-18.3.3-不是每个公式都表示函数">注记 18.3.3不是每个公式都表示函数</h3><p>公式要表示函数，必须对每个输入有唯一输出。例如公式 <spanclass="math display">\[x_1\approx x_1\wedge y\not\approx y\]</span> 不可能表示任何函数，因为对任意 <spanclass="math inline">\(n,m\)</span>， <span class="math display">\[N\vdash \neg(\bar n\approx\bar n\wedge \bar m\not\approx \bar m)\]</span> 它没有输出。</p><p>一个公式也不可能表示两个不同函数。若同一公式 <spanclass="math inline">\(p\)</span> 同时表示 <spanclass="math inline">\(f_1\)</span> 和 <spanclass="math inline">\(f_2\)</span>，且存在 <spanclass="math inline">\(\vec n\)</span> 使 <span class="math display">\[f_1(\vec n)\neq f_2(\vec n)\]</span> 则由 <span class="math inline">\(p\)</span> 表示 <spanclass="math inline">\(f_1\)</span> 得 <span class="math display">\[N\vdash p(\bar{\vec n},\overline{f_1(\vec n)})\]</span> 由 <span class="math inline">\(p\)</span> 表示 <spanclass="math inline">\(f_2\)</span> 得 <span class="math display">\[N\vdash \neg p(\bar{\vec n},\overline{f_1(\vec n)})\]</span> 这会导致 <span class="math inline">\(N\)</span> 矛盾</p><p>这也说明，公式和函数之间不是一一对应</p><p><span class="math inline">\(K_N\)</span>的公式只有可数多个，而自然数上的数论函数有不可数多个，所以大量数论函数不能被<span class="math inline">\(K_N\)</span> 公式表示</p><p>后面真正重要的结论是：凡是递归函数都可表示</p><h3 id="定义-18.3.4-投影函数">定义 18.3.4 投影函数</h3><p><span class="math inline">\(k\)</span> 元投影函数 <spanclass="math inline">\(p_i^k\)</span> 定义为 <spanclass="math display">\[p_i^k(n_1,\dots,n_k)=n_i\qquad (i=1,\dots,k)\]</span></p><p>投影函数在 <span class="math inline">\(K_N\)</span> 中由公式 <spanclass="math display">\[x_1\approx x_1\wedge\cdots\wedge x_k\approx x_k\wedge y\approx x_i\]</span> 表示。</p><h3 id="命题-18.3.5-加法乘法和投影函数可表示">命题 18.3.5加法、乘法和投影函数可表示</h3><p>二元加法、二元乘法和所有投影函数都在 <spanclass="math inline">\(K_N\)</span> 中可表示</p><details class="gray" data-header-exclude>    <summary><i class="fa-solid fa-chevron-right"></i>证明： 点击查看证明 </summary>    <div class='content markdown-body'>      <p>加法由公式 <span class="math display">\[x_1+x_2\approx y\]</span> 表示。</p><p>若 <span class="math inline">\(n_1+n_2=m\)</span>，由命题 18.2.3 得<span class="math display">\[N\vdash \bar n_1+\bar n_2\approx\bar m\]</span> 即 <span class="math display">\[N\vdash p(\bar n_1,\bar n_2,\bar m)\]</span></p><p>若 <span class="math inline">\(n_1+n_2\neq m\)</span>，则由命题18.2.6 得 <span class="math display">\[N\vdash \bar m\not\approx \overline{n_1+n_2}\]</span> 又由 <span class="math display">\[N\vdash \bar n_1+\bar n_2\approx\overline{n_1+n_2}\]</span> 和等词推理推出 <span class="math display">\[N\vdash \neg(\bar n_1+\bar n_2\approx\bar m)\]</span></p><p>输出唯一性来自等词传递性</p><p>乘法用公式 <span class="math display">\[x_1\times x_2\approx y\]</span> 表示，证明同理，用命题 18.2.4</p><p>投影函数由定义中的公式表示。若 <span class="math inline">\(y\)</span>等于第 <span class="math inline">\(i\)</span>个输入，公式可证；若不等，由不同数字不等可证否定；输出唯一性由等词传递性得到</p>    </div>  </details><h3 id="定义-18.3.6-可表示关系">定义 18.3.6 可表示关系</h3><p>设 <span class="math display">\[R\subseteq\mathbb N^k\]</span> 是 <span class="math inline">\(k\)</span>元数论关系。若存在含有 <span class="math inline">\(k\)</span>个自由变元的公式 <span class="math display">\[p(x_1,\dots,x_k)\]</span> 使得对任意 <span class="math inline">\(n_1,\dots,n_k\in\mathbbN\)</span>：</p><p>若 <span class="math display">\[(n_1,\dots,n_k)\in R\]</span> 则 <span class="math display">\[N\vdash p(\bar n_1,\dots,\bar n_k)\]</span></p><p>若 <span class="math display">\[(n_1,\dots,n_k)\notin R\]</span> 则 <span class="math display">\[N\vdash \neg p(\bar n_1,\dots,\bar n_k)\]</span></p><p>则称 <span class="math inline">\(R\)</span> 在 <spanclass="math inline">\(K_N\)</span> 中可表示</p><p>相等关系可由公式 <span class="math display">\[x_1\approx x_2\]</span> 表示。</p><p>小于等于关系可由公式 <span class="math display">\[\exists z(z+x_1\approx x_2)\]</span> 表示，因为 <span class="math inline">\(n_1\le n_2\)</span>当且仅当存在 <span class="math inline">\(z\)</span> 使 <spanclass="math display">\[z+n_1=n_2\]</span></p><p>小于关系可由 <span class="math display">\[\exists z(z&#39;+x_1\approx x_2)\]</span> 表示。</p><h3 id="定义-18.3.7-特征函数">定义 18.3.7 特征函数</h3><p>关系 <span class="math inline">\(R\subseteq\mathbb N^k\)</span>的特征函数 <span class="math inline">\(C_R\)</span> 定义为 <spanclass="math display">\[C_R(n_1,\dots,n_k)=\begin{cases}1, &amp; (n_1,\dots,n_k)\in R\\0, &amp; (n_1,\dots,n_k)\notin R\end{cases}\]</span></p><p>特征函数把关系问题转化成函数问题</p><h3 id="命题-18.3.8-关系可表示当且仅当特征函数可表示">命题 18.3.8关系可表示当且仅当特征函数可表示</h3><p><span class="math inline">\(k\)</span> 元关系 <spanclass="math inline">\(R\)</span> 可表示，当且仅当它的特征函数 <spanclass="math inline">\(C_R\)</span> 可表示。</p><details class="gray" data-header-exclude>    <summary><i class="fa-solid fa-chevron-right"></i>证明： 点击查看证明 </summary>    <div class='content markdown-body'>      <p>先设 <span class="math inline">\(R\)</span> 由公式 <spanclass="math inline">\(p(x_1,\dots,x_k)\)</span> 表示。定义公式 <spanclass="math display">\[q(x_1,\dots,x_k,y)\equiv\bigl(p(x_1,\dots,x_k)\wedge y\approx\bar1\bigr)\vee\bigl(\neg p(x_1,\dots,x_k)\wedge y\approx\bar0\bigr)\]</span></p><p>若 <span class="math inline">\(\vec n\in R\)</span>，则 <spanclass="math inline">\(N\vdash p(\bar{\vec n})\)</span>，并且 <spanclass="math display">\[C_R(\vec n)=1\]</span> 所以 <span class="math display">\[N\vdash q(\bar{\vec n},\bar1)\]</span></p><p>若 <span class="math inline">\(\vec n\notin R\)</span>，则 <spanclass="math inline">\(N\vdash\neg p(\bar{\vec n})\)</span>，并且 <spanclass="math display">\[C_R(\vec n)=0\]</span> 所以 <span class="math display">\[N\vdash q(\bar{\vec n},\bar0)\]</span> 错误输出的否定和输出唯一性由 <spanclass="math inline">\(\bar0\not\approx\bar1\)</span>以及等词推理得到。因此 <span class="math inline">\(C_R\)</span>可表示</p><p>反过来，若 <span class="math inline">\(C_R\)</span> 由公式 <spanclass="math inline">\(q(x_1,\dots,x_k,y)\)</span> 表示，则 <spanclass="math inline">\(R\)</span> 由公式 <span class="math display">\[q(x_1,\dots,x_k,\bar1)\]</span> 表示</p><p>若 <span class="math inline">\(\vec n\in R\)</span>，则 <spanclass="math inline">\(C_R(\vec n)=1\)</span>，所以 <spanclass="math display">\[N\vdash q(\bar{\vec n},\bar1)\]</span></p><p>若 <span class="math inline">\(\vec n\notin R\)</span>，则 <spanclass="math inline">\(C_R(\vec n)=0\)</span>，由错误值条件得 <spanclass="math display">\[N\vdash \neg q(\bar{\vec n},\bar1)\]</span></p><p>所以 <span class="math inline">\(R\)</span> 可表示</p>    </div>  </details><h2 id="定义-18.4-复合与-mu-算子保持可表示性">定义 18.4 复合与 <spanclass="math inline">\(\mu\)</span> 算子保持可表示性</h2><p>先证明两个技术结论：函数复合保持可表示性，最小数算子 <spanclass="math inline">\(\mu\)</span> 保持可表示性</p><h3 id="定理-18.4.1-函数复合保持可表示性">定理 18.4.1函数复合保持可表示性</h3><p>设 <span class="math inline">\(j\)</span> 元函数 <spanclass="math inline">\(g\)</span> 和 <spanclass="math inline">\(j\)</span> 个 <spanclass="math inline">\(k\)</span> 元函数 <span class="math display">\[h_1,\dots,h_j\]</span> 都在 <span class="math inline">\(K_N\)</span> 中可表示。定义<span class="math display">\[f(\vec n)=g(h_1(\vec n),\dots,h_j(\vec n))\]</span> 则 <span class="math inline">\(f\)</span> 也在 <spanclass="math inline">\(K_N\)</span> 中可表示。</p><details class="gray" data-header-exclude>    <summary><i class="fa-solid fa-chevron-right"></i>证明： 点击查看证明 </summary>    <div class='content markdown-body'>      <p>设 <span class="math inline">\(g\)</span> 由公式 <spanclass="math display">\[q(y_1,\dots,y_j,y)\]</span> 表示，<span class="math inline">\(h_i\)</span> 由公式 <spanclass="math display">\[r_i(x_1,\dots,x_k,y_i)\]</span> 表示。</p><p>定义公式 <span class="math display">\[P(\vec x,y)\equiv\exists y_1\cdots\exists y_j\left(\bigwedge_{i=1}^{j}r_i(\vec x,y_i)\wedgeq(y_1,\dots,y_j,y)\right)\]</span></p><p>若 <span class="math display">\[f(\vec n)=m\]</span> 令 <span class="math display">\[a_i=h_i(\vec n)\]</span> 则由 <span class="math inline">\(h_i\)</span> 可表示性， <spanclass="math display">\[N\vdash r_i(\bar{\vec n},\bar a_i)\]</span> 又由 <span class="math inline">\(g\)</span> 可表示性， <spanclass="math display">\[N\vdash q(\bar a_1,\dots,\bar a_j,\bar m)\]</span> 所以逐次使用存在引入得 <span class="math display">\[N\vdash P(\bar{\vec n},\bar m)\]</span></p><p>若 <span class="math inline">\(P(\bar{\vec n},t)\)</span> 成立，由各<span class="math inline">\(r_i\)</span> 的输出唯一性可推出 <spanclass="math display">\[y_i\approx \overline{h_i(\vec n)}\]</span> 再由 <span class="math inline">\(q\)</span> 的输出唯一性推出<span class="math display">\[t\approx \overline{g(h_1(\vec n),\dots,h_j(\vec n))}\]</span> 即 <span class="math display">\[t\approx\overline{f(\vec n)}\]</span></p><p>由命题 18.3.2，<span class="math inline">\(P\)</span> 表示 <spanclass="math inline">\(f\)</span></p>    </div>  </details><h3 id="定义-18.4.2-最小数算子-mu">定义 18.4.2 最小数算子 <spanclass="math inline">\(\mu\)</span></h3><p>设 <span class="math inline">\(g(\vec n,x)\)</span> 是 <spanclass="math inline">\(k+1\)</span> 元函数。若对每个 <spanclass="math inline">\(\vec n\)</span> 都存在自然数 <spanclass="math inline">\(x\)</span> 使 <span class="math display">\[g(\vec n,x)=0\]</span> 则定义 <span class="math display">\[f(\vec n)=\mu x[g(\vec n,x)=0]\]</span> 为使 <span class="math inline">\(g(\vec n,x)=0\)</span>成立的最小 <span class="math inline">\(x\)</span></p><p>这个 <span class="math inline">\(x\)</span> 称为方程 <spanclass="math display">\[g(\vec n,x)=0\]</span> 的根，而且有最小性： <span class="math display">\[g(\vec n,f(\vec n))=0\]</span> 并且 <span class="math display">\[g(\vec n,x)=0\to f(\vec n)\le x\]</span></p><p>若不要求根总是存在，则 <span class="math inline">\(\mu\)</span>算子可能产生部分函数。汪芳庭在定义递归函数时先讨论处处有定义的函数，因此使用<span class="math inline">\(\mu\)</span> 算子时常附带“根存在性条件”</p><h3 id="定理-18.4.3-mu-算子保持可表示性">定理 18.4.3 <spanclass="math inline">\(\mu\)</span> 算子保持可表示性</h3><p>若 <span class="math inline">\(g(\vec n,x)\)</span> 在 <spanclass="math inline">\(K_N\)</span> 中可表示，并且对每个 <spanclass="math inline">\(\vec n\)</span> 都存在 <spanclass="math inline">\(x\)</span> 使 <span class="math display">\[g(\vec n,x)=0\]</span> 则 <span class="math display">\[f(\vec n)=\mu x[g(\vec n,x)=0]\]</span> 也在 <span class="math inline">\(K_N\)</span> 中可表示。</p><details class="gray" data-header-exclude>    <summary><i class="fa-solid fa-chevron-right"></i>证明： 点击查看证明 </summary>    <div class='content markdown-body'>      <p>设 <span class="math inline">\(g\)</span> 由公式 <spanclass="math display">\[q(x_1,\dots,x_k,z,y)\]</span> 表示，其中 <span class="math inline">\(y\)</span>是函数值位置。也就是说， <span class="math display">\[q(\vec x,z,\bar0)\]</span> 表达 <span class="math display">\[g(\vec x,z)=0\]</span></p><p>定义公式 <span class="math display">\[P(\vec x,z)\equivq(\vec x,z,\bar0)\wedge\forall u\bigl(q(\vec x,u,\bar0)\to z\le u\bigr)\]</span> 其中 <span class="math display">\[z\le u\]</span> 用已经可表示的小于等于公式表示。</p><p>若 <span class="math display">\[f(\vec n)=r\]</span> 则 <span class="math inline">\(g(\vecn,r)=0\)</span>，且对任意 <span class="math inline">\(u\)</span>，若<span class="math inline">\(g(\vec n,u)=0\)</span>，则 <spanclass="math inline">\(r\le u\)</span>。由 <spanclass="math inline">\(g\)</span> 和 <spanclass="math inline">\(\le\)</span> 的可表示性得 <spanclass="math display">\[N\vdash P(\bar{\vec n},\bar r)\]</span></p><p>若 <span class="math inline">\(P(\bar{\vec n},t)\)</span> 成立，则<span class="math inline">\(t\)</span> 是一个根，并且不大于任何根。由于<span class="math inline">\(f(\vec n)\)</span> 是最小根，<spanclass="math inline">\(t\)</span> 必须等于 <span class="math display">\[\overline{f(\vec n)}\]</span> 这个等式可在 <span class="math inline">\(N\)</span>中由数字大小关系的可表示性推出。</p><p>所以 <span class="math inline">\(P\)</span> 表示 <spanclass="math inline">\(f\)</span></p>    </div>  </details><h2 id="定义-18.5-递归函数">定义 18.5 递归函数</h2><p>暂时离开形式系统 <spanclass="math inline">\(K_N\)</span>，在自然数函数本身上建立“递归函数”的概念。为后面证明“递归函数都能在<span class="math inline">\(K_N\)</span> 中表示”做准备</p><h3 id="定义-18.5.1-递归函数的一般定义">定义 18.5.1递归函数的一般定义</h3><p>递归函数从三个基本函数出发。</p><p>零函数： <span class="math display">\[z(n)=0\]</span></p><p>后继函数： <span class="math display">\[s(n)=n+1\]</span></p><p>投影函数： <span class="math display">\[p_i^k(n_1,\dots,n_k)=n_i\]</span></p><p>然后允许有限次使用以下三条规则</p><p>规则 I：复合。若 <span class="math display">\[g,\ h_1,\dots,h_j\]</span> 已经得到，则 <span class="math display">\[f(\vec n)=g(h_1(\vec n),\dots,h_j(\vec n))\]</span> 也得到</p><p>规则 II：递归。若 <span class="math inline">\(g\)</span> 是 <spanclass="math inline">\(k\)</span> 元函数，<spanclass="math inline">\(h\)</span> 是 <spanclass="math inline">\(k+2\)</span> 元函数，则定义 <spanclass="math inline">\(k+1\)</span> 元函数 <spanclass="math inline">\(f\)</span>： <span class="math display">\[f(n_1,\dots,n_k,0)=g(n_1,\dots,n_k)\]</span> <span class="math display">\[f(n_1,\dots,n_k,n+1)=h(n_1,\dots,n_k,n,f(n_1,\dots,n_k,n))\]</span></p><p>规则 III：<span class="math inline">\(\mu\)</span> 算子。若 <spanclass="math inline">\(g\)</span> 是 <spanclass="math inline">\(k+1\)</span> 元函数，并且对任意 <spanclass="math inline">\(n_1,\dots,n_k\)</span> 都存在 <spanclass="math inline">\(x\)</span> 使 <span class="math display">\[g(n_1,\dots,n_k,x)=0\]</span> 则定义 <span class="math display">\[f(n_1,\dots,n_k)=\mu x[g(n_1,\dots,n_k,x)=0]\]</span></p><p>由基本函数经过有限次规则 I、II、III得到的函数称为<strong>递归函数</strong>或<strong>一般递归函数</strong></p><p>若只允许规则 I 和规则 II，不允许规则III，则得到的函数称为<strong>原始递归函数</strong></p><p>若去掉规则 III中的根存在性要求，则可能得到不处处有定义的函数，这类函数称为<strong>递归偏函数</strong>或<strong>部分递归函数</strong></p><h3 id="例-18.5.2-常值函数">例 18.5.2 常值函数</h3><p><span class="math inline">\(k\)</span> 元常值函数 <spanclass="math display">\[c_m(n_1,\dots,n_k)=m\]</span> 是递归函数</p><details class="gray" data-header-exclude>    <summary><i class="fa-solid fa-chevron-right"></i>证明： 点击查看证明 </summary>    <div class='content markdown-body'>      <p>先构造 <span class="math inline">\(c_0\)</span>： <spanclass="math display">\[c_0(n_1,\dots,n_k)=z(p_1^k(n_1,\dots,n_k))\]</span> 这是零函数和投影函数的复合。</p><p>若 <span class="math inline">\(c_m\)</span> 已经得到，则 <spanclass="math display">\[c_{m+1}=s\circ c_m\]</span> 也得到</p><p>所以对 <span class="math inline">\(m\)</span>作归纳，所有常值函数都是递归函数</p>    </div>  </details><h3 id="例-18.5.3-加法和乘法是递归函数">例 18.5.3加法和乘法是递归函数</h3><p>加法由递归规则定义： <span class="math display">\[n_1+0=p_1^1(n_1)\]</span> <span class="math display">\[n_1+(n+1)=s(p_3^3(n_1,n,n_1+n))\]</span></p><p>换成更熟悉的写法就是： <span class="math display">\[n_1+0=n_1\]</span> <span class="math display">\[n_1+(n+1)=(n_1+n)+1\]</span></p><p>乘法由递归规则定义： <span class="math display">\[n_1\times0=z(n_1)\]</span> <span class="math display">\[n_1\times(n+1)=p_3^3(n_1,n,n_1\times n)+p_1^3(n_1,n,n_1\times n)\]</span> 即 <span class="math display">\[n_1\times0=0\]</span> <span class="math display">\[n_1\times(n+1)=n_1\times n+n_1\]</span></p><p>因为加法已经递归，所以乘法也递归</p><h3 id="例-18.5.4-前驱函数和截差函数">例 18.5.4 前驱函数和截差函数</h3><p>前驱函数定义为 <span class="math display">\[p^{-}(n)=\begin{cases}0, &amp; n=0\\n-1, &amp; n&gt;0\end{cases}\]</span></p><p>它递归，因为 <span class="math display">\[p^{-}(0)=0\]</span> <span class="math display">\[p^{-}(n+1)=n=p_1^2(n,p^{-}(n))\]</span></p><p>截差函数定义为 <span class="math display">\[n_1\mathbin{\dot-} n_2=\begin{cases}n_1-n_2, &amp; n_1\ge n_2\\0, &amp; n_1&lt;n_2\end{cases}\]</span></p><p>它递归，因为 <span class="math display">\[n_1\dotminus0=n_1\]</span> <span class="math display">\[n_1\dotminus(n+1)=p^{-}(n_1\dotminus n)\]</span></p><p>前驱函数还可以写成 <span class="math display">\[p^{-}(n)=n\dotminus1\]</span></p><h3 id="例-18.5.5-符号函数">例 18.5.5 符号函数</h3><p>定义 <span class="math display">\[\operatorname{sg}(n)=\begin{cases}0, &amp; n=0\\1, &amp; n&gt;0\end{cases}\]</span></p><p>以及 <span class="math display">\[\overline{\operatorname{sg}}(n)=\begin{cases}1, &amp; n=0\\0, &amp; n&gt;0\end{cases}\]</span></p><p><span class="math inline">\(\operatorname{sg}\)</span> 是递归的，因为<span class="math display">\[\operatorname{sg}(0)=0\]</span> <span class="math display">\[\operatorname{sg}(n+1)=1\]</span></p><p>而 <span class="math display">\[\overline{\operatorname{sg}}(n)=1\dotminus \operatorname{sg}(n)\]</span> 所以 <spanclass="math inline">\(\overline{\operatorname{sg}}\)</span>也是递归函数。</p><p>这两个函数常用来构造关系的特征函数。例如 <spanclass="math display">\[C_{=}(n_1,n_2)=\overline{\operatorname{sg}}(|n_1-n_2|)\]</span></p><h3 id="命题-18.5.6-变元重排减元和增元">命题 18.5.6变元重排、减元和增元</h3><p>若 <span class="math inline">\(f\)</span> 是 <spanclass="math inline">\(k\)</span> 元递归函数，则由 <spanclass="math display">\[g(n_1,\dots,n_l)=f(n_{m_1},\dots,n_{m_k})\]</span> 定义的 <span class="math inline">\(l\)</span>元函数也是递归函数，其中每个 <span class="math inline">\(m_i\)</span>满足 <span class="math display">\[1\le m_i\le l\]</span></p><details class="gray" data-header-exclude>    <summary><i class="fa-solid fa-chevron-right"></i>证明： 点击查看证明 </summary>    <div class='content markdown-body'>      <p>因为 <span class="math display">\[n_{m_i}=p_{m_i}^{\,l}(n_1,\dots,n_l)\]</span> 所以 <span class="math display">\[g(\vec n)=f(p_{m_1}^{\,l}(\vec n),\dots,p_{m_k}^{\,l}(\vec n))\]</span> 是 <span class="math inline">\(f\)</span>与投影函数的复合。</p><p>投影函数递归，递归函数对复合封闭，所以 <spanclass="math inline">\(g\)</span> 递归。</p>    </div>  </details><p>这个命题常用来“减元”“换元”和“增元”。例如三元函数 <spanclass="math inline">\(f\)</span> 可以得到： <spanclass="math display">\[u(n_1,n_2)=f(n_1,n_2,n_2)\]</span> <span class="math display">\[g(n_1,n_2)=f(n_1,n_1,n_2)\]</span> <span class="math display">\[h(n_1,n_2,n_3,n_4)=f(n_3,n_1,n_4)\]</span> 这些都仍是递归函数。</p><h3 id="例-18.5.7-常用递归函数">例 18.5.7 常用递归函数</h3><p>绝对差： <span class="math display">\[|n_1-n_2|=(n_1\dotminus n_2)+(n_2\dotminus n_1)\]</span> 所以递归。</p><p>最小值： <span class="math display">\[\min(n_1,n_2)=n_1\dotminus(n_1\dotminus n_2)\]</span> 多元最小值递归地定义为 <span class="math display">\[\min(n_1,\dots,n_k)=\min(\min(n_1,\dots,n_{k-1}),n_k)\]</span> 最大值同理可由 <span class="math display">\[\max(n_1,n_2)=n_1+(n_2\dotminus n_1)\]</span> 得到。</p><p>指数函数可递归定义为 <span class="math display">\[n^0=\overline{\operatorname{sg}}(n)\]</span> 这里约定 <span class="math inline">\(0^0=0\)</span>，并且<span class="math display">\[n^{m+1}=n^m\times n\]</span></p><p>余数函数 <spanclass="math inline">\(\operatorname{rem}(n_1,n_2)\)</span> 表示用 <spanclass="math inline">\(n_1\)</span> 除 <spanclass="math inline">\(n_2\)</span> 后所得余数，约定 <spanclass="math inline">\(n_1=0\)</span> 时余数为 <spanclass="math inline">\(0\)</span>。它可用递归定义： <spanclass="math display">\[\operatorname{rem}(n_1,0)=0\]</span> <span class="math display">\[\operatorname{rem}(n_1,n+1)=(\operatorname{rem}(n_1,n)+1)\,\operatorname{sg}\bigl(n_1\dotminus(\operatorname{rem}(n_1,n)+1)\bigr)\]</span> 这表示余数每次加一；若已经达到除数，就回到 <spanclass="math inline">\(0\)</span>。</p><h3 id="命题-18.5.8-有界求和和有界求积保持递归性">命题 18.5.8有界求和和有界求积保持递归性</h3><p>设 <span class="math inline">\(f(\vec n,i)\)</span> 是 <spanclass="math inline">\(k+1\)</span> 元递归函数。定义 <spanclass="math display">\[g(\vec n)=\sum_{i\le n_k}f(n_1,\dots,n_{k-1},i)\]</span> 和 <span class="math display">\[h(\vec n,m)=\sum_{i\le m}f(\vec n,i)\]</span> 则 <span class="math inline">\(g,h\)</span> 都是递归函数。</p><p>类似地，有界求积 <span class="math display">\[\prod_{i\le m}f(\vec n,i)\]</span> 也是递归函数。</p><details class="gray" data-header-exclude>    <summary><i class="fa-solid fa-chevron-right"></i>证明： 点击查看证明 </summary>    <div class='content markdown-body'>      <p>以有界求和为例。</p><p>定义 <span class="math display">\[h(\vec n,0)=f(\vec n,0)\]</span> <span class="math display">\[h(\vec n,m+1)=h(\vec n,m)+f(\vec n,m+1)\]</span> 因为加法和 <span class="math inline">\(f\)</span>都递归，所以由递归规则可知 <span class="math inline">\(h\)</span>递归。</p><p>再令 <span class="math display">\[g(n_1,\dots,n_k)=h(n_1,\dots,n_{k-1},n_k)\]</span> 由复合可知 <span class="math inline">\(g\)</span> 递归。</p><p>有界求积把基础步改为 <span class="math display">\[h(\vec n,0)=f(\vec n,0)\]</span> 递推步改为 <span class="math display">\[h(\vec n,m+1)=h(\vec n,m)\times f(\vec n,m+1)\]</span> 即可。</p>    </div>  </details><h2 id="定义-18.6-递归关系与递归集">定义 18.6 递归关系与递归集</h2><p>汪芳庭第 3.4.2 节用特征函数定义递归关系。</p><h3 id="定义-18.6.1-递归关系">定义 18.6.1 递归关系</h3><p>设 <span class="math display">\[R\subseteq\mathbb N^k\]</span> 是 <span class="math inline">\(k\)</span>元关系。若它的特征函数 <span class="math inline">\(C_R\)</span>是递归函数，则称 <span class="math inline">\(R\)</span>是<strong>递归关系</strong>。</p><p>一元递归关系也称为 <span class="math inline">\(\mathbb N\)</span>的<strong>递归子集</strong>，简称递归集。</p><h3 id="例-18.6.2-基本递归关系">例 18.6.2 基本递归关系</h3><p>关系 <span class="math inline">\(\le\)</span>、<spanclass="math inline">\(=\)</span>、<spanclass="math inline">\(&lt;\)</span> 都是递归关系。</p><p>事实上： <span class="math display">\[C_{\le}(n_1,n_2)=\overline{\operatorname{sg}}(n_1\mathbin{\dot-} n_2)\]</span></p><p>因为 <span class="math inline">\(n_1\le n_2\)</span> 当且仅当 <spanclass="math inline">\(n_1\mathbin{\dot-} n_2=0\)</span></p><p>相等关系可写为 <span class="math display">\[C_{=}(n_1,n_2)=\overline{\operatorname{sg}}(|n_1-n_2|)\]</span></p><p>小于关系可写为 <span class="math display">\[C_{&lt;}(n_1,n_2)=C_{\le}(n_1+1,n_2)\]</span></p><h3 id="命题-18.6.3-递归关系的闭包性质">命题 18.6.3递归关系的闭包性质</h3><p>若 <span class="math inline">\(R,R_1,R_2\)</span> 是 <spanclass="math inline">\(k\)</span> 元递归关系，则：</p><ul><li>补关系 <span class="math inline">\(\overline R\)</span>是递归关系</li><li>并关系 <span class="math inline">\(R_1\cup R_2\)</span>是递归关系</li><li>交关系 <span class="math inline">\(R_1\cap R_2\)</span>是递归关系</li></ul><details class="gray" data-header-exclude>    <summary><i class="fa-solid fa-chevron-right"></i>证明： 点击查看证明 </summary>    <div class='content markdown-body'>      <p>由特征函数计算： <span class="math display">\[C_{\overline R}(\vec n)=1\mathbin{\dot-} C_R(\vec n)\]</span></p><p>并关系： <span class="math display">\[C_{R_1\cup R_2}(\vec n)=\operatorname{sg}(C_{R_1}(\vec n)+C_{R_2}(\vec n))\]</span></p><p>交关系： <span class="math display">\[C_{R_1\cap R_2}(\vec n)=C_{R_1}(\vec n)\times C_{R_2}(\vec n)\]</span></p><p>右边都是递归函数的复合，所以仍递归。</p>    </div>  </details><h3 id="命题-18.6.4-有界量词保持递归性">命题 18.6.4有界量词保持递归性</h3><p>设 <span class="math inline">\(R(\vec n,x)\)</span> 是 <spanclass="math inline">\(k+1\)</span> 元递归关系。定义 <spanclass="math display">\[Q(\vec n,m)\iff \exists x&lt;m\,R(\vec n,x)\]</span> 则 <span class="math inline">\(Q\)</span> 是递归关系。</p><p>同理， <span class="math display">\[P(\vec n,m)\iff \forall x&lt;m\,R(\vec n,x)\]</span> 也是递归关系。</p><details class="gray" data-header-exclude>    <summary><i class="fa-solid fa-chevron-right"></i>证明： 点击查看证明 </summary>    <div class='content markdown-body'>      <p>存在量词情形： <span class="math display">\[C_Q(\vec n,m)=\operatorname{sg}\left(\sum_{x&lt;m}C_R(\vec n,x)\right)\]</span> 有界求和保持递归性，所以 <spanclass="math inline">\(C_Q\)</span> 递归。</p><p>全称量词情形可用补关系和有界存在表示： <span class="math display">\[\forall x&lt;m\,R(\vec n,x)\iff\neg\exists x&lt;m\,\neg R(\vec n,x)\]</span> 由递归关系对补和有界存在封闭，得 <spanclass="math inline">\(P\)</span> 递归。</p>    </div>  </details><h3 id="例-18.6.5-有限集整除关系和素数集">例 18.6.5有限集、整除关系和素数集</h3><p>空集、全集、单点集和有限集都是递归集。比如 <spanclass="math display">\[C_{\{a\}}(n)=C_{=}(n,a)\]</span> 有限集 <span class="math display">\[\{a_1,\dots,a_l\}\]</span> 是若干单点集的并。</p><p>整除关系 <span class="math display">\[\operatorname{Divi}(n_1,n_2)\iffn_1=0\text{ 或 }n_1\text{ 能整除 }n_2\]</span> 也是递归关系。可用余数函数写为 <span class="math display">\[C_{\operatorname{Divi}}(n_1,n_2)=\overline{\operatorname{sg}}(\operatorname{rem}(n_1,n_2))\]</span></p><p>素数集 <span class="math inline">\(\operatorname{Prm}\)</span>也是递归集。一个数 <span class="math inline">\(n&gt;1\)</span>是素数，当且仅当在不大于 <span class="math inline">\(n\)</span>的数中，它的因子只有 <span class="math inline">\(1\)</span>和自身。可用有界求和表达“因子个数不超过两个”。因此素数的特征函数是递归函数</p><p>第 <span class="math inline">\(n\)</span> 个素数函数 <spanclass="math inline">\(p(n)\)</span> 也递归： <spanclass="math display">\[p(0)=2\]</span> <span class="math display">\[p(n+1)=\mu x\,[\,p(n)&lt;x\wedge x\in \operatorname{Prm}\,]\]</span> 这里右边的条件是递归关系，并且根存在，所以由 <spanclass="math inline">\(\mu\)</span> 算子得到递归函数。</p><h2 id="定义-18.7-递归函数的可表示性">定义 18.7 递归函数的可表示性</h2><p>回到数论函数与 <span class="math inline">\(K_N\)</span>公式的关系。</p><p><span class="math display">\[\mathrm{REP}=\{f\mid f\text{ 在 }K_N\text{ 中可表示}\}\]</span></p><p><span class="math display">\[\mathrm{REC}=\{f\mid f\text{ 是递归函数}\}\]</span></p><p>还定义一个辅助类 <spanclass="math inline">\(\mathrm{REC}^\ast\)</span>：从加法、乘法、投影函数、关系<span class="math inline">\(\le\)</span>的特征函数出发，经过有限次复合和 <spanclass="math inline">\(\mu\)</span> 算子得到的函数全体</p><p>使用 <span class="math inline">\(\mathrm{REC}^\ast\)</span>的原因是：加法、乘法、投影函数和 <spanclass="math inline">\(\le\)</span>的特征函数已经容易证明可表示；而复合和 <spanclass="math inline">\(\mu\)</span> 又保持可表示性</p><h3 id="命题-18.7.1-mathrmrecastsubseteq-mathrmrec">命题 18.7.1 <spanclass="math inline">\(\mathrm{REC}^\ast\subseteq\mathrm{REC}\)</span></h3><p>辅助类 <span class="math inline">\(\mathrm{REC}^\ast\)</span>中的每个函数都是递归函数</p><details class="gray" data-header-exclude>    <summary><i class="fa-solid fa-chevron-right"></i>证明： 点击查看证明 </summary>    <div class='content markdown-body'>      <p>加法、乘法和投影函数已经在 18.5 中证明递归</p><p>关系 <span class="math inline">\(\le\)</span> 的特征函数 <spanclass="math display">\[C_{\le}(n_1,n_2)=\overline{\operatorname{sg}}(n_1\mathbin{\dot-} n_2)\]</span> 也是递归函数。</p><p>递归函数对复合封闭，对满足根存在条件的 <spanclass="math inline">\(\mu\)</span> 算子封闭</p><p>所以由这些函数有限次构造出的 <spanclass="math inline">\(\mathrm{REC}^\ast\)</span> 函数都递归</p>    </div>  </details><h3 id="命题-18.7.2-mathrmrecastsubseteq-mathrmrep">命题 18.7.2 <spanclass="math inline">\(\mathrm{REC}^\ast\subseteq\mathrm{REP}\)</span></h3><p>辅助类 <span class="math inline">\(\mathrm{REC}^\ast\)</span>中的每个函数都在 <span class="math inline">\(K_N\)</span> 中可表示。</p><details class="gray" data-header-exclude>    <summary><i class="fa-solid fa-chevron-right"></i>证明： 点击查看证明 </summary>    <div class='content markdown-body'>      <p>加法、乘法和投影函数可表示，见命题 18.3.5。</p><p>关系 <span class="math inline">\(\le\)</span>可表示，故其特征函数可表示，见命题 18.3.8。</p><p>由定理 18.4.1，复合保持可表示性。</p><p>由定理 18.4.3，<span class="math inline">\(\mu\)</span>算子保持可表示性。</p><p>因此从这些初始函数出发有限次构造出的函数都可表示，即 <spanclass="math display">\[\mathrm{REC}^\ast\subseteq \mathrm{REP}\]</span></p>    </div>  </details><h3 id="命题-18.7.3-mathrmrecsubseteq-mathrmrecast">命题 18.7.3 <spanclass="math inline">\(\mathrm{REC}\subseteq\mathrm{REC}^\ast\)</span></h3><p>每个递归函数都属于 <spanclass="math inline">\(\mathrm{REC}^\ast\)</span></p><details class="gray" data-header-exclude>    <summary><i class="fa-solid fa-chevron-right"></i>证明： 点击查看证明 </summary>    <div class='content markdown-body'>      <p>关键在于说明递归定义可以用 <spanclass="math inline">\(\mathrm{REC}^\ast\)</span> 中允许的构造模拟</p><p>设 <span class="math inline">\(f\)</span> 由原始递归给出： <spanclass="math display">\[f(\vec n,0)=g(\vec n)\]</span> <span class="math display">\[f(\vec n,y+1)=h(\vec n,y,f(\vec n,y))\]</span></p><p>要计算 <span class="math inline">\(f(\vecn,y)\)</span>，只需知道有限序列 <span class="math display">\[a_0,a_1,\dots,a_y\]</span> 其中 <span class="math display">\[a_0=g(\vec n)\]</span> <span class="math display">\[a_{i+1}=h(\vec n,i,a_i)\]</span> 最后输出 <span class="math inline">\(a_y\)</span></p><p>有限序列可以用素数幂编码为一个自然数： <span class="math display">\[\langle a_0,\dots,a_y\rangle=2^{a_0+1}3^{a_1+1}\cdots p_y^{a_y+1}\]</span> 序列长度、取第 <span class="math inline">\(i\)</span>项、检查某数是否为这样的计算过程编码，都是可以由加法、乘法、<spanclass="math inline">\(\le\)</span>、复合和 <spanclass="math inline">\(\mu\)</span> 表达出来的函数或关系，因此属于 <spanclass="math inline">\(\mathrm{REC}^\ast\)</span></p><p>于是可定义一个 <span class="math inline">\(\mathrm{REC}^\ast\)</span>关系： <span class="math display">\[H(\vec n,y,s)\]</span> 表示“<span class="math inline">\(s\)</span> 编码了从 <spanclass="math inline">\(0\)</span> 到 <spanclass="math inline">\(y\)</span> 的正确计算过程”。再用 <spanclass="math inline">\(\mu\)</span> 找到某个这样的 <spanclass="math inline">\(s\)</span>，最后取出第 <spanclass="math inline">\(y\)</span> 项</p><p>这说明原始递归规则可以在 <spanclass="math inline">\(\mathrm{REC}^\ast\)</span> 中模拟</p><p>基本函数也都属于 <spanclass="math inline">\(\mathrm{REC}^\ast\)</span>：零函数可由 <spanclass="math display">\[z(n)=C_{\le}(n+1,n)\]</span> 得到，后继函数可由 <span class="math display">\[s(n)=n+1\]</span> 得到，投影函数本来就是初始函数。</p><p>因此所有递归函数都属于 <spanclass="math inline">\(\mathrm{REC}^\ast\)</span></p>    </div>  </details><h3 id="定理-18.7.4-递归函数可表示性定理">定理 18.7.4递归函数可表示性定理</h3><p>每个递归函数都在 <span class="math inline">\(K_N\)</span>中可表示。</p><p>也就是 <span class="math display">\[\mathrm{REC}\subseteq \mathrm{REP}\]</span></p><details class="gray" data-header-exclude>    <summary><i class="fa-solid fa-chevron-right"></i>证明： 点击查看证明 </summary>    <div class='content markdown-body'>      <p>由命题 18.7.3， <span class="math display">\[\mathrm{REC}\subseteq \mathrm{REC}^\ast\]</span> 由命题 18.7.2， <span class="math display">\[\mathrm{REC}^\ast\subseteq \mathrm{REP}\]</span> 所以 <span class="math display">\[\mathrm{REC}\subseteq \mathrm{REP}\]</span></p><p>这就是 Gödel不完备性定理所需的重要桥梁：凡是递归可计算的数论函数，都能变成 <spanclass="math inline">\(K_N\)</span> 中的公式</p>    </div>  </details><h3 id="推论-18.7.5-递归关系可表示">推论 18.7.5 递归关系可表示</h3><p>每个递归关系都在 <span class="math inline">\(K_N\)</span>中可表示</p><details class="gray" data-header-exclude>    <summary><i class="fa-solid fa-chevron-right"></i>证明： 点击查看证明 </summary>    <div class='content markdown-body'>      <p>若 <span class="math inline">\(R\)</span> 是递归关系，则它的特征函数<span class="math inline">\(C_R\)</span> 是递归函数</p><p>由递归函数可表示性定理，<span class="math inline">\(C_R\)</span> 在<span class="math inline">\(K_N\)</span> 中可表示</p><p>由命题 18.3.8，<span class="math inline">\(R\)</span> 可表示</p>    </div>  </details><h2 id="定义-18.8-对-k_n-的递归分析">定义 18.8 对 <spanclass="math inline">\(K_N\)</span> 的递归分析</h2><p>对 <span class="math inline">\(K_N\)</span>自身的语法做递归分析。前面已经知道递归关系可表示，现在要证明：关于 <spanclass="math inline">\(K_N\)</span>公式、项、替换、证明的许多语法关系本身就是递归关系</p><p>这一步把形式系统变成自然数对象。完成之后，“某个数是公式的 Gödel数”“某个数是证明的 Gödel 数”“某个公式可证”都能写成算术公式</p><h3 id="定义-18.8.1-前置式与唯一读法">定义 18.8.1 前置式与唯一读法</h3><p>为了方便分析，把项和公式先写成前置式。例如：</p><p><span class="math display">\[t_1+t_2\]</span> 写成 <span class="math display">\[+t_1t_2\]</span></p><p><span class="math display">\[t_1\times t_2\]</span> 写成 <span class="math display">\[\times t_1t_2\]</span></p><p><span class="math display">\[t_1\approx t_2\]</span> 写成 <span class="math display">\[\approx t_1t_2\]</span></p><p><span class="math display">\[p\to q\]</span> 写成 <span class="math display">\[\to pq\]</span></p><p>前置式的好处是括号可以省略，但仍然能唯一恢复结构</p><p>给字母规定权重： <span class="math display">\[w(x_i)=w(\bar0)=1\]</span> <span class="math display">\[w(&#39;)=w(\neg)=0\]</span> <span class="math display">\[w(+)=w(\times)=w(\approx)=w(\to)=w(\forall)=-1\]</span></p><p>字母串 <span class="math inline">\(u_1\cdots u_n\)</span>的权重定义为 <span class="math display">\[w(u_1\cdots u_n)=w(u_1)+\cdots+w(u_n)\]</span></p><h3 id="命题-18.8.2-唯一读法引理">命题 18.8.2 唯一读法引理</h3><p>在 <span class="math inline">\(K_N\)</span> 的前置式中：</p><ol type="1"><li>若字母串构成项或公式，则其总权重为 <spanclass="math inline">\(1\)</span></li><li>一个项或公式的任一真前段都不是项或公式</li><li>若一个字母串可由两个项或两个公式拼接而成，则拼接方式唯一</li></ol><details class="gray" data-header-exclude>    <summary><i class="fa-solid fa-chevron-right"></i>证明： 点击查看证明 </summary>    <div class='content markdown-body'>      <p>第一点对项和公式的构造作归纳</p><p>基本项 <span class="math inline">\(x_i\)</span> 和 <spanclass="math inline">\(\bar0\)</span> 的权重都是 <spanclass="math inline">\(1\)</span></p><p>若 <span class="math inline">\(t\)</span> 是项，则 <spanclass="math inline">\(t&#39;\)</span> 的权重为 <spanclass="math display">\[w(t&#39;)=w(&#39;)+w(t)=0+1=1\]</span></p><p>若 <span class="math inline">\(t_1,t_2\)</span> 是项，则 <spanclass="math display">\[w(+t_1t_2)=-1+1+1=1\]</span> 乘法项同理</p><p>若 <span class="math inline">\(t_1,t_2\)</span> 是项，则原子公式<span class="math display">\[\approx t_1t_2\]</span> 权重为 <span class="math display">\[-1+1+1=1\]</span></p><p>否定公式 <span class="math inline">\(\neg p\)</span> 权重为 <spanclass="math display">\[0+1=1\]</span> 蕴涵公式 <span class="math inline">\(\to pq\)</span> 权重为<span class="math display">\[-1+1+1=1\]</span> 全称公式 <span class="math inline">\(\forall x_i p\)</span>中，<span class="math inline">\(\forall\)</span> 的权重为 <spanclass="math inline">\(-1\)</span>，<spanclass="math inline">\(x_i\)</span> 的权重为 <spanclass="math inline">\(1\)</span>，<span class="math inline">\(p\)</span>的权重为 <span class="math inline">\(1\)</span>，总和仍为 <spanclass="math inline">\(1\)</span></p><p>第二点也对构造归纳。直观上，前置式的开头符号已经决定后面需要几个完整成分；在完整成分没有读完以前，权重不会回到<span class="math inline">\(1\)</span>。例如 <spanclass="math inline">\(+t_1t_2\)</span> 的真前段要么还没读完 <spanclass="math inline">\(t_1\)</span>，要么读完 <spanclass="math inline">\(t_1\)</span> 但还没读完 <spanclass="math inline">\(t_2\)</span>，都不可能是完整项</p><p>第三点由第二点推出。若同一字母串既能分为 <spanclass="math inline">\(t_1t_2\)</span>，又能分为 <spanclass="math inline">\(s_1s_2\)</span>，且 <spanclass="math inline">\(t_1\)</span> 与 <spanclass="math inline">\(s_1\)</span>长度不同，那么较短者就是较长者的真前段，但二者都是项或公式，矛盾。因此第一段相同，第二段也相同</p><p>所以前置式具有唯一读法</p>    </div>  </details><p>唯一读法引理保证我们可以机械判定一个有限字母串是不是项或公式，并且能机械地拆出它的组成部分</p><h3 id="定义-18.8.3-gödel-数">定义 18.8.3 Gödel 数</h3><p>Gödel 数是把符号串编码为自然数的方法。</p><p>汪芳庭给每个基本字母分配一个奇数编码：</p><p><span class="math display">\[\begin{array}{c|ccccccccc}u&amp;&#39;&amp;+&amp;\times&amp;\neg&amp;\to&amp;\forall&amp;\approx&amp;\bar0&amp;x_i\\\hlineg(u)&amp;1&amp;3&amp;5&amp;7&amp;9&amp;11&amp;13&amp;15&amp;15+2i\end{array}\]</span></p><p>不同字母有不同的 Gödel 数，而且全是奇数</p><p>若字母串为 <span class="math display">\[u_0u_1\cdots u_k\]</span> 则定义其 Gödel 数为 <span class="math display">\[g(u_0u_1\cdots u_k)=2^{g(u_0)}3^{g(u_1)}\cdots p_k^{g(u_k)}\]</span> 其中 <span class="math inline">\(p_k\)</span> 是第 <spanclass="math inline">\(k+1\)</span> 个素数</p><p>由于自然数素因子分解唯一，不同字母串的 Gödel 数不同</p><p>反过来，给定一个自然数，只需分解它的素因子，就能判断它是否是某个字母串的Gödel 数，并恢复该字母串</p><p>如果要编码一个字母串的有限序列 <span class="math display">\[s_0,s_1,\dots,s_n\]</span> 则再用同样方式编码： <span class="math display">\[g(s_0,\dots,s_n)=2^{g(s_0)}3^{g(s_1)}\cdots p_n^{g(s_n)}\]</span></p><p>这里要注意两个层次：</p><ul><li>一个公式作为字母串有 Gödel 数</li><li>一个公式序列作为有限序列也有 Gödel 数</li></ul><p>同一个形式对象在不同层次出现时，编码方式不同，但每次使用时都能从上下文判断其身份</p><h3 id="定义-18.8.4-过程值递归">定义 18.8.4 过程值递归</h3><p>为了处理公式序列和证明序列，需要能递归地操作有限序列的编码。常用操作包括：</p><ul><li>判断 <span class="math inline">\(n\)</span>是否为某个有限序列的编码</li><li>取序列长度</li><li>取序列第 <span class="math inline">\(i\)</span> 项</li><li>把两个序列拼接</li><li>在项或公式中进行替换</li></ul><p>这些都基于素数幂编码。</p><p>例如，若 <span class="math display">\[s=2^{a_0+1}3^{a_1+1}\cdots p_k^{a_k+1}\]</span> 则“第 <span class="math inline">\(i\)</span> 项为 <spanclass="math inline">\(a_i\)</span>”可以通过判断 <spanclass="math inline">\(p_i\)</span> 在 <spanclass="math inline">\(s\)</span> 的素因子分解中的指数得到。素数集、第<span class="math inline">\(i\)</span>个素数、整除关系、余数函数都已经是递归对象，所以取项函数也是递归的。</p><p>过程值递归的基本形式是：若某函数的计算过程可以写成有限序列，并且每一步是否正确可递归检查，那么整个函数可由“寻找一个正确过程编码，再读取最后一项”得到。</p><p>这正是前面证明原始递归能被 <spanclass="math inline">\(\mathrm{REC}^\ast\)</span> 模拟的技术基础。</p><h3 id="命题-18.8.5-语法对象的-gödel-数集合是递归集">命题 18.8.5语法对象的 Gödel 数集合是递归集</h3><p>以下集合都是递归集：</p><ul><li><spanclass="math inline">\(\mathrm{VS}\)</span>：个体变元作为独立字母串的Gödel 数集合</li><li><span class="math inline">\(\mathrm{TM}\)</span>：所有 <spanclass="math inline">\(K_N\)</span> 项的 Gödel 数集合</li><li><span class="math inline">\(\mathrm{YF}\)</span>：所有原子公式的Gödel 数集合</li><li><span class="math inline">\(\mathrm{FM}\)</span>：所有 <spanclass="math inline">\(K_N\)</span> 公式的 Gödel 数集合</li></ul><details class="gray" data-header-exclude>    <summary><i class="fa-solid fa-chevron-right"></i>证明： 点击查看证明 </summary>    <div class='content markdown-body'>      <p><span class="math inline">\(\mathrm{VS}\)</span> 最简单。由编码表，<span class="math display">\[g(x_i)=15+2i\]</span> 作为独立字母串的 Gödel 数为 <span class="math display">\[2^{15+2i}\]</span> 因此 <span class="math display">\[n\in\mathrm{VS}\iff\exists i&lt;n\, (n=2^{15+2i})\]</span> 这是有界存在形式，故递归。</p><p><span class="math inline">\(\mathrm{TM}\)</span>可按项的构造给出递归判定：</p><ul><li><span class="math inline">\(\bar0\)</span> 是项</li><li>变元是项</li><li>若 <span class="math inline">\(x\)</span> 是项，则 <spanclass="math inline">\(&#39;x\)</span> 是项</li><li>若 <span class="math inline">\(x,y\)</span> 是项，则 <spanclass="math inline">\(+xy\)</span> 和 <span class="math inline">\(\timesxy\)</span> 是项</li></ul><p>由于前置式唯一读法保证一个串的最外层构造可以唯一判定，所以“<spanclass="math inline">\(n\)</span> 是项的 Gödel 数”可递归判定。</p><p><span class="math inline">\(\mathrm{YF}\)</span> 只有原子公式 <spanclass="math display">\[\approx t_1t_2\]</span> 所以只需检查最外层符号是否为 <spanclass="math inline">\(\approx\)</span>，并递归检查后面两个成分是否为项。</p><p><span class="math inline">\(\mathrm{FM}\)</span> 按公式构造判定：</p><ul><li>原子公式是公式</li><li>若 <span class="math inline">\(p\)</span> 是公式，则 <spanclass="math inline">\(\neg p\)</span> 是公式</li><li>若 <span class="math inline">\(p,q\)</span> 是公式，则 <spanclass="math inline">\(\to pq\)</span> 是公式</li><li>若 <span class="math inline">\(x_i\)</span> 是变元且 <spanclass="math inline">\(p\)</span> 是公式，则 <spanclass="math inline">\(\forall x_i p\)</span> 是公式</li></ul><p>同样由唯一读法，最外层构造可机械拆分，所以 <spanclass="math inline">\(\mathrm{FM}\)</span> 是递归集。</p>    </div>  </details><h3 id="定义-18.8.6-替换关系和-sub-函数">定义 18.8.6 替换关系和 Sub函数</h3><p>后面构造自指公式时，需要把一个公式中的自由变元替换为某个数字。设<span class="math display">\[\mathrm{Sub}(n_1,n_2,n_3)\]</span> 表示如下函数关系：</p><p><span class="math inline">\(n_1\)</span> 是某个变元的 Gödel 数，<spanclass="math inline">\(n_2\)</span> 是某个项或公式的 Gödel 数，<spanclass="math inline">\(n_3\)</span> 是替换进去的项的 Gödel 数，输出是把<span class="math inline">\(n_2\)</span> 中自由出现的 <spanclass="math inline">\(n_1\)</span> 全部替换为 <spanclass="math inline">\(n_3\)</span> 后所得对象的 Gödel 数。</p><p>汪芳庭先分析一个三元递归关系 <spanclass="math inline">\(\mathrm{SBS}\)</span>，表示“替换结果正确”。其证明按项和公式的构造分类型：</p><ul><li>若对象本身就是要替换的变元，则替换结果就是新项</li><li>若对象是其他变元或常元，则替换结果不变</li><li>若对象是 <span class="math inline">\(t&#39;\)</span>、<spanclass="math inline">\(\neg p\)</span>，则递归替换内部对象</li><li>若对象是 <span class="math inline">\(t_1+t_2\)</span>、<spanclass="math inline">\(t_1\times t_2\)</span>、<spanclass="math inline">\(t_1\approx t_2\)</span>、<spanclass="math inline">\(p\to q\)</span>，则分别替换两个子对象</li><li>若对象是 <span class="math inline">\(\forall x_i p\)</span>，则当<span class="math inline">\(x_i\)</span>正是被替换变元时不进入量词内部，否则替换公式 <spanclass="math inline">\(p\)</span></li></ul><p>因为唯一读法保证这些情形互不混淆，并且每个子对象的 Gödel 数都小于整体Gödel 数，所以该替换关系是递归关系。</p><p>于是替换函数 <span class="math display">\[\mathrm{Sub}(n_1,n_2,n_3)\]</span> 也是递归函数。</p><p>特别地，令 <span class="math display">\[\mathrm{Num}(n)=g(\bar n)\]</span> 表示自然数 <span class="math inline">\(n\)</span> 的数码 <spanclass="math inline">\(\bar n\)</span> 的 Gödel 数，则 <spanclass="math display">\[\mathrm{Num}\]</span> 也是递归函数。</p><h3 id="命题-18.8.7-证明关系是递归关系">命题 18.8.7证明关系是递归关系</h3><p>设 <span class="math inline">\(\mathrm{PRF}(x,y)\)</span> 表示：</p><p><span class="math inline">\(x\)</span> 是 <spanclass="math inline">\(K_N\)</span> 中公式 <spanclass="math inline">\(y\)</span> 的一个证明的 Gödel 数。</p><p>则 <span class="math inline">\(\mathrm{PRF}\)</span> 是递归关系。</p><details class="gray" data-header-exclude>    <summary><i class="fa-solid fa-chevron-right"></i>证明： 点击查看证明 </summary>    <div class='content markdown-body'>      <p>给定 <span class="math inline">\(x,y\)</span>，先把 <spanclass="math inline">\(x\)</span> 解码为一个有限公式序列： <spanclass="math display">\[A_0,A_1,\dots,A_m\]</span> 若解码失败，则不是证明。</p><p>若解码成功，逐项检查：</p><ol type="1"><li>每个 <span class="math inline">\(A_i\)</span> 是否是公式，即其 Gödel数是否在 <span class="math inline">\(\mathrm{FM}\)</span> 中。</li><li>最后一项是否等于 <span class="math inline">\(y\)</span>。</li><li>每个 <span class="math inline">\(A_i\)</span>是否是逻辑公理、等词公理、算术公理，或是否能由前面若干项按 MP、Gen等规则推出。</li></ol><p>逻辑公理模式、等词公理模式、算术公理模式的 Gödel数集合都是递归集。推理规则只涉及有限位置的公式匹配，也可递归检查。</p><p>所以 <span class="math inline">\(\mathrm{PRF}(x,y)\)</span>是递归关系。</p>    </div>  </details><h3 id="推论-18.8.8-可证公式集递归可枚举">推论 18.8.8可证公式集递归可枚举</h3><p>令 <span class="math display">\[\mathrm{PA}=\{y\mid \exists x\,\mathrm{PRF}(x,y)\}\]</span> 即 <span class="math inline">\(K_N\)</span> 中可证公式的 Gödel数集合。则 <span class="math inline">\(\mathrm{PA}\)</span>是递归可枚举集。</p><details class="gray" data-header-exclude>    <summary><i class="fa-solid fa-chevron-right"></i>证明： 点击查看证明 </summary>    <div class='content markdown-body'>      <p>枚举自然数 <span class="math display">\[x=0,1,2,\dots\]</span> 对每个 <spanclass="math inline">\(x\)</span>，检查它是否是某个证明的 Gödel数。若是，并且其最后公式的 Gödel 数为 <spanclass="math inline">\(y\)</span>，就输出 <spanclass="math inline">\(y\)</span>。</p><p>由于 <span class="math inline">\(\mathrm{PRF}\)</span>是递归关系，每一步检查有限完成。</p><p>所有可证公式都有某个有限证明，因此最终会被枚举出来。</p>    </div>  </details><h2 id="定义-18.9-gödel-不完备性定理">定义 18.9 Gödel 不完备性定理</h2><p>第 4 章开始讨论不完备性。汪芳庭书先明确两个概念：完备性和 <spanclass="math inline">\(\omega\)</span>-无矛盾。</p><p>一个公式集 <span class="math inline">\(\Gamma\)</span>称为<strong>完备的</strong>，若对任意闭式 <spanclass="math inline">\(p\)</span>，都有 <span class="math display">\[\Gamma\vdash p\]</span> 或 <span class="math display">\[\Gamma\vdash \neg p\]</span></p><p>也就是说，每个句子都能被 <span class="math inline">\(\Gamma\)</span>判定。</p><h3 id="定义-18.9.1-omega-无矛盾">定义 18.9.1 <spanclass="math inline">\(\omega\)</span>-无矛盾</h3><p>公式集 <span class="math inline">\(\Gamma\)</span> 称为 <strong><spanclass="math inline">\(\omega\)</span>-无矛盾</strong>，若不存在只含一个自由变元<span class="math inline">\(x\)</span> 的公式 <spanclass="math inline">\(p(x)\)</span>，使得下面两件事同时成立：</p><p>对每个自然数 <span class="math inline">\(n\)</span>， <spanclass="math display">\[\Gamma\vdash p(\bar n)\]</span></p><p>同时 <span class="math display">\[\Gamma\vdash \neg\forall x\,p(x)\]</span></p><p>直观地说，<spanclass="math inline">\(\omega\)</span>-无矛盾排除了这种情况：系统逐个证明<span class="math display">\[p(\bar0),p(\bar1),p(\bar2),\dots\]</span> 却又证明“并非所有 <span class="math inline">\(x\)</span>都满足 <span class="math inline">\(p(x)\)</span>”。</p><h3 id="命题-18.9.2-omega-无矛盾推出无矛盾">命题 18.9.2 <spanclass="math inline">\(\omega\)</span>-无矛盾推出无矛盾</h3><p>若 <span class="math inline">\(\Gamma\)</span> 是 <spanclass="math inline">\(\omega\)</span>-无矛盾的，则 <spanclass="math inline">\(\Gamma\)</span> 是无矛盾的。</p><details class="gray" data-header-exclude>    <summary><i class="fa-solid fa-chevron-right"></i>证明： 点击查看证明 </summary>    <div class='content markdown-body'>      <p>反证。若 <span class="math inline">\(\Gamma\)</span>有矛盾，则在经典逻辑中，任意公式都可由 <spanclass="math inline">\(\Gamma\)</span> 证明。</p><p>取任意公式 <span class="math inline">\(p(x)\)</span>。因为 <spanclass="math inline">\(\Gamma\)</span> 有矛盾，所以对每个 <spanclass="math inline">\(n\)</span>， <span class="math display">\[\Gamma\vdash p(\bar n)\]</span> 同时也有 <span class="math display">\[\Gamma\vdash \neg\forall x\,p(x)\]</span></p><p>这正是 <span class="math inline">\(\omega\)</span>-矛盾。</p><p>所以若 <span class="math inline">\(\Gamma\)</span> 是 <spanclass="math inline">\(\omega\)</span>-无矛盾，它必定无矛盾。</p>    </div>  </details><h3 id="定义-18.9.3-gödel-关系-w">定义 18.9.3 Gödel 关系 <spanclass="math inline">\(W\)</span></h3><p>为了构造自指句，定义二元关系 <span class="math inline">\(W\)</span>：<span class="math display">\[W=\{(n_1,n_2)\midn_1\text{ 是某公式 }p(x_1)\text{ 的 Gödel 数，且 }n_2\text{ 是 }p(\bar n_1)\text{ 从 }N\text{ 的证明的 Gödel 数}\}\]</span></p><p>换句话说，<span class="math inline">\(W(n_1,n_2)\)</span> 表示：</p><p>“<span class="math inline">\(n_2\)</span> 是把 <spanclass="math inline">\(n_1\)</span>所编码公式自代入后所得公式的一个证明。”</p><p>利用上一节的递归分析，可以写成： <span class="math display">\[(n_1,n_2)\in W\iffn_1\in \mathrm{FM}\wedge\mathrm{PRF}\bigl(n_2,\mathrm{Sub}(g(x_1),n_1,\mathrm{Num}(n_1))\bigr)\]</span></p><p>其中 <span class="math inline">\(g(x_1)\)</span> 是变元 <spanclass="math inline">\(x_1\)</span> 的 Gödel 数，<spanclass="math inline">\(\mathrm{Num}(n_1)\)</span> 是数码 <spanclass="math inline">\(\bar n_1\)</span> 的 Gödel 数。</p><p>因此 <span class="math inline">\(W\)</span>是递归关系。由递归关系可表示性，存在 <spanclass="math inline">\(K_N\)</span> 公式 <span class="math display">\[w(x_1,x_2)\]</span> 表示 <span class="math inline">\(W\)</span>。</p><h3 id="定义-18.9.4-gödel-句">定义 18.9.4 Gödel 句</h3><p>令 <span class="math display">\[p(x_1)\equiv \forall x_2\,\neg w(x_1,x_2)\]</span></p><p>设 <span class="math inline">\(p(x_1)\)</span> 的 Gödel 数为 <spanclass="math display">\[m=g(p(x_1))\]</span></p><p>把自己的 Gödel 数代入，得到闭式 <span class="math display">\[p(\bar m)\]</span> 即 <span class="math display">\[\forall x_2\,\neg w(\bar m,x_2)\]</span></p><p>它的直观含义是：</p><p>不存在自然数 <span class="math inline">\(x_2\)</span>，使 <spanclass="math inline">\(x_2\)</span> 是 <span class="math inline">\(p(\barm)\)</span> 从 <span class="math inline">\(N\)</span> 的证明的 Gödel数。</p><p>也就是说，<span class="math inline">\(p(\bar m)\)</span> 在说：</p><p><span class="math display">\[\text{“我在 }N\text{ 中不可证。”}\]</span></p><h3 id="定理-18.9.5-gödel-第一不完备性定理">定理 18.9.5 Gödel第一不完备性定理</h3><p>设 <span class="math inline">\(N\)</span> 是形式算术 <spanclass="math inline">\(K_N\)</span> 的算术公理集。</p><ol type="1"><li><p>若 <span class="math inline">\(N\)</span> 无矛盾，则 <spanclass="math display">\[N\not\vdash p(\bar m)\]</span></p></li><li><p>若 <span class="math inline">\(N\)</span> 是 <spanclass="math inline">\(\omega\)</span>-无矛盾的，则 <spanclass="math display">\[N\not\vdash \neg p(\bar m)\]</span></p></li></ol><p>因此若 <span class="math inline">\(N\)</span> 是 <spanclass="math inline">\(\omega\)</span>-无矛盾的，则 <spanclass="math inline">\(N\)</span> 不完备。</p><details class="gray" data-header-exclude>    <summary><i class="fa-solid fa-chevron-right"></i>证明： 点击查看证明 </summary>    <div class='content markdown-body'>      <p>先证明第一点。</p><p>反设 <span class="math display">\[N\vdash p(\bar m)\]</span> 设 <span class="math inline">\(n\)</span> 是这个证明的 Gödel数。由 <span class="math inline">\(W\)</span> 的定义， <spanclass="math display">\[(m,n)\in W\]</span> 因为 <span class="math inline">\(w\)</span> 表示 <spanclass="math inline">\(W\)</span>，所以 <span class="math display">\[N\vdash w(\bar m,\bar n)\]</span></p><p>另一方面，由 <span class="math display">\[p(\bar m)\equiv \forall x_2\,\neg w(\bar m,x_2)\]</span> 和 <span class="math display">\[N\vdash p(\bar m)\]</span> 可用全称实例化推出 <span class="math display">\[N\vdash \neg w(\bar m,\bar n)\]</span></p><p>于是 <span class="math inline">\(N\)</span> 同时证明 <spanclass="math display">\[w(\bar m,\bar n)\]</span> 和 <span class="math display">\[\neg w(\bar m,\bar n)\]</span> 与无矛盾性矛盾。</p><p>所以 <span class="math display">\[N\not\vdash p(\bar m)\]</span></p><p>再证明第二点。</p><p>反设 <span class="math display">\[N\vdash \neg p(\bar m)\]</span> 即 <span class="math display">\[N\vdash \neg\forall x_2\,\neg w(\bar m,x_2)\]</span></p><p>由第一点，<span class="math inline">\(p(\bar m)\)</span> 在 <spanclass="math inline">\(N\)</span> 中没有证明。所以对任意自然数 <spanclass="math inline">\(n\)</span>，<span class="math inline">\(n\)</span>都不是 <span class="math inline">\(p(\bar m)\)</span> 的证明的 Gödel数，即 <span class="math display">\[(m,n)\notin W\]</span> 因为 <span class="math inline">\(w\)</span> 表示 <spanclass="math inline">\(W\)</span>，所以对每个 <spanclass="math inline">\(n\)</span>， <span class="math display">\[N\vdash \neg w(\bar m,\bar n)\]</span></p><p>现在令 <span class="math display">\[q(x_2)\equiv \neg w(\bar m,x_2)\]</span> 则对每个 <span class="math inline">\(n\)</span>， <spanclass="math display">\[N\vdash q(\bar n)\]</span> 但同时 <span class="math display">\[N\vdash \neg\forall x_2\,q(x_2)\]</span></p><p>这说明 <span class="math inline">\(N\)</span> 是 <spanclass="math inline">\(\omega\)</span>-矛盾的。</p><p>所以若 <span class="math inline">\(N\)</span> 是 <spanclass="math inline">\(\omega\)</span>-无矛盾的，就不可能证明 <spanclass="math display">\[\neg p(\bar m)\]</span></p><p>因此 <span class="math inline">\(p(\bar m)\)</span>和它的否定都不可证，<span class="math inline">\(N\)</span> 不完备。</p>    </div>  </details><p>这个证明的关键如下：</p><p>第一，证明关系 <span class="math inline">\(\mathrm{PRF}\)</span>可递归分析</p><p>第二，递归关系可在 <span class="math inline">\(K_N\)</span>中表示</p><p>第三，把“我没有证明”这个语法事实算术化，并把自己的 Gödel 数代入</p><h2 id="定义-18.10-gödel-rosser-定理church-问题和第二不完备性">定义18.10 Gödel-Rosser 定理、Church 问题和第二不完备性</h2><p>Gödel 原始定理中，为了证明 <span class="math inline">\(\neg p(\barm)\)</span> 不可证，需要 <spanclass="math inline">\(\omega\)</span>-无矛盾，Rosser改进把这个条件降为普通无矛盾</p><h3 id="定义-18.10.1-rosser-关系">定义 18.10.1 Rosser 关系</h3><p>设 <span class="math inline">\(N^\ast\)</span> 是 <spanclass="math inline">\(N\)</span>的一个递归无矛盾扩张。定义两个关系：</p><p><span class="math display">\[W(n_1,n_2)\]</span> 表示 <span class="math inline">\(n_2\)</span> 是 <spanclass="math inline">\(p(\bar n_1)\)</span> 从 <spanclass="math inline">\(N^\ast\)</span> 的证明的 Gödel 数。</p><p><span class="math display">\[W^\ast(n_1,n_2)\]</span> 表示 <span class="math inline">\(n_2\)</span> 是 <spanclass="math inline">\(\neg p(\bar n_1)\)</span> 从 <spanclass="math inline">\(N^\ast\)</span> 的证明的 Gödel 数。</p><p>由于 <span class="math inline">\(N^\ast\)</span>的公理集合可递归判定，两个证明关系仍然是递归关系，故可由公式 <spanclass="math display">\[w(x_1,x_2),\qquad w^\ast(x_1,x_2)\]</span> 表示。</p><p>定义 Rosser 公式： <span class="math display">\[r(x_1)\equiv\forall x_2\left(w(x_1,x_2)\to\exists y(y\le x_2\wedge w^\ast(x_1,y))\right)\]</span></p><p>设 <span class="math display">\[m=g(r(x_1))\]</span> 则 Rosser 句为 <span class="math display">\[r(\bar m)\]</span></p><p>它的直观含义是：</p><p>若我有一个证明，那么我的否定也有一个不长于它的证明</p><h3 id="定理-18.10.2-gödel-rosser-定理">定理 18.10.2 Gödel-Rosser定理</h3><p>若 <span class="math inline">\(N^\ast\)</span> 是 <spanclass="math inline">\(N\)</span> 的递归无矛盾扩张，则 <spanclass="math inline">\(N^\ast\)</span> 不完备。更具体地说，存在闭式 <spanclass="math inline">\(r(\bar m)\)</span>，使得 <spanclass="math display">\[N^\ast\not\vdash r(\bar m)\]</span> 且 <span class="math display">\[N^\ast\not\vdash \neg r(\bar m)\]</span></p><details class="gray" data-header-exclude>    <summary><i class="fa-solid fa-chevron-right"></i>证明： 点击查看证明 </summary>    <div class='content markdown-body'>      <p>这里只写证明主线。</p><p>先反设 <span class="math display">\[N^\ast\vdash r(\bar m)\]</span> 取 <span class="math inline">\(r(\bar m)\)</span> 的最小证明Gödel 数为 <span class="math inline">\(n\)</span>。则 <spanclass="math display">\[W(m,n)\]</span> 成立，故 <span class="math display">\[N^\ast\vdash w(\bar m,\bar n)\]</span></p><p>由 Rosser 句 <span class="math display">\[r(\bar m)\]</span> 可推出 <span class="math display">\[\exists y(y\le \bar n\wedge w^\ast(\bar m,y))\]</span> 即存在一个不超过 <span class="math inline">\(n\)</span> 的<span class="math inline">\(\neg r(\bar m)\)</span> 的证明。</p><p>这与 <span class="math inline">\(N^\ast\)</span> 无矛盾矛盾。</p><p>再反设 <span class="math display">\[N^\ast\vdash \neg r(\bar m)\]</span> 取 <span class="math inline">\(\neg r(\bar m)\)</span>的最小证明 Gödel 数为 <span class="math inline">\(n\)</span>。则 <spanclass="math display">\[W^\ast(m,n)\]</span> 成立。</p><p>如果存在 <span class="math inline">\(y\le n\)</span> 使 <spanclass="math inline">\(W(m,y)\)</span> 成立，那么 <spanclass="math inline">\(N^\ast\)</span> 同时证明 <spanclass="math inline">\(r(\bar m)\)</span> 和 <spanclass="math inline">\(\neg r(\barm)\)</span>，与无矛盾性矛盾。因此对所有 <span class="math inline">\(y\len\)</span>，都有 <span class="math display">\[\neg W(m,y)\]</span> 这些都是有限多个具体事实，可在 <spanclass="math inline">\(N^\ast\)</span> 中逐个证明对应的 <spanclass="math display">\[\neg w(\bar m,\bar y)\]</span></p><p>结合 <span class="math display">\[W^\ast(m,n)\]</span> 可推出 Rosser 句本身 <span class="math display">\[r(\bar m)\]</span> 从而又与 <span class="math display">\[N^\ast\vdash \neg r(\bar m)\]</span> 矛盾。</p><p>所以 <span class="math inline">\(r(\bar m)\)</span>与其否定都不可证</p>    </div>  </details><p>Rosser改进的意义在于：普通无矛盾就足以推出形式算术的不可完备性，不需要 <spanclass="math inline">\(\omega\)</span>-无矛盾</p><h3 id="定义-18.10.3-church-问题">定义 18.10.3 Church 问题</h3><p>Church问题问的是：是否存在一个机械方法，能够判定任意一阶谓词逻辑公式是否为有效式，或者判定任意形式算术公式是否可证</p><p>不完备性和递归论结果给出的回答是否定的。</p><p>如果存在一个算法能判定所有足够强的形式算术定理，那么就可以把可证性作为递归关系表示出来，再构造一个说“我不可证”的句子。这个句子会导致与Gödel 构造同类的矛盾。</p><p>因此，足够强的形式算术不存在判定其全部定理的算法。</p><h3 id="定理-18.10.4-gödel-第二不完备性定理">定理 18.10.4 Gödel第二不完备性定理</h3><p>设 <span class="math display">\[\mathrm{Con}(N)\]</span> 是“<span class="math inline">\(N\)</span>无矛盾”的算术化公式。例如可以写成： <span class="math display">\[\neg\exists x\,\mathrm{PRF}(x,\ulcorner \bar0&#39;\approx\bar0\urcorner)\]</span> 表示不存在一个证明推出明显矛盾 <span class="math display">\[\bar0&#39;\approx\bar0\]</span></p><p>若 <span class="math inline">\(N\)</span> 无矛盾，则 <spanclass="math display">\[N\not\vdash \mathrm{Con}(N)\]</span></p><details class="gray" data-header-exclude>    <summary><i class="fa-solid fa-chevron-right"></i>证明： 点击查看证明 </summary>    <div class='content markdown-body'>      <p>这里只说明证明思想。</p><p>第一不完备性定理的证明可以在 <span class="math inline">\(N\)</span>内部形式化：在 <span class="math inline">\(N\)</span> 中可以证明 <spanclass="math display">\[\mathrm{Con}(N)\to p(\bar m)\]</span> 也就是说，如果 <span class="math inline">\(N\)</span>能证明自己无矛盾，那么 <span class="math inline">\(N\)</span> 就能证明Gödel 句。</p><p>若又有 <span class="math display">\[N\vdash \mathrm{Con}(N)\]</span> 则由 MP 得 <span class="math display">\[N\vdash p(\bar m)\]</span> 这与第一不完备性定理中“若 <spanclass="math inline">\(N\)</span> 无矛盾，则 <spanclass="math inline">\(N\not\vdash p(\bar m)\)</span>”矛盾。</p><p>所以在 <span class="math inline">\(N\)</span> 真正无矛盾时，<spanclass="math inline">\(N\)</span> 不能证明自身无矛盾。</p>    </div>  </details><p>第二不完备性不是说我们永远不能证明 <spanclass="math inline">\(N\)</span> 无矛盾，而是说不能在 <spanclass="math inline">\(N\)</span>自身内部完成这样的证明。较强系统可以证明较弱系统的无矛盾性，但足够强且无矛盾的系统不能完全依赖自身证明自身无矛盾</p><h2 id="定义-18.11-形式算术的不可判定性">定义 18.11形式算术的不可判定性</h2><p>一个形式系统称为可判定的，是指存在算法，对任意公式 <spanclass="math inline">\(p\)</span>，都能在有限步内判断 <spanclass="math display">\[N\vdash p\]</span> 是否成立</p><h3 id="定理-18.11.1-形式算术不可判定">定理 18.11.1形式算术不可判定</h3><p>若 <span class="math inline">\(N\)</span> 无矛盾，则 <spanclass="math inline">\(K_N\)</span>的定理集合不是递归集。也就是说，不存在算法判定任意 <spanclass="math inline">\(K_N\)</span> 公式是否可证</p><details class="gray" data-header-exclude>    <summary><i class="fa-solid fa-chevron-right"></i>证明： 点击查看证明 </summary>    <div class='content markdown-body'>      <p>证明思想如下</p><p>若 <span class="math inline">\(N\)</span> 的定理集是递归集，则“<spanclass="math inline">\(p\)</span> 在 <spanclass="math inline">\(N\)</span>中不可证”也是递归关系。由递归关系可表示性，可以在 <spanclass="math inline">\(K_N\)</span> 内部构造一个公式表示 <spanclass="math display">\[\text{“编号为 }x\text{ 的公式不可证”}\]</span></p><p>再把这个公式自己的 Gödel 数代入，就得到一个更强的自指句 <spanclass="math inline">\(G\)</span>： <span class="math display">\[G\leftrightarrow \text{“}G\text{ 不可证”}\]</span> 并且由于“不可证”此时被假定为递归关系，若 <spanclass="math inline">\(G\)</span> 真的不可证，<spanclass="math inline">\(N\)</span> 反而能证明表示这个事实的公式，从而证明<span class="math inline">\(G\)</span>。</p><p>若 <span class="math inline">\(N\vdash G\)</span>，则与 <spanclass="math inline">\(G\)</span> 的含义和无矛盾性冲突。</p><p>若 <span class="math inline">\(N\not\vdashG\)</span>，由于不可证性关系被假定可判定并可表示，<spanclass="math inline">\(N\)</span> 又能证明 <spanclass="math inline">\(G\)</span>。</p><p>两种情况都矛盾。</p><p>所以 <span class="math inline">\(N\)</span> 的定理集不可判定。</p>    </div>  </details><p>形式算术的定理集虽然不可判定，但它是递归可枚举的：枚举所有证明，输出每个证明的末公式即可。这种“可枚举但不可判定”的现象是递归论和数理逻辑中的核心现象</p><h2 id="定义-18.12-递归可枚举集与算术集">定义 18.12递归可枚举集与算术集</h2><p>第 4.3 节继续讨论递归可枚举集和算术可定义性。</p><h3 id="定义-18.12.1-递归可枚举集">定义 18.12.1 递归可枚举集</h3><p>集合 <span class="math inline">\(A\subseteq\mathbb N\)</span>称为<strong>递归可枚举集</strong>，若存在递归关系 <spanclass="math inline">\(R(x,y)\)</span>，使得 <spanclass="math display">\[x\in A\iff \exists y\,R(x,y)\]</span></p><p>等价地说，可以用机械过程列出 <span class="math inline">\(A\)</span>的所有元素。若 <span class="math inline">\(x\inA\)</span>，它最终会被列出；若 <span class="math inline">\(x\notinA\)</span>，过程可能永远不会给出否定回答。</p><h3 id="命题-18.12.2-递归集与递归可枚举集">命题 18.12.2递归集与递归可枚举集</h3><p>集合 <span class="math inline">\(A\)</span> 是递归集，当且仅当 <spanclass="math inline">\(A\)</span> 和它的补集 <spanclass="math inline">\(\overline A\)</span> 都是递归可枚举集。</p><details class="gray" data-header-exclude>    <summary><i class="fa-solid fa-chevron-right"></i>证明： 点击查看证明 </summary>    <div class='content markdown-body'>      <p>若 <span class="math inline">\(A\)</span> 是递归集，则可以依次检查<span class="math display">\[0,1,2,\dots\]</span> 是否属于 <span class="math inline">\(A\)</span>。属于就列入<span class="math inline">\(A\)</span>，不属于就列入 <spanclass="math inline">\(\overline A\)</span>，所以二者都递归可枚举。</p><p>反过来，若 <span class="math inline">\(A\)</span> 和 <spanclass="math inline">\(\overline A\)</span> 都递归可枚举。给定 <spanclass="math inline">\(x\)</span>，同时运行两个枚举过程：</p><ul><li>一个枚举 <span class="math inline">\(A\)</span></li><li>一个枚举 <span class="math inline">\(\overline A\)</span></li></ul><p>由于 <span class="math inline">\(x\)</span>必在其中一个集合中，它最终会出现在其中一个枚举里。若出现在 <spanclass="math inline">\(A\)</span> 的枚举中，判定 <spanclass="math inline">\(x\in A\)</span>；若出现在 <spanclass="math inline">\(\overline A\)</span> 的枚举中，判定 <spanclass="math inline">\(x\notin A\)</span>。</p><p>所以 <span class="math inline">\(A\)</span> 递归。</p>    </div>  </details><h3 id="命题-18.12.3-可证公式集递归可枚举">命题 18.12.3可证公式集递归可枚举</h3><p><span class="math inline">\(K_N\)</span> 中可证公式的 Gödel 数集合<span class="math display">\[\mathrm{PA}=\{y\mid \exists x\,\mathrm{PRF}(x,y)\}\]</span> 是递归可枚举集。</p><details class="gray" data-header-exclude>    <summary><i class="fa-solid fa-chevron-right"></i>证明： 点击查看证明 </summary>    <div class='content markdown-body'>      <p>因为 <span class="math inline">\(\mathrm{PRF}(x,y)\)</span>是递归关系，所以 <span class="math display">\[y\in \mathrm{PA}\iff\exists x\,\mathrm{PRF}(x,y)\]</span> 正是递归可枚举集的定义形式。</p><p>也可以直接理解为：枚举所有自然数作为候选证明编码，凡是合法证明，就输出它证明的公式。</p>    </div>  </details><h3 id="定义-18.12.4-算术集">定义 18.12.4 算术集</h3><p>集合 <span class="math inline">\(A\subseteq\mathbb N\)</span>称为<strong>算术集</strong>，若存在 <spanclass="math inline">\(K_N\)</span> 语言中的公式 <spanclass="math display">\[p(x)\]</span> 使得对每个自然数 <span class="math inline">\(n\)</span>：<span class="math display">\[n\in A\iff\mathbb N\models p(\bar n)\]</span></p><p>这里的 <span class="math inline">\(\mathbb N\models\)</span>表示在标准自然数模型中为真。注意这不是说 <spanclass="math inline">\(N\vdash p(\barn)\)</span>，而是说公式在标准模型中为真。</p><h3 id="定理-18.12.5-递归可枚举集都是算术集">定理 18.12.5递归可枚举集都是算术集</h3><p>若 <span class="math inline">\(A\)</span> 是递归可枚举集，则 <spanclass="math inline">\(A\)</span> 是算术集。</p><details class="gray" data-header-exclude>    <summary><i class="fa-solid fa-chevron-right"></i>证明： 点击查看证明 </summary>    <div class='content markdown-body'>      <p>因为 <span class="math inline">\(A\)</span> 递归可枚举，存在递归关系<span class="math inline">\(R(x,y)\)</span>，使 <spanclass="math display">\[x\in A\iff \exists y\,R(x,y)\]</span></p><p>递归关系可在 <span class="math inline">\(K_N\)</span>中表示，所以存在公式 <span class="math display">\[r(x,y)\]</span> 表示 <span class="math inline">\(R\)</span>。</p><p>令 <span class="math display">\[p(x)\equiv \exists y\,r(x,y)\]</span></p><p>则在标准模型中， <span class="math display">\[\mathbb N\models p(\bar n)\]</span> 当且仅当存在自然数 <span class="math inline">\(m\)</span> 使<span class="math display">\[R(n,m)\]</span> 成立，也就是 <span class="math display">\[n\in A\]</span></p><p>所以 <span class="math inline">\(A\)</span> 是算术集。</p>    </div>  </details><h3 id="定理-18.12.6-真公式集不是算术可定义的">定理 18.12.6真公式集不是算术可定义的</h3><p>设 <span class="math display">\[\mathrm{TRUE}=\{\ulcorner p\urcorner\mid p\text{ 是在标准自然数模型中为真的闭式}\}\]</span> 则 <span class="math inline">\(\mathrm{TRUE}\)</span>不是算术集。</p><p>这就是 Tarski 真理不可定义定理在算术中的形式。</p><details class="gray" data-header-exclude>    <summary><i class="fa-solid fa-chevron-right"></i>证明： 点击查看证明 </summary>    <div class='content markdown-body'>      <p>反证。假设存在公式 <span class="math display">\[T(x)\]</span> 定义真理，即对每个闭式 <spanclass="math inline">\(p\)</span>， <span class="math display">\[\mathbb N\models T(\ulcorner p\urcorner)\iff\mathbb N\models p\]</span></p><p>考虑公式 <span class="math display">\[\neg T(x)\]</span> 用自指构造得到闭式 <spanclass="math inline">\(G\)</span>，满足 <span class="math display">\[\mathbb N\modelsG\leftrightarrow \neg T(\ulcorner G\urcorner)\]</span></p><p>若 <span class="math inline">\(G\)</span> 真，则由 <spanclass="math inline">\(T\)</span> 定义真理，<spanclass="math inline">\(T(\ulcorner G\urcorner)\)</span>真；但由上式，<span class="math inline">\(\neg T(\ulcornerG\urcorner)\)</span> 真，矛盾。</p><p>若 <span class="math inline">\(G\)</span> 假，则 <spanclass="math inline">\(T(\ulcorner G\urcorner)\)</span> 假，于是 <spanclass="math inline">\(\neg T(\ulcorner G\urcorner)\)</span> 真，由上式得<span class="math inline">\(G\)</span> 真，也矛盾。</p><p>所以标准算术真理不能由算术公式定义。</p>    </div>  </details><p>这个结论比不完备性还强：不完备性说某些真句在给定系统中不可证；真理不可定义定理说，所有真算术句构成的集合甚至不能在算术内部整体定义出来</p><h2 id="定义-18.13-turing-机与-turing-论题">定义 18.13 Turing 机与Turing 论题</h2><p>递归函数是一种形式化的可计算性定义，Turing机是另一种形式化定义。二者最终给出同一类可计算函数</p><h3 id="定义-18.13.1-turing-机">定义 18.13.1 Turing 机</h3><p>一台 Turing 机包含：</p><ul><li>一条向两端无限延伸的纸带，纸带分成格子</li><li>有限符号表，其中包含空白符号</li><li>有限状态集合，其中有初始状态和停机状态</li><li>一个读写头，每次扫描一个格子</li><li>有限条指令</li></ul><p>每条指令规定：在当前状态和当前读到的符号下，</p><ul><li>写入一个符号</li><li>向左或向右移动一格，或保持不动</li><li>进入一个新状态</li></ul><p>机器的某一时刻完整情况称为<strong>瞬时描述</strong>，包括：</p><ul><li>当前状态</li><li>纸带上非空白部分的内容</li><li>读写头所在位置</li></ul><p>一次计算就是瞬时描述的有限或无限序列</p><h3 id="定义-18.13.2-turing-可计算函数">定义 18.13.2 Turing可计算函数</h3><p>函数 <span class="math display">\[f:\mathbb N^k\to\mathbb N\]</span> 称为 Turing 可计算，若存在一台 Turing 机，对每个输入 <spanclass="math inline">\(\vec n\)</span>：</p><ul><li>若 <span class="math inline">\(f(\vec n)\)</span>有定义，则机器最终停机并输出 <span class="math inline">\(f(\vecn)\)</span></li><li>若 <span class="math inline">\(f(\vec n)\)</span>无定义，则机器不停机</li></ul><p>若只讨论处处有定义的函数，则机器对每个输入都必须停机</p><h3 id="定理-18.13.3-turing-可计算函数等于部分递归函数">定理 18.13.3Turing 可计算函数等于部分递归函数</h3><p>一个数论函数是 Turing 可计算的，当且仅当它是部分递归函数</p><details class="gray" data-header-exclude>    <summary><i class="fa-solid fa-chevron-right"></i>证明： 点击查看证明 </summary>    <div class='content markdown-body'>      <p>这里只写证明思路。</p><p>先看从递归函数到 Turing 机。</p><p>零函数、后继函数、投影函数都可以直接由 Turing机计算。函数复合对应机器程序的串接：先计算内部函数，再把输出交给外部函数。原始递归对应有限循环。<spanclass="math inline">\(\mu\)</span> 算子对应依次搜索 <spanclass="math display">\[0,1,2,\dots\]</span> 直到找到满足条件的最小值。若找不到，就不停机</p><p>所以部分递归函数都 Turing 可计算。</p><p>再看从 Turing 机到递归函数。</p><p>把一台机器的瞬时描述编码成自然数，包括状态、纸带内容和读写头位置。机器从一个瞬时描述到下一个瞬时描述的转移是有限表控制的，所以是递归函数</p><p>设 <span class="math display">\[C_M(x,t)\]</span> 表示机器 <span class="math inline">\(M\)</span> 在输入 <spanclass="math inline">\(x\)</span> 上运行 <spanclass="math inline">\(t\)</span>步后的瞬时描述编码。这个函数可由原始递归定义</p><p>“<span class="math inline">\(M\)</span> 在 <spanclass="math inline">\(t\)</span>步时停机”是递归关系。若机器最终停机，则停机时间可写成 <spanclass="math display">\[\mu t[\text{$M$ 在 $t$ 步停机}]\]</span> 输出也可由停机瞬时描述递归读出</p><p>因此 Turing 机计算的函数是部分递归函数</p>    </div>  </details><h3 id="定义-18.13.4-turing-论题">定义 18.13.4 Turing 论题</h3><p>Turing 论题，也称 Church-Turing 论题：</p><p>一切直观上能机械计算的函数，正是 Turing可计算函数，也就是递归函数</p><h3 id="定理-18.13.5-停机问题不可判定">定理 18.13.5停机问题不可判定</h3><p>不存在一台 Turing 机能判定任意机器在任意输入上是否停机。</p><details class="gray" data-header-exclude>    <summary><i class="fa-solid fa-chevron-right"></i>证明： 点击查看证明 </summary>    <div class='content markdown-body'>      <p>反证。假设存在判定机 <spanclass="math inline">\(D\)</span>，对任意机器 <spanclass="math inline">\(M\)</span> 和输入 <spanclass="math inline">\(x\)</span>： <span class="math display">\[D(M,x)=\begin{cases}1, &amp; M(x)\text{ 停机}\\0, &amp; M(x)\text{ 不停机}\end{cases}\]</span></p><p>构造新机器 <span class="math inline">\(E\)</span>，输入一台机器 <spanclass="math inline">\(M\)</span> 的编码：</p><ul><li>若 <span class="math inline">\(D(M,M)=1\)</span>，即 <spanclass="math inline">\(M(M)\)</span> 停机，则 <spanclass="math inline">\(E(M)\)</span> 进入死循环</li><li>若 <span class="math inline">\(D(M,M)=0\)</span>，即 <spanclass="math inline">\(M(M)\)</span> 不停机，则 <spanclass="math inline">\(E(M)\)</span> 立即停机</li></ul><p>现在考察 <span class="math inline">\(E(E)\)</span></p><p>若 <span class="math inline">\(E(E)\)</span> 停机，则按 <spanclass="math inline">\(E\)</span> 的定义，必须有 <spanclass="math inline">\(D(E,E)=0\)</span>，即 <spanclass="math inline">\(E(E)\)</span> 不停机，矛盾</p><p>若 <span class="math inline">\(E(E)\)</span> 不停机，则按 <spanclass="math inline">\(E\)</span> 的定义，必须有 <spanclass="math inline">\(D(E,E)=1\)</span>，即 <spanclass="math inline">\(E(E)\)</span> 停机，矛盾</p><p>所以停机判定机不存在</p>    </div>  </details><h3 id="推论-18.13.6-存在递归可枚举但非递归的集合">推论 18.13.6存在递归可枚举但非递归的集合</h3><p>停机集合 <span class="math display">\[K=\{\langle M,x\rangle\mid M(x)\text{ 停机}\}\]</span> 是递归可枚举集，但不是递归集</p><details class="gray" data-header-exclude>    <summary><i class="fa-solid fa-chevron-right"></i>证明： 点击查看证明 </summary>    <div class='content markdown-body'>      <p><span class="math inline">\(K\)</span>递归可枚举：枚举所有机器输入对，并逐步模拟运行。只要某个机器在某个输入上停机，就输出对应编码</p><p><span class="math inline">\(K\)</span> 不是递归集：若 <spanclass="math inline">\(K\)</span> 是递归集，就存在算法判定任意 <spanclass="math inline">\(M(x)\)</span>是否停机，这正是停机问题的判定算法，与停机问题不可判定矛盾</p>    </div>  </details><h2 id="人与机器">18.14 人与机器</h2><p>从 Turing 论题看，只要一种过程能被明确写成机械规则，它就应当可以由Turing 机模拟。因此“机械可计算”有一个非常稳定的数学刻画</p><p>但 Gödel不完备性说明：对足够强的形式算术系统来说，总存在该系统无法判定的算术句。若一个人站在系统外部承认系统无矛盾，就能看出Gödel 句在标准模型中为真；但系统内部不能证明它</p><p>这不应被简单理解成“人一定超过机器”。更准确的说法是：任何固定的、递归给出的形式系统，只要它足够强且无矛盾，就不能穷尽全部算术真理。若把人的推理也完全固定为某个递归形式系统，那么同样会受到不完备性限制</p>]]></content>
    
    
    <summary type="html">形式算术、递归函数与不完备性</summary>
    
    
    
    <category term="代数结构" scheme="https://exdoubled.github.io/categories/%E4%BB%A3%E6%95%B0%E7%BB%93%E6%9E%84/"/>
    
    
    <category term="笔记" scheme="https://exdoubled.github.io/tags/%E7%AC%94%E8%AE%B0/"/>
    
    <category term="离散数学" scheme="https://exdoubled.github.io/tags/%E7%A6%BB%E6%95%A3%E6%95%B0%E5%AD%A6/"/>
    
    <category term="代数结构与数理逻辑" scheme="https://exdoubled.github.io/tags/%E4%BB%A3%E6%95%B0%E7%BB%93%E6%9E%84%E4%B8%8E%E6%95%B0%E7%90%86%E9%80%BB%E8%BE%91/"/>
    
  </entry>
  
  <entry>
    <title>代数结构与数理逻辑学习笔记7</title>
    <link href="https://exdoubled.github.io/lssx/ls17/"/>
    <id>https://exdoubled.github.io/lssx/ls17/</id>
    <published>2026-06-13T05:00:00.000Z</published>
    <updated>2026-06-14T15:32:54.942Z</updated>
    
    <content type="html"><![CDATA[<h2 id="定义-17.1-论说演绎与一致性">定义 17.1 论说、演绎与一致性</h2><p>先讨论自然语言中的<strong>论说</strong></p><p>一个论说通常由若干前提和一个结论组成： <span class="math display">\[\frac{P_1,\ P_2,\ \dots,\ P_n}{C}\]</span></p><p>其中 <span class="math inline">\(P_1,\dots,P_n\)</span> 是前提，<spanclass="math inline">\(C\)</span> 是结论</p><p>论说的好坏不取决于结论事实上是否为真，而取决于前提是否足以支持结论</p><p>若不存在前提全真而结论假的情形，则称该论说是<strong>有效的</strong></p><p>若一组命题不可能同时为真，则称这组命题是<strong>不一致的</strong>；否则称为<strong>一致的</strong></p><p>因此：</p><ul><li>有效性关心“前提真时结论是否必真”</li><li>一致性关心“一组命题是否能同时为真”</li><li>反证法本质上把有效性转化为不一致性</li></ul><h3 id="定义-17.1.1-论说的好坏">定义 17.1.1 论说的好坏</h3><p>论说的评价可以分成两个层次：</p><ul><li>前提是否可接受</li><li>从前提到结论的过渡是否可靠</li></ul><p>逻辑主要研究第二个问题，也就是说，逻辑并不首先判断前提事实上是否真，而是判断：<span class="math display">\[\text{如果前提都真，结论是否必真}\]</span></p><p>例如：</p><p><span class="math display">\[\frac{\text{所有人都会死},\quad\text{苏格拉底是人}}{\text{苏格拉底会死}}\]</span></p><p>这个论说在形式上有效。即使有人争论某个前提，逻辑形式仍然是可靠的</p><p>而 <span class="math display">\[\frac{\text{所有猫都是动物},\quad \text{狗是动物}}{\text{狗是猫}}\]</span></p><p>前提可以都真，但结论假，所以形式无效</p><p>形式化以后，上面两个论说分别对应：</p><p><span class="math display">\[\frac{\forall x(H(x)\to M(x)),\ H(s)}{M(s)}\]</span></p><p>和</p><p><span class="math display">\[\frac{\forall x(C(x)\to A(x)),\ A(d)}{C(d)}\]</span></p><p>第二个形式犯的是“肯定后件”的错误</p><h3 id="定义-17.1.2-可演绎性可证性和独立性">定义 17.1.2可演绎性、可证性和独立性</h3><p>设 <span class="math inline">\(T\)</span> 是一组命题或公式，<spanclass="math inline">\(A\)</span> 是一个命题或公式</p><p>若 <span class="math inline">\(A\)</span> 可以从 <spanclass="math inline">\(T\)</span> 中通过允许的推理规则推出，则称 <spanclass="math inline">\(A\)</span> 从 <spanclass="math inline">\(T\)</span> <strong>可演绎</strong>，记作 <spanclass="math display">\[T\vdash A\]</span></p><p>若没有前提也能推出 <span class="math inline">\(A\)</span>，则称 <spanclass="math inline">\(A\)</span> <strong>可证</strong>： <spanclass="math display">\[\vdash A\]</span></p><p>若 <span class="math display">\[T\not\vdash A\]</span> 且 <span class="math display">\[T\not\vdash \neg A\]</span> 则称 <span class="math inline">\(A\)</span> 相对于 <spanclass="math inline">\(T\)</span> 是<strong>独立的</strong></p><p>独立性表示：在给定前提或公理系统 <spanclass="math inline">\(T\)</span> 中，既不能证明 <spanclass="math inline">\(A\)</span>，也不能证明其否定</p><h3 id="定义-17.1.3-一致性与爆炸原则">定义 17.1.3 一致性与爆炸原则</h3><p>若一个系统或公式集 <span class="math inline">\(T\)</span>能推出某个公式及其否定： <span class="math display">\[T\vdash A,\qquad T\vdash\neg A\]</span> 则称 <span class="math inline">\(T\)</span><strong>不一致</strong></p><p>若不存在这样的 <span class="math inline">\(A\)</span>，则称 <spanclass="math inline">\(T\)</span> <strong>一致</strong></p><p>在经典逻辑中，不一致会导致<strong>爆炸原则</strong>： <spanclass="math display">\[A,\neg A\vdash B\]</span> 其中 <span class="math inline">\(B\)</span> 是任意公式</p><details class="gray" data-header-exclude>    <summary><i class="fa-solid fa-chevron-right"></i>证明： 点击查看证明 </summary>    <div class='content markdown-body'>      <p>由 <span class="math inline">\(A\)</span> 可得 <spanclass="math display">\[A\vee B\]</span> 这是析取引入。</p><p>又有 <span class="math inline">\(\neg A\)</span></p><p>由析取三段论： <span class="math display">\[A\vee B,\quad \neg A\vdash B\]</span></p><p>因此从矛盾 <span class="math inline">\(A,\neg A\)</span> 可以推出任意<span class="math inline">\(B\)</span></p>    </div>  </details><h3 id="定理-17.1.1-有效性与不一致性">定理 17.1.1 有效性与不一致性</h3><p>设 <span class="math inline">\(P_1,\dots,P_n,C\)</span> 是命题，则<span class="math display">\[P_1,\dots,P_n\models C\]</span> 当且仅当 <span class="math display">\[P_1,\dots,P_n,\neg C\]</span> 不一致</p><details class="gray" data-header-exclude>    <summary><i class="fa-solid fa-chevron-right"></i>证明： 点击查看证明 </summary>    <div class='content markdown-body'>      <p>若 <span class="math inline">\(P_1,\dots,P_n\modelsC\)</span>，则不存在一种情形使所有 <spanclass="math inline">\(P_i\)</span> 为真而 <spanclass="math inline">\(C\)</span> 为假</p><p>但 <span class="math inline">\(C\)</span> 为假正是 <spanclass="math inline">\(\neg C\)</span> 为真，所以不存在一种情形使 <spanclass="math display">\[P_1,\dots,P_n,\neg C\]</span> 同时为真，即它们不一致</p><p>反过来，若 <span class="math inline">\(P_1,\dots,P_n,\neg C\)</span>不一致，则不可能所有 <span class="math inline">\(P_i\)</span> 为真且<span class="math inline">\(\neg C\)</span> 为真</p><p>也就是说，只要所有 <span class="math inline">\(P_i\)</span>为真，<span class="math inline">\(C\)</span> 就不能为假，只能为真</p><p>所以 <span class="math display">\[P_1,\dots,P_n\models C\]</span></p>    </div>  </details><h2 id="定义-17.2-形式语言与形式系统">定义 17.2 形式语言与形式系统</h2><p>形式逻辑首先要把自然语言论说翻译成形式语言，然后在形式系统中推演</p><p>一个形式系统通常包含：</p><ul><li>字母表：允许使用哪些符号</li><li>形成规则：哪些符号串是合式公式</li><li>公理或初始公式：哪些公式可直接使用</li><li>推理规则：怎样从已有公式推出新公式</li></ul><p>命题逻辑中，字母表一般包括：</p><ul><li>命题变元：<spanclass="math inline">\(p,q,r,p_1,p_2,\dots\)</span></li><li>联结词：<spanclass="math inline">\(\neg,\wedge,\vee,\to,\leftrightarrow\)</span></li><li>括号和辅助符号</li></ul><p>一阶逻辑中，还要加入：</p><ul><li>个体变元与个体常元</li><li>函数符号</li><li>谓词符号</li><li>等词 <span class="math inline">\(=\)</span></li><li>量词 <span class="math inline">\(\forall,\exists\)</span></li></ul><h3 id="定义-17.2.1-对象语言与元语言">定义 17.2.1 对象语言与元语言</h3><p>对象语言是被研究的形式语言。例如命题逻辑中的 <spanclass="math display">\[(p\to q)\wedge p\]</span> 是一条对象语言公式</p><p>元语言是用来谈论对象语言的语言，例如：</p><ul><li>“<span class="math inline">\(A\)</span> 是公式”</li><li>“<span class="math inline">\(A\)</span> 是重言式”</li><li>“<span class="math inline">\(\Gamma\vdash A\)</span>”</li><li>“<span class="math inline">\(\Gamma\models A\)</span>”</li></ul><p>常见记号区别如下：</p><table><thead><tr><th style="text-align: center;">记号</th><th style="text-align: center;">层次</th><th style="text-align: left;">含义</th></tr></thead><tbody><tr><td style="text-align: center;"><spanclass="math inline">\(\to\)</span></td><td style="text-align: center;">对象语言</td><td style="text-align: left;">公式内部的蕴含联结词</td></tr><tr><td style="text-align: center;"><spanclass="math inline">\(\Rightarrow\)</span></td><td style="text-align: center;">元语言</td><td style="text-align: left;">“推出”“因此”</td></tr><tr><td style="text-align: center;"><spanclass="math inline">\(\vdash\)</span></td><td style="text-align: center;">元语言</td><td style="text-align: left;">形式可证</td></tr><tr><td style="text-align: center;"><spanclass="math inline">\(\models\)</span></td><td style="text-align: center;">元语言</td><td style="text-align: left;">语义有效或语义后承</td></tr><tr><td style="text-align: center;"><spanclass="math inline">\(\equiv\)</span></td><td style="text-align: center;">元语言</td><td style="text-align: left;">两个公式等值</td></tr></tbody></table><p><span class="math display">\[A\to B\]</span> 是一个公式；而 <span class="math display">\[A\models B\]</span> 不是公式，而是说“所有使 <span class="math inline">\(A\)</span>为真的解释也使 <span class="math inline">\(B\)</span> 为真”</p><h3 id="定义-17.2.2-推演定理与可证性">定义 17.2.2推演、定理与可证性</h3><p>在形式系统中，从公式集 <span class="math inline">\(\Gamma\)</span>推出公式 <span class="math inline">\(A\)</span>，记作 <spanclass="math display">\[\Gamma\vdash A\]</span></p><p>含义是：存在一个有限公式序列，每一步要么是 <spanclass="math inline">\(\Gamma\)</span>中的前提，要么是公理，要么由前面若干步按推理规则得到，最后一步是 <spanclass="math inline">\(A\)</span></p><p>若 <span class="math display">\[\varnothing\vdash A\]</span> 则称 <span class="math inline">\(A\)</span>是系统中的<strong>定理</strong>，通常简写为 <spanclass="math display">\[\vdash A\]</span></p><p>这里要区分：</p><ul><li><span class="math inline">\(\Gamma\models A\)</span>表示语义后承，即所有解释下前提真则结论真</li><li><span class="math inline">\(\Gamma\vdash A\)</span>表示形式可证，即有一个形式推演序列</li></ul><p>可靠性和完全性正是研究这两个关系之间的联系</p><h3 id="定义-17.2.3-形式系统的四个问题">定义 17.2.3形式系统的四个问题</h3><p><strong>可靠性</strong>： <span class="math display">\[\Gamma\vdash A \Rightarrow \Gamma\models A\]</span></p><p><strong>完全性</strong>： <span class="math display">\[\Gamma\models A \Rightarrow \Gamma\vdash A\]</span></p><p><strong>一致性</strong>： 是否存在 <spanclass="math inline">\(A\)</span> 使得 <span class="math display">\[\Gamma\vdash A,\qquad \Gamma\vdash\neg A\]</span></p><p><strong>可判定性</strong>： 是否存在机械过程，对任意公式 <spanclass="math inline">\(A\)</span>，都能在有限步内判断 <spanclass="math inline">\(A\)</span> 是否可证或是否有效</p><p>命题逻辑是可判定的，因为可以列真值表</p><p>一阶逻辑有效性不是一般可判定的，但它是半可判定的：若一个公式有效，原则上可以通过枚举证明最终找到证明；若无效，则不一定能有限步停机确认</p><h2 id="定义-17.3-可靠性与完全性">定义 17.3 可靠性与完全性</h2><p>一个形式系统若满足： <span class="math display">\[\Gamma\vdash A \Rightarrow \Gamma\models A\]</span> 则称它是<strong>可靠的</strong></p><p>可靠性说明：系统中能证明出来的结论不会在语义上出错</p><p>一个形式系统若满足： <span class="math display">\[\Gamma\models A \Rightarrow \Gamma\vdash A\]</span> 则称它是<strong>完全的</strong></p><p>完全性说明：所有语义上有效的结论，都能在系统中被形式证明出来</p><p>如果同时有可靠性和完全性，则形式证明与语义有效性完全吻合： <spanclass="math display">\[\Gamma\vdash A \iff \Gamma\models A\]</span></p><h3 id="定理-17.3.1-可靠性的证明思路">定理 17.3.1 可靠性的证明思路</h3><p>若形式系统的每条公理都是有效式，并且每条推理规则都保持有效性，则该形式系统可靠</p><details class="gray" data-header-exclude>    <summary><i class="fa-solid fa-chevron-right"></i>证明： 点击查看证明 </summary>    <div class='content markdown-body'>      <p>设 <span class="math display">\[\Gamma\vdash A\]</span> 即存在一个从 <span class="math inline">\(\Gamma\)</span> 到<span class="math inline">\(A\)</span> 的有限推演序列</p><p>对推演序列的长度作归纳。</p><p>若某一步是前提，则它在所有满足 <spanclass="math inline">\(\Gamma\)</span> 的解释下为真</p><p>若某一步是公理，则由假设，公理是有效式，在所有解释下为真</p><p>若某一步由前面若干步通过推理规则得到，由归纳假设，前面若干步在所有满足<span class="math inline">\(\Gamma\)</span>的解释下为真；又推理规则保持有效性，所以这一步也为真</p><p>因此推演最后一步 <span class="math inline">\(A\)</span> 在所有满足<span class="math inline">\(\Gamma\)</span> 的解释下为真，即 <spanclass="math display">\[\Gamma\models A\]</span></p>    </div>  </details><h3 id="注记-17.3.2-完全性的意义">注记 17.3.2 完全性的意义</h3><p>可靠性通常只要逐条检查公理和规则；完全性要说明：只要一个公式在语义上不可反驳，就一定能在形式系统中找到证明</p><p>在命题逻辑中，完全性可以借助真值表或主范式证明</p><p>在一阶逻辑中，完全性对应 Gödel 完全性定理，其内容是： <spanclass="math display">\[\Gamma\models A \Rightarrow \Gamma\vdash A\]</span></p><p>这和后面的 Gödel不完全性定理不是同一个命题。完全性定理说的是“一阶逻辑本身的推理系统足够强”；不完全性定理说的是“足够表达算术的具体理论不可能同时满足某些理想性质”。</p><h2 id="定义-17.4-命题演算的语义概念">定义 17.4 命题演算的语义概念</h2><p>设 <span class="math inline">\(A\)</span> 是命题公式，<spanclass="math inline">\(v\)</span> 是真值指派。</p><p>若 <span class="math inline">\(v(A)=1\)</span>，称 <spanclass="math inline">\(v\)</span> <strong>满足</strong> <spanclass="math inline">\(A\)</span>，记作 <span class="math display">\[v\models A\]</span></p><p>若 <span class="math inline">\(v\)</span> 满足公式集 <spanclass="math inline">\(\Gamma\)</span> 中所有公式，记作 <spanclass="math display">\[v\models\Gamma\]</span></p><p>若存在某个 <span class="math inline">\(v\)</span> 满足 <spanclass="math inline">\(\Gamma\)</span>，则称 <spanclass="math inline">\(\Gamma\)</span> <strong>可满足</strong></p><p>若没有任何 <span class="math inline">\(v\)</span> 满足 <spanclass="math inline">\(\Gamma\)</span>，则称 <spanclass="math inline">\(\Gamma\)</span> <strong>不可满足</strong></p><p>若每个满足 <span class="math inline">\(\Gamma\)</span>的真值指派都满足 <span class="math inline">\(A\)</span>，则称 <spanclass="math inline">\(A\)</span> 是 <spanclass="math inline">\(\Gamma\)</span> 的<strong>语义后承</strong>：<span class="math display">\[\Gamma\models A\]</span></p><p>特别地，若 <span class="math inline">\(\varnothing\modelsA\)</span>，则称 <span class="math inline">\(A\)</span>是<strong>重言式</strong>或<strong>有效式</strong></p><h3 id="定理-17.4.1-语义后承与不可满足">定理 17.4.1语义后承与不可满足</h3><p><span class="math display">\[\Gamma\models A\iff\Gamma\cup\{\neg A\}\text{ 不可满足}\]</span></p><details class="gray" data-header-exclude>    <summary><i class="fa-solid fa-chevron-right"></i>证明： 点击查看证明 </summary>    <div class='content markdown-body'>      <p>若 <span class="math inline">\(\Gamma\models A\)</span>，则任意满足<span class="math inline">\(\Gamma\)</span> 的真值指派都满足 <spanclass="math inline">\(A\)</span></p><p>因此不可能存在真值指派同时满足 <spanclass="math inline">\(\Gamma\)</span> 和 <spanclass="math inline">\(\neg A\)</span></p><p>所以 <span class="math inline">\(\Gamma\cup\{\neg A\}\)</span>不可满足</p><p>反过来，若 <span class="math inline">\(\Gamma\cup\{\neg A\}\)</span>不可满足，则不存在真值指派满足 <spanclass="math inline">\(\Gamma\)</span> 且使 <spanclass="math inline">\(A\)</span> 为假</p><p>于是每个满足 <span class="math inline">\(\Gamma\)</span>的真值指派都满足 <span class="math inline">\(A\)</span>，即 <spanclass="math display">\[\Gamma\models A\]</span></p>    </div>  </details><h3 id="定理-17.4.2-命题逻辑的可判定性">定理 17.4.2命题逻辑的可判定性</h3><p>命题公式 <span class="math inline">\(A\)</span>是否为重言式是可判定的</p><details class="gray" data-header-exclude>    <summary><i class="fa-solid fa-chevron-right"></i>证明： 点击查看证明 </summary>    <div class='content markdown-body'>      <p>若 <span class="math inline">\(A\)</span> 中只出现 <spanclass="math display">\[p_1,\dots,p_n\]</span> 这 <span class="math inline">\(n\)</span> 个命题变元，则 <spanclass="math inline">\(A\)</span> 的真值只由这 <spanclass="math inline">\(n\)</span> 个变元的取值决定</p><p>共有 <span class="math inline">\(2^n\)</span> 种赋值</p><p>逐一计算 <span class="math inline">\(A\)</span> 在这 <spanclass="math inline">\(2^n\)</span> 个赋值下的真值：</p><ul><li>若每行都为 <span class="math inline">\(1\)</span>，则 <spanclass="math inline">\(A\)</span> 是重言式</li><li>若至少一行为 <span class="math inline">\(0\)</span>，则 <spanclass="math inline">\(A\)</span> 不是重言式</li></ul><p>这个过程有限步结束，所以重言式判定是可判定的</p>    </div>  </details><h2 id="定义-17.5-费奇式推演">定义 17.5 费奇式推演</h2><p>讲义把命题演算看成从“语义判断”进入“形式推演”的阶段：</p><ul><li>语义判断使用 <spanclass="math inline">\(\models\)</span>，依靠真值指派</li><li>形式推演使用 <spanclass="math inline">\(\vdash\)</span>，依靠推理规则</li><li>可靠性证明 <span class="math inline">\(\vdash\)</span>不会推出语义错误的结论</li><li>完全性证明所有语义有效的结论都能被规则推出</li></ul><p>费奇式推演的特点是用缩进或竖线表示临时假设的作用范围。</p><p>典型结构如下：</p><p><span class="math display">\[\begin{array}{ll}1. &amp; A \quad \text{假设}\\2. &amp; \cdots\\3. &amp; B\\4. &amp; A\to B \quad \to\text{ 引入}\end{array}\]</span></p><p>这表示：若在临时假设 <span class="math inline">\(A\)</span> 下推出了<span class="math inline">\(B\)</span>，则可退出该假设，得到 <spanclass="math inline">\(A\to B\)</span></p><p>费奇式推演常用规则包括：</p><ul><li>合取引入：由 <span class="math inline">\(A\)</span> 与 <spanclass="math inline">\(B\)</span> 得 <span class="math inline">\(A\wedgeB\)</span></li><li>合取消去：由 <span class="math inline">\(A\wedge B\)</span> 得 <spanclass="math inline">\(A\)</span>，也可得 <spanclass="math inline">\(B\)</span></li><li>析取引入：由 <span class="math inline">\(A\)</span> 得 <spanclass="math inline">\(A\vee B\)</span></li><li>蕴含消去：由 <span class="math inline">\(A\to B\)</span> 与 <spanclass="math inline">\(A\)</span> 得 <spanclass="math inline">\(B\)</span></li><li>蕴含引入：若假设 <span class="math inline">\(A\)</span> 能推出 <spanclass="math inline">\(B\)</span>，则得 <span class="math inline">\(A\toB\)</span></li><li>否定引入：若假设 <span class="math inline">\(A\)</span>导出矛盾，则得 <span class="math inline">\(\neg A\)</span></li><li>否定消去：由 <span class="math inline">\(A\)</span> 与 <spanclass="math inline">\(\neg A\)</span> 得矛盾</li></ul><h3 id="定义-17.5.1-子证明">定义 17.5.1 子证明</h3><p>费奇式推演的核心是<strong>子证明</strong></p><p>子证明以一个临时假设开始： <span class="math display">\[[A]\]</span></p><p>在该假设作用范围内推出若干公式。退出子证明后，可以使用引入规则把整段子证明包装成一个公式</p><p>最常见的是蕴含引入： <span class="math display">\[\begin{array}{c}[A]\\\vdots\\B\\\hlineA\to B\end{array}\]</span></p><p>其意思是：如果在假设 <span class="math inline">\(A\)</span>的条件下可以推出 <spanclass="math inline">\(B\)</span>，那么无条件地可以推出 <spanclass="math inline">\(A\to B\)</span></p><p>否定引入类似： <span class="math display">\[\begin{array}{c}[A]\\\vdots\\\bot\\\hline\neg A\end{array}\]</span></p><p>其中 <span class="math inline">\(\bot\)</span> 表示矛盾</p><h3 id="定义-17.5.2-析取消去">定义 17.5.2 析取消去</h3><p>费奇式推演中，析取消去常写为分情况讨论：</p><p>若有 <span class="math display">\[A\vee B\]</span> 并且：</p><ul><li>在假设 <span class="math inline">\(A\)</span> 下能推出 <spanclass="math inline">\(C\)</span></li><li>在假设 <span class="math inline">\(B\)</span> 下也能推出 <spanclass="math inline">\(C\)</span></li></ul><p>则可推出 <span class="math inline">\(C\)</span>。</p><p>形式为： <span class="math display">\[\begin{array}{c}A\vee B\\[A]\quad \vdots\quad C\\[B]\quad \vdots\quad C\\\hlineC\end{array}\]</span></p><p>这对应自然语言中的“分情况讨论”：无论析取式哪一边成立，结论 <spanclass="math inline">\(C\)</span> 都成立</p><h3 id="例-17.5.3-费奇式证明假言三段论">例 17.5.3费奇式证明假言三段论</h3><p>证明： <span class="math display">\[A\to B,\ B\to C\vdash A\to C\]</span></p><p>推演：</p><table><thead><tr><th style="text-align: center;">步骤</th><th style="text-align: center;">公式</th><th style="text-align: center;">理由</th></tr></thead><tbody><tr><td style="text-align: center;">1</td><td style="text-align: center;"><span class="math inline">\(A\toB\)</span></td><td style="text-align: center;">前提</td></tr><tr><td style="text-align: center;">2</td><td style="text-align: center;"><span class="math inline">\(B\toC\)</span></td><td style="text-align: center;">前提</td></tr><tr><td style="text-align: center;">3</td><td style="text-align: center;"><spanclass="math inline">\(A\)</span></td><td style="text-align: center;">临时假设</td></tr><tr><td style="text-align: center;">4</td><td style="text-align: center;"><spanclass="math inline">\(B\)</span></td><td style="text-align: center;">1,3 蕴含消去</td></tr><tr><td style="text-align: center;">5</td><td style="text-align: center;"><spanclass="math inline">\(C\)</span></td><td style="text-align: center;">2,4 蕴含消去</td></tr><tr><td style="text-align: center;">6</td><td style="text-align: center;"><span class="math inline">\(A\toC\)</span></td><td style="text-align: center;">3-5 蕴含引入</td></tr></tbody></table><p>其中第 3 步的假设只在子证明中有效；第 6步退出该假设，得到条件命题</p><h3 id="例-17.5.4-费奇式证明反证法">例 17.5.4 费奇式证明反证法</h3><p>证明： <span class="math display">\[A\to B,\ A\to\neg B\vdash \neg A\]</span></p><p>推演：</p><table><thead><tr><th style="text-align: center;">步骤</th><th style="text-align: center;">公式</th><th style="text-align: center;">理由</th></tr></thead><tbody><tr><td style="text-align: center;">1</td><td style="text-align: center;"><span class="math inline">\(A\toB\)</span></td><td style="text-align: center;">前提</td></tr><tr><td style="text-align: center;">2</td><td style="text-align: center;"><span class="math inline">\(A\to\negB\)</span></td><td style="text-align: center;">前提</td></tr><tr><td style="text-align: center;">3</td><td style="text-align: center;"><spanclass="math inline">\(A\)</span></td><td style="text-align: center;">临时假设</td></tr><tr><td style="text-align: center;">4</td><td style="text-align: center;"><spanclass="math inline">\(B\)</span></td><td style="text-align: center;">1,3 蕴含消去</td></tr><tr><td style="text-align: center;">5</td><td style="text-align: center;"><span class="math inline">\(\negB\)</span></td><td style="text-align: center;">2,3 蕴含消去</td></tr><tr><td style="text-align: center;">6</td><td style="text-align: center;"><spanclass="math inline">\(\bot\)</span></td><td style="text-align: center;">4,5 矛盾</td></tr><tr><td style="text-align: center;">7</td><td style="text-align: center;"><span class="math inline">\(\negA\)</span></td><td style="text-align: center;">3-6 否定引入</td></tr></tbody></table><p>这正是反证思路：假设 <spanclass="math inline">\(A\)</span>，推出矛盾，于是得到 <spanclass="math inline">\(\neg A\)</span></p><h2 id="定义-17.6-命题演算中的极大一致集">定义 17.6命题演算中的极大一致集</h2><p>设 <span class="math inline">\(\Gamma\)</span> 是一组命题公式</p><p>若 <span class="math inline">\(\Gamma\)</span> 不能推出矛盾，则称<span class="math inline">\(\Gamma\)</span>是<strong>一致的</strong></p><p>若 <span class="math inline">\(\Gamma\)</span> 一致，并且对任意公式<span class="math inline">\(A\)</span>，只要 <spanclass="math inline">\(A\notin\Gamma\)</span>，则 <spanclass="math display">\[\Gamma\cup\{A\}\]</span> 不一致，则称 <span class="math inline">\(\Gamma\)</span>是<strong>极大一致集</strong></p><p>极大一致集可以理解为“已经尽可能完整而又不矛盾的一套命题选择”</p><h3 id="定理-17.6.1-lindenbaum-扩张思想">定理 17.6.1 Lindenbaum扩张思想</h3><p>任意一致的命题公式集 <span class="math inline">\(\Gamma\)</span>都可以扩张为某个极大一致集 <spanclass="math inline">\(\Delta\)</span>，使得 <spanclass="math display">\[\Gamma\subseteq\Delta\]</span></p><details class="gray" data-header-exclude>    <summary><i class="fa-solid fa-chevron-right"></i>证明： 点击查看证明 </summary>    <div class='content markdown-body'>      <p>把所有命题公式排成一个序列： <span class="math display">\[A_1,A_2,A_3,\dots\]</span></p><p>从 <span class="math inline">\(\Gamma_0=\Gamma\)</span>开始递归构造：</p><p>若 <span class="math display">\[\Gamma_n\cup\{A_{n+1}\}\]</span> 一致，则令 <span class="math display">\[\Gamma_{n+1}=\Gamma_n\cup\{A_{n+1}\}\]</span></p><p>否则令 <span class="math display">\[\Gamma_{n+1}=\Gamma_n\cup\{\neg A_{n+1}\}\]</span></p><p>最后令 <span class="math display">\[\Delta=\bigcup_{n=0}^{\infty}\Gamma_n\]</span></p><p>每一步都保持一致；若加入 <span class="math inline">\(A_{n+1}\)</span>会不一致，则加入 <span class="math inline">\(\neg A_{n+1}\)</span>保持一致，否则原来的 <span class="math inline">\(\Gamma_n\)</span>会已经推出 <span class="math inline">\(A_{n+1}\)</span> 与 <spanclass="math inline">\(\neg A_{n+1}\)</span> 的矛盾</p><p>构造后，对任意公式 <span class="math inline">\(A_i\)</span>，恰有<span class="math inline">\(A_i\)</span> 或 <spanclass="math inline">\(\neg A_i\)</span> 被加入 <spanclass="math inline">\(\Delta\)</span></p><p>因此 <span class="math inline">\(\Delta\)</span> 是极大一致集</p>    </div>  </details><h3 id="定理-17.6.2-极大一致集的基本性质">定理 17.6.2极大一致集的基本性质</h3><p>若 <span class="math inline">\(\Delta\)</span>是极大一致集，则对任意命题公式 <spanclass="math inline">\(A,B\)</span>：</p><ul><li>恰有一个在 <span class="math inline">\(\Delta\)</span> 中：<spanclass="math inline">\(A\)</span> 或 <span class="math inline">\(\negA\)</span></li><li><span class="math inline">\(A\wedge B\in\Delta\)</span> 当且仅当<span class="math inline">\(A\in\Delta\)</span> 且 <spanclass="math inline">\(B\in\Delta\)</span></li><li><span class="math inline">\(A\vee B\in\Delta\)</span> 当且仅当 <spanclass="math inline">\(A\in\Delta\)</span> 或 <spanclass="math inline">\(B\in\Delta\)</span></li><li><span class="math inline">\(A\to B\in\Delta\)</span> 当且仅当 <spanclass="math inline">\(A\notin\Delta\)</span> 或 <spanclass="math inline">\(B\in\Delta\)</span></li></ul><details class="gray" data-header-exclude>    <summary><i class="fa-solid fa-chevron-right"></i>证明： 点击查看证明 </summary>    <div class='content markdown-body'>      <p>这里只说明思路</p><p>由于 <span class="math inline">\(\Delta\)</span> 极大一致，对任意公式<span class="math inline">\(A\)</span>，若 <spanclass="math inline">\(A\notin\Delta\)</span>，则加入 <spanclass="math inline">\(A\)</span> 会导致不一致</p><p>这意味着从 <span class="math inline">\(\Delta\cup\{A\}\)</span>可推出矛盾，因此从 <span class="math inline">\(\Delta\)</span> 可推出<span class="math inline">\(\neg A\)</span>，于是 <spanclass="math inline">\(\neg A\)</span> 必须属于极大一致集</p><p>同时 <span class="math inline">\(A\)</span> 与 <spanclass="math inline">\(\neg A\)</span> 不能都在 <spanclass="math inline">\(\Delta\)</span> 中，否则 <spanclass="math inline">\(\Delta\)</span> 本身不一致</p><p>所以 <span class="math inline">\(A\)</span> 与 <spanclass="math inline">\(\neg A\)</span> 恰有一个在 <spanclass="math inline">\(\Delta\)</span> 中</p><p>其他三条由对应联结词的引入和消去规则推出。例如 <spanclass="math inline">\(A\wedge B\)</span> 在 <spanclass="math inline">\(\Delta\)</span> 中，则由合取消去得 <spanclass="math inline">\(A,B\)</span> 都应在 <spanclass="math inline">\(\Delta\)</span> 中；反过来若 <spanclass="math inline">\(A,B\)</span> 都在 <spanclass="math inline">\(\Delta\)</span> 中，则由合取引入得 <spanclass="math inline">\(A\wedge B\)</span> 在 <spanclass="math inline">\(\Delta\)</span> 中</p><p>析取和蕴含情形类似</p>    </div>  </details><h3 id="定理-17.6.3-命题演算完全性的证明骨架">定理 17.6.3命题演算完全性的证明骨架</h3><p>若 <span class="math display">\[\Gamma\models A\]</span> 则 <span class="math display">\[\Gamma\vdash A\]</span></p><details class="gray" data-header-exclude>    <summary><i class="fa-solid fa-chevron-right"></i>证明： 点击查看证明 </summary>    <div class='content markdown-body'>      <p>证明逆否命题</p><p>假设 <span class="math display">\[\Gamma\not\vdash A\]</span></p><p>则 <span class="math display">\[\Gamma\cup\{\neg A\}\]</span> 是一致的；否则若它不一致，就可由反证法推出 <spanclass="math inline">\(\Gamma\vdash A\)</span></p><p>由 Lindenbaum 扩张思想，把 <span class="math display">\[\Gamma\cup\{\neg A\}\]</span> 扩张为极大一致集 <spanclass="math inline">\(\Delta\)</span></p><p>根据极大一致集的性质，定义真值指派 <spanclass="math inline">\(v_\Delta\)</span>： <span class="math display">\[v_\Delta(p)=1 \iff p\in\Delta\]</span></p><p>再由极大一致集关于联结词的性质可得： <span class="math display">\[v_\Delta(B)=1 \iff B\in\Delta\]</span> 对任意命题公式 <span class="math inline">\(B\)</span> 成立</p><p>由于 <span class="math display">\[\Gamma\subseteq\Delta\]</span> 所以 <span class="math inline">\(v_\Delta\)</span> 满足 <spanclass="math inline">\(\Gamma\)</span>。</p><p>又因为 <span class="math display">\[\neg A\in\Delta\]</span> 所以 <span class="math inline">\(v_\Delta(A)=0\)</span></p><p>因此存在一个满足 <span class="math inline">\(\Gamma\)</span> 但不满足<span class="math inline">\(A\)</span> 的赋值，说明 <spanclass="math display">\[\Gamma\not\models A\]</span></p><p>于是逆否命题成立，即 <span class="math display">\[\Gamma\models A\Rightarrow\Gamma\vdash A\]</span></p>    </div>  </details><h2 id="定义-17.7-谓词演算中的量词规则">定义 17.7谓词演算中的量词规则</h2><p>在一阶谓词演算中，除了命题联结词规则，还需要量词规则</p><ul><li>先处理没有等词的一阶推演，核心是量词引入和量词消去</li><li>再加入等词，得到带等词的谓词逻辑</li><li>最后讨论谓词演算的可靠性、完全性以及由完全性导出的元逻辑结果</li></ul><p>量词规则比命题规则更容易出错，因为它们涉及“任意对象”“某个对象”和变量是否自由。</p><h3 id="定义-17.7.1-自由变元约束变元和可替换性">定义 17.7.1自由变元、约束变元和可替换性</h3><p>在公式 <span class="math display">\[\forall xA\]</span> 或 <span class="math display">\[\exists xA\]</span> 中，量词后面的 <span class="math inline">\(A\)</span>是量词的辖域</p><p>若变元 <span class="math inline">\(x\)</span> 的出现位于某个 <spanclass="math inline">\(\forall x\)</span> 或 <spanclass="math inline">\(\exists x\)</span>的辖域内，则这次出现是<strong>约束的</strong>；否则是<strong>自由的</strong></p><p>例如： <span class="math display">\[\forall x(P(x)\to Q(y))\]</span> 中，<span class="math inline">\(x\)</span> 是约束变元，<spanclass="math inline">\(y\)</span> 是自由变元</p><p>在量词规则中，经常需要把项 <span class="math inline">\(t\)</span>代入公式 <span class="math inline">\(A(x)\)</span> 中的自由 <spanclass="math inline">\(x\)</span></p><p>这种代入必须避免<strong>变量捕获</strong></p><p>例如： <span class="math display">\[\forall yR(x,y)\]</span> 中，把 <span class="math inline">\(y\)</span> 代入 <spanclass="math inline">\(x\)</span> 会得到 <span class="math display">\[\forall yR(y,y)\]</span></p><p>原来作为自由项出现的 <span class="math inline">\(y\)</span> 被 <spanclass="math inline">\(\forall y\)</span> 捕获，因此这个代入不合法</p><p>所以全称消去和存在引入中都要附加条件：项 <spanclass="math inline">\(t\)</span> 对 <spanclass="math inline">\(x\)</span> 可自由代入</p><p>常见规则包括：</p><p><strong>全称消去</strong>： <span class="math display">\[\frac{\forall xA(x)}{A(t)}\]</span> 其中 <span class="math inline">\(t\)</span> 可以代入 <spanclass="math inline">\(x\)</span>，且不能造成变量捕获</p><p><strong>存在引入</strong>： <span class="math display">\[\frac{A(t)}{\exists xA(x)}\]</span></p><p><strong>全称引入</strong>： 若在不依赖于关于 <spanclass="math inline">\(x\)</span> 的特殊假设的情况下推出 <spanclass="math inline">\(A(x)\)</span>，则可推出 <spanclass="math display">\[\forall xA(x)\]</span></p><p><strong>存在消去</strong>： 若由 <span class="math inline">\(\existsxA(x)\)</span> 可临时取一个新的对象 <spanclass="math inline">\(c\)</span> 满足 <spanclass="math inline">\(A(c)\)</span>，并在不依赖 <spanclass="math inline">\(c\)</span> 特殊性的情况下推出 <spanclass="math inline">\(B\)</span>，则可推出 <spanclass="math inline">\(B\)</span></p><p>存在消去中的“新对象”条件很重要：<spanclass="math inline">\(c\)</span>不能已经出现在未解除的前提或结论中，否则会把“存在某个对象”误用成“某个指定对象”</p><h3 id="定理-17.7.2-全称消去可靠">定理 17.7.2 全称消去可靠</h3><p>若 <span class="math display">\[\forall xA(x)\]</span> 成立，则对任意可代入项 <spanclass="math inline">\(t\)</span>， <span class="math display">\[A(t)\]</span> 成立。</p><details class="gray" data-header-exclude>    <summary><i class="fa-solid fa-chevron-right"></i>证明： 点击查看证明 </summary>    <div class='content markdown-body'>      <p><span class="math inline">\(\forall xA(x)\)</span>的含义是：论域中任意对象都满足 <spanclass="math inline">\(A\)</span></p><p>项 <span class="math inline">\(t\)</span>在给定解释下表示论域中的某个对象</p><p>既然任意对象都满足 <span class="math inline">\(A\)</span>，那么 <spanclass="math inline">\(t\)</span> 所表示的对象也满足 <spanclass="math inline">\(A\)</span></p><p>所以可推出 <span class="math display">\[A(t)\]</span></p><p>可代入条件保证代入没有改变公式中变量的绑定关系。</p>    </div>  </details><h3 id="定理-17.7.3-存在消去的新常元条件">定理 17.7.3存在消去的新常元条件</h3><p>从 <span class="math display">\[\exists xA(x)\]</span> 使用存在消去时，引入的新常元 <spanclass="math inline">\(c\)</span>不能出现在当前未解除的前提或最终结论中</p><details class="gray" data-header-exclude>    <summary><i class="fa-solid fa-chevron-right"></i>证明： 点击查看证明 </summary>    <div class='content markdown-body'>      <p><span class="math inline">\(\exists xA(x)\)</span>只说明“至少有一个对象满足 <spanclass="math inline">\(A\)</span>”，没有说明是哪一个对象</p><p>因此临时引入的 <span class="math inline">\(c\)</span>只能表示“某个任意选取的见证对象”</p><p>如果 <span class="math inline">\(c\)</span>已经出现在前提或结论中，就可能把“存在某个对象”误读为“这个指定对象”</p><p>例如从 <span class="math display">\[\exists xP(x)\]</span> 不能推出 <span class="math display">\[P(a)\]</span> 因为 <span class="math inline">\(a\)</span> 可能不是那个满足<span class="math inline">\(P\)</span> 的对象。</p><p>所以存在消去要求 <span class="math inline">\(c\)</span>是新常元，并且最终推出的结论不能依赖 <spanclass="math inline">\(c\)</span> 的特殊名字</p>    </div>  </details><h3 id="例-17.7.4-量词推演">例 17.7.4 量词推演</h3><p>证明： <span class="math display">\[\forall x(P(x)\to Q(x)),\ \forall xP(x)\vdash \forall xQ(x)\]</span></p><p>推演：</p><table><thead><tr><th style="text-align: center;">步骤</th><th style="text-align: center;">公式</th><th style="text-align: center;">理由</th></tr></thead><tbody><tr><td style="text-align: center;">1</td><td style="text-align: center;"><span class="math inline">\(\forallx(P(x)\to Q(x))\)</span></td><td style="text-align: center;">前提</td></tr><tr><td style="text-align: center;">2</td><td style="text-align: center;"><span class="math inline">\(\forallxP(x)\)</span></td><td style="text-align: center;">前提</td></tr><tr><td style="text-align: center;">3</td><td style="text-align: center;"><span class="math inline">\(P(a)\toQ(a)\)</span></td><td style="text-align: center;">1 全称消去</td></tr><tr><td style="text-align: center;">4</td><td style="text-align: center;"><spanclass="math inline">\(P(a)\)</span></td><td style="text-align: center;">2 全称消去</td></tr><tr><td style="text-align: center;">5</td><td style="text-align: center;"><spanclass="math inline">\(Q(a)\)</span></td><td style="text-align: center;">3,4 蕴含消去</td></tr><tr><td style="text-align: center;">6</td><td style="text-align: center;"><span class="math inline">\(\forallxQ(x)\)</span></td><td style="text-align: center;">5 全称引入</td></tr></tbody></table><p>这里 <span class="math inline">\(a\)</span> 是任意对象，没有使用关于<span class="math inline">\(a\)</span>的特殊前提，所以最后可以全称引入</p><h3 id="例-17.7.5-存在消去示例">例 17.7.5 存在消去示例</h3><p>证明： <span class="math display">\[\exists x(P(x)\wedge Q(x))\vdash \exists xP(x)\]</span></p><p>推演：</p><table><thead><tr><th style="text-align: center;">步骤</th><th style="text-align: center;">公式</th><th style="text-align: center;">理由</th></tr></thead><tbody><tr><td style="text-align: center;">1</td><td style="text-align: center;"><span class="math inline">\(\existsx(P(x)\wedge Q(x))\)</span></td><td style="text-align: center;">前提</td></tr><tr><td style="text-align: center;">2</td><td style="text-align: center;"><span class="math inline">\(P(c)\wedgeQ(c)\)</span></td><td style="text-align: center;">存在消去临时假设，<spanclass="math inline">\(c\)</span> 新</td></tr><tr><td style="text-align: center;">3</td><td style="text-align: center;"><spanclass="math inline">\(P(c)\)</span></td><td style="text-align: center;">2 合取消去</td></tr><tr><td style="text-align: center;">4</td><td style="text-align: center;"><span class="math inline">\(\existsxP(x)\)</span></td><td style="text-align: center;">3 存在引入</td></tr><tr><td style="text-align: center;">5</td><td style="text-align: center;"><span class="math inline">\(\existsxP(x)\)</span></td><td style="text-align: center;">1,2-4 存在消去</td></tr></tbody></table><p>第 5 步退出关于 <span class="math inline">\(c\)</span>的临时假设，因为最终结论 <span class="math inline">\(\existsxP(x)\)</span> 不含 <span class="math inline">\(c\)</span></p><h2 id="定义-17.8-等词与相等规则">定义 17.8 等词与相等规则</h2><p>带等词的一阶逻辑把 <span class="math inline">\(=\)</span>作为逻辑符号</p><p>等词通常满足：</p><p><strong>自同一性</strong>： <span class="math display">\[x=x\]</span></p><p><strong>替换性</strong>： 若 <span class="math display">\[x=y\]</span> 且公式 <span class="math inline">\(A(x)\)</span>成立，则可推出 <span class="math display">\[A(y)\]</span></p><p>也就是说，相等对象在任意性质和关系中可以相互替换</p><h3 id="例-17.8.1-等词推理">例 17.8.1 等词推理</h3><p>由 <span class="math display">\[a=b,\quad P(a)\]</span> 可推出 <span class="math display">\[P(b)\]</span></p><p>这是替换性的直接应用。</p><p>若有函数符号 <span class="math inline">\(f\)</span>，由 <spanclass="math display">\[a=b\]</span> 也可推出 <span class="math display">\[f(a)=f(b)\]</span></p><p>这表示函数对相等对象取相等值。</p><h3 id="定理-17.8.2-等词的同余性质">定理 17.8.2 等词的同余性质</h3><p>若 <span class="math display">\[a_1=b_1,\dots,a_n=b_n\]</span> 则对任意 <span class="math inline">\(n\)</span> 元函数符号<span class="math inline">\(f\)</span>， <span class="math display">\[f(a_1,\dots,a_n)=f(b_1,\dots,b_n)\]</span></p><p>对任意 <span class="math inline">\(n\)</span> 元谓词符号 <spanclass="math inline">\(R\)</span>， <span class="math display">\[R(a_1,\dots,a_n)\leftrightarrow R(b_1,\dots,b_n)\]</span></p><details class="gray" data-header-exclude>    <summary><i class="fa-solid fa-chevron-right"></i>证明： 点击查看证明 </summary>    <div class='content markdown-body'>      <p>这是相等替换性的多次应用</p><p>函数情形：从 <span class="math inline">\(a_1=b_1\)</span> 可把 <spanclass="math inline">\(f(a_1,a_2,\dots,a_n)\)</span> 中第一处替换为 <spanclass="math inline">\(b_1\)</span>；再用 <spanclass="math inline">\(a_2=b_2\)</span> 替换第二处，如此继续，最后得到<span class="math display">\[f(a_1,\dots,a_n)=f(b_1,\dots,b_n)\]</span></p><p>谓词情形同理。若 <span class="math display">\[R(a_1,\dots,a_n)\]</span> 成立，则逐项用 <span class="math inline">\(a_i=b_i\)</span>替换，得 <span class="math display">\[R(b_1,\dots,b_n)\]</span></p><p>反向由 <span class="math inline">\(b_i=a_i\)</span> 得到。</p>    </div>  </details><h2 id="定义-17.9-henkin-思想">定义 17.9 Henkin 思想</h2><p>若有公式 <span class="math display">\[\exists xA(x)\]</span> 则引入一个新的常元 <spanclass="math inline">\(c\)</span>，并加入公式 <spanclass="math display">\[\exists xA(x)\to A(c)\]</span></p><p>这个 <span class="math inline">\(c\)</span>称为该存在公式的<strong>见证常元</strong>。</p><p>直观上，如果存在某个对象满足 <spanclass="math inline">\(A\)</span>，就给这样一个对象起一个新名字。</p><h3 id="定理-17.9.1-henkin-见证的作用">定理 17.9.1 Henkin见证的作用</h3><p>加入足够多的 Henkin 见证后，极大一致理论可以构造出一个模型</p><details class="gray" data-header-exclude>    <summary><i class="fa-solid fa-chevron-right"></i>证明： 点击查看证明 </summary>    <div class='content markdown-body'>      <p>这里只写证明思路</p><p>设 <span class="math inline">\(\Delta\)</span> 是带 Henkin见证的极大一致理论</p><p>用语言中的闭项构成模型的对象</p><p>若有等词，则把可证明相等的闭项看成同一个对象</p><p>谓词解释为： <span class="math display">\[R(t_1,\dots,t_n)\text{ 在模型中为真}\]</span> 当且仅当 <span class="math display">\[R(t_1,\dots,t_n)\in\Delta\]</span></p><p>函数解释按项的构造给出。</p><p>关键在于存在量词：若 <span class="math display">\[\exists xA(x)\in\Delta\]</span> 则 Henkin 公理保证某个见证常元 <spanclass="math inline">\(c\)</span> 满足 <span class="math display">\[A(c)\in\Delta\]</span></p><p>于是存在命题在构造出的模型中确实有见证</p><p>这样就能证明 <span class="math inline">\(\Delta\)</span>中每个公式都在该模型中为真，从而得到模型存在</p>    </div>  </details><h2 id="定义-17.10-紧致性">定义 17.10 紧致性</h2><p>一阶逻辑的<strong>紧致性定理</strong>说：</p><p>若公式集 <span class="math inline">\(\Gamma\)</span>的每个有限子集都有模型，则 <span class="math inline">\(\Gamma\)</span>有模型</p><p>等价地：</p><p>若 <span class="math display">\[\Gamma\models A\]</span> 则存在有限子集 <span class="math display">\[\Gamma_0\subseteq\Gamma\]</span> 使得 <span class="math display">\[\Gamma_0\models A\]</span></p><p>紧致性说明：一阶逻辑的语义后承虽然可以有无限多前提，但真正推出某个公式时，只需要有限多个前提</p><h3 id="定理-17.10.1-紧致性来自完全性">定理 17.10.1紧致性来自完全性</h3><p>若一阶逻辑可靠且完全，则可推出紧致性</p><details class="gray" data-header-exclude>    <summary><i class="fa-solid fa-chevron-right"></i>证明： 点击查看证明 </summary>    <div class='content markdown-body'>      <p>设 <span class="math inline">\(\Gamma\models A\)</span></p><p>由完全性， <span class="math display">\[\Gamma\vdash A\]</span></p><p>形式证明是有限长的，所以证明中实际用到的前提只可能是 <spanclass="math inline">\(\Gamma\)</span>中有限多个公式。设这些公式组成有限集 <spanclass="math inline">\(\Gamma_0\)</span>。</p><p>则 <span class="math display">\[\Gamma_0\vdash A\]</span></p><p>由可靠性， <span class="math display">\[\Gamma_0\models A\]</span></p><p>所以只需有限前提即可推出 <span class="math inline">\(A\)</span></p><p>令 <span class="math inline">\(A\)</span>为矛盾式，也得到“若每个有限子集可满足，则整体可满足”的形式</p>    </div>  </details><h3 id="例-17.10.2-紧致性的应用">例 17.10.2 紧致性的应用</h3><p>存在一个一阶理论，它有任意大的有限模型约束，并因此有无限模型</p><p>设语言中有无穷多个常元 <span class="math display">\[c_0,c_1,c_2,\dots\]</span></p><p>加入公式： <span class="math display">\[c_i\neq c_j\qquad (i\neq j)\]</span></p><p>任取有限多个这样的公式，只涉及有限多个常元，可以用一个足够大的有限集合解释这些常元，使它们两两不同</p><p>所以每个有限子集都有模型</p><p>由紧致性，整个公式集有模型</p><p>在这个模型中，所有 <span class="math inline">\(c_i\)</span>解释为两两不同的对象，因此模型必须是无限的</p><p>这个例子说明，一阶逻辑可以通过无限理论“逼出”无限模型，但单个一阶句子通常不能表达“论域无限”</p><h2 id="定义-17.11-löwenheim-skolem-定理">定义 17.11 Löwenheim-Skolem定理</h2><p>Löwenheim-Skolem 定理讨论一阶理论模型的大小</p><p>若一个可数一阶理论有无限模型，则它有可数无限模型</p><p>更一般地，一阶理论如果有足够大的无限模型，往往也有较小基数的模型</p><p>这个定理体现了一阶逻辑表达能力的限制：一阶句子通常不能唯一刻画某个无限结构的基数</p><h3 id="注记-17.11.1-skolem-悖论">注记 17.11.1 Skolem 悖论</h3><p>集合论可以在一阶语言中谈论不可数集合；但 Löwenheim-Skolem定理又说明，如果这个一阶理论有模型，则在适当条件下它有可数模型</p><p>于是出现所谓 Skolem 悖论：可数模型内部也可能“认为”某个集合不可数</p><p>关键在于：</p><ul><li>“可数模型”是从模型外部看的大小</li><li>“不可数”是模型内部没有某个双射对象</li></ul><p>内外视角不同，所以并不真正矛盾</p><h3 id="例-17.11.2-一阶逻辑不能刻画恰为标准自然数模型">例 17.11.2一阶逻辑不能刻画“恰为标准自然数模型”</h3><p>一阶 Peano 算术有标准模型 <spanclass="math inline">\(\mathbb{N}\)</span></p><p>但由 Löwenheim-Skolem 和紧致性相关思想可知，一阶 Peano算术也有非标准模型</p><p>这些模型中除了 <span class="math display">\[0,1,2,\dots\]</span> 所对应的标准部分，还存在“非标准自然数”</p><p>因此，一阶公理可以刻画许多算术性质，却不能把自然数结构唯一固定到只剩标准模型</p><h2 id="定义-17.12-可定义性">定义 17.12 可定义性</h2><p>在一个结构中，若某个集合或关系可以由一阶公式刻画，则称它是<strong>可定义的</strong></p><p>设结构为 <span class="math inline">\(\mathcal{M}\)</span>，公式为<span class="math inline">\(A(x)\)</span>。若 <spanclass="math display">\[S=\{a\in M\mid \mathcal{M}\models A(a)\}\]</span> 则称 <span class="math inline">\(S\)</span> 由公式 <spanclass="math inline">\(A(x)\)</span> 定义</p><p>如果公式中允许参数，则称为带参数可定义；否则称为无参数可定义</p><h3 id="例-17.12.1-在整数结构中的可定义性">例 17.12.1在整数结构中的可定义性</h3><p>在结构 <span class="math display">\[(\mathbb{Z},+,\times,0,1)\]</span> 中，偶数集合可以用公式 <span class="math display">\[\exists y(x=y+y)\]</span> 定义</p><p>因为 <span class="math inline">\(x\)</span> 是偶数，当且仅当存在整数<span class="math inline">\(y\)</span> 使 <span class="math display">\[x=2y=y+y\]</span></p><h3 id="定义-17.12.2-显式可定义与隐式可定义">定义 17.12.2显式可定义与隐式可定义</h3><p>若一个关系可以直接由某个公式定义，称为<strong>显式可定义</strong></p><p>若一个关系虽然没有给出直接公式，但在结构或理论中被其他符号唯一决定，称为<strong>隐式可定义</strong></p><p>在经典模型论中有 Beth 可定义性定理：</p><p>若一个关系在一阶理论中隐式可定义，则它也显式可定义</p><p>直观地说，一阶逻辑中“被唯一决定”和“可用公式写出来”在这种意义下是一致的</p><h2 id="定义-17.13-不完全性">定义 17.13 不完全性</h2><p>不完全性并非对一阶逻辑的完全性定理的否定</p><p>一阶逻辑完全性定理说： <span class="math display">\[\Gamma\models A \Rightarrow \Gamma\vdash A\]</span></p><p>Gödel 不完全性定理讨论的是能表达足够算术的形式理论 <spanclass="math inline">\(T\)</span></p><p>粗略说，若 <span class="math inline">\(T\)</span> 满足：</p><ul><li><span class="math inline">\(T\)</span> 是一致的</li><li><span class="math inline">\(T\)</span> 的公理和证明可有效识别</li><li><span class="math inline">\(T\)</span> 足够表达基本算术</li></ul><p>则存在算术命题 <span class="math inline">\(G\)</span>，使得在 <spanclass="math inline">\(T\)</span> 中既不能证明 <spanclass="math inline">\(G\)</span>，也不能证明 <spanclass="math inline">\(\neg G\)</span></p><p>这说明：足够强的一致形式算术理论不可能把所有算术真理都证明出来</p><h3 id="定义-17.13.1-算术化">定义 17.13.1 算术化</h3><p>Gödel 不完全性证明的关键技术是<strong>算术化</strong></p><p>它把符号、公式、证明这些语法对象编码为自然数。</p><p>例如可以给每个基本符号分配一个自然数编码，再把有限符号串编码成一个自然数</p><p>这样，“<span class="math inline">\(n\)</span> 是某个公式的编码”“<spanclass="math inline">\(m\)</span> 是公式 <spanclass="math inline">\(n\)</span>的证明编码”这类语法命题就可以转化为关于自然数的算术命题</p><p>一旦语法可以在算术内部表达，理论就可以谈论自身的证明行为</p><h3 id="定义-17.13.2-自指句">定义 17.13.2 自指句</h3><p>不完全性证明构造一个句子 <spanclass="math inline">\(G\)</span>，其直观含义是： <spanclass="math display">\[G \equiv \text{“我不可在 }T\text{ 中证明”}\]</span></p><p>若 <span class="math inline">\(T\)</span> 证明了 <spanclass="math inline">\(G\)</span>，则 <spanclass="math inline">\(G\)</span>所说的“我不可证明”就是假的，从而导致不一致</p><p>若 <span class="math inline">\(T\)</span> 证明了 <spanclass="math inline">\(\neg G\)</span>，则相当于证明“<spanclass="math inline">\(G\)</span> 可证明”，在适当条件下也会导致问题</p><p>因此在一致且足够强的理论中，<span class="math inline">\(G\)</span>既不可证也不可反证</p><h3 id="注记-17.13.3-第一不完全性与第二不完全性">注记 17.13.3第一不完全性与第二不完全性</h3><p><strong>第一不完全性定理</strong>：</p><p>足够强且一致的可有效公理化理论是不完全的，即存在既不能证明也不能反证的命题</p><p><strong>第二不完全性定理</strong>：</p><p>足够强且一致的理论不能在自身内部证明自己的相容性</p><p>直观地说，形式系统可以非常强，但只要它足够表达算术并保持一致，就无法完全封闭地证明自身没有矛盾</p>]]></content>
    
    
    <summary type="html">符号逻辑讲义补充：形式系统、演算与元定理</summary>
    
    
    
    <category term="代数结构" scheme="https://exdoubled.github.io/categories/%E4%BB%A3%E6%95%B0%E7%BB%93%E6%9E%84/"/>
    
    
    <category term="笔记" scheme="https://exdoubled.github.io/tags/%E7%AC%94%E8%AE%B0/"/>
    
    <category term="离散数学" scheme="https://exdoubled.github.io/tags/%E7%A6%BB%E6%95%A3%E6%95%B0%E5%AD%A6/"/>
    
    <category term="代数结构与数理逻辑" scheme="https://exdoubled.github.io/tags/%E4%BB%A3%E6%95%B0%E7%BB%93%E6%9E%84%E4%B8%8E%E6%95%B0%E7%90%86%E9%80%BB%E8%BE%91/"/>
    
  </entry>
  
  <entry>
    <title>代数结构与数理逻辑学习笔记6</title>
    <link href="https://exdoubled.github.io/lssx/ls16/"/>
    <id>https://exdoubled.github.io/lssx/ls16/</id>
    <published>2026-06-13T04:00:00.000Z</published>
    <updated>2026-06-17T15:34:42.845Z</updated>
    
    <content type="html"><![CDATA[<p>参考书：</p><p>离散数学及其应用（第 2 版）（屈婉玲等）</p><p>符号逻辑讲义（徐明）</p><h2 id="定义-16.1-命题与联结词">定义 16.1 命题与联结词</h2><p><strong>命题</strong>是能判断真假的陈述句。真命题真值为 <spanclass="math inline">\(1\)</span>，假命题真值为 <spanclass="math inline">\(0\)</span></p><p>不能再分解的命题称为<strong>简单命题</strong>或<strong>原子命题</strong>，常用<span class="math display">\[p,q,r,p_1,q_1,\dots\]</span> 表示</p><p>由简单命题和联结词构成的命题称为<strong>复合命题</strong></p><p>常用联结词如下：</p><table><colgroup><col style="width: 26%" /><col style="width: 26%" /><col style="width: 26%" /><col style="width: 21%" /></colgroup><thead><tr><th style="text-align: center;">联结词</th><th style="text-align: center;">符号</th><th style="text-align: center;">复合命题</th><th style="text-align: center;">真值条件</th></tr></thead><tbody><tr><td style="text-align: center;">否定</td><td style="text-align: center;"><spanclass="math inline">\(\neg\)</span></td><td style="text-align: center;"><span class="math inline">\(\negp\)</span></td><td style="text-align: center;"><span class="math inline">\(p\)</span>真时 <span class="math inline">\(\neg p\)</span> 假，<spanclass="math inline">\(p\)</span> 假时 <span class="math inline">\(\negp\)</span> 真</td></tr><tr><td style="text-align: center;">合取</td><td style="text-align: center;"><spanclass="math inline">\(\wedge\)</span></td><td style="text-align: center;"><span class="math inline">\(p\wedgeq\)</span></td><td style="text-align: center;"><span class="math inline">\(p,q\)</span>同真时为真</td></tr><tr><td style="text-align: center;">析取</td><td style="text-align: center;"><spanclass="math inline">\(\vee\)</span></td><td style="text-align: center;"><span class="math inline">\(p\veeq\)</span></td><td style="text-align: center;"><span class="math inline">\(p,q\)</span>至少一个真时为真</td></tr><tr><td style="text-align: center;">蕴含</td><td style="text-align: center;"><spanclass="math inline">\(\to\)</span></td><td style="text-align: center;"><span class="math inline">\(p\toq\)</span></td><td style="text-align: center;">仅当前件真、后件假时为假</td></tr><tr><td style="text-align: center;">等价</td><td style="text-align: center;"><spanclass="math inline">\(\leftrightarrow\)</span></td><td style="text-align: center;"><spanclass="math inline">\(p\leftrightarrow q\)</span></td><td style="text-align: center;"><span class="math inline">\(p,q\)</span>真值相同时为真</td></tr></tbody></table><p>真值表为：</p><table style="width:100%;"><colgroup><col style="width: 14%" /><col style="width: 14%" /><col style="width: 14%" /><col style="width: 14%" /><col style="width: 14%" /><col style="width: 14%" /><col style="width: 14%" /></colgroup><thead><tr><th style="text-align: center;"><spanclass="math inline">\(p\)</span></th><th style="text-align: center;"><spanclass="math inline">\(q\)</span></th><th style="text-align: center;"><span class="math inline">\(\negp\)</span></th><th style="text-align: center;"><span class="math inline">\(p\wedgeq\)</span></th><th style="text-align: center;"><span class="math inline">\(p\veeq\)</span></th><th style="text-align: center;"><span class="math inline">\(p\toq\)</span></th><th style="text-align: center;"><spanclass="math inline">\(p\leftrightarrow q\)</span></th></tr></thead><tbody><tr><td style="text-align: center;">0</td><td style="text-align: center;">0</td><td style="text-align: center;">1</td><td style="text-align: center;">0</td><td style="text-align: center;">0</td><td style="text-align: center;">1</td><td style="text-align: center;">1</td></tr><tr><td style="text-align: center;">0</td><td style="text-align: center;">1</td><td style="text-align: center;">1</td><td style="text-align: center;">0</td><td style="text-align: center;">1</td><td style="text-align: center;">1</td><td style="text-align: center;">0</td></tr><tr><td style="text-align: center;">1</td><td style="text-align: center;">0</td><td style="text-align: center;">0</td><td style="text-align: center;">0</td><td style="text-align: center;">1</td><td style="text-align: center;">0</td><td style="text-align: center;">0</td></tr><tr><td style="text-align: center;">1</td><td style="text-align: center;">1</td><td style="text-align: center;">0</td><td style="text-align: center;">1</td><td style="text-align: center;">1</td><td style="text-align: center;">1</td><td style="text-align: center;">1</td></tr></tbody></table><p>蕴含式 <span class="math inline">\(p\toq\)</span>：不表示两个命题之间一定有因果关系，只表示一种真值函数，只要<span class="math inline">\(p\)</span> 为假，<spanclass="math inline">\(p\to q\)</span> 就为真</p><h2 id="定义-16.2-命题公式及其赋值">定义 16.2 命题公式及其赋值</h2><p>命题公式递归定义如下：</p><ul><li>单个命题变项是命题公式</li><li>若 <span class="math inline">\(A\)</span> 是命题公式，则 <spanclass="math inline">\(\neg A\)</span> 是命题公式</li><li>若 <span class="math inline">\(A,B\)</span> 是命题公式，则 <spanclass="math inline">\((A\wedge B),(A\vee B),(A\to B),(A\leftrightarrowB)\)</span> 是命题公式</li><li>有限次使用以上规则得到的符号串才是命题公式</li></ul><p>命题公式也称为<strong>合式公式</strong></p><p>为减少括号，通常约定联结词优先级为： <span class="math display">\[\neg,\quad \wedge,\quad \vee,\quad \to,\quad \leftrightarrow\]</span> 其中 <span class="math inline">\(\neg\)</span>优先级最高，<span class="math inline">\(\leftrightarrow\)</span>优先级最低</p><p>给命题公式中所有命题变项指定真值，称为该公式的一个<strong>赋值</strong>或<strong>解释</strong></p><p>若公式 <span class="math inline">\(A\)</span> 中有 <spanclass="math inline">\(n\)</span> 个不同命题变项，则共有 <spanclass="math inline">\(2^n\)</span> 种赋值</p><p>在每个赋值下，公式 <span class="math inline">\(A\)</span>都有唯一确定的真值</p><p>若 <span class="math inline">\(A\)</span> 在所有赋值下均为真，则称<span class="math inline">\(A\)</span>为<strong>重言式</strong>或<strong>永真式</strong>，记为 <spanclass="math inline">\(\models A\)</span></p><p>若 <span class="math inline">\(A\)</span> 在所有赋值下均为假，则称<span class="math inline">\(A\)</span>为<strong>矛盾式</strong>或<strong>永假式</strong></p><p>若 <span class="math inline">\(A\)</span> 至少在一个赋值下为真，则称<span class="math inline">\(A\)</span> 为<strong>可满足式</strong></p><p>既不是重言式也不是矛盾式的公式称为<strong>偶然式</strong></p><h3 id="定理-16.2.1-重言式矛盾式和可满足式">定理 16.2.1重言式、矛盾式和可满足式</h3><p>设 <span class="math inline">\(A\)</span> 是命题公式，则：</p><ul><li><span class="math inline">\(A\)</span> 是重言式当且仅当 <spanclass="math inline">\(\neg A\)</span> 是矛盾式</li><li><span class="math inline">\(A\)</span> 是矛盾式当且仅当 <spanclass="math inline">\(\neg A\)</span> 是重言式</li><li><span class="math inline">\(A\)</span> 可满足当且仅当 <spanclass="math inline">\(A\)</span> 不是矛盾式</li></ul><details class="gray" data-header-exclude>    <summary><i class="fa-solid fa-chevron-right"></i>证明： 点击查看证明 </summary>    <div class='content markdown-body'>      <p>由否定联结词真值表： <span class="math display">\[v(\neg A)=1-v(A)\]</span></p><p>若 <span class="math inline">\(A\)</span> 在所有赋值下都为真，则<span class="math inline">\(\neg A\)</span> 在所有赋值下都为假，所以<span class="math inline">\(\neg A\)</span> 是矛盾式</p><p>反过来，若 <span class="math inline">\(\neg A\)</span> 是矛盾式，则<span class="math inline">\(\neg A\)</span> 在所有赋值下都为假，故 <spanclass="math inline">\(A\)</span> 在所有赋值下都为真，即 <spanclass="math inline">\(A\)</span> 是重言式</p><p>第二条同理</p><p>第三条只是“存在赋值使 <span class="math inline">\(A\)</span>为真”和“并非所有赋值都使 <span class="math inline">\(A\)</span>为假”的等价</p>    </div>  </details><h2 id="定义-16.3-等值式">定义 16.3 等值式</h2><p>设 <span class="math inline">\(A,B\)</span>是命题公式。若在任意赋值下 <span class="math inline">\(A\)</span> 与<span class="math inline">\(B\)</span> 的真值都相同，则称 <spanclass="math inline">\(A\)</span> 与 <spanclass="math inline">\(B\)</span> <strong>等值</strong>，记为 <spanclass="math display">\[A\Leftrightarrow B\]</span> 或 <span class="math display">\[A\equiv B\]</span></p><p>等值式也可以用重言式刻画： <span class="math display">\[A\equiv B \iff A\leftrightarrow B\text{ 是重言式}\]</span></p><p>常用基本等值式如下：</p><table><colgroup><col style="width: 55%" /><col style="width: 44%" /></colgroup><thead><tr><th style="text-align: center;">名称</th><th style="text-align: center;">等值式</th></tr></thead><tbody><tr><td style="text-align: center;">双重否定律</td><td style="text-align: center;"><span class="math inline">\(\neg\negA\equiv A\)</span></td></tr><tr><td style="text-align: center;">幂等律</td><td style="text-align: center;"><span class="math inline">\(A\veeA\equiv A,\quad A\wedge A\equiv A\)</span></td></tr><tr><td style="text-align: center;">交换律</td><td style="text-align: center;"><span class="math inline">\(A\veeB\equiv B\vee A,\quad A\wedge B\equiv B\wedge A\)</span></td></tr><tr><td style="text-align: center;">结合律</td><td style="text-align: center;"><span class="math inline">\((A\veeB)\vee C\equiv A\vee(B\vee C)\)</span>；<spanclass="math inline">\((A\wedge B)\wedge C\equiv A\wedge(B\wedgeC)\)</span></td></tr><tr><td style="text-align: center;">分配律</td><td style="text-align: center;"><spanclass="math inline">\(A\vee(B\wedge C)\equiv(A\vee B)\wedge(A\veeC)\)</span>；<span class="math inline">\(A\wedge(B\vee C)\equiv(A\wedgeB)\vee(A\wedge C)\)</span></td></tr><tr><td style="text-align: center;">De Morgan 律</td><td style="text-align: center;"><span class="math inline">\(\neg(A\veeB)\equiv\neg A\wedge\neg B\)</span>；<spanclass="math inline">\(\neg(A\wedge B)\equiv\neg A\vee\negB\)</span></td></tr><tr><td style="text-align: center;">吸收律</td><td style="text-align: center;"><spanclass="math inline">\(A\vee(A\wedge B)\equiv A,\quad A\wedge(A\veeB)\equiv A\)</span></td></tr><tr><td style="text-align: center;">零律</td><td style="text-align: center;"><span class="math inline">\(A\vee1\equiv 1,\quad A\wedge 0\equiv 0\)</span></td></tr><tr><td style="text-align: center;">同一律</td><td style="text-align: center;"><span class="math inline">\(A\vee0\equiv A,\quad A\wedge 1\equiv A\)</span></td></tr><tr><td style="text-align: center;">排中律</td><td style="text-align: center;"><span class="math inline">\(A\vee\negA\equiv 1\)</span></td></tr><tr><td style="text-align: center;">矛盾律</td><td style="text-align: center;"><span class="math inline">\(A\wedge\negA\equiv 0\)</span></td></tr><tr><td style="text-align: center;">蕴含等值式</td><td style="text-align: center;"><span class="math inline">\(A\toB\equiv\neg A\vee B\)</span></td></tr><tr><td style="text-align: center;">等价等值式</td><td style="text-align: center;"><spanclass="math inline">\(A\leftrightarrow B\equiv(A\to B)\wedge(B\toA)\)</span></td></tr><tr><td style="text-align: center;">假言易位</td><td style="text-align: center;"><span class="math inline">\(A\toB\equiv\neg B\to\neg A\)</span></td></tr><tr><td style="text-align: center;">等价否定等值式</td><td style="text-align: center;"><spanclass="math inline">\(A\leftrightarrow B\equiv\neg A\leftrightarrow\negB\)</span></td></tr><tr><td style="text-align: center;">归谬论</td><td style="text-align: center;"><span class="math inline">\((A\to B)\wedge (A \to \neg B) \equiv \neg A\)</span></td></tr></tbody></table><p>等值演算就是不断用已知等值式替换公式中的子公式，从而把一个公式化成更方便的形式</p><h3 id="定理-16.3.1-等值代换">定理 16.3.1 等值代换</h3><p>若 <span class="math inline">\(A\equiv B\)</span></p><p>则在任意命题公式中，把某处出现的 <spanclass="math inline">\(A\)</span> 替换为 <spanclass="math inline">\(B\)</span>，所得公式与原公式等值</p><h3 id="例-16.3.2-等值演算">例 16.3.2 等值演算</h3><p>化简： <span class="math display">\[\neg(p\to q)\vee(p\wedge q)\]</span></p><p>有 <span class="math display">\[\neg(p\to q)\vee(p\wedge q)\equiv\neg(\neg p\vee q)\vee(p\wedge q)\]</span></p><p><span class="math display">\[\equiv(p\wedge\neg q)\vee(p\wedge q)\]</span></p><p><span class="math display">\[\equivp\wedge(\neg q\vee q)\]</span></p><p><span class="math display">\[\equivp\wedge 1\]</span></p><p><span class="math display">\[\equiv p\]</span></p><h2 id="定义-16.4-析取范式与合取范式">定义 16.4 析取范式与合取范式</h2><p>命题变项及其否定统称为<strong>文字</strong></p><p>有限个文字的合取称为<strong>简单合取式</strong>： <spanclass="math display">\[l_1\wedge l_2\wedge\cdots\wedge l_k\]</span></p><p>有限个文字的析取称为<strong>简单析取式</strong>： <spanclass="math display">\[l_1\vee l_2\vee\cdots\vee l_k\]</span></p><p>有限个简单合取式的析取称为<strong>析取范式</strong></p><p>有限个简单析取式的合取称为<strong>合取范式</strong></p><p>也就是说： <span class="math display">\[D_1\vee D_2\vee\cdots\vee D_m\]</span> 是析取范式，其中每个 <span class="math inline">\(D_i\)</span>是简单合取式；</p><p><span class="math display">\[C_1\wedge C_2\wedge\cdots\wedge C_m\]</span> 是合取范式，其中每个 <span class="math inline">\(C_i\)</span>是简单析取式。</p><h3 id="定理-16.4.1-范式存在">定理 16.4.1 范式存在</h3><p>任意命题公式都可以化为与之等值的析取范式，也可以化为与之等值的合取范式</p><details class="gray" data-header-exclude>    <summary><i class="fa-solid fa-chevron-right"></i>证明： 点击查看证明 </summary>    <div class='content markdown-body'>      <p>按等值演算步骤处理</p><p>第一步，消去蕴含和等价： <span class="math display">\[A\to B\equiv \neg A\vee B\]</span> <span class="math display">\[A\leftrightarrow B\equiv(A\wedge B)\vee(\neg A\wedge\neg B)\]</span></p><p>第二步，把否定号内移到命题变项前： <span class="math display">\[\neg\neg A\equiv A\]</span> <span class="math display">\[\neg(A\vee B)\equiv\neg A\wedge\neg B\]</span> <span class="math display">\[\neg(A\wedge B)\equiv\neg A\vee\neg B\]</span></p><p>第三步，使用分配律： <span class="math display">\[A\wedge(B\vee C)\equiv(A\wedge B)\vee(A\wedge C)\]</span> 可得到析取范式；</p><p>使用 <span class="math display">\[A\vee(B\wedge C)\equiv(A\vee B)\wedge(A\vee C)\]</span> 可得到合取范式</p><p>每一步都是等值变形，所以最终公式与原公式等值</p>    </div>  </details><h3 id="定义-16.4.2-主析取范式与主合取范式">定义 16.4.2主析取范式与主合取范式</h3><p>设公式含命题变项 <span class="math display">\[p_1,p_2,\dots,p_n\]</span></p><p>含全部 <span class="math inline">\(n\)</span>个命题变项的简单合取式称为<strong>极小项</strong>，其中每个命题变项恰出现一次，形式为<span class="math inline">\(p_i\)</span> 或 <spanclass="math inline">\(\neg p_i\)</span></p><p>极小项只在一个赋值下为真</p><p>含全部 <span class="math inline">\(n\)</span>个命题变项的简单析取式称为<strong>极大项</strong>，其中每个命题变项恰出现一次，形式为<span class="math inline">\(p_i\)</span> 或 <spanclass="math inline">\(\neg p_i\)</span></p><p>极大项只在一个赋值下为假</p><p>由极小项组成的析取范式称为<strong>主析取范式</strong></p><p>由极大项组成的合取范式称为<strong>主合取范式</strong></p><p>求主析取范式的方法：</p><ul><li>列出公式真值表</li><li>找出使公式为真的赋值行</li><li>每一行写出对应极小项</li><li>把这些极小项析取起来</li></ul><p>求主合取范式的方法：</p><ul><li>列出公式真值表</li><li>找出使公式为假的赋值行</li><li>每一行写出对应极大项</li><li>把这些极大项合取起来</li></ul><h2 id="定义-16.5-联结词完备集">定义 16.5 联结词完备集</h2><p>若任意命题公式都可以用某个联结词集合中的联结词表示，则称这个联结词集合是<strong>功能完备集</strong></p><p>教材中常用的功能完备集有： <span class="math display">\[\{\neg,\wedge,\vee\}\]</span></p><p><span class="math display">\[\{\neg,\wedge\}\]</span></p><p><span class="math display">\[\{\neg,\vee\}\]</span></p><p><span class="math display">\[\{\neg,\to\}\]</span></p><h3 id="定理-16.5.1-常用联结词完备集">定理 16.5.1 常用联结词完备集</h3><p>集合 <span class="math display">\[\{\neg,\wedge\},\quad \{\neg,\vee\},\quad \{\neg,\to\}\]</span> 都是功能完备集</p><details class="gray" data-header-exclude>    <summary><i class="fa-solid fa-chevron-right"></i>证明： 点击查看证明 </summary>    <div class='content markdown-body'>      <p>由范式存在定理，任意命题公式都可化为只含 <spanclass="math inline">\(\neg,\wedge,\vee\)</span></p><p>的公式</p><p>对 <span class="math inline">\(\{\neg,\wedge\}\)</span>： <spanclass="math display">\[A\vee B\equiv\neg(\neg A\wedge\neg B)\]</span> 所以可用 <span class="math inline">\(\neg,\wedge\)</span> 表示<span class="math inline">\(\vee\)</span></p><p>对 <span class="math inline">\(\{\neg,\vee\}\)</span>： <spanclass="math display">\[A\wedge B\equiv\neg(\neg A\vee\neg B)\]</span> 所以可用 <span class="math inline">\(\neg,\vee\)</span> 表示<span class="math inline">\(\wedge\)</span></p><p>对 <span class="math inline">\(\{\neg,\to\}\)</span>： <spanclass="math display">\[A\vee B\equiv\neg A\to B\]</span> 且 <span class="math display">\[A\wedge B\equiv\neg(A\to\neg B)\]</span> 所以可用 <span class="math inline">\(\neg,\to\)</span> 表示<span class="math inline">\(\vee\)</span> 与 <spanclass="math inline">\(\wedge\)</span></p><p>因此这三组联结词都是功能完备集</p>    </div>  </details><h2 id="定义-16.6-推理的形式结构">定义 16.6 推理的形式结构</h2><p>设 <span class="math inline">\(A_1,A_2,\dots,A_k,B\)</span>是命题公式。若对任意赋值，只要 <spanclass="math inline">\(A_1,A_2,\dots,A_k\)</span>都为真，<spanclass="math inline">\(B\)</span> 也为真，则称从前提 <spanclass="math inline">\(A_1,\dots,A_k\)</span> 推出结论 <spanclass="math inline">\(B\)</span> 的推理是<strong>有效的</strong>，记为<span class="math display">\[A_1,A_2,\dots,A_k\Rightarrow B\]</span> 或 <span class="math display">\[A_1,A_2,\dots,A_k\models B\]</span></p><p>推理有效性的真值表判定： <span class="math display">\[A_1,\dots,A_k\models B\]</span> 当且仅当 <span class="math display">\[(A_1\wedge A_2\wedge\cdots\wedge A_k)\to B\]</span> 是重言式</p><p>也等价于 <span class="math display">\[A_1\wedge A_2\wedge\cdots\wedge A_k\wedge\neg B\]</span> 是矛盾式</p><h3 id="定理-16.6.1-推理有效性的等价条件">定理 16.6.1推理有效性的等价条件</h3><p>设 <span class="math inline">\(A_1,\dots,A_k,B\)</span>是命题公式，则以下三者等价：</p><ul><li><span class="math inline">\(A_1,\dots,A_k\models B\)</span></li><li><span class="math inline">\((A_1\wedge\cdots\wedge A_k)\toB\)</span> 是重言式</li><li><span class="math inline">\(A_1\wedge\cdots\wedge A_k\wedge\negB\)</span> 是矛盾式</li></ul><details class="gray" data-header-exclude>    <summary><i class="fa-solid fa-chevron-right"></i>证明： 点击查看证明 </summary>    <div class='content markdown-body'>      <p>第一条等价于第二条：</p><p>对任意赋值，若所有前提为真则结论为真，正是 <spanclass="math display">\[(A_1\wedge\cdots\wedge A_k)\to B\]</span> 在任意赋值下为真。</p><p>第二条等价于第三条：</p><p>蕴含式 <span class="math display">\[(A_1\wedge\cdots\wedge A_k)\to B\]</span> 等值于 <span class="math display">\[\neg(A_1\wedge\cdots\wedge A_k)\vee B\]</span></p><p>它不是重言式，当且仅当存在赋值使 <span class="math display">\[A_1\wedge\cdots\wedge A_k\]</span> 为真且 <span class="math inline">\(B\)</span> 为假，也就是<span class="math display">\[A_1\wedge\cdots\wedge A_k\wedge\neg B\]</span> 可满足</p><p>所以蕴含式是重言式，当且仅当前提合取再合取 <spanclass="math inline">\(\neg B\)</span> 是矛盾式</p>    </div>  </details><h2 id="定义-16.7-自然推理系统-p">定义 16.7 自然推理系统 P</h2><p>自然推理系统 <span class="math inline">\(P\)</span>由三部分组成：</p><ul><li>字母表：命题变项符号、联结词符号和括号</li><li>合式公式：即命题公式</li><li>推理规则</li></ul><p>系统 <span class="math inline">\(P\)</span> 的基本推理规则包括：</p><ol type="1"><li><strong>前提引入规则</strong>：证明的任意步骤都可以引入前提</li><li><strong>结论引入规则</strong>：证明中已经得到的结论可以作为后续证明的前提</li><li><strong>置换规则</strong>：证明中任一公式的子公式可以用与它等值的公式替换</li></ol><p>常用推理规则如下：</p><table><colgroup><col style="width: 50%" /><col style="width: 50%" /></colgroup><thead><tr><th style="text-align: center;">名称</th><th style="text-align: center;">推理形式</th></tr></thead><tbody><tr><td style="text-align: center;">假言推理</td><td style="text-align: center;"><span class="math inline">\(\dfrac{A\toB,\ A}{B}\)</span></td></tr><tr><td style="text-align: center;">附加规则</td><td style="text-align: center;"><spanclass="math inline">\(\dfrac{A}{A\vee B}\)</span></td></tr><tr><td style="text-align: center;">化简规则</td><td style="text-align: center;"><spanclass="math inline">\(\dfrac{A\wedge B}{A}\)</span></td></tr><tr><td style="text-align: center;">拒取式规则</td><td style="text-align: center;"><span class="math inline">\(\dfrac{A\toB,\ \neg B}{\neg A}\)</span></td></tr><tr><td style="text-align: center;">等价三段论</td><td style="text-align: center;"><spanclass="math inline">\(\dfrac{A\leftrightarrow B,\ B\leftrightarrowC}{A\leftrightarrow C}\)</span></td></tr><tr><td style="text-align: center;">假言三段论</td><td style="text-align: center;"><span class="math inline">\(\dfrac{A\toB,\ B\to C}{A\to C}\)</span></td></tr><tr><td style="text-align: center;">析取三段论</td><td style="text-align: center;"><span class="math inline">\(\dfrac{A\veeB,\ \neg B}{A}\)</span></td></tr><tr><td style="text-align: center;">构造性二难</td><td style="text-align: center;"><span class="math inline">\(\dfrac{A\toB,\ C\to D,\ A\vee C}{B\vee D}\)</span></td></tr><tr><td style="text-align: center;">构造性二难</td><td style="text-align: center;"><span class="math inline">\(\dfrac{A\toB,\ \neg A\to B}{B}\)</span></td></tr><tr><td style="text-align: center;">破坏性二难</td><td style="text-align: center;"><span class="math inline">\(\dfrac{A\toB,\ C\to D,\ \neg B\vee\neg D}{\neg A\vee\neg C}\)</span></td></tr><tr><td style="text-align: center;">合取引入</td><td style="text-align: center;"><span class="math inline">\(\dfrac{A,\B}{A\wedge B}\)</span></td></tr></tbody></table><h3 id="定理-16.7.1-假言推理有效">定理 16.7.1 假言推理有效</h3><p><span class="math display">\[A\to B,\ A\models B\]</span></p><details class="gray" data-header-exclude>    <summary><i class="fa-solid fa-chevron-right"></i>证明： 点击查看证明 </summary>    <div class='content markdown-body'>      <p>只需证明 <span class="math display">\[((A\to B)\wedge A)\to B\]</span> 是重言式</p><p>若前件为真，则 <span class="math inline">\(A\to B\)</span> 为真且<span class="math inline">\(A\)</span> 为真</p><p>由蕴含联结词真值表，<span class="math inline">\(A\)</span> 真且 <spanclass="math inline">\(A\to B\)</span> 真时，<spanclass="math inline">\(B\)</span> 必真</p><p>所以不可能出现前件真而后件假的赋值，该蕴含式为重言式</p>    </div>  </details><h3 id="定理-16.7.2-拒取式有效">定理 16.7.2 拒取式有效</h3><p><span class="math display">\[A\to B,\ \neg B\models \neg A\]</span></p><details class="gray" data-header-exclude>    <summary><i class="fa-solid fa-chevron-right"></i>证明： 点击查看证明 </summary>    <div class='content markdown-body'>      <p>由假言易位：<span class="math inline">\(A\to B\equiv \neg B\to\negA\)</span></p><p>于是前提 <span class="math inline">\(A\to B\)</span> 可置换为 <spanclass="math inline">\(\neg B\to\neg A\)</span></p><p>再由假言推理，从 <span class="math display">\[\neg B\to\neg A,\quad \neg B\]</span> 推出 <span class="math display">\[\neg A\]</span></p><p>故拒取式有效</p>    </div>  </details><h3 id="例-16.7.3-自然推理系统-p-中的证明">例 16.7.3 自然推理系统 P中的证明</h3><p>证明： <span class="math display">\[p\vee q,\ q\to r,\ p\to s,\ \neg s\models r\vee q\]</span></p><p>证明序列可写为：</p><table><thead><tr><th style="text-align: center;">步骤</th><th style="text-align: center;">公式</th><th style="text-align: center;">理由</th></tr></thead><tbody><tr><td style="text-align: center;">1</td><td style="text-align: center;"><span class="math inline">\(p\veeq\)</span></td><td style="text-align: center;">前提引入</td></tr><tr><td style="text-align: center;">2</td><td style="text-align: center;"><span class="math inline">\(q\tor\)</span></td><td style="text-align: center;">前提引入</td></tr><tr><td style="text-align: center;">3</td><td style="text-align: center;"><span class="math inline">\(p\tos\)</span></td><td style="text-align: center;">前提引入</td></tr><tr><td style="text-align: center;">4</td><td style="text-align: center;"><span class="math inline">\(\negs\)</span></td><td style="text-align: center;">前提引入</td></tr><tr><td style="text-align: center;">5</td><td style="text-align: center;"><span class="math inline">\(\negp\)</span></td><td style="text-align: center;">3,4 拒取式</td></tr><tr><td style="text-align: center;">6</td><td style="text-align: center;"><spanclass="math inline">\(q\)</span></td><td style="text-align: center;">1,5 析取三段论</td></tr><tr><td style="text-align: center;">7</td><td style="text-align: center;"><spanclass="math inline">\(r\)</span></td><td style="text-align: center;">2,6 假言推理</td></tr><tr><td style="text-align: center;">8</td><td style="text-align: center;"><span class="math inline">\(r\veeq\)</span></td><td style="text-align: center;">7 附加规则</td></tr></tbody></table><p>所以推理有效</p><h2 id="定义-16.8-一阶逻辑命题符号化">定义 16.8 一阶逻辑命题符号化</h2><p>命题逻辑把简单命题看作整体，不能分析命题内部的个体、性质和关系。</p><p>一阶逻辑引入：</p><ul><li>个体词：表示具体对象或变量，如 <spanclass="math inline">\(a,b,x,y\)</span></li><li>谓词：表示对象的性质或对象之间的关系，如 <spanclass="math inline">\(F(x),G(x,y)\)</span></li><li>量词：表示对象范围</li></ul><p>两个基本量词：</p><table><thead><tr><th style="text-align: center;">量词</th><th style="text-align: center;">符号</th><th style="text-align: center;">读法</th></tr></thead><tbody><tr><td style="text-align: center;">全称量词</td><td style="text-align: center;"><spanclass="math inline">\(\forall\)</span></td><td style="text-align: center;">对任意、所有</td></tr><tr><td style="text-align: center;">存在量词</td><td style="text-align: center;"><spanclass="math inline">\(\exists\)</span></td><td style="text-align: center;">存在、至少有一个</td></tr></tbody></table><p>例如在论域为全体人的情况下：</p><ul><li>“所有人都会死”可符号化为 <span class="math inline">\(\forallx(H(x)\to M(x))\)</span></li><li>“有些学生喜欢离散数学”可符号化为 <span class="math inline">\(\existsx(S(x)\wedge L(x))\)</span></li><li>“没有人既是学生又是老师”可符号化为 <spanclass="math inline">\(\neg\exists x(S(x)\wedge T(x))\)</span></li></ul><h3 id="注记-16.8.1-符号化中的常见形式">注记 16.8.1符号化中的常见形式</h3><p>全称命题常写成蕴含式： <span class="math display">\[\forall x(P(x)\to Q(x))\]</span> 表示“所有 <span class="math inline">\(P\)</span> 都是 <spanclass="math inline">\(Q\)</span>”</p><p>存在命题常写成合取式： <span class="math display">\[\exists x(P(x)\wedge Q(x))\]</span> 表示“存在某个对象既是 <span class="math inline">\(P\)</span>又是 <span class="math inline">\(Q\)</span>”</p><p>“没有 <span class="math inline">\(P\)</span> 是 <spanclass="math inline">\(Q\)</span>”常写为： <span class="math display">\[\neg\exists x(P(x)\wedge Q(x))\]</span> 或等值地写为： <span class="math display">\[\forall x(P(x)\to\neg Q(x))\]</span></p><h2 id="定义-16.9-一阶逻辑公式及解释">定义 16.9 一阶逻辑公式及解释</h2><p>一阶逻辑中常见符号包括：</p><ul><li>个体常项：<span class="math inline">\(a,b,c,\dots\)</span></li><li>个体变项：<span class="math inline">\(x,y,z,\dots\)</span></li><li>函数符号：<span class="math inline">\(f,g,\dots\)</span></li><li>谓词符号：<span class="math inline">\(F,G,R,\dots\)</span></li><li>量词：<span class="math inline">\(\forall,\exists\)</span></li><li>命题联结词：<spanclass="math inline">\(\neg,\wedge,\vee,\to,\leftrightarrow\)</span></li></ul><p><strong>项</strong>表示个体：</p><ul><li>个体常项和个体变项是项</li><li>若 <span class="math inline">\(f\)</span> 是 <spanclass="math inline">\(n\)</span> 元函数符号，<spanclass="math inline">\(t_1,\dots,t_n\)</span> 是项，则 <spanclass="math inline">\(f(t_1,\dots,t_n)\)</span> 是项</li></ul><p><strong>原子公式</strong>包括：</p><ul><li><span class="math inline">\(n\)</span> 元谓词 <spanclass="math inline">\(F\)</span> 作用在 <spanclass="math inline">\(n\)</span> 个项上：<spanclass="math inline">\(F(t_1,\dots,t_n)\)</span></li><li>等式：<span class="math inline">\(t_1=t_2\)</span></li></ul><p>一阶公式由原子公式经过命题联结词和量词构成。</p><p>在 <span class="math inline">\(\forall xA\)</span> 或 <spanclass="math inline">\(\exists xA\)</span> 中，<spanclass="math inline">\(A\)</span> 称为量词的<strong>辖域</strong></p><p>若 <span class="math inline">\(x\)</span> 的一次出现受到 <spanclass="math inline">\(\forall x\)</span> 或 <spanclass="math inline">\(\exists x\)</span>约束，则称为<strong>约束出现</strong>；否则称为<strong>自由出现</strong></p><p>没有自由变项的公式称为<strong>闭式</strong></p><p>给出一阶公式的解释时，需要指定：</p><ul><li>个体域 <span class="math inline">\(D\)</span></li><li>个体常项在 <span class="math inline">\(D\)</span> 中对应的元素</li><li>函数符号在 <span class="math inline">\(D\)</span> 上对应的函数</li><li>谓词符号在 <span class="math inline">\(D\)</span>上对应的性质或关系</li></ul><p>在解释确定以后，一阶公式才有真值</p><h3 id="例-16.9.1-公式解释">例 16.9.1 公式解释</h3><p>设个体域为整数集 <spanclass="math inline">\(\mathbb{Z}\)</span>，谓词 <spanclass="math inline">\(P(x)\)</span> 表示“<spanclass="math inline">\(x\)</span> 是偶数”，谓词 <spanclass="math inline">\(Q(x)\)</span> 表示“<spanclass="math inline">\(x&gt;0\)</span>”。</p><p>则 <span class="math display">\[\exists x(P(x)\wedge Q(x))\]</span> 表示“存在正偶数”，为真</p><p>而 <span class="math display">\[\forall x(P(x)\to Q(x))\]</span> 表示“所有偶数都是正数”，为假。</p><p>定义 16.10 一阶逻辑等值式与置换规则</p><p>若一阶公式 <span class="math inline">\(A,B\)</span>在任意解释下真值都相同，则称 <span class="math inline">\(A\)</span> 与<span class="math inline">\(B\)</span> <strong>等值</strong>，记为 <spanclass="math display">\[A\equiv B\]</span></p><p>命题逻辑中的等值式在一阶逻辑中仍可使用</p><p>一阶逻辑特有的基本等值式主要是量词等值式：</p><p><span class="math display">\[\neg\forall xA(x)\equiv \exists x\neg A(x)\]</span></p><p><span class="math display">\[\neg\exists xA(x)\equiv \forall x\neg A(x)\]</span></p><p><span class="math display">\[\forall x(A(x)\wedge B(x))\equiv \forall xA(x)\wedge\forall xB(x)\]</span></p><p><span class="math display">\[\exists x(A(x)\vee B(x))\equiv \exists xA(x)\vee\exists xB(x)\]</span></p><p>若 <span class="math inline">\(x\)</span> 不在 <spanclass="math inline">\(B\)</span> 中自由出现，则有：</p><p><span class="math display">\[\forall x(A(x)\wedge B)\equiv \forall xA(x)\wedge B\]</span></p><p><span class="math display">\[\exists x(A(x)\wedge B)\equiv \exists xA(x)\wedge B\]</span></p><p><span class="math display">\[\forall x(A(x)\vee B)\equiv \forall xA(x)\vee B\]</span></p><p><span class="math display">\[\exists x(A(x)\vee B)\equiv \exists xA(x)\vee B\]</span></p><p>相同量词可以交换： <span class="math display">\[\forall x\forall yA(x,y)\equiv\forall y\forall xA(x,y)\]</span></p><p><span class="math display">\[\exists x\exists yA(x,y)\equiv\exists y\exists xA(x,y)\]</span></p><p>但不同量词一般不能随意交换： <span class="math display">\[\forall x\exists yA(x,y)\not\equiv\exists y\forall xA(x,y)\]</span></p><p>一阶逻辑的置换规则：若 <span class="math display">\[A\equiv B\]</span> 则在公式中可以用 <span class="math inline">\(B\)</span>置换某处出现的 <spanclass="math inline">\(A\)</span>，所得公式与原公式等值</p><h3 id="定理-16.10.1-量词否定等值式">定理 16.10.1 量词否定等值式</h3><p><span class="math display">\[\neg\forall xA(x)\equiv \exists x\neg A(x)\]</span></p><p><span class="math display">\[\neg\exists xA(x)\equiv \forall x\neg A(x)\]</span></p><details class="gray" data-header-exclude>    <summary><i class="fa-solid fa-chevron-right"></i>证明： 点击查看证明 </summary>    <div class='content markdown-body'>      <p>第一式：</p><p><span class="math inline">\(\neg\forall xA(x)\)</span> 表示“并非所有<span class="math inline">\(x\)</span> 都满足 <spanclass="math inline">\(A(x)\)</span>”。</p><p>这等价于“存在某个 <span class="math inline">\(x\)</span> 不满足 <spanclass="math inline">\(A(x)\)</span>”，即 <span class="math display">\[\exists x\neg A(x)\]</span></p><p>第二式：</p><p><span class="math inline">\(\neg\exists xA(x)\)</span> 表示“不存在<span class="math inline">\(x\)</span> 满足 <spanclass="math inline">\(A(x)\)</span>”。</p><p>这等价于“所有 <span class="math inline">\(x\)</span> 都不满足 <spanclass="math inline">\(A(x)\)</span>”，即 <span class="math display">\[\forall x\neg A(x)\]</span></p>    </div>  </details><h3 id="定理-16.10.2-量词对合取与析取的分配">定理 16.10.2量词对合取与析取的分配</h3><p><span class="math display">\[\forall x(A(x)\wedge B(x))\equiv \forall xA(x)\wedge\forall xB(x)\]</span></p><p><span class="math display">\[\exists x(A(x)\vee B(x))\equiv \exists xA(x)\vee\exists xB(x)\]</span></p><details class="gray" data-header-exclude>    <summary><i class="fa-solid fa-chevron-right"></i>证明： 点击查看证明 </summary>    <div class='content markdown-body'>      <p>第一式：</p><p><span class="math inline">\(\forall x(A(x)\wedge B(x))\)</span>表示对每个对象 <span class="math inline">\(x\)</span>，<spanclass="math inline">\(A(x)\)</span> 和 <spanclass="math inline">\(B(x)\)</span> 都成立。</p><p>这等价于：</p><ul><li>对每个对象 <span class="math inline">\(x\)</span>，<spanclass="math inline">\(A(x)\)</span> 成立</li><li>对每个对象 <span class="math inline">\(x\)</span>，<spanclass="math inline">\(B(x)\)</span> 成立</li></ul><p>即 <span class="math display">\[\forall xA(x)\wedge\forall xB(x)\]</span></p><p>第二式：</p><p><span class="math inline">\(\exists x(A(x)\vee B(x))\)</span>表示存在某个对象 <span class="math inline">\(x\)</span>，使 <spanclass="math inline">\(A(x)\)</span> 或 <spanclass="math inline">\(B(x)\)</span> 成立。</p><p>这等价于“存在对象使 <span class="math inline">\(A(x)\)</span>成立，或者存在对象使 <span class="math inline">\(B(x)\)</span> 成立”，即<span class="math display">\[\exists xA(x)\vee\exists xB(x)\]</span></p>    </div>  </details><h3 id="注记-16.10.3-两个常见错误">注记 16.10.3 两个常见错误</h3><p>一般没有： <span class="math display">\[\forall x(A(x)\vee B(x))\equiv \forall xA(x)\vee\forall xB(x)\]</span></p><p>也一般没有： <span class="math display">\[\exists x(A(x)\wedge B(x))\equiv \exists xA(x)\wedge\exists xB(x)\]</span></p><p>例如在个体域 <span class="math inline">\(\{1,2\}\)</span> 上，令<span class="math inline">\(A(x)\)</span> 表示 <spanclass="math inline">\(x=1\)</span>，<spanclass="math inline">\(B(x)\)</span> 表示 <spanclass="math inline">\(x=2\)</span></p><p>则 <span class="math display">\[\forall x(A(x)\vee B(x))\]</span> 为真，但 <span class="math display">\[\forall xA(x)\vee\forall xB(x)\]</span> 为假</p><p>同时 <span class="math display">\[\exists xA(x)\wedge\exists xB(x)\]</span> 为真，但 <span class="math display">\[\exists x(A(x)\wedge B(x))\]</span> 为假</p><h2 id="定义-16.11-一阶逻辑前束范式">定义 16.11 一阶逻辑前束范式</h2><p>若一阶公式写成 <span class="math display">\[Q_1x_1Q_2x_2\cdots Q_nx_nB\]</span> 其中每个 <span class="math inline">\(Q_i\)</span> 是 <spanclass="math inline">\(\forall\)</span> 或 <spanclass="math inline">\(\exists\)</span>，而 <spanclass="math inline">\(B\)</span>中不再含量词，则称该公式为<strong>前束范式</strong></p><p>前面的 <span class="math display">\[Q_1x_1Q_2x_2\cdots Q_nx_n\]</span> 称为量词前缀，<span class="math inline">\(B\)</span>称为母式</p><p>例如： <span class="math display">\[\forall x\exists y(P(x)\to R(x,y))\]</span> 是前束范式</p><p>化前束范式的一般步骤：</p><ol type="1"><li>消去 <span class="math inline">\(\to\)</span> 与 <spanclass="math inline">\(\leftrightarrow\)</span></li><li>用 De Morgan 律和量词否定律把否定号移到原子公式前</li><li>必要时改名约束变项，避免不同量词使用同名变项</li><li>使用量词辖域扩张与收缩等值式，把量词逐步移到公式最前面</li></ol><h3 id="定理-16.11.1-前束范式存在">定理 16.11.1 前束范式存在</h3><p>任意一阶逻辑公式都可以化为与之等值的前束范式</p><details class="gray" data-header-exclude>    <summary><i class="fa-solid fa-chevron-right"></i>证明： 点击查看证明 </summary>    <div class='content markdown-body'>      <p>按前束范式的化法逐步变形</p><p>第一步，利用 <span class="math inline">\(A\to B\equiv \neg A\veeB\)</span> 和 <span class="math inline">\(A\leftrightarrowB\equiv(A\wedge B)\vee(\neg A\wedge\neg B)\)</span> 消去 <spanclass="math inline">\(\to,\leftrightarrow\)</span></p><p>第二步，利用命题逻辑的否定等值式和量词否定等值式： <spanclass="math display">\[\neg\forall xA\equiv\exists x\neg A\]</span> <span class="math display">\[\neg\exists xA\equiv\forall x\neg A\]</span> 把否定号移到原子公式前</p><p>第三步，如果两个量词使用同一个变项，或量词变项与自由变项冲突，则先改名约束变项</p><p>第四步，利用量词辖域扩张与收缩公式，把量词逐步提出到公式最前面</p><p>经过有限步等值变形后，得到形如 <span class="math display">\[Q_1x_1Q_2x_2\cdots Q_nx_nB\]</span> 的公式，其中 <span class="math inline">\(B\)</span>不含量词</p><p>每一步都保持等值，因此所得前束范式与原公式等值</p>    </div>  </details><h3 id="例-16.11.2-化为前束范式">例 16.11.2 化为前束范式</h3><p>将 <span class="math display">\[\forall xP(x)\to \exists yQ(y)\]</span> 化为前束范式。</p><p>先消去蕴含： <span class="math display">\[\forall xP(x)\to \exists yQ(y)\equiv\neg\forall xP(x)\vee \exists yQ(y)\]</span></p><p>用量词否定律： <span class="math display">\[\equiv\exists x\neg P(x)\vee \exists yQ(y)\]</span></p><p>提出量词： <span class="math display">\[\equiv\exists x\exists y(\neg P(x)\vee Q(y))\]</span></p><p>所以其一个前束范式为 <span class="math display">\[\exists x\exists y(\neg P(x)\vee Q(y))\]</span></p>]]></content>
    
    
    <summary type="html">命题逻辑与一阶逻辑</summary>
    
    
    
    <category term="代数结构" scheme="https://exdoubled.github.io/categories/%E4%BB%A3%E6%95%B0%E7%BB%93%E6%9E%84/"/>
    
    
    <category term="笔记" scheme="https://exdoubled.github.io/tags/%E7%AC%94%E8%AE%B0/"/>
    
    <category term="离散数学" scheme="https://exdoubled.github.io/tags/%E7%A6%BB%E6%95%A3%E6%95%B0%E5%AD%A6/"/>
    
    <category term="代数结构与数理逻辑" scheme="https://exdoubled.github.io/tags/%E4%BB%A3%E6%95%B0%E7%BB%93%E6%9E%84%E4%B8%8E%E6%95%B0%E7%90%86%E9%80%BB%E8%BE%91/"/>
    
  </entry>
  
  <entry>
    <title>快速傅里叶变换</title>
    <link href="https://exdoubled.github.io/sf/sf12/"/>
    <id>https://exdoubled.github.io/sf/sf12/</id>
    <published>2026-06-12T09:00:00.000Z</published>
    <updated>2026-06-16T03:21:46.331Z</updated>
    
    <content type="html"><![CDATA[<p>快速傅里叶变换 (Fast Fourier Transform, FFT)是一种高效计算离散傅里叶变换 (Discrete Fourier Transform, DFT)的分治算法</p><p>在算法课中，FFT 作用是把两个次数界为 <spanclass="math inline">\(n\)</span> 的多项式乘法从朴素的 <spanclass="math inline">\(O(n^2)\)</span> 降到：</p><p><span class="math display">\[O(n\log n)\]</span></p><p>更一般地，FFT可以快速计算卷积，因此也会出现在整数乘法、字符串匹配、模式匹配、信号处理等问题中</p><h2 id="多项式乘法">多项式乘法</h2><p>设有两个多项式：</p><p><span class="math display">\[A(x)=a_0+a_1x+\cdots+a_{n-1}x^{n-1}\]</span></p><p><span class="math display">\[B(x)=b_0+b_1x+\cdots+b_{n-1}x^{n-1}\]</span></p><p>它们的乘积为：</p><p><span class="math display">\[C(x)=A(x)B(x)=c_0+c_1x+\cdots+c_{2n-2}x^{2n-2}\]</span></p><p>其中第 <span class="math inline">\(i\)</span> 项系数为：</p><p><span class="math display">\[c_i=\sum_{k=0}^{i}a_kb_{i-k}\]</span></p><p>这里默认越界的系数为 <span class="math inline">\(0\)</span></p><p>这个公式就是卷积。若直接按照定义计算，每个 <spanclass="math inline">\(a_k\)</span> 都可能与每个 <spanclass="math inline">\(b_j\)</span> 相乘，运行时间为：</p><p><span class="math display">\[O(n^2)\]</span></p><hr /><h2 id="多项式的两种表示">多项式的两种表示</h2><h3 id="系数表示">系数表示</h3><p>系数表示就是直接保存：</p><p><span class="math display">\[(a_0,a_1,\ldots,a_{n-1})\]</span></p><p>这种表示下：</p><ul><li>多项式加法很容易，逐项相加即可，时间 <spanclass="math inline">\(O(n)\)</span></li><li>多项式在单点求值可以用 Horner 法，时间 <spanclass="math inline">\(O(n)\)</span></li><li>多项式乘法朴素做法需要 <spanclass="math inline">\(O(n^2)\)</span></li></ul><p>Horner 法可以写成：</p><p><span class="math display">\[A(x_0)=a_0+x_0(a_1+x_0(a_2+\cdots+x_0a_{n-1})\cdots)\]</span></p><p>它只需要线性次乘加</p><h3 id="点值表示">点值表示</h3><p>一个次数界为 <span class="math inline">\(n\)</span> 的多项式也可以用<span class="math inline">\(n\)</span> 个点值对表示：</p><p><span class="math display">\[\{(x_0,y_0),(x_1,y_1),\ldots,(x_{n-1},y_{n-1})\}\]</span></p><p>其中 <span class="math inline">\(x_0,x_1,\ldots,x_{n-1}\)</span>两两不同，并且：</p><p><span class="math display">\[y_k=A(x_k)\]</span></p><p><span class="math inline">\(n\)</span>个互不相同点上的取值可以唯一确定一个次数界为 <spanclass="math inline">\(n\)</span> 的多项式</p><p>这个结论可以从范德蒙德矩阵看出来。把求值关系写成矩阵：</p><p><span class="math display">\[\begin{bmatrix}1 &amp; x_0 &amp; x_0^2 &amp; \cdots &amp; x_0^{n-1}\\1 &amp; x_1 &amp; x_1^2 &amp; \cdots &amp; x_1^{n-1}\\\vdots &amp; \vdots &amp; \vdots &amp; \ddots &amp; \vdots\\1 &amp; x_{n-1} &amp; x_{n-1}^2 &amp; \cdots &amp; x_{n-1}^{n-1}\end{bmatrix}\begin{bmatrix}a_0\\a_1\\\vdots\\a_{n-1}\end{bmatrix}=\begin{bmatrix}y_0\\y_1\\\vdots\\y_{n-1}\end{bmatrix}\]</span></p><p>左边矩阵是范德蒙德矩阵。它的行列式为：</p><p><span class="math display">\[\prod_{0\leq j&lt;k\leq n-1}(x_k-x_j)\]</span></p><p>只要所有 <span class="math inline">\(x_k\)</span>互不相同，行列式就非零，因此矩阵可逆，系数唯一存在</p><h3 id="点值表示下的乘法">点值表示下的乘法</h3><p>如果：</p><p><span class="math display">\[C(x)=A(x)B(x)\]</span></p><p>那么对任意点 <span class="math inline">\(x_k\)</span> 都有：</p><p><span class="math display">\[C(x_k)=A(x_k)B(x_k)\]</span></p><p>也就是说，在相同求值点上，只要逐点相乘就可以得到乘积多项式的点值表示。</p><p>但有一个次数问题：若 <span class="math inline">\(A\)</span> 和 <spanclass="math inline">\(B\)</span> 的次数界都是 <spanclass="math inline">\(n\)</span>，则 <spanclass="math inline">\(C\)</span> 的次数界是 <spanclass="math inline">\(2n-1\)</span> 或 <spanclass="math inline">\(2n\)</span> 量级，为了唯一确定 <spanclass="math inline">\(C\)</span>，需要至少 <spanclass="math inline">\(2n-1\)</span>个点值，实际算法中通常取一个方便的长度 <span class="math inline">\(N\geq2n-1\)</span></p><p>因此，用点值表示做多项式乘法的思路是：</p><ol type="1"><li>把 <span class="math inline">\(A,B\)</span>从系数表示转换为点值表示</li><li>逐点相乘</li><li>把乘积从点值表示插值回系数表示</li></ol><p>如果第一步和第三步都能在 <span class="math inline">\(O(n\logn)\)</span> 时间内完成，总乘法就能达到 <spanclass="math inline">\(O(n\log n)\)</span></p><p>FFT 的作用是高效完成这两种表示之间的转换</p><hr /><h2 id="单位复数根">单位复数根</h2><p>FFT 的特殊求值点来自单位复数根</p><h3 id="定义">定义</h3><p><span class="math inline">\(n\)</span> 次单位复数根是满足：</p><p><span class="math display">\[\omega^n=1\]</span></p><p>的复数。它们一共有 <span class="math inline">\(n\)</span>个，均匀分布在复平面的单位圆上：</p><p><span class="math display">\[1,\omega_n,\omega_n^2,\ldots,\omega_n^{n-1}\]</span></p><p>其中：</p><p><span class="math display">\[\omega_n=e^{2\pi i/n}\]</span></p><p>称为主 <span class="math inline">\(n\)</span> 次单位根。</p><p>由欧拉公式：</p><p><span class="math display">\[e^{i\theta}=\cos\theta+i\sin\theta\]</span></p><p>可知：</p><p><span class="math display">\[\omega_n=\cos\frac{2\pi}{n}+i\sin\frac{2\pi}{n}\]</span></p><h3 id="消去性质">消去性质</h3><p>对任意正整数 <span class="math inline">\(d\)</span>，有：</p><p><span class="math display">\[\omega_{dn}^{dk}=\omega_n^k\]</span></p><p>因为：</p><p><span class="math display">\[\omega_{dn}^{dk}=\left(e^{2\pi i/(dn)}\right)^{dk}=e^{2\pi ik/n}=\omega_n^k\]</span></p><p>这个性质说明，高阶单位根的某些幂会退化成低阶单位根</p><h3 id="折半性质">折半性质</h3><p>当 <span class="math inline">\(n\)</span> 为偶数时，<spanclass="math inline">\(n\)</span> 个 <spanclass="math inline">\(n\)</span> 次单位根的平方正好是 <spanclass="math inline">\(n/2\)</span> 个 <spanclass="math inline">\(n/2\)</span> 次单位根，并且每个出现两次：</p><p><span class="math display">\[(\omega_n^k)^2=\omega_n^{2k}=\omega_{n/2}^k\]</span></p><p>并且：</p><p><span class="math display">\[(\omega_n^{k+n/2})^2=\omega_n^{2k+n}=\omega_n^{2k}\]</span></p><p>FFT 分治正是利用这个性质：把 <span class="math inline">\(n\)</span>个求值点平方后，只剩下 <span class="math inline">\(n/2\)</span>个不同的求值点</p><h3 id="求和性质">求和性质</h3><p><span class="math display">\[\sum_{j=0}^{n-1}\omega_n^{jk}=\begin{cases}n &amp; n\mid k\\0 &amp; n\nmid k\end{cases}\]</span></p><p>当 <span class="math inline">\(n\nmid k\)</span>时，这是等比数列：</p><p><span class="math display">\[\sum_{j=0}^{n-1}\omega_n^{jk}=\frac{1-(\omega_n^k)^n}{1-\omega_n^k}=0\]</span></p><p>因为 <span class="math inline">\((\omega_n^k)^n=1\)</span>，但 <spanclass="math inline">\(\omega_n^k\neq 1\)</span></p><hr /><h2 id="dft">DFT</h2><p>给定系数向量：</p><p><span class="math display">\[a=(a_0,a_1,\ldots,a_{n-1})\]</span></p><p>定义多项式：</p><p><span class="math display">\[A(x)=\sum_{j=0}^{n-1}a_jx^j\]</span></p><p>它的离散傅里叶变换 DFT 是向量：</p><p><span class="math display">\[y=(y_0,y_1,\ldots,y_{n-1})\]</span></p><p>其中：</p><p><span class="math display">\[y_k=A(\omega_n^k)=\sum_{j=0}^{n-1}a_j\omega_n^{jk},\qquad 0\leq k&lt;n\]</span></p><p>所以 DFT 本质上就是把多项式 <span class="math inline">\(A(x)\)</span>在 <span class="math inline">\(n\)</span> 个单位复数根处求值</p><p>用矩阵表示：</p><p><span class="math display">\[\begin{bmatrix}y_0\\y_1\\\vdots\\y_{n-1}\end{bmatrix}=\begin{bmatrix}1 &amp; 1 &amp; 1 &amp; \cdots &amp; 1\\1 &amp; \omega_n &amp; \omega_n^2 &amp; \cdots &amp; \omega_n^{n-1}\\1 &amp; \omega_n^2 &amp; \omega_n^4 &amp; \cdots &amp;\omega_n^{2(n-1)}\\\vdots &amp; \vdots &amp; \vdots &amp; \ddots &amp; \vdots\\1 &amp; \omega_n^{n-1} &amp; \omega_n^{2(n-1)} &amp; \cdots &amp;\omega_n^{(n-1)(n-1)}\end{bmatrix}\begin{bmatrix}a_0\\a_1\\\vdots\\a_{n-1}\end{bmatrix}\]</span></p><p>这仍然是一个范德蒙德矩阵，只是求值点被固定为：</p><p><span class="math display">\[1,\omega_n,\omega_n^2,\ldots,\omega_n^{n-1}\]</span></p><p>朴素计算这个矩阵乘法需要：</p><p><span class="math display">\[O(n^2)\]</span></p><p>FFT 的目标是把它降到：</p><p><span class="math display">\[O(n\log n)\]</span></p><hr /><h2 id="fft-的分治思想">FFT 的分治思想</h2><p>设 <span class="math inline">\(n\)</span> 是 <spanclass="math inline">\(2\)</span>的幂。把多项式按系数下标奇偶拆成两部分：</p><p><span class="math display">\[A^{[0]}(x)=a_0+a_2x+a_4x^2+\cdots+a_{n-2}x^{n/2-1}\]</span></p><p><span class="math display">\[A^{[1]}(x)=a_1+a_3x+a_5x^2+\cdots+a_{n-1}x^{n/2-1}\]</span></p><p>则：</p><p><span class="math display">\[A(x)=A^{[0]}(x^2)+xA^{[1]}(x^2)\]</span></p><p>现在要计算：</p><p><span class="math display">\[A(\omega_n^0),A(\omega_n^1),\ldots,A(\omega_n^{n-1})\]</span></p><p>代入上式：</p><p><span class="math display">\[A(\omega_n^k)=A^{[0]}((\omega_n^k)^2)+\omega_n^kA^{[1]}((\omega_n^k)^2)\]</span></p><p>由于：</p><p><span class="math display">\[(\omega_n^k)^2=\omega_{n/2}^k\]</span></p><p>因此只需要递归地把 <span class="math inline">\(A^{[0]}\)</span> 和<span class="math inline">\(A^{[1]}\)</span> 在 <spanclass="math inline">\(n/2\)</span> 个 <spanclass="math inline">\(n/2\)</span> 次单位根处求值</p><h3 id="合并公式">合并公式</h3><p>令：</p><p><span class="math display">\[y_k^{[0]}=A^{[0]}(\omega_{n/2}^k)\]</span></p><p><span class="math display">\[y_k^{[1]}=A^{[1]}(\omega_{n/2}^k)\]</span></p><p>对 <span class="math inline">\(0\leq k&lt;n/2\)</span>，有：</p><p><span class="math display">\[y_k=A(\omega_n^k)=y_k^{[0]}+\omega_n^k y_k^{[1]}\]</span></p><p>同时：</p><p><span class="math display">\[y_{k+n/2}=A(\omega_n^{k+n/2})\]</span></p><p>注意：</p><p><span class="math display">\[\omega_n^{k+n/2}=\omega_n^k\omega_n^{n/2}=-\omega_n^k\]</span></p><p>而：</p><p><span class="math display">\[(\omega_n^{k+n/2})^2=(\omega_n^k)^2\]</span></p><p>所以：</p><p><span class="math display">\[y_{k+n/2}=y_k^{[0]}-\omega_n^k y_k^{[1]}\]</span></p><p>因此一次递归得到两个长度为 <span class="math inline">\(n/2\)</span>的 DFT 后，只需要 <span class="math inline">\(O(n)\)</span>时间就能合并成长度为 <span class="math inline">\(n\)</span> 的 DFT</p><hr /><h2 id="递归-fft">递归 FFT</h2><p>递归算法可以写成：</p><div class="code-container" data-rel="Txt"><figure class="iseeu highlight txt"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">RECURSIVE-FFT(a)</span><br><span class="line">    n = a.length</span><br><span class="line">    if n == 1</span><br><span class="line">        return a</span><br><span class="line"></span><br><span class="line">    omega_n = exp(2*pi*i/n)</span><br><span class="line">    omega = 1</span><br><span class="line"></span><br><span class="line">    a_even = (a_0, a_2, ..., a_&#123;n-2&#125;)</span><br><span class="line">    a_odd  = (a_1, a_3, ..., a_&#123;n-1&#125;)</span><br><span class="line"></span><br><span class="line">    y_even = RECURSIVE-FFT(a_even)</span><br><span class="line">    y_odd  = RECURSIVE-FFT(a_odd)</span><br><span class="line"></span><br><span class="line">    for k = 0 to n/2 - 1</span><br><span class="line">        y_k       = y_even_k + omega * y_odd_k</span><br><span class="line">        y_&#123;k+n/2&#125; = y_even_k - omega * y_odd_k</span><br><span class="line">        omega = omega * omega_n</span><br><span class="line"></span><br><span class="line">    return y</span><br></pre></td></tr></table></figure></div><h3 id="时间复杂度">时间复杂度</h3><p>每一层递归做两次规模为 <span class="math inline">\(n/2\)</span> 的FFT，再做 <span class="math inline">\(O(n)\)</span> 的合并：</p><p><span class="math display">\[T(n)=2T(n/2)+O(n)\]</span></p><p>由主定理得到：</p><p><span class="math display">\[T(n)=O(n\log n)\]</span></p><p>递归树也可以直观看出这一点：</p><ul><li>第 <span class="math inline">\(0\)</span> 层总规模 <spanclass="math inline">\(n\)</span></li><li>第 <span class="math inline">\(1\)</span> 层两个子问题，总规模仍为<span class="math inline">\(n\)</span></li><li>第 <span class="math inline">\(2\)</span> 层四个子问题，总规模仍为<span class="math inline">\(n\)</span></li><li>一共有 <span class="math inline">\(\log n\)</span> 层</li></ul><p>所以总时间为：</p><p><span class="math display">\[O(n\log n)\]</span></p><h3 id="关于-n-是-2-的幂">关于 <span class="math inline">\(n\)</span> 是<span class="math inline">\(2\)</span> 的幂</h3><p>上面的递归 FFT 假设 <span class="math inline">\(n\)</span> 是 <spanclass="math inline">\(2\)</span> 的幂</p><p>在多项式乘法中，这通常不是问题：可以把系数向量补零到不小于结果长度的下一个<span class="math inline">\(2\)</span> 的幂</p><p>例如两个长度为 <span class="math inline">\(n\)</span>的系数向量相乘，结果长度最多为 <spanclass="math inline">\(2n-1\)</span>，可以取：</p><p><span class="math display">\[N=2^{\lceil \log_2(2n-1)\rceil}\]</span></p><p>然后把两个输入都补到长度 <span class="math inline">\(N\)</span></p><p>但需要注意，这不是说“任意长度的 DFT 都可以无损地当作长度为 <spanclass="math inline">\(2\)</span> 的幂”</p><p>假设 <span class="math inline">\(n\)</span> 是 <spanclass="math inline">\(2\)</span> 的幂并不是完全不失一般性。对任意长度DFT，需要更一般的 FFT变体或不同的分解方法；只是对本节的多项式乘法应用，补零已经足够</p><hr /><h2 id="逆-dft">逆 DFT</h2><p>DFT 把系数向量 <span class="math inline">\(a\)</span> 变成点值向量<span class="math inline">\(y\)</span>，逆 DFT 要做相反的事：给定：</p><p><span class="math display">\[y_0,y_1,\ldots,y_{n-1}\]</span></p><p>恢复：</p><p><span class="math display">\[a_0,a_1,\ldots,a_{n-1}\]</span></p><p>由上面的矩阵变换得：</p><p><span class="math display">\[a_j=\frac{1}{n}\sum_{k=0}^{n-1}y_k\omega_n^{-jk}\]</span></p><p>也就是说，逆 DFT 与 DFT 几乎一样，只需要把 <spanclass="math inline">\(\omega_n\)</span> 换成 <spanclass="math inline">\(\omega_n^{-1}\)</span>，最后再除以 <spanclass="math inline">\(n\)</span></p><h3 id="公式证明">公式证明</h3><p>把 <span class="math inline">\(y_k\)</span> 展开：</p><p><span class="math display">\[\begin{aligned}\frac{1}{n}\sum_{k=0}^{n-1}y_k\omega_n^{-jk}&amp;=\frac{1}{n}\sum_{k=0}^{n-1}\left(\sum_{\ell=0}^{n-1}a_\ell\omega_n^{\ell k}\right)\omega_n^{-jk}\\&amp;=\frac{1}{n}\sum_{\ell=0}^{n-1}a_\ell\sum_{k=0}^{n-1}\omega_n^{(\ell-j)k}\end{aligned}\]</span></p><p>由单位根求和性质：</p><p><span class="math display">\[\sum_{k=0}^{n-1}\omega_n^{(\ell-j)k}=\begin{cases}n &amp; \ell=j\\0 &amp; \ell\neq j\end{cases}\]</span></p><p>所以最后只剩下：</p><p><span class="math display">\[a_j\]</span></p><p>因此逆 DFT 的公式成立</p><h3 id="用-fft-计算逆-dft">用 FFT 计算逆 DFT</h3><p>逆 DFT 可以直接复用 FFT 框架：</p><div class="code-container" data-rel="Txt"><figure class="iseeu highlight txt"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">INVERSE-FFT(y)</span><br><span class="line">    run FFT on y using omega_n^&#123;-1&#125;</span><br><span class="line">    divide every output by n</span><br></pre></td></tr></table></figure></div><p>时间复杂度仍然是：</p><p><span class="math display">\[O(n\log n)\]</span></p><hr /><h2 id="fft-多项式乘法">FFT 多项式乘法</h2><p>现在可以把前面的内容组合起来</p><p>输入：</p><p><span class="math display">\[A(x)=\sum_{i=0}^{n-1}a_ix^i\]</span></p><p><span class="math display">\[B(x)=\sum_{i=0}^{n-1}b_ix^i\]</span></p><p>目标是输出：</p><p><span class="math display">\[C(x)=A(x)B(x)\]</span></p><p>算法：</p><div class="code-container" data-rel="Txt"><figure class="iseeu highlight txt"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">FFT-POLYNOMIAL-MULTIPLY(a, b)</span><br><span class="line">    N = the smallest power of 2 with N &gt;= len(a) + len(b) - 1</span><br><span class="line"></span><br><span class="line">    pad a and b with zeros to length N</span><br><span class="line"></span><br><span class="line">    A = FFT(a)</span><br><span class="line">    B = FFT(b)</span><br><span class="line"></span><br><span class="line">    for k = 0 to N - 1</span><br><span class="line">        C_k = A_k * B_k</span><br><span class="line"></span><br><span class="line">    c = INVERSE-FFT(C)</span><br><span class="line"></span><br><span class="line">    return c_0, c_1, ..., c_&#123;len(a)+len(b)-2&#125;</span><br></pre></td></tr></table></figure></div><h3 id="正确性">正确性</h3><p>补零后，<span class="math inline">\(A\)</span> 和 <spanclass="math inline">\(B\)</span> 都被看成次数界为 <spanclass="math inline">\(N\)</span> 的多项式。由于：</p><p><span class="math display">\[N\geq \deg(A)+\deg(B)+1\]</span></p><p>乘积 <span class="math inline">\(C\)</span> 的系数可以被长度 <spanclass="math inline">\(N\)</span> 的向量完整容纳</p><p>FFT 求出的是：</p><p><span class="math display">\[A(\omega_N^k),\quad B(\omega_N^k)\]</span></p><p>逐点相乘得到：</p><p><span class="math display">\[A(\omega_N^k)B(\omega_N^k)=C(\omega_N^k)\]</span></p><p>也就是 <span class="math inline">\(C\)</span> 在 <spanclass="math inline">\(N\)</span> 个不同单位根处的点值表示</p><p>由于次数界为 <span class="math inline">\(N\)</span> 的多项式由 <spanclass="math inline">\(N\)</span> 个不同点值唯一确定，逆 DFT插值回来的系数就是 <span class="math inline">\(C(x)\)</span> 的系数</p><h3 id="复杂度">复杂度</h3><p>算法包含：</p><ul><li>两次 FFT：<span class="math inline">\(2O(N\log N)\)</span></li><li>一次逐点相乘：<span class="math inline">\(O(N)\)</span></li><li>一次逆 FFT：<span class="math inline">\(O(N\log N)\)</span></li></ul><p>所以总时间为：</p><p><span class="math display">\[O(N\log N)\]</span></p><p>当 <span class="math inline">\(N=O(n)\)</span> 时：</p><p><span class="math display">\[O(n\log n)\]</span></p><p>空间复杂度通常为：</p><p><span class="math display">\[O(N)\]</span></p><p>若递归实现中频繁复制数组，实际可能产生额外空间；工程实现中通常使用迭代原地FFT</p><hr /><h2 id="卷积">卷积</h2><p>对两个序列：</p><p><span class="math display">\[a=(a_0,a_1,\ldots,a_{n-1})\]</span></p><p><span class="math display">\[b=(b_0,b_1,\ldots,b_{m-1})\]</span></p><p>它们的线性卷积定义为：</p><p><span class="math display">\[(a*b)_t=\sum_i a_i b_{t-i}\]</span></p><p>其中越界项视为 <span class="math inline">\(0\)</span></p><p>如果构造多项式：</p><p><span class="math display">\[A(x)=\sum_i a_ix^i,\qquad B(x)=\sum_i b_ix^i\]</span></p><p>那么：</p><p><span class="math display">\[A(x)B(x)=\sum_t (a*b)_t x^t\]</span></p><p>所以卷积就是多项式乘法的系数</p><p>因此，FFT 可以在：</p><p><span class="math display">\[O((n+m)\log(n+m))\]</span></p><p>时间内计算线性卷积</p><h3 id="线性卷积和循环卷积">线性卷积和循环卷积</h3><p>DFT 本身天然对应长度为 <span class="math inline">\(N\)</span>的循环卷积</p><p>如果不补零，超过 <span class="math inline">\(N-1\)</span>次的高次项会折回低次项，相当于在模 <spanclass="math inline">\(x^N-1\)</span> 意义下相乘</p><p>为了得到普通的线性卷积，必须取：</p><p><span class="math display">\[N\geq n+m-1\]</span></p><p>并把两个序列都补零到长度 <span class="math inline">\(N\)</span></p><p>这也是多项式乘法算法中要先补零的原因</p><hr /><h2 id="应用一二进制串中的三等距-1">应用一：二进制串中的三等距 1</h2><h3 id="问题">问题</h3><p>给定长度为 <span class="math inline">\(n\)</span> 的二进制串 <spanclass="math inline">\(S\)</span>，记：</p><p><span class="math display">\[L=\{i:S[i]=1\}\]</span></p><p>判断是否存在三个位置：</p><p><span class="math display">\[a&lt;b&lt;c\]</span></p><p>满足：</p><p><span class="math display">\[S[a]=S[b]=S[c]=1\]</span></p><p>并且三者等距：</p><p><span class="math display">\[b-a=c-b\]</span></p><p>等价地：</p><p><span class="math display">\[a+c=2b\]</span></p><p>例如：</p><ul><li><code>11100000</code> 中有位置 <spanclass="math inline">\(0,1,2\)</span></li><li><code>110110010</code> 中有位置 <spanclass="math inline">\(1,4,7\)</span></li><li><code>1011</code> 中没有三个等距的 <spanclass="math inline">\(1\)</span></li></ul><h3 id="朴素算法">朴素算法</h3><p>直接枚举起点 <span class="math inline">\(i\)</span> 和间距 <spanclass="math inline">\(d\)</span>：</p><p><span class="math display">\[i,\ i+d,\ i+2d\]</span></p><p>检查这三个位置是否都是 <span class="math inline">\(1\)</span></p><p>起点和间距各有 <span class="math inline">\(O(n)\)</span>种，时间复杂度为：</p><p><span class="math display">\[O(n^2)\]</span></p><h3 id="卷积算法">卷积算法</h3><p>构造多项式：</p><p><span class="math display">\[P(x)=\sum_{i\in L}x^i\]</span></p><p>计算：</p><p><span class="math display">\[Q(x)=P(x)^2\]</span></p><p>设：</p><p><span class="math display">\[Q(x)=\sum_t q_tx^t\]</span></p><p>则 <span class="math inline">\(q_t\)</span> 表示有多少个有序对 <spanclass="math inline">\((a,c)\)</span> 满足：</p><p><span class="math display">\[a,c\in L,\qquad a+c=t\]</span></p><p>现在固定一个中点 <span class="math inline">\(b\inL\)</span>。如果存在 <span class="math inline">\(a,c\in L\)</span>使得：</p><p><span class="math display">\[a+c=2b,\qquad a\neq c\]</span></p><p>那么：</p><p><span class="math display">\[a,b,c\]</span></p><p>就是三个等距的 <span class="math inline">\(1\)</span></p><p>为什么检查 <span class="math inline">\(q_{2b}\geq 3\)</span>？</p><ul><li>对 <span class="math inline">\((b,b)\)</span>，一定贡献 <spanclass="math inline">\(1\)</span></li><li>如果存在 <span class="math inline">\(a\neq c\)</span> 且 <spanclass="math inline">\(a+c=2b\)</span>，那么 <spanclass="math inline">\((a,c)\)</span> 和 <spanclass="math inline">\((c,a)\)</span> 会再贡献 <spanclass="math inline">\(2\)</span></li></ul><p>所以：</p><p><span class="math display">\[q_{2b}\geq 3\]</span></p><p>当且仅当存在以 <span class="math inline">\(b\)</span>为中点的非平凡等距三元组</p><p>算法：</p><div class="code-container" data-rel="Txt"><figure class="iseeu highlight txt"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">THREE-EVENLY-SPACED-ONES(S)</span><br><span class="line">    L = &#123;i : S[i] = 1&#125;</span><br><span class="line">    build P(x) = sum_&#123;i in L&#125; x^i</span><br><span class="line">    Q = P * P using FFT</span><br><span class="line"></span><br><span class="line">    for each b in L</span><br><span class="line">        if coefficient Q[2b] &gt;= 3</span><br><span class="line">            return YES</span><br><span class="line"></span><br><span class="line">    return NO</span><br></pre></td></tr></table></figure></div><p>构造和扫描是 <span class="math inline">\(O(n)\)</span>，FFT 乘法是<span class="math inline">\(O(n\log n)\)</span>，所以总时间为：</p><p><span class="math display">\[O(n\log n)\]</span></p><hr /><h2 id="应用二字符串匹配">应用二：字符串匹配</h2><h3 id="问题-1">问题</h3><p>给定文本：</p><p><span class="math display">\[t[0],t[1],\ldots,t[n-1]\]</span></p><p>和模式串：</p><p><span class="math display">\[p[0],p[1],\ldots,p[m-1]\]</span></p><p>目标是找所有位移 <span class="math inline">\(i\)</span>，使得：</p><p><span class="math display">\[t[i+j]=p[j],\qquad 0\leq j&lt;m\]</span></p><p>也就是：</p><p><span class="math display">\[t[i:i+m-1]=p\]</span></p><p>朴素算法对每个位置比较 <span class="math inline">\(m\)</span>个字符，最坏时间为：</p><p><span class="math display">\[O(nm)\]</span></p><p>如果把字符编码为数字，可以用卷积一次性计算所有位置的匹配分数。</p><h3 id="匹配分数">匹配分数</h3><p>把每个字符编码成一个整数。对位移 <spanclass="math inline">\(i\)</span> 定义：</p><p><span class="math display">\[X[i]=\sum_{j=0}^{m-1}(t[i+j]-p[j])^2\]</span></p><p>显然：</p><p><span class="math display">\[X[i]=0\]</span></p><p>当且仅当所有对应字符都相同，即位移 <spanclass="math inline">\(i\)</span> 是一个匹配</p><p>展开平方：</p><p><span class="math display">\[\begin{aligned}X[i]&amp;=\sum_{j=0}^{m-1}t[i+j]^2-2\sum_{j=0}^{m-1}p[j]t[i+j]+\sum_{j=0}^{m-1}p[j]^2\end{aligned}\]</span></p><p>其中：</p><ul><li><span class="math inline">\(\sum t[i+j]^2\)</span> 可以用前缀和在<span class="math inline">\(O(1)\)</span> 时间得到每个 <spanclass="math inline">\(i\)</span> 的值</li><li><span class="math inline">\(\sum p[j]^2\)</span> 是常数</li><li>难点是交叉项</li></ul><p>令：</p><p><span class="math display">\[Y[i]=\sum_{j=0}^{m-1}p[j]t[i+j]\]</span></p><p>只要能快速算出所有 <spanclass="math inline">\(Y[i]\)</span>，就能算出所有 <spanclass="math inline">\(X[i]\)</span></p><h3 id="用卷积计算交叉项">用卷积计算交叉项</h3><p>令 <span class="math inline">\(t^*\)</span> 是 <spanclass="math inline">\(t\)</span> 的反转：</p><p><span class="math display">\[t^*[k]=t[n-1-k]\]</span></p><p>构造两个多项式：</p><p><span class="math display">\[F(x)=\sum_{j=0}^{m-1}p[j]x^j\]</span></p><p><span class="math display">\[G(x)=\sum_{k=0}^{n-1}t^*[k]x^k\]</span></p><p>看乘积 <span class="math inline">\(H(x)=F(x)G(x)\)</span> 中 <spanclass="math inline">\(x^{n-1-i}\)</span> 的系数：</p><p><span class="math display">\[\begin{aligned}[x^{n-1-i}]H(x)&amp;=\sum_{j=0}^{m-1}p[j]t^*[n-1-i-j]\\&amp;=\sum_{j=0}^{m-1}p[j]t[i+j]\\&amp;=Y[i]\end{aligned}\]</span></p><p>因此一次卷积就能得到所有位移的交叉项</p><p>算法：</p><div class="code-container" data-rel="Txt"><figure class="iseeu highlight txt"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">FFT-STRING-MATCHING(t, p)</span><br><span class="line">    encode characters as numbers</span><br><span class="line"></span><br><span class="line">    compute prefix sums of t[k]^2</span><br><span class="line">    const_p = sum p[j]^2</span><br><span class="line"></span><br><span class="line">    F = polynomial with coefficients p[0], ..., p[m-1]</span><br><span class="line">    G = polynomial with coefficients reverse(t)</span><br><span class="line">    H = F * G using FFT</span><br><span class="line"></span><br><span class="line">    for i = 0 to n - m</span><br><span class="line">        cross = coefficient H[n - 1 - i]</span><br><span class="line">        text_part = sum_&#123;k=i&#125;^&#123;i+m-1&#125; t[k]^2</span><br><span class="line">        X = text_part - 2 * cross + const_p</span><br><span class="line">        if X == 0</span><br><span class="line">            report i</span><br></pre></td></tr></table></figure></div><p>总时间为：</p><p><span class="math display">\[O(n\log n)\]</span></p><p>这里默认字符编码满足：两个字符相同当且仅当编码相同</p><hr /><h2 id="带通配符的字符串匹配">带通配符的字符串匹配</h2><p>若模式串中允许通配符 <code>*</code>，它可以匹配任意字符</p><p>可以把通配符位置设为 <spanclass="math inline">\(0\)</span>，普通字符编码为非零数</p><p>定义新的匹配分数： <span class="math display">\[X[i]=\sum_{j=0}^{m-1}p[j](t[i+j]-p[j])^2\]</span></p><p>如果 <span class="math inline">\(p[j]=0\)</span>，这一项自动为 <spanclass="math inline">\(0\)</span>，表示通配符不产生约束</p><p>如果 <span class="math inline">\(p[j]\neq 0\)</span>，这一项为 <spanclass="math inline">\(0\)</span> 当且仅当：</p><p><span class="math display">\[t[i+j]=p[j]\]</span></p><p>因此：</p><p><span class="math display">\[X[i]=0\]</span></p><p>当且仅当模式串在位置 <span class="math inline">\(i\)</span>匹配文本</p><p>展开：</p><p><span class="math display">\[\begin{aligned}X[i]&amp;=\sum_{j=0}^{m-1}p[j]t[i+j]^2-2\sum_{j=0}^{m-1}p[j]^2t[i+j]+\sum_{j=0}^{m-1}p[j]^3\end{aligned}\]</span></p><p>这里需要两类交叉相关：</p><p><span class="math display">\[\sum p[j]t[i+j]^2\]</span></p><p>和：</p><p><span class="math display">\[\sum p[j]^2t[i+j]\]</span></p><p>它们都可以用“模式系数多项式乘以反转文本系数多项式”的方式通过 FFT求出</p><p>具体地：</p><ul><li>用 <span class="math inline">\(p[j]\)</span> 和 <spanclass="math inline">\(t[k]^2\)</span> 的反转计算第一项</li><li>用 <span class="math inline">\(p[j]^2\)</span> 和 <spanclass="math inline">\(t[k]\)</span> 的反转计算第二项</li><li><span class="math inline">\(\sum p[j]^3\)</span> 是常数</li></ul><p>因此带通配符的版本仍然可以在：</p><p><span class="math display">\[O(n\log n)\]</span></p><p>时间内完成</p><hr /><h2 id="迭代-fft-补充">迭代 FFT 补充</h2><p>核心思想是：递归不断按偶数下标和奇数下标拆分，等价于按照下标的二进制位逆序重新排列输入。</p><p>例如 <span class="math inline">\(n=8\)</span>时，下标的三位二进制和位逆序为：</p><table><thead><tr><th style="text-align: center;">原下标</th><th style="text-align: center;">二进制</th><th style="text-align: center;">位逆序</th><th style="text-align: center;">新位置</th></tr></thead><tbody><tr><td style="text-align: center;">0</td><td style="text-align: center;">000</td><td style="text-align: center;">000</td><td style="text-align: center;">0</td></tr><tr><td style="text-align: center;">1</td><td style="text-align: center;">001</td><td style="text-align: center;">100</td><td style="text-align: center;">4</td></tr><tr><td style="text-align: center;">2</td><td style="text-align: center;">010</td><td style="text-align: center;">010</td><td style="text-align: center;">2</td></tr><tr><td style="text-align: center;">3</td><td style="text-align: center;">011</td><td style="text-align: center;">110</td><td style="text-align: center;">6</td></tr><tr><td style="text-align: center;">4</td><td style="text-align: center;">100</td><td style="text-align: center;">001</td><td style="text-align: center;">1</td></tr><tr><td style="text-align: center;">5</td><td style="text-align: center;">101</td><td style="text-align: center;">101</td><td style="text-align: center;">5</td></tr><tr><td style="text-align: center;">6</td><td style="text-align: center;">110</td><td style="text-align: center;">011</td><td style="text-align: center;">3</td></tr><tr><td style="text-align: center;">7</td><td style="text-align: center;">111</td><td style="text-align: center;">111</td><td style="text-align: center;">7</td></tr></tbody></table><p>先做位逆序置换后，再按长度 <spanclass="math inline">\(2,4,8,\ldots,n\)</span> 逐层合并。</p><p>每个合并单元都由若干个“蝴蝶操作”组成。设当前块长度为 <spanclass="math inline">\(m\)</span>，半长为 <spanclass="math inline">\(m/2\)</span>，旋转因子为 <spanclass="math inline">\(w\)</span>，则一对输入：</p><p><span class="math display">\[u=a[k+j]\]</span></p><p><span class="math display">\[v=w\cdot a[k+j+m/2]\]</span></p><p>输出为：</p><p><span class="math display">\[a[k+j]=u+v\]</span></p><p><span class="math display">\[a[k+j+m/2]=u-v\]</span></p><p>这正是递归合并公式：</p><p><span class="math display">\[y_k=y_k^{[0]}+\omega y_k^{[1]}\]</span></p><p><span class="math display">\[y_{k+n/2}=y_k^{[0]}-\omega y_k^{[1]}\]</span></p><p>迭代实现的优势是：</p><ul><li>可以原地计算</li><li>常数较小</li><li>更容易控制内存</li><li>工程库通常会进一步优化缓存和旋转因子计算</li></ul><hr /><h2 id="数值精度与整数卷积">数值精度与整数卷积</h2><p>标准 FFT 使用复数，因此会有浮点误差</p><p>如果输入是整数，理论上卷积结果也是整数。工程实现中常见做法是：</p><ol type="1"><li>使用双精度或长双精度复数 FFT</li><li>逆 FFT 后把接近整数的结果四舍五入</li><li>忽略很小的虚部误差</li></ol><p>当系数很大或长度很长时，浮点误差可能累积，需要更谨慎的处理。</p><p>如果必须精确计算整数卷积，可以使用模意义下的 FFT，也常称为 NTT(Number TheoreticTransform)。它把复数单位根替换成模素数意义下的原根，从而避免浮点误差</p><p>只要在某个代数结构中存在合适的主 <spanclass="math inline">\(n\)</span> 次单位根，并且 <spanclass="math inline">\(n\)</span> 可逆，就可以定义 DFT、逆 DFT和对应的快速算法</p><hr /><h2 id="小结">小结</h2><p>本节核心结论：</p><table><thead><tr><th style="text-align: center;">内容</th><th style="text-align: center;">结论</th></tr></thead><tbody><tr><td style="text-align: center;">系数表示</td><td style="text-align: center;">多项式直接用系数向量表示，乘法朴素为<span class="math inline">\(O(n^2)\)</span></td></tr><tr><td style="text-align: center;">点值表示</td><td style="text-align: center;">在相同点上相乘即可得到乘积点值</td></tr><tr><td style="text-align: center;">插值唯一性</td><td style="text-align: center;"><span class="math inline">\(n\)</span>个不同点值唯一确定次数界为 <span class="math inline">\(n\)</span>的多项式</td></tr><tr><td style="text-align: center;">DFT</td><td style="text-align: center;">在 <spanclass="math inline">\(n\)</span> 个单位复数根处求值</td></tr><tr><td style="text-align: center;">FFT</td><td style="text-align: center;">用奇偶拆分和单位根折半性质在 <spanclass="math inline">\(O(n\log n)\)</span> 时间计算 DFT</td></tr><tr><td style="text-align: center;">逆 DFT</td><td style="text-align: center;">用 <spanclass="math inline">\(\omega_n^{-1}\)</span> 做 FFT 后整体除以 <spanclass="math inline">\(n\)</span></td></tr><tr><td style="text-align: center;">多项式乘法</td><td style="text-align: center;">FFT 求值，逐点相乘，逆 FFT 插值，总时间<span class="math inline">\(O(n\log n)\)</span></td></tr><tr><td style="text-align: center;">卷积</td><td style="text-align: center;">多项式乘法的系数就是卷积</td></tr><tr><td style="text-align: center;">等距 1</td><td style="text-align: center;">用 <spanclass="math inline">\(P(x)^2\)</span> 的系数统计两端点之和</td></tr><tr><td style="text-align: center;">字符串匹配</td><td style="text-align: center;">用卷积快速计算所有位移的相关项</td></tr></tbody></table><p>从算法设计角度看，FFT展示了一个技巧：当直接处理对象困难时，可以换一种表示方法；如果两种表示之间能快速转换，就可能让原本昂贵的操作变得便宜</p>]]></content>
    
    
    <summary type="html">多项式表示，单位复数根，DFT，FFT，逆DFT，多项式乘法，卷积，等距1与字符串匹配</summary>
    
    
    
    <category term="算法设计与分析" scheme="https://exdoubled.github.io/categories/%E7%AE%97%E6%B3%95%E8%AE%BE%E8%AE%A1%E4%B8%8E%E5%88%86%E6%9E%90/"/>
    
    
    <category term="笔记" scheme="https://exdoubled.github.io/tags/%E7%AC%94%E8%AE%B0/"/>
    
    <category term="算法导论" scheme="https://exdoubled.github.io/tags/%E7%AE%97%E6%B3%95%E5%AF%BC%E8%AE%BA/"/>
    
    <category term="算法" scheme="https://exdoubled.github.io/tags/%E7%AE%97%E6%B3%95/"/>
    
  </entry>
  
</feed>
