<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <title>zLeoAlex's Blog</title>
    <link href="https://leoalex0.github.io/atom.xml" rel="self" />
    <link href="https://leoalex0.github.io" />
    <id>https://leoalex0.github.io/atom.xml</id>
    <author>
        <name>zLeoAlex</name>
        
        <email>leoalex0@users.noreply.github.com</email>
        
    </author>
    <updated>2026-05-28T12:00:00Z</updated>
    <entry>
    <title>Knot-tying: 惰性求值下 KMP 算法的自递归之美</title>
    <link href="https://leoalex0.github.io/post/Knot-tying%3A%20%E6%83%B0%E6%80%A7%E6%B1%82%E5%80%BC%E4%B8%8B%20KMP%20%E7%AE%97%E6%B3%95%E7%9A%84%E8%87%AA%E9%80%92%E5%BD%92%E4%B9%8B%E7%BE%8E.html" />
    <id>https://leoalex0.github.io/post/Knot-tying%3A%20%E6%83%B0%E6%80%A7%E6%B1%82%E5%80%BC%E4%B8%8B%20KMP%20%E7%AE%97%E6%B3%95%E7%9A%84%E8%87%AA%E9%80%92%E5%BD%92%E4%B9%8B%E7%BE%8E.html</id>
    <published>2026-05-28T12:00:00Z</published>
    <updated>2026-05-28T12:00:00Z</updated>
    <summary type="html"><![CDATA[<p>这篇文章依次做三件事：用十行 Haskell 实现 KMP，形式化证明该实现的正确性，再从惰性求值的角度解释”循环依赖”为何可行。</p>
<hr />
<h2 id="前缀函数与-kmp-算法">前缀函数与 KMP 算法</h2>
<p>字符串匹配问题：给定模式串 <span class="math inline">\(pat\)</span> 和文本 <span class="math inline">\(txt\)</span>，找出 <span class="math inline">\(pat\)</span> 在 <span class="math inline">\(txt\)</span> 中的所有出现位置。朴素做法逐字符比对，失配时模式串回退一位、文本指针也回退，最坏 <span class="math inline">\(O(nm)\)</span>。</p>
<p><strong>KMP 的核心思想</strong>：失配时文本指针不必回退。如果已经匹配了 <span class="math inline">\(k\)</span> 个字符，那么模式串的前 <span class="math inline">\(k-1\)</span> 个字符和文本中刚扫描过的部分是相同的。利用这部分重叠信息，可以将模式串向前滑动到下一个可能匹配的位置，而不需要重新扫描文本。</p>
<p>这个”重叠信息”就是<strong>前缀函数</strong> <span class="math inline">\(\pi\)</span>。对模式串 <span class="math inline">\(s[0:n]\)</span>（下标区间均为前闭后开 <span class="math inline">\([a,b)\)</span>），<span class="math inline">\(\pi(i)\)</span> 定义为：</p>
<p><span class="math display">\[
\pi(i) = \max_{k \in [0,i]}\big\{\,k \;\big|\; s[0:k] = s[i+1-k:i+1] \,\big\}
\]</span></p>
<p>也即 <span class="math inline">\(\pi(i)\)</span> 是 <span class="math inline">\(s[0:i+1]\)</span> 的最长<strong>真前缀</strong>同时也是<strong>后缀</strong>的长度。规定 <span class="math inline">\(\pi(0)=0\)</span>。</p>
<p>例如模式串 <code>"aabaaab"</code> 的 <span class="math inline">\(\pi\)</span>：</p>
<table>
<thead>
<tr>
<th><span class="math inline">\(i\)</span></th>
<th>0</th>
<th>1</th>
<th>2</th>
<th>3</th>
<th>4</th>
<th>5</th>
<th>6</th>
</tr>
</thead>
<tbody>
<tr>
<td><span class="math inline">\(s[i]\)</span></td>
<td>a</td>
<td>a</td>
<td>b</td>
<td>a</td>
<td>a</td>
<td>a</td>
<td>b</td>
</tr>
<tr>
<td><span class="math inline">\(\pi[i]\)</span></td>
<td>0</td>
<td>1</td>
<td>0</td>
<td>1</td>
<td>2</td>
<td>2</td>
<td>3</td>
</tr>
</tbody>
</table>
<p><span class="math inline">\(\pi\)</span> 的直观含义：<span class="math inline">\(\pi(i)=k\)</span> 意味着 <span class="math inline">\(s[0:k]\)</span> 和 <span class="math inline">\(s[i+1-k:i+1]\)</span> 完全相同。当在文本位置 <span class="math inline">\(j\)</span> 处 <span class="math inline">\(s[k]\)</span> 失配时，可以保持文本指针不动，将模式串状态<strong>回退到</strong> <span class="math inline">\(\pi(k-1)\)</span> 而不是 <span class="math inline">\(0\)</span>——因为前 <span class="math inline">\(\pi(k-1)\)</span> 个字符已经在失配位置之前被隐式匹配过了。这就是 KMP 线性时间的来源。</p>
<hr />
<h2 id="haskell-实现">Haskell 实现</h2>
<div class="sourceCode" id="cb1"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="kw">qualified</span> <span class="dt">Data.Array</span> <span class="kw">as</span> <span class="dt">A</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a><span class="co">-- | 单步转移：给定前缀函数 pi、模式串 pat、当前状态 s 和字符 c，返回下一状态。</span></span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a><span class="ot">step ::</span> (<span class="dt">Eq</span> tok) <span class="ot">=&gt;</span> <span class="dt">A.Array</span> <span class="dt">Int</span> <span class="dt">Int</span> <span class="ot">-&gt;</span> <span class="dt">A.Array</span> <span class="dt">Int</span> tok <span class="ot">-&gt;</span> <span class="dt">Int</span> <span class="ot">-&gt;</span> tok <span class="ot">-&gt;</span> <span class="dt">Int</span></span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a>step <span class="fu">pi</span> pat s c</span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a>  <span class="op">|</span> pat <span class="op">A.!</span> s <span class="op">==</span> c <span class="ot">=</span> s <span class="op">+</span> <span class="dv">1</span>                              <span class="co">-- 命中：前进</span></span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a>  <span class="op">|</span> s <span class="op">==</span> <span class="dv">0</span>         <span class="ot">=</span> <span class="dv">0</span>                                  <span class="co">-- 到根：停止</span></span>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a>  <span class="op">|</span> <span class="fu">otherwise</span>      <span class="ot">=</span> step <span class="fu">pi</span> pat (<span class="fu">pi</span> <span class="op">A.!</span> (s <span class="op">-</span> <span class="dv">1</span>)) c     <span class="co">-- 沿 pi 失败链回跳</span></span>
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-10"><a href="#cb1-10" aria-hidden="true" tabindex="-1"></a><span class="co">-- | 前缀函数 pi，通过 knot-tying 用 scanl 一步完成。</span></span>
<span id="cb1-11"><a href="#cb1-11" aria-hidden="true" tabindex="-1"></a><span class="ot">prefix ::</span> (<span class="dt">Eq</span> tok) <span class="ot">=&gt;</span> <span class="dt">A.Array</span> <span class="dt">Int</span> tok <span class="ot">-&gt;</span> <span class="dt">A.Array</span> <span class="dt">Int</span> <span class="dt">Int</span></span>
<span id="cb1-12"><a href="#cb1-12" aria-hidden="true" tabindex="-1"></a>prefix pat</span>
<span id="cb1-13"><a href="#cb1-13" aria-hidden="true" tabindex="-1"></a>  <span class="op">|</span> <span class="fu">null</span> pat   <span class="ot">=</span> A.listArray (<span class="dv">0</span>, <span class="dv">0</span>) [<span class="dv">0</span>]</span>
<span id="cb1-14"><a href="#cb1-14" aria-hidden="true" tabindex="-1"></a>  <span class="op">|</span> <span class="fu">otherwise</span>  <span class="ot">=</span> <span class="fu">pi</span></span>
<span id="cb1-15"><a href="#cb1-15" aria-hidden="true" tabindex="-1"></a>  <span class="kw">where</span></span>
<span id="cb1-16"><a href="#cb1-16" aria-hidden="true" tabindex="-1"></a>    <span class="co">-- knot: pi 与 pat 同边界，扫描 step 自身得出</span></span>
<span id="cb1-17"><a href="#cb1-17" aria-hidden="true" tabindex="-1"></a>    <span class="fu">pi</span> <span class="ot">=</span> A.listArray (A.bounds pat) (<span class="fu">scanl</span> (step <span class="fu">pi</span> pat) <span class="dv">0</span> (<span class="fu">drop</span> <span class="dv">1</span> (A.elems pat)))</span>
<span id="cb1-18"><a href="#cb1-18" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-19"><a href="#cb1-19" aria-hidden="true" tabindex="-1"></a><span class="co">-- | KMP 匹配：prefix 算前缀函数，scanl 推进状态，any 检测命中。</span></span>
<span id="cb1-20"><a href="#cb1-20" aria-hidden="true" tabindex="-1"></a><span class="ot">contains ::</span> (<span class="dt">Eq</span> tok) <span class="ot">=&gt;</span> <span class="dt">A.Array</span> <span class="dt">Int</span> tok <span class="ot">-&gt;</span> [tok] <span class="ot">-&gt;</span> <span class="dt">Bool</span></span>
<span id="cb1-21"><a href="#cb1-21" aria-hidden="true" tabindex="-1"></a>contains pat <span class="ot">=</span> <span class="fu">any</span> (<span class="op">==</span> n) <span class="op">.</span> <span class="fu">scanl</span> (step <span class="fu">pi</span> pat) <span class="dv">0</span></span>
<span id="cb1-22"><a href="#cb1-22" aria-hidden="true" tabindex="-1"></a>  <span class="kw">where</span></span>
<span id="cb1-23"><a href="#cb1-23" aria-hidden="true" tabindex="-1"></a>    <span class="fu">pi</span> <span class="ot">=</span> prefix pat</span>
<span id="cb1-24"><a href="#cb1-24" aria-hidden="true" tabindex="-1"></a>    n  <span class="ot">=</span> A.rangeSize (A.bounds pat)</span></code></pre></div>
<p><code>step</code> 四行，<code>prefix</code> 四行，<code>contains</code> 三行。<span class="math inline">\(\pi\)</span> 直接复用 <code>pat</code> 的边界（<code>A.bounds pat</code>），无需单独声明 <span class="math inline">\(n\)</span>。传统实现中 <code>n = length pat</code>、<code>for i = 1 to n-1</code> 的模板代码被 <code>scanl</code> + <code>drop 1</code> + <code>elems</code> 取代。</p>
<hr />
<h2 id="代码分析">代码分析</h2>
<h3 id="step"><code>step</code></h3>
<div class="sourceCode" id="cb2"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="ot">step ::</span> <span class="dt">Array</span> <span class="dt">Int</span> <span class="dt">Int</span> → <span class="dt">Array</span> <span class="dt">Int</span> tok → <span class="dt">Int</span> → tok → <span class="dt">Int</span></span></code></pre></div>
<p><span class="math inline">\(\pi\)</span>、模式串、当前状态 → 字符 → 新状态。命中则前进，失配则沿 <span class="math inline">\(\pi\)</span> 失败链回跳，到根则停止。</p>
<p><code>step</code> 不持有 <span class="math inline">\(\pi\)</span> 和模式串——二者均为显式入参。<code>prefix</code> 传入构造中的 <span class="math inline">\(\pi\)</span>（knot），<code>contains</code> 传入已构造的 <span class="math inline">\(\pi\)</span>（普通调用）。同一函数，两种用法。</p>
<h3 id="prefix"><code>prefix</code></h3>
<div class="sourceCode" id="cb3"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="fu">pi</span> <span class="ot">=</span> A.listArray (A.bounds pat) (<span class="fu">scanl</span> (step <span class="fu">pi</span> pat) <span class="dv">0</span> (<span class="fu">drop</span> <span class="dv">1</span> (A.elems pat)))</span></code></pre></div>
<p>两个动作：</p>
<ol>
<li><code>scanl</code> 从 <span class="math inline">\(0\)</span> 开始，将 <code>step pi pat</code> 依次应用到 <span class="math inline">\(pat[1..n-1]\)</span>，产生 <span class="math inline">\(\pi(0),\pi(1),\dots,\pi(n-1)\)</span></li>
<li><code>pi</code> 同时是 <code>step</code> 的入参——<strong>knot</strong>：<span class="math inline">\(\pi\)</span> 用 <code>step</code> 扫描自身来构造，<code>step</code> 又需要 <span class="math inline">\(\pi\)</span> 做失败链回跳</li>
</ol>
<p><code>pi</code> 直接复用 <code>pat</code> 的边界——<code>A.bounds pat</code> 决定 <code>pi</code> 的长度，省去手动传递 <span class="math inline">\(n\)</span>。</p>
<pre class="mermaid"><code>flowchart TD
    scanl[&quot;scanl (step pi pat)&quot;] --&gt;|&quot;产生&quot;| pi[&quot;pi&quot;]
    pi --&gt;|&quot;pi A.! (s-1)&quot;| step[&quot;step pi pat&quot;]
    scanl --&gt;|&quot;施加&quot;| step</code></pre>
<p>在严格求值的语言（如 Rust、C++）中这是”用未完成的数据结构读取自身”，是悖论。在 Haskell 中，<code>Data.Array</code> 对 boxed 元素是惰性的：<code>scanl</code> 自左向右逐个产生 thunk，当 <code>step</code> 需要 <span class="math inline">\(\pi(j-1)\)</span> 时（<span class="math inline">\(j-1 &lt;\)</span> 当前索引），该 thunk 已被 <code>scanl</code> 顺序强制过。</p>
<h3 id="contains"><code>contains</code></h3>
<div class="sourceCode" id="cb5"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a>contains pat <span class="ot">=</span> <span class="fu">any</span> (<span class="op">==</span> n) <span class="op">.</span> <span class="fu">scanl</span> (step <span class="fu">pi</span> pat) <span class="dv">0</span></span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a>  <span class="kw">where</span></span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a>    <span class="fu">pi</span> <span class="ot">=</span> prefix pat</span>
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a>    n  <span class="ot">=</span> A.rangeSize (A.bounds pat)</span></code></pre></div>
<p><code>scanl (step pi pat) 0</code> 在文本上驱动状态机，生成状态序列；<code>any (== n)</code> 检测是否抵达接受状态 <span class="math inline">\(n\)</span>。<code>prefix</code> 提供 <span class="math inline">\(\pi\)</span>。三者数据流：</p>
<pre class="mermaid"><code>flowchart LR
    prefix[&quot;prefix&quot;] --&gt;|&quot;pi&quot;| contains[&quot;contains&quot;]
    prefix --&gt; step[&quot;step&quot;]
    contains --&gt; step</code></pre>
<hr />
<h2 id="正确性证明">正确性证明</h2>
<p>定义 <span class="math inline">\(\pi\)</span>、<span class="math inline">\(\mathrm{step}\)</span>、<span class="math inline">\(\{p_i\}\)</span>，证 <span class="math inline">\(\{p_i\} = \{\pi(i)\}\)</span>。</p>
<h3 id="定义">定义</h3>
<p><strong>定义 1（前缀函数）</strong>　对字符串 <span class="math inline">\(s[0:n]\)</span>（区间记号均为前闭后开 <span class="math inline">\([a,b)\)</span>），<span class="math inline">\(\pi : \{0,\dots,n-1\} \to \mathbb{N}\)</span>：</p>
<p><span class="math display">\[
\pi(i) = \begin{cases}
0, &amp; i = 0 \\
\max\{\,k \in [0,i] \mid s[0:k] = s[i+1-k : i+1]\,\}, &amp; i \ge 1
\end{cases}
\]</span></p>
<p><strong>定义 2（转移函数）</strong>　给定 <span class="math inline">\(s\)</span> 及其 <span class="math inline">\(\pi\)</span>，<span class="math inline">\(\mathrm{step} : \mathbb{N} \times \Sigma \to \mathbb{N}\)</span>（其中 <span class="math inline">\(\Sigma\)</span> 为字符集）：</p>
<p><span class="math display">\[
\mathrm{step}(j, c) = \begin{cases}
j + 1, &amp; \text{若 } s[j] = c \\[2pt]
0,     &amp; \text{若 } s[j] \neq c \;\land\; j = 0 \\[2pt]
\mathrm{step}(\pi(j-1),\, c), &amp; \text{若 } s[j] \neq c \;\land\; j &gt; 0
\end{cases}
\]</span></p>
<p><strong>定义 3（scanl 序列）</strong>　序列 <span class="math inline">\(\{p_i\}_{i=0}^{n-1}\)</span>：</p>
<p><span class="math display">\[
p_i = \begin{cases}
0, &amp; i = 0 \\
\mathrm{step}(p_{i-1},\, s[i]), &amp; i \ge 1
\end{cases}
\]</span></p>
<p>即 <span class="math inline">\([p_0,\dots,p_{n-1}] = \mathrm{scanl\ step\ 0}\ [s[1],\dots,s[n-1]]\)</span>。</p>
<h3 id="定理">定理</h3>
<p><strong>定理</strong>　<span class="math inline">\(\forall i \in [0, n-1]:\; p_i = \pi(i)\)</span>。</p>
<p><em>证明</em>　对 <span class="math inline">\(i\)</span> 归纳。</p>
<p><strong>基始</strong> <span class="math inline">\(i = 0\)</span>：<span class="math inline">\(p_0 = 0 = \pi(0)\)</span>。证毕</p>
<p><strong>归纳步</strong>　假设对所有 <span class="math inline">\(k &lt; i\)</span> 有 <span class="math inline">\(p_k = \pi(k)\)</span>（强归纳）。令 <span class="math inline">\(j = p_{i-1} = \pi(i-1)\)</span>，则 <span class="math inline">\(p_i = \mathrm{step}(j, s[i])\)</span>。</p>
<p><span class="math inline">\(\mathrm{step}\)</span> 的定义有三条分支，按此展开讨论：</p>
<hr />
<p><strong>情况 A</strong>　<span class="math inline">\(s[j] = s[i]\)</span>。</p>
<p>此时 <span class="math inline">\(p_i = \mathrm{step}(j, s[i]) = j+1\)</span>。下证 <span class="math inline">\(\pi(i) = j+1\)</span>。</p>
<p>（<span class="math inline">\(\ge\)</span>）
<span class="math display">\[
\begin{aligned}
s[0:j] &amp;= s[i-j:i] &amp;&amp; [j = \pi(i-1)] \\
s[j] &amp;= s[i] &amp;&amp; [\text{前提}] \\[2pt]
\implies s[0:j+1] &amp;= s[i-j:i+1] &amp;&amp; [\text{拼接}] \\
\implies \pi(i) &amp;\ge j+1 &amp;&amp; [\pi\ \text{定义}]
\end{aligned}
\]</span></p>
<p>（<span class="math inline">\(\le\)</span>）
<span class="math display">\[
\begin{aligned}
k = \pi(i)
&amp;\implies s[0:k] = s[i+1-k:i+1] &amp;&amp; [\pi\ \text{定义}] \\
&amp;\implies s[0:k-1] = s[i+1-k:i] &amp;&amp; [\text{截末字符}] \\
&amp;\implies \pi(i-1) \ge k-1 &amp;&amp; [\pi\ \text{定义}] \\
&amp;\implies j \ge k-1 &amp;&amp; [\pi(i-1)=j] \\
&amp;\implies k \le j+1 \\
&amp;\implies \pi(i) \le j+1
\end{aligned}
\]</span></p>
<p>由 <span class="math inline">\((\ge)(\le)\)</span>：<span class="math inline">\(\pi(i) = j+1 = p_i\)</span>。证毕</p>
<hr />
<p><strong>情况 B</strong>　<span class="math inline">\(s[j] \neq s[i]\)</span> 且 <span class="math inline">\(j = 0\)</span>。</p>
<p>此时 <span class="math inline">\(p_i = \mathrm{step}(j, s[i]) = 0\)</span>。下证 <span class="math inline">\(\pi(i) = 0\)</span>。</p>
<p>令 <span class="math inline">\(M_m = \{\,k \mid s[0:k] = s[m+1-k:m+1]\,\}\)</span>，则 <span class="math inline">\(\pi(m) = \max M_m\)</span>。</p>
<p>由 <span class="math inline">\(\pi(i-1) = 0\)</span>：
<span class="math display">\[
M_{i-1} = \{0\} \tag{1}
\]</span></p>
<p>对任意 <span class="math inline">\(k \ge 1\)</span>：
<span class="math display">\[
\begin{aligned}
k \in M_i
&amp;\implies s[0:k] = s[i+1-k:i+1] &amp;&amp; [M_i\ \text{定义}] \\
&amp;\implies s[0:k-1] = s[i+1-k:i] &amp;&amp; [\text{截末字符}] \\
&amp;\implies k-1 \in M_{i-1} &amp;&amp; [M_{i-1}\ \text{定义}] \\
&amp;\implies k-1 = 0 &amp;&amp; [\text{由 (1)}] \\
&amp;\implies k = 1
\end{aligned}
\]</span></p>
<p>又：
<span class="math display">\[
\begin{aligned}
1 \in M_i
&amp;\iff s[0:1] = s[i:i+1] &amp;&amp; [M_i\ \text{定义}] \\
s[0] &amp;= s[j] \neq s[i] &amp;&amp; [j=0,\ \text{前提}] \\[2pt]
&amp;\Downarrow \\
1 &amp;\notin M_i
\end{aligned}
\]</span></p>
<p>综上，<span class="math inline">\(M_i\)</span> 中不存在 <span class="math inline">\(k \ge 1\)</span>。而 <span class="math inline">\(0 \in M_i\)</span> 恒真（空匹配），故：
<span class="math display">\[
M_i = \{0\},\qquad \pi(i) = \max M_i = 0 = p_i
\]</span></p>
<p>证毕</p>
<hr />
<p><strong>情况 C</strong>　<span class="math inline">\(s[j] \neq s[i]\)</span> 且 <span class="math inline">\(j &gt; 0\)</span>。</p>
<p>此时 <span class="math inline">\(p_i = \mathrm{step}(j, s[i]) = \mathrm{step}(\pi(j-1), s[i])\)</span>。</p>
<p><strong>（i）证 <span class="math inline">\(\pi(i) \le j\)</span></strong>　设 <span class="math inline">\(k = \pi(i)\)</span>。若 <span class="math inline">\(k = 0\)</span>，则 <span class="math inline">\(k \le j\)</span> 平凡。下设 <span class="math inline">\(k \ge 1\)</span>：</p>
<p><span class="math display">\[
\begin{aligned}
k = \pi(i)
&amp;\implies s[0:k] = s[i+1-k:i+1] &amp;&amp; [\pi\text{ 定义}] \\
&amp;\implies s[0:k-1] = s[i+1-k:i] &amp;&amp; [\text{截末字符}] \\
&amp;\implies \pi(i-1) \ge k-1 &amp;&amp; [\pi\text{ 定义}] \\
&amp;\implies j \ge k-1 &amp;&amp; [\pi(i-1)=j] \\
&amp;\implies k \le j+1 &amp;&amp; \text{(1)}
\end{aligned}
\]</span></p>
<p>又：
<span class="math display">\[
\begin{aligned}
s[0:k] = s[i+1-k:i+1] &amp;\implies s[k-1] = s[i] &amp;&amp; [\text{末字符}] \\
s[j] \neq s[i] &amp;\implies k-1 \neq j &amp;&amp; [\text{前提}] \\
&amp;\implies k \neq j+1 &amp;&amp; \text{(2)}
\end{aligned}
\]</span></p>
<p>由 (1)(2) 及 <span class="math inline">\(k\)</span> 为整数：<span class="math inline">\(k \le j\)</span>，即 <span class="math inline">\(\pi(i) \le j\)</span>。</p>
<p><strong>（ii）将 <span class="math inline">\(\pi(i)\)</span> 用 <span class="math inline">\(j\)</span> 表示</strong>　令 <span class="math inline">\(k = \pi(i)\)</span>，<span class="math inline">\(k \le j\)</span>：</p>
<p><span class="math display">\[
\begin{aligned}
k = \pi(i)
&amp;\iff s[0:k] = s[i+1-k:i+1] &amp;&amp; [\pi\text{ 定义}] \\
&amp;\iff s[0:k-1] = s[i+1-k:i]\;\wedge\; s[k-1] = s[i] &amp;&amp; [\text{拆末字符}] \\
&amp;\iff s[0:k-1] = s[j+1-k:j]\;\wedge\; s[k-1] = s[i] &amp;&amp; [\text{由 } s[0:j]=s[i-j:i]]
\end{aligned}
\]</span></p>
<p>故：
<span class="math display">\[
\pi(i) = \max\{\,k \mid s[0:k-1] = s[j+1-k:j],\; s[k-1] = s[i]\,\}
\]</span></p>
<p>令 <span class="math inline">\(\ell = k-1\)</span>：
<span class="math display">\[
\pi(i) = \max\{\,\ell+1 \mid s[0:\ell] = s[j-\ell:j],\; s[\ell] = s[i]\,\}
\]</span></p>
<p>（空集则 <span class="math inline">\(0\)</span>。）</p>
<p>记该链为 <span class="math inline">\(m_0 = \pi(j-1)\)</span>，<span class="math inline">\(m_1 = \pi(m_0-1)\)</span>，…，<span class="math inline">\(m_k = 0\)</span>。展开 <span class="math inline">\(\mathrm{step}\)</span> 的递归定义：</p>
<p><span class="math display">\[
\begin{aligned}
\mathrm{step}(m_0, s[i])
&amp;= \begin{cases}
m_0 + 1, &amp; s[m_0] = s[i] \\
\mathrm{step}(m_1, s[i]), &amp; s[m_0] \neq s[i]
\end{cases} \\
\mathrm{step}(m_1, s[i])
&amp;= \begin{cases}
m_1 + 1, &amp; s[m_1] = s[i] \\
\mathrm{step}(m_2, s[i]), &amp; s[m_1] \neq s[i]
\end{cases} \\
&amp;\;\;\vdots \\
\mathrm{step}(m_k, s[i]) &amp;= 0 \qquad (m_k = 0)
\end{aligned}
\]</span></p>
<p>逐层代入得 <span class="math inline">\(\mathrm{step}(m_0, s[i]) = m_t + 1\)</span>，其中 <span class="math inline">\(t\)</span> 是满足 <span class="math inline">\(s[m_t] = s[i]\)</span> 的最小下标（不存在则 <span class="math inline">\(0\)</span>）。因 <span class="math inline">\(m_0 &gt; m_1 &gt; \dots &gt; m_k\)</span>，此 <span class="math inline">\(m_t\)</span> 是链中满足条件者的最大值，故：</p>
<p><span class="math display">\[
\mathrm{step}(\pi(j-1), s[i]) = \max\{\,\ell+1 \mid \ell \in \{\pi(j-1),\,\pi(\pi(j-1)-1),\,\dots,\,0\},\; s[\ell]=s[i]\,\}
\]</span></p>
<p>因此需确认该链等于 <span class="math inline">\(\{\,\ell \mid s[0:\ell] = s[j-\ell:j]\,\}\)</span>。分两个方向证明该等式：</p>
<p><span class="math display">\[
\begin{aligned}
\{\,\ell \mid s[0:\ell] = s[j-\ell:j]\,\} &amp;\subseteq \{\pi(j-1),\,\pi(\pi(j-1)-1),\,\dots,\,0\} \\
\{\,\ell \mid s[0:\ell] = s[j-\ell:j]\,\} &amp;\supseteq \{\pi(j-1),\,\pi(\pi(j-1)-1),\,\dots,\,0\}
\end{aligned}
\]</span></p>
<p>先证 <span class="math inline">\(\supseteq\)</span>。对链上任一 <span class="math inline">\(m_i\)</span>：</p>
<p><span class="math display">\[
\begin{aligned}
m_i &amp;= \pi(m_{i-1}-1) \\
&amp;\implies s[0:m_i] = s[m_{i-1}-m_i:m_{i-1}] &amp;&amp; [\pi\text{ 定义}] \\
&amp;\implies s[0:m_i] = s[j-m_i:j] &amp;&amp; [\text{代入 } s[0:m_{i-1}]=s[j-m_{i-1}:j]]
\end{aligned}
\]</span></p>
<p>故 <span class="math inline">\(m_i \in \{\,\ell \mid s[0:\ell] = s[j-\ell:j]\,\}\)</span>。</p>
<p>再证 <span class="math inline">\(\subseteq\)</span>。设 <span class="math inline">\(\ell\)</span> 满足 <span class="math inline">\(s[0:\ell]=s[j-\ell:j]\)</span>，记 <span class="math inline">\(m_0 = \pi(j-1)\)</span>。</p>
<p><span class="math display">\[
\begin{aligned}
\ell &amp;\le m_0 &amp;&amp; [m_0 = \max\{\ell \mid s[0:\ell]=s[j-\ell:j]\}] \\
\ell &lt; m_t &amp;\implies s[0:\ell] = s[m_t-\ell:m_t] &amp;&amp; [s[0:m_t]=s[j-m_t:j],\ \ell&lt;m_t] \\
&amp;\implies \ell \le \pi(m_t-1) = m_{t+1} &amp;&amp; [\pi\text{ 定义}] \\
&amp;\implies \ell = m_{t+1} \lor \ell &lt; m_{t+1}
\end{aligned}
\]</span></p>
<p>由 <span class="math inline">\(\pi(m_t-1) &lt; m_t\)</span> 知链 <span class="math inline">\(m_0 &gt; m_1 &gt; m_2 &gt; \dots &gt; 0\)</span> 严格递减，因此有限步内必有 <span class="math inline">\(\ell = m_t\)</span>（对某 <span class="math inline">\(t\)</span>）或 <span class="math inline">\(\ell = 0\)</span>，即 <span class="math inline">\(\ell \in \{m_0,m_1,\dots,0\}\)</span>。</p>
<p>因此两集合相等：
<span class="math display">\[
\{\,\ell \mid s[0:\ell] = s[j-\ell:j]\,\} = \{\pi(j-1),\,\pi(\pi(j-1)-1),\,\dots,\,0\}
\]</span></p>
<p>代入该等式：
<span class="math display">\[
\mathrm{step}(\pi(j-1), s[i]) = \max\{\,\ell+1 \mid s[0:\ell]=s[j-\ell:j],\; s[\ell]=s[i]\,\}
\]</span></p>
<p>由强归纳，链上每个 <span class="math inline">\(\pi\)</span> 值均已得证，故 <span class="math inline">\(\mathrm{step}(\pi(j-1), s[i]) = \pi(i)\)</span>。</p>
<p>故 <span class="math inline">\(p_i = \pi(i)\)</span>。证毕</p>
<hr />
<p>综上，由数学归纳法，<span class="math inline">\(\forall i:\; p_i = \pi(i)\)</span>。证毕</p>
<hr />
<h2 id="惰性求值">惰性求值</h2>
<p><code>prefix</code> 中出现了看似循环的依赖：<code>pi</code> 的定义引用了 <code>step pi pat</code>，而 <code>step</code> 的第三个参数正是 <code>pi</code> 自身。在大多数语言中，这行代码会立即崩溃——无法使用尚未构造完成的数据结构。</p>
<p>原因在于<strong>求值策略</strong>的差异。</p>
<h3 id="严格求值与惰性求值">严格求值与惰性求值</h3>
<p>主流语言（Rust、C++、Java、Python 等）采用<strong>严格求值</strong>（strict/eager evaluation）：函数调用前所有参数必须计算为确定的值。先算结果，再传参数。</p>
<p>Haskell 采用<strong>惰性求值</strong>（lazy evaluation）：表达式只在<strong>结果被需要</strong>时才计算。函数参数传入时并不求值，而是打包为一个<strong>thunk</strong>——“等需要时再算”的延迟计算。当某个操作需要 thunk 的实际值（如模式匹配、输出、算术运算）时，运行时才<strong>强制</strong>（force）这个 thunk，执行计算并替换为结果。</p>
<h3 id="spine-与-thunk">spine 与 thunk</h3>
<p><code>Data.Array</code> 在内存中由两部分组成：</p>
<ul>
<li><strong>spine</strong>：索引结构与边界信息</li>
<li><strong>元素</strong>：各槽位的值</li>
</ul>
<p><code>A.listArray</code> <strong>求值 spine</strong>（校验边界合法性），但<strong>保留元素为 thunk</strong>——每个槽位放一个尚未计算的表达式。</p>
<p><code>scanl</code> 从左到右扫描模式串时，依次产生 <span class="math inline">\(\pi(0),\pi(1),\dots\)</span>。每个 <span class="math inline">\(\pi(i)\)</span> 是一个 thunk。当 <code>step</code> 需要 <span class="math inline">\(\pi(j-1)\)</span> 时（<span class="math inline">\(j-1 &lt; i\)</span>），该 thunk 已被 <code>scanl</code> 在前面的迭代中强制为具体整数。</p>
<pre><code>时间线（scanl 从左到右）：
  索引 0: 强制 thunk₀ → π(0)=0           ✓ 无依赖
  索引 1: 强制 thunk₁ → step 需要 π(0)    ✓ π(0) 已就绪
  索引 2: 强制 thunk₂ → step 需要 π(1)    ✓ π(1) 已就绪
  ...
  索引 i: 强制 thunkᵢ → step 需要 π(j-1)  ✓ j-1 &lt; i，已就绪</code></pre>
<p>关键性质：<strong>数据依赖索引始终小于当前计算索引</strong>。故计算图是<strong>有向无环</strong>的——不存在前向引用，仅为”引用自身数组的前缀”。惰性求值让这种自引用结构可自左向右依次解开，每一步只读已计算部分。</p>
<h3 id="严格求值语言的对比">严格求值语言的对比</h3>
<p>在严格求值的语言中，<code>A.listArray</code> 不仅求值 spine，还会<strong>立即求值所有元素</strong>——数组构造器要求各槽位在创建时即确定。构造 <span class="math inline">\(\pi\)</span> 时 <code>scanl</code> 第一步就需要访问 <span class="math inline">\(\pi\)</span>（但 <span class="math inline">\(\pi\)</span> 还不存在），形成死锁。</p>
<p>Rust / C++ 的 KMP 必须用两趟循环，或手动维护状态、每次只访问已写入位置。这是手动编码”拓扑排序”。</p>
<p>Haskell 的惰性数组将<strong>拓扑排序交给运行时</strong>——数据依赖前向（<span class="math inline">\(j &lt; i\)</span>）即可自动串行化。knot-tying 的本质：<strong>利用求值顺序的拓扑性质，将循环依赖转化为有向无环计算图。</strong></p>
<hr />
<h2 id="结语">结语</h2>
<p>KMP 的自引用结构——<span class="math inline">\(\pi(i)\)</span> 依赖 <span class="math inline">\(\pi(j)\)</span>（<span class="math inline">\(j &lt; i\)</span>）——在惰性语言中不必绕开，可直接写为程序文本。<code>step</code> 将”跟随失败链”抽象为纯函数，<code>prefix</code> 用 <code>scanl</code> 将同一函数同时用于构造与使用。函数式编程”以数据流表达控制流”的一则例证。</p>]]></summary>
</entry>
<entry>
    <title>Gentoo 下 Minecraft 1.12 报 No OpenGL context found in the current thread</title>
    <link href="https://leoalex0.github.io/post/Gentoo-Minecraft-1-12-OpenGL-error.html" />
    <id>https://leoalex0.github.io/post/Gentoo-Minecraft-1-12-OpenGL-error.html</id>
    <published>2021-06-07T14:37:02Z</published>
    <updated>2021-06-07T14:37:02Z</updated>
    <summary type="html"><![CDATA[<h2 id="问题">问题</h2>
<p>在 Gentoo 上通过 MultiMC 启动 Minecraft 1.12.2 时，客户端在初始化阶段崩溃，错误日志里能看到 <code>No OpenGL context found in the current thread</code>。</p>
<pre class="log"><code>[14:37:51] [Client thread/INFO]: Setting user: zLeoAlex
[14:37:52] [Client thread/INFO]: LWJGL Version: 2.9.4
---- Minecraft Crash Report ----
// Quite honestly, I wouldn&#39;t worry myself about that.

Time: 6/7/21 2:37 PM
Description: Initializing game

java.lang.ExceptionInInitializerError
	at bib.av(SourceFile:661)
	at bib.aq(SourceFile:456)
	at bib.a(SourceFile:404)
	at net.minecraft.client.main.Main.main(SourceFile:123)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.multimc.onesix.OneSixLauncher.launchWithMainClass(OneSixLauncher.java:196)
	at org.multimc.onesix.OneSixLauncher.launch(OneSixLauncher.java:231)
	at org.multimc.EntryPoint.listen(EntryPoint.java:143)
	at org.multimc.EntryPoint.main(EntryPoint.java:34)
Caused by: java.lang.ArrayIndexOutOfBoundsException: 0
	at org.lwjgl.opengl.LinuxDisplay.getAvailableDisplayModes(LinuxDisplay.java:951)
	at org.lwjgl.opengl.LinuxDisplay.init(LinuxDisplay.java:738)
	at org.lwjgl.opengl.Display.&lt;clinit&gt;(Display.java:138)
	... 12 more


A detailed walkthrough of the error, its code path and all known details is as follows:
---------------------------------------------------------------------------------------

-- Head --
Thread: Client thread
Stacktrace:
	at bib.av(SourceFile:661)
	at bib.aq(SourceFile:456)

-- Initialization --
Details:
Stacktrace:
	at bib.a(SourceFile:404)
	at net.minecraft.client.main.Main.main(SourceFile:123)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.multimc.onesix.OneSixLauncher.launchWithMainClass(OneSixLauncher.java:196)
	at org.multimc.onesix.OneSixLauncher.launch(OneSixLauncher.java:231)
	at org.multimc.EntryPoint.listen(EntryPoint.java:143)
	at org.multimc.EntryPoint.main(EntryPoint.java:34)

-- System Details --
Details:
	Minecraft Version: 1.12.2
	Operating System: Linux (amd64) version 5.10.27-gentoo-x86_64
	Java Version: 1.8.0_292, Gentoo
	Java VM Version: OpenJDK 64-Bit Server VM (mixed mode), Gentoo
	Memory: 498398744 bytes (475 MB) / 649592832 bytes (619 MB) up to 7635730432 bytes (7282 MB)
	JVM Flags: 2 total; -Xms512m -Xmx8192m
	IntCache: cache: 0, tcache: 0, allocated: 0, tallocated: 0
	Launched Version: MultiMC5
	LWJGL: 2.9.4
	OpenGL: ~~ERROR~~ RuntimeException: No OpenGL context found in the current thread.
	GL Caps:
	Using VBOs: Yes
	Is Modded: Probably not. Jar signature remains and client brand is untouched.
	Type: Client (map_client.txt)
	Resource Packs:
	Current Language: ~~ERROR~~ NullPointerException: null
	Profiler Position: N/A (disabled)
	CPU: &lt;unknown&gt;
#@!@# Game crashed! Crash report saved to: #@!@# /home/leo/.local/share/multimc/instances/1.12.2/.minecraft/crash-reports/crash-2021-06-07_14.37.52-client.txt</code></pre>
<p>不过同一套环境里 Minecraft 1.16 可以正常启动，所以问题并不像是显卡驱动、Java 或 MultiMC 整体不可用。</p>
<h2 id="修复">修复</h2>
<p>安装 <code>x11-apps/xrandr</code>：</p>
<pre class="shell"><code># emerge -av x11-apps/xrandr</code></pre>
<p>从堆栈看，崩溃发生在 LWJGL 2 查询显示模式时。补上 <code>xrandr</code> 后，Minecraft 1.12.2 就可以正常完成初始化。</p>]]></summary>
</entry>
<entry>
    <title>齐次坐标、线性代数与圆锥曲线-1</title>
    <link href="https://leoalex0.github.io/post/%E9%BD%90%E6%AC%A1%E5%9D%90%E6%A0%87%E3%80%81%E7%BA%BF%E6%80%A7%E4%BB%A3%E6%95%B0%E4%B8%8E%E5%9C%86%E9%94%A5%E6%9B%B2%E7%BA%BF-1.html" />
    <id>https://leoalex0.github.io/post/%E9%BD%90%E6%AC%A1%E5%9D%90%E6%A0%87%E3%80%81%E7%BA%BF%E6%80%A7%E4%BB%A3%E6%95%B0%E4%B8%8E%E5%9C%86%E9%94%A5%E6%9B%B2%E7%BA%BF-1.html</id>
    <published>2021-04-30T21:09:12Z</published>
    <updated>2021-04-30T21:09:12Z</updated>
    <summary type="html"><![CDATA[<h2 id="简介">简介</h2>
<p>本文与其说是一篇文章，不如说是对于<a href="https://zhuanlan.zhihu.com/p/103884912">这篇文章</a>的简化与总结。</p>
<p>最终目的是通过引入尽可能少的外部假定，使得我们可以从一个更高的视角看高中的圆锥曲线题。</p>
<h2 id="基础要素">基础要素</h2>
<p>观察一个任意二次曲线</p>
<p><span class="math display">\[
Ax^2+Bxy+Cy^2+Dx+Ey+F=0
\]</span></p>
<p>首先，我们可以做如下换元：</p>
<p><span class="math display">\[
\begin{cases}
  x&#39;=\frac x z\\
  y&#39;=\frac y z\\
\end{cases}
\]</span></p>
<p>其中<span class="math inline">\(z\)</span>为任意实数，则我们可以有：</p>
<p><span class="math display">\[
Ax^2+Bxy+Cy^2+Fz^2+Dxz+Eyz=0
\]</span></p>
<p>我们均可以将其写成矩阵相乘的形式:</p>
<p><span class="math display">\[
\begin{bmatrix}
  x \\ y \\ z
\end{bmatrix}^T
\begin{bmatrix}
  A   &amp; B/2 &amp; D/2 \\
  B/2 &amp; C   &amp; E/2 \\
  D/2 &amp; E/2 &amp; F   \\
\end{bmatrix}
\begin{bmatrix}
 x \\ y \\  z
\end{bmatrix}
= 0
\]</span></p>
<p>故我们可以用一个实对称矩阵来表示一个二次曲线。</p>
<p>如果把齐次坐标中的<span class="math inline">\((x,y,z)\)</span>暂时当成三维空间坐标，那么这个二次型描述的是一个过原点的二次锥面。
我们平时在直角坐标系中看到的圆锥曲线，可以理解为这个锥面与仿射平面<span class="math inline">\(z=1\)</span>的截线。</p>
<figure class="geogebra-panel">
  <div id="geogebra-homogeneous-conic-3d" class="geogebra-applet" data-geogebra data-source="/post/齐次坐标、线性代数与圆锥曲线-1/homogeneous-conic-3d.geogebra"></div>
  <figcaption>GeoGebra：齐次二次型的锥面与 z=1 截面。</figcaption>
</figure>
<p>另外对于一条直线，如果我们做同样的换元，我们也可以以一个向量来表述它。
但为了不与点/坐标产生歧义，我们约定后文以行向量表示直线，列向量表示点/坐标。</p>
<p><span class="math display">\[
Ax+By+C=0 \Longleftrightarrow
\begin{bmatrix}
  A &amp; B &amp; C
\end{bmatrix}
\begin{bmatrix}
  x \\ y \\ z
\end{bmatrix}
= 0
\]</span></p>
<h3 id="小结">小结</h3>
<table>
<colgroup>
<col style="width: 18%" />
<col style="width: 23%" />
<col style="width: 58%" />
</colgroup>
<thead>
<tr>
<th>概念</th>
<th>直角坐标系中</th>
<th>矩阵表示的齐次坐标中</th>
</tr>
</thead>
<tbody>
<tr>
<td>点</td>
<td><span class="math inline">\((x_p,y_p)\)</span></td>
<td><span class="math inline">\(\forall z:\begin{bmatrix} x_p/z\\y_p/z\\z \end{bmatrix}\)</span></td>
</tr>
<tr>
<td>直线</td>
<td><span class="math inline">\(Ax+By+C=0\)</span></td>
<td><span class="math inline">\(\begin{bmatrix}A&amp;B&amp;C\end{bmatrix}\)</span></td>
</tr>
<tr>
<td>二次曲线</td>
<td><span class="math inline">\(Ax^2+Bxy+Cy^2+Dx+Ey+F=0\)</span></td>
<td><span class="math inline">\(\begin{bmatrix}A&amp;B/2&amp;D/2\\B/2&amp;C&amp;E/2\\D/2&amp;E/2&amp;F\\\end{bmatrix}\)</span></td>
</tr>
<tr>
<td>点<span class="math inline">\(p\)</span>在直线<span class="math inline">\(l\)</span>上</td>
<td><span class="math inline">\(l(p)=0\)</span></td>
<td><span class="math inline">\(lp=0\)</span></td>
</tr>
<tr>
<td>点<span class="math inline">\(p\)</span>在二次曲线<span class="math inline">\(c\)</span>上</td>
<td><span class="math inline">\(c(p)=0\)</span></td>
<td><span class="math inline">\(p^Tcp=0\)</span></td>
</tr>
</tbody>
</table>
<h2 id="基础原语">基础原语</h2>
<h3 id="点线关系">点线关系</h3>
<p>我们都知道两相异的点可以定义出一条直线，两非平行直线也相交于一点。那么在其次坐标中会如何呢？</p>
<blockquote>
<p>也可以选择把无穷远点看作两平行线交点，无穷远线看作相同两点连成的直线。
这样一样对偶。</p>
</blockquote>
<p>首先看两直线的交点，若存在两直线<span class="math inline">\(l_1,l_2\)</span>，则其交点<span class="math inline">\(p\)</span>需要同时满足：</p>
<p><span class="math display">\[
\begin{cases}
  l_1p=0\\
  l_2p=0\\
\end{cases}
\]</span></p>
<p>可以看到这与立体几何中“求与两向量同时垂直的第三个向量”这一类问题拥有完全相同的形式。</p>
<p>而这一问题最简单直接的解法就是作两向量的叉乘。但出于对偶性的要求，此处定义两行矩阵叉乘得到列矩阵，列矩阵叉乘得到行矩阵。</p>
<blockquote>
<p>在向量意义上，简单地，叉乘可以定义为：</p>
<p><span class="math display">\[
a\times b = \det\left(\begin{matrix}
\vec i &amp; \vec j &amp; \vec k \\
a_1 &amp; a_2 &amp; a_3 \\
b_1 &amp; b_2 &amp; b_3 \\
\end{matrix}\right)
\]</span></p>
<p>其中,<span class="math inline">\(\vec i,\vec j,\vec k\)</span>为各方向上的单位向量。
注意到：</p>
<p><span class="math display">\[
\begin{aligned}
(a\times b)\cdot a &amp;= \det\left(\begin{matrix}
\vec i &amp; \vec j &amp; \vec k \\
a_1 &amp; a_2 &amp; a_3 \\
b_1 &amp; b_2 &amp; b_3 \\
\end{matrix}\right) \cdot a\\
&amp;= \det\left(\begin{matrix}
a_1 &amp; a_2 &amp; a_3 \\
a_1 &amp; a_2 &amp; a_3 \\
b_1 &amp; b_2 &amp; b_3 \\
\end{matrix}\right) \\
&amp;= 0
\end{aligned}
\]</span></p>
<p>对于<span class="math inline">\(b\)</span>同理，所以对于<span class="math inline">\(c=a\times b\)</span>，任取<span class="math inline">\(\lambda_1,\lambda_2\)</span>，
我们有：<span class="math inline">\(c\cdot (\lambda_1 a+\lambda_2 b) = 0\)</span></p>
</blockquote>
<p>那么我们可以直接写出上述方程组的解：<span class="math inline">\(p=l_1\times l_2\)</span>。</p>
<blockquote>
<p>严格意义上是解系<span class="math inline">\(cl_1\times l_2\)</span>，其中<span class="math inline">\(c\)</span>是常数。</p>
<p>但常数<span class="math inline">\(c\)</span>大小与解在平面直角坐标系上的对应位置无关，故此处取<span class="math inline">\(c=1\)</span></p>
</blockquote>
<p>接下来看过两点的直线，若存在点<span class="math inline">\(p_1,p_2\)</span>，则其连成的直线<span class="math inline">\(l\)</span>满足：</p>
<p><span class="math display">\[
\begin{cases}
  lp_1 = 0 \\
  lp_2 = 0 \\
\end{cases}
\]</span></p>
<p>对等式两边翻转可以得到和上面一样的形式，故其解为：<span class="math inline">\(l=p_1 \times p_2\)</span></p>
<table>
<colgroup>
<col style="width: 25%" />
<col style="width: 47%" />
<col style="width: 27%" />
</colgroup>
<thead>
<tr>
<th>概念</th>
<th>直角坐标中</th>
<th>矩阵表示的齐次坐标中</th>
</tr>
</thead>
<tbody>
<tr>
<td>直线<span class="math inline">\(l_1,l_2\)</span>的交点</td>
<td><span class="math inline">\(l_1(p)=l_2(p)=0\)</span> 的解</td>
<td><span class="math inline">\(p=l_1\times l_2\)</span></td>
</tr>
<tr>
<td>过<span class="math inline">\(p_1,p_2\)</span>的直线</td>
<td><span class="math inline">\((x-x_1)(y_1-y_2)=(y-y_1)(x_1-x_2)\)</span></td>
<td><span class="math inline">\(l=p_1\times p_2\)</span></td>
</tr>
</tbody>
</table>
<h3 id="直线与点的线性组合">直线与点的线性组合</h3>
<p>注意到，对于两直线的交点<span class="math inline">\(p=l_1\times l_2\)</span>，对于任意<span class="math inline">\(\lambda_1,\lambda_2\)</span>，
对两直线的线性组合<span class="math inline">\(l=\lambda_1 l_1 + \lambda_2 l_2\)</span>有：</p>
<p><span class="math display">\[
\begin{aligned}
l p &amp;= \lambda_1 l_1 p + \lambda_2 l_2 p \\
    &amp;= 0 + 0 = 0
\end{aligned}
\]</span></p>
<p>故可知其线性组合亦通过两直线的交点。这对应直角坐标系的一个直线系。</p>
<p>对偶地，对于两点联结的直线:<span class="math inline">\(l=p_1 \times p_2\)</span>。对<span class="math inline">\(p_1,p_2\)</span>的任意一个线性组合：
<span class="math inline">\(p=\lambda_1 p_1 + \lambda_2 p_2\)</span>。我们有：</p>
<p><span class="math display">\[
\begin{aligned}
  lp &amp;= \lambda_1 lp_1 + \lambda_2 lp_2 \\
     &amp;= 0 + 0 = 0
\end{aligned}
\]</span></p>
<p>其也在二者构成的直线上。</p>
<p>这与直角坐标系上类似。</p>
<h3 id="平行">平行</h3>
<p>注意一条特殊的“直线”<span class="math inline">\(l_\infty = \begin{bmatrix} 0&amp;0&amp;1 \end{bmatrix}\)</span>。</p>
<p>其与任意直线<span class="math inline">\(l\)</span>的线性组合<span class="math inline">\(l&#39; = \lambda_1 l_\infty + \lambda_2 l\)</span>与<span class="math inline">\(l\)</span>的交点均无法在直角坐标系内表示。</p>
<p><span class="math display">\[
\begin{aligned}
l&#39; \times l &amp;= \lambda_1 l_\infty \times l \\
 &amp;= \lambda_1 \begin{bmatrix}-l_y &amp; l_x &amp; 0\end{bmatrix}
\end{aligned}
\]</span></p>
<p>且其在平面直角坐标系上的表示正好与<span class="math inline">\(l\)</span>平行。故我们可以将所有与<span class="math inline">\(l\)</span>相差任意倍<span class="math inline">\(l_\infty\)</span>的直线认为其与<span class="math inline">\(l\)</span>平行。</p>
<p>但对偶地，<span class="math inline">\(\begin{bmatrix} 0\\0\\1 \end{bmatrix}\)</span>却代表原点。</p>]]></summary>
</entry>
<entry>
    <title>Debian10配置Docker容器访问IPv6问题</title>
    <link href="https://leoalex0.github.io/post/Docker%E5%AE%B9%E5%99%A8%E8%AE%BF%E9%97%AEIPv6%E9%97%AE%E9%A2%98.html" />
    <id>https://leoalex0.github.io/post/Docker%E5%AE%B9%E5%99%A8%E8%AE%BF%E9%97%AEIPv6%E9%97%AE%E9%A2%98.html</id>
    <published>2021-03-19T01:34:32Z</published>
    <updated>2021-03-19T01:34:32Z</updated>
    <summary type="html"><![CDATA[<h2 id="打开docker的ipv6支持">打开Docker的IPv6支持</h2>
<p>若想要容器能够监听IPv6的接口，那么首先容器内部需要自己有个IPv6的接口。</p>
<p>参见<a href="https://docs.docker.com/config/daemon/ipv6/">docker文档</a></p>
<p>修改<code>/etc/docker/daemon.json</code>，合并以下JSON配置</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode json"><code class="sourceCode json"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="fu">{</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a>  <span class="dt">&quot;ipv6&quot;</span><span class="fu">:</span> <span class="kw">true</span><span class="fu">,</span></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a>  <span class="dt">&quot;fixed-cidr-v6&quot;</span><span class="fu">:</span> <span class="st">&quot;2001:db8:1::/64&quot;</span></span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a><span class="fu">}</span></span></code></pre></div>
<p>然后自行<code>systemctl restart docker</code>之类重启<code>docker daemon</code>。</p>
<p>其中第一项会开启默认的bridge network的IPv6支持，下一个会在IPv6的地址池里添加对应的网段。</p>
<p>不过很奇怪的是，这一个网段的<strong>全部</strong>地址都会分给全局默认的bridge network，所以如果自己的docker-compose文件里有网络需要IPv6支持的，得手动分配一个网段，或者直接改用默认的全局bridge。</p>
<h2 id="设置ip6tables转发流量">设置ip6tables转发流量</h2>
<p>搞定上面一步之后，理论上你容器内expose出的端口都可以监听IPv6的接口了。但若是想让容器内部访问IPv6的外网，还需要配置ip6tables转发流量。</p>
<p>参见<a href="https://github.com/docker/for-linux/issues/648">这个issue</a></p>
<p>使用如下命令转发收到的对应地址的流量。</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="ex">ip6tables</span> <span class="at">-t</span> nat <span class="at">-A</span> POSTROUTING <span class="at">-s</span> 2001:db8:1::/64 ! <span class="at">-o</span> docker0 <span class="at">-j</span> MASQUERADE</span></code></pre></div>
<p>但总感觉这个方案还不够完美，只能说暂时够用了。</p>]]></summary>
</entry>
<entry>
    <title>[CW]Faberge easter eggs crush test题解(数学部分)</title>
    <link href="https://leoalex0.github.io/post/CW-Faberge-easter-eggs-crush-test.html" />
    <id>https://leoalex0.github.io/post/CW-Faberge-easter-eggs-crush-test.html</id>
    <published>2020-06-27T21:34:50Z</published>
    <updated>2020-06-27T21:34:50Z</updated>
    <summary type="html"><![CDATA[<h2 id="递推式">递推式</h2>
<p>按照题意，若设所求函数为<span class="math inline">\(F(n,m)\)</span>，则有：</p>
<p><span class="math display">\[
\begin{aligned}
  F(0,m)&amp;=0\\
  F(n,0)&amp;=0\\
  F(n,m)&amp;=F(n,m-1)+F(n-1,m-1)+1
\end{aligned}
\]</span></p>
<h2 id="生成函数">生成函数</h2>
<p>注意到其生成函数满足</p>
<p><span class="math display">\[
  G(x,y)=yG(x,y)+xyG(x,y)+\frac{xy}{(1-x)(1-y)}
\]</span></p>
<p>移项可得：</p>
<p><span class="math display">\[
\begin{aligned}
  (1-y-xy)G(x,y)&amp;=\frac{xy}{(1-x)(1-y)} \\
  G(x,y)&amp;=y(1-y)^{-1}x(1-x)^{-1}(1-y-xy)^{-1}\\
\end{aligned}
\]</span></p>
<h2 id="解生成函数">解生成函数</h2>
<p>令: <span class="math inline">\(p_1=(1-x)^{-1},p_2=(1-y-xy)^{-1}\)</span></p>
<p>则: <span class="math inline">\(G(x,y)=y(1-y)^{-1}xp_1p_2\)</span></p>
<p><span class="math display">\[
\frac {\partial^n G(x,y)} {\partial x^n}
= y(1-y)^{-1}\frac { \partial^nxp_1p_2 } {\partial x^n}\\
\left. \frac{\partial^n G(x,y)}{\partial x^n} \right| _{x=0}
= y(1-y)^{-1}\left.\frac{ \partial^nxp_1p_2 }{\partial x^n}\right| _{x=0}
\]</span></p>
<h3 id="莱布尼兹法则">莱布尼兹法则</h3>
<p>注意如下同构</p>
<p><span class="math display">\[
\begin{aligned}
令a^nb^m &amp;\sim u^{(n)}v^{(m)} \\
a^nb^m(a+b)=a^{n+1}b^m+a^nb^{m+1} &amp;
\sim u^{(n+1)}v^{(m)}+u^{(n)}v^{(m+1)}=(u^{(n)}v^{(m)})&#39; \\
a^0b^0(a+b)^n=\sum_{i=0}^n \frac {n!} {i!(n-i)!} a^ib^{n-i}&amp;
\sim (uv)^{(n)}=\sum_{i=0}^n \frac {n!} {i!(n-i)!} u^{(i)}v^{(n-i)} \\
\end{aligned}
\]</span></p>
<h3 id="处理p_1p_2">处理<span class="math inline">\(p_1p_2\)</span></h3>
<p>解出</p>
<p><span class="math display">\[
\begin {aligned}
\frac {\partial^np_1} {\partial x^n} &amp;=n!(1-x)^{-n-1} \\
\frac {\partial^np_2} {\partial x^n}&amp; =n!y^n(1-y-xy)^{-n-1}\\
\left. \frac {\partial^np_1}{\partial x^n} \right|_{x=0} &amp;=  n! \\
\left. \frac {\partial^np_2}{\partial x^n} \right|_{x=0} &amp;= n!y^n(1-y)^{-n-1}\\
\left .\frac {\partial^n p_1p_2} {\partial x^n} \right|_{x=0}&amp;
=\sum_{i=0}^n \frac {n!}{i!(n-i)!}
\left.\frac{\partial^{n-i}p_1}{\partial x^{n-i}}
\frac {\partial^i p_2} {\partial x^i} \right|_{x=0}  \\
&amp;= \sum_{i=0}^n \frac {n!} {i!(n-i)!} i!y^i(1-y)^{-i-1}  (n-i)! \\
&amp;=n! \sum_{i=0}^{n} y^i(1-y)^{-i-1} \\
\end {aligned}
\]</span></p>
<h3 id="处理xp_1p_2">处理<span class="math inline">\(xp_1p_2\)</span></h3>
<p><span class="math display">\[
\begin{aligned}
\frac {\partial^n xp_1p_2} {\partial x^{n}}&amp;
= \sum _{i=0}^n \frac {n!} {i!(n-i)!}\frac {d^ix}{\partial x^i}
\frac {d^{n-i}p_1p_2} {\partial x^{n-i}} \\
&amp;=x \frac {\partial^n p_1p_2} {\partial x^{n}}
+n\frac {d^{n-1}p_1p_2} {\partial x^{n-1}} \\
\left. \frac {\partial^n xp_1p_2} {\partial x^{n}} \right| _{x=0}&amp;
= n\left .\frac {d^{n-1}p_1p_2} {\partial x^{n-1}} \right| _{x=0}\\
&amp;=n(n-1)! \sum _{i=0}^{n-1} y^i(1-y)^{-i-1} \\
&amp;=n!\sum _{i=0}^{n-1} y^i(1-y)^{-i-1}
\end{aligned}
\]</span></p>
<h3 id="解出gxy">解出<span class="math inline">\(G(x,y)\)</span></h3>
<p><span class="math display">\[
\begin{aligned}
G(x,y)&amp;=\sum_{n=0}^\infty
\frac{\left. \frac {\partial^n G(x,y)} {\partial x^n} \right|_{x=0}}
{n!}x^n &amp; 麦克劳林展开 \\
&amp;=\sum _{n=0}^\infty \frac {n!\sum _{j=0}^{n-1}y^{j+1}(1-y)^{-j-2}} {n!} x^n &amp; 代入\\
&amp;=\sum_{n=0}^\infty x^n\sum _{j=0}^{n-1}  y^{j+1}(1-y)^{-j-2} &amp; 前置x^n\\
&amp;=\sum_{n=0}^\infty x^n\sum _{j=0}^{n-1}  y^{j+1}
\sum_{i=0}^\infty \frac {(i+j+1)!}{i!(j+1)!} y^i &amp; 展开(1-y)^{-j-2}\\
&amp;=\sum_{n=0}^\infty x^n\sum _{j=0}^{n-1}
\sum _{i=0}^\infty \frac {(i+j+1)!}{i!(j+1)!} y^{i+j+1} &amp; 乘进y^{j+1}\\
&amp;=\sum_{n=0}^\infty x^n\sum _{j=0}^{n-1}
\sum _{m=j+1}^\infty \frac {m!}{(j+1)!(m-j-1)!} y^m &amp; 换元:m=i+j+1\\
&amp;=\sum_{n=0}^\infty x^n\sum _{j=0}^{n-1}
\sum _{m=j+1}^\infty \frac {m!}{(j+1)!(m-j-1)!} y^m &amp; 乘进来\\
&amp;= \sum_{n=0}^\infty x^n \sum _{m=1}^\infty
\sum _{j=0}^{\min(m,n)-1}\frac {m!}{(j+1)!(m-j-1)!} y^m&amp; 交换求和顺序 \\
&amp;= \sum_{n=0}^\infty x^n \sum _{m=1}^\infty y^m
\sum _{j=1}^{\min(n,m)}\frac {m!}{j!(m-j)!}  &amp; 换元：j=j+1
\end{aligned} \\
\]</span></p>
<h2 id="解">解</h2>
<p><span class="math display">\[
F(n,m)=\sum _{j=1}^{\min(n,m)} \frac {m!} {j!(m-j)!}
\]</span></p>]]></summary>
</entry>
<entry>
    <title>Incident Algebra与反演</title>
    <link href="https://leoalex0.github.io/post/%E5%8F%8D%E6%BC%94.html" />
    <id>https://leoalex0.github.io/post/%E5%8F%8D%E6%BC%94.html</id>
    <published>2020-06-11T16:10:11Z</published>
    <updated>2020-06-11T16:10:11Z</updated>
    <summary type="html"><![CDATA[<h2 id="incidence-algebra">Incidence algebra</h2>
<h3 id="定义">定义</h3>
<p>局部有限偏序集(locally finite poset)是一种特殊的偏序集，其满足对于所有的闭区间<span class="math inline">\([a,b]\)</span>是有限的。</p>
<blockquote>
<p>偏序关系的性质:</p>
<ul>
<li>自反性：<span class="math inline">\(\forall a\in S:a\le a\)</span></li>
<li>反对称性：<span class="math inline">\(\forall a,b\in S: a\le b \wedge b\le a \rightarrow a=b\)</span></li>
<li>传递性:<span class="math inline">\(\forall a,b,c\in S: a\le b \wedge b\le c \rightarrow a\le c\)</span></li>
</ul>
<p>偏序集上的区间：</p>
<ul>
<li><span class="math inline">\([a,b]=\{x|a\le x \wedge x\le b\}\)</span></li>
<li><span class="math inline">\([a,b)=\{x|a\le x\wedge x\le b\wedge b\neq x\}\)</span></li>
<li><span class="math inline">\((a,b]=\{x|x\neq a\wedge a\le x\wedge x\le b\}\)</span></li>
<li><span class="math inline">\((a,b)=\{x|x\neq a\wedge a\le x \wedge x\le b\wedge b\neq x\}\)</span></li>
</ul>
</blockquote>
<p>Incident algebra中的元素<span class="math inline">\(f\)</span>是一个局部有限偏序集<span class="math inline">\(S\)</span>上的的非空区间<span class="math inline">\([a,b]\)</span>到一个标量(实际上幺环就足够了)<span class="math inline">\(f(a,b)\)</span>的函数。定义其构成的集合为<span class="math inline">\(X\)</span>，其上的运算定义如下：</p>
<p><span class="math display">\[
(f*g)(a,b)=\sum_{x\in [a,b]} f(a,x)g(x,b)
\]</span></p>
<p>有时我们也会在上面额外定义一个加法运算<span class="math inline">\((f+g)(a,b)=f(a,b)+g(a,b)\)</span>，这将使得其满足一些有趣的性质。</p>
<h3 id="性质">性质</h3>
<h4 id="结合律">结合律</h4>
<p><span class="math display">\[
\begin{aligned}
((f*g)*h)(a,b)&amp;=\sum_{x\in[a,b]} (f*g)(a,x)h(x,b) &amp; 定义\\
&amp;=\sum_{x\in [a,b]}\left(\sum_{y\in [a,x]} f(a,y)g(y,x)\right)h(x,b) &amp; 定义 \\
&amp;=\sum_{x\in [a,b]}\sum_{y\in[a,x]} f(a,y)g(y,x)h(x,b) &amp; 分配律\\
&amp;=\sum_{y\in [a,b]}\sum_{x\in[y,b]} f(a,y)g(y,x)h(x,b) &amp; 交换求和序\\
&amp;=\sum_{y\in[a,b]}f(a,y)\sum_{x\in[y,b]}g(y,x)h(x,b) &amp; 分配律\\
&amp;=\sum_{y\in[a,b]}f(a,y)(g*h)(y,b) &amp; 定义\\
&amp;=(f*(g*h))(a,b) &amp; 定义
\end{aligned}
\]</span></p>
<h4 id="单位元">单位元</h4>
<p>定义函数<span class="math inline">\(\delta\)</span></p>
<p><span class="math display">\[
\delta (a,b) =[a=b]= \begin{cases}
1 &amp; a=b\\
0 &amp; otherwise
\end{cases}
\]</span></p>
<p>任意<span class="math inline">\(f\in X\)</span>，有：</p>
<p><span class="math display">\[
\begin{aligned}
(f*\delta)(a,b)&amp;=\sum_{x\in [a,b]} f(a,x)\delta(x,b)\\
&amp;=\sum_{x\in [a,b]} f(a,x)[x=b]\\
&amp;=f(a,b) \\
(\delta*f)(a,b)&amp;=\sum_{x\in[a,b]}\delta(a,x)f(x,b)\\
&amp;=\sum_{x\in[a,b]}[a=x]f(x,b) \\
&amp;=f(a,b)
\end{aligned}
\]</span></p>
<p>故<span class="math inline">\(&lt;X,*&gt;\)</span>构成幺半群(Monoid)， <span class="math inline">\(\delta\)</span> 是其单位元，根据幺半群的性质可知它是唯一的单位元。</p>
<h4 id="分配律">分配律</h4>
<p><span class="math display">\[
\begin{aligned}
(f*(g+h))(a,b)&amp;=\sum_{x\in [a,b]} f(a,x)(g+h)(x,b) &amp; 定义\\
&amp;=\sum_{x\in[a,b]}f(a,x)[g(x,b)+h(x,b)]\\
&amp;=\sum_{x\in[a,b]}f(a,x)g(x,b)+f(a,x)h(x,b) \\
&amp;=\sum_{x\in[a,b]}f(a,x)g(x,b)+\sum_{x\in [a,b]}f(a,x)h(x,b) \\
&amp;=(f*g)(a,b)+(f*h)(a,b)\\
&amp;=(f*g+f*h)(a,b)
\end{aligned}
\]</span></p>
<p><span class="math inline">\((f+g)*h=f*h+g*h\)</span>类似</p>
<h4 id="逆">逆</h4>
<p>对于<span class="math inline">\(f\in X \wedge \forall a\in S:f(a,a)\neq 0\)</span>，我们可以构造：</p>
<p><span class="math display">\[
g(a,b)= \begin{cases}
\frac 1 {f(a,b)} &amp; a=b\\
\frac {-\sum_{x\in (a,b]} f(a,x)g(x,b)} {f(a,a)} &amp; a \neq b\\
\end{cases}
\]</span></p>
<p>因为<span class="math inline">\((a,b]=[a,b]-\{a\}\)</span>，且<span class="math inline">\([a,b]\)</span>是有限的，故上述定义是良构的。</p>
<p>那么我们有：</p>
<p><span class="math display">\[
\begin{aligned}
(f*g)(a,b)=\sum_{x\in[a,b]}f(a,x)g(x,b)&amp;=\begin{cases}
f(a,b)g(a,b)=f(a,b)\frac 1 {f(a,b)}=1 &amp; a=b\\
f(a,a)g(a,b)+\sum_{x\in(a,b]} f(a,x)g(x,b)=0 &amp; a\neq b
\end{cases} \\
&amp;=\delta(a,b)
\end{aligned}
\]</span></p>
<p>另外，<span class="math inline">\(g(a,a)=\frac 1 {f(a,a)}\neq 0\)</span>，也满足上述条件</p>
<p>对称地，我们可以构造其另一个方向的逆：</p>
<p><span class="math display">\[
g&#39;(a,b)= \begin{cases}
\frac 1 {f(a,b)} &amp; a=b\\
\frac {-\sum_{x\in [a,b)} g&#39;(a,x)f(x,b)} {f(b,b)} &amp; a \neq b\\
\end{cases}
\]</span></p>
<p>满足<span class="math inline">\((g&#39;*f)=\delta\)</span></p>
<p>故可知<span class="math inline">\(\forall a\in S:f(a,a)\neq 0\)</span>是<span class="math inline">\(f\)</span>可逆的充分条件。</p>
<p>因为<span class="math inline">\(1=\delta(a,a)=f(a,a)f^{-1}(a,a)\)</span>，故其亦是可逆的必要条件（<span class="math inline">\(\forall x\in\mathbb R:0\times x=0\)</span>，以此反证)</p>
<p>因此，<span class="math inline">\(f\)</span>可逆的充要条件是<span class="math inline">\(\forall a\in S:f(a,a)\neq 0\)</span></p>
<blockquote>
<p>我们可以构造Incident algebra元素的一个子集
<span class="math inline">\(\hat X=\left\{f|f\in X\wedge \forall a\in S:f(a,a)\neq 0\right\}\)</span>，
那么对于代数结构<span class="math inline">\(&lt;\hat X,*&gt;\)</span>，有：</p>
<ul>
<li>封闭性(两非<span class="math inline">\(0\)</span>数积非<span class="math inline">\(0\)</span>)</li>
</ul>
<p><span class="math display">\[
  (f*g)(a,a)=f(a,a)g(a,a)\neq0
\]</span></p>
<ul>
<li>结合律（同<span class="math inline">\(&lt;X,*&gt;\)</span>)</li>
<li>有单位元<span class="math inline">\(\delta\in\hat X\)</span></li>
<li>有逆(上述已证)</li>
</ul>
<p>故可知<span class="math inline">\(&lt;\hat X,*&gt;\)</span>构成群</p>
<p>亦可知 (左右逆元一致，简证如下)</p>
<p><span class="math display">\[
g=\delta*g=(g&#39;*f)*g=g&#39;*f*g=g&#39;*(f*g)=g&#39;*\delta=g&#39;
\]</span></p>
</blockquote>
<h3 id="与一般函数之间的关系">与一般函数之间的关系</h3>
<p>若定义<span class="math inline">\(F,G:S\to \mathbb R,f\in \hat X,a,b\in S\)</span>，且二者满足下列关系:</p>
<p><span class="math display">\[
F(x)=\sum_{k\in [a,x]} G(k)f(k,x)
\]</span></p>
<p>则有：</p>
<p><span class="math display">\[
\begin{aligned}
\sum_{k\in [a,x]} F(k)f^{-1}(k,x) &amp;=
\sum_{k\in[a,x]} \left[\sum_{t\in [a,k]} G(t)f(t,k)\right] f^{-1}(k,x) &amp; 定义\\
&amp;=\sum_{k\in [a,x]}\sum_{t\in [a,k]} G(t)f(t,k)f^{-1}(k,x) &amp; 分配律\\
&amp;=\sum_{t\in [a,x]} \sum_{k\in [t,x]} G(t)f(t,k)f^{-1}(k,x) &amp; 交换求和序\\
&amp;=\sum_{t\in [a,x]} G(t) \sum_{k\in [t,x]} f(t,k)f^{-1}(k,x) &amp; 分配律\\
&amp;=\sum_{t\in [a,x]} G(t) \delta(t,x) &amp; 定义\\
&amp;=\sum_{t\in [a,x]} G(t) [t=x] = G(x)
\end{aligned}
\]</span></p>
<blockquote>
<p>本质上，它是<span class="math inline">\(F(x)=F&#39;(a,x)\)</span>的一种等价。</p>
</blockquote>
<p>对称地，也可以构造：</p>
<p><span class="math display">\[
F(x)=\sum_{k\in[x,b]} f(x,k)G(k)\\
G(x)=\sum_{k\in[x,b]}f^{-1}(x,k)F(k)
\]</span></p>
<p>故我们可以在其上构造一个群作用。</p>
<h2 id="偏序集与zeta函数">偏序集与<span class="math inline">\(\zeta\)</span>函数</h2>
<h3 id="定义-1">定义</h3>
<p>定义<span class="math inline">\(\zeta\in X\)</span>为：</p>
<p><span class="math display">\[
\zeta(a,b)=[a\le b]=\begin{cases}
1 &amp; a\le b \\
0 &amp; otherwise
\end{cases}
\]</span></p>
<p>因为在<span class="math inline">\(X\)</span>定义时便要求<span class="math inline">\([a,b]\)</span>构成非空区间，
而<span class="math inline">\(\exists x\in S:a\le x \wedge x\le b \Leftrightarrow a\le b\)</span>。
因此其也可以看作<span class="math inline">\(\zeta(a,b)=1\)</span></p>
<p>因为<span class="math inline">\(\forall a\in S:\zeta(a,a)=1\)</span>，所以<span class="math inline">\(\zeta\in \hat X\)</span></p>
<p>故可令<span class="math inline">\(\mu=\zeta^{-1}\)</span>，则有：</p>
<p><span class="math display">\[
\mu (a,b)=\begin{cases}
1 &amp; a=b\\
-\sum_{x\in(a,b]} \mu(x,b) = -\sum_{x\in [a,b)} \mu(a,x) &amp; a\lt b\\
0 &amp; otherwise
\end{cases}
\]</span></p>
<p>令<span class="math inline">\(S_1,S_2\)</span>为两偏序集，定义<span class="math inline">\(S_1\times S_2\)</span>上的偏序关系：</p>
<p><span class="math display">\[
&lt;a_1,b_1&gt;\le &lt;a_2,b_2&gt;\Leftrightarrow a_1\le a_2 \wedge b_1\le b_2
\]</span></p>
<p>因此，我们可以定义<span class="math inline">\(S_1\times S_2\)</span>也是一个偏序集，进一步，有限个偏序集的直积也是一个偏序集。</p>
<h3 id="性质-1">性质</h3>
<p>区间<span class="math inline">\([a,b]\)</span>中元素的个数可以表示为：</p>
<p><span class="math display">\[
\zeta^2(a,b)=\sum_{x\in[a,b]}\zeta(a,x)\zeta(x,b)=\sum_{x\in[a,b]}1=Card([a,b])
\]</span></p>
<p>设局部有限偏序集<span class="math inline">\(S_1,S_2\)</span>，有<span class="math inline">\(S=S_1\times S_2\)</span>。若<span class="math inline">\(\zeta_1,\zeta_2,\zeta\)</span>分别为<span class="math inline">\(S_1,S_2,S\)</span>上的<span class="math inline">\(\zeta\)</span>函数，<span class="math inline">\(\mu_1,\mu_2,\mu\)</span>分别为<span class="math inline">\(S_1,S_2,S\)</span>上的<span class="math inline">\(\mu\)</span>函数，<span class="math inline">\(\delta_1,\delta_2,\delta\)</span>分别为<span class="math inline">\(S_1,S_2,S\)</span>上的<span class="math inline">\(\delta\)</span>函数。</p>
<p><span class="math inline">\(\forall a,b\in S\wedge a\le b\)</span>，令<span class="math inline">\(a=&lt;a_1,a_2&gt;,b=&lt;b_1,b_2&gt;\)</span>，易得：</p>
<p><span class="math display">\[
\begin{aligned}
\delta(a,b)&amp;=[a=b] \\
&amp;=[a_1=b_1\wedge a_2=b_2]\\
&amp;=[a_1=b_1][a_2=b_2]\\
&amp;=\delta_1(a_1,b_1)\delta_2(a_2,b_2)\\
\\
\zeta(a,b)&amp;=[a\le b] \\
&amp;=[a_1\le b_1 \wedge a_2\le b_2] \\
&amp;=[a_1\le b_1][a_2\le b_2]\\
&amp;=\zeta_1(a_1,b_1)\zeta_2(a_2,b_2)\\
\end{aligned}
\]</span></p>
<p>进一步地，构造<span class="math inline">\(\mu&#39;(a,b)=\mu_1(a_1,b_1)\mu_2(a_2,b_2)\)</span>，则有:</p>
<p><span class="math display">\[
\begin{aligned}
(\mu&#39;*\zeta)(a,b) &amp;= \sum_{x\in [a,b]} \mu&#39;(a,x)\zeta(x,b) &amp; 定义\\
&amp;=\sum_{x_1\in [a_1,b_1] \wedge x_2\in [a_2,b_2]}
\mu_1(a_1,x_1)\mu_2(a_2,x_2)\zeta_1(x_1,b_1)\zeta_2(x_2,b_2)&amp; 展开 \\
&amp;=\sum_{x_1\in [a_1,b_1]}\sum_{x_2\in [a_2,b_2]}
\mu_1(a_1,x_1)\zeta_1(x_1,b_1) \mu_2(a_2,x_2)\zeta_2(x_2,b_2) &amp; 拆开x_1,x_2的求和\\
&amp;=\sum_{x_1\in [a_1,b_1]}\mu_1(a_1,x_1)\zeta_1(x_1,b_1)
\sum_{x_2\in [a_2,b_2]} \mu_2(a_2,x_2)\zeta_2(x_2,b_2) &amp; 分配律\\
&amp;= (u_1*\zeta_1)(a_1,b_1) (u_1*\zeta_2)(a_2,b_2) \\
&amp;= \delta_1(a_1,b_1)\delta_2(a_2,b_2)\\
&amp;=\delta(a,b)
\end{aligned}
\]</span></p>
<p>故<span class="math inline">\(\mu&#39;=\zeta^{-1}=\mu\)</span>，即:<span class="math inline">\(\mu(a,b)=\mu_1(a_1,b_1)\mu_2(a_2,b_2)\)</span></p>
<h2 id="反演">反演</h2>
<h3 id="二项式反演">二项式反演</h3>
<p>设<span class="math inline">\(n\)</span>次多项式<span class="math inline">\(p_n(x)=x^n,q_n(x)=(x-1)^n\)</span>，则有：</p>
<p><span class="math display">\[
\begin{aligned}
q_n(x)&amp;=\sum_{k=0}^n \binom n k (-1)^{n-k} x^k \\
&amp;= \sum_{k=0}^n \binom n k (-1)^{n-k} q_k(x) \\
\\
p_n(x) &amp;= ((x-1)+1)^n\\
&amp;= \sum_{k=0}^n \binom n k (x-1)^k \\
&amp;= \sum_{k=0}^n \binom n k q_k(x)
\end{aligned}
\]</span></p>
<p>可以定义<span class="math inline">\((n+1)\times (n+1)\)</span>方阵<span class="math inline">\(A,B\)</span>（下标从0开始）：</p>
<p><span class="math display">\[
\begin{aligned}
A_{i,j}&amp;= \binom j i (-1)^{b-a} \\
B_{i,j}&amp;= \binom j i \\
\end{aligned}
\]</span></p>
<p>可知：</p>
<ul>
<li><span class="math inline">\(A,B\)</span>均为单位上三角矩阵</li>
<li>构造<span class="math inline">\(p,q\)</span>的系数向量可知：<span class="math inline">\(AB=BA=I\)</span>，即<span class="math inline">\(A,B\)</span>互为逆矩阵。</li>
</ul>
<p>令<span class="math inline">\(f(a,b)=A_{a,b},g(a,b)=B_{a,b}\)</span>。由于<span class="math inline">\(A,B\)</span>均为单位上三角矩阵，
故<span class="math inline">\(f,g\)</span>可以被看作以<span class="math inline">\([0,n]\)</span>整数区间，小于等于作为偏序关系的Incident algebra中的元素。
其可以和<span class="math inline">\((n+1)\times (n+1)\)</span>矩阵上乘法同构。</p>
<p><span class="math display">\[
\begin{aligned}
(f*g)(a,b)&amp;=\sum_{x\in [a,b]} f(a,x)g(x,b) \\
&amp;=\underbrace{\sum_{x\in [0,a)} f(a,x)g(x,b)}_{f(a,x)=0}
+\sum_{x\in [a,b]} f(a,x)g(x,b)
+\underbrace{\sum_{x\in (b,n]} f(a,x)g(x,b)}_{g(x,b)=0}\\
&amp;=\sum_{x\in [0,n]} f(a,x)g(x,b) \\
&amp;=(AB)_{a,b}=I_{a,b}\\
&amp;=[a=b]=\delta(a,b)
\end{aligned}
\]</span></p>
<p>所以<span class="math inline">\(f,g\)</span>互逆。</p>
<p>按照Incident algebra与一般函数之间的关系，对<span class="math inline">\([0,n]\)</span>上一整数<span class="math inline">\(a\)</span>，下两式定义等价：</p>
<p><span class="math display">\[
\begin{aligned}
F(x)&amp;=\sum_{k\in [a,x]}G(k)g(k,x)=\sum_{k\in[a,x]} \binom x k G(k) \\
G(x)&amp;=\sum_{k\in[a,x]} F(k)f(k,x)=\sum_{k\in[a,x]}(-1)^{x-k} \binom x k F(k) \\
\end{aligned}
\]</span></p>
<p>特别地，对于<span class="math inline">\(a=0\)</span>，我们有：</p>
<p><span class="math display">\[
\begin{aligned}
F(x)&amp;=\sum_{k\in [0,x]}G(k)g(k,x)=\sum_{k\in[0,x]} \binom x k G(k) \\
G(x)&amp;=\sum_{k\in[0,x]} F(k)f(k,x)=\sum_{k\in[0,x]}(-1)^{x-k} \binom x k F(k) \\
\end{aligned}
\]</span></p>
<p>一般，我们将此称作二项式反演公式。</p>
<h3 id="莫比乌斯反演">莫比乌斯反演</h3>
<p>将正整数集<span class="math inline">\(\mathbb N^+\)</span>上的整除作为偏序关系。我们可以得到另一个Incident algebra实例。</p>
<p>其中：</p>
<p><span class="math display">\[
\begin{aligned}
\zeta(a,b)&amp;=\begin{cases}
1 &amp; a | b \\
0 &amp; otherwise\\
\end{cases} \\
\\
\mu(a,b)&amp;=\begin{cases}
1 &amp; a=b \\
-\sum_{a\mid x \wedge a\neq x \wedge x\mid b} \mu(x,b) &amp; a\mid b \wedge a\neq b \\
0 &amp; otherwise
\end{cases}
\end{aligned}
\]</span></p>
<p>另外，通过数学归纳，注意到：</p>
<p><span class="math display">\[
\begin{aligned}
\mu(ka,kb) &amp;= \begin{cases}
1 &amp; ka = kb &amp;\Leftrightarrow a=b \\
-\sum_{ka\mid kx \wedge ka\neq kx \wedge kx\mid kb} \mu(kx,kb)
&amp; ka \mid kb \wedge ka\neq kb &amp; \Leftrightarrow a \mid b\wedge a\neq b \\
0 &amp; otherwise
\end{cases} \\
&amp;= \mu(a,b)
\end{aligned}
\]</span></p>
<p>而且，当<span class="math inline">\(a \mid b\)</span>时，<span class="math inline">\(\mu(a,b)\)</span>有意义（或者说可能不为<span class="math inline">\(0\)</span>）。
故我们可以构造函数<span class="math inline">\(\mu&#39;(\frac b a) = \mu(1,\frac b a)=\mu(a,b)\)</span>，即可简化计算。
那么，我们有：</p>
<p><span class="math display">\[
\mu&#39;(p)=\begin{cases}
1 &amp; p=1 \\
-\sum_{x\neq 1 \wedge x\mid p} \mu&#39;(\frac p x) &amp; otherwise
\end{cases}
\]</span></p>
<p>这个函数又被称作经典莫比乌斯函数。</p>
<p>按照Incident algebra与一般函数之间的关系，取下限<span class="math inline">\(a=1\)</span>(故任取<span class="math inline">\(k\)</span>均与<span class="math inline">\(a\)</span>整除)，我们有下述两等价定义：</p>
<p><span class="math display">\[
\begin{aligned}
F(x) &amp;= \sum_{k\mid x} G(k) \\
G(x) &amp;= \sum_{k\mid x} F(k) \mu(k,x) = \sum_{k\mid x} F(k) \mu&#39;(\frac x k)
\end{aligned}
\]</span></p>
<p>其中称下式为上式的莫比乌斯反演公式。</p>]]></summary>
</entry>
<entry>
    <title>洛谷-P1383 高级打字机 题解</title>
    <link href="https://leoalex0.github.io/post/%E6%B4%9B%E8%B0%B7-P1383-%E9%AB%98%E7%BA%A7%E6%89%93%E5%AD%97%E6%9C%BA.html" />
    <id>https://leoalex0.github.io/post/%E6%B4%9B%E8%B0%B7-P1383-%E9%AB%98%E7%BA%A7%E6%89%93%E5%AD%97%E6%9C%BA.html</id>
    <published>2020-05-20T00:23:53Z</published>
    <updated>2020-05-20T00:23:53Z</updated>
    <summary type="html"><![CDATA[<!-- # 洛谷-P1383 高级打字机 题解 -->
<p>如果使用 Haskell 语言的话，本题可以使用 <code>containers</code> 包中的 <code>Data.Sequence</code> 秒杀，因为 <code>Seq</code> 本身便是一颗可持久的 <code>FingerTree</code>，用树套树的话，便可以做到一个非常优异的时间/空间复杂度。</p>
<p>惰性求值但是的确是在线算法，可以撤回撤回（返回撤回前的时间点，不知道算不算）。</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="ot">{-# OPTIONS_GHC -O2 #-}</span> <span class="co">-- 强开O2</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span>           <span class="dt">Control.Monad</span></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span>           <span class="dt">Data.Functor</span>  (($&gt;))</span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span>           <span class="dt">Data.Maybe</span>    (fromJust)</span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span>           <span class="dt">Data.Sequence</span></span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span>           <span class="dt">Text.Printf</span>   (printf)</span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a>(<span class="op">!</span>) <span class="ot">=</span> <span class="fu">index</span> <span class="co">-- 强行把函数变成运算符</span></span>
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-10"><a href="#cb1-10" aria-hidden="true" tabindex="-1"></a><span class="ot">main ::</span> <span class="dt">IO</span> ()</span>
<span id="cb1-11"><a href="#cb1-11" aria-hidden="true" tabindex="-1"></a>main <span class="ot">=</span> <span class="kw">do</span></span>
<span id="cb1-12"><a href="#cb1-12" aria-hidden="true" tabindex="-1"></a>    n <span class="ot">&lt;- readLn ::</span> <span class="dt">IO</span> <span class="dt">Int</span> <span class="co">-- 读取第一行的整数n，需要手动指定类型，不然会有歧义</span></span>
<span id="cb1-13"><a href="#cb1-13" aria-hidden="true" tabindex="-1"></a>    fromList [fromList []] <span class="ot">`foldM_&#39;`</span> [<span class="dv">1</span><span class="op">..</span>n] <span class="op">$</span> \<span class="fu">seq</span> _ <span class="ot">-&gt;</span> <span class="kw">do</span> <span class="co">-- 函数的中缀用法，注意初始的历史记录应该有一个空串</span></span>
<span id="cb1-14"><a href="#cb1-14" aria-hidden="true" tabindex="-1"></a>        [[op],c] <span class="ot">&lt;-</span> <span class="fu">words</span> <span class="op">&lt;$&gt;</span> <span class="fu">getLine</span> <span class="co">-- 读取每一行的指令，此处使用了模式匹配（见下文）</span></span>
<span id="cb1-15"><a href="#cb1-15" aria-hidden="true" tabindex="-1"></a>        <span class="kw">case</span> op <span class="kw">of</span></span>
<span id="cb1-16"><a href="#cb1-16" aria-hidden="true" tabindex="-1"></a>            <span class="ch">&#39;T&#39;</span> <span class="ot">-&gt;</span> <span class="fu">pure</span> <span class="op">$</span> (<span class="fu">seq</span><span class="op">!</span><span class="dv">0</span> <span class="op">|&gt;</span> <span class="fu">head</span> c) <span class="op">&lt;|</span> <span class="fu">seq</span>  <span class="co">-- 历史记录第0号（即上一次修改的结果）后插入当前字符，然后前插入历史记录</span></span>
<span id="cb1-17"><a href="#cb1-17" aria-hidden="true" tabindex="-1"></a>            <span class="ch">&#39;U&#39;</span> <span class="ot">-&gt;</span> <span class="fu">pure</span> <span class="op">$</span> (<span class="fu">seq</span><span class="op">!</span><span class="fu">read</span> c) <span class="op">&lt;|</span> <span class="fu">seq</span> <span class="co">-- 将参数读取为整数（此处无需标明，因为函数有明确的类型限制，编译器将自动推导） 取历史记录的相应项，前插入历史记录</span></span>
<span id="cb1-18"><a href="#cb1-18" aria-hidden="true" tabindex="-1"></a>            <span class="ch">&#39;Q&#39;</span> <span class="ot">-&gt;</span> printf <span class="st">&quot;%c\n&quot;</span> (<span class="fu">seq</span><span class="op">!</span><span class="dv">0</span><span class="op">!</span>(<span class="fu">read</span> c<span class="op">-</span><span class="dv">1</span>))  <span class="op">$&gt;</span> <span class="fu">seq</span> <span class="co">-- 将上一次修改的结果的相应位置的字符打印出来，并返回本身的历史记录（($ &gt;)运算符特性）</span></span>
<span id="cb1-19"><a href="#cb1-19" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-20"><a href="#cb1-20" aria-hidden="true" tabindex="-1"></a>foldM_&#39; z l f <span class="ot">=</span> foldM_ f z l <span class="co">-- 调整参数位置，追求好用</span></span></code></pre></div>
<p>代码十分朴素。</p>
<ul>
<li>空间复杂度: <span class="math inline">\(O(n\log n)\)</span> ，其中 <span class="math inline">\(n\)</span> 为总字符数（不是很紧确，但是..能过）</li>
<li>每次从后面添加字符，时间复杂度: <span class="math inline">\(O(1)\)</span></li>
<li>每次打印第 <span class="math inline">\(i\)</span> 个字符，时间复杂度: <span class="math inline">\(O(\log (\min (i,n-i)))\)</span> ，其中 <span class="math inline">\(n\)</span> 为当前字符数， <span class="math inline">\(i\)</span> 为需要打印的字符位置。</li>
<li>每次撤销，时间复杂度: <span class="math inline">\(O(\log (\min(i,n-i)))\)</span> ，其中 <span class="math inline">\(n\)</span> 为总修改次数，<span class="math inline">\(i\)</span> 为撤销步数。</li>
<li>总时间复杂度可估算为: <span class="math inline">\(O(n\log n)\)</span></li>
</ul>
<p>如果想看 <code>FingerTree</code> 原理或者希望以此为契机了解一下 <code>Haskell</code> 的话参见后文。也可以参考论文原文。</p>
<p>参考文献：</p>
<ul>
<li>Hinze R, Paterson R. Finger trees: a simple general-purpose data structure[J]. Journal of functional programming, 2006, 16(2): 197-217.</li>
<li><a href="https://hackage.haskell.org/package/fingertree" class="uri">https://hackage.haskell.org/package/fingertree</a></li>
<li><a href="https://hackage.haskell.org/package/container" class="uri">https://hackage.haskell.org/package/container</a></li>
<li><a href="https://wiki.haskell.org/Functional_dependencies" class="uri">https://wiki.haskell.org/Functional_dependencies</a></li>
</ul>
<h2 id="fingertree概述">FingerTree概述</h2>
<p>说到可持久化，就得想到 Immutable ，说到 Immutable ，自然而然就是函数式编程语言，说到函数式编程语言，自然而然就是 <code>Haskell</code> ，而说到纯函数式的数据结构，自然而然也绕不开 <code>FingerTree</code> 。</p>
<p><code>FingerTree</code> 是一种理论上非常通用也非常高效的数据结构，插入头/尾都只需要摊还 <span class="math inline">\(O(1)\)</span> 的时间，
而对其的“随机”访问只需要 <span class="math inline">\(O(\log \min(i,n-i))\)</span></p>
<blockquote>
<p>其中 <span class="math inline">\(i\)</span> 为你访问的下标，所以可以看出访问头/尾其实也是 <span class="math inline">\(O(1)\)</span> 。</p>
</blockquote>
<p>关于它的论文可以戳<a href="https://www.staff.city.ac.uk/~ross/papers/FingerTree.pdf">这里</a>。当然这不是最早的一篇，但我看的就是这篇，才疏学浅没办法在这里列更早的。</p>
<p>但为什么说“理论”上呢，就和斐波那契堆类似，他的常数比较大。（<del>主要因素是Immutable的语言必须维护一个GC来做垃圾回收。</del>）</p>
<blockquote>
<p>[更新] 事后我拿 <code>Rust</code> 实现了一遍 <code>FingerTree</code> ，发现其时空消耗均比 <code>Haskell</code> 大，故可认为并非GC原因。</p>
<p>不过也有可能是我写的常数就是大呢（</p>
<p>详见：<a href="https://www.luogu.com.cn/record/60755672">提交记录</a></p>
</blockquote>
<p>那为什么说“随机”呢？因为完全泛化的 <code>FingerTree</code> 的访问依赖的不是下标，而是一个被称作 <span class="math inline">\(Measure\)</span> （测度？我也不知道怎么翻译，所以直接拉的原文）的幺半群 (<strong>Monoid</strong>)。</p>
<blockquote>
<p>先解释一下什么是幺半群。幺半群是一个集合 <span class="math inline">\(X\)</span> 和集合里元素之间一个二元运算 <span class="math inline">\(\cdot:X\times X\to X\)</span> 的统称（有序对），并要求：</p>
<ul>
<li>二元运算满足结合率，即 <span class="math inline">\(\forall x,y,z\in X: (x\cdot y)\cdot z = x\cdot (y\cdot z)\)</span></li>
<li>集合 <span class="math inline">\(X\)</span> 中存在一个单位元，即 <span class="math inline">\(\exists e:(\forall x: e\cdot x=x\cdot e=x)\)</span></li>
</ul>
<p>比如说自然数集 <span class="math inline">\(\mathbb N\)</span> 与其上的加法 <span class="math inline">\(+\)</span> 便可以构成一个幺半群（后面我们可以用这条性质做出传统意义上的下标索引）</p>
<p>进一步，我们可以定义两个幺半群的笛卡尔积也是一个幺半群，即对于 <span class="math inline">\((X,\cdot_1)\)</span> 与 <span class="math inline">\((Y,\cdot_2)\)</span> ，
我们可以定义一个运算 <span class="math inline">\(\cdot:(X\times Y) \times (X\times Y)\to X\times Y\)</span> ，并使其满足结合率。定义方法如下：</p>
<ul>
<li><span class="math inline">\((x_1,y_1) \cdot (x_2,y_2)=(x_1\cdot_1 x_2,y_1\cdot_2 y_2)\)</span></li>
</ul>
<p>我们可以用这个条件来拓展我们索引方式（比如论文原文里可以看到用 <span class="math inline">\(\max\)</span> 幺半群实现的最大堆/优先队列）</p>
</blockquote>
<h2 id="haskell基础">Haskell基础</h2>
<p>首先，用 <code>Haskell</code> 的方式定义一下幺半群（实际上这个标准库有，不需要自己实现）</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="kw">class</span> <span class="dt">Monoid</span> a <span class="kw">where</span></span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a><span class="ot">    mempty ::</span> a</span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a><span class="ot">    mappend ::</span> a <span class="ot">-&gt;</span> a <span class="ot">-&gt;</span> a</span></code></pre></div>
<p>与常用的C-like语言中的 <code>class</code> 不同，在 <code>Haskell</code> 中，这代表一个类型类，是对类型 <code>a</code> 的一种约束，类似于接口/抽象类一类的概念。</p>
<p>然后是 <code>Foldable</code> ，它代表我们可以在一个类型(其实是一个 <code>Kind</code> ，拿类型生成类型的类型构造子)上按照某种顺序去遍历。类似于 <code>Java 8</code> 中的 <code>reduce</code> 。注意这个其实标准库也有，只是提一嘴。</p>
<div class="sourceCode" id="cb3"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="kw">class</span> <span class="dt">Foldable</span> f <span class="kw">where</span></span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a><span class="ot">    foldl ::</span> (b <span class="ot">-&gt;</span> a <span class="ot">-&gt;</span> b) <span class="ot">-&gt;</span> b <span class="ot">-&gt;</span> f a <span class="ot">-&gt;</span> b</span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a><span class="ot">    foldr ::</span> (a <span class="ot">-&gt;</span> b <span class="ot">-&gt;</span> b) <span class="ot">-&gt;</span> b <span class="ot">-&gt;</span> f a <span class="ot">-&gt;</span> b</span></code></pre></div>
<p>还需要提一嘴的是 <code>Haskell</code> 最基本的数据结构，链表(实际上还是单端链表，所以只提供最基本的功能)。</p>
<div class="sourceCode" id="cb4"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="kw">data</span> [] a <span class="ot">=</span> [] <span class="op">|</span> a<span class="op">:</span>([] a)</span></code></pre></div>
<p>它意味着一个 <span class="math inline">\(a\)</span> 类型的链表有两种构造方式，一种是空链表，令一种是一个 <span class="math inline">\(a\)</span> 类型的元素用 <code>:</code> 运算符拼接一个 <span class="math inline">\(a\)</span> 类型的链表。（对，中缀构造函数， <code>C++</code> 是做不到这点的。）</p>
<p>另外还有一个比较常用的 <code>data</code> 被称作 <code>Maybe</code> ，它的定义等价于：</p>
<div class="sourceCode" id="cb5"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="kw">data</span> <span class="dt">Maybe</span> a <span class="ot">=</span> <span class="dt">Nothing</span> <span class="op">|</span> <span class="dt">Just</span> a</span></code></pre></div>
<p>想一想，这个定义是什么意思？</p>
<p>从这里开始就可以看到 <code>Haskell</code> 的一些特性了。<code>Haskell</code> 中的数据结构并非由<em>字段</em>和<em>操作</em>构成，而是由几类数据的组合组合而成。当然它是可以实现上面的俩 <code>class</code> 的。这里我不做过多介绍。</p>
<h2 id="fingertree-实现简要复述论文">FingerTree 实现（简要复述论文）</h2>
<p><img src="https://www.staff.city.ac.uk/~ross/papers/FingerTree/example-tree.svg" alt="FingerTree 概要图" /></p>
<p>首先是一些基础定义。</p>
<div class="sourceCode" id="cb6"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a><span class="co">-- | Things that can be measured.</span></span>
<span id="cb6-2"><a href="#cb6-2" aria-hidden="true" tabindex="-1"></a><span class="kw">class</span> (<span class="dt">Monoid</span> v) <span class="ot">=&gt;</span> <span class="dt">Measured</span> v a <span class="op">|</span> a <span class="ot">-&gt;</span> v <span class="kw">where</span></span>
<span id="cb6-3"><a href="#cb6-3" aria-hidden="true" tabindex="-1"></a><span class="ot">    measure ::</span> a <span class="ot">-&gt;</span> v</span>
<span id="cb6-4"><a href="#cb6-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-5"><a href="#cb6-5" aria-hidden="true" tabindex="-1"></a><span class="kw">data</span> <span class="dt">Node</span> v a <span class="ot">=</span> <span class="dt">Node2</span> v a a <span class="op">|</span> <span class="dt">Node3</span> v a a a</span>
<span id="cb6-6"><a href="#cb6-6" aria-hidden="true" tabindex="-1"></a><span class="kw">data</span> <span class="dt">Digit</span> a</span>
<span id="cb6-7"><a href="#cb6-7" aria-hidden="true" tabindex="-1"></a>    <span class="ot">=</span> <span class="dt">One</span> a</span>
<span id="cb6-8"><a href="#cb6-8" aria-hidden="true" tabindex="-1"></a>    <span class="op">|</span> <span class="dt">Two</span> a a</span>
<span id="cb6-9"><a href="#cb6-9" aria-hidden="true" tabindex="-1"></a>    <span class="op">|</span> <span class="dt">Three</span> a a a</span>
<span id="cb6-10"><a href="#cb6-10" aria-hidden="true" tabindex="-1"></a>    <span class="op">|</span> <span class="dt">Four</span> a a a a</span>
<span id="cb6-11"><a href="#cb6-11" aria-hidden="true" tabindex="-1"></a><span class="kw">data</span> <span class="dt">FingerTree</span> v a</span>
<span id="cb6-12"><a href="#cb6-12" aria-hidden="true" tabindex="-1"></a>     <span class="ot">=</span> <span class="dt">Empty</span></span>
<span id="cb6-13"><a href="#cb6-13" aria-hidden="true" tabindex="-1"></a>     <span class="op">|</span> <span class="dt">Single</span> a</span>
<span id="cb6-14"><a href="#cb6-14" aria-hidden="true" tabindex="-1"></a>     <span class="op">|</span> <span class="dt">Deep</span> v (<span class="dt">Digit</span> a) (<span class="dt">FingerTree</span> v (<span class="dt">Node</span> v a)) (<span class="dt">Digit</span> a)</span></code></pre></div>
<p>这里开始出现了最开始提到的 <code>Measured</code>，此处将其抽象为一个可以将某个元素<em>测量</em>出一个 <code>Monoid</code> 的函数。但这个 <code>Monoid</code> 有大用。</p>
<p>此处出现的语法使用了一个 <code>Functional Dependencies</code> 的拓展(可参见 <a href="https://wiki.haskell.org/Functional_dependencies">wiki</a> )。大概意思是保证对于一个类型，我只能把它 <code>measure</code> 成一种其他类型，不能 <code>measure</code> 成另一种。也就是说，类型 <code>a</code> 到类型 <code>v</code> 是一个单射。</p>
<p>可以看到这里的 <span class="math inline">\(v\)</span> 类型变元，这个就是稍后要维护的 <code>Monoid</code>，也是索引数据的依据。</p>
<p>可以看到一个 <code>Node</code> 可能有2-3个元素，保证一个 <code>Digit</code> 有 1-4 个元素。(图中也可以看出，不过引用链接是国外的可能比较卡) <code>Digit</code> 和 <code>Node</code> 也可以相互转化，拼接，但后文不再说明实现。</p>
<p>还有一点值得注意的是，如果当前这一层的 <code>FingerTree</code> 缓存的是 <code>a</code> 类型的话，那么下一层所缓存的就是 <code>Node a</code> 类型了。这也就意味着，下层比上层“厚实”。</p>
<p>自然地，我们可以给 <code>Node</code> 和 <code>Digit</code> 来个 <code>Foldable</code>，但具体部分就不展示了。</p>
<p>那么我们应该如何方便的维护这个 <span class="math inline">\(v\)</span> 类型的数据呢，答案是换一种方式重写构造函数。</p>
<div class="sourceCode" id="cb7"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true" tabindex="-1"></a><span class="ot">deep ::</span>  (<span class="dt">Measured</span> v a) <span class="ot">=&gt;</span></span>
<span id="cb7-2"><a href="#cb7-2" aria-hidden="true" tabindex="-1"></a>      <span class="dt">Digit</span> a <span class="ot">-&gt;</span> <span class="dt">FingerTree</span> v (<span class="dt">Node</span> v a) <span class="ot">-&gt;</span> <span class="dt">Digit</span> a <span class="ot">-&gt;</span> <span class="dt">FingerTree</span> v a</span>
<span id="cb7-3"><a href="#cb7-3" aria-hidden="true" tabindex="-1"></a> deep pr m sf <span class="ot">=</span> <span class="dt">Deep</span> ((measure pr <span class="ot">`mappend`</span> measure m) <span class="ot">`mappend`</span> measure sf) pr m sf</span></code></pre></div>
<p>此处的 <code>mappend</code> 是 <code>Haskell</code> 里函数的中缀用法（就是把函数当运算符用）。</p>
<p>此时，我们不需要每次构造树的时候手动维护 <span class="math inline">\(v\)</span> 类型的数据了，只需要简单把 <code>Deep</code> 替换为 <code>deep</code> 即可。</p>
<p>另外，关于 <code>Measured</code> 本身，我们也可以让 <code>FingerTree</code> 也是 <code>Measured</code>，具体代码如下：</p>
<div class="sourceCode" id="cb8"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb8-1"><a href="#cb8-1" aria-hidden="true" tabindex="-1"></a><span class="kw">instance</span> (<span class="dt">Measured</span> v a) <span class="ot">=&gt;</span> <span class="dt">Measured</span> v (<span class="dt">FingerTree</span> v a) <span class="kw">where</span></span>
<span id="cb8-2"><a href="#cb8-2" aria-hidden="true" tabindex="-1"></a>      measure <span class="dt">Empty</span>           <span class="ot">=</span>  <span class="fu">mempty</span></span>
<span id="cb8-3"><a href="#cb8-3" aria-hidden="true" tabindex="-1"></a>      measure (<span class="dt">Single</span> x)      <span class="ot">=</span>  measure x</span>
<span id="cb8-4"><a href="#cb8-4" aria-hidden="true" tabindex="-1"></a>      measure (<span class="dt">Deep</span> v _ _ _)  <span class="ot">=</span>  v</span></code></pre></div>
<p>注意到此处使用了 <code>Haskell</code> 被称作<strong>模式匹配</strong> (<strong>Pattern Match</strong>)的特性。（不是模式串匹配啦）把数据结构重新打回了它最开始定义时的样子，并且怎么被构造的就怎么被匹配。(真-打回娘胎)</p>
<blockquote>
<p>模式匹配不止可以匹配一层，也可以匹配多层。至于用途嘛..请自行搜索 Haskell 的 20 行红黑树。</p>
</blockquote>
<p>可以看到对于 <code>Deep</code> 一类，我们直接使用了其缓存的 <span class="math inline">\(v\)</span> 类型字段，这样就可以在 <span class="math inline">\(O(1)\)</span> 时间内得到一颗子树的测度。(如果对单个元素的 <code>measure</code> 也是 <span class="math inline">\(O(1)\)</span> 的话)</p>
<p>当然 <code>Node</code> 和 <code>Digit</code> 也可以是 <code>Measured</code>，具体实现请自行脑补。</p>
<p>接下来是前插入的定义。</p>
<div class="sourceCode" id="cb9"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb9-1"><a href="#cb9-1" aria-hidden="true" tabindex="-1"></a><span class="kw">infixr</span> <span class="dv">5</span> <span class="op">&lt;|</span></span>
<span id="cb9-2"><a href="#cb9-2" aria-hidden="true" tabindex="-1"></a><span class="co">-- | /O(1)/. Add an element to the left end of a sequence.</span></span>
<span id="cb9-3"><a href="#cb9-3" aria-hidden="true" tabindex="-1"></a><span class="co">-- Mnemonic: a triangle with the single element at the pointy end.</span></span>
<span id="cb9-4"><a href="#cb9-4" aria-hidden="true" tabindex="-1"></a><span class="ot">(&lt;|) ::</span> <span class="dt">Measured</span> v a <span class="ot">=&gt;</span> a <span class="ot">-&gt;</span> <span class="dt">FingerTree</span> v a <span class="ot">-&gt;</span> <span class="dt">FingerTree</span> v a</span>
<span id="cb9-5"><a href="#cb9-5" aria-hidden="true" tabindex="-1"></a>a <span class="op">&lt;|</span> <span class="dt">Empty</span>              <span class="ot">=</span>  <span class="dt">Single</span> a</span>
<span id="cb9-6"><a href="#cb9-6" aria-hidden="true" tabindex="-1"></a>a <span class="op">&lt;|</span> <span class="dt">Single</span> b           <span class="ot">=</span>  deep (<span class="dt">One</span> a) <span class="dt">Empty</span> (<span class="dt">One</span> b)</span>
<span id="cb9-7"><a href="#cb9-7" aria-hidden="true" tabindex="-1"></a>a <span class="op">&lt;|</span> <span class="dt">Deep</span> v (<span class="dt">Four</span> b c d e) m sf</span>
<span id="cb9-8"><a href="#cb9-8" aria-hidden="true" tabindex="-1"></a>    <span class="ot">=</span> <span class="dt">Deep</span> (measure a <span class="ot">`mappend`</span> v) (<span class="dt">Two</span> a b) (node3 c d e <span class="op">&lt;|</span> m) sf</span>
<span id="cb9-9"><a href="#cb9-9" aria-hidden="true" tabindex="-1"></a>a <span class="op">&lt;|</span> <span class="dt">Deep</span> v pr m sf     <span class="ot">=</span> <span class="dt">Deep</span> (measure a <span class="ot">`mappend`</span> v) (consDigit a pr) m sf</span></code></pre></div>
<p>注意到这里我们自定义了一个运算符，它的优先级是5,并且是右结合的。（不愧是 <code>Haskell</code>，轻易做到了 <code>C++</code> 做不到的事情。）</p>
<p><code>consDigit</code> 就是给 <code>pr</code> 前插入一个元素。当然，如果是满的肯定会报错，但是在它之前的模式匹配会避免这一点发生。</p>
<p>类似地还有后插入</p>
<div class="sourceCode" id="cb10"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb10-1"><a href="#cb10-1" aria-hidden="true" tabindex="-1"></a><span class="kw">infixl</span> <span class="dv">5</span> <span class="op">.</span></span>
<span id="cb10-2"><a href="#cb10-2" aria-hidden="true" tabindex="-1"></a><span class="co">-- | /O(1)/. Add an element to the right end of a sequence.</span></span>
<span id="cb10-3"><a href="#cb10-3" aria-hidden="true" tabindex="-1"></a><span class="co">-- Mnemonic: a triangle with the single element at the pointy end.</span></span>
<span id="cb10-4"><a href="#cb10-4" aria-hidden="true" tabindex="-1"></a><span class="ot">(|&gt;) ::</span> (<span class="dt">Measured</span> v a) <span class="ot">=&gt;</span> <span class="dt">FingerTree</span> v a <span class="ot">-&gt;</span> a <span class="ot">-&gt;</span> <span class="dt">FingerTree</span> v a</span>
<span id="cb10-5"><a href="#cb10-5" aria-hidden="true" tabindex="-1"></a><span class="dt">Empty</span> <span class="op">|&gt;</span> a              <span class="ot">=</span>  <span class="dt">Single</span> a</span>
<span id="cb10-6"><a href="#cb10-6" aria-hidden="true" tabindex="-1"></a><span class="dt">Single</span> a <span class="op">|&gt;</span> b           <span class="ot">=</span>  deep (<span class="dt">One</span> a) <span class="dt">Empty</span> (<span class="dt">One</span> b)</span>
<span id="cb10-7"><a href="#cb10-7" aria-hidden="true" tabindex="-1"></a><span class="dt">Deep</span> v pr m (<span class="dt">Four</span> a b c d) <span class="op">|&gt;</span> e</span>
<span id="cb10-8"><a href="#cb10-8" aria-hidden="true" tabindex="-1"></a>    <span class="ot">=</span> <span class="dt">Deep</span> (v <span class="ot">`mappend`</span> measure e) pr (m <span class="op">|&gt;</span> node3 a b c) (<span class="dt">Two</span> d e)</span>
<span id="cb10-9"><a href="#cb10-9" aria-hidden="true" tabindex="-1"></a><span class="dt">Deep</span> v pr m sf <span class="op">|&gt;</span> x     <span class="ot">=</span> <span class="dt">Deep</span> (v <span class="ot">`mappend`</span> measure x) pr m (snocDigit sf x)</span></code></pre></div>
<p>可以看到代码基本上和前插入是对称的。（数据结构本身也是对称的，所以这里写作 <code>snocDigit</code>，其中 <code>snoc</code> 正是 <code>cons</code> 的回文）后面还有很多对称的结构，均以左半部分为例。</p>
<p>然后是一个分割原语（当然右侧也是对称的，可以看到又出现了运算符构造子。）</p>
<div class="sourceCode" id="cb11"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb11-1"><a href="#cb11-1" aria-hidden="true" tabindex="-1"></a><span class="co">-- | View of the left end of a sequence.</span></span>
<span id="cb11-2"><a href="#cb11-2" aria-hidden="true" tabindex="-1"></a><span class="kw">data</span> <span class="dt">ViewL</span> s a</span>
<span id="cb11-3"><a href="#cb11-3" aria-hidden="true" tabindex="-1"></a>     <span class="ot">=</span> <span class="dt">EmptyL</span>        <span class="co">-- ^ empty sequence</span></span>
<span id="cb11-4"><a href="#cb11-4" aria-hidden="true" tabindex="-1"></a>     <span class="op">|</span> a <span class="op">:&lt;</span> s a      <span class="co">-- ^ leftmost element and the rest of the sequence</span></span>
<span id="cb11-5"><a href="#cb11-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb11-6"><a href="#cb11-6" aria-hidden="true" tabindex="-1"></a><span class="co">-- | /O(1)/. Analyse the left end of a sequence.</span></span>
<span id="cb11-7"><a href="#cb11-7" aria-hidden="true" tabindex="-1"></a><span class="ot">viewl ::</span> (<span class="dt">Measured</span> v a) <span class="ot">=&gt;</span> <span class="dt">FingerTree</span> v a <span class="ot">-&gt;</span> <span class="dt">ViewL</span> (<span class="dt">FingerTree</span> v) a</span>
<span id="cb11-8"><a href="#cb11-8" aria-hidden="true" tabindex="-1"></a>viewl <span class="dt">Empty</span>                     <span class="ot">=</span>  <span class="dt">EmptyL</span></span>
<span id="cb11-9"><a href="#cb11-9" aria-hidden="true" tabindex="-1"></a>viewl (<span class="dt">Single</span> x)                <span class="ot">=</span>  x <span class="op">:&lt;</span> <span class="dt">Empty</span></span>
<span id="cb11-10"><a href="#cb11-10" aria-hidden="true" tabindex="-1"></a>viewl (<span class="dt">Deep</span> _ (<span class="dt">One</span> x) m sf)     <span class="ot">=</span>  x <span class="op">:&lt;</span> rotL m sf</span>
<span id="cb11-11"><a href="#cb11-11" aria-hidden="true" tabindex="-1"></a>viewl (<span class="dt">Deep</span> _ pr m sf)          <span class="ot">=</span>  lheadDigit pr <span class="op">:&lt;</span> deep (ltailDigit pr) m sf</span>
<span id="cb11-12"><a href="#cb11-12" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb11-13"><a href="#cb11-13" aria-hidden="true" tabindex="-1"></a><span class="ot">rotL ::</span> (<span class="dt">Measured</span> v a) <span class="ot">=&gt;</span> <span class="dt">FingerTree</span> v (<span class="dt">Node</span> v a) <span class="ot">-&gt;</span> <span class="dt">Digit</span> a <span class="ot">-&gt;</span> <span class="dt">FingerTree</span> v a</span>
<span id="cb11-14"><a href="#cb11-14" aria-hidden="true" tabindex="-1"></a>rotL m sf      <span class="ot">=</span>   <span class="kw">case</span> viewl m <span class="kw">of</span></span>
<span id="cb11-15"><a href="#cb11-15" aria-hidden="true" tabindex="-1"></a>    <span class="dt">EmptyL</span>  <span class="ot">-&gt;</span>  digitToTree sf</span>
<span id="cb11-16"><a href="#cb11-16" aria-hidden="true" tabindex="-1"></a>    a <span class="op">:&lt;</span> m&#39; <span class="ot">-&gt;</span>  <span class="dt">Deep</span> (measure m <span class="ot">`mappend`</span> measure sf) (nodeToDigit a) m&#39; sf</span></code></pre></div>
<p><code>lheadDigit</code> 和 <code>ltailDigit</code> 是指一个 <code>Digit</code> 的最左边元素和剩下的元素，当然如果 <code>Digit</code> 只有一个元素的画 <code>ltail</code> 就会报错，但是前面的模式匹配也避免了这一点。</p>
<p><code>digitToTree</code> 是一个将 <code>Digit</code> 变为一个 <code>FingerTree</code> 的函数，因为也就常数个元素，所以时间复杂度 <span class="math inline">\(O(1)\)</span>。</p>
<p><code>nodeToDigit</code> 也是跟名字异样，将一个 <code>Node</code>(2或3个元素)变成一个 <code>Digit</code>。</p>
<p><code>case ... of</code> 语法是 <code>Haskell</code> 类似于 <code>C++</code> 的 <code>switch</code> 语句的地方。不同之处在于其是有返回值的，更像一个多出口的 <code>?:</code> 运算符。</p>
<p><code>viewl</code> 函数构造一颗树的左分割，将其切割出最左元素（若没有则用 <code>EmptyL</code> 构造子）。</p>
<p>另外值得注意的是此处的 <code>viewl</code> 和 <code>rotL</code> 是互相调用，也是一种递归哦。</p>
<p>当然其有对称实现 <code>viewr</code>。但不在此赘述。</p>
<p>有了 <code>viewl</code> 之后，我们就可以处理一些 <code>prefix</code> (即 <code>Deep</code> 中第一个 <code>Digit</code>)为空/不存在时对树的构造了</p>
<div class="sourceCode" id="cb12"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb12-1"><a href="#cb12-1" aria-hidden="true" tabindex="-1"></a><span class="ot">deepL ::</span> (<span class="dt">Measured</span> v a) <span class="ot">=&gt;</span></span>
<span id="cb12-2"><a href="#cb12-2" aria-hidden="true" tabindex="-1"></a>    <span class="dt">Maybe</span> (<span class="dt">Digit</span> a) <span class="ot">-&gt;</span> <span class="dt">FingerTree</span> v (<span class="dt">Node</span> v a) <span class="ot">-&gt;</span> <span class="dt">Digit</span> a <span class="ot">-&gt;</span> <span class="dt">FingerTree</span> v a</span>
<span id="cb12-3"><a href="#cb12-3" aria-hidden="true" tabindex="-1"></a>deepL <span class="dt">Nothing</span> m sf      <span class="ot">=</span>   rotL m sf</span>
<span id="cb12-4"><a href="#cb12-4" aria-hidden="true" tabindex="-1"></a>deepL (<span class="dt">Just</span> pr) m sf    <span class="ot">=</span>   deep pr m sf</span></code></pre></div>
<p>此处的 <code>Nothing</code> 就是指 <code>prefix</code> 不存在的情况，此时需要<em>左旋</em>一波。</p>
<p>当然对于 <code>Just</code> 的情况就直接调用上面的deep了。</p>
<p>那么什么情况下左边/右边为空呢？当然是做分割的时候了。</p>
<p>那么终于，要迎来最后的部分了，搜索，与分割。</p>
<div class="sourceCode" id="cb13"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb13-1"><a href="#cb13-1" aria-hidden="true" tabindex="-1"></a><span class="kw">data</span> <span class="dt">Split</span> t a <span class="ot">=</span> <span class="dt">Split</span> t a t</span>
<span id="cb13-2"><a href="#cb13-2" aria-hidden="true" tabindex="-1"></a><span class="kw">data</span> <span class="dt">SearchResult</span> v a</span>
<span id="cb13-3"><a href="#cb13-3" aria-hidden="true" tabindex="-1"></a>    <span class="ot">=</span> <span class="dt">Position</span> (<span class="dt">FingerTree</span> v a) a (<span class="dt">FingerTree</span> v a)</span>
<span id="cb13-4"><a href="#cb13-4" aria-hidden="true" tabindex="-1"></a>        <span class="co">-- ^ A tree opened at a particular element: the prefix to the</span></span>
<span id="cb13-5"><a href="#cb13-5" aria-hidden="true" tabindex="-1"></a>       <span class="co">-- left, the element, and the suffix to the right.</span></span>
<span id="cb13-6"><a href="#cb13-6" aria-hidden="true" tabindex="-1"></a>   <span class="op">|</span> <span class="dt">OnLeft</span></span>
<span id="cb13-7"><a href="#cb13-7" aria-hidden="true" tabindex="-1"></a>       <span class="co">-- ^ A position to the left of the sequence, indicating that the</span></span>
<span id="cb13-8"><a href="#cb13-8" aria-hidden="true" tabindex="-1"></a>       <span class="co">-- predicate is &#39;True&#39; at both ends.</span></span>
<span id="cb13-9"><a href="#cb13-9" aria-hidden="true" tabindex="-1"></a>   <span class="op">|</span> <span class="dt">OnRight</span></span>
<span id="cb13-10"><a href="#cb13-10" aria-hidden="true" tabindex="-1"></a>       <span class="co">-- ^ A position to the right of the sequence, indicating that the</span></span>
<span id="cb13-11"><a href="#cb13-11" aria-hidden="true" tabindex="-1"></a>       <span class="co">-- predicate is &#39;False&#39; at both ends.</span></span>
<span id="cb13-12"><a href="#cb13-12" aria-hidden="true" tabindex="-1"></a>   <span class="op">|</span> <span class="dt">Nowhere</span></span>
<span id="cb13-13"><a href="#cb13-13" aria-hidden="true" tabindex="-1"></a>       <span class="co">-- ^ No position in the tree, returned if the predicate is &#39;True&#39;</span></span>
<span id="cb13-14"><a href="#cb13-14" aria-hidden="true" tabindex="-1"></a>       <span class="co">-- at the left end and &#39;False&#39; at the right end.  This will not</span></span>
<span id="cb13-15"><a href="#cb13-15" aria-hidden="true" tabindex="-1"></a>       <span class="co">-- occur if the predicate in monotonic on the tree.</span></span>
<span id="cb13-16"><a href="#cb13-16" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb13-17"><a href="#cb13-17" aria-hidden="true" tabindex="-1"></a><span class="ot">search ::</span> (<span class="dt">Measured</span> v a) <span class="ot">=&gt;</span></span>
<span id="cb13-18"><a href="#cb13-18" aria-hidden="true" tabindex="-1"></a>    (v <span class="ot">-&gt;</span> v <span class="ot">-&gt;</span> <span class="dt">Bool</span>) <span class="ot">-&gt;</span> <span class="dt">FingerTree</span> v a <span class="ot">-&gt;</span> <span class="dt">SearchResult</span> v a</span>
<span id="cb13-19"><a href="#cb13-19" aria-hidden="true" tabindex="-1"></a>search p t</span>
<span id="cb13-20"><a href="#cb13-20" aria-hidden="true" tabindex="-1"></a>  <span class="op">|</span> p_left <span class="op">&amp;&amp;</span> p_right <span class="ot">=</span> <span class="dt">OnLeft</span></span>
<span id="cb13-21"><a href="#cb13-21" aria-hidden="true" tabindex="-1"></a>  <span class="op">|</span> <span class="fu">not</span> p_left <span class="op">&amp;&amp;</span> p_right <span class="ot">=</span> <span class="kw">case</span> searchTree p <span class="fu">mempty</span> t <span class="fu">mempty</span> <span class="kw">of</span></span>
<span id="cb13-22"><a href="#cb13-22" aria-hidden="true" tabindex="-1"></a>         <span class="dt">Split</span> l x r <span class="ot">-&gt;</span> <span class="dt">Position</span> l x r</span>
<span id="cb13-23"><a href="#cb13-23" aria-hidden="true" tabindex="-1"></a>  <span class="op">|</span> <span class="fu">not</span> p_left <span class="op">&amp;&amp;</span> <span class="fu">not</span> p_right <span class="ot">=</span> <span class="dt">OnRight</span></span>
<span id="cb13-24"><a href="#cb13-24" aria-hidden="true" tabindex="-1"></a>  <span class="op">|</span> <span class="fu">otherwise</span> <span class="ot">=</span> <span class="dt">Nowhere</span></span>
<span id="cb13-25"><a href="#cb13-25" aria-hidden="true" tabindex="-1"></a>   <span class="kw">where</span></span>
<span id="cb13-26"><a href="#cb13-26" aria-hidden="true" tabindex="-1"></a>     p_left <span class="ot">=</span> p <span class="fu">mempty</span> vt</span>
<span id="cb13-27"><a href="#cb13-27" aria-hidden="true" tabindex="-1"></a>     p_right <span class="ot">=</span> p vt <span class="fu">mempty</span></span>
<span id="cb13-28"><a href="#cb13-28" aria-hidden="true" tabindex="-1"></a>     vt <span class="ot">=</span> measure t</span>
<span id="cb13-29"><a href="#cb13-29" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb13-30"><a href="#cb13-30" aria-hidden="true" tabindex="-1"></a><span class="ot">searchTree ::</span> (<span class="dt">Measured</span> v a) <span class="ot">=&gt;</span></span>
<span id="cb13-31"><a href="#cb13-31" aria-hidden="true" tabindex="-1"></a>     (v <span class="ot">-&gt;</span> v <span class="ot">-&gt;</span> <span class="dt">Bool</span>) <span class="ot">-&gt;</span> v <span class="ot">-&gt;</span> <span class="dt">FingerTree</span> v a <span class="ot">-&gt;</span> v <span class="ot">-&gt;</span> <span class="dt">Split</span> (<span class="dt">FingerTree</span> v a) a</span>
<span id="cb13-32"><a href="#cb13-32" aria-hidden="true" tabindex="-1"></a>searchTree _ _ <span class="dt">Empty</span> _ <span class="ot">=</span> illegal_argument <span class="st">&quot;searchTree&quot;</span></span>
<span id="cb13-33"><a href="#cb13-33" aria-hidden="true" tabindex="-1"></a>searchTree _ _ (<span class="dt">Single</span> x) _ <span class="ot">=</span> <span class="dt">Split</span> <span class="dt">Empty</span> x <span class="dt">Empty</span></span>
<span id="cb13-34"><a href="#cb13-34" aria-hidden="true" tabindex="-1"></a>searchTree p vl (<span class="dt">Deep</span> _ pr m sf) vr</span>
<span id="cb13-35"><a href="#cb13-35" aria-hidden="true" tabindex="-1"></a>  <span class="op">|</span> p vlp vmsr  <span class="ot">=</span>  <span class="kw">let</span>  <span class="dt">Split</span> l x r     <span class="ot">=</span>  searchDigit p vl pr vmsr</span>
<span id="cb13-36"><a href="#cb13-36" aria-hidden="true" tabindex="-1"></a>                   <span class="kw">in</span>   <span class="dt">Split</span> (<span class="fu">maybe</span> <span class="dt">Empty</span> digitToTree l) x (deepL r m sf)</span>
<span id="cb13-37"><a href="#cb13-37" aria-hidden="true" tabindex="-1"></a>  <span class="op">|</span> p vlpm vsr  <span class="ot">=</span>  <span class="kw">let</span>  <span class="dt">Split</span> ml xs mr  <span class="ot">=</span>  searchTree p vlp m vsr</span>
<span id="cb13-38"><a href="#cb13-38" aria-hidden="true" tabindex="-1"></a>                        <span class="dt">Split</span> l x r     <span class="ot">=</span>  searchNode p (vlp <span class="ot">`mappend`</span> measure ml)       xs (measure mr <span class="ot">`mappend`</span> vsr)</span>
<span id="cb13-39"><a href="#cb13-39" aria-hidden="true" tabindex="-1"></a>                   <span class="kw">in</span>   <span class="dt">Split</span> (deepR pr  ml l) x (deepL r mr sf)</span>
<span id="cb13-40"><a href="#cb13-40" aria-hidden="true" tabindex="-1"></a>  <span class="op">|</span> <span class="fu">otherwise</span>   <span class="ot">=</span>  <span class="kw">let</span>  <span class="dt">Split</span> l x r     <span class="ot">=</span>  searchDigit p vlpm sf vr</span>
<span id="cb13-41"><a href="#cb13-41" aria-hidden="true" tabindex="-1"></a>                   <span class="kw">in</span>   <span class="dt">Split</span> (deepR pr  m  l) x (<span class="fu">maybe</span> <span class="dt">Empty</span> digitToTree r)</span>
<span id="cb13-42"><a href="#cb13-42" aria-hidden="true" tabindex="-1"></a>  <span class="kw">where</span></span>
<span id="cb13-43"><a href="#cb13-43" aria-hidden="true" tabindex="-1"></a>    vlp     <span class="ot">=</span>  vl <span class="ot">`mappend`</span> measure pr</span>
<span id="cb13-44"><a href="#cb13-44" aria-hidden="true" tabindex="-1"></a>    vlpm    <span class="ot">=</span>  vlp <span class="ot">`mappend`</span> vm</span>
<span id="cb13-45"><a href="#cb13-45" aria-hidden="true" tabindex="-1"></a>    vmsr    <span class="ot">=</span>  vm <span class="ot">`mappend`</span> vsr</span>
<span id="cb13-46"><a href="#cb13-46" aria-hidden="true" tabindex="-1"></a>    vsr     <span class="ot">=</span>  measure sf <span class="ot">`mappend`</span> vr</span>
<span id="cb13-47"><a href="#cb13-47" aria-hidden="true" tabindex="-1"></a>    vm      <span class="ot">=</span>  measure m</span></code></pre></div>
<p>此处只列出了 <code>searchTree</code> 这一子函数，且十分冗长，其实 <code>searchNode</code> 和 <code>searchDigit</code> 也类似，本质上就是为了找到第一个使得对于输入的函数，在值左边的分割为假，右边的为真。一步步细化下去而已。</p>
<p><code>maybe</code> 函数的类型是 <code>b -&gt; (a -&gt; b) -&gt; Maybe a -&gt; b</code>，看类型签名基本可以猜到功能。所以也不细说。</p>
<p>这里可以看到 <code>where</code> 从句。<code>where</code> 从句和常量定义很像，但却是后置的，而且具有惰性求值的特征（对，自带Lazy，而且实际上如果不做额外标注，整个 <code>Haskell</code> 程序都是惰性求值的）。</p>
<p>到这一步为止，我们只差将原本用于索引的下标整成 <code>Monoid</code> 就好了，并且其和任意类型均可以构成 <code>Measured</code>（因为单个元素大小都是1）</p>
<div class="sourceCode" id="cb14"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb14-1"><a href="#cb14-1" aria-hidden="true" tabindex="-1"></a><span class="kw">newtype</span> <span class="dt">Size</span> <span class="ot">=</span> <span class="dt">Size</span> <span class="dt">Int</span></span>
<span id="cb14-2"><a href="#cb14-2" aria-hidden="true" tabindex="-1"></a><span class="kw">newtype</span> <span class="dt">Elem</span> a <span class="ot">=</span> <span class="dt">Elem</span> a</span>
<span id="cb14-3"><a href="#cb14-3" aria-hidden="true" tabindex="-1"></a><span class="kw">instance</span> <span class="dt">Monoid</span> <span class="dt">Size</span> <span class="kw">where</span></span>
<span id="cb14-4"><a href="#cb14-4" aria-hidden="true" tabindex="-1"></a>	<span class="fu">mempty</span> <span class="ot">=</span> <span class="dt">Size</span> <span class="dv">0</span></span>
<span id="cb14-5"><a href="#cb14-5" aria-hidden="true" tabindex="-1"></a>	<span class="fu">mappend</span> (<span class="dt">Size</span> a) (<span class="dt">Size</span> b) <span class="ot">=</span> <span class="dt">Size</span> (a<span class="op">+</span>b)</span>
<span id="cb14-6"><a href="#cb14-6" aria-hidden="true" tabindex="-1"></a><span class="kw">instance</span> <span class="dt">Measured</span> <span class="dt">Size</span> (<span class="dt">Elem</span> a) <span class="kw">where</span></span>
<span id="cb14-7"><a href="#cb14-7" aria-hidden="true" tabindex="-1"></a>	measure _ <span class="ot">=</span> <span class="dt">Size</span> <span class="dv">1</span></span></code></pre></div>
<p>这里注意，由于我们之前使用了 <code>Functional Dependencies</code>，所以我们需要新建一个类型作为容器来容纳我们的类型。所以此处使用了不会导致额外开销的 <code>newtype</code> 关键字替代 <code>data</code>（在当前环境下，二者语义一致）</p>
<p>接下来就可以实现 <code>Seq</code> 了，具体可以参考 <code>container</code> 包的源代码或者论文原文。</p>]]></summary>
</entry>
<entry>
    <title>Raspberry PI 3B+刷OpenWRT坑点笔记</title>
    <link href="https://leoalex0.github.io/post/RPI3B-OpenWrt%E5%9D%91%E7%82%B9.html" />
    <id>https://leoalex0.github.io/post/RPI3B-OpenWrt%E5%9D%91%E7%82%B9.html</id>
    <published>2020-05-11T00:24:30Z</published>
    <updated>2020-05-11T00:24:30Z</updated>
    <summary type="html"><![CDATA[<h2 id="刷写系统环节">刷写系统环节</h2>
<ol>
<li><p>RPI3B和RPI3B+用的同一款固件，可以通用</p></li>
<li><p>用个稍微好一点点的读卡器吧，毕竟也有几百M</p>
<blockquote>
<p>我Surface上的读卡器插两次就扫不到设备了，十分蛇皮</p>
<p>USB 读卡器又有点点接触不良，刷个几百k就一个读写错误，心累.jpg</p>
</blockquote></li>
<li><p>如果希望能完全使用TF卡的空间，单纯的resize分区是不行的，还要记得<code>fsck</code>然后跑一遍<code>resize2fs</code></p>
<blockquote>
<p>不知道为什么我先配完系统之后改大小然后就崩了。</p>
<p>但是如果在第一次启动前把分区大小调好就没事。</p>
</blockquote></li>
<li><p><code>WiFi</code>的启用需要等到第一次启动以后，第一次启动的时候会生成配置文件，之后改改就好</p></li>
<li><p>如果需要用<code>USB to TTL</code>之类的操作连接PI，那么还要改<code>config.txt</code>，没改成功过，不细说</p></li>
</ol>
<h2 id="配置环节">配置环节</h2>
<ol>
<li>信道填自动就搜不到<code>WiFi</code>了不知道咋回事儿
<ul>
<li>802.11ac的52-64信道在国内要求DFS/TPC(<a href="https://zh.wikipedia.org/wiki/DFS">动态频率选择</a>和<a href="https://zh.wikipedia.org/w/index.php?title=TPC&amp;action=edit&amp;redlink=1">传输功率控制</a>)，可能是因为树莓派并没有相应的功能还往这几个信道上挤导致的。</li>
</ul></li>
<li><code>wan</code>的接口名一定要<strong>小写</strong>，一定要<strong>小写</strong>，一定要<strong>小写</strong>，或者手动修改为防火墙<code>wan</code>区域</li>
<li>刚配好时<code>opkg</code>镜像不能用<code>https</code>，只能走<code>http</code>，因为里面套件还不全</li>
</ol>
<h2 id="网络配置">网络配置</h2>
<h3 id="多拨">多拨</h3>
<ul>
<li>使用<code>mwan3</code>插件</li>
<li>前面接个AP模式的路由器</li>
<li>使用<code>ip link add link eth0 name vethX type macvlan</code>来添加虚拟网口</li>
<li>其中留一个虚拟网口接<code>br-lan</code>接口，其余每个一个<code>PPPoE</code>接口</li>
<li>设置<code>mwan3</code>的时候<strong>一定</strong>要填测试IP，让相应的策略能自动开启</li>
</ul>]]></summary>
</entry>

</feed>
