<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="zh-CN">
  <title>Zowely</title>
  <subtitle>Code &amp; Sakura</subtitle>
  <link href="https://www.lansganbs.cn/" rel="alternate" type="text/html" />
  <link href="https://www.lansganbs.cn/atom.xml" rel="self" type="application/atom+xml" />
  <id>https://www.lansganbs.cn/</id>
  <updated>2026-09-03T07:48:36.225Z</updated>
  <author><name>Zowely</name></author>
  <entry>
    <title>Sublime Text 4 算法竞赛向配置</title>
    <link href="https://www.lansganbs.cn/posts/%E7%AE%97%E6%B3%95%E7%AB%9E%E8%B5%9B/sublime-text-4-%E7%AE%97%E6%B3%95%E7%AB%9E%E8%B5%9B%E5%90%91%E9%85%8D%E7%BD%AE/" rel="alternate" type="text/html" />
    <id>https://www.lansganbs.cn/posts/%E7%AE%97%E6%B3%95%E7%AB%9E%E8%B5%9B/sublime-text-4-%E7%AE%97%E6%B3%95%E7%AB%9E%E8%B5%9B%E5%90%91%E9%85%8D%E7%BD%AE/</id>
    <published>2025-10-12T00:00:00.000Z</published>
    <updated>2025-10-12T21:23:48.000Z</updated>
    <summary>Sublime Text 4 算法竞赛向配置</summary>
    <content type="html"><![CDATA[<section><h1>Sublime Text 4 算法竞赛向配置<a href="#sublime-text-4-算法竞赛向配置"><span>#</span></a></h1><section><h2>1. 下载Sublime Text 4<a href="#1-下载sublime-text-4"><span>#</span></a></h2><p>去官网 <a href="https://www.sublimetext.com/download" target="_blank">Download</a> 下载，放在任意位置皆可。下载 <code>portable version</code> 和正常下载几乎没有区别，下载 <code>portable version</code> 后是个文件夹，不会改某些文件的打开方式；正常下载会改某些文件的打开方式，一般选择后者就可以。</p><p>下载后建议在桌面创建一个快捷方式，进入刚才下载的位置，找到类似 <code>sublime_text.exe</code> 的应用，按住 <code>Alt</code> 拖到桌面即可创建快捷方式，右键等方式创建快捷方式也可以。</p><div><div><div></div><div>Note</div></div><div><p>从官网下载下来的是试用版（无限期），会间隔一些时间跳出购买弹窗，但是完全不影响使用。可以自行去找破解方式，本文不提供，破解时要注意版本的局限性，版本不同可能导致破解方式不同。</p></div></div></section><section><h2>2. 下载插件<a href="#2-下载插件"><span>#</span></a></h2><p><code>Ctrl + Shift + P</code>打开命令面板，搜索 <code>Install Package Control</code>，然后点击。</p><p>稍作等待，会出现提示，类似下图提示代表下载完成：</p><p></p><figure><img src="https://www.lansganbs.cn/images/article/%E7%AE%97%E6%B3%95%E7%AB%9E%E8%B5%9B/Sublime%20Text%204%E7%AE%97%E6%B3%95%E7%AB%9E%E8%B5%9B%E5%90%91%E9%85%8D%E7%BD%AE/PCI%E4%B8%8B%E8%BD%BD%E5%AE%8C%E6%88%90.png" alt="alt text" /><figcaption>alt text</figcaption></figure><p></p><p>之后依旧 <code>Ctrl + Shift + P</code> 打开命令面板，输入 <code>pci</code> ，点击第一个 <code>Package Control: Install Package</code> 进入插件列表，开始下载软件。</p><p>下面关于插件的安装操作，如无特殊提示，都使用上面这段方法安装。</p><section><h3>汉化插件<a href="#汉化插件"><span>#</span></a></h3><p>搜索 <code>ChineseLocalizations</code>，点击安装即可。</p></section><section><h3>一键爬取样例<a href="#一键爬取样例"><span>#</span></a></h3><ul>
<li>
<p>搜索 <code>CppFastOlympicCoding</code>，点击安装即可。只要你的当前工作目录下有相关C++编译器，设置了C++的环境变量即可自动编译。</p>
</li>
<li>
<p>去<strong>浏览器</strong>下载，自己用什么浏览器，就去哪个浏览器的插件市场下载，名为 <code>Competitive Companion</code>。安装后在浏览器右上角插件部分会显示，长下面这个样子：</p>
</li>
</ul><p></p><figure><img src="https://www.lansganbs.cn/images/article/%E7%AE%97%E6%B3%95%E7%AB%9E%E8%B5%9B/Sublime%20Text%204%E7%AE%97%E6%B3%95%E7%AB%9E%E8%B5%9B%E5%90%91%E9%85%8D%E7%BD%AE/Competitive%20Companion.png" alt="alt text" /><figcaption>alt text</figcaption></figure><p></p><p>右键改插件，去该插件设置里把 <code>Custom ports</code> 设置为 <code>12345</code>，如下图所示：</p><p></p><figure><img src="https://www.lansganbs.cn/images/article/%E7%AE%97%E6%B3%95%E7%AB%9E%E8%B5%9B/Sublime%20Text%204%E7%AE%97%E6%B3%95%E7%AB%9E%E8%B5%9B%E5%90%91%E9%85%8D%E7%BD%AE/Competitive%20Companion%E8%AE%BE%E7%BD%AE.png" alt="alt text" /><figcaption>alt text</figcaption></figure><p></p><p>另外 <code>Request timeout</code> 这一栏可以适当减少，但不要太短，这里是抓取样例的延迟。如果发现需要很多次才可以抓取到样例，就增大到 1500-2000，我目前设置改为了 1500，几乎可以稳定抓取到。</p><ul>
<li>下载 <a href="https://github.com/DrSwad/FastOlympicCodingHook/archive/refs/heads/master.zip" target="_blank">FastOlympicCodingHook</a>，你会得到一个大概名为 <code>FastOlympicCodingHook-master.zip</code> 的压缩包，并进行解压。内容如下所示。</li>
</ul><p></p><figure><img src="https://www.lansganbs.cn/images/article/%E7%AE%97%E6%B3%95%E7%AB%9E%E8%B5%9B/Sublime%20Text%204%E7%AE%97%E6%B3%95%E7%AB%9E%E8%B5%9B%E5%90%91%E9%85%8D%E7%BD%AE/FastOlympicCodingHook.png" alt="alt text" /><figcaption>alt text</figcaption></figure><p></p><p>如果你的 <code>Sublime Text 4</code> 已经汉化，那么在上面一栏中点击 <code>首选项-&gt;浏览插件目录</code>，进入插件目录，之后把<code>FastOlympicCodingHook-master.zip</code> 解压后的文件夹粘贴复制到这里，并修改这个文件夹的名字，现在解压后的名字应该叫做 <code>FastOlympicCodingHook-master</code>，删除掉后面的 <code>-master</code>。</p><ul>
<li>打开CppFastOlympicCoding文件夹，这个文件夹就在刚才的插件目录中。找到此文件夹下的 <code>FastOlympicCoding (Windows).Sublime-settings</code> 打开，在文件末尾添加 <code>"tests_file_suffix": "__tests"</code>，例如下图所示：</li>
</ul><p></p><figure><img src="https://www.lansganbs.cn/images/article/%E7%AE%97%E6%B3%95%E7%AB%9E%E8%B5%9B/Sublime%20Text%204%E7%AE%97%E6%B3%95%E7%AB%9E%E8%B5%9B%E5%90%91%E9%85%8D%E7%BD%AE/CppFastOlympicCoding%E9%85%8D%E7%BD%AE.png" alt="alt text" /><figcaption>alt text</figcaption></figure><p></p><p>注意上面一行要写一个逗号，要是英文输入法的逗号。</p><p>下面是爬取样例的教程：</p><p>新建文件，保存为 <code>xxx.cpp</code> ，在 <code>Sublime Text 4</code> 中右键，点击 <code>Listen to Competitive Companion</code>，之后打开浏览器的题目界面，例如 <a href="https://www.luogu.com.cn/problem/P1001" target="_blank">A + B Problem</a>，点击刚才下载的小绿点，就是下面这个。</p><p></p><figure><img src="https://www.lansganbs.cn/images/article/%E7%AE%97%E6%B3%95%E7%AB%9E%E8%B5%9B/Sublime%20Text%204%E7%AE%97%E6%B3%95%E7%AB%9E%E8%B5%9B%E5%90%91%E9%85%8D%E7%BD%AE/Competitive%20Companion.png" alt="alt text" /><figcaption>alt text</figcaption></figure><p></p><p>写好代码，按 <code>Ctrl + Alt + B</code> 即可自测样例，如果样例正确，测试样例会自动折叠；否则会展开输入和输出。</p><p>可以设置 <code>Listen to Competitive Companion</code> 快捷键，在 <code>Sublime Text 4</code> 上面一栏中点击 <code>首选项-&gt;快捷键设置</code>，目前右侧应该是只有一对中括号的，在右边输入：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>[</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>{ "keys": ["ctrl+shift+x"], "command": "fast_olympic_coding_hook" },</span></div></div><div><div><div>3</div></div><div><span>]</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>之后就可以按 <code>ctrl+shift+x</code> 快捷键就行，无需右键点击 <code>Listen to Competitive Companion</code> 了。</p></section><section><h3>在Sublime Text 4中编译<a href="#在sublime-text-4中编译"><span>#</span></a></h3><p>在 <code>Sublime Text 4</code> 上面一栏中点击 <code>工具-&gt;编译系统-&gt;新建编译系统</code>，把下面的代码整个替换掉刚刚打开的文件中：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>{</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>"cmd": ["g++", "${file}", "-std=c++20", "-o", "${file_path}\\\\${file_base_name}", "&amp;", "start", "cmd", "/c", "${file_path}\\\\${file_base_name} &amp; echo. &amp; pause"],</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>"file_regex": "^(..[^:]*):([0-9]+):?([0-9]+)?:? (.*)$", "working_dir": "${file_path}",</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>"selector": "source.c, source.c++", "shell": true,</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>"encoding":"cp936",</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>"variants":</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>[</span></div></div><div><div><div>9</div></div><div><span><span>        </span></span><span>{</span></div></div><div><div><div>10</div></div><div><span><span>            </span></span><span>"name" : "Build Only",</span></div></div><div><div><div>11</div></div><div><span><span>            </span></span><span>"cmd":["g++", "${file}", "-std=c++20", "-o", "${file_path}\\\\${file_base_name}"]</span></div></div><div><div><div>12</div></div><div><span><span>        </span></span><span>},</span></div></div><div><div><div>13</div></div><div><span><span>        </span></span><span>{</span></div></div><div><div><div>14</div></div><div><span><span>            </span></span><span>"name" : "Run Only",</span></div></div><div><div><div>15</div></div><div><span><span>            </span></span><span>"cmd" : ["start", "cmd", "/c", "${file_path}\\\\${file_base_name} &amp; echo. &amp; pause"]</span></div></div><div><div><div>16</div></div><div><span><span>        </span></span><span>},</span></div></div><div><div><div>17</div></div><div><span><span>        </span></span><span>{</span></div></div><div><div><div>18</div></div><div><span><span>            </span></span><span>"name" : "Pipe Build and Run",</span></div></div><div><div><div>19</div></div><div><span><span>            </span></span><span>"cmd":["g++", "${file}", "-std=c++20", "-o", "${file_path}\\\\${file_base_name}","&amp;","${file_path}\\\\${file_base_name}", "&lt;","${file_path}\\\\in","&gt;","${file_path}\\\\out"]</span></div></div><div><div><div>20</div></div><div><span><span>        </span></span><span>},</span></div></div><div><div><div>21</div></div><div><span><span>        </span></span><span>{</span></div></div><div><div><div>22</div></div><div><span><span>            </span></span><span>"name" : "Pipe Run Only",</span></div></div><div><div><div>23</div></div><div><span><span>            </span></span><span>"cmd" : ["${file_base_name}", "&lt;","in","&gt;","out"]</span></div></div><div><div><div>24</div></div><div><span><span>        </span></span><span>},</span></div></div><div><div><div>25</div></div><div><span><span>        </span></span><span>{</span></div></div><div><div><div>26</div></div><div><span><span>            </span></span><span>"name" : "Project Build &amp; Run",</span></div></div><div><div><div>27</div></div><div><span><span>            </span></span><span>"cmd" : ["g++", "${file_path}\\\\*.cpp", "-std=c++20", "-o", "${file_path}\\\\project", "&amp;", "start", "cmd", "/c", "${file_path}\\\\project &amp; echo. &amp; pause"]</span></div></div><div><div><div>28</div></div><div><span><span>        </span></span><span>},</span></div></div><div><div><div>29</div></div><div><span><span>        </span></span><span>{</span></div></div><div><div><div>30</div></div><div><span><span>            </span></span><span>"name" : "Project Build Only",</span></div></div><div><div><div>31</div></div><div><span><span>            </span></span><span>"cmd" : ["g++", "${file_path}\\\\*.cpp", "-std=c++20", "-o", "${file_path}\\\\project"]</span></div></div><div><div><div>32</div></div><div><span><span>        </span></span><span>},</span></div></div><div><div><div>33</div></div><div><span><span>        </span></span><span>{</span></div></div><div><div><div>34</div></div><div><span><span>            </span></span><span>"name" : "Project Run Only",</span></div></div><div><div><div>35</div></div><div><span><span>            </span></span><span>"cmd" : ["start", "cmd", "/c", "${file_path}\\\\project &amp; echo. &amp; pause"]</span></div></div><div><div><div>36</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>37</div></div><div><span><span>    </span></span><span>]</span></div></div><div><div><div>38</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>你可以自定义 C++ 版本，将代码中所有 <code>-std=c++20</code> 替换成别的即可。</p><p>然后保存命名为 <code>CPP.Sublime-build</code> 即可。</p><p>之后写一段代码，可以按 <code>Ctrl + B</code> 来在终端编译运行，可能会跳出几个选项，选择类似 <code>CPP.Sublime-build</code>（应该会有一个一模一样的）的即可。编译不了说明要么没按照上面的配置一步一步来，要么就是电脑里压根没有 C++ 编译器。</p></section><section><h3>Default File Type<a href="#default-file-type"><span>#</span></a></h3><p>搜索 <code>Default File Type</code>，点击安装即可。</p><p>这个插件的作用是当你新建文件时，可以指定新建的文件类型是什么类型，这里默认修改内容使得新建文件都是C++类型。</p><p>在上文保存编译系统的目录中，新建一个文件，文件名为default_file_type.sublime-settings</p><p>然后输入以下代码保存：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>{</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>"use_current_file_syntax": false,</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>"default_new_file_syntax": "Packages/C++/C++.sublime-syntax"</span></div></div><div><div><div>4</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /><p>下面的插件均为可选插件</p></section><section><h3>Transparency<a href="#transparency"><span>#</span></a></h3><p>透明化窗口，这个因人而异，不喜欢可以不装。<code>ctrl + shift + 1</code> 到 <code>ctrl + shift + 6</code> 透明化程度从低到高。无需额外设置。</p></section><section><h3>SublimeAStyleFormatter<a href="#sublimeastyleformatter"><span>#</span></a></h3><p>使用 <code>ctrl + alt + F</code> 一键格式化代码，默认为 <code>Java</code> 风格。如无风格更改，无需额外设置。</p></section><section><h3>BracketHighlighter<a href="#brackethighlighter"><span>#</span></a></h3><p>对括号进行高亮设置，增加代码的可读性。无需额外设置。</p></section><section><h3>ColorSublime<a href="#colorsublime"><span>#</span></a></h3><p><code>ctrl+shift+P</code> 输入 <code>ColorSublime:Install Theme</code>，点进去可以自由选择主题。</p></section></section><section><h2>3. 更改设置<a href="#3-更改设置"><span>#</span></a></h2><section><h3>修改字体<a href="#修改字体"><span>#</span></a></h3><p>建议为 <a href="https://github.com/tonsky/FiraCode/releases/download/6.2/Fira_Code_v6.2.zip" target="_blank">Fira Code</a>，下载解压后全选，右键点击安装所有 ttf 文件，然后在 <code>首选项-&gt; 设置</code> 中加一行 <code>"font_face": "Fira Code",</code>。如果要调整字体大小，那么加一行 <code>"font_size": value,</code>，其中 <code>value</code> 是字体大小，例如这是我目前的：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>{</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>"ignored_packages":</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>[</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>"Vintage",</span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>],</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>"index_files": true,</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>"color_scheme": "Packages/Colorsublime - Themes/Dracula.tmTheme",</span></div></div><div><div><div>8</div></div><div><span><span>  </span></span><span>"font_face": "Fira Code",</span></div></div><div><div><div>9</div></div><div><span><span>  </span></span><span>"font_size": 11,</span></div></div><div><div><div>10</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>设置 Tab 为 x 个空格，将 Tab 转为空格<a href="#设置-tab-为-x-个空格将-tab-转为空格"><span>#</span></a></h3><p><code>首选项-&gt; 设置</code>，在左侧 <code>ctrl+F</code> 搜索 <code>tab_size</code>，默认为 4，可以改为自己常用的，例如2。</p><p>不同操作系统或者文件下，一个 <code>Tab</code> 代表的空格数不一样，所以左侧搜索 <code>translate_tabs_to_spaces</code>，改为 <code>"translate_tabs_to_spaces": true,</code>，这句话的意思是将 Tab 都转换为空格，这样就可以避免上述问题。</p></section></section></section>]]></content>
    <author><name>Zowely</name></author>
    <category term="算法竞赛" />
    <category term="算法竞赛" />
    <category term="环境配置" />
    <category term="C++" />
  </entry>
  <entry>
    <title>分块莫队</title>
    <link href="https://www.lansganbs.cn/posts/%E7%AE%97%E6%B3%95%E7%AB%9E%E8%B5%9B/%E5%88%86%E5%9D%97%E8%8E%AB%E9%98%9F/" rel="alternate" type="text/html" />
    <id>https://www.lansganbs.cn/posts/%E7%AE%97%E6%B3%95%E7%AB%9E%E8%B5%9B/%E5%88%86%E5%9D%97%E8%8E%AB%E9%98%9F/</id>
    <published>2024-12-30T00:00:00.000Z</published>
    <updated>2025-10-02T21:27:53.000Z</updated>
    <summary>2025春哈尔滨理工大学新生算法培训</summary>
    <content type="html"><![CDATA[<section><h1>分块算法<a href="#分块算法"><span>#</span></a></h1><section><h2>什么是分块算法<a href="#什么是分块算法"><span>#</span></a></h2><p>分块是一种思想，对整块整体处理，对零散快单独处理。</p><p>分块实际上就是暴力，不过可以被称为“优雅的暴力”。分块能解决很多类型的问题，很多算法也利用了分块的思想并进行优化。莫队的很多题目中也会一并使用分块来解决。</p><p>另外分块也可以分为图论分块，数论分块，字符串分块，数据结构分块等等，分块所设计的领域非常广，往往也可以使用分块来解决一些看似需要高深算法来解决的问题。</p><p>例如：<a href="https://www.luogu.com.cn/problem/P3372" target="_blank">P3372 【模板】线段树 1</a></p><p>显然利用现在所学知识无法在 <span><span>O(nlogn)O(nlogn)</span><span><span><span></span><span>O</span><span>(</span><span>n</span><span>l</span><span>o</span><span>g</span><span>n</span><span>)</span></span></span></span> 的时间复杂度下来解决此问题，那么这时我们可以考虑使用这种算法。</p></section><section><h2>何时分块<a href="#何时分块"><span>#</span></a></h2><p>再给出一个块，块长为<span><span>NN</span><span><span><span></span><span>N</span></span></span></span>，要求求解区间 <span><span>[l,r][l,r]</span><span><span><span></span><span>[</span><span>l</span><span>,</span><span></span><span>r</span><span>]</span></span></span></span> 的部分相关问题，如求解区间 <span><span>[l,r][l,r]</span><span><span><span></span><span>[</span><span>l</span><span>,</span><span></span><span>r</span><span>]</span></span></span></span> 的最大值，最小值和区间和等问题。这时我们可以来维护区间 <span><span>[l,r][l,r]</span><span><span><span></span><span>[</span><span>l</span><span>,</span><span></span><span>r</span><span>]</span></span></span></span>内的块和附近的少量元素。</p></section><section><h2>如何分块<a href="#如何分块"><span>#</span></a></h2><section><h3>块长<a href="#块长"><span>#</span></a></h3><p>我们可以通过“猜”的方式来大致得出相对优秀的块长</p><ul>
<li>长度为<span><span>11</span><span><span><span></span><span>1</span></span></span></span>？长度为<span><span>nn</span><span><span><span></span><span>n</span></span></span></span>？如分</li>
<li>长度为<span><span>n2\frac{n}{2}</span><span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span><span>2</span></span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>n</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span></span></span></span> 依然很难维护</li>
<li>长度为<span><span>lognlogn</span><span><span><span></span><span>l</span><span>o</span><span>g</span><span>n</span></span></span></span> 需要维护<span><span>nlogn\frac{n}{logn}</span><span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span><span>l</span><span>o</span><span>g</span><span>n</span></span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>n</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span></span></span></span>的块 假设数据范围为<span><span>2e52e5</span><span><span><span></span><span>2</span><span>e</span><span>5</span></span></span></span> 是大约<span><span>3800038000</span><span><span><span></span><span>38000</span></span></span></span>个块</li>
<li>长度为<span><span>n\sqrt{n}</span><span><span><span></span><span><span><span><span><span><span></span><span><span>n</span></span></span><span><span></span><span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span> 需要维护<span><span>n\sqrt{n}</span><span><span><span></span><span><span><span><span><span><span></span><span><span>n</span></span></span><span><span></span><span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span>的块 只需要维护<span><span>450450</span><span><span><span></span><span>450</span></span></span></span>个块</li>
</ul><p>实际上 块的大小可以通过计算得出</p><p>若以顺序查找来确定块，则分块查找成功时的平均查找长度为</p><span><span><span>ASLls=Lb+Lw=b+12+s+12=ns+s2+1=n+s22s+1ASL_{ls}=L_{b}+L_{w}=\frac{b+1}{2}+\frac{s+1}{2}=\frac{\frac{n}{s}+s}{2}+1=\frac{n+s^{2}}{2s}+1</span><span><span><span></span><span>A</span><span>S</span><span><span>L</span><span><span><span><span><span><span></span><span><span><span>l</span><span>s</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>=</span><span></span></span><span><span></span><span><span>L</span><span><span><span><span><span><span></span><span><span><span>b</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>+</span><span></span></span><span><span></span><span><span>L</span><span><span><span><span><span><span></span><span><span><span>w</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>=</span><span></span></span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span>2</span></span></span><span><span></span><span></span></span><span><span></span><span><span>b</span><span></span><span>+</span><span></span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span></span><span>+</span><span></span></span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span>2</span></span></span><span><span></span><span></span></span><span><span></span><span><span>s</span><span></span><span>+</span><span></span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span></span><span>=</span><span></span></span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span>2</span></span></span><span><span></span><span></span></span><span><span></span><span><span><span></span><span><span><span><span><span><span></span><span><span><span>s</span></span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>n</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span></span><span>+</span><span></span><span>s</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span></span><span>+</span><span></span></span><span><span></span><span>1</span><span></span><span>=</span><span></span></span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span>2</span><span>s</span></span></span><span><span></span><span></span></span><span><span></span><span><span>n</span><span></span><span>+</span><span></span><span><span>s</span><span><span><span><span><span><span></span><span><span><span>2</span></span></span></span></span></span></span></span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span></span><span>+</span><span></span></span><span><span></span><span>1</span></span></span></span></span><p><span><span>nn</span><span><span><span></span><span>n</span></span></span></span>为查找表的长度，<span><span>ss</span><span><span><span></span><span>s</span></span></span></span>为块的长度</p><p><span><span>b=⌈ns⌉b=⌈\frac{n}{s}⌉</span><span><span><span></span><span>b</span><span></span><span>=</span><span></span></span><span><span></span><span>⌈</span><span><span></span><span><span><span><span><span><span></span><span><span><span>s</span></span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>n</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span>⌉</span></span></span></span>为块的个数。当<span><span>s=ns=\sqrt{n}</span><span><span><span></span><span>s</span><span></span><span>=</span><span></span></span><span><span></span><span><span><span><span><span><span></span><span><span>n</span></span></span><span><span></span><span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span>时，<span><span>ASLlsASL_{ls}</span><span><span><span></span><span>A</span><span>S</span><span><span>L</span><span><span><span><span><span><span></span><span><span><span>l</span><span>s</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span>取最小值<span><span>n+1\sqrt{n}+1</span><span><span><span></span><span><span><span><span><span><span></span><span><span>n</span></span></span><span><span></span><span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span><span>+</span><span></span></span><span><span></span><span>1</span></span></span></span></p></section><section><h3>时间复杂度<a href="#时间复杂度"><span>#</span></a></h3><ul>
<li><span><span>nn</span><span><span><span></span><span>n</span></span></span></span>次询问，长度为<span><span>mm</span><span><span><span></span><span>m</span></span></span></span>，时间复杂度为 <span><span>O(nm)O(n\sqrt{m})</span><span><span><span></span><span>O</span><span>(</span><span>n</span><span><span><span><span><span><span></span><span><span>m</span></span></span><span><span></span><span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span>)</span></span></span></span>，准确来说为<span><span>O(nn+nm)O(n\sqrt{n}+n\sqrt{m})</span><span><span><span></span><span>O</span><span>(</span><span>n</span><span><span><span><span><span><span></span><span><span>n</span></span></span><span><span></span><span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span><span>+</span><span></span></span><span><span></span><span>n</span><span><span><span><span><span><span></span><span><span>m</span></span></span><span><span></span><span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span>)</span></span></span></span></li>
</ul></section><section><h3>实现思路<a href="#实现思路"><span>#</span></a></h3><p>我们具体需要维护的有如下三点：</p><ul>
<li>在前面的一部分零散的元素</li>
<li>中间的若干个整块</li>
<li>后面的一部分零散的元素</li>
</ul></section><section><h3>具体实现<a href="#具体实现"><span>#</span></a></h3><p><a href="https://www.luogu.com.cn/problem/P3372" target="_blank">P3372 【模板】线段树 1</a></p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>int</span><span> a[N], st[N], ed[N], sum[N], add[N], len, id[N];</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>void</span><span> </span><span>change</span><span>(</span><span>int</span><span> </span><span>l</span><span>, </span><span>int</span><span> </span><span>r</span><span>, </span><span>int</span><span> </span><span>k</span><span>) {</span></div></div><div><div><div>4</div></div><div><span>  </span><span>if</span><span> (id[l] </span><span>==</span><span> id[r]) {</span></div></div><div><div><div>5</div></div><div><span>    </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> l; i </span><span>&lt;=</span><span> r; i</span><span>++</span><span>) {</span></div></div><div><div><div>6</div></div><div><span><span>      </span></span><span>a[i] </span><span>+=</span><span> k;</span></div></div><div><div><div>7</div></div><div><span><span>      </span></span><span>sum[id[i]] </span><span>+=</span><span> k;</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>9</div></div><div><span><span>  </span></span><span>} </span><span>else</span><span> {</span></div></div><div><div><div>10</div></div><div><span>    </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> l; i </span><span>&lt;=</span><span> ed[l]; i</span><span>++</span><span>) {</span></div></div><div><div><div>11</div></div><div><span><span>      </span></span><span>a[i] </span><span>+=</span><span> k;</span></div></div><div><div><div>12</div></div><div><span><span>      </span></span><span>sum[id[i]] </span><span>+=</span><span> k;</span></div></div><div><div><div>13</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>14</div></div><div><span>    </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> st[r]; i </span><span>&lt;=</span><span> r; i</span><span>++</span><span>) {</span></div></div><div><div><div>15</div></div><div><span><span>      </span></span><span>a[i] </span><span>+=</span><span> k;</span></div></div><div><div><div>16</div></div><div><span><span>      </span></span><span>sum[id[i]] </span><span>+=</span><span> k;</span></div></div><div><div><div>17</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>18</div></div><div><span>    </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> id[l] </span><span>+</span><span> </span><span>1</span><span>; i </span><span>&lt;</span><span> id[r]; i</span><span>++</span><span>) {</span></div></div><div><div><div>19</div></div><div><span><span>      </span></span><span>add[i] </span><span>+=</span><span> k;</span></div></div><div><div><div>20</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>21</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>22</div></div><div><span>}</span></div></div><div><div><div>23</div></div><div>
</div></div><div><div><div>24</div></div><div><span>int</span><span> </span><span>query</span><span>(</span><span>int</span><span> </span><span>l</span><span>, </span><span>int</span><span> </span><span>r</span><span>) {</span></div></div><div><div><div>25</div></div><div><span>  </span><span>int</span><span> ans </span><span>=</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>26</div></div><div><span>  </span><span>if</span><span> (id[l] </span><span>==</span><span> id[r]) {</span></div></div><div><div><div>27</div></div><div><span>    </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> l; i </span><span>&lt;=</span><span> r; i</span><span>++</span><span>) {</span></div></div><div><div><div>28</div></div><div><span><span>      </span></span><span>ans </span><span>+=</span><span> a[i] </span><span>+</span><span> add[id[i]];</span></div></div><div><div><div>29</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>30</div></div><div><span><span>  </span></span><span>} </span><span>else</span><span> {</span></div></div><div><div><div>31</div></div><div><span>    </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> l; i </span><span>&lt;=</span><span> ed[l]; i</span><span>++</span><span>) {</span></div></div><div><div><div>32</div></div><div><span><span>      </span></span><span>ans </span><span>+=</span><span> a[i] </span><span>+</span><span> add[id[i]];</span></div></div><div><div><div>33</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>34</div></div><div><span>    </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> st[r]; i </span><span>&lt;=</span><span> r; i</span><span>++</span><span>) {</span></div></div><div><div><div>35</div></div><div><span><span>      </span></span><span>ans </span><span>+=</span><span> a[i] </span><span>+</span><span> add[id[i]];</span></div></div><div><div><div>36</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>37</div></div><div><span>    </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> id[l] </span><span>+</span><span> </span><span>1</span><span>; i </span><span>&lt;</span><span> id[r]; i</span><span>++</span><span>) {</span></div></div><div><div><div>38</div></div><div><span><span>      </span></span><span>ans </span><span>+=</span><span> sum[i] </span><span>+</span><span> add[i] </span><span>*</span><span> (ed[i] </span><span>-</span><span> st[i] </span><span>+</span><span> </span><span>1</span><span>);</span></div></div><div><div><div>39</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>40</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>41</div></div><div><span>  </span><span>return</span><span> ans;</span></div></div><div><div><div>42</div></div><div><span>}</span></div></div><div><div><div>43</div></div><div>
</div></div><div><div><div>44</div></div><div><span>void</span><span> </span><span>solve</span><span>() {</span></div></div><div><div><div>45</div></div><div><span>  </span><span>int</span><span> n, m;</span></div></div><div><div><div>46</div></div><div><span><span>  </span></span><span>cin </span><span>&gt;&gt;</span><span> n </span><span>&gt;&gt;</span><span> m;</span></div></div><div><div><div>47</div></div><div><span><span>  </span></span><span>len </span><span>=</span><span> </span><span>sqrt</span><span>(n);</span></div></div><div><div><div>48</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>1</span><span>; i </span><span>&lt;=</span><span> n; i</span><span>++</span><span>) {</span></div></div><div><div><div>49</div></div><div><span><span>    </span></span><span>cin </span><span>&gt;&gt;</span><span> a[i];</span></div></div><div><div><div>50</div></div><div><span><span>    </span></span><span>id[i] </span><span>=</span><span> (i </span><span>-</span><span> </span><span>1</span><span>) </span><span>/</span><span> len </span><span>+</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>51</div></div><div><span><span>    </span></span><span>st[i] </span><span>=</span><span> (id[i] </span><span>-</span><span> </span><span>1</span><span>) </span><span>*</span><span> len </span><span>+</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>52</div></div><div><span><span>    </span></span><span>ed[i] </span><span>=</span><span> </span><span>min</span><span>(id[i] </span><span>*</span><span> len, n);</span></div></div><div><div><div>53</div></div><div><span><span>    </span></span><span>sum[id[i]] </span><span>+=</span><span> a[i];</span></div></div><div><div><div>54</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>55</div></div><div><span>  </span><span>while</span><span> (m</span><span>--</span><span>) {</span></div></div><div><div><div>56</div></div><div><span>    </span><span>int</span><span> op, x, y, k;</span></div></div><div><div><div>57</div></div><div><span><span>    </span></span><span>cin </span><span>&gt;&gt;</span><span> op </span><span>&gt;&gt;</span><span> x </span><span>&gt;&gt;</span><span> y;</span></div></div><div><div><div>58</div></div><div><span>    </span><span>if</span><span> (op </span><span>==</span><span> </span><span>1</span><span>) {</span></div></div><div><div><div>59</div></div><div><span><span>      </span></span><span>cin </span><span>&gt;&gt;</span><span> k;</span></div></div><div><div><div>60</div></div><div><span>      </span><span>change</span><span>(x, y, k);</span></div></div><div><div><div>61</div></div><div><span><span>    </span></span><span>} </span><span>else</span><span> {</span></div></div><div><div><div>62</div></div><div><span><span>      </span></span><span>cout </span><span>&lt;&lt;</span><span> </span><span>query</span><span>(x, y) </span><span>&lt;&lt;</span><span> endl;</span></div></div><div><div><div>63</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>64</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>65</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p><a href="https://www.luogu.com.cn/problem/P3865" target="_blank">P3865 【模板】ST 表 &amp;&amp; RMQ 问题</a></p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>int</span><span> st[N], ed[N], mx[N], id[N], a[N], len;</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>int</span><span> </span><span>query</span><span>() {</span></div></div><div><div><div>4</div></div><div><span>  </span><span>int</span><span> ans </span><span>=</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>5</div></div><div><span>  </span><span>int</span><span> l, r;</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>cin </span><span>&gt;&gt;</span><span> l </span><span>&gt;&gt;</span><span> r;</span></div></div><div><div><div>7</div></div><div><span>  </span><span>if</span><span> (id[l] </span><span>==</span><span> id[r]) {</span></div></div><div><div><div>8</div></div><div><span>    </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> l; i </span><span>&lt;=</span><span> r; i</span><span>++</span><span>) {</span></div></div><div><div><div>9</div></div><div><span><span>      </span></span><span>ans </span><span>=</span><span> </span><span>max</span><span>(ans, a[i]);</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>11</div></div><div><span><span>  </span></span><span>} </span><span>else</span><span> {</span></div></div><div><div><div>12</div></div><div><span>    </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> l; i </span><span>&lt;=</span><span> ed[id[l]]; i</span><span>++</span><span>) {</span></div></div><div><div><div>13</div></div><div><span><span>      </span></span><span>ans </span><span>=</span><span> </span><span>max</span><span>(ans, a[i]);</span></div></div><div><div><div>14</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>15</div></div><div><span>    </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> st[id[r]]; i </span><span>&lt;=</span><span> r; i</span><span>++</span><span>) {</span></div></div><div><div><div>16</div></div><div><span><span>      </span></span><span>ans </span><span>=</span><span> </span><span>max</span><span>(ans, a[i]);</span></div></div><div><div><div>17</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>18</div></div><div><span>    </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> id[l] </span><span>+</span><span> </span><span>1</span><span>; i </span><span>&lt;=</span><span> id[r] </span><span>-</span><span> </span><span>1</span><span>; i</span><span>++</span><span>) {</span></div></div><div><div><div>19</div></div><div><span><span>      </span></span><span>ans </span><span>=</span><span> </span><span>max</span><span>(ans, mx[i]);</span></div></div><div><div><div>20</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>21</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>22</div></div><div><span>  </span><span>return</span><span> ans;</span></div></div><div><div><div>23</div></div><div><span>}</span></div></div><div><div><div>24</div></div><div>
</div></div><div><div><div>25</div></div><div><span>void</span><span> </span><span>solve</span><span>() {</span></div></div><div><div><div>26</div></div><div><span>  </span><span>int</span><span> n, m;</span></div></div><div><div><div>27</div></div><div><span><span>  </span></span><span>cin </span><span>&gt;&gt;</span><span> n </span><span>&gt;&gt;</span><span> m;</span></div></div><div><div><div>28</div></div><div><span><span>  </span></span><span>len </span><span>=</span><span> </span><span>sqrt</span><span>(n);</span></div></div><div><div><div>29</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>1</span><span>; i </span><span>&lt;=</span><span> n; i</span><span>++</span><span>) {</span></div></div><div><div><div>30</div></div><div><span><span>    </span></span><span>cin </span><span>&gt;&gt;</span><span> a[i];</span></div></div><div><div><div>31</div></div><div><span><span>    </span></span><span>id[i] </span><span>=</span><span> (i </span><span>-</span><span> </span><span>1</span><span>) </span><span>/</span><span> len </span><span>+</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>32</div></div><div><span><span>    </span></span><span>st[id[i]] </span><span>=</span><span> (id[i] </span><span>-</span><span> </span><span>1</span><span>) </span><span>*</span><span> len </span><span>+</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>33</div></div><div><span><span>    </span></span><span>ed[id[i]] </span><span>=</span><span> id[i] </span><span>*</span><span> len;</span></div></div><div><div><div>34</div></div><div><span><span>    </span></span><span>mx[id[i]] </span><span>=</span><span> (i </span><span>==</span><span> st[id[i]]) </span><span>?</span><span> a[i] </span><span>:</span><span> </span><span>max</span><span>(mx[id[i]], a[i]);</span></div></div><div><div><div>35</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>36</div></div><div><span>  </span><span>while</span><span> (m</span><span>--</span><span>) {</span></div></div><div><div><div>37</div></div><div><span><span>    </span></span><span>cout </span><span>&lt;&lt;</span><span> </span><span>query</span><span>() </span><span>&lt;&lt;</span><span> endl;</span></div></div><div><div><div>38</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>39</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><hr /></section></section></section>
<section><h1>莫队算法<a href="#莫队算法"><span>#</span></a></h1><section><h2>莫队？<a href="#莫队"><span>#</span></a></h2><p></p><figure><img src="https://www.lansganbs.cn/images/article/%E7%AE%97%E6%B3%95%E7%AB%9E%E8%B5%9B/%E5%88%86%E5%9D%97%E8%8E%AB%E9%98%9F/%E8%8E%AB%E9%98%9F%E6%98%AF%E8%B0%81.png" alt="alt text" /><figcaption>alt text</figcaption></figure><p></p><p></p><figure><img src="https://www.lansganbs.cn/images/article/%E7%AE%97%E6%B3%95%E7%AB%9E%E8%B5%9B/%E5%88%86%E5%9D%97%E8%8E%AB%E9%98%9F/%E8%8E%AB%E9%98%9F%E6%98%AF%E6%88%91.png" alt="alt text" /><figcaption>alt text</figcaption></figure><p></p></section><section><h2>何为莫队算法<a href="#何为莫队算法"><span>#</span></a></h2><p>为了解决区间问题，最开始的区间问题是前缀和，然后出现越来越难的问题，越来越困难的算法：分块，RMQ，树状数组，线段树等等，于是莫队算法出现，就是为了解决这类的区间问题。</p></section><section><h2>分块算法和莫队算法的异同<a href="#分块算法和莫队算法的异同"><span>#</span></a></h2><p>同：</p><ul>
<li>都是为了解决区间问题而存在</li>
</ul><p>异：</p><ul>
<li>莫队查询的更快 但是只能离线查询</li>
<li>分块查询的更慢 但是可以在线查询</li>
</ul><blockquote><p>离线查询：查询操作是在已知所有查询的情况下进行的。也就是说，在开始查询之前，所有的查询都已经确定并且可以提前处理。</p><p>在线查询：查询操作是在不知道所有查询内容的情况下进行的。每次查询时都立即处理，不会提前对查询进行任何优化。查询顺序通常是动态的。</p></blockquote></section><section><h2>莫队算法基本原理<a href="#莫队算法基本原理"><span>#</span></a></h2><p>利用双指针来进行区间的移动查询</p></section><section><h2>莫队算法的核心<a href="#莫队算法的核心"><span>#</span></a></h2><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>while</span><span> (l </span><span>&gt;</span><span> q[i].l) {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>add</span><span>(a[</span><span>--</span><span>l]);</span></div></div><div><div><div>3</div></div><div><span>}</span></div></div><div><div><div>4</div></div><div><span>while</span><span> (r </span><span>&lt;</span><span> q[i].r) {</span></div></div><div><div><div>5</div></div><div><span>  </span><span>add</span><span>(a[</span><span>++</span><span>r]);</span></div></div><div><div><div>6</div></div><div><span>}</span></div></div><div><div><div>7</div></div><div><span>while</span><span> (l </span><span>&lt;</span><span> q[i].l) {</span></div></div><div><div><div>8</div></div><div><span>  </span><span>del</span><span>(a[l</span><span>++</span><span>]);</span></div></div><div><div><div>9</div></div><div><span>}</span></div></div><div><div><div>10</div></div><div><span>while</span><span> (r </span><span>&gt;</span><span> q[i].r) {</span></div></div><div><div><div>11</div></div><div><span>      </span><span>del</span><span>(a[r</span><span>--</span><span>]);</span></div></div><div><div><div>12</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>具体实现</p><p><a href="https://www.luogu.com.cn/problem/P1494" target="_blank">P1494 [国家集训队] 小 Z 的袜子</a></p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>int</span><span> a[N], sum, ans1[N], len, ans2[N], cnt[N];</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>struct</span><span> </span><span>query</span><span> {</span></div></div><div><div><div>4</div></div><div><span>  </span><span>int</span><span> l, r, id;</span></div></div><div><div><div>5</div></div><div><span>} q[N];</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>void</span><span> </span><span>add</span><span>(</span><span>int</span><span> </span><span>x</span><span>) {</span></div></div><div><div><div>8</div></div><div><span><span>  </span></span><span>sum </span><span>+=</span><span> cnt[x];</span></div></div><div><div><div>9</div></div><div><span><span>  </span></span><span>cnt[x]</span><span>++</span><span>;</span></div></div><div><div><div>10</div></div><div><span>}</span></div></div><div><div><div>11</div></div><div>
</div></div><div><div><div>12</div></div><div><span>void</span><span> </span><span>del</span><span>(</span><span>int</span><span> </span><span>x</span><span>) {</span></div></div><div><div><div>13</div></div><div><span><span>  </span></span><span>cnt[x]</span><span>--</span><span>;</span></div></div><div><div><div>14</div></div><div><span><span>  </span></span><span>sum </span><span>-=</span><span> cnt[x];</span></div></div><div><div><div>15</div></div><div><span>}</span></div></div><div><div><div>16</div></div><div>
</div></div><div><div><div>17</div></div><div><span>void</span><span> </span><span>solve</span><span>() {</span></div></div><div><div><div>18</div></div><div><span>  </span><span>int</span><span> n, m;</span></div></div><div><div><div>19</div></div><div><span><span>  </span></span><span>cin </span><span>&gt;&gt;</span><span> n </span><span>&gt;&gt;</span><span> m;</span></div></div><div><div><div>20</div></div><div><span><span>  </span></span><span>len </span><span>=</span><span> </span><span>sqrt</span><span>(n);</span></div></div><div><div><div>21</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>1</span><span>; i </span><span>&lt;=</span><span> n; i</span><span>++</span><span>) {</span></div></div><div><div><div>22</div></div><div><span><span>    </span></span><span>cin </span><span>&gt;&gt;</span><span> a[i];</span></div></div><div><div><div>23</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>24</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>1</span><span>; i </span><span>&lt;=</span><span> m; i</span><span>++</span><span>) {</span></div></div><div><div><div>25</div></div><div><span><span>    </span></span><span>cin </span><span>&gt;&gt;</span><span> q[i].l </span><span>&gt;&gt;</span><span> q[i].r;</span></div></div><div><div><div>26</div></div><div><span><span>    </span></span><span>q[i].id </span><span>=</span><span> i;</span></div></div><div><div><div>27</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>28</div></div><div><span>  </span><span>sort</span><span>(q </span><span>+</span><span> </span><span>1</span><span>, q </span><span>+</span><span> m </span><span>+</span><span> </span><span>1</span><span>, [](</span><span>const</span><span> </span><span>query</span><span> </span><span>&amp;</span><span>lhs</span><span>, </span><span>const</span><span> </span><span>query</span><span> </span><span>&amp;</span><span>rhs</span><span>) {</span></div></div><div><div><div>29</div></div><div><span>    </span><span>if</span><span> ((lhs.l </span><span>-</span><span> </span><span>1</span><span>) </span><span>/</span><span> len </span><span>!=</span><span> (rhs.l </span><span>-</span><span> </span><span>1</span><span>) </span><span>/</span><span> len) </span><span>return</span><span> lhs.l </span><span>&lt;</span><span> rhs.l;</span></div></div><div><div><div>30</div></div><div><span>    </span><span>return</span><span> lhs.r </span><span>&lt;</span><span> rhs.r;</span></div></div><div><div><div>31</div></div><div><span><span>  </span></span><span>});</span></div></div><div><div><div>32</div></div><div>
</div></div><div><div><div>33</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>1</span><span>, l </span><span>=</span><span> </span><span>1</span><span>, r </span><span>=</span><span> </span><span>0</span><span>; i </span><span>&lt;=</span><span> m; i</span><span>++</span><span>) {</span></div></div><div><div><div>34</div></div><div><span>    </span><span>if</span><span> (q[i].l </span><span>==</span><span> q[i].r) {</span></div></div><div><div><div>35</div></div><div><span><span>      </span></span><span>ans1[q[i].id] </span><span>=</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>36</div></div><div><span><span>      </span></span><span>ans2[q[i].id] </span><span>=</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>37</div></div><div><span>      </span><span>continue</span><span>;</span></div></div><div><div><div>38</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>39</div></div><div><span>    </span><span>while</span><span> (l </span><span>&gt;</span><span> q[i].l) {</span></div></div><div><div><div>40</div></div><div><span>      </span><span>add</span><span>(a[</span><span>--</span><span>l]);</span></div></div><div><div><div>41</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>42</div></div><div><span>    </span><span>while</span><span> (r </span><span>&lt;</span><span> q[i].r) {</span></div></div><div><div><div>43</div></div><div><span>      </span><span>add</span><span>(a[</span><span>++</span><span>r]);</span></div></div><div><div><div>44</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>45</div></div><div><span>    </span><span>while</span><span> (l </span><span>&lt;</span><span> q[i].l) {</span></div></div><div><div><div>46</div></div><div><span>      </span><span>del</span><span>(a[l</span><span>++</span><span>]);</span></div></div><div><div><div>47</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>48</div></div><div><span>    </span><span>while</span><span> (r </span><span>&gt;</span><span> q[i].r) {</span></div></div><div><div><div>49</div></div><div><span>      </span><span>del</span><span>(a[r</span><span>--</span><span>]);</span></div></div><div><div><div>50</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>51</div></div><div><span>    </span><span>if</span><span> (sum </span><span>==</span><span> </span><span>0</span><span>) {</span></div></div><div><div><div>52</div></div><div><span><span>      </span></span><span>ans1[q[i].id] </span><span>=</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>53</div></div><div><span><span>      </span></span><span>ans2[q[i].id] </span><span>=</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>54</div></div><div><span>      </span><span>continue</span><span>;</span></div></div><div><div><div>55</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>56</div></div><div><span><span>    </span></span><span>ans1[q[i].id] </span><span>=</span><span> sum;</span></div></div><div><div><div>57</div></div><div><span><span>    </span></span><span>ans2[q[i].id] </span><span>=</span><span> (r </span><span>-</span><span> l </span><span>+</span><span> </span><span>1</span><span>) </span><span>*</span><span> (r </span><span>-</span><span> l) </span><span>/</span><span> </span><span>2</span><span>;</span></div></div><div><div><div>58</div></div><div><span>    </span><span>int</span><span> t </span><span>=</span><span> </span><span>__gcd</span><span>(ans1[q[i].id], ans2[q[i].id]);</span></div></div><div><div><div>59</div></div><div><span><span>    </span></span><span>ans1[q[i].id] </span><span>/=</span><span> t;</span></div></div><div><div><div>60</div></div><div><span><span>    </span></span><span>ans2[q[i].id] </span><span>/=</span><span> t;</span></div></div><div><div><div>61</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>62</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>1</span><span>; i </span><span>&lt;=</span><span> m; i</span><span>++</span><span>) {</span></div></div><div><div><div>63</div></div><div><span><span>    </span></span><span>cout </span><span>&lt;&lt;</span><span> ans1[i] </span><span>&lt;&lt;</span><span> </span><span>'/'</span><span> </span><span>&lt;&lt;</span><span> ans2[i] </span><span>&lt;&lt;</span><span> endl;</span></div></div><div><div><div>64</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>65</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section><section><h2>普通莫队的优化<a href="#普通莫队的优化"><span>#</span></a></h2><section><h3>奇偶排序优化<a href="#奇偶排序优化"><span>#</span></a></h3><p>对于如下数据：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>设块的大小为2</span></div></div><div><div><div>2</div></div><div><span>1 1</span></div></div><div><div><div>3</div></div><div><span>2 100</span></div></div><div><div><div>4</div></div><div><span>3 1</span></div></div><div><div><div>5</div></div><div><span>4 100</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>经过排序后的等效数据：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>1 1</span></div></div><div><div><div>2</div></div><div><span>2 100</span></div></div><div><div><div>3</div></div><div><span>3 1</span></div></div><div><div><div>4</div></div><div><span>4 100</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>手动模拟一下可以发现， 指针的移动次数大概为<span><span>300300</span><span><span><span></span><span>300</span></span></span></span>次，我们处理完第一个<strong>块</strong>之后<span><span>(l=2,r=100)(l=2, r=100)</span><span><span><span></span><span>(</span><span>l</span><span></span><span>=</span><span></span></span><span><span></span><span>2</span><span>,</span><span></span><span>r</span><span></span><span>=</span><span></span></span><span><span></span><span>100</span><span>)</span></span></span></span>，此时只需要移动2次指针到<span><span>l=4,r=100l=4, r=100</span><span><span><span></span><span>l</span><span></span><span>=</span><span></span></span><span><span></span><span>4</span><span>,</span><span></span><span>r</span><span></span><span>=</span><span></span></span><span><span></span><span>100</span></span></span></span>就可以得到第四个询问的答案，再移动<span><span>100100</span><span><span><span></span><span>100</span></span></span></span>次到<span><span>l=3,r=1l=3, r=1</span><span><span><span></span><span>l</span><span></span><span>=</span><span></span></span><span><span></span><span>3</span><span>,</span><span></span><span>r</span><span></span><span>=</span><span></span></span><span><span></span><span>1</span></span></span></span>就可以得到第三次询问的答案，但是我们却将指针移动<span><span>100100</span><span><span><span></span><span>100</span></span></span></span>次到<span><span>l=3,r=1l=3, r=1</span><span><span><span></span><span>l</span><span></span><span>=</span><span></span></span><span><span></span><span>3</span><span>,</span><span></span><span>r</span><span></span><span>=</span><span></span></span><span><span></span><span>1</span></span></span></span>来获取第三个询问的答案，再移动<span><span>100100</span><span><span><span></span><span>100</span></span></span></span>次到<span><span>l=4,r=100l=4, r=100</span><span><span><span></span><span>l</span><span></span><span>=</span><span></span></span><span><span></span><span>4</span><span>,</span><span></span><span>r</span><span></span><span>=</span><span></span></span><span><span></span><span>100</span></span></span></span>获取第四个询问的答案，这样多了<span><span>9898</span><span><span><span></span><span>98</span></span></span></span>次的指针移动。我们怎么优化这个地方呢？</p><p>这里我们就要用到奇偶化排序。什么是奇偶化排序？奇偶化排序即对于属于奇数块的询问，<span><span>rr</span><span><span><span></span><span>r</span></span></span></span>按从小到大排序，对于属于偶数块的排序，<span><span>rr</span><span><span><span></span><span>r</span></span></span></span>从大到小排序，这样我们的<span><span>rr</span><span><span><span></span><span>r</span></span></span></span>指针在处理完这个奇数块的问题后，将在返回的途中处理偶数块的问题，再向<span><span>nn</span><span><span><span></span><span>n</span></span></span></span>移动处理下一个奇数块的问题，优化了<span><span>rr</span><span><span><span></span><span>r</span></span></span></span>指针的移动次数，理论上能快一倍。</p><p>优化后并排序后的等效数据：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>1 1</span></div></div><div><div><div>2</div></div><div><span>2 100</span></div></div><div><div><div>3</div></div><div><span>4 100</span></div></div><div><div><div>4</div></div><div><span>3 1</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>  </span><span>sort</span><span>(q </span><span>+</span><span> </span><span>1</span><span>, q </span><span>+</span><span> m </span><span>+</span><span> </span><span>1</span><span>, [](</span><span>const</span><span> </span><span>query</span><span> </span><span>&amp;</span><span>lhs</span><span>, </span><span>const</span><span> </span><span>query</span><span> </span><span>&amp;</span><span>rhs</span><span>) {</span></div></div><div><div><div>2</div></div><div><span>    </span><span>if</span><span> ((lhs.l </span><span>-</span><span> </span><span>1</span><span>) </span><span>/</span><span> len </span><span>!=</span><span> (rhs.l </span><span>-</span><span> </span><span>1</span><span>) </span><span>/</span><span> len) {</span></div></div><div><div><div>3</div></div><div><span>      </span><span>return</span><span> lhs.l </span><span>&lt;</span><span> rhs.l;</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>5</div></div><div><span>    </span><span>if</span><span> (((lhs.l </span><span>-</span><span> </span><span>1</span><span>) </span><span>/</span><span> len </span><span>+</span><span> </span><span>1</span><span>) </span><span>&amp;</span><span> </span><span>1</span><span>) {</span></div></div><div><div><div>6</div></div><div><span>      </span><span>return</span><span> lhs.r </span><span>&lt;</span><span> rhs.r;</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>8</div></div><div><span>    </span><span>return</span><span> lhs.r </span><span>&gt;</span><span> rhs.r;</span></div></div><div><div><div>9</div></div><div><span><span>  </span></span><span>});</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>bool</span><span> </span><span>cmp</span><span>(</span><span>node</span><span> </span><span>a</span><span>,</span><span>node</span><span> </span><span>b</span><span>) {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>return</span><span> pos[a.l] </span><span>^</span><span> pos[b.l] </span><span>?</span><span> pos[a.l] </span><span>&lt;</span><span> pos[b.l] </span><span>:</span><span> pos[a.l] </span><span>&amp;</span><span> </span><span>1</span><span> </span><span>?</span><span> a.r </span><span>&lt;</span><span> b.r </span><span>:</span><span> a.r </span><span>&gt;</span><span> b.r;</span></div></div><div><div><div>3</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>块的大小优化<a href="#块的大小优化"><span>#</span></a></h3><p></p><figure><img src="https://www.lansganbs.cn/images/article/%E7%AE%97%E6%B3%95%E7%AB%9E%E8%B5%9B/%E5%88%86%E5%9D%97%E8%8E%AB%E9%98%9F/%E5%9D%97%E9%95%BF.png" alt="alt text" /><figcaption>alt text</figcaption></figure><p></p><p>网上大多都说分块大小取<span><span>n\sqrt{n}</span><span><span><span></span><span><span><span><span><span><span></span><span><span>n</span></span></span><span><span></span><span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span>最优，此时时间复杂度为<span><span>O(nn)O(n\sqrt{n})</span><span><span><span></span><span>O</span><span>(</span><span>n</span><span><span><span><span><span><span></span><span><span>n</span></span></span><span><span></span><span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span>)</span></span></span></span>，实际上这是不严谨的，当<span><span>nn</span><span><span><span></span><span>n</span></span></span></span>，<span><span>mm</span><span><span><span></span><span>m</span></span></span></span>差距较大时使用<span><span>n\sqrt{n}</span><span><span><span></span><span><span><span><span><span><span></span><span><span>n</span></span></span><span><span></span><span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span>作为分块大小效率会明显降低。</p><hr /><p>普通莫队时间复杂度的证明：</p><p>具体证明方法有多种：</p><ul>
<li>第一种（By yihang_01）</li>
</ul><p>记：令每一块中 <span><span>LL</span><span><span><span></span><span>L</span></span></span></span> 的最大值为 <span><span>max1,max2,max3,⋯ ,max⌈n⌉max_1, max_2, max_3, \cdots, max_{\lceil \sqrt{n} \rceil}</span><span><span><span></span><span>ma</span><span><span>x</span><span><span><span><span><span><span></span><span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>,</span><span></span><span>ma</span><span><span>x</span><span><span><span><span><span><span></span><span><span>2</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>,</span><span></span><span>ma</span><span><span>x</span><span><span><span><span><span><span></span><span><span>3</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>,</span><span></span><span>⋯</span><span></span><span></span><span>,</span><span></span><span>ma</span><span><span>x</span><span><span><span><span><span><span></span><span><span><span>⌈</span><span><span><span><span><span><span></span><span><span>n</span></span></span><span><span></span><span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span>⌉</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span>。</p><p>由第一次排序可知，<br />
<span><span>max1≤max2≤⋯≤max⌈n⌉max_1 \leq max_2 \leq \cdots \leq max_{\lceil \sqrt{n} \rceil}</span><span><span><span></span><span>ma</span><span><span>x</span><span><span><span><span><span><span></span><span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>≤</span><span></span></span><span><span></span><span>ma</span><span><span>x</span><span><span><span><span><span><span></span><span><span>2</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>≤</span><span></span></span><span><span></span><span>⋯</span><span></span><span>≤</span><span></span></span><span><span></span><span>ma</span><span><span>x</span><span><span><span><span><span><span></span><span><span><span>⌈</span><span><span><span><span><span><span></span><span><span>n</span></span></span><span><span></span><span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span>⌉</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span>。</p><p>显然，对于每一块暴力求出第一个询问的时间复杂度为 <span><span>O(n)O(n)</span><span><span><span></span><span>O</span><span>(</span><span>n</span><span>)</span></span></span></span>。</p><p>考虑最坏的情况，在每一块中，<span><span>RR</span><span><span><span></span><span>R</span></span></span></span> 的最大值均为 <span><span>nn</span><span><span><span></span><span>n</span></span></span></span>，每次修改操作均要将 <span><span>LL</span><span><span><span></span><span>L</span></span></span></span> 由 <span><span>maxi−1max_{i-1}</span><span><span><span></span><span>ma</span><span><span>x</span><span><span><span><span><span><span></span><span><span><span>i</span><span>−</span><span>1</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span> 修改至 <span><span>maximax_i</span><span><span><span></span><span>ma</span><span><span>x</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span> 或由 <span><span>maximax_i</span><span><span><span></span><span>ma</span><span><span>x</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span> 修改至 <span><span>maxi−1max_{i-1}</span><span><span><span></span><span>ma</span><span><span>x</span><span><span><span><span><span><span></span><span><span><span>i</span><span>−</span><span>1</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span>。</p><p>考虚 <span><span>RR</span><span><span><span></span><span>R</span></span></span></span>：因为 <span><span>RR</span><span><span><span></span><span>R</span></span></span></span> 在块中已经排好序，所以在同一块修改它的时间复杂度为 <span><span>O(n)O(n)</span><span><span><span></span><span>O</span><span>(</span><span>n</span><span>)</span></span></span></span>。<br />
对于所有块就是 <span><span>O(nn)O(n\sqrt{n})</span><span><span><span></span><span>O</span><span>(</span><span>n</span><span><span><span><span><span><span></span><span><span>n</span></span></span><span><span></span><span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span>)</span></span></span></span>。</p><p>重点分析 <span><span>LL</span><span><span><span></span><span>L</span></span></span></span>：因为每一次改变的时间复杂度都是 <span><span>O(n⋅(maxi−maxi−1))O(n \cdot (max_i - max_{i-1}))</span><span><span><span></span><span>O</span><span>(</span><span>n</span><span></span><span>⋅</span><span></span></span><span><span></span><span>(</span><span>ma</span><span><span>x</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>−</span><span></span></span><span><span></span><span>ma</span><span><span>x</span><span><span><span><span><span><span></span><span><span><span>i</span><span>−</span><span>1</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>))</span></span></span></span> 的，所以在同一块中的时间复杂度为</p><span><span><span>O(n⋅(maxi−maxi−1))O\Big(\sqrt{n} \cdot (max_i - max_{i-1})\Big)</span><span><span><span></span><span>O</span><span><span>(</span></span><span><span><span><span><span><span></span><span><span>n</span></span></span><span><span></span><span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span><span>⋅</span><span></span></span><span><span></span><span>(</span><span>ma</span><span><span>x</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>−</span><span></span></span><span><span></span><span>ma</span><span><span>x</span><span><span><span><span><span><span></span><span><span><span>i</span><span>−</span><span>1</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>)</span><span><span>)</span></span></span></span></span></span><p>将每一块上的时间复杂度合在一起，可以得到</p><span><span><span>O(n(max1−1)+n(max2−max1)+n(max3−max2)+⋯+n(max⌈n⌉−max⌈n⌉−1))O\big(\sqrt{n}(max_1 - 1) + \sqrt{n}(max_2 - max_1) + \sqrt{n}(max_3 - max_2) + \cdots + \sqrt{n}(max_{\lceil \sqrt{n} \rceil} - max_{\lceil \sqrt{n} \rceil - 1})\big)</span><span><span><span></span><span>O</span><span><span>(</span></span><span><span><span><span><span><span></span><span><span>n</span></span></span><span><span></span><span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span>(</span><span>ma</span><span><span>x</span><span><span><span><span><span><span></span><span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>−</span><span></span></span><span><span></span><span>1</span><span>)</span><span></span><span>+</span><span></span></span><span><span></span><span><span><span><span><span><span></span><span><span>n</span></span></span><span><span></span><span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span>(</span><span>ma</span><span><span>x</span><span><span><span><span><span><span></span><span><span>2</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>−</span><span></span></span><span><span></span><span>ma</span><span><span>x</span><span><span><span><span><span><span></span><span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>)</span><span></span><span>+</span><span></span></span><span><span></span><span><span><span><span><span><span></span><span><span>n</span></span></span><span><span></span><span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span>(</span><span>ma</span><span><span>x</span><span><span><span><span><span><span></span><span><span>3</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>−</span><span></span></span><span><span></span><span>ma</span><span><span>x</span><span><span><span><span><span><span></span><span><span>2</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>)</span><span></span><span>+</span><span></span></span><span><span></span><span>⋯</span><span></span><span>+</span><span></span></span><span><span></span><span><span><span><span><span><span></span><span><span>n</span></span></span><span><span></span><span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span>(</span><span>ma</span><span><span>x</span><span><span><span><span><span><span></span><span><span><span>⌈</span><span><span><span><span><span><span></span><span><span>n</span></span></span><span><span></span><span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span>⌉</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>−</span><span></span></span><span><span></span><span>ma</span><span><span>x</span><span><span><span><span><span><span></span><span><span><span>⌈</span><span><span><span><span><span><span></span><span><span>n</span></span></span><span><span></span><span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span>⌉</span><span>−</span><span>1</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>)</span><span><span>)</span></span></span></span></span></span><span><span><span>=O(n⋅max1−1+max2−max1+max3−max2+⋯+max⌈n⌉−1−max⌈n⌉−2+max⌈n⌉−max⌈n⌉−1)= O\big(\sqrt{n} \cdot max_1 - 1 + max_2 - max_1 + max_3 - max_2 + \cdots + max_{\lceil \sqrt{n} \rceil - 1} - max_{\lceil \sqrt{n} \rceil - 2} + max_{\lceil \sqrt{n} \rceil} - max_{\lceil \sqrt{n} \rceil - 1}\big)</span><span><span><span></span><span>=</span><span></span></span><span><span></span><span>O</span><span><span>(</span></span><span><span><span><span><span><span></span><span><span>n</span></span></span><span><span></span><span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span><span>⋅</span><span></span></span><span><span></span><span>ma</span><span><span>x</span><span><span><span><span><span><span></span><span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>−</span><span></span></span><span><span></span><span>1</span><span></span><span>+</span><span></span></span><span><span></span><span>ma</span><span><span>x</span><span><span><span><span><span><span></span><span><span>2</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>−</span><span></span></span><span><span></span><span>ma</span><span><span>x</span><span><span><span><span><span><span></span><span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>+</span><span></span></span><span><span></span><span>ma</span><span><span>x</span><span><span><span><span><span><span></span><span><span>3</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>−</span><span></span></span><span><span></span><span>ma</span><span><span>x</span><span><span><span><span><span><span></span><span><span>2</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>+</span><span></span></span><span><span></span><span>⋯</span><span></span><span>+</span><span></span></span><span><span></span><span>ma</span><span><span>x</span><span><span><span><span><span><span></span><span><span><span>⌈</span><span><span><span><span><span><span></span><span><span>n</span></span></span><span><span></span><span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span>⌉</span><span>−</span><span>1</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>−</span><span></span></span><span><span></span><span>ma</span><span><span>x</span><span><span><span><span><span><span></span><span><span><span>⌈</span><span><span><span><span><span><span></span><span><span>n</span></span></span><span><span></span><span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span>⌉</span><span>−</span><span>2</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>+</span><span></span></span><span><span></span><span>ma</span><span><span>x</span><span><span><span><span><span><span></span><span><span><span>⌈</span><span><span><span><span><span><span></span><span><span>n</span></span></span><span><span></span><span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span>⌉</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>−</span><span></span></span><span><span></span><span>ma</span><span><span>x</span><span><span><span><span><span><span></span><span><span><span>⌈</span><span><span><span><span><span><span></span><span><span>n</span></span></span><span><span></span><span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span>⌉</span><span>−</span><span>1</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span><span>)</span></span></span></span></span></span><span><span><span>=O(n⋅max⌈n⌉)= O(\sqrt{n} \cdot max_{\lceil \sqrt{n} \rceil})</span><span><span><span></span><span>=</span><span></span></span><span><span></span><span>O</span><span>(</span><span><span><span><span><span><span></span><span><span>n</span></span></span><span><span></span><span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span><span>⋅</span><span></span></span><span><span></span><span>ma</span><span><span>x</span><span><span><span><span><span><span></span><span><span><span>⌈</span><span><span><span><span><span><span></span><span><span>n</span></span></span><span><span></span><span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span>⌉</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>)</span></span></span></span></span><p>由于 <span><span>max⌈n⌉max_{\lceil \sqrt{n} \rceil}</span><span><span><span></span><span>ma</span><span><span>x</span><span><span><span><span><span><span></span><span><span><span>⌈</span><span><span><span><span><span><span></span><span><span>n</span></span></span><span><span></span><span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span>⌉</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span> 最大为 <span><span>nn</span><span><span><span></span><span>n</span></span></span></span>，所以 <span><span>LL</span><span><span><span></span><span>L</span></span></span></span> 的总体时间复杂度最坏为 <span><span>O(nn)O(n\sqrt{n})</span><span><span><span></span><span>O</span><span>(</span><span>n</span><span><span><span><span><span><span></span><span><span>n</span></span></span><span><span></span><span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span>)</span></span></span></span>。</p><p>综上所述，莫队算法的时间复杂度为 <span><span>O(nn)O(n\sqrt{n})</span><span><span><span></span><span>O</span><span>(</span><span>n</span><span><span><span><span><span><span></span><span><span>n</span></span></span><span><span></span><span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span>)</span></span></span></span>。</p><ul>
<li>第二种</li>
</ul><ol>
<li>
<p>排序：<span><span>O(nlog⁡n)O(n \log n)</span><span><span><span></span><span>O</span><span>(</span><span>n</span><span></span><span>lo<span>g</span></span><span></span><span>n</span><span>)</span></span></span></span></p>
</li>
<li>
<p>设每个块 <span><span>ii</span><span><span><span></span><span>i</span></span></span></span> 中分布有 <span><span>xix_i</span><span><span><span></span><span><span>x</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span> 个左端点，那么处理块 <span><span>ii</span><span><span><span></span><span>i</span></span></span></span> 的最坏时间复杂度为 <span><span>O(xin)O(x_i \sqrt{n})</span><span><span><span></span><span>O</span><span>(</span><span><span>x</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span><span><span><span><span><span></span><span><span>n</span></span></span><span><span></span><span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span>)</span></span></span></span>。  指针跨越整个块的时间复杂度是 <span><span>O(n)O(\sqrt{n})</span><span><span><span></span><span>O</span><span>(</span><span><span><span><span><span><span></span><span><span>n</span></span></span><span><span></span><span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span>)</span></span></span></span>，要跨越 <span><span>n\sqrt{n}</span><span><span><span></span><span><span><span><span><span><span></span><span><span>n</span></span></span><span><span></span><span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span> 次，所以左端点移动的总时间复杂度为 <span><span>O(∑xin+nn)=O(nn+n)O\big(\sum x_i \sqrt{n} + \sqrt{n}\sqrt{n}\big) = O(n \sqrt{n} + n)</span><span><span><span></span><span>O</span><span><span>(</span></span><span></span><span>∑</span><span></span><span><span>x</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span><span><span><span><span><span></span><span><span>n</span></span></span><span><span></span><span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span><span>+</span><span></span></span><span><span></span><span><span><span><span><span><span></span><span><span>n</span></span></span><span><span></span><span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span><span><span><span><span><span></span><span><span>n</span></span></span><span><span></span><span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span><span>)</span></span><span></span><span>=</span><span></span></span><span><span></span><span>O</span><span>(</span><span>n</span><span><span><span><span><span><span></span><span><span>n</span></span></span><span><span></span><span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span><span>+</span><span></span></span><span><span></span><span>n</span><span>)</span></span></span></span>。</p>
</li>
<li>
<p>右端点在一个左端点相同的块内是有序的。对于每个块 <span><span>ii</span><span><span><span></span><span>i</span></span></span></span> 中的 <span><span>xix_i</span><span><span><span></span><span><span>x</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span> 个区间，右端点最多跳完整的一个序列（不会往回跳）。一共有 <span><span>n\sqrt{n}</span><span><span><span></span><span><span><span><span><span><span></span><span><span>n</span></span></span><span><span></span><span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span> 个块，所以右端点移动的总时间复杂度为 <span><span>O(nn)O(n \sqrt{n})</span><span><span><span></span><span>O</span><span>(</span><span>n</span><span><span><span><span><span><span></span><span><span>n</span></span></span><span><span></span><span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span>)</span></span></span></span>。</p>
</li>
</ol><p>总复杂度：<span><span>O(nn+nlog⁡n)O(n \sqrt{n} + n \log n)</span><span><span><span></span><span>O</span><span>(</span><span>n</span><span><span><span><span><span><span></span><span><span>n</span></span></span><span><span></span><span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span><span>+</span><span></span></span><span><span></span><span>n</span><span></span><span>lo<span>g</span></span><span></span><span>n</span><span>)</span></span></span></span></p><ul>
<li>第三种</li>
</ul><p>分块相同时，右端点递增是<span><span>O(n)O(n)</span><span><span><span></span><span>O</span><span>(</span><span>n</span><span>)</span></span></span></span>的，分块共有<span><span>O(n)O(\sqrt{n})</span><span><span><span></span><span>O</span><span>(</span><span><span><span><span><span><span></span><span><span>n</span></span></span><span><span></span><span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span>)</span></span></span></span>个，复杂度为<span><span>n1.5n^{1.5}</span><span><span><span></span><span><span>n</span><span><span><span><span><span><span></span><span><span><span>1.5</span></span></span></span></span></span></span></span></span></span></span></span></p><p>分块转移时，右端点最多变化<span><span>NN</span><span><span><span></span><span>N</span></span></span></span>，分块共有<span><span>O(n)O(\sqrt{n})</span><span><span><span></span><span>O</span><span>(</span><span><span><span><span><span><span></span><span><span>n</span></span></span><span><span></span><span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span>)</span></span></span></span>个，复杂度为<span><span>n1.5n^{1.5}</span><span><span><span></span><span><span>n</span><span><span><span><span><span><span></span><span><span><span>1.5</span></span></span></span></span></span></span></span></span></span></span></span></p><p>分块相同时，左端点最多变化<span><span>n\sqrt{n}</span><span><span><span></span><span><span><span><span><span><span></span><span><span>n</span></span></span><span><span></span><span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span>，分块转移时，左端点最多变化<span><span>2n2\sqrt{n}</span><span><span><span></span><span>2</span><span><span><span><span><span><span></span><span><span>n</span></span></span><span><span></span><span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></p><p>共有<span><span>NN</span><span><span><span></span><span>N</span></span></span></span>个询问，复杂度为<span><span>n1.5n^{1.5}</span><span><span><span></span><span><span>n</span><span><span><span><span><span><span></span><span><span><span>1.5</span></span></span></span></span></span></span></span></span></span></span></span></p><p>综上，<span><span>nn</span><span><span><span></span><span>n</span></span></span></span>次询问，长度为<span><span>mm</span><span><span><span></span><span>m</span></span></span></span>，块大小为<span><span>m\sqrt{m}</span><span><span><span></span><span><span><span><span><span><span></span><span><span>m</span></span></span><span><span></span><span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span>的莫队，时间复杂度为<span><span>O(nm)O(n\sqrt{m})</span><span><span><span></span><span>O</span><span>(</span><span>n</span><span><span><span><span><span><span></span><span><span>m</span></span></span><span><span></span><span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span>)</span></span></span></span>。</p><hr /><p>普通莫队最优块长的证明：</p><p>设每一块的大小为<span><span>TT</span><span><span><span></span><span>T</span></span></span></span>，序列长为<span><span>nn</span><span><span><span></span><span>n</span></span></span></span>，询问个数为<span><span>mm</span><span><span><span></span><span>m</span></span></span></span>。</p><p>那么最多有<span><span>nT\frac{n}{T}</span><span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span><span>T</span></span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>n</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span></span></span></span>块。</p><p>对于右端点的移动，每一块最多移动<span><span>nn</span><span><span><span></span><span>n</span></span></span></span>次，有<span><span>nT\frac{n}{T}</span><span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span><span>T</span></span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>n</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span></span></span></span>块，所以右端点时间复杂度为<span><span>O(n2T) O\left(\frac{n^2}{T}\right)</span><span><span><span></span><span>O</span><span></span><span><span><span>(</span></span><span><span></span><span><span><span><span><span><span></span><span><span><span>T</span></span></span></span><span><span></span><span></span></span><span><span></span><span><span><span><span>n</span><span><span><span><span><span><span></span><span><span>2</span></span></span></span></span></span></span></span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span><span>)</span></span></span></span></span></span>。</p><p>对于左端点的移动，每一次最多移动<span><span>TT</span><span><span><span></span><span>T</span></span></span></span>次，有<span><span>mm</span><span><span><span></span><span>m</span></span></span></span>次移动，所以左端点时间复杂度为<span><span>O(mT)O(mT)</span><span><span><span></span><span>O</span><span>(</span><span>m</span><span>T</span><span>)</span></span></span></span>。</p><p>那么总时间复杂度为<span><span>O(n2T+mT)O\left(\frac{n^2}{T} + mT\right)</span><span><span><span></span><span>O</span><span></span><span><span><span>(</span></span><span><span></span><span><span><span><span><span><span></span><span><span><span>T</span></span></span></span><span><span></span><span></span></span><span><span></span><span><span><span><span>n</span><span><span><span><span><span><span></span><span><span>2</span></span></span></span></span></span></span></span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span></span><span>+</span><span></span><span>m</span><span>T</span><span><span>)</span></span></span></span></span></span>。</p><p>设<span><span>n2T+mT=S\frac{n^2}{T} + mT = S</span><span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span><span>T</span></span></span></span><span><span></span><span></span></span><span><span></span><span><span><span><span>n</span><span><span><span><span><span><span></span><span><span>2</span></span></span></span></span></span></span></span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span></span><span>+</span><span></span></span><span><span></span><span>m</span><span>T</span><span></span><span>=</span><span></span></span><span><span></span><span>S</span></span></span></span>。</p><p>原式等于<span><span>n2+mT2−ST=0n^2 + mT^2 - ST = 0</span><span><span><span></span><span><span>n</span><span><span><span><span><span><span></span><span><span>2</span></span></span></span></span></span></span></span><span></span><span>+</span><span></span></span><span><span></span><span>m</span><span><span>T</span><span><span><span><span><span><span></span><span><span>2</span></span></span></span></span></span></span></span><span></span><span>−</span><span></span></span><span><span></span><span>S</span><span>T</span><span></span><span>=</span><span></span></span><span><span></span><span>0</span></span></span></span>。</p><p>这样变为一个经典的二次函数求最小值的问题。</p><p><span><span>Δ=S2−4mn2≥0\Delta = S^2 - 4mn^2 \geq 0</span><span><span><span></span><span>Δ</span><span></span><span>=</span><span></span></span><span><span></span><span><span>S</span><span><span><span><span><span><span></span><span><span>2</span></span></span></span></span></span></span></span><span></span><span>−</span><span></span></span><span><span></span><span>4</span><span>m</span><span><span>n</span><span><span><span><span><span><span></span><span><span>2</span></span></span></span></span></span></span></span><span></span><span>≥</span><span></span></span><span><span></span><span>0</span></span></span></span></p><p>为取到最小值，<span><span>Δ=0\Delta = 0</span><span><span><span></span><span>Δ</span><span></span><span>=</span><span></span></span><span><span></span><span>0</span></span></span></span>。</p><p>那么<span><span>S2−4mn2=0S^2 - 4mn^2 = 0</span><span><span><span></span><span><span>S</span><span><span><span><span><span><span></span><span><span>2</span></span></span></span></span></span></span></span><span></span><span>−</span><span></span></span><span><span></span><span>4</span><span>m</span><span><span>n</span><span><span><span><span><span><span></span><span><span>2</span></span></span></span></span></span></span></span><span></span><span>=</span><span></span></span><span><span></span><span>0</span></span></span></span>。</p><p><span><span>S2=4mn2S^2 = 4mn^2</span><span><span><span></span><span><span>S</span><span><span><span><span><span><span></span><span><span>2</span></span></span></span></span></span></span></span><span></span><span>=</span><span></span></span><span><span></span><span>4</span><span>m</span><span><span>n</span><span><span><span><span><span><span></span><span><span>2</span></span></span></span></span></span></span></span></span></span></span></p><p><span><span>S=2mnS = 2\sqrt{mn}</span><span><span><span></span><span>S</span><span></span><span>=</span><span></span></span><span><span></span><span>2</span><span><span><span><span><span><span></span><span><span>mn</span></span></span><span><span></span><span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></p><p>代入回 <span><span>x=−b2a+Δ2ax = -\frac{b}{2a} + \frac{\sqrt{\Delta}}{2a}</span><span><span><span></span><span>x</span><span></span><span>=</span><span></span></span><span><span></span><span>−</span><span><span></span><span><span><span><span><span><span></span><span><span><span>2</span><span>a</span></span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>b</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span></span><span>+</span><span></span></span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span><span>2</span><span>a</span></span></span></span><span><span></span><span></span></span><span><span></span><span><span><span><span><span><span><span><span></span><span><span>Δ</span></span></span><span><span></span><span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span></span></span></span></p><p>算出 <span><span>T=nmT = \frac{n}{\sqrt{m}}</span><span><span><span></span><span>T</span><span></span><span>=</span><span></span></span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span><span><span><span><span><span><span></span><span><span>m</span></span></span><span><span></span><span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>n</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span></span></span></span></p></section></section><section><h2>其他莫队<a href="#其他莫队"><span>#</span></a></h2><section><h3>带修莫队<a href="#带修莫队"><span>#</span></a></h3><p>前面说过，普通莫队只能解决没有修改的问题，那么如果想解决修改问题呢？</p><p><a href="https://www.luogu.com.cn/problem/P1903" target="_blank">P1903 [国家集训队] 数颜色 / 维护队列</a></p><p>那就需要带修莫队，带修莫队就是一种支持修改查询的莫队。</p><p>当然，这是一道在线问题，但我们可以把这个在线问题转化为离线问题。</p><p>普通莫队是把点经过排序，下一次询问是从上一次询问转移而来，但是有修改的问题在经过排序后，查询的结果也会随着排序而改变，把所有的修改操作加上一个时间戳 <span><span>[l,r,time][l,r,time]</span><span><span><span></span><span>[</span><span>l</span><span>,</span><span></span><span>r</span><span>,</span><span></span><span>t</span><span>im</span><span>e</span><span>]</span></span></span></span>。</p><p>排序规则：第一关键字为左端点所在块 <span><span>lB\frac{l}{B}</span><span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span><span>B</span></span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>l</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span></span></span></span>，第二关键字为右端点所在块 <span><span>rB\frac{r}{B}</span><span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span><span>B</span></span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>r</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span></span></span></span>，第三关键字是时间 <span><span>tt</span><span><span><span></span><span>t</span></span></span></span>。</p><p>每次询问先做区间拓展，再考虑时间戳，也就是之前的修改对当次查询的影响。</p><p>(1) <span><span>jj</span><span><span><span></span><span>j</span></span></span></span> &gt; <span><span>ii</span><span><span><span></span><span>i</span></span></span></span>，则把 <span><span>i+1i+1</span><span><span><span></span><span>i</span><span></span><span>+</span><span></span></span><span><span></span><span>1</span></span></span></span> 到 <span><span>jj</span><span><span><span></span><span>j</span></span></span></span> 个修改的贡献加上</p><p>(2) <span><span>jj</span><span><span><span></span><span>j</span></span></span></span> &lt; <span><span>ii</span><span><span><span></span><span>i</span></span></span></span>，则把 <span><span>ii</span><span><span><span></span><span>i</span></span></span></span> 到 <span><span>j+1j+1</span><span><span><span></span><span>j</span><span></span><span>+</span><span></span></span><span><span></span><span>1</span></span></span></span> 个修改的贡献还原</p><section><h4>带修莫队的时间复杂度及最优块长的证明<a href="#带修莫队的时间复杂度及最优块长的证明"><span>#</span></a></h4><p>块的大小为 <span><span>B23B^{\frac{2}{3}}</span><span><span><span></span><span><span>B</span><span><span><span><span><span><span></span><span><span><span><span></span><span><span><span><span><span><span></span><span><span><span>3</span></span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>2</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span></span></span></span></span></span></span></span></span></span></span></span>,修改个数为 <span><span>cc</span><span><span><span></span><span>c</span></span></span></span>，询问次数为 <span><span>qq</span><span><span><span></span><span>q</span></span></span></span>，则总移动次数为 <span><span>O(cn2B2+qB+n2B)O(\frac{cn^{2}}{B^{2}}+qB+\frac{n^{2}}{B})</span><span><span><span></span><span>O</span><span>(</span><span><span></span><span><span><span><span><span><span></span><span><span><span><span>B</span><span><span><span><span><span><span></span><span><span><span>2</span></span></span></span></span></span></span></span></span></span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>c</span><span><span>n</span><span><span><span><span><span><span></span><span><span><span>2</span></span></span></span></span></span></span></span></span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span></span><span>+</span><span></span></span><span><span></span><span>q</span><span>B</span><span></span><span>+</span><span></span></span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span><span>B</span></span></span></span><span><span></span><span></span></span><span><span></span><span><span><span><span>n</span><span><span><span><span><span><span></span><span><span><span>2</span></span></span></span></span></span></span></span></span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span>)</span></span></span></span>，操作次数为 <span><span>mm</span><span><span><span></span><span>m</span></span></span></span> 的话，则为 <span><span>O(mn2B2+mB+n2B)O(\frac{mn^{2}}{B^{2}}+mB+\frac{n^{2}}{B})</span><span><span><span></span><span>O</span><span>(</span><span><span></span><span><span><span><span><span><span></span><span><span><span><span>B</span><span><span><span><span><span><span></span><span><span><span>2</span></span></span></span></span></span></span></span></span></span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>m</span><span><span>n</span><span><span><span><span><span><span></span><span><span><span>2</span></span></span></span></span></span></span></span></span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span></span><span>+</span><span></span></span><span><span></span><span>m</span><span>B</span><span></span><span>+</span><span></span></span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span><span>B</span></span></span></span><span><span></span><span></span></span><span><span></span><span><span><span><span>n</span><span><span><span><span><span><span></span><span><span><span>2</span></span></span></span></span></span></span></span></span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span>)</span></span></span></span>。</p><p><span><span>BB</span><span><span><span></span><span>B</span></span></span></span> 可以取</p><span><span><span>B=n2312(9m3n2+327m6n4−m3n6)13+(9m3n2+327m6n4−m3n6)13323mB=\frac{n^2}{3^{\frac{1}{2}}(9m^3n^2+\sqrt{3}\sqrt{27m^6n^4-m^3n^6})^{\frac{1}{3}}}+ \frac{(9m^3n^2+\sqrt{3}\sqrt{27m^6n^4 - m^3n^6})^{\frac{1}{3}}}{3^{\frac{2}{3}}m}</span><span><span><span></span><span>B</span><span></span><span>=</span><span></span></span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span><span>3</span><span><span><span><span><span><span></span><span><span><span><span></span><span><span><span><span><span><span></span><span><span><span>2</span></span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>1</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span></span></span></span></span></span></span></span></span><span>(</span><span>9</span><span><span>m</span><span><span><span><span><span><span></span><span><span>3</span></span></span></span></span></span></span></span><span><span>n</span><span><span><span><span><span><span></span><span><span>2</span></span></span></span></span></span></span></span><span></span><span>+</span><span></span><span><span><span><span><span><span></span><span><span>3</span></span></span><span><span></span><span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span><span><span><span><span><span></span><span><span>27</span><span><span>m</span><span><span><span><span><span><span></span><span><span>6</span></span></span></span></span></span></span></span><span><span>n</span><span><span><span><span><span><span></span><span><span>4</span></span></span></span></span></span></span></span><span></span><span>−</span><span></span><span><span>m</span><span><span><span><span><span><span></span><span><span>3</span></span></span></span></span></span></span></span><span><span>n</span><span><span><span><span><span><span></span><span><span>6</span></span></span></span></span></span></span></span></span></span><span><span></span><span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span><span>)</span><span><span><span><span><span><span></span><span><span><span><span></span><span><span><span><span><span><span></span><span><span><span>3</span></span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>1</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span></span></span></span></span></span></span></span></span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>n</span><span><span><span><span><span><span></span><span><span>2</span></span></span></span></span></span></span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span></span><span>+</span><span></span></span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span><span>3</span><span><span><span><span><span><span></span><span><span><span><span></span><span><span><span><span><span><span></span><span><span><span>3</span></span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>2</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span></span></span></span></span></span></span></span></span><span>m</span></span></span><span><span></span><span></span></span><span><span></span><span><span>(</span><span>9</span><span><span>m</span><span><span><span><span><span><span></span><span><span>3</span></span></span></span></span></span></span></span><span><span>n</span><span><span><span><span><span><span></span><span><span>2</span></span></span></span></span></span></span></span><span></span><span>+</span><span></span><span><span><span><span><span><span></span><span><span>3</span></span></span><span><span></span><span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span><span><span><span><span><span></span><span><span>27</span><span><span>m</span><span><span><span><span><span><span></span><span><span>6</span></span></span></span></span></span></span></span><span><span>n</span><span><span><span><span><span><span></span><span><span>4</span></span></span></span></span></span></span></span><span></span><span>−</span><span></span><span><span>m</span><span><span><span><span><span><span></span><span><span>3</span></span></span></span></span></span></span></span><span><span>n</span><span><span><span><span><span><span></span><span><span>6</span></span></span></span></span></span></span></span></span></span><span><span></span><span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span><span>)</span><span><span><span><span><span><span></span><span><span><span><span></span><span><span><span><span><span><span></span><span><span><span>3</span></span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>1</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span></span></span></span></span></span></span></span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span></span></span></span></span><p>正常写题时肯定不会去算这么离谱的一个块长 <span><span>BB</span><span><span><span></span><span>B</span></span></span></span>，直接视作 <span><span>n=mn=m</span><span><span><span></span><span>n</span><span></span><span>=</span><span></span></span><span><span></span><span>m</span></span></span></span> 的话，就可以得到总移动次数为 <span><span>O(n3B2+nB+n2B)O(\frac{n^{3}}{B^{2}}+nB+\frac{n^{2}}{B})</span><span><span><span></span><span>O</span><span>(</span><span><span></span><span><span><span><span><span><span></span><span><span><span><span>B</span><span><span><span><span><span><span></span><span><span><span>2</span></span></span></span></span></span></span></span></span></span></span></span><span><span></span><span></span></span><span><span></span><span><span><span><span>n</span><span><span><span><span><span><span></span><span><span><span>3</span></span></span></span></span></span></span></span></span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span></span><span>+</span><span></span></span><span><span></span><span>n</span><span>B</span><span></span><span>+</span><span></span></span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span><span>B</span></span></span></span><span><span></span><span></span></span><span><span></span><span><span><span><span>n</span><span><span><span><span><span><span></span><span><span><span>2</span></span></span></span></span></span></span></span></span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span>)</span></span></span></span>，那么当 <span><span>B=n23B=n^{\frac{2}{3}}</span><span><span><span></span><span>B</span><span></span><span>=</span><span></span></span><span><span></span><span><span>n</span><span><span><span><span><span><span></span><span><span><span><span></span><span><span><span><span><span><span></span><span><span><span>3</span></span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>2</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span></span></span></span></span></span></span></span></span></span></span></span> 时，取得最小值，此时最小值为 <span><span>O(n53)O(n^{\frac{5}{3}})</span><span><span><span></span><span>O</span><span>(</span><span><span>n</span><span><span><span><span><span><span></span><span><span><span><span></span><span><span><span><span><span><span></span><span><span><span>3</span></span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>5</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span></span></span></span></span></span></span></span></span><span>)</span></span></span></span>。</p><hr /><p>同样，对于最优块长也可以做如下证明：</p><p>可以这么认为，序列的值是随着时间而变化的。</p><p>那我们就在坐标系上再加上一个时间维度，用 <span><span>(l,r,t)(l,r,t)</span><span><span><span></span><span>(</span><span>l</span><span>,</span><span></span><span>r</span><span>,</span><span></span><span>t</span><span>)</span></span></span></span> 来表示一个查询</p><p></p><figure><img src="https://www.lansganbs.cn/images/article/%E7%AE%97%E6%B3%95%E7%AB%9E%E8%B5%9B/%E5%88%86%E5%9D%97%E8%8E%AB%E9%98%9F/%E5%B8%A6%E4%BF%AE%E8%8E%AB%E9%98%9F%E5%9B%BE%E4%BE%8B.png" alt="alt text" /><figcaption>alt text</figcaption></figure><p></p><p>很明显，我们需要分别按照l与r分块，在同一块内的询问按照t从小到大完成。块的大小就是 <span><span>n23n^{\frac{2}{3}}</span><span><span><span></span><span><span>n</span><span><span><span><span><span><span></span><span><span><span><span></span><span><span><span><span><span><span></span><span><span><span>3</span></span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>2</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span></span></span></span></span></span></span></span></span></span></span></span>，只是这个粗略得出的块长只是相对优秀的，而不是最优。</p><p>综上，带修莫队的渐进时间复杂度为 <span><span>O(nlogn+n53)O(nlogn+n^{\frac{5}{3}})</span><span><span><span></span><span>O</span><span>(</span><span>n</span><span>l</span><span>o</span><span>g</span><span>n</span><span></span><span>+</span><span></span></span><span><span></span><span><span>n</span><span><span><span><span><span><span></span><span><span><span><span></span><span><span><span><span><span><span></span><span><span><span>3</span></span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>5</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span></span></span></span></span></span></span></span></span><span>)</span></span></span></span>（视作 <span><span>n=mn=m</span><span><span><span></span><span>n</span><span></span><span>=</span><span></span></span><span><span></span><span>m</span></span></span></span> ），认为是 <span><span>O(n53)O(n^{\frac{5}{3}})</span><span><span><span></span><span>O</span><span>(</span><span><span>n</span><span><span><span><span><span><span></span><span><span><span><span></span><span><span><span><span><span><span></span><span><span><span>3</span></span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>5</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span></span></span></span></span></span></span></span></span><span>)</span></span></span></span>。</p></section><section><h4>实现过程<a href="#实现过程"><span>#</span></a></h4><p>对于询问，我们记录以下值：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>struct</span><span> </span><span>kkk</span><span> {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>int</span><span> l;</span><span>  //左端点</span></div></div><div><div><div>3</div></div><div><span>  </span><span>int</span><span> r;</span><span>  //右端点</span></div></div><div><div><div>4</div></div><div><span>  </span><span>int</span><span> t;</span><span>  //此询问前修改数量</span></div></div><div><div><div>5</div></div><div><span>  </span><span>int</span><span> id;</span><span>  //询问编号</span></div></div><div><div><div>6</div></div><div><span>}q[N];</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>对于修改，我们记录以下值：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>struct</span><span> </span><span>ttt</span><span> {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>int</span><span> id;</span><span>  //修改位置</span></div></div><div><div><div>3</div></div><div><span>  </span><span>int</span><span> val;</span><span>//修改值</span></div></div><div><div><div>4</div></div><div><span>}c[N];</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>将原来值对答案的影响抹去，再将修改值对答案的影响加上，然后更新该位置的值</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>#define</span><span> </span><span>add</span><span>(</span><span>x</span><span>) {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>if</span><span>(</span><span>++</span><span>vis[x]</span><span>==</span><span>1</span><span>)sum</span><span>++</span><span>;</span></div></div><div><div><div>3</div></div><div><span>}</span></div></div><div><div><div>4</div></div><div><span>#define</span><span> </span><span>del</span><span>(</span><span>x</span><span>) {</span></div></div><div><div><div>5</div></div><div><span>  </span><span>if</span><span>(</span><span>--</span><span>vis[x]</span><span>==</span><span>0</span><span>)sum</span><span>--</span><span>;</span></div></div><div><div><div>6</div></div><div><span>}</span></div></div><div><div><div>7</div></div><div><span>void</span><span> </span><span>change</span><span>(</span><span>int</span><span> </span><span>x</span><span>) {</span></div></div><div><div><div>8</div></div><div><span>  </span><span>if</span><span>(c[x].id</span><span>&gt;=</span><span>l</span><span>&amp;&amp;</span><span>c[x].id</span><span>&lt;=</span><span>r) {</span></div></div><div><div><div>9</div></div><div><span>    </span><span>del</span><span>(v[c[x].id]);</span></div></div><div><div><div>10</div></div><div><span>    </span><span>add</span><span>(c[x].val);</span></div></div><div><div><div>11</div></div><div><span><span>  </span></span><span>}</span><span>  //改变影响</span></div></div><div><div><div>12</div></div><div><span>  </span><span>swap</span><span>(c[x].val,v[c[x].id]);</span><span>    //值更新 *</span></div></div><div><div><div>13</div></div><div><span>}</span></div></div><div><div><div>14</div></div><div><span>...</span></div></div><div><div><div>15</div></div><div><span>while</span><span>(now</span><span>&lt;</span><span>q[i].t)</span><span>change</span><span>(</span><span>++</span><span>now);</span><span>  //修改</span></div></div><div><div><div>16</div></div><div><span>while</span><span>(now</span><span>&gt;</span><span>q[i].t)</span><span>change</span><span>(now</span><span>--</span><span>);</span><span>  //修改</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><blockquote><p>这道题能转化为离线的根本原因是这一次的查询并不需要使用上一次的查询结果，只是题目让我们立即返回查询结果，但当次结果只是依赖于之前的修改，而不是依赖于之前的查询，所以我们给修改打上一个时间戳就可以了。</p></blockquote><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>int</span><span> n, m;</span></div></div><div><div><div>2</div></div><div><span>int</span><span> len;</span></div></div><div><div><div>3</div></div><div><span>int</span><span> mq, mr;</span></div></div><div><div><div>4</div></div><div><span>struct</span><span> </span><span>op</span><span> {</span></div></div><div><div><div>5</div></div><div><span>  </span><span>int</span><span> l, r;</span></div></div><div><div><div>6</div></div><div><span>  </span><span>int</span><span> idx, tim;</span></div></div><div><div><div>7</div></div><div><span>  </span><span>bool</span><span> </span><span>operator</span><span>&lt;</span><span>(</span><span>const</span><span> </span><span>op</span><span>&amp;</span><span> </span><span>that</span><span>) </span><span>const</span><span> {</span></div></div><div><div><div>8</div></div><div><span>    </span><span>if</span><span> (l </span><span>/</span><span> len </span><span>!=</span><span> that.l </span><span>/</span><span> len) </span><span>return</span><span> l </span><span>&lt;</span><span> that.l;</span></div></div><div><div><div>9</div></div><div><span>    </span><span>if</span><span> (r </span><span>/</span><span> len </span><span>!=</span><span> that.r </span><span>/</span><span> len) </span><span>return</span><span> r </span><span>&lt;</span><span> that.r;</span></div></div><div><div><div>10</div></div><div><span>    </span><span>return</span><span> tim </span><span>&lt;</span><span> that.tim;</span></div></div><div><div><div>11</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>12</div></div><div><span>} q[N];</span></div></div><div><div><div>13</div></div><div><span>struct</span><span> </span><span>modify</span><span> {</span></div></div><div><div><div>14</div></div><div><span>  </span><span>int</span><span> p, c;</span></div></div><div><div><div>15</div></div><div><span>} R[N];</span></div></div><div><div><div>16</div></div><div><span>int</span><span> a[N], cnt[N];</span></div></div><div><div><div>17</div></div><div><span>int</span><span> ans[N];</span></div></div><div><div><div>18</div></div><div><span>int</span><span> res;</span></div></div><div><div><div>19</div></div><div><span>void</span><span> </span><span>add</span><span>(</span><span>int</span><span> </span><span>x</span><span>) {</span></div></div><div><div><div>20</div></div><div><span>  </span><span>if</span><span> (cnt[x] </span><span>==</span><span> </span><span>0</span><span>) res</span><span>++</span><span>;</span></div></div><div><div><div>21</div></div><div><span><span>  </span></span><span>cnt[x]</span><span>++</span><span>;</span></div></div><div><div><div>22</div></div><div><span>}</span></div></div><div><div><div>23</div></div><div><span>void</span><span> </span><span>del</span><span>(</span><span>int</span><span> </span><span>x</span><span>) {</span></div></div><div><div><div>24</div></div><div><span><span>  </span></span><span>cnt[x]</span><span>--</span><span>;</span></div></div><div><div><div>25</div></div><div><span>  </span><span>if</span><span> (cnt[x] </span><span>==</span><span> </span><span>0</span><span>) res</span><span>--</span><span>;</span></div></div><div><div><div>26</div></div><div><span>}</span></div></div><div><div><div>27</div></div><div><span>void</span><span> </span><span>solve</span><span>() {</span></div></div><div><div><div>28</div></div><div><span><span>  </span></span><span>cin </span><span>&gt;&gt;</span><span> n </span><span>&gt;&gt;</span><span> m;</span></div></div><div><div><div>29</div></div><div><span><span>  </span></span><span>len </span><span>=</span><span> </span><span>pow</span><span>(n, </span><span>2.0</span><span> </span><span>/</span><span> </span><span>3.0</span><span>);</span></div></div><div><div><div>30</div></div><div><span>  </span><span>int</span><span> l, r;</span></div></div><div><div><div>31</div></div><div><span>  </span><span>char</span><span> ch;</span></div></div><div><div><div>32</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>1</span><span>; i </span><span>&lt;=</span><span> n; i</span><span>++</span><span>) {</span></div></div><div><div><div>33</div></div><div><span><span>    </span></span><span>cin </span><span>&gt;&gt;</span><span> a[i];</span></div></div><div><div><div>34</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>35</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>1</span><span>; i </span><span>&lt;=</span><span> m; i</span><span>++</span><span>) {</span></div></div><div><div><div>36</div></div><div><span><span>    </span></span><span>cin </span><span>&gt;&gt;</span><span> ch </span><span>&gt;&gt;</span><span> l </span><span>&gt;&gt;</span><span> r;</span></div></div><div><div><div>37</div></div><div><span>    </span><span>if</span><span> (ch </span><span>==</span><span> </span><span>'Q'</span><span>)</span></div></div><div><div><div>38</div></div><div><span><span>      </span></span><span>q[</span><span>++</span><span>mq] </span><span>=</span><span> {l, r, mq, mr};</span></div></div><div><div><div>39</div></div><div><span>    </span><span>else</span></div></div><div><div><div>40</div></div><div><span><span>      </span></span><span>R[</span><span>++</span><span>mr] </span><span>=</span><span> {l, r};</span></div></div><div><div><div>41</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>42</div></div><div><span>  </span><span>sort</span><span>(q </span><span>+</span><span> </span><span>1</span><span>, q </span><span>+</span><span> </span><span>1</span><span> </span><span>+</span><span> mq);</span></div></div><div><div><div>43</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>1</span><span>, l </span><span>=</span><span> </span><span>1</span><span>, r </span><span>=</span><span> </span><span>0</span><span>, x </span><span>=</span><span> </span><span>0</span><span>; i </span><span>&lt;=</span><span> mq; i</span><span>++</span><span>) {</span></div></div><div><div><div>44</div></div><div><span>    </span><span>while</span><span> (q[i].l </span><span>&lt;</span><span> l) </span><span>add</span><span>(a[</span><span>--</span><span>l]);</span></div></div><div><div><div>45</div></div><div><span>    </span><span>while</span><span> (r </span><span>&lt;</span><span> q[i].r) </span><span>add</span><span>(a[</span><span>++</span><span>r]);</span></div></div><div><div><div>46</div></div><div><span>    </span><span>while</span><span> (l </span><span>&lt;</span><span> q[i].l) </span><span>del</span><span>(a[l</span><span>++</span><span>]);</span></div></div><div><div><div>47</div></div><div><span>    </span><span>while</span><span> (q[i].r </span><span>&lt;</span><span> r) </span><span>del</span><span>(a[r</span><span>--</span><span>]);</span></div></div><div><div><div>48</div></div><div>
</div></div><div><div><div>49</div></div><div><span><span>    </span></span><span>// 时间戳</span></div></div><div><div><div>50</div></div><div><span><span>    </span></span><span>// 要将 a[pos] 和 R[x].c 交换，而不能用其他变量代替，因为需要来回滚动</span></div></div><div><div><div>51</div></div><div><span>    </span><span>while</span><span> (x </span><span>&lt;</span><span> q[i].tim) {</span></div></div><div><div><div>52</div></div><div><span>      </span><span>++</span><span>x;</span><span>  // 先加</span></div></div><div><div><div>53</div></div><div><span>      </span><span>int</span><span> pos </span><span>=</span><span> R[x].p;</span></div></div><div><div><div>54</div></div><div><span><span>      </span></span><span>// 修改数在区间内</span></div></div><div><div><div>55</div></div><div><span>      </span><span>if</span><span> (l </span><span>&lt;=</span><span> pos </span><span>&amp;&amp;</span><span> pos </span><span>&lt;=</span><span> r) {</span></div></div><div><div><div>56</div></div><div><span>        </span><span>add</span><span>(R[x].c);</span></div></div><div><div><div>57</div></div><div><span>        </span><span>del</span><span>(a[pos]);</span></div></div><div><div><div>58</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>59</div></div><div><span>      </span><span>swap</span><span>(a[pos], R[x].c);</span></div></div><div><div><div>60</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>61</div></div><div><span>    </span><span>while</span><span> (x </span><span>&gt;</span><span> q[i].tim) {</span></div></div><div><div><div>62</div></div><div><span>      </span><span>int</span><span> pos </span><span>=</span><span> R[x].p;</span></div></div><div><div><div>63</div></div><div><span>      </span><span>if</span><span> (l </span><span>&lt;=</span><span> pos </span><span>&amp;&amp;</span><span> pos </span><span>&lt;=</span><span> r) {</span></div></div><div><div><div>64</div></div><div><span>        </span><span>add</span><span>(R[x].c);</span></div></div><div><div><div>65</div></div><div><span>        </span><span>del</span><span>(a[pos]);</span></div></div><div><div><div>66</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>67</div></div><div><span>      </span><span>swap</span><span>(a[pos], R[x].c);</span></div></div><div><div><div>68</div></div><div><span><span>      </span></span><span>x</span><span>--</span><span>;</span><span>  // 后减</span></div></div><div><div><div>69</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>70</div></div><div><span><span>    </span></span><span>ans[q[i].idx] </span><span>=</span><span> res;</span></div></div><div><div><div>71</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>72</div></div><div>
</div></div><div><div><div>73</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>1</span><span>; i </span><span>&lt;=</span><span> mq; i</span><span>++</span><span>) {</span></div></div><div><div><div>74</div></div><div><span><span>    </span></span><span>cout </span><span>&lt;&lt;</span><span> ans[i] </span><span>&lt;&lt;</span><span> endl;</span></div></div><div><div><div>75</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>76</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section></section><section><h3>回滚莫队<a href="#回滚莫队"><span>#</span></a></h3><p>当删除或增加的其中一个在操作时，不能或者不方便统计答案时，可以尝试使用回滚莫队解决。</p><p>原题链接</p><p><a href="https://www2.ioi-jp.org/camp/2014/2014-sp-tasks/2014-sp-d1.pdf" target="_blank">歴史の研究(Historical Research)</a></p><p><a href="https://www2.ioi-jp.org/camp/2014/2014-sp-tasks/index.html" target="_blank">第１３回日本情報オリンピック　春季トレーニング合宿</a></p><p>zh-CN链接</p><p><a href="https://vjudge.net/contest/603288#problem/K" target="_blank">歴史の研究</a></p><section><h4>实现过程<a href="#实现过程-1"><span>#</span></a></h4><ol>
<li>对于左右端点在同一块的暴力计算。</li>
<li>跟普通莫队一样排序，左端点换块时清空答案，把左指针设为左端点所在块的下一块的开头，把右指针设为左端点所在块的最后一位。这样右指针是单调递增的，每处理一个询问时，先记录一些信息（比如答案），再左移左指针并更新答案，然后把左指针移回左端点所在块的下一块的开头，过程中回滚除记录下的信息以外的其它影响，最后把记录下的信息恢复。如：先右移右指针，再记录 <span><span>ansans</span><span><span><span></span><span>an</span><span>s</span></span></span></span>，然后左移左指针时增加 <span><span>cntcnt</span><span><span><span></span><span>c</span><span>n</span><span>t</span></span></span></span> 并更新 <span><span>ansans</span><span><span><span></span><span>an</span><span>s</span></span></span></span>，回滚时把增加的 <span><span>cntcnt</span><span><span><span></span><span>c</span><span>n</span><span>t</span></span></span></span> 减掉，最后把 <span><span>ansans</span><span><span><span></span><span>an</span><span>s</span></span></span></span> 回滚为记录的值。</li>
</ol><p>{% folding 查看代码 By yihang_01 %}</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>int</span><span> n, m, a[N], len, cnt1[N], cnt2[N], ans[N], st[N], ed[N], id[N], b[N], p;</span><span>//cnt1 为统计数字个数的桶，cnt2 为处理暴力结果的桶，ans 为答案数组，b 为离散化后的数组，p 为离散化后的数组长度</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>struct</span><span> </span><span>query</span><span> {</span></div></div><div><div><div>4</div></div><div><span>    </span><span>int</span><span> l, r, id;</span></div></div><div><div><div>5</div></div><div><span>    </span><span>bool</span><span> </span><span>operator</span><span>&lt;</span><span>(</span><span>const</span><span> </span><span>query</span><span> </span><span>&amp;</span><span>rhs</span><span>) </span><span>const</span><span> {</span></div></div><div><div><div>6</div></div><div><span>        </span><span>if</span><span> (::id[l] </span><span>!=</span><span> ::id[rhs.l]) </span><span>return</span><span> l </span><span>&lt;</span><span> rhs.l;</span></div></div><div><div><div>7</div></div><div><span>        </span><span>return</span><span> r </span><span>&lt;</span><span> rhs.r;</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>9</div></div><div><span>} q[N];</span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span>void</span><span> </span><span>add</span><span>(</span><span>int</span><span> </span><span>x</span><span>, </span><span>int</span><span> </span><span>&amp;</span><span>tmp</span><span>) {</span></div></div><div><div><div>12</div></div><div><span>    </span><span>++</span><span>cnt1[x];</span></div></div><div><div><div>13</div></div><div><span><span>    </span></span><span>tmp </span><span>=</span><span> </span><span>max</span><span>(tmp, cnt1[x] </span><span>*</span><span> b[x]);</span></div></div><div><div><div>14</div></div><div><span>}</span></div></div><div><div><div>15</div></div><div>
</div></div><div><div><div>16</div></div><div><span>void</span><span> </span><span>del</span><span>(</span><span>int</span><span> </span><span>x</span><span>) { </span><span>--</span><span>cnt1[x]; }</span></div></div><div><div><div>17</div></div><div>
</div></div><div><div><div>18</div></div><div><span>void</span><span> </span><span>solve</span><span>() {</span></div></div><div><div><div>19</div></div><div><span><span>    </span></span><span>cin </span><span>&gt;&gt;</span><span> n </span><span>&gt;&gt;</span><span> m;</span></div></div><div><div><div>20</div></div><div><span><span>    </span></span><span>len </span><span>=</span><span> </span><span>sqrt</span><span>(n);</span></div></div><div><div><div>21</div></div><div><span>    </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>1</span><span>; i </span><span>&lt;=</span><span> n; i</span><span>++</span><span>) {</span></div></div><div><div><div>22</div></div><div><span><span>        </span></span><span>cin </span><span>&gt;&gt;</span><span> a[i];</span></div></div><div><div><div>23</div></div><div><span><span>        </span></span><span>b[i] </span><span>=</span><span> a[i];</span></div></div><div><div><div>24</div></div><div><span><span>        </span></span><span>id[i] </span><span>=</span><span> (i </span><span>-</span><span> </span><span>1</span><span>) </span><span>/</span><span> len </span><span>+</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>25</div></div><div><span><span>        </span></span><span>st[i] </span><span>=</span><span> (id[i] </span><span>-</span><span> </span><span>1</span><span>) </span><span>*</span><span> len </span><span>+</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>26</div></div><div><span><span>        </span></span><span>ed[i] </span><span>=</span><span> </span><span>min</span><span>(id[i] </span><span>*</span><span> len, n);</span></div></div><div><div><div>27</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>28</div></div><div><span>    </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>1</span><span>; i </span><span>&lt;=</span><span> m; i</span><span>++</span><span>) {</span></div></div><div><div><div>29</div></div><div><span><span>        </span></span><span>cin </span><span>&gt;&gt;</span><span> q[i].l </span><span>&gt;&gt;</span><span> q[i].r;</span></div></div><div><div><div>30</div></div><div><span><span>        </span></span><span>q[i].id </span><span>=</span><span> i;</span></div></div><div><div><div>31</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>32</div></div><div><span>    </span><span>sort</span><span>(q </span><span>+</span><span> </span><span>1</span><span>, q </span><span>+</span><span> m </span><span>+</span><span> </span><span>1</span><span>);</span></div></div><div><div><div>33</div></div><div>
</div></div><div><div><div>34</div></div><div><span><span>    </span></span><span>// 离散化</span></div></div><div><div><div>35</div></div><div><span>    </span><span>sort</span><span>(b </span><span>+</span><span> </span><span>1</span><span>, b </span><span>+</span><span> n </span><span>+</span><span> </span><span>1</span><span>);</span></div></div><div><div><div>36</div></div><div><span><span>    </span></span><span>p </span><span>=</span><span> </span><span>unique</span><span>(b </span><span>+</span><span> </span><span>1</span><span>, b </span><span>+</span><span> n </span><span>+</span><span> </span><span>1</span><span>) </span><span>-</span><span> b </span><span>-</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>37</div></div><div><span>    </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>1</span><span>; i </span><span>&lt;=</span><span> n; i</span><span>++</span><span>)</span></div></div><div><div><div>38</div></div><div><span><span>        </span></span><span>a[i] </span><span>=</span><span> </span><span>lower_bound</span><span>(b </span><span>+</span><span> </span><span>1</span><span>, b </span><span>+</span><span> p </span><span>+</span><span> </span><span>1</span><span>, a[i]) </span><span>-</span><span> b;</span><span>//a 数组现在存放的是离散化后的值</span></div></div><div><div><div>39</div></div><div><span>    </span><span>int</span><span> now </span><span>=</span><span> </span><span>0</span><span>, last </span><span>=</span><span> </span><span>0</span><span>, lstl </span><span>=</span><span> </span><span>0</span><span>, tmp </span><span>=</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>40</div></div><div><span>    </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>1</span><span>, l </span><span>=</span><span> </span><span>1</span><span>, r </span><span>=</span><span> </span><span>0</span><span>; i </span><span>&lt;=</span><span> m; i</span><span>++</span><span>) {</span></div></div><div><div><div>41</div></div><div><span>        </span><span>if</span><span> (id[q[i].l] </span><span>==</span><span> id[q[i].r]) {</span><span>  // 左右区间属于同一块则进行暴力处理答案</span></div></div><div><div><div>42</div></div><div><span>            </span><span>for</span><span> (</span><span>int</span><span> j </span><span>=</span><span> q[i].l; j </span><span>&lt;=</span><span> q[i].r; j</span><span>++</span><span>) </span><span>++</span><span>cnt2[a[j]];</span></div></div><div><div><div>43</div></div><div><span>            </span><span>for</span><span> (</span><span>int</span><span> j </span><span>=</span><span> q[i].l; j </span><span>&lt;=</span><span> q[i].r; j</span><span>++</span><span>)</span></div></div><div><div><div>44</div></div><div><span><span>                </span></span><span>ans[q[i].id] </span><span>=</span><span> </span><span>max</span><span>(ans[q[i].id], cnt2[a[j]] </span><span>*</span><span> b[a[j]]);</span></div></div><div><div><div>45</div></div><div><span>            </span><span>for</span><span> (</span><span>int</span><span> j </span><span>=</span><span> q[i].l; j </span><span>&lt;=</span><span> q[i].r; j</span><span>++</span><span>) </span><span>--</span><span>cnt2[a[j]];</span></div></div><div><div><div>46</div></div><div><span>            </span><span>continue</span><span>;</span></div></div><div><div><div>47</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>48</div></div><div><span><span>        </span></span><span>// 访问到了新的块，先把上一个块的答案清空</span></div></div><div><div><div>49</div></div><div><span>        </span><span>if</span><span> (id[q[i].l] </span><span>!=</span><span> last) {</span></div></div><div><div><div>50</div></div><div><span>            </span><span>while</span><span> (r </span><span>&gt;</span><span> ed[q[i].l]) </span><span>del</span><span>(a[r</span><span>--</span><span>]);</span><span>   // 右指针移至上一个区间的右端点</span></div></div><div><div><div>51</div></div><div><span>            </span><span>while</span><span> (l </span><span>&lt;=</span><span> ed[q[i].l]) </span><span>del</span><span>(a[l</span><span>++</span><span>]);</span><span>  // 左指针右移至下一个区间的左端点</span></div></div><div><div><div>52</div></div><div><span><span>            </span></span><span>tmp </span><span>=</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>53</div></div><div><span><span>            </span></span><span>last </span><span>=</span><span> id[q[i].l];</span></div></div><div><div><div>54</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>55</div></div><div><span><span>        </span></span><span>// 扩展右指针</span></div></div><div><div><div>56</div></div><div><span>        </span><span>while</span><span> (r </span><span>&lt;</span><span> q[i].r) </span><span>add</span><span>(a[</span><span>++</span><span>r], tmp);</span></div></div><div><div><div>57</div></div><div><span><span>        </span></span><span>lstl </span><span>=</span><span> l;</span><span>  // 准确来说 l 才是原先的左指针</span></div></div><div><div><div>58</div></div><div><span><span>        </span></span><span>now </span><span>=</span><span> tmp;</span><span>  // 非常重要</span></div></div><div><div><div>59</div></div><div><span><span>        </span></span><span>// 扩展左指针</span></div></div><div><div><div>60</div></div><div><span>        </span><span>while</span><span> (lstl </span><span>&gt;</span><span> q[i].l) </span><span>add</span><span>(a[</span><span>--</span><span>lstl], now);</span></div></div><div><div><div>61</div></div><div><span><span>        </span></span><span>ans[q[i].id] </span><span>=</span><span> now;</span></div></div><div><div><div>62</div></div><div><span><span>        </span></span><span>// 回滚左指针</span></div></div><div><div><div>63</div></div><div><span>        </span><span>while</span><span> (lstl </span><span>&lt;</span><span> l) </span><span>del</span><span>(a[lstl</span><span>++</span><span>]);</span></div></div><div><div><div>64</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>65</div></div><div><span>    </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>1</span><span>; i </span><span>&lt;=</span><span> m; i</span><span>++</span><span>) cout </span><span>&lt;&lt;</span><span> ans[i] </span><span>&lt;&lt;</span><span> </span><span>'</span><span>\n</span><span>'</span><span>;</span></div></div><div><div><div>66</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section></section><section><h3>树上莫队<a href="#树上莫队"><span>#</span></a></h3><p>莫队算法的出现正是为了解决部分数据结构相关问题而存在，当然可以上树。</p><p><a href="https://www.luogu.com.cn/problem/P2325" target="_blank">P2325 [SCOI2005] 王室联邦</a>这道题目可以说是专为树上莫队设计的题目。</p><section><h4>分块方式<a href="#分块方式"><span>#</span></a></h4><p>这里提供一种构造方式，证明略。</p><p>dfs，并创建一个栈，dfs一个点时先记录初始栈顶高度，每dfs完当前节点的一棵子树就判断栈内（相对于刚开始dfs时）新增节点的数量是否≥B，是则将栈内所有新增点分为同一块，核心点为当前dfs的点，当前节点结束dfs时将当前节点入栈，整个dfs结束后将栈内所有剩余节点归入已经分好的最后一个块。</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>void</span><span> </span><span>solve</span><span>() {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>auto</span><span> dfs </span><span>=</span><span> [</span><span>&amp;</span><span>](</span><span>auto</span><span> </span><span>&amp;&amp;</span><span>self</span><span>, </span><span>auto</span><span> </span><span>u</span><span>, </span><span>auto</span><span> </span><span>fa</span><span>) -&gt; </span><span>auto</span><span> {</span></div></div><div><div><div>3</div></div><div><span>    </span><span>int</span><span> t </span><span>=</span><span> top;</span></div></div><div><div><div>4</div></div><div><span>    </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> head[u]; i; i </span><span>=</span><span> nxt[i]) {</span></div></div><div><div><div>5</div></div><div><span>      </span><span>int</span><span> v </span><span>=</span><span> to[i];</span></div></div><div><div><div>6</div></div><div><span>      </span><span>if</span><span> (v </span><span>!=</span><span> fa) {</span></div></div><div><div><div>7</div></div><div><span>        </span><span>dfs</span><span>(v, u);</span></div></div><div><div><div>8</div></div><div><span>        </span><span>if</span><span> (top </span><span>-</span><span> t </span><span>&gt;=</span><span> B) {</span></div></div><div><div><div>9</div></div><div><span><span>          </span></span><span>key[</span><span>++</span><span>tot] </span><span>=</span><span> u;</span></div></div><div><div><div>10</div></div><div><span>          </span><span>while</span><span> (top </span><span>&gt;</span><span> t) bl[stk[top</span><span>--</span><span>]] </span><span>=</span><span> tot;</span></div></div><div><div><div>11</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>12</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>13</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>14</div></div><div><span><span>    </span></span><span>stk[</span><span>++</span><span>top] </span><span>=</span><span> u;</span></div></div><div><div><div>15</div></div><div><span><span>  </span></span><span>};</span></div></div><div><div><div>16</div></div><div><span>  </span><span>dfs</span><span>(</span><span>1</span><span>, </span><span>0</span><span>);</span></div></div><div><div><div>17</div></div><div><span>  </span><span>if</span><span> (</span><span>!</span><span>tot) </span><span>++</span><span>tot;</span></div></div><div><div><div>18</div></div><div><span><span>  </span></span><span>key[tot] </span><span>=</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>19</div></div><div><span>  </span><span>while</span><span> (top) bl[stk[top</span><span>--</span><span>]] </span><span>=</span><span> tot;</span></div></div><div><div><div>20</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>lambda函数原本是不支持递归的，但是有几种方法使其支持递归：</p><ol>
<li>
<p>传入参数 auto &amp;&amp;self 如 <code>auto dfs = [&amp;](auto &amp;&amp;self, auto u, auto fa) -&gt; auto</code> 递归时使用self递归</p>
</li>
<li>
<p>采用匿名函数 如 <code>function&lt;void(int,int)&gt; dfs = [&amp;](auto u,auto fa) -&gt; auto</code> 递归时使用原函数名递归</p>
</li>
<li>
<p>采用Deducing this特性 如 <code>auto dfs = [&amp;] self(auto u, auto fa) -&gt; auto</code> 递归时使用self名递归</p>
</li>
</ol><p>为什么lambda自身在定义时无法被调用</p><ol>
<li>
<p>匿名性：Lambda 表达式是匿名的，编译器在定义时不为其生成名称，因此无法在其内部直接引用或调用自己。</p>
</li>
<li>
<p>捕获和名称：在 lambda 定义时，虽然可以捕获外部变量，但不能直接引用自身，因为 lambda 的名字在定义时尚未确定。</p>
</li>
</ol><blockquote><p>auto dfs = … 的写法只是将一个 Lambda 表达式赋值给名为dfs的变量，并没有定义一个真正的命名函数。它本质上是一个匿名函数，只是通过auto dfs来保存该变量，而非声明了一个具名函数。</p></blockquote></section><section><h4>修改方式<a href="#修改方式"><span>#</span></a></h4><p>所谓“修改”，就是由询问 <span><span>(cu,cv)(cu, cv)</span><span><span><span></span><span>(</span><span>c</span><span>u</span><span>,</span><span></span><span>c</span><span>v</span><span>)</span></span></span></span> 更新至询问 <span><span>(tu,tv)(tu, tv)</span><span><span><span></span><span>(</span><span>t</span><span>u</span><span>,</span><span></span><span>t</span><span>v</span><span>)</span></span></span></span>。</p><p>如果把两条路径上的点全部修改，复杂度是和暴力一样的，所以需要做一些处理。</p><p><span><span>T(u,v)T(u, v)</span><span><span><span></span><span>T</span><span>(</span><span>u</span><span>,</span><span></span><span>v</span><span>)</span></span></span></span> 表示 <span><span>uu</span><span><span><span></span><span>u</span></span></span></span> 到 <span><span>vv</span><span><span><span></span><span>v</span></span></span></span> 的路径上除 <span><span>lca(u,v)lca(u, v)</span><span><span><span></span><span>l</span><span>c</span><span>a</span><span>(</span><span>u</span><span>,</span><span></span><span>v</span><span>)</span></span></span></span> 外的所有点构成的集合，<span><span>S(u,v)S(u, v)</span><span><span><span></span><span>S</span><span>(</span><span>u</span><span>,</span><span></span><span>v</span><span>)</span></span></span></span> 代表<span><span>uu</span><span><span><span></span><span>u</span></span></span></span> 到<span><span>vv</span><span><span><span></span><span>v</span></span></span></span>的路径，<span><span>xorxor</span><span><span><span></span><span>x</span><span>or</span></span></span></span>表示集合对称差（就跟异或差不多）。</p><ol>
<li>两个指针 <span><span>cu,cvcu, cv</span><span><span><span></span><span>c</span><span>u</span><span>,</span><span></span><span>c</span><span>v</span></span></span></span>（相当于序列队的 <span><span>l,rl, r</span><span><span><span></span><span>l</span><span>,</span><span></span><span>r</span></span></span></span> 两个指针），<span><span>ansans</span><span><span><span></span><span>an</span><span>s</span></span></span></span> 记录 <span><span>T(cu,cv)T(cu, cv)</span><span><span><span></span><span>T</span><span>(</span><span>c</span><span>u</span><span>,</span><span></span><span>c</span><span>v</span><span>)</span></span></span></span> 的答案，<span><span>visvis</span><span><span><span></span><span>v</span><span>i</span><span>s</span></span></span></span> 数组记录每个节点是否在 <span><span>T(cu,cv)T(cu, cv)</span><span><span><span></span><span>T</span><span>(</span><span>c</span><span>u</span><span>,</span><span></span><span>c</span><span>v</span><span>)</span></span></span></span>内。</li>
<li>由 <span><span>T(cu,cv)T(cu, cv)</span><span><span><span></span><span>T</span><span>(</span><span>c</span><span>u</span><span>,</span><span></span><span>c</span><span>v</span><span>)</span></span></span></span> 更新至 <span><span>T(tu,tv)T(tu, tv)</span><span><span><span></span><span>T</span><span>(</span><span>t</span><span>u</span><span>,</span><span></span><span>t</span><span>v</span><span>)</span></span></span></span> 时，将 <span><span>T(cu,tu)T(cu, tu)</span><span><span><span></span><span>T</span><span>(</span><span>c</span><span>u</span><span>,</span><span></span><span>t</span><span>u</span><span>)</span></span></span></span>和 <span><span>T(cv,tv)T(cv, tv)</span><span><span><span></span><span>T</span><span>(</span><span>c</span><span>v</span><span>,</span><span></span><span>t</span><span>v</span><span>)</span></span></span></span>的 <span><span>visvis</span><span><span><span></span><span>v</span><span>i</span><span>s</span></span></span></span>分别取反，并相应地更新答案。</li>
<li>将答案记录到 <span><span>outout</span><span><span><span></span><span>o</span><span>u</span><span>t</span></span></span></span> 数组（离线后用于输出那个）时对 <span><span>lca(cu,cv)lca(cu, cv)</span><span><span><span></span><span>l</span><span>c</span><span>a</span><span>(</span><span>c</span><span>u</span><span>,</span><span></span><span>c</span><span>v</span><span>)</span></span></span></span>（此时的 <span><span>cu,cvcu, cv</span><span><span><span></span><span>c</span><span>u</span><span>,</span><span></span><span>c</span><span>v</span></span></span></span> 已更新为上一步中的 <span><span>tu,tvtu, tv</span><span><span><span></span><span>t</span><span>u</span><span>,</span><span></span><span>t</span><span>v</span></span></span></span>）的 <span><span>visvis</span><span><span><span></span><span>v</span><span>i</span><span>s</span></span></span></span> 取反并更新答案，记录完再改回来（因为 <span><span>lcalca</span><span><span><span></span><span>l</span><span>c</span><span>a</span></span></span></span> 处理比较麻烦，这样搞比较方便）。</li>
</ol><p><span><span>T(cu,cv)⊕T(tu,tv) T(cu, cv) \oplus T(tu, tv)</span><span><span><span></span><span>T</span><span>(</span><span>c</span><span>u</span><span>,</span><span></span><span>c</span><span>v</span><span>)</span><span></span><span>⊕</span><span></span></span><span><span></span><span>T</span><span>(</span><span>t</span><span>u</span><span>,</span><span></span><span>t</span><span>v</span><span>)</span></span></span></span></p><p><span><span>=(S(cu,root)⊕S(cv,root))⊕(S(tu,root)⊕S(tv,root)) = (S(cu, root) \oplus S(cv, root)) \oplus (S(tu, root) \oplus S(tv, root))</span><span><span><span></span><span>=</span><span></span></span><span><span></span><span>(</span><span>S</span><span>(</span><span>c</span><span>u</span><span>,</span><span></span><span>r</span><span>oo</span><span>t</span><span>)</span><span></span><span>⊕</span><span></span></span><span><span></span><span>S</span><span>(</span><span>c</span><span>v</span><span>,</span><span></span><span>r</span><span>oo</span><span>t</span><span>))</span><span></span><span>⊕</span><span></span></span><span><span></span><span>(</span><span>S</span><span>(</span><span>t</span><span>u</span><span>,</span><span></span><span>r</span><span>oo</span><span>t</span><span>)</span><span></span><span>⊕</span><span></span></span><span><span></span><span>S</span><span>(</span><span>t</span><span>v</span><span>,</span><span></span><span>r</span><span>oo</span><span>t</span><span>))</span></span></span></span></p><p><span><span>=(S(cu,root)⊕S(tu,root))⊕(S(cv,root)⊕S(tv,root))= (S(cu, root) \oplus S(tu, root)) \oplus (S(cv, root) \oplus S(tv, root))</span><span><span><span></span><span>=</span><span></span></span><span><span></span><span>(</span><span>S</span><span>(</span><span>c</span><span>u</span><span>,</span><span></span><span>r</span><span>oo</span><span>t</span><span>)</span><span></span><span>⊕</span><span></span></span><span><span></span><span>S</span><span>(</span><span>t</span><span>u</span><span>,</span><span></span><span>r</span><span>oo</span><span>t</span><span>))</span><span></span><span>⊕</span><span></span></span><span><span></span><span>(</span><span>S</span><span>(</span><span>c</span><span>v</span><span>,</span><span></span><span>r</span><span>oo</span><span>t</span><span>)</span><span></span><span>⊕</span><span></span></span><span><span></span><span>S</span><span>(</span><span>t</span><span>v</span><span>,</span><span></span><span>r</span><span>oo</span><span>t</span><span>))</span></span></span></span></p><p><span><span>=T(cu,tu)⊕T(cv,tu)= T(cu, tu) \oplus T(cv, tu)</span><span><span><span></span><span>=</span><span></span></span><span><span></span><span>T</span><span>(</span><span>c</span><span>u</span><span>,</span><span></span><span>t</span><span>u</span><span>)</span><span></span><span>⊕</span><span></span></span><span><span></span><span>T</span><span>(</span><span>c</span><span>v</span><span>,</span><span></span><span>t</span><span>u</span><span>)</span></span></span></span></p><p>之所以要把 <span><span>T(cu,cv)⊕T(tu,tv)T(cu, cv) \oplus T(tu, tv)</span><span><span><span></span><span>T</span><span>(</span><span>c</span><span>u</span><span>,</span><span></span><span>c</span><span>v</span><span>)</span><span></span><span>⊕</span><span></span></span><span><span></span><span>T</span><span>(</span><span>t</span><span>u</span><span>,</span><span></span><span>t</span><span>v</span><span>)</span></span></span></span> 转化成 <span><span>T(cu,tu)⊕T(cv,tu)T(cu, tu) \oplus T(cv, tu)</span><span><span><span></span><span>T</span><span>(</span><span>c</span><span>u</span><span>,</span><span></span><span>t</span><span>u</span><span>)</span><span></span><span>⊕</span><span></span></span><span><span></span><span>T</span><span>(</span><span>c</span><span>v</span><span>,</span><span></span><span>t</span><span>u</span><span>)</span></span></span></span>，是因为这样的话就能通过对询问排序来保证复杂度。排序方式就是以<span><span>uu</span><span><span><span></span><span>u</span></span></span></span>所在块编号为第一关键字，<span><span>vv</span><span><span><span></span><span>v</span></span></span></span> 的编号为第二关键字排序。如果结合了带修莫队，就还要以时间为第三关键字。</p></section><section><h4>时间复杂度<a href="#时间复杂度-1"><span>#</span></a></h4><p>不带修：<span><span>O(nm)O(n\sqrt{m})</span><span><span><span></span><span>O</span><span>(</span><span>n</span><span><span><span><span><span><span></span><span><span>m</span></span></span><span><span></span><span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span>)</span></span></span></span>，带修：<span><span>O(n53)O(n^{\frac{5}{3}})</span><span><span><span></span><span>O</span><span>(</span><span><span>n</span><span><span><span><span><span><span></span><span><span><span><span></span><span><span><span><span><span><span></span><span><span><span>3</span></span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>5</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span></span></span></span></span></span></span></span></span><span>)</span></span></span></span></p><p><a href="https://uoj.ac/problem/58" target="_blank">#58. 【WC2013】糖果公园</a></p></section></section><section><h3>莫队的在线化改造<a href="#莫队的在线化改造"><span>#</span></a></h3><p><a href="https://www.luogu.com.cn/problem/P1903" target="_blank">P1903 [国家集训队] 数颜色 / 维护队列</a></p><p>上面这道题做如下修改：</p><ul>
<li>在读入每个更新操作的位置 <span><span>PP</span><span><span><span></span><span>P</span></span></span></span> 时，把 <span><span>PP</span><span><span><span></span><span>P</span></span></span></span> 用上一次 <span><span>QueryQuery</span><span><span><span></span><span>Q</span><span>u</span><span>er</span><span>y</span></span></span></span> 的答案进行异或后再得到真正的 <span><span>PP</span><span><span><span></span><span>P</span></span></span></span>。</li>
</ul><p>有了这个背景，就能更清楚的理解为什么原题可以改造为离线了，而这道修改后的题目不可以。正因为每次修改操作紧密联系于上一次查询操作，必须真的按照题目的要求“立即输出查询结果”才能进行此次修改，而原题可以假的“立即输出”。如果把这道题强行加上时间戳，如果经过排序后第 <span><span>11</span><span><span><span></span><span>1</span></span></span></span> 个修改操作是原来的第 <span><span>9999</span><span><span><span></span><span>99</span></span></span></span> 个修改，那此次操作需要用到第 <span><span>9898</span><span><span><span></span><span>98</span></span></span></span> 行的查询结果（假设输入为一行修改+一行查询），而想要得到 <span><span>9898</span><span><span><span></span><span>98</span></span></span></span> 行的查询结果，有需要用到 <span><span>9797</span><span><span><span></span><span>97</span></span></span></span> 行的修改，反复套娃，这个排序不如不排，时间复杂度直接变为 <span><span>O(n2)O(n^{2})</span><span><span><span></span><span>O</span><span>(</span><span><span>n</span><span><span><span><span><span><span></span><span><span><span>2</span></span></span></span></span></span></span></span></span><span>)</span></span></span></span>。</p><p>所以这下真的变成在线了，不能用莫队了。</p><p>但我相信大家是不会使用Spaly/Fenwick/Segt（可能是主席树）来做这道题的（以上均为口胡，没写过）。</p><p>不，这道题还可以使用莫队，我们可以强行来用莫队，进行莫队的在线化改造！</p><section><h4>普通莫队的在线化改造<a href="#普通莫队的在线化改造"><span>#</span></a></h4><section><h5>在线化改造的关键<a href="#在线化改造的关键"><span>#</span></a></h5><p>我们都知道，普通的莫队算法会先把所有查询离线排好序，然后通过“从上一个查询区间转移到下一个区间”的方式来快速计算答案。</p><p>但如果我们想把莫队算法“在线化”，就不能简单地从上一个查询区间直接转移。为应对这种情况，可以先挑选出一些特别的区间作为“特征区间”，并处理这些特征区间的答案，让这些区间成为莫队中的“上一个区间”，在线查询就能从相应的特征区间“跳转”过来。</p></section><section><h5>特征区间的要求<a href="#特征区间的要求"><span>#</span></a></h5><ol>
<li>这些特征区间的所有信息，必须能在可接受的时间复杂度内全部算出来。</li>
<li>对于任意一个真实查询，都能在合适的时间内从某个特征区间转移过来。</li>
</ol></section><section><h5>选取特征点<a href="#选取特征点"><span>#</span></a></h5><p>假设我们在序列中每隔 <span><span>dd</span><span><span><span></span><span>d</span></span></span></span> 步选一个“特征点”，并且把任意两个特征点之间的区间都做成特征区间。</p><ul>
<li>这样，预处理时的复杂度大约是 <span><span>O(n2d)O(\frac{n^{2}}{d})</span><span><span><span></span><span>O</span><span>(</span><span><span></span><span><span><span><span><span><span></span><span><span><span>d</span></span></span></span><span><span></span><span></span></span><span><span></span><span><span><span><span>n</span><span><span><span><span><span><span></span><span><span><span>2</span></span></span></span></span></span></span></span></span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span>)</span></span></span></span>。</li>
<li>当处理一个真实查询时，如果只要移动区间的左右端点分别不超过 <span><span>d2\frac{d}{2}</span><span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span><span>2</span></span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>d</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span></span></span></span> 步，那么就能从相应的特征区间转移到真实查询的区间，花费 <span><span>O(d)O(d)</span><span><span><span></span><span>O</span><span>(</span><span>d</span><span>)</span></span></span></span> 的时间。</li>
<li>可以证明 <span><span>d=nd=\sqrt{n}</span><span><span><span></span><span>d</span><span></span><span>=</span><span></span></span><span><span></span><span><span><span><span><span><span></span><span><span>n</span></span></span><span><span></span><span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span> 最优。</li>
</ul></section><section><h5>保存特征区间信息<a href="#保存特征区间信息"><span>#</span></a></h5><p>为了在后来能够复用特征区间的信息，需要记录下：</p><ul>
<li>特征区间的答案本身。</li>
<li>莫队所需的辅助信息，比如每种颜色在区间内出现的次数等（常见于莫队中统计出现频次的需求），往往是几个数组。</li>
</ul><p>直接记住所有特征区间的完整信息可能会占用过多的空间导致爆空间。绝大多数莫队所需要的数据都具有“可减”性质（例如出现次数可以通过增减来实现维护），所以我们只需要把 <span><span>[1,si][1, s_{i}]</span><span><span><span></span><span>[</span><span>1</span><span>,</span><span></span><span><span>s</span><span><span><span><span><span><span></span><span><span><span>i</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>]</span></span></span></span> 这前缀范围的信息保存起来，需要时再用前缀和或加减方法获得目标区间的数据。</p></section><section><h5>实现步骤<a href="#实现步骤"><span>#</span></a></h5><ol>
<li>先对所有特征区间做预处理，存下它们的答案和辅助信息。</li>
<li>当真正查询出现时，先找到“最近”的特征区间，再通过不超过 <span><span>d2\frac{d}{2}</span><span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span><span>2</span></span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>d</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span></span></span></span> 步的端点移动，将这个特征区间调整到查询所需的区间，并得出结果。</li>
</ol><p>这样一来，就能把原本只能离线处理的莫队，做成一个“在线化”的版本，成功实现了莫队的在线化改造。</p></section></section><section><h4>带修莫队的在线化改造<a href="#带修莫队的在线化改造"><span>#</span></a></h4><p>普通莫队的改造中提到的特征区间预处理只是个常见的小技巧，但需要在预处理阶段对所有特征区间的答案和信息进行保存，这让我们在支持修改时很麻烦。</p><p>回顾带修莫队做法：在每次查询前，先把修改操作处理到当前时间，维护相关结构后再进行查询。此时常见的数据结构（如线段树）通常是“懒标记”地处理修改，只有在真正需要的时候才更新。</p><p>本算法则类似：</p><ol>
<li>我们仍然划分特征区间，并在预处理时保存中间信息，但在执行修改时不直接更新特征区间的答案，仅更新莫队维护的核心信息（如颜色出现次数等）即可。</li>
<li>询问发生时，如果特征区间的答案早就过期（上次更新时间小于当前时间），就先更新它，再从这个特征区间转移到目标区间并得到正确答案。</li>
<li>由于莫队在维护中间信息时是最新的，所以真正计算答案时要“反向”更新，以保证最终求得的是当前正确结果。</li>
</ol><section><h5>时间复杂度分析<a href="#时间复杂度分析"><span>#</span></a></h5><ul>
<li>若 <span><span>d=n23d=n^{\frac{2}{3}}</span><span><span><span></span><span>d</span><span></span><span>=</span><span></span></span><span><span></span><span><span>n</span><span><span><span><span><span><span></span><span><span><span><span></span><span><span><span><span><span><span></span><span><span><span>3</span></span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>2</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span></span></span></span></span></span></span></span></span></span></span></span>，则选取的特征点有 <span><span>O(n13)O(n^{\frac{1}{3}})</span><span><span><span></span><span>O</span><span>(</span><span><span>n</span><span><span><span><span><span><span></span><span><span><span><span></span><span><span><span><span><span><span></span><span><span><span>3</span></span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>1</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span></span></span></span></span></span></span></span></span><span>)</span></span></span></span> 个。</li>
<li>每次修改若强行更新所有预处理区间会导致极高复杂度，所以采用“懒更新”方式，仅在需要时才刷新对应区间的答案。</li>
<li>总整体复杂度约为 <span><span>O(n53)O(n^{\frac{5}{3}})</span><span><span><span></span><span>O</span><span>(</span><span><span>n</span><span><span><span><span><span><span></span><span><span><span><span></span><span><span><span><span><span><span></span><span><span><span>3</span></span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>5</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span></span></span></span></span></span></span></span></span><span>)</span></span></span></span>，与带修莫队相当，但原则上不会达到最坏情况。</li>
</ul></section><section><h5>实现步骤简要<a href="#实现步骤简要"><span>#</span></a></h5><ol>
<li>预处理：计算并存下所有特征区间的答案 + 中间信息</li>
<li>修改：只更新莫队中间状态，不更新特征区间答案</li>
<li>查询：
<ul>
<li>若特征区间答案过期，先更新</li>
<li>再用不超过 <span><span>d2\frac{d}{2}</span><span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span><span>2</span></span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>d</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span></span></span></span> 次移动，将特征区间扩展/收缩到查询区间，输出结果</li>
</ul>
</li>
</ol></section><section><h5>时空复杂度的分析<a href="#时空复杂度的分析"><span>#</span></a></h5><p>在普通莫队基础上做在线化改造，时间复杂度保持不变，但会额外消耗 <span><span>O(n)O(n)</span><span><span><span></span><span>O</span><span>(</span><span>n</span><span>)</span></span></span></span> 的空间；在带修莫队基础上做改造，时间复杂度也与带修莫队相同，但需要额外 <span><span>O(n13)O(n^{\frac{1}{3}})</span><span><span><span></span><span>O</span><span>(</span><span><span>n</span><span><span><span><span><span><span></span><span><span><span><span></span><span><span><span><span><span><span></span><span><span><span>3</span></span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>1</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span></span></span></span></span></span></span></span></span><span>)</span></span></span></span> 的空间。尽管改造过程只需“向前修改”，常数因素仍会导致实际运行效率与原带修莫队相差不大。</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>#include</span><span> </span><span>&lt;bits/stdc++.h&gt;</span></div></div><div><div><div>2</div></div><div><span>using</span><span> </span><span>namespace</span><span> </span><span>std</span><span>;</span></div></div><div><div><div>3</div></div><div><span>const</span><span> </span><span>int</span><span> MAXN </span><span>=</span><span> </span><span>50050</span><span>;</span></div></div><div><div><div>4</div></div><div><span>const</span><span> </span><span>int</span><span> BLNB </span><span>=</span><span> </span><span>550</span><span>;</span></div></div><div><div><div>5</div></div><div><span>const</span><span> </span><span>int</span><span> COL </span><span>=</span><span> </span><span>1000050</span><span>;</span></div></div><div><div><div>6</div></div><div><span>void</span><span> </span><span>read</span><span>(</span><span>int</span><span> </span><span>&amp;</span><span>x</span><span>) {</span></div></div><div><div><div>7</div></div><div><span>  </span><span>char</span><span> ch;</span></div></div><div><div><div>8</div></div><div><span>  </span><span>while</span><span> (ch </span><span>=</span><span> </span><span>getchar</span><span>(), ch </span><span>&lt;</span><span> </span><span>'!'</span><span>);</span></div></div><div><div><div>9</div></div><div><span><span>  </span></span><span>x </span><span>=</span><span> ch </span><span>-</span><span> </span><span>48</span><span>;</span></div></div><div><div><div>10</div></div><div><span>  </span><span>while</span><span> (ch </span><span>=</span><span> </span><span>getchar</span><span>(), ch </span><span>&gt;</span><span> </span><span>'!'</span><span>) x </span><span>=</span><span> (x </span><span>&lt;&lt;</span><span> </span><span>3</span><span>) </span><span>+</span><span> (x </span><span>&lt;&lt;</span><span> </span><span>1</span><span>) </span><span>+</span><span> ch </span><span>-</span><span> </span><span>48</span><span>;</span></div></div><div><div><div>11</div></div><div><span>}</span></div></div><div><div><div>12</div></div><div><span>int</span><span> target[MAXN];</span></div></div><div><div><div>13</div></div><div><span>struct</span><span> </span><span>Change</span><span> {</span></div></div><div><div><div>14</div></div><div><span>  </span><span>int</span><span> p, col, las;</span></div></div><div><div><div>15</div></div><div><span>} change[MAXN];</span></div></div><div><div><div>16</div></div><div><span>int</span><span> nc, n, m, mp[COL], tot, D, cnt[BLNB][MAXN </span><span>*</span><span> </span><span>2</span><span>], c[MAXN], blnm, spe[BLNB];</span></div></div><div><div><div>17</div></div><div><span>int</span><span> CNT[MAXN </span><span>*</span><span> </span><span>2</span><span>], ans[BLNB][BLNB], ima, tim[BLNB][BLNB], id[MAXN];</span></div></div><div><div><div>18</div></div><div><span>// 细节：我们不能直接在cnt[][]上做更改，所以需要记录一个临时的变化量数组CNT[]</span></div></div><div><div><div>19</div></div><div><span>// 变量解释：nc表示当前时间，mp[]和tot是离散化用的，D表示特征点步长，cnt[][]是预处理的莫队信息，id[]记录下标为i的特征点是第几个特征点，spe[]用于存储所有的特征点下标，ans[][]表示特征区间的答案，tim[][]记录答案的上一次更新时间，target[]表示离位置i最近的特征点坐标。</span></div></div><div><div><div>20</div></div><div><span>inline</span><span> </span><span>int</span><span> </span><span>getc</span><span>(</span><span>int</span><span> </span><span>sl</span><span>, </span><span>int</span><span> </span><span>sr</span><span>, </span><span>int</span><span> </span><span>p</span><span>) {</span></div></div><div><div><div>21</div></div><div><span>  </span><span>if</span><span> (sl </span><span>==</span><span> </span><span>0</span><span> </span><span>&amp;&amp;</span><span> sr </span><span>==</span><span> </span><span>0</span><span>)</span></div></div><div><div><div>22</div></div><div><span>    </span><span>return</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>23</div></div><div><span>  </span><span>else</span><span> {</span></div></div><div><div><div>24</div></div><div><span>    </span><span>if</span><span> (c[sl] </span><span>==</span><span> p)</span></div></div><div><div><div>25</div></div><div><span>      </span><span>return</span><span> cnt[id[sr]][p] </span><span>-</span><span> cnt[id[sl]][p] </span><span>+</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>26</div></div><div><span>    </span><span>else</span></div></div><div><div><div>27</div></div><div><span>      </span><span>return</span><span> cnt[id[sr]][p] </span><span>-</span><span> cnt[id[sl]][p];</span></div></div><div><div><div>28</div></div><div><span><span>  </span></span><span>}</span><span>  // 细节：端点特判一下</span></div></div><div><div><div>29</div></div><div><span>}</span></div></div><div><div><div>30</div></div><div><span>// 函数作用：读取区间[sl,sr]中的莫队数组信息。</span></div></div><div><div><div>31</div></div><div><span>inline</span><span> </span><span>void</span><span> </span><span>del</span><span>(</span><span>int</span><span> </span><span>pos</span><span>, </span><span>int</span><span> </span><span>sl</span><span>, </span><span>int</span><span> </span><span>sr</span><span>) {</span></div></div><div><div><div>32</div></div><div><span>  </span><span>if</span><span> ((</span><span>--</span><span>CNT[c[pos]]) </span><span>+</span><span> </span><span>getc</span><span>(sl, sr, c[pos]) </span><span>==</span><span> </span><span>0</span><span>) </span><span>--</span><span>ima;</span></div></div><div><div><div>33</div></div><div><span>}</span></div></div><div><div><div>34</div></div><div><span>inline</span><span> </span><span>void</span><span> </span><span>add</span><span>(</span><span>int</span><span> </span><span>pos</span><span>, </span><span>int</span><span> </span><span>sl</span><span>, </span><span>int</span><span> </span><span>sr</span><span>) {</span></div></div><div><div><div>35</div></div><div><span>  </span><span>if</span><span> ((</span><span>++</span><span>CNT[c[pos]]) </span><span>+</span><span> </span><span>getc</span><span>(sl, sr, c[pos]) </span><span>==</span><span> </span><span>1</span><span>) </span><span>++</span><span>ima;</span></div></div><div><div><div>36</div></div><div><span>}</span></div></div><div><div><div>37</div></div><div><span>int</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>38</div></div><div><span>  </span><span>read</span><span>(n);</span></div></div><div><div><div>39</div></div><div><span>  </span><span>read</span><span>(m);</span></div></div><div><div><div>40</div></div><div><span><span>  </span></span><span>D </span><span>=</span><span> </span><span>pow</span><span>(n, </span><span>2.0</span><span> </span><span>/</span><span> </span><span>3</span><span>);</span><span>  // 带修莫队的块大小</span></div></div><div><div><div>41</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>1</span><span>; i </span><span>&lt;=</span><span> n; </span><span>++</span><span>i) {</span></div></div><div><div><div>42</div></div><div><span>    </span><span>read</span><span>(c[i]);</span></div></div><div><div><div>43</div></div><div><span>    </span><span>if</span><span> (</span><span>!</span><span>mp[c[i]])</span></div></div><div><div><div>44</div></div><div><span><span>      </span></span><span>c[i] </span><span>=</span><span> mp[c[i]] </span><span>=</span><span> </span><span>++</span><span>tot;</span></div></div><div><div><div>45</div></div><div><span>    </span><span>else</span></div></div><div><div><div>46</div></div><div><span><span>      </span></span><span>c[i] </span><span>=</span><span> mp[c[i]];</span></div></div><div><div><div>47</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>48</div></div><div><span>  </span><span>int</span><span> tmp </span><span>=</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>49</div></div><div><span><span>  </span></span><span>spe[blnm </span><span>=</span><span> </span><span>1</span><span>] </span><span>=</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>50</div></div><div><span><span>  </span></span><span>id[</span><span>1</span><span>] </span><span>=</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>51</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>1</span><span>; i </span><span>&lt;=</span><span> n; </span><span>++</span><span>i) {</span></div></div><div><div><div>52</div></div><div><span>    </span><span>if</span><span> (i </span><span>-</span><span> tmp </span><span>==</span><span> D) tmp </span><span>=</span><span> i, spe[</span><span>++</span><span>blnm] </span><span>=</span><span> i, id[i] </span><span>=</span><span> blnm;</span></div></div><div><div><div>53</div></div><div><span><span>    </span></span><span>target[i] </span><span>=</span><span> tmp;</span></div></div><div><div><div>54</div></div><div><span><span>  </span></span><span>}</span><span>  // 预处理特征点以及每个点对应的离它最近的特征点</span></div></div><div><div><div>55</div></div><div><span>  </span><span>int</span><span> p </span><span>=</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>56</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>1</span><span>; i </span><span>&lt;=</span><span> n; </span><span>++</span><span>i) {</span></div></div><div><div><div>57</div></div><div><span>    </span><span>++</span><span>CNT[c[i]];</span></div></div><div><div><div>58</div></div><div><span>    </span><span>if</span><span> (i </span><span>==</span><span> spe[p]) {</span></div></div><div><div><div>59</div></div><div><span>      </span><span>for</span><span> (</span><span>int</span><span> j </span><span>=</span><span> </span><span>1</span><span>; j </span><span>&lt;=</span><span> n; </span><span>++</span><span>j) cnt[p][j] </span><span>=</span><span> CNT[j];</span></div></div><div><div><div>60</div></div><div><span>      </span><span>++</span><span>p;</span></div></div><div><div><div>61</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>62</div></div><div><span><span>  </span></span><span>}</span><span>  // 预处理莫队所需信息</span></div></div><div><div><div>63</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>1</span><span>; i </span><span>&lt;=</span><span> blnm; </span><span>++</span><span>i) {</span></div></div><div><div><div>64</div></div><div><span>    </span><span>int</span><span> p </span><span>=</span><span> i </span><span>+</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>65</div></div><div><span><span>    </span></span><span>ima </span><span>=</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>66</div></div><div><span>    </span><span>memset</span><span>(CNT, </span><span>0</span><span>, sizeof CNT);</span></div></div><div><div><div>67</div></div><div><span>    </span><span>for</span><span> (</span><span>int</span><span> j </span><span>=</span><span> spe[i]; j </span><span>&lt;=</span><span> n; </span><span>++</span><span>j) {</span></div></div><div><div><div>68</div></div><div><span>      </span><span>if</span><span> ((</span><span>++</span><span>CNT[c[j]]) </span><span>==</span><span> </span><span>1</span><span>) </span><span>++</span><span>ima;</span></div></div><div><div><div>69</div></div><div><span>      </span><span>if</span><span> (j </span><span>==</span><span> spe[p]) {</span></div></div><div><div><div>70</div></div><div><span><span>        </span></span><span>ans[i][p] </span><span>=</span><span> ima;</span></div></div><div><div><div>71</div></div><div><span>        </span><span>++</span><span>p;</span></div></div><div><div><div>72</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>73</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>74</div></div><div><span><span>  </span></span><span>}</span><span>  // 预处理特征区间答案</span></div></div><div><div><div>75</div></div><div><span>  </span><span>memset</span><span>(CNT, </span><span>0</span><span>, sizeof CNT);</span></div></div><div><div><div>76</div></div><div><span>  </span><span>while</span><span> (m</span><span>--</span><span>) {</span></div></div><div><div><div>77</div></div><div><span>    </span><span>char</span><span> opt;</span></div></div><div><div><div>78</div></div><div><span>    </span><span>int</span><span> l, r;</span></div></div><div><div><div>79</div></div><div><span><span>    </span></span><span>ima </span><span>=</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>80</div></div><div><span>    </span><span>while</span><span> (opt </span><span>=</span><span> </span><span>getchar</span><span>(), opt </span><span>!=</span><span> </span><span>'Q'</span><span> </span><span>&amp;&amp;</span><span> opt </span><span>!=</span><span> </span><span>'R'</span><span>);</span></div></div><div><div><div>81</div></div><div><span>    </span><span>read</span><span>(l);</span></div></div><div><div><div>82</div></div><div><span>    </span><span>read</span><span>(r);</span></div></div><div><div><div>83</div></div><div><span>    </span><span>if</span><span> (opt </span><span>==</span><span> </span><span>'R'</span><span>) {</span></div></div><div><div><div>84</div></div><div><span><span>      </span></span><span>change[</span><span>++</span><span>nc].p </span><span>=</span><span> l;</span></div></div><div><div><div>85</div></div><div><span>      </span><span>if</span><span> (</span><span>!</span><span>mp[r])</span></div></div><div><div><div>86</div></div><div><span><span>        </span></span><span>r </span><span>=</span><span> mp[r] </span><span>=</span><span> </span><span>++</span><span>tot;</span></div></div><div><div><div>87</div></div><div><span>      </span><span>else</span></div></div><div><div><div>88</div></div><div><span><span>        </span></span><span>r </span><span>=</span><span> mp[r];</span></div></div><div><div><div>89</div></div><div><span><span>      </span></span><span>change[nc].col </span><span>=</span><span> r;</span></div></div><div><div><div>90</div></div><div><span><span>      </span></span><span>change[nc].las </span><span>=</span><span> c[l];</span></div></div><div><div><div>91</div></div><div><span>      </span><span>int</span><span> p </span><span>=</span><span> blnm;</span></div></div><div><div><div>92</div></div><div><span>      </span><span>for</span><span> (; spe[p] </span><span>&gt;=</span><span> l; </span><span>--</span><span>p)</span></div></div><div><div><div>93</div></div><div><span>        </span><span>--</span><span>cnt[p][c[l]], </span><span>++</span><span>cnt[p][r];</span><span>  // 修改中间过程的信息</span></div></div><div><div><div>94</div></div><div><span><span>      </span></span><span>c[l] </span><span>=</span><span> r;</span></div></div><div><div><div>95</div></div><div><span><span>    </span></span><span>} </span><span>else</span><span> {</span></div></div><div><div><div>96</div></div><div><span>      </span><span>int</span><span> sl </span><span>=</span><span> target[l], sr </span><span>=</span><span> target[r];</span></div></div><div><div><div>97</div></div><div><span>      </span><span>int</span><span> SL </span><span>=</span><span> sl, SR </span><span>=</span><span> sr;</span></div></div><div><div><div>98</div></div><div><span><span>      </span></span><span>// sl、sr表示所需特征区间的左右端点。</span></div></div><div><div><div>99</div></div><div><span>      </span><span>if</span><span> (sl </span><span>==</span><span> sr) {</span></div></div><div><div><div>100</div></div><div><span>        </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> l; i </span><span>&lt;=</span><span> r; </span><span>++</span><span>i)</span></div></div><div><div><div>101</div></div><div><span>          </span><span>if</span><span> (</span><span>++</span><span>CNT[c[i]] </span><span>==</span><span> </span><span>1</span><span>) </span><span>++</span><span>ima;</span></div></div><div><div><div>102</div></div><div><span>        </span><span>printf</span><span>(</span><span>"</span><span>%d\n</span><span>"</span><span>, ima);</span><span>  // 细节：区间左右端点所属特征点相同，暴力计算</span></div></div><div><div><div>103</div></div><div><span>        </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> l; i </span><span>&lt;=</span><span> r; </span><span>++</span><span>i) </span><span>--</span><span>CNT[c[i]];</span></div></div><div><div><div>104</div></div><div><span><span>        </span></span><span>// 临时数组还原</span></div></div><div><div><div>105</div></div><div><span><span>      </span></span><span>} </span><span>else</span><span> {</span></div></div><div><div><div>106</div></div><div><span>        </span><span>for</span><span> (</span><span>int</span><span> t </span><span>=</span><span> nc; t </span><span>&gt;</span><span> tim[id[sl]][id[sr]]; </span><span>--</span><span>t) {</span></div></div><div><div><div>107</div></div><div><span>          </span><span>if</span><span> (sl </span><span>&lt;=</span><span> change[t].p </span><span>&amp;&amp;</span><span> change[t].p </span><span>&lt;=</span><span> sr) {</span></div></div><div><div><div>108</div></div><div><span>            </span><span>if</span><span> (</span><span>++</span><span>CNT[change[t].las] </span><span>+</span><span> </span><span>getc</span><span>(sl, sr, change[t].las) </span><span>==</span><span> </span><span>1</span><span>)</span></div></div><div><div><div>109</div></div><div><span>              </span><span>--</span><span>ans[id[sl]][id[sr]];</span></div></div><div><div><div>110</div></div><div><span>            </span><span>if</span><span> (</span><span>--</span><span>CNT[change[t].col] </span><span>+</span><span> </span><span>getc</span><span>(sl, sr, change[t].col) </span><span>==</span><span> </span><span>0</span><span>)</span></div></div><div><div><div>111</div></div><div><span>              </span><span>++</span><span>ans[id[sl]][id[sr]];</span></div></div><div><div><div>112</div></div><div><span><span>            </span></span><span>// 反向计算答案，即把原来带修莫队的东西反过来写，详情可以参考题解里普通带修莫队的修改方式做对照。</span></div></div><div><div><div>113</div></div><div><span><span>          </span></span><span>}</span></div></div><div><div><div>114</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>115</div></div><div><span>        </span><span>for</span><span> (</span><span>int</span><span> t </span><span>=</span><span> nc; t </span><span>&gt;</span><span> tim[id[sl]][id[sr]]; </span><span>--</span><span>t)</span></div></div><div><div><div>116</div></div><div><span>          </span><span>if</span><span> (sl </span><span>&lt;=</span><span> change[t].p </span><span>&amp;&amp;</span><span> change[t].p </span><span>&lt;=</span><span> sr) {</span></div></div><div><div><div>117</div></div><div><span>            </span><span>--</span><span>CNT[change[t].las];</span></div></div><div><div><div>118</div></div><div><span>            </span><span>++</span><span>CNT[change[t].col];</span></div></div><div><div><div>119</div></div><div><span><span>          </span></span><span>}</span></div></div><div><div><div>120</div></div><div><span><span>        </span></span><span>// 临时数组还原</span></div></div><div><div><div>121</div></div><div><span><span>        </span></span><span>tim[id[sl]][id[sr]] </span><span>=</span><span> nc;</span></div></div><div><div><div>122</div></div><div><span><span>        </span></span><span>ima </span><span>=</span><span> ans[id[sl]][id[sr]];</span></div></div><div><div><div>123</div></div><div><span>        </span><span>while</span><span> (sl </span><span>&lt;</span><span> l) </span><span>del</span><span>(sl</span><span>++</span><span>, SL, SR);</span></div></div><div><div><div>124</div></div><div><span>        </span><span>while</span><span> (sl </span><span>&gt;</span><span> l) </span><span>add</span><span>(</span><span>--</span><span>sl, SL, SR);</span></div></div><div><div><div>125</div></div><div><span>        </span><span>while</span><span> (sr </span><span>&lt;</span><span> r) </span><span>add</span><span>(</span><span>++</span><span>sr, SL, SR);</span></div></div><div><div><div>126</div></div><div><span>        </span><span>while</span><span> (sr </span><span>&gt;</span><span> r) </span><span>del</span><span>(sr</span><span>--</span><span>, SL, SR);</span></div></div><div><div><div>127</div></div><div><span><span>        </span></span><span>// 可爱的四句莫队</span></div></div><div><div><div>128</div></div><div><span>        </span><span>printf</span><span>(</span><span>"</span><span>%d\n</span><span>"</span><span>, ima);</span></div></div><div><div><div>129</div></div><div><span>        </span><span>while</span><span> (SL </span><span>&lt;</span><span> l) </span><span>add</span><span>(SL</span><span>++</span><span>, </span><span>0</span><span>, </span><span>0</span><span>);</span></div></div><div><div><div>130</div></div><div><span>        </span><span>while</span><span> (SL </span><span>&gt;</span><span> l) </span><span>del</span><span>(</span><span>--</span><span>SL, </span><span>0</span><span>, </span><span>0</span><span>);</span></div></div><div><div><div>131</div></div><div><span>        </span><span>while</span><span> (SR </span><span>&lt;</span><span> r) </span><span>del</span><span>(</span><span>++</span><span>SR, </span><span>0</span><span>, </span><span>0</span><span>);</span></div></div><div><div><div>132</div></div><div><span>        </span><span>while</span><span> (SR </span><span>&gt;</span><span> r) </span><span>add</span><span>(SR</span><span>--</span><span>, </span><span>0</span><span>, </span><span>0</span><span>);</span></div></div><div><div><div>133</div></div><div><span><span>        </span></span><span>// 临时数组还原</span></div></div><div><div><div>134</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>135</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>136</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>137</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>当然上述代码解决的只是<a href="https://www.luogu.com.cn/problem/P1903" target="_blank">P1903 [国家集训队] 数颜色 / 维护队列</a>原问题，只是我们自己强制自己把这道题写成了在线莫队。</p><p>实际上一些问题是强制在线的，不像P1903一样可以转化为离线问题，这时在线莫队就显得尤为重要，可以用相对简单的在线莫队解决相对困难的问题，比如树套树，主席树，Fenwike等等需要高级DS的问题。</p><p>具体可以看一下这道题<a href="https://www.luogu.com.cn/problem/U540166" target="_blank">U540166 【模板】诗乃莫队</a></p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>enum</span><span> </span><span>EventType</span><span> { </span><span>QUERY</span><span>, </span><span>MODIFY</span><span> };</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>struct</span><span> </span><span>Query</span><span> {</span></div></div><div><div><div>4</div></div><div><span>  </span><span>int</span><span> l, r, t, idx;</span></div></div><div><div><div>5</div></div><div><span>};</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>struct</span><span> </span><span>Modification</span><span> {</span></div></div><div><div><div>8</div></div><div><span>  </span><span>int</span><span> pos;</span><span>    // 修改位置</span></div></div><div><div><div>9</div></div><div><span>  </span><span>int</span><span> type;</span><span>   // 1: 修改颜色, 2: 修改数字</span></div></div><div><div><div>10</div></div><div><span>  </span><span>int</span><span> prevC;</span><span>  // 若是颜色修改，原来的颜色</span></div></div><div><div><div>11</div></div><div><span>  </span><span>int</span><span> nowC;</span></div></div><div><div><div>12</div></div><div><span>  </span><span>int</span><span> prevA;</span><span>  // 若是数字修改，原来的数字</span></div></div><div><div><div>13</div></div><div><span>  </span><span>int</span><span> nowA;</span></div></div><div><div><div>14</div></div><div><span>};</span></div></div><div><div><div>15</div></div><div>
</div></div><div><div><div>16</div></div><div><span>int</span><span> n, m, k;</span></div></div><div><div><div>17</div></div><div><span>int</span><span> initA[NMAX], curA[NMAX];</span></div></div><div><div><div>18</div></div><div><span>int</span><span> initC[NMAX], curC[NMAX];</span><span>  // curC 存储离散化后的颜色</span></div></div><div><div><div>19</div></div><div><span>vector</span><span>&lt;int&gt;</span><span> compColors;</span></div></div><div><div><div>20</div></div><div>
</div></div><div><div><div>21</div></div><div><span>int</span><span> totQuery </span><span>=</span><span> </span><span>0</span><span>, totMod </span><span>=</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>22</div></div><div><span>vector</span><span>&lt;</span><span>Query</span><span>&gt;</span><span> queries;</span></div></div><div><div><div>23</div></div><div><span>vector</span><span>&lt;</span><span>Modification</span><span>&gt;</span><span> mods;</span></div></div><div><div><div>24</div></div><div>
</div></div><div><div><div>25</div></div><div><span>ll ansArr[NMAX];</span></div></div><div><div><div>26</div></div><div>
</div></div><div><div><div>27</div></div><div><span>// 优化：使用 vector 记录每个颜色（离散化后编号）的当前数字和</span></div></div><div><div><div>28</div></div><div><span>vector</span><span>&lt;</span><span>ll</span><span>&gt;</span><span> colSumArr;</span><span>  // 下标范围 [1, totColors]</span></div></div><div><div><div>29</div></div><div>
</div></div><div><div><div>30</div></div><div><span>ll curAns </span><span>=</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>31</div></div><div>
</div></div><div><div><div>32</div></div><div><span>// 快速计算 f(x) = x^k，其中 k=1,2,3</span></div></div><div><div><div>33</div></div><div><span>inline</span><span> </span><span>ll</span><span> </span><span>f</span><span>(</span><span>ll</span><span> </span><span>x</span><span>) {</span></div></div><div><div><div>34</div></div><div><span>  </span><span>if</span><span> (k </span><span>==</span><span> </span><span>1</span><span>)</span></div></div><div><div><div>35</div></div><div><span>    </span><span>return</span><span> x;</span></div></div><div><div><div>36</div></div><div><span>  </span><span>else</span><span> </span><span>if</span><span> (k </span><span>==</span><span> </span><span>2</span><span>)</span></div></div><div><div><div>37</div></div><div><span>    </span><span>return</span><span> x </span><span>*</span><span> x;</span></div></div><div><div><div>38</div></div><div><span>  </span><span>else</span></div></div><div><div><div>39</div></div><div><span>    </span><span>return</span><span> x </span><span>*</span><span> x </span><span>*</span><span> x;</span></div></div><div><div><div>40</div></div><div><span>}</span></div></div><div><div><div>41</div></div><div>
</div></div><div><div><div>42</div></div><div><span>// 内联更新函数：移除位置 pos 的贡献</span></div></div><div><div><div>43</div></div><div><span>inline</span><span> </span><span>void</span><span> </span><span>removePos</span><span>(</span><span>int</span><span> </span><span>pos</span><span>) {</span></div></div><div><div><div>44</div></div><div><span>  </span><span>int</span><span> color </span><span>=</span><span> curC[pos];</span><span>  // 范围 1..totColors</span></div></div><div><div><div>45</div></div><div><span>  </span><span>int</span><span> val </span><span>=</span><span> curA[pos];</span></div></div><div><div><div>46</div></div><div><span>  </span><span>ll</span><span> oldVal </span><span>=</span><span> colSumArr[color];</span></div></div><div><div><div>47</div></div><div><span>  </span><span>ll</span><span> newVal </span><span>=</span><span> oldVal </span><span>-</span><span> val;</span></div></div><div><div><div>48</div></div><div><span><span>  </span></span><span>curAns </span><span>-=</span><span> (</span><span>f</span><span>(oldVal) </span><span>-</span><span> </span><span>f</span><span>(newVal));</span></div></div><div><div><div>49</div></div><div><span><span>  </span></span><span>colSumArr[color] </span><span>=</span><span> newVal;</span></div></div><div><div><div>50</div></div><div><span>}</span></div></div><div><div><div>51</div></div><div>
</div></div><div><div><div>52</div></div><div><span>// 内联更新函数：添加位置 pos 的贡献</span></div></div><div><div><div>53</div></div><div><span>inline</span><span> </span><span>void</span><span> </span><span>addPos</span><span>(</span><span>int</span><span> </span><span>pos</span><span>) {</span></div></div><div><div><div>54</div></div><div><span>  </span><span>int</span><span> color </span><span>=</span><span> curC[pos];</span></div></div><div><div><div>55</div></div><div><span>  </span><span>int</span><span> val </span><span>=</span><span> curA[pos];</span></div></div><div><div><div>56</div></div><div><span>  </span><span>ll</span><span> oldVal </span><span>=</span><span> colSumArr[color];</span></div></div><div><div><div>57</div></div><div><span>  </span><span>ll</span><span> newVal </span><span>=</span><span> oldVal </span><span>+</span><span> val;</span></div></div><div><div><div>58</div></div><div><span><span>  </span></span><span>curAns </span><span>+=</span><span> (</span><span>f</span><span>(newVal) </span><span>-</span><span> </span><span>f</span><span>(oldVal));</span></div></div><div><div><div>59</div></div><div><span><span>  </span></span><span>colSumArr[color] </span><span>=</span><span> newVal;</span></div></div><div><div><div>60</div></div><div><span>}</span></div></div><div><div><div>61</div></div><div>
</div></div><div><div><div>62</div></div><div><span>// 应用修改操作 modIdx（如果 pos 在当前区间内，先 remove 后 add）</span></div></div><div><div><div>63</div></div><div><span>void</span><span> </span><span>applyModification</span><span>(</span><span>int</span><span> </span><span>modIdx</span><span>, </span><span>int</span><span> </span><span>L</span><span>, </span><span>int</span><span> </span><span>R</span><span>) {</span></div></div><div><div><div>64</div></div><div><span>  </span><span>Modification</span><span> </span><span>&amp;</span><span>mod </span><span>=</span><span> mods[modIdx];</span></div></div><div><div><div>65</div></div><div><span>  </span><span>int</span><span> pos </span><span>=</span><span> mod.pos;</span></div></div><div><div><div>66</div></div><div><span>  </span><span>if</span><span> (L </span><span>&lt;=</span><span> pos </span><span>&amp;&amp;</span><span> pos </span><span>&lt;=</span><span> R) </span><span>removePos</span><span>(pos);</span></div></div><div><div><div>67</div></div><div><span>  </span><span>if</span><span> (mod.type </span><span>==</span><span> </span><span>1</span><span>) {</span></div></div><div><div><div>68</div></div><div><span><span>    </span></span><span>// 颜色修改：curC[pos] 从 prevC 变为 nowC</span></div></div><div><div><div>69</div></div><div><span><span>    </span></span><span>curC[pos] </span><span>=</span><span> mod.nowC;</span></div></div><div><div><div>70</div></div><div><span><span>  </span></span><span>} </span><span>else</span><span> {</span></div></div><div><div><div>71</div></div><div><span><span>    </span></span><span>curA[pos] </span><span>=</span><span> mod.nowA;</span></div></div><div><div><div>72</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>73</div></div><div><span>  </span><span>if</span><span> (L </span><span>&lt;=</span><span> pos </span><span>&amp;&amp;</span><span> pos </span><span>&lt;=</span><span> R) </span><span>addPos</span><span>(pos);</span></div></div><div><div><div>74</div></div><div><span>}</span></div></div><div><div><div>75</div></div><div>
</div></div><div><div><div>76</div></div><div><span>// 撤销修改操作 modIdx</span></div></div><div><div><div>77</div></div><div><span>void</span><span> </span><span>undoModification</span><span>(</span><span>int</span><span> </span><span>modIdx</span><span>, </span><span>int</span><span> </span><span>L</span><span>, </span><span>int</span><span> </span><span>R</span><span>) {</span></div></div><div><div><div>78</div></div><div><span>  </span><span>Modification</span><span> </span><span>&amp;</span><span>mod </span><span>=</span><span> mods[modIdx];</span></div></div><div><div><div>79</div></div><div><span>  </span><span>int</span><span> pos </span><span>=</span><span> mod.pos;</span></div></div><div><div><div>80</div></div><div><span>  </span><span>if</span><span> (L </span><span>&lt;=</span><span> pos </span><span>&amp;&amp;</span><span> pos </span><span>&lt;=</span><span> R) </span><span>removePos</span><span>(pos);</span></div></div><div><div><div>81</div></div><div><span>  </span><span>if</span><span> (mod.type </span><span>==</span><span> </span><span>1</span><span>) {</span></div></div><div><div><div>82</div></div><div><span><span>    </span></span><span>curC[pos] </span><span>=</span><span> mod.prevC;</span></div></div><div><div><div>83</div></div><div><span><span>  </span></span><span>} </span><span>else</span><span> {</span></div></div><div><div><div>84</div></div><div><span><span>    </span></span><span>curA[pos] </span><span>=</span><span> mod.prevA;</span></div></div><div><div><div>85</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>86</div></div><div><span>  </span><span>if</span><span> (L </span><span>&lt;=</span><span> pos </span><span>&amp;&amp;</span><span> pos </span><span>&lt;=</span><span> R) </span><span>addPos</span><span>(pos);</span></div></div><div><div><div>87</div></div><div><span>}</span></div></div><div><div><div>88</div></div><div>
</div></div><div><div><div>89</div></div><div><span>void</span><span> </span><span>solve</span><span>() {</span></div></div><div><div><div>90</div></div><div><span><span>  </span></span><span>cin </span><span>&gt;&gt;</span><span> n </span><span>&gt;&gt;</span><span> m </span><span>&gt;&gt;</span><span> k;</span></div></div><div><div><div>91</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>1</span><span>; i </span><span>&lt;=</span><span> n; i</span><span>++</span><span>) {</span></div></div><div><div><div>92</div></div><div><span><span>    </span></span><span>cin </span><span>&gt;&gt;</span><span> initA[i];</span></div></div><div><div><div>93</div></div><div><span><span>    </span></span><span>curA[i] </span><span>=</span><span> initA[i];</span></div></div><div><div><div>94</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>95</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>1</span><span>; i </span><span>&lt;=</span><span> n; i</span><span>++</span><span>) {</span></div></div><div><div><div>96</div></div><div><span><span>    </span></span><span>cin </span><span>&gt;&gt;</span><span> initC[i];</span></div></div><div><div><div>97</div></div><div><span><span>    </span></span><span>compColors.</span><span>push_back</span><span>(initC[i]);</span></div></div><div><div><div>98</div></div><div><span><span>    </span></span><span>curC[i] </span><span>=</span><span> initC[i];</span><span>  // 后续会进行离散化</span></div></div><div><div><div>99</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>100</div></div><div>
</div></div><div><div><div>101</div></div><div><span>  </span><span>ll</span><span> lastans </span><span>=</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>102</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>0</span><span>; i </span><span>&lt;</span><span> m; i</span><span>++</span><span>) {</span></div></div><div><div><div>103</div></div><div><span>    </span><span>char</span><span> op;</span></div></div><div><div><div>104</div></div><div><span><span>    </span></span><span>cin </span><span>&gt;&gt;</span><span> op;</span></div></div><div><div><div>105</div></div><div><span>    </span><span>if</span><span> (op </span><span>==</span><span> </span><span>'Q'</span><span>) {</span></div></div><div><div><div>106</div></div><div><span>      </span><span>int</span><span> l, r;</span></div></div><div><div><div>107</div></div><div><span><span>      </span></span><span>cin </span><span>&gt;&gt;</span><span> l </span><span>&gt;&gt;</span><span> r;</span></div></div><div><div><div>108</div></div><div><span><span>      </span></span><span>l </span><span>^=</span><span> lastans;</span></div></div><div><div><div>109</div></div><div><span><span>      </span></span><span>r </span><span>^=</span><span> lastans;</span></div></div><div><div><div>110</div></div><div><span>      </span><span>if</span><span> (l </span><span>&gt;</span><span> r) </span><span>swap</span><span>(l, r);</span></div></div><div><div><div>111</div></div><div><span><span>      </span></span><span>queries.</span><span>push_back</span><span>({l, r, (</span><span>int</span><span>)totMod, (</span><span>int</span><span>)totQuery});</span></div></div><div><div><div>112</div></div><div><span><span>      </span></span><span>totQuery</span><span>++</span><span>;</span></div></div><div><div><div>113</div></div><div><span><span>    </span></span><span>} </span><span>else</span><span> </span><span>if</span><span> (op </span><span>==</span><span> </span><span>'C'</span><span>) {</span></div></div><div><div><div>114</div></div><div><span>      </span><span>int</span><span> x, y;</span></div></div><div><div><div>115</div></div><div><span><span>      </span></span><span>cin </span><span>&gt;&gt;</span><span> x </span><span>&gt;&gt;</span><span> y;</span></div></div><div><div><div>116</div></div><div><span><span>      </span></span><span>x </span><span>^=</span><span> lastans;</span></div></div><div><div><div>117</div></div><div><span><span>      </span></span><span>y </span><span>^=</span><span> lastans;</span></div></div><div><div><div>118</div></div><div><span><span>      </span></span><span>compColors.</span><span>push_back</span><span>(y);</span></div></div><div><div><div>119</div></div><div><span><span>      </span></span><span>mods.</span><span>push_back</span><span>({x, </span><span>1</span><span>, curC[x], y, </span><span>0</span><span>, </span><span>0</span><span>});</span></div></div><div><div><div>120</div></div><div><span><span>      </span></span><span>curC[x] </span><span>=</span><span> y;</span></div></div><div><div><div>121</div></div><div><span><span>      </span></span><span>totMod</span><span>++</span><span>;</span></div></div><div><div><div>122</div></div><div><span><span>    </span></span><span>} </span><span>else</span><span> {</span></div></div><div><div><div>123</div></div><div><span>      </span><span>int</span><span> x, y;</span></div></div><div><div><div>124</div></div><div><span><span>      </span></span><span>cin </span><span>&gt;&gt;</span><span> x </span><span>&gt;&gt;</span><span> y;</span></div></div><div><div><div>125</div></div><div><span><span>      </span></span><span>x </span><span>^=</span><span> lastans;</span></div></div><div><div><div>126</div></div><div><span><span>      </span></span><span>y </span><span>^=</span><span> lastans;</span></div></div><div><div><div>127</div></div><div><span><span>      </span></span><span>mods.</span><span>push_back</span><span>({x, </span><span>2</span><span>, </span><span>0</span><span>, </span><span>0</span><span>, curA[x], y});</span></div></div><div><div><div>128</div></div><div><span><span>      </span></span><span>curA[x] </span><span>=</span><span> y;</span></div></div><div><div><div>129</div></div><div><span><span>      </span></span><span>totMod</span><span>++</span><span>;</span></div></div><div><div><div>130</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>131</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>132</div></div><div>
</div></div><div><div><div>133</div></div><div><span><span>  </span></span><span>// 离散化：压缩所有颜色</span></div></div><div><div><div>134</div></div><div><span>  </span><span>sort</span><span>(compColors.</span><span>begin</span><span>(), compColors.</span><span>end</span><span>());</span></div></div><div><div><div>135</div></div><div><span><span>  </span></span><span>compColors.</span><span>erase</span><span>(</span><span>unique</span><span>(compColors.</span><span>begin</span><span>(), compColors.</span><span>end</span><span>()),</span></div></div><div><div><div>136</div></div><div><span><span>                   </span></span><span>compColors.</span><span>end</span><span>());</span></div></div><div><div><div>137</div></div><div><span>  </span><span>int</span><span> totColors </span><span>=</span><span> compColors.</span><span>size</span><span>();</span></div></div><div><div><div>138</div></div><div><span>  </span><span>auto</span><span> getColorId </span><span>=</span><span> [</span><span>&amp;</span><span>](</span><span>int</span><span> </span><span>c</span><span>) -&gt; </span><span>int</span><span> {</span></div></div><div><div><div>139</div></div><div><span>    </span><span>return</span><span> (</span><span>int</span><span>)(</span><span>lower_bound</span><span>(compColors.</span><span>begin</span><span>(), compColors.</span><span>end</span><span>(), c) </span><span>-</span></div></div><div><div><div>140</div></div><div><span><span>                 </span></span><span>compColors.</span><span>begin</span><span>()) </span><span>+</span></div></div><div><div><div>141</div></div><div><span>           </span><span>1</span><span>;</span></div></div><div><div><div>142</div></div><div><span><span>  </span></span><span>};</span></div></div><div><div><div>143</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>1</span><span>; i </span><span>&lt;=</span><span> n; i</span><span>++</span><span>) {</span></div></div><div><div><div>144</div></div><div><span><span>    </span></span><span>initC[i] </span><span>=</span><span> </span><span>getColorId</span><span>(initC[i]);</span></div></div><div><div><div>145</div></div><div><span><span>    </span></span><span>curC[i] </span><span>=</span><span> initC[i];</span></div></div><div><div><div>146</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>147</div></div><div><span>  </span><span>for</span><span> (</span><span>auto</span><span> </span><span>&amp;</span><span>mod : mods) {</span></div></div><div><div><div>148</div></div><div><span>    </span><span>if</span><span> (mod.type </span><span>==</span><span> </span><span>1</span><span>) {</span></div></div><div><div><div>149</div></div><div><span><span>      </span></span><span>mod.prevC </span><span>=</span><span> </span><span>getColorId</span><span>(mod.prevC);</span></div></div><div><div><div>150</div></div><div><span><span>      </span></span><span>mod.nowC </span><span>=</span><span> </span><span>getColorId</span><span>(mod.nowC);</span></div></div><div><div><div>151</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>152</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>153</div></div><div>
</div></div><div><div><div>154</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>1</span><span>; i </span><span>&lt;=</span><span> n; i</span><span>++</span><span>) {</span></div></div><div><div><div>155</div></div><div><span><span>    </span></span><span>curA[i] </span><span>=</span><span> initA[i];</span></div></div><div><div><div>156</div></div><div><span><span>    </span></span><span>curC[i] </span><span>=</span><span> initC[i];</span></div></div><div><div><div>157</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>158</div></div><div>
</div></div><div><div><div>159</div></div><div><span><span>  </span></span><span>// 初始化 colSumArr，所有颜色贡献初始为 0</span></div></div><div><div><div>160</div></div><div><span><span>  </span></span><span>colSumArr.</span><span>assign</span><span>(totColors </span><span>+</span><span> </span><span>1</span><span>, </span><span>0</span><span>);</span></div></div><div><div><div>161</div></div><div><span><span>  </span></span><span>curAns </span><span>=</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>162</div></div><div>
</div></div><div><div><div>163</div></div><div><span>  </span><span>int</span><span> blockSize </span><span>=</span><span> </span><span>max</span><span>((ll)</span><span>1</span><span>, (ll)</span><span>pow</span><span>(n, </span><span>2.0</span><span> </span><span>/</span><span> </span><span>3.0</span><span>));</span></div></div><div><div><div>164</div></div><div><span>  </span><span>sort</span><span>(queries.</span><span>begin</span><span>(), queries.</span><span>end</span><span>(), [</span><span>&amp;</span><span>](</span><span>const</span><span> </span><span>Query</span><span> </span><span>&amp;</span><span>A</span><span>, </span><span>const</span><span> </span><span>Query</span><span> </span><span>&amp;</span><span>B</span><span>) {</span></div></div><div><div><div>165</div></div><div><span>    </span><span>int</span><span> ablock </span><span>=</span><span> A.l </span><span>/</span><span> blockSize, bblock </span><span>=</span><span> B.l </span><span>/</span><span> blockSize;</span></div></div><div><div><div>166</div></div><div><span>    </span><span>if</span><span> (ablock </span><span>!=</span><span> bblock) </span><span>return</span><span> ablock </span><span>&lt;</span><span> bblock;</span></div></div><div><div><div>167</div></div><div><span>    </span><span>int</span><span> rblockA </span><span>=</span><span> A.r </span><span>/</span><span> blockSize, rblockB </span><span>=</span><span> B.r </span><span>/</span><span> blockSize;</span></div></div><div><div><div>168</div></div><div><span>    </span><span>if</span><span> (rblockA </span><span>!=</span><span> rblockB) </span><span>return</span><span> A.r </span><span>&lt;</span><span> B.r;</span></div></div><div><div><div>169</div></div><div><span>    </span><span>return</span><span> A.t </span><span>&lt;</span><span> B.t;</span></div></div><div><div><div>170</div></div><div><span><span>  </span></span><span>});</span></div></div><div><div><div>171</div></div><div>
</div></div><div><div><div>172</div></div><div><span><span>  </span></span><span>// 恢复 curA, curC 为初始状态</span></div></div><div><div><div>173</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>1</span><span>; i </span><span>&lt;=</span><span> n; i</span><span>++</span><span>) {</span></div></div><div><div><div>174</div></div><div><span><span>    </span></span><span>curA[i] </span><span>=</span><span> initA[i];</span></div></div><div><div><div>175</div></div><div><span><span>    </span></span><span>curC[i] </span><span>=</span><span> initC[i];</span></div></div><div><div><div>176</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>177</div></div><div><span><span>  </span></span><span>// colSumArr 已经初始化为 0，curAns = 0</span></div></div><div><div><div>178</div></div><div><span>  </span><span>int</span><span> L </span><span>=</span><span> </span><span>1</span><span>, R </span><span>=</span><span> </span><span>0</span><span>, curT </span><span>=</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>179</div></div><div><span>  </span><span>for</span><span> (</span><span>auto</span><span> </span><span>&amp;</span><span>q : queries) {</span></div></div><div><div><div>180</div></div><div><span>    </span><span>while</span><span> (curT </span><span>&lt;</span><span> q.t) {</span></div></div><div><div><div>181</div></div><div><span>      </span><span>applyModification</span><span>(curT, L, R);</span></div></div><div><div><div>182</div></div><div><span><span>      </span></span><span>curT</span><span>++</span><span>;</span></div></div><div><div><div>183</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>184</div></div><div><span>    </span><span>while</span><span> (curT </span><span>&gt;</span><span> q.t) {</span></div></div><div><div><div>185</div></div><div><span><span>      </span></span><span>curT</span><span>--</span><span>;</span></div></div><div><div><div>186</div></div><div><span>      </span><span>undoModification</span><span>(curT, L, R);</span></div></div><div><div><div>187</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>188</div></div><div><span>    </span><span>while</span><span> (R </span><span>&lt;</span><span> q.r) {</span></div></div><div><div><div>189</div></div><div><span><span>      </span></span><span>R</span><span>++</span><span>;</span></div></div><div><div><div>190</div></div><div><span>      </span><span>addPos</span><span>(R);</span></div></div><div><div><div>191</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>192</div></div><div><span>    </span><span>while</span><span> (R </span><span>&gt;</span><span> q.r) {</span></div></div><div><div><div>193</div></div><div><span>      </span><span>removePos</span><span>(R);</span></div></div><div><div><div>194</div></div><div><span><span>      </span></span><span>R</span><span>--</span><span>;</span></div></div><div><div><div>195</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>196</div></div><div><span>    </span><span>while</span><span> (L </span><span>&lt;</span><span> q.l) {</span></div></div><div><div><div>197</div></div><div><span>      </span><span>removePos</span><span>(L);</span></div></div><div><div><div>198</div></div><div><span><span>      </span></span><span>L</span><span>++</span><span>;</span></div></div><div><div><div>199</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>200</div></div><div><span>    </span><span>while</span><span> (L </span><span>&gt;</span><span> q.l) {</span></div></div><div><div><div>201</div></div><div><span><span>      </span></span><span>L</span><span>--</span><span>;</span></div></div><div><div><div>202</div></div><div><span>      </span><span>addPos</span><span>(L);</span></div></div><div><div><div>203</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>204</div></div><div><span><span>    </span></span><span>ansArr[q.idx] </span><span>=</span><span> curAns;</span></div></div><div><div><div>205</div></div><div><span><span>    </span></span><span>lastans </span><span>=</span><span> curAns;</span></div></div><div><div><div>206</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>207</div></div><div>
</div></div><div><div><div>208</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>0</span><span>; i </span><span>&lt;</span><span> totQuery; i</span><span>++</span><span>) {</span></div></div><div><div><div>209</div></div><div><span><span>    </span></span><span>cout </span><span>&lt;&lt;</span><span> ansArr[i] </span><span>&lt;&lt;</span><span> endl;</span></div></div><div><div><div>210</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>211</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section></section></section><section><h3>二维莫队<a href="#二维莫队"><span>#</span></a></h3><p><a href="https://ouuan.github.io/post/%E4%BA%8C%E7%BB%B4%E8%8E%AB%E9%98%9F%E8%A7%A3%E9%A2%98%E6%8A%A5%E5%91%8A/" target="_blank">二维莫队解题报告</a></p></section><section><h3>莫队二次离线<a href="#莫队二次离线"><span>#</span></a></h3><p><a href="https://www.luogu.com.cn/article/jkcn8dfi" target="_blank">莫队二次离线</a></p></section></section></section>]]></content>
    <author><name>Zowely</name></author>
    <category term="算法竞赛" />
    <category term="算法竞赛" />
    <category term="算法教程" />
    <category term="C++" />
  </entry>
  <entry>
    <title>字节跳动 Infra 二面、三面合面面经</title>
    <link href="https://www.lansganbs.cn/posts/%E9%9D%A2%E8%AF%95%E7%9B%B8%E5%85%B3/%E5%AD%97%E8%8A%82%E8%B7%B3%E5%8A%A8infra%E4%BA%8C%E9%9D%A2%E4%B8%89%E9%9D%A2%E9%9D%A2%E7%BB%8F/" rel="alternate" type="text/html" />
    <id>https://www.lansganbs.cn/posts/%E9%9D%A2%E8%AF%95%E7%9B%B8%E5%85%B3/%E5%AD%97%E8%8A%82%E8%B7%B3%E5%8A%A8infra%E4%BA%8C%E9%9D%A2%E4%B8%89%E9%9D%A2%E9%9D%A2%E7%BB%8F/</id>
    <published>2026-09-02T00:00:00.000Z</published>
    <updated>2026-09-03T00:00:00.000Z</updated>
    <summary>字节跳动 Infra 二面、三面合面问题整理</summary>
    <content type="html"><![CDATA[This post is password protected.]]></content>
    <author><name>Zowely</name></author>
    <category term="面试相关" />
    <category term="面试" />
    <category term="分布式" />
    <category term="数据库" />
    <category term="Raft" />
    <category term="Agent" />
  </entry>
  <entry>
    <title>快手 Infra 二面面经</title>
    <link href="https://www.lansganbs.cn/posts/%E9%9D%A2%E8%AF%95%E7%9B%B8%E5%85%B3/%E5%BF%AB%E6%89%8Binfra%E4%BA%8C%E9%9D%A2%E9%9D%A2%E7%BB%8F/" rel="alternate" type="text/html" />
    <id>https://www.lansganbs.cn/posts/%E9%9D%A2%E8%AF%95%E7%9B%B8%E5%85%B3/%E5%BF%AB%E6%89%8Binfra%E4%BA%8C%E9%9D%A2%E9%9D%A2%E7%BB%8F/</id>
    <published>2026-09-02T00:00:00.000Z</published>
    <updated>2026-09-03T00:00:00.000Z</updated>
    <summary>快手 Infra 二面问题整理</summary>
    <content type="html"><![CDATA[This post is password protected.]]></content>
    <author><name>Zowely</name></author>
    <category term="面试相关" />
    <category term="面试" />
    <category term="分布式" />
    <category term="Raft" />
    <category term="Agent" />
  </entry>
  <entry>
    <title>小米 Infra 一面面经</title>
    <link href="https://www.lansganbs.cn/posts/%E9%9D%A2%E8%AF%95%E7%9B%B8%E5%85%B3/%E5%B0%8F%E7%B1%B3infra%E4%B8%80%E9%9D%A2%E9%9D%A2%E7%BB%8F/" rel="alternate" type="text/html" />
    <id>https://www.lansganbs.cn/posts/%E9%9D%A2%E8%AF%95%E7%9B%B8%E5%85%B3/%E5%B0%8F%E7%B1%B3infra%E4%B8%80%E9%9D%A2%E9%9D%A2%E7%BB%8F/</id>
    <published>2026-09-01T00:00:00.000Z</published>
    <updated>2026-09-03T00:00:00.000Z</updated>
    <summary>小米 Infra 一面问题整理</summary>
    <content type="html"><![CDATA[This post is password protected.]]></content>
    <author><name>Zowely</name></author>
    <category term="面试相关" />
    <category term="面试" />
    <category term="分布式" />
    <category term="数据库" />
    <category term="Raft" />
  </entry>
  <entry>
    <title>快手 Infra 一面面经</title>
    <link href="https://www.lansganbs.cn/posts/%E9%9D%A2%E8%AF%95%E7%9B%B8%E5%85%B3/%E5%BF%AB%E6%89%8Binfra%E4%B8%80%E9%9D%A2%E9%9D%A2%E7%BB%8F/" rel="alternate" type="text/html" />
    <id>https://www.lansganbs.cn/posts/%E9%9D%A2%E8%AF%95%E7%9B%B8%E5%85%B3/%E5%BF%AB%E6%89%8Binfra%E4%B8%80%E9%9D%A2%E9%9D%A2%E7%BB%8F/</id>
    <published>2026-08-27T00:00:00.000Z</published>
    <updated>2026-09-03T00:00:00.000Z</updated>
    <summary>快手 Infra 一面问题整理</summary>
    <content type="html"><![CDATA[This post is password protected.]]></content>
    <author><name>Zowely</name></author>
    <category term="面试相关" />
    <category term="面试" />
    <category term="分布式" />
    <category term="Raft" />
    <category term="Agent" />
  </entry>
  <entry>
    <title>腾讯 Infra 二面面经</title>
    <link href="https://www.lansganbs.cn/posts/%E9%9D%A2%E8%AF%95%E7%9B%B8%E5%85%B3/%E8%85%BE%E8%AE%AFinfra%E4%BA%8C%E9%9D%A2%E9%9D%A2%E7%BB%8F/" rel="alternate" type="text/html" />
    <id>https://www.lansganbs.cn/posts/%E9%9D%A2%E8%AF%95%E7%9B%B8%E5%85%B3/%E8%85%BE%E8%AE%AFinfra%E4%BA%8C%E9%9D%A2%E9%9D%A2%E7%BB%8F/</id>
    <published>2026-08-27T00:00:00.000Z</published>
    <updated>2026-09-03T00:00:00.000Z</updated>
    <summary>腾讯 Infra 二面问题整理</summary>
    <content type="html"><![CDATA[This post is password protected.]]></content>
    <author><name>Zowely</name></author>
    <category term="面试相关" />
    <category term="面试" />
    <category term="分布式" />
    <category term="数据库" />
    <category term="Raft" />
  </entry>
  <entry>
    <title>MiniMax Infra 一面面经</title>
    <link href="https://www.lansganbs.cn/posts/%E9%9D%A2%E8%AF%95%E7%9B%B8%E5%85%B3/minimax-infra%E4%B8%80%E9%9D%A2%E9%9D%A2%E7%BB%8F/" rel="alternate" type="text/html" />
    <id>https://www.lansganbs.cn/posts/%E9%9D%A2%E8%AF%95%E7%9B%B8%E5%85%B3/minimax-infra%E4%B8%80%E9%9D%A2%E9%9D%A2%E7%BB%8F/</id>
    <published>2026-08-18T00:00:00.000Z</published>
    <updated>2026-09-03T00:00:00.000Z</updated>
    <summary>MiniMax Infra 一面问题整理</summary>
    <content type="html"><![CDATA[This post is password protected.]]></content>
    <author><name>Zowely</name></author>
    <category term="面试相关" />
    <category term="面试" />
    <category term="分布式" />
    <category term="数据库" />
    <category term="Agent" />
  </entry>
  <entry>
    <title>字节跳动 Infra 一面面经</title>
    <link href="https://www.lansganbs.cn/posts/%E9%9D%A2%E8%AF%95%E7%9B%B8%E5%85%B3/%E5%AD%97%E8%8A%82%E8%B7%B3%E5%8A%A8infra%E4%B8%80%E9%9D%A2%E9%9D%A2%E7%BB%8F/" rel="alternate" type="text/html" />
    <id>https://www.lansganbs.cn/posts/%E9%9D%A2%E8%AF%95%E7%9B%B8%E5%85%B3/%E5%AD%97%E8%8A%82%E8%B7%B3%E5%8A%A8infra%E4%B8%80%E9%9D%A2%E9%9D%A2%E7%BB%8F/</id>
    <published>2026-08-18T00:00:00.000Z</published>
    <updated>2026-09-03T00:00:00.000Z</updated>
    <summary>字节跳动 Infra 一面问题整理</summary>
    <content type="html"><![CDATA[This post is password protected.]]></content>
    <author><name>Zowely</name></author>
    <category term="面试相关" />
    <category term="面试" />
    <category term="分布式" />
    <category term="数据库" />
    <category term="Raft" />
    <category term="Agent" />
  </entry>
  <entry>
    <title>腾讯 Infra 一面面经</title>
    <link href="https://www.lansganbs.cn/posts/%E9%9D%A2%E8%AF%95%E7%9B%B8%E5%85%B3/%E8%85%BE%E8%AE%AFinfra%E4%B8%80%E9%9D%A2%E9%9D%A2%E7%BB%8F/" rel="alternate" type="text/html" />
    <id>https://www.lansganbs.cn/posts/%E9%9D%A2%E8%AF%95%E7%9B%B8%E5%85%B3/%E8%85%BE%E8%AE%AFinfra%E4%B8%80%E9%9D%A2%E9%9D%A2%E7%BB%8F/</id>
    <published>2026-08-17T00:00:00.000Z</published>
    <updated>2026-09-03T00:00:00.000Z</updated>
    <summary>腾讯 Infra 一面问题整理</summary>
    <content type="html"><![CDATA[This post is password protected.]]></content>
    <author><name>Zowely</name></author>
    <category term="面试相关" />
    <category term="面试" />
    <category term="分布式" />
    <category term="数据库" />
    <category term="Raft" />
  </entry>
  <entry>
    <title>2026 音律联觉「昔时我见」游记</title>
    <link href="https://www.lansganbs.cn/posts/%E7%94%9F%E6%B4%BB/2026%E9%9F%B3%E5%BE%8B%E8%81%94%E8%A7%89%E6%98%94%E6%97%B6%E6%88%91%E8%A7%81%E6%B8%B8%E8%AE%B0/" rel="alternate" type="text/html" />
    <id>https://www.lansganbs.cn/posts/%E7%94%9F%E6%B4%BB/2026%E9%9F%B3%E5%BE%8B%E8%81%94%E8%A7%89%E6%98%94%E6%97%B6%E6%88%91%E8%A7%81%E6%B8%B8%E8%AE%B0/</id>
    <published>2026-06-07T00:00:00.000Z</published>
    <updated>2026-06-07T00:00:00.000Z</updated>
    <summary>或许是全网最迟的 2026 音律联觉「昔时我见」游记</summary>
    <content type="html"><![CDATA[<section><h2>前言<a href="#前言"><span>#</span></a></h2><p>先说一个暴论：没去过音律联觉的博士，终将度过一段不完整的「舟生」。</p></section>
<section><h2>3.31<a href="#331"><span>#</span></a></h2><p>前几年就想去音律联觉，但哈尔滨离上海实在太远，加上经费有限，一直没能成行。今年五一正好在上海搬砖，天时地利人和，这能不去？然而 3.31 一开大败而归，直接被黄牛肘飞。</p></section>
<section><h2>4.15<a href="#415"><span>#</span></a></h2><p>二开前夜，找了朋友帮忙抢票。我这边还在转圈，朋友那边已经甩来一张付款截图，神了。成功肘赢 60w 博士和黄牛，拿下 5.4 夜场。之后火速找到组织，还报了演出后的海底捞团建——连海底捞都是二开抢到的资格。</p></section>
<section><h2>4.30<a href="#430"><span>#</span></a></h2><p>五湖四海的博士们已经陆续抵达上海，群里各种面基返图刷屏。但社畜加班到太晚，没时间和群友们线下面基 T_T。只能在家狠狠加练《Little Wish》、damedane、黄铁/利刃、《铁花飞》……音律怎能不唱？！</p></section>
<section><h2>5.1<a href="#51"><span>#</span></a></h2><p>初中同学没抢到音律的票，但抢到了 ACF 和少前2，于是也来上海玩五天。算起来已经六年没见，落地当天就约着一起去大悦城的明日方舟快闪。</p><p>地铁上，「蟑螂」们已经开始伸触须了，一路看到不少博士拎着音律联觉的袋子。可惜我还要等到 5.4，已经一天都等不了了。</p><p>快闪逛了，饭也吃了，聊着聊着，恍惚间好像又回到了初中。我没约到 5.1 的快闪，但他帮我带了一只阿米娅的棉花娃娃，之后一路带着娃在音律联觉场内场外到处拍。从大悦城逛到南京路，从游戏聊到生活，从学习聊到未来，在南京路还狠狠消费了一波。5.1 的人实在太多，百联 ZX 倒是比想象中空一些。逛到很晚要赶地铁回家，才发现很多地铁口已经封了，从南京路一路找到豫园附近才坐上。</p><p>顺带一提，百联 ZX 还是太商务了，大悦城才是生活。应该在大悦城多买点的，奈何人实在太多。不用坐电梯、一路逛到顶层的设计确实好，但承载力太有限，每周末都挤爆，更别说五一，体验说不上多好，但总归是二次元圣地之一。</p></section>
<section><h2>5.2～5.3<a href="#5253"><span>#</span></a></h2><p>依旧大悦城。</p></section>
<section><h2>5.4<a href="#54"><span>#</span></a></h2><p>终于盼到 5.4。</p><p>中午吃完饭就早早去了世博园，正好撞上日间场散场，博士们全堆在世博园和次圆圆交换无料。本来只准备了 40 份无料，结果半小时全发光了，明年直接准备 400 份。本以为自己会社恐到不敢交换，但蟑螂们聚在一起，到处伸触须，还有偷偷塞的。几天后突然在包里翻到不知名博士送的无料，感动。</p><p>世博园顶层还有音律联觉的立牌，带着娃直接开始拍拍拍。</p><p><img src="https://www.lansganbs.cn/images/article/%E7%94%9F%E6%B4%BB/2026%E9%9F%B3%E5%BE%8B%E8%81%94%E8%A7%89%E6%98%94%E6%97%B6%E6%88%91%E8%A7%81%E6%B8%B8%E8%AE%B0/ambience-01.jpeg" alt="" /></p><p><img src="https://www.lansganbs.cn/images/article/%E7%94%9F%E6%B4%BB/2026%E9%9F%B3%E5%BE%8B%E8%81%94%E8%A7%89%E6%98%94%E6%97%B6%E6%88%91%E8%A7%81%E6%B8%B8%E8%AE%B0/ambience-02.jpeg" alt="" /></p><p><img src="https://www.lansganbs.cn/images/article/%E7%94%9F%E6%B4%BB/2026%E9%9F%B3%E5%BE%8B%E8%81%94%E8%A7%89%E6%98%94%E6%97%B6%E6%88%91%E8%A7%81%E6%B8%B8%E8%AE%B0/ambience-03.jpeg" alt="" /></p><p><img src="https://www.lansganbs.cn/images/article/%E7%94%9F%E6%B4%BB/2026%E9%9F%B3%E5%BE%8B%E8%81%94%E8%A7%89%E6%98%94%E6%97%B6%E6%88%91%E8%A7%81%E6%B8%B8%E8%AE%B0/ambience-04.jpeg" alt="" /></p><p>下午甚至还有「场外音律联觉」，<a href="https://www.bilibili.com/video/BV1A8RBBUEx1/?spm_id_from=333.337.search-card.all.click&amp;vd_source=60c62217f407501a120565605e2b96a2" target="_blank">点击收看不断追加新干员的场外音律联觉一小时</a>。很幸运抢到前排，几乎听完了全程，弥补了正场没有《铁花飞》的遗憾。老师们还演奏了《鸟之诗》、《Anytime Anywhere》，甚至有打灰歌。我宣布打灰歌已取代 damedane，成为新的难忘今宵。一直演到夜场检票。</p><p>这个角度拍中华艺术宫很好看。</p><p><img src="https://www.lansganbs.cn/images/article/%E7%94%9F%E6%B4%BB/2026%E9%9F%B3%E5%BE%8B%E8%81%94%E8%A7%89%E6%98%94%E6%97%B6%E6%88%91%E8%A7%81%E6%B8%B8%E8%AE%B0/ambience-05.jpeg" alt="" /></p><p>在梅奔门口拍了好多照片，检票后登上台阶，刚好能看到黄浦江和绝美的夕阳。领完伴手礼入座，发现旁边的女博比我还早到。看我掏出棉花娃娃合影，她也掏出两个娃，直接给我们三个娃一起合了张照。加了好友，她还塞了无料给我，但我已经发光了 T_T。老师们塞给我无料，我却一份掏不出来，愧疚。</p><p><img src="https://www.lansganbs.cn/images/article/%E7%94%9F%E6%B4%BB/2026%E9%9F%B3%E5%BE%8B%E8%81%94%E8%A7%89%E6%98%94%E6%97%B6%E6%88%91%E8%A7%81%E6%B8%B8%E8%AE%B0/ambience-06.jpeg" alt="" /></p><p><img src="https://www.lansganbs.cn/images/article/%E7%94%9F%E6%B4%BB/2026%E9%9F%B3%E5%BE%8B%E8%81%94%E8%A7%89%E6%98%94%E6%97%B6%E6%88%91%E8%A7%81%E6%B8%B8%E8%AE%B0/ambience-07.jpeg" alt="" /></p><p>梅奔真的太爽，入场那一刻，世界仿佛聚焦于你。听到了最爱的净罪合约曲，还有赫德雷 EP，今年安可曲也完全意想不到，神了。mili 老师，神！安可曲开幕还有塔露拉独享大屏，难道说？！</p><p>音律联觉就像给了每位博士拉特兰人的通感——我们知道什么时候该哭，什么时候该笑，什么时候该一起合唱、一起喊口号。到现在还能回忆起「青草城」时大家齐喊 ALL IN，一起喊「画了不卖，心胸狭隘」，一起喊「鹰角网络是杀人凶手！」，大概永远也忘不了全场大合唱 “Yes I’m on fire” 的那个 5.4 夜晚。今年的净罪和利刃唱得太爽，音律的选曲太顶，塞壬的歌也太无敌。</p><p>两个小时的音律完全意犹未尽。散场时本来没太大感觉，但走出梅奔内场，每个工作人员都在说「博士们明年再见」，眼睛突然就尿尿了。曾经在网上看到音律的散场大道，没想到今天自己也走在上面。离场前往海底捞接驳车的路上，还看到了痛车巡游。白天进场，晚上出场，有一种恍惚感。</p><p>海底捞虽然也是二开，但运气好拼到了大桌，入场非常早。和老师们吃得开心，又收获了一大堆无料。海底捞照例有《Little Wish》和打灰歌环节，爽唱。一直吃到第二天凌晨，还跟老师们拍了大合照。</p><p><img src="https://www.lansganbs.cn/images/article/%E7%94%9F%E6%B4%BB/2026%E9%9F%B3%E5%BE%8B%E8%81%94%E8%A7%89%E6%98%94%E6%97%B6%E6%88%91%E8%A7%81%E6%B8%B8%E8%AE%B0/ambience-09.jpeg" alt="" /></p><p>5.4 的无料总结！</p><p><img src="https://www.lansganbs.cn/images/article/%E7%94%9F%E6%B4%BB/2026%E9%9F%B3%E5%BE%8B%E8%81%94%E8%A7%89%E6%98%94%E6%97%B6%E6%88%91%E8%A7%81%E6%B8%B8%E8%AE%B0/ambience-08.jpeg" alt="" /></p></section>
<section><h2>5.5<a href="#55"><span>#</span></a></h2><p>依旧大悦城，约了 5.5 的快闪，又消费了一波。</p><p><img src="https://www.lansganbs.cn/images/article/%E7%94%9F%E6%B4%BB/2026%E9%9F%B3%E5%BE%8B%E8%81%94%E8%A7%89%E6%98%94%E6%97%B6%E6%88%91%E8%A7%81%E6%B8%B8%E8%AE%B0/scene-stage-01.jpeg" alt="" /></p><p><img src="https://www.lansganbs.cn/images/article/%E7%94%9F%E6%B4%BB/2026%E9%9F%B3%E5%BE%8B%E8%81%94%E8%A7%89%E6%98%94%E6%97%B6%E6%88%91%E8%A7%81%E6%B8%B8%E8%AE%B0/scene-stage-02.jpeg" alt="" /></p><p><img src="https://www.lansganbs.cn/images/article/%E7%94%9F%E6%B4%BB/2026%E9%9F%B3%E5%BE%8B%E8%81%94%E8%A7%89%E6%98%94%E6%97%B6%E6%88%91%E8%A7%81%E6%B8%B8%E8%AE%B0/scene-stage-03.jpeg" alt="" /></p></section>
<section><h2>总结<a href="#总结"><span>#</span></a></h2><p>大家因为相同的喜好，聚在相同的地方，怀着相同的心意。这一刻，我不再觉得自己是背着亚克力挂件、毛茸茸挂件的怪人——满天星光在这里汇流，看着大家的动物朋友们聚在一起，有一种难以严肃的幸福。或许某种程度上，这才是一款游戏的社交属性该有的样子。</p><p>天南地北的博士们，感谢你们，让我在这一刻有了种莫名的归属感。很多感动只有现场才能感受得到，音律联觉场内与场外的氛围，是任何录播都无法比拟的。</p><p>音律联觉太美妙了，美妙得像一场不愿醒来的梦。一座魔都，一席盛宴，一场幻梦。</p><p>今天已经是 6.7，但还是没能从 5.4 夜的那场梦里醒来。</p><p>重铸未来，方舟启航。</p><p>博士们，我们明日见，「See you soon」。</p></section>]]></content>
    <author><name>Zowely</name></author>
    <category term="生活" />
    <category term="生活" />
    <category term="音律联觉" />
    <category term="明日方舟" />
    <category term="游记" />
  </entry>
  <entry>
    <title>明日方舟素材均衡规划器</title>
    <link href="https://www.lansganbs.cn/posts/%E9%A1%B9%E7%9B%AE%E5%BC%80%E5%8F%91/%E6%98%8E%E6%97%A5%E6%96%B9%E8%88%9F%E7%B4%A0%E6%9D%90%E5%9D%87%E8%A1%A1%E8%A7%84%E5%88%92%E5%99%A8/" rel="alternate" type="text/html" />
    <id>https://www.lansganbs.cn/posts/%E9%A1%B9%E7%9B%AE%E5%BC%80%E5%8F%91/%E6%98%8E%E6%97%A5%E6%96%B9%E8%88%9F%E7%B4%A0%E6%9D%90%E5%9D%87%E8%A1%A1%E8%A7%84%E5%88%92%E5%99%A8/</id>
    <published>2026-05-03T00:00:00.000Z</published>
    <updated>2026-04-30T00:00:00.000Z</updated>
    <summary>一个基于蓝材口径折算的明日方舟素材缺口分析工具，自动识别 MAA/企鹅物流库存 JSON，计算仓库失衡点</summary>
    <content type="html"><![CDATA[<a href="https://github.com/LANSGANBS/Arknights" target="_blank"><div><div><div><div></div><div>LANSGANBS</div></div><div>/</div><div>Arknights</div></div><div></div></div><div>Waiting for api.github.com...</div><div><div>00K</div><div>0K</div><div>0K</div><span>Waiting...</span></div></a>
<p>在线体验：<a href="https://ak.lansganbs.cn/" target="_blank">ak.lansganbs.cn</a></p>
<section><h2>起因<a href="#起因"><span>#</span></a></h2><p>明日方舟不缺「指定材料 → 刷哪关」的计算器，但几乎没有工具回答一个更前置的问题：<strong>我现在整体最缺什么？</strong></p><p>大多数计算器的思路是「目标驱动」——先选一个想合成的材料，再反推最优刷图路线。但对于已经有一定库存的老玩家，真正的痛点往往不是「某一种材料去哪刷」，而是「仓库整体失衡了，不知道该先补哪一块」。</p><p>这个工具换了一个切入点：把仓库里所有素材统一折算到一个可比较的口径，然后直接告诉你缺口最大的蓝材是什么。</p></section>
<section><h2>核心思路<a href="#核心思路"><span>#</span></a></h2><p>整个计算流程可以概括为三步：</p><ol>
<li><strong>折算</strong>：将所有品质的素材按权重折算为蓝色素材等价物</li>
<li><strong>排序</strong>：按缺口量从大到小排列，找出仓库最失衡的方向</li>
<li><strong>决策</strong>：玩家根据排序结果决定接下来优先刷什么</li>
</ol><p>这种「先看全局再定目标」的思路，更适合已经有一定库存积累、不想每次都先指定目标材料的玩家。</p></section>
<section><h2>功能<a href="#功能"><span>#</span></a></h2><section><h3>库存导入<a href="#库存导入"><span>#</span></a></h3><p>支持两种主流库存 JSON 格式，导入时自动识别来源：</p><ul>
<li><strong>企鹅物流规划器</strong>格式</li>
<li><strong>MAA 仓库识别</strong>导出的 <code>details.data</code> 格式</li>
</ul><p>如果上传的是 MAA 导出数据，会先过滤掉经验书、芯片、模组材料等无关项，再转换为内部统一的企鹅物流格式处理。</p></section><section><h3>权重系统<a href="#权重系统"><span>#</span></a></h3><p>内置一份默认权重数据，无需额外配置即可使用。权重决定了不同品质素材之间的折算比例——比如一个紫材等价于多少蓝材。</p><p>如果需要更新权重，可以从 <a href="https://ark.yituliu.cn/material/value" target="_blank">一图流</a> 下载最新数据，再通过页面重新导入覆盖。</p></section><section><h3>在线编辑<a href="#在线编辑"><span>#</span></a></h3><p>导入库存后，可以直接在页面中修改任意素材的数量，无需重新上传文件。方便手动调整或做假设分析。</p></section><section><h3>缺口计算<a href="#缺口计算"><span>#</span></a></h3><p>点击计算后，会输出当前最缺的蓝色素材排名，并支持展开查看完整排序。一目了然地看到仓库哪里失衡最严重。</p></section><section><h3>库存导出<a href="#库存导出"><span>#</span></a></h3><p>支持将当前库存导出为企鹅物流格式的 JSON 文件（<code>export_penguin.json</code>），方便在其他工具中继续使用。</p></section></section>
<section><h2>本地运行<a href="#本地运行"><span>#</span></a></h2><p>确保本地已安装 Node.js 18+ 和 npm，然后执行：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span># macOS / Linux</span></div></div><div><div><div>2</div></div><div><span>./start.sh</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span># Windows CMD</span></div></div><div><div><div>5</div></div><div><span>start.bat</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span># Windows PowerShell</span></div></div><div><div><div>8</div></div><div><span>.</span><span>\</span><span>start.ps1</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>首次启动会自动安装前端依赖并打开本地页面。</p><p>手动启动：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>cd</span><span> </span><span>frontend</span></div></div><div><div><div>2</div></div><div><span>npm</span><span> </span><span>ci</span></div></div><div><div><div>3</div></div><div><span>npm</span><span> </span><span>run</span><span> </span><span>start</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section>
<section><h2>部署<a href="#部署"><span>#</span></a></h2><p>仓库已包含 GitHub Actions 工作流，推送到 <code>main</code> 分支后自动部署到 GitHub Pages：</p><ol>
<li>将仓库推到 GitHub</li>
<li>进入 Settings → Pages</li>
<li>Source 选择 GitHub Actions</li>
<li>推送后自动构建部署</li>
</ol></section>
<section><h2>技术细节<a href="#技术细节"><span>#</span></a></h2><ul>
<li><strong>纯静态页面</strong>，无后端依赖，所有计算在浏览器端完成</li>
<li><strong>Liquid Glass</strong> 视觉风格</li>
<li>只关注素材，不处理芯片、经验书、龙门币等其他物品</li>
<li>所有品质素材统一折算到蓝材口径进行比较</li>
</ul></section>]]></content>
    <author><name>Zowely</name></author>
    <category term="项目开发" />
    <category term="项目开发" />
    <category term="Vue" />
    <category term="TypeScript" />
    <category term="工具" />
    <category term="明日方舟" />
  </entry>
  <entry>
    <title>小红书基础架构部面经</title>
    <link href="https://www.lansganbs.cn/posts/%E9%9D%A2%E8%AF%95%E7%9B%B8%E5%85%B3/%E5%B0%8F%E7%BA%A2%E4%B9%A6%E5%9F%BA%E7%A1%80%E6%9E%B6%E6%9E%84%E9%83%A8%E9%9D%A2%E7%BB%8F/" rel="alternate" type="text/html" />
    <id>https://www.lansganbs.cn/posts/%E9%9D%A2%E8%AF%95%E7%9B%B8%E5%85%B3/%E5%B0%8F%E7%BA%A2%E4%B9%A6%E5%9F%BA%E7%A1%80%E6%9E%B6%E6%9E%84%E9%83%A8%E9%9D%A2%E7%BB%8F/</id>
    <published>2026-03-09T00:00:00.000Z</published>
    <updated>2026-09-03T00:00:00.000Z</updated>
    <summary>小红书基础架构部-对象存储与协议开发实习面经，三次技术面 + OC，全程拷打项目</summary>
    <content type="html"><![CDATA[<section><h2>一面（03-02）<a href="#一面03-02"><span>#</span></a></h2><ol>
<li>Docker 和 Kubernetes 有什么区别？</li>
<li>请介绍 Binlog、Redo Log 和 Undo Log 的作用及写入时机。</li>
<li>Redo Log 和数据页最终都要写入磁盘，为什么还需要额外写 Redo Log？</li>
<li>给定一个正整数 <span><span>aa</span><span><span><span></span><span>a</span></span></span></span>，将其分解为若干大于 1 的正整数之积，且满足 <span><span>a1≤a2≤⋯≤ana_1 \leq a_2 \leq \cdots \leq a_n</span><span><span><span></span><span><span>a</span><span><span><span><span><span><span></span><span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>≤</span><span></span></span><span><span></span><span><span>a</span><span><span><span><span><span><span></span><span><span>2</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>≤</span><span></span></span><span><span></span><span>⋯</span><span></span><span>≤</span><span></span></span><span><span></span><span><span>a</span><span><span><span><span><span><span></span><span><span>n</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span>，其中 <span><span>a=aa=a</span><span><span><span></span><span>a</span><span></span><span>=</span><span></span></span><span><span></span><span>a</span></span></span></span> 也算一种分解，求共有多少种不同的分解方式。</li>
<li>请介绍 Multi-Raft 项目中的 Replica Group。</li>
<li>为什么采用 Multi-Raft，而不是让所有数据共用一个 Raft Group？</li>
<li>Multi-Raft 共识引擎做过哪些性能调优？</li>
<li>Raft 日志复制发生冲突时，如何快速回溯并定位匹配位置？</li>
<li>Multi-Raft 系统是否支持日志回收，具体如何实现？</li>
<li>请介绍 Raft 协议中的 Leader、Follower 和 Candidate 三种角色。</li>
<li>如果同一个 Term 中出现两个 Leader，系统应如何处理？</li>
<li>Raft 集群发生网络分区后，网络恢复时会经历什么流程？</li>
<li>Multi-Raft 系统如何实现负载均衡？</li>
</ol></section>
<section><h2>二面（03-03）<a href="#二面03-03"><span>#</span></a></h2><ol>
<li>给定平面上的四个点，如何判断它们能否组成一个正方形？</li>
<li>给定 <span><span>nn</span><span><span><span></span><span>n</span></span></span></span> 个数，如何实现一个既不会溢出、又不会产生精度误差的 <code>avg</code> 函数？</li>
<li>为什么使用增量平均公式仍然不能满足“无精度误差”的要求？</li>
<li>Raft 日志复制发生冲突时，如何快速回溯？</li>
<li>如果 Follower 缺少对应日志，Leader 应如何定位双方日志的冲突位置？</li>
<li>日志回溯过程中能否触发新的 Leader 选举？</li>
<li>对 Raft 原论文所做的协议改进，应如何论证其正确性？</li>
<li>Multi-Raft 或 Raft 协议中最核心的性质有哪些？</li>
<li>Raft 制作 Snapshot 的根本诉求是解决 CPU 使用率过高吗？</li>
<li>为什么 Raft 节点重启时需要从第一条日志开始恢复，是因为系统没有快照吗？</li>
<li>阅读或了解过哪些开源 Raft 实现？</li>
</ol></section>
<section><h2>三面（03-06）<a href="#三面03-06"><span>#</span></a></h2><ol>
<li>请介绍 Redis 的持久化机制。</li>
<li>生成 RDB 快照后，Redis 是否可以继续一直写入 AOF？</li>
<li>持续写入 AOF 是否会直接导致文件过大？</li>
<li>C/C++ 中指针和数组有什么区别？</li>
<li>TCP 端口承担什么作用？</li>
<li>TCP/UDP 端口号只有 65535 个，当进程数或连接数超过这个数量时，端口是否会不够用？</li>
<li>服务器建立大量连接后，内核如何根据收到的数据包找到对应连接，例如多个连接的目标端口都是 80 时如何区分？</li>
<li>请介绍项目的完整业务流程。</li>
<li>项目中的订单号是如何生成的？</li>
<li>项目引入 Redis 是为了解决什么问题？</li>
<li>项目引入消息队列是为了解决什么问题？</li>
<li>项目是否可以不使用 Redis，去掉后会产生什么影响？</li>
<li>项目是否可以不使用消息队列，去掉后会产生什么影响？</li>
<li>为什么消息处理需要保持局部顺序性？</li>
<li>如果张三的订单消息阻塞，同一队列中后续的李四、王五订单应如何处理？</li>
<li>用户尚未成功创建订单时为什么能够点击支付，订单创建与支付流程是否应该串行化？</li>
<li>如果购买接口中的下单操作执行失败，后续流程应如何处理？</li>
</ol></section>]]></content>
    <author><name>Zowely</name></author>
    <category term="面试相关" />
    <category term="面试" />
    <category term="Golang" />
    <category term="分布式" />
    <category term="Raft" />
    <category term="Redis" />
    <category term="K8S" />
  </entry>
  <entry>
    <title>腾讯S3线面经</title>
    <link href="https://www.lansganbs.cn/posts/%E9%9D%A2%E8%AF%95%E7%9B%B8%E5%85%B3/%E8%85%BE%E8%AE%AFs3%E7%BA%BF%E9%9D%A2%E7%BB%8F/" rel="alternate" type="text/html" />
    <id>https://www.lansganbs.cn/posts/%E9%9D%A2%E8%AF%95%E7%9B%B8%E5%85%B3/%E8%85%BE%E8%AE%AFs3%E7%BA%BF%E9%9D%A2%E7%BB%8F/</id>
    <published>2026-03-05T00:00:00.000Z</published>
    <updated>2026-09-03T00:00:00.000Z</updated>
    <summary>腾讯技术提前批 S3线 Go后端开发一面面经，二面拒了</summary>
    <content type="html"><![CDATA[<section><h2>一面（03-05）<a href="#一面03-05"><span>#</span></a></h2><ol>
<li>Go 的垃圾回收机制是怎样的？</li>
<li>Go 垃圾回收为什么采用三色标记法？</li>
<li>Go 的 <code>map</code> 底层是如何实现的？</li>
<li>Go 的 <code>map</code> 内部是否自带锁？</li>
<li>Go 的 <code>map</code> 是否支持并发访问，应如何控制并发？</li>
<li>Go 中 <code>defer</code> 关键字有什么作用？</li>
<li>注册多个 <code>defer</code> 时，它们按照什么顺序执行？</li>
<li>使用过哪些 Go 框架？</li>
<li>ProtoBuf 和 JSON 有哪些异同？</li>
<li>Raft 为什么需要使用 Snapshot？</li>
<li>RAG 适用于哪些场景？</li>
<li>哪些产品使用了 RAG？</li>
<li>如何确认一个产品确实使用了 RAG？</li>
<li>实现 Top-K 算法，并保证时间复杂度低于 <span><span>O(nlog⁡n)O(n \log n)</span><span><span><span></span><span>O</span><span>(</span><span>n</span><span></span><span>lo<span>g</span></span><span></span><span>n</span><span>)</span></span></span></span>。</li>
</ol></section>]]></content>
    <author><name>Zowely</name></author>
    <category term="面试相关" />
    <category term="面试" />
    <category term="Golang" />
    <category term="分布式" />
    <category term="Raft" />
  </entry>
  <entry>
    <title>字节抖音直播后端面经</title>
    <link href="https://www.lansganbs.cn/posts/%E9%9D%A2%E8%AF%95%E7%9B%B8%E5%85%B3/%E5%AD%97%E8%8A%82%E6%8A%96%E9%9F%B3%E7%9B%B4%E6%92%AD%E5%90%8E%E7%AB%AF%E9%9D%A2%E7%BB%8F/" rel="alternate" type="text/html" />
    <id>https://www.lansganbs.cn/posts/%E9%9D%A2%E8%AF%95%E7%9B%B8%E5%85%B3/%E5%AD%97%E8%8A%82%E6%8A%96%E9%9F%B3%E7%9B%B4%E6%92%AD%E5%90%8E%E7%AB%AF%E9%9D%A2%E7%BB%8F/</id>
    <published>2026-02-09T00:00:00.000Z</published>
    <updated>2026-09-03T00:00:00.000Z</updated>
    <summary>字节跳动抖音直播后端开发面经，终面挂</summary>
    <content type="html"><![CDATA[<section><h2>一面（01-30）<a href="#一面01-30"><span>#</span></a></h2><ol>
<li>如果自行设计一个限流器，会如何设计？</li>
<li>给定一个有序、可并行执行的任务列表，如何使用 Golang 并行执行任务，并在前序任务执行完毕后按照原顺序输出结果？</li>
<li>Raft 中日志不匹配时需要逐条回溯，时间复杂度为 <span><span>O(N)O(N)</span><span><span><span></span><span>O</span><span>(</span><span>N</span><span>)</span></span></span></span>，应该如何优化？</li>
<li>Raft 日志的批量同步应该如何实现？</li>
<li>批量同步 Raft 日志时，如何确定每一批应该发送多少条日志？</li>
<li>Raft 性能测试数据是如何测得的，又应该如何量化？</li>
<li>Raft 日志未经优化时可能遇到磁盘 I/O 瓶颈，应该如何优化？</li>
<li>Multi-Raft 节点重启后应该如何恢复？</li>
<li>Multi-Raft 系统的流量监控是如何实现的？</li>
<li>如何使用 Prometheus 进行指标埋点，需要重点监控哪些指标？</li>
</ol></section>
<section><h2>二面（02-03）<a href="#二面02-03"><span>#</span></a></h2><ol>
<li>进程和线程有什么区别？</li>
<li>哪些场景适合使用多线程，哪些场景适合使用多进程？</li>
<li>进程之间有哪些通信方式？</li>
<li>Linux 中常见的信号有哪些？</li>
<li>TCP 拥塞控制的过程是怎样的，其中涉及哪些超时器或重传机制？</li>
<li>发起一次网络请求时，会涉及哪些网络缓存？</li>
<li>给定一个数字集合 <code>digits</code> 和上限 <span><span>nn</span><span><span><span></span><span>n</span></span></span></span>，如何使用 <code>digits</code> 中的数字组成一个严格小于 <span><span>nn</span><span><span><span></span><span>n</span></span></span></span> 的最大数？</li>
<li>Multi-Raft 和单 Raft 有什么区别？</li>
<li>Multi-Raft 的数据同步是如何执行的？</li>
<li>Multi-Raft 系统如何管理不同节点上的 Leader，Leader 是否存在备份？</li>
<li>Multi-Raft Group 的 Leader 宕机后，系统应该如何处理？</li>
<li>Raft 的完整选举流程是怎样的？</li>
</ol></section>
<section><h2>终面（02-09）<a href="#终面02-09"><span>#</span></a></h2><ol>
<li>Kubernetes HPA 执行水平扩展时，各节点之间是否需要相互通信？</li>
<li>新增节点正在加载数据时，是否应该对外提供服务？</li>
<li>如何确认服务已经 Ready，是由服务主动上报，还是由 Kubernetes 主动探测？</li>
<li>如何实现 LeetCode 56「合并区间」？</li>
<li>高并发场景下，可以在数据库上游提供哪些支持来降低数据库压力？</li>
<li>消息队列应该放在系统链路的什么位置，为什么要放在那里？</li>
<li>消费服务宕机后，消息队列中的消息应该如何处理？</li>
<li>请介绍项目中的本地信息表。</li>
<li>GORM 的预加载机制是什么，如何利用它提升性能，又会付出什么代价？</li>
<li>如果系统需要支持 10,000 QPS，应该如何估算所需内存？</li>
<li>如果系统 QPS 持续增长，应该如何扩展和保护系统？</li>
</ol></section>]]></content>
    <author><name>Zowely</name></author>
    <category term="面试相关" />
    <category term="面试" />
    <category term="Golang" />
    <category term="分布式" />
    <category term="Raft" />
    <category term="K8S" />
  </entry>
  <entry>
    <title>CloudWeGo-Kitex</title>
    <link href="https://www.lansganbs.cn/posts/%E9%A1%B9%E7%9B%AE%E5%BC%80%E5%8F%91/cloudwego-kitex/" rel="alternate" type="text/html" />
    <id>https://www.lansganbs.cn/posts/%E9%A1%B9%E7%9B%AE%E5%BC%80%E5%8F%91/cloudwego-kitex/</id>
    <published>2026-02-05T00:00:00.000Z</published>
    <updated>2026-02-07T16:51:41.000Z</updated>
    <summary>深入解析 CloudWeGo-Kitex 高性能 RPC 框架的核心原理，包括基于 Netpoll 的网络层、代码生成机制、中间件执行流程、服务发现与负载均衡、超时控制与熔断策略，以及与 Hertz 的集成方案。</summary>
    <content type="html"><![CDATA[<section><h1>1. 为什么选择 Kitex<a href="#1-为什么选择-kitex"><span>#</span></a></h1><p>如果你已经熟悉 gRPC，可能会问：为什么还需要 Kitex？</p><section><h2>1.1 gRPC 的局限性<a href="#11-grpc-的局限性"><span>#</span></a></h2><p>gRPC 是 Google 开源的 RPC 框架，基于 HTTP/2 协议，但它有一些限制：</p><ul>
<li><strong>协议绑定</strong>：强制使用 HTTP/2，无法切换到其他传输协议</li>
<li><strong>网络库</strong>：基于 Go 标准库的 <code>net/http</code>，在高并发场景下性能受限</li>
<li><strong>扩展性</strong>：中间件机制相对简单，难以实现复杂的治理功能</li>
<li><strong>序列化</strong>：仅支持 ProtoBuf，无法使用 Thrift</li>
</ul></section><section><h2>1.2 Kitex 的核心优势<a href="#12-kitex-的核心优势"><span>#</span></a></h2><p>Kitex 是字节跳动开源的 RPC 框架，属于 CloudWeGo 微服务生态的一部分。</p><p><strong>底层逻辑</strong>：Kitex 不依赖 HTTP/2，而是基于自研的网络库 <strong>Netpoll</strong>，实现了：</p><ul>
<li><strong>多协议支持</strong>：支持 Thrift、ProtoBuf、gRPC 协议</li>
<li><strong>高性能网络层</strong>：基于 epoll/kqueue，零拷贝优化</li>
<li><strong>丰富的治理功能</strong>：服务发现、负载均衡、熔断、限流、超时控制</li>
<li><strong>代码生成优化</strong>：通过 <code>kitex</code> 工具生成高性能的序列化代码</li>
</ul><p><strong>与 gRPC 的核心差异</strong>：</p><ul>
<li>gRPC 是”协议优先”（HTTP/2），Kitex 是”性能优先”（可插拔协议）</li>
<li>gRPC 的中间件是拦截器（Interceptor），Kitex 的中间件是洋葱模型（Middleware）</li>
<li>Kitex 内置了完整的微服务治理功能，gRPC 需要额外集成</li>
</ul></section></section>
<section><h1>2. 核心架构<a href="#2-核心架构"><span>#</span></a></h1><section><h2>2.1 分层设计<a href="#21-分层设计"><span>#</span></a></h2><p>Kitex 采用四层架构：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>治理层 (服务发现/负载均衡/熔断)</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>↓</span></div></div><div><div><div>3</div></div><div><span>中间件层 (Middleware)</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>↓</span></div></div><div><div><div>5</div></div><div><span>协议层 (Thrift/Protobuf/gRPC)</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>↓</span></div></div><div><div><div>7</div></div><div><span>网络层 (Netpoll)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>底层逻辑</strong>：这种分层使得 Kitex 可以灵活扩展。例如：</p><ul>
<li>协议层可以切换 Thrift/ProtoBuf</li>
<li>网络层可以切换 Netpoll/Go Net</li>
<li>治理层可以插拔不同的服务发现组件（Consul/Etcd/Nacos）</li>
</ul></section><section><h2>2.2 核心数据结构<a href="#22-核心数据结构"><span>#</span></a></h2><section><h3>RPCInfo<a href="#rpcinfo"><span>#</span></a></h3><p>Kitex 的核心是 <code>rpcinfo.RPCInfo</code>，它贯穿整个请求生命周期。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>type</span><span> </span><span>RPCInfo</span><span> </span><span>interface</span><span> {</span></div></div><div><div><div>2</div></div><div><span>    </span><span>From</span><span>() </span><span>EndpointInfo</span><span>    </span><span>// 调用方信息</span></div></div><div><div><div>3</div></div><div><span>    </span><span>To</span><span>() </span><span>EndpointInfo</span><span>      </span><span>// 被调用方信息</span></div></div><div><div><div>4</div></div><div><span>    </span><span>Invocation</span><span>() </span><span>Invocation</span><span> </span><span>// 方法调用信息</span></div></div><div><div><div>5</div></div><div><span>    </span><span>Config</span><span>() </span><span>RPCConfig</span><span>     </span><span>// RPC 配置</span></div></div><div><div><div>6</div></div><div><span>    </span><span>Stats</span><span>() </span><span>RPCStats</span><span>       </span><span>// 统计信息</span></div></div><div><div><div>7</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>关键字段解析</strong>：</p><ul>
<li><code>From/To</code>：记录调用链路信息（服务名、方法名、地址）</li>
<li><code>Invocation</code>：当前调用的方法名和参数类型</li>
<li><code>Stats</code>：记录耗时、错误等统计信息</li>
</ul><p><strong>底层逻辑</strong>：<code>RPCInfo</code> 通过 <code>context.Context</code> 传递，中间件可以读取和修改它，实现链路追踪、监控等功能。</p></section></section></section>
<section><h1>3. 快速上手<a href="#3-快速上手"><span>#</span></a></h1><section><h2>3.1 代码生成<a href="#31-代码生成"><span>#</span></a></h2><p>Kitex 使用代码生成工具，基于 Thrift/ProtoBuf IDL 生成客户端和服务端代码。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span># 安装 kitex 工具</span></div></div><div><div><div>2</div></div><div><span>go</span><span> </span><span>install</span><span> </span><span>github.com/cloudwego/kitex/tool/cmd/kitex@latest</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span># 基于 Thrift IDL 生成代码</span></div></div><div><div><div>5</div></div><div><span>kitex</span><span> </span><span>-module</span><span> </span><span>example.com/project</span><span> </span><span>-service</span><span> </span><span>user-service</span><span> </span><span>user.thrift</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>生成的目录结构：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>.</span></div></div><div><div><div>2</div></div><div><span>├── handler.go           # 服务端 Handler（需要你实现业务逻辑）</span></div></div><div><div><div>3</div></div><div><span>├── main.go             # 服务端启动入口</span></div></div><div><div><div>4</div></div><div><span>├── kitex_gen/          # 生成的代码</span></div></div><div><div><div>5</div></div><div><span>│   └── user/</span></div></div><div><div><div>6</div></div><div><span>│       ├── user.go     # Thrift 结构体</span></div></div><div><div><div>7</div></div><div><span>│       ├── userservice.go  # 服务接口</span></div></div><div><div><div>8</div></div><div><span>│       └── ...</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>3.2 实现服务端<a href="#32-实现服务端"><span>#</span></a></h2><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>package</span><span> </span><span>main</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>import</span><span> (</span></div></div><div><div><div>4</div></div><div><span>    </span><span>"</span><span>context</span><span>"</span></div></div><div><div><div>5</div></div><div><span>    </span><span>"</span><span>github.com/cloudwego/kitex/server</span><span>"</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>user </span><span>"</span><span>example.com/project/kitex_gen/user/userservice</span><span>"</span></div></div><div><div><div>7</div></div><div><span>)</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>type</span><span> </span><span>UserServiceImpl</span><span> </span><span>struct</span><span>{}</span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span>func</span><span> (</span><span>s </span><span>*</span><span>UserServiceImpl</span><span>) </span><span>GetUser</span><span>(</span><span>ctx</span><span> </span><span>context</span><span>.</span><span>Context</span><span>, </span><span>req</span><span> </span><span>*</span><span>user</span><span>.</span><span>GetUserRequest</span><span>) (</span><span>*</span><span>user</span><span>.</span><span>User</span><span>, </span><span>error</span><span>) {</span></div></div><div><div><div>12</div></div><div><span>    </span><span>return</span><span> </span><span>&amp;</span><span>user</span><span>.</span><span>User</span><span>{</span></div></div><div><div><div>13</div></div><div><span><span>        </span></span><span>ID:       req.UserID,</span></div></div><div><div><div>14</div></div><div><span><span>        </span></span><span>Username: </span><span>"TestUser"</span><span>,</span></div></div><div><div><div>15</div></div><div><span><span>    </span></span><span>}, </span><span>nil</span></div></div><div><div><div>16</div></div><div><span>}</span></div></div><div><div><div>17</div></div><div>
</div></div><div><div><div>18</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>19</div></div><div><span><span>    </span></span><span>svr </span><span>:=</span><span> user.</span><span>NewServer</span><span>(</span><span>new</span><span>(</span><span>UserServiceImpl</span><span>))</span></div></div><div><div><div>20</div></div><div><span><span>    </span></span><span>err </span><span>:=</span><span> svr.</span><span>Run</span><span>()</span></div></div><div><div><div>21</div></div><div><span>    </span><span>if</span><span> err </span><span>!=</span><span> </span><span>nil</span><span> {</span></div></div><div><div><div>22</div></div><div><span>        </span><span>panic</span><span>(err)</span></div></div><div><div><div>23</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>24</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p><strong>底层逻辑</strong>：</p><ul>
<li><code>NewServer</code> 会自动配置 Netpoll 网络层、Thrift 协议层</li>
<li>默认监听 <code>:8888</code> 端口</li>
<li>支持通过 <code>server.WithServiceAddr()</code> 自定义地址</li>
</ul></section><section><h2>3.3 调用客户端<a href="#33-调用客户端"><span>#</span></a></h2><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>package</span><span> </span><span>main</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>import</span><span> (</span></div></div><div><div><div>4</div></div><div><span>    </span><span>"</span><span>context</span><span>"</span></div></div><div><div><div>5</div></div><div><span>    </span><span>"</span><span>github.com/cloudwego/kitex/client</span><span>"</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>user </span><span>"</span><span>example.com/project/kitex_gen/user/userservice</span><span>"</span></div></div><div><div><div>7</div></div><div><span>)</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>c, err </span><span>:=</span><span> user.</span><span>NewClient</span><span>(</span><span>"user-service"</span><span>, client.</span><span>WithHostPorts</span><span>(</span><span>"127.0.0.1:8888"</span><span>))</span></div></div><div><div><div>11</div></div><div><span>    </span><span>if</span><span> err </span><span>!=</span><span> </span><span>nil</span><span> {</span></div></div><div><div><div>12</div></div><div><span>        </span><span>panic</span><span>(err)</span></div></div><div><div><div>13</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>14</div></div><div>
</div></div><div><div><div>15</div></div><div><span><span>    </span></span><span>resp, err </span><span>:=</span><span> c.</span><span>GetUser</span><span>(context.</span><span>Background</span><span>(), </span><span>&amp;</span><span>user</span><span>.</span><span>GetUserRequest</span><span>{UserID: </span><span>123</span><span>})</span></div></div><div><div><div>16</div></div><div><span>    </span><span>if</span><span> err </span><span>!=</span><span> </span><span>nil</span><span> {</span></div></div><div><div><div>17</div></div><div><span>        </span><span>panic</span><span>(err)</span></div></div><div><div><div>18</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>19</div></div><div>
</div></div><div><div><div>20</div></div><div><span>    </span><span>println</span><span>(resp.Username)</span></div></div><div><div><div>21</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p><strong>底层逻辑</strong>：</p><ul>
<li><code>NewClient</code> 第一个参数是服务名（用于服务发现）</li>
<li><code>WithHostPorts</code> 直连模式，不使用服务发现</li>
<li>客户端会自动管理连接池</li>
</ul></section></section>
<section><h1>4. 中间件机制<a href="#4-中间件机制"><span>#</span></a></h1><section><h2>4.1 中间件执行流程<a href="#41-中间件执行流程"><span>#</span></a></h2><p>Kitex 的中间件采用洋葱模型，与 Hertz 类似。</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> </span><span>LogMiddleware</span><span>(</span><span>next</span><span> </span><span>endpoint</span><span>.</span><span>Endpoint</span><span>) </span><span>endpoint</span><span>.</span><span>Endpoint</span><span> {</span></div></div><div><div><div>2</div></div><div><span>    </span><span>return</span><span> </span><span>func</span><span>(</span><span>ctx</span><span> </span><span>context</span><span>.</span><span>Context</span><span>, </span><span>req</span><span>, </span><span>resp</span><span> </span><span>interface</span><span>{}) </span><span>error</span><span> {</span></div></div><div><div><div>3</div></div><div><span><span>        </span></span><span>start </span><span>:=</span><span> time.</span><span>Now</span><span>()</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span><span>        </span></span><span>err </span><span>:=</span><span> </span><span>next</span><span>(ctx, req, resp) </span><span>// 执行下一个中间件或 Handler</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span><span>        </span></span><span>ri </span><span>:=</span><span> rpcinfo.</span><span>GetRPCInfo</span><span>(ctx)</span></div></div><div><div><div>8</div></div><div><span><span>        </span></span><span>fmt.</span><span>Printf</span><span>(</span><span>"[</span><span>%s</span><span>] </span><span>%v\n</span><span>"</span><span>, ri.</span><span>To</span><span>().</span><span>Method</span><span>(), time.</span><span>Since</span><span>(start))</span></div></div><div><div><div>9</div></div><div><span>        </span><span>return</span><span> err</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>11</div></div><div><span>}</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span>svr </span><span>:=</span><span> user.</span><span>NewServer</span><span>(</span></div></div><div><div><div>14</div></div><div><span>    </span><span>new</span><span>(</span><span>UserServiceImpl</span><span>),</span></div></div><div><div><div>15</div></div><div><span><span>    </span></span><span>server.</span><span>WithMiddleware</span><span>(LogMiddleware),</span></div></div><div><div><div>16</div></div><div><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p><strong>底层逻辑</strong>：中间件通过函数闭包实现洋葱模型：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>Middleware1 → Middleware2 → Handler → Middleware2 → Middleware1</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>4.2 客户端与服务端中间件<a href="#42-客户端与服务端中间件"><span>#</span></a></h2><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>// 服务端中间件</span></div></div><div><div><div>2</div></div><div><span>svr </span><span>:=</span><span> user.</span><span>NewServer</span><span>(</span></div></div><div><div><div>3</div></div><div><span>    </span><span>new</span><span>(</span><span>UserServiceImpl</span><span>),</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>server.</span><span>WithMiddleware</span><span>(serverMiddleware),</span></div></div><div><div><div>5</div></div><div><span>)</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>// 客户端中间件</span></div></div><div><div><div>8</div></div><div><span>cli, _ </span><span>:=</span><span> user.</span><span>NewClient</span><span>(</span></div></div><div><div><div>9</div></div><div><span>    </span><span>"user-service"</span><span>,</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>client.</span><span>WithMiddleware</span><span>(clientMiddleware),</span></div></div><div><div><div>11</div></div><div><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>底层逻辑</strong>：</p><ul>
<li>服务端中间件在接收请求后执行</li>
<li>客户端中间件在发送请求前执行</li>
<li>两者可以共享相同的中间件逻辑（如链路追踪）</li>
</ul></section></section>
<section><h1>5. 服务发现与负载均衡<a href="#5-服务发现与负载均衡"><span>#</span></a></h1><section><h2>5.1 服务发现<a href="#51-服务发现"><span>#</span></a></h2><p>Kitex 支持多种服务发现组件，通过 Resolver 接口实现。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>import</span><span> (</span></div></div><div><div><div>2</div></div><div><span>    </span><span>"</span><span>github.com/cloudwego/kitex/client</span><span>"</span></div></div><div><div><div>3</div></div><div><span>    </span><span>"</span><span>github.com/kitex-contrib/registry-etcd</span><span>"</span></div></div><div><div><div>4</div></div><div><span>)</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>// 使用 Etcd 作为服务发现</span></div></div><div><div><div>7</div></div><div><span>r, _ </span><span>:=</span><span> etcd.</span><span>NewEtcdResolver</span><span>([]</span><span>string</span><span>{</span><span>"127.0.0.1:2379"</span><span>})</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>cli, _ </span><span>:=</span><span> user.</span><span>NewClient</span><span>(</span></div></div><div><div><div>10</div></div><div><span>    </span><span>"user-service"</span><span>,</span></div></div><div><div><div>11</div></div><div><span><span>    </span></span><span>client.</span><span>WithResolver</span><span>(r),</span></div></div><div><div><div>12</div></div><div><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>底层逻辑</strong>：</p><ul>
<li>客户端启动时，从 Etcd 获取服务实例列表</li>
<li>监听 Etcd 变更，动态更新实例列表</li>
<li>每次 RPC 调用时，从实例列表中选择一个（通过负载均衡策略）</li>
</ul></section><section><h2>5.2 负载均衡<a href="#52-负载均衡"><span>#</span></a></h2><p>Kitex 内置多种负载均衡策略：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>import</span><span> </span><span>"</span><span>github.com/cloudwego/kitex/pkg/loadbalance</span><span>"</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>cli, _ </span><span>:=</span><span> user.</span><span>NewClient</span><span>(</span></div></div><div><div><div>4</div></div><div><span>    </span><span>"user-service"</span><span>,</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>client.</span><span>WithLoadBalancer</span><span>(loadbalance.</span><span>NewWeightedRoundRobinBalancer</span><span>()),</span></div></div><div><div><div>6</div></div><div><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>支持的策略</strong>：</p><ul>
<li><strong>RoundRobin</strong>：轮询</li>
<li><strong>WeightedRoundRobin</strong>：加权轮询（根据实例权重分配流量）</li>
<li><strong>Random</strong>：随机选择</li>
<li><strong>ConsistentHash</strong>：一致性哈希（相同请求路由到相同实例）</li>
</ul><p><strong>底层逻辑</strong>：负载均衡器在每次 RPC 调用时执行：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>1. 获取可用实例列表</span></div></div><div><div><div>2</div></div><div><span>2. 根据策略选择一个实例</span></div></div><div><div><div>3</div></div><div><span>3. 建立连接并发送请求</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>5.3 超时控制<a href="#53-超时控制"><span>#</span></a></h2><p>Kitex 支持多层超时控制：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>// 客户端全局超时</span></div></div><div><div><div>2</div></div><div><span>cli, _ </span><span>:=</span><span> user.</span><span>NewClient</span><span>(</span></div></div><div><div><div>3</div></div><div><span>    </span><span>"user-service"</span><span>,</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>client.</span><span>WithRPCTimeout</span><span>(</span><span>3</span><span> </span><span>*</span><span> time.Second),</span></div></div><div><div><div>5</div></div><div><span>)</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>// 单次调用超时</span></div></div><div><div><div>8</div></div><div><span>ctx, cancel </span><span>:=</span><span> context.</span><span>WithTimeout</span><span>(context.</span><span>Background</span><span>(), </span><span>1</span><span>*</span><span>time.Second)</span></div></div><div><div><div>9</div></div><div><span>defer</span><span> </span><span>cancel</span><span>()</span></div></div><div><div><div>10</div></div><div><span>resp, err </span><span>:=</span><span> cli.</span><span>GetUser</span><span>(ctx, req)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>底层逻辑</strong>：</p><ul>
<li><code>WithRPCTimeout</code>：设置默认超时时间</li>
<li><code>context.WithTimeout</code>：覆盖默认超时</li>
<li>超时后会自动取消请求，释放连接</li>
</ul></section></section>
<section><h1>6. 与 Hertz 集成<a href="#6-与-hertz-集成"><span>#</span></a></h1><p>在微服务架构中，常见的模式是：<strong>Hertz 作为网关层接收 HTTP 请求，Kitex 作为服务层处理 RPC 调用</strong>。</p><section><h2>6.1 集成示例<a href="#61-集成示例"><span>#</span></a></h2><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>package</span><span> </span><span>main</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>import</span><span> (</span></div></div><div><div><div>4</div></div><div><span>    </span><span>"</span><span>context</span><span>"</span></div></div><div><div><div>5</div></div><div><span>    </span><span>"</span><span>github.com/cloudwego/hertz/pkg/app</span><span>"</span></div></div><div><div><div>6</div></div><div><span>    </span><span>"</span><span>github.com/cloudwego/hertz/pkg/app/server</span><span>"</span></div></div><div><div><div>7</div></div><div><span>    </span><span>"</span><span>github.com/cloudwego/kitex/client</span><span>"</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>user </span><span>"</span><span>example.com/project/kitex_gen/user/userservice</span><span>"</span></div></div><div><div><div>9</div></div><div><span>)</span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span>var</span><span> userClient </span><span>user</span><span>.</span><span>Client</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>14</div></div><div><span>    </span><span>// 初始化 Kitex 客户端</span></div></div><div><div><div>15</div></div><div><span><span>    </span></span><span>userClient, _ </span><span>=</span><span> user.</span><span>NewClient</span><span>(</span><span>"user-service"</span><span>, client.</span><span>WithHostPorts</span><span>(</span><span>"127.0.0.1:8888"</span><span>))</span></div></div><div><div><div>16</div></div><div>
</div></div><div><div><div>17</div></div><div><span>    </span><span>// 初始化 Hertz 服务器</span></div></div><div><div><div>18</div></div><div><span><span>    </span></span><span>h </span><span>:=</span><span> server.</span><span>Default</span><span>()</span></div></div><div><div><div>19</div></div><div>
</div></div><div><div><div>20</div></div><div><span><span>    </span></span><span>h.</span><span>GET</span><span>(</span><span>"/user/:id"</span><span>, </span><span>func</span><span>(</span><span>ctx</span><span> </span><span>context</span><span>.</span><span>Context</span><span>, </span><span>c</span><span> </span><span>*</span><span>app</span><span>.</span><span>RequestContext</span><span>) {</span></div></div><div><div><div>21</div></div><div><span><span>        </span></span><span>userID </span><span>:=</span><span> c.</span><span>Param</span><span>(</span><span>"id"</span><span>)</span></div></div><div><div><div>22</div></div><div>
</div></div><div><div><div>23</div></div><div><span>        </span><span>// 调用 Kitex 客户端</span></div></div><div><div><div>24</div></div><div><span><span>        </span></span><span>resp, err </span><span>:=</span><span> userClient.</span><span>GetUser</span><span>(ctx, </span><span>&amp;</span><span>user</span><span>.</span><span>GetUserRequest</span><span>{</span></div></div><div><div><div>25</div></div><div><span><span>            </span></span><span>UserID: </span><span>parseInt64</span><span>(userID),</span></div></div><div><div><div>26</div></div><div><span><span>        </span></span><span>})</span></div></div><div><div><div>27</div></div><div>
</div></div><div><div><div>28</div></div><div><span>        </span><span>if</span><span> err </span><span>!=</span><span> </span><span>nil</span><span> {</span></div></div><div><div><div>29</div></div><div><span><span>            </span></span><span>c.</span><span>JSON</span><span>(</span><span>500</span><span>, </span><span>map</span><span>[</span><span>string</span><span>]</span><span>string</span><span>{</span><span>"error"</span><span>: err.</span><span>Error</span><span>()})</span></div></div><div><div><div>30</div></div><div><span>            </span><span>return</span></div></div><div><div><div>31</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>32</div></div><div>
</div></div><div><div><div>33</div></div><div><span><span>        </span></span><span>c.</span><span>JSON</span><span>(</span><span>200</span><span>, resp)</span></div></div><div><div><div>34</div></div><div><span><span>    </span></span><span>})</span></div></div><div><div><div>35</div></div><div>
</div></div><div><div><div>36</div></div><div><span><span>    </span></span><span>h.</span><span>Spin</span><span>()</span></div></div><div><div><div>37</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p><strong>底层逻辑</strong>：</p><ul>
<li>Hertz 的 <code>context.Context</code> 可以直接传递给 Kitex，保持链路追踪信息</li>
<li>Kitex 客户端可以复用，不需要每次请求都创建</li>
</ul></section></section>
<section><h1>7. 性能优化要点<a href="#7-性能优化要点"><span>#</span></a></h1><section><h2>7.1 连接池配置<a href="#71-连接池配置"><span>#</span></a></h2><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>cli, _ </span><span>:=</span><span> user.</span><span>NewClient</span><span>(</span></div></div><div><div><div>2</div></div><div><span>    </span><span>"user-service"</span><span>,</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>client.</span><span>WithLongConnection</span><span>(</span></div></div><div><div><div>4</div></div><div><span>        </span><span>connpool</span><span>.</span><span>IdleConfig</span><span>{</span></div></div><div><div><div>5</div></div><div><span><span>            </span></span><span>MaxIdlePerAddress: </span><span>10</span><span>,</span></div></div><div><div><div>6</div></div><div><span><span>            </span></span><span>MaxIdleGlobal:     </span><span>100</span><span>,</span></div></div><div><div><div>7</div></div><div><span><span>            </span></span><span>MaxIdleTimeout:    </span><span>60</span><span> </span><span>*</span><span> time.Second,</span></div></div><div><div><div>8</div></div><div><span><span>        </span></span><span>},</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>),</span></div></div><div><div><div>10</div></div><div><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>底层逻辑</strong>：</p><ul>
<li><code>MaxIdlePerAddress</code>：每个服务实例保持的最大空闲连接数</li>
<li><code>MaxIdleGlobal</code>：全局最大空闲连接数</li>
<li><code>MaxIdleTimeout</code>：空闲连接超时时间</li>
</ul></section><section><h2>7.2 协议选择<a href="#72-协议选择"><span>#</span></a></h2><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>// 使用 Thrift Binary 协议（默认，最快）</span></div></div><div><div><div>2</div></div><div><span>svr </span><span>:=</span><span> user.</span><span>NewServer</span><span>(</span><span>new</span><span>(</span><span>UserServiceImpl</span><span>))</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>// 使用 ProtoBuf 协议</span></div></div><div><div><div>5</div></div><div><span>svr </span><span>:=</span><span> user.</span><span>NewServer</span><span>(</span></div></div><div><div><div>6</div></div><div><span>    </span><span>new</span><span>(</span><span>UserServiceImpl</span><span>),</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>server.</span><span>WithPayloadCodec</span><span>(thrift.</span><span>NewThriftCodecDisableFramed</span><span>()),</span></div></div><div><div><div>8</div></div><div><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>性能对比</strong>：</p><ul>
<li><strong>Thrift Binary</strong>：最快，体积中等</li>
<li><strong>Thrift Compact</strong>：体积最小，速度稍慢</li>
<li><strong>ProtoBuf</strong>：兼容 gRPC 生态，性能介于两者之间</li>
</ul></section></section>
<section><h1>8. 总结<a href="#8-总结"><span>#</span></a></h1><p>Kitex 是为高性能 RPC 场景设计的框架，核心优势在于：</p><ol>
<li><strong>网络层优化</strong>：基于 Netpoll，支持 epoll/kqueue，减少协程切换开销</li>
<li><strong>多协议支持</strong>：支持 Thrift、ProtoBuf、gRPC，灵活选择</li>
<li><strong>完整的治理功能</strong>：服务发现、负载均衡、熔断、超时控制一应俱全</li>
<li><strong>生态集成</strong>：与 Hertz 无缝配合，适合构建微服务架构</li>
</ol><p><strong>适用场景</strong>：</p><ul>
<li>高并发 RPC 服务（QPS &gt; 10k）</li>
<li>需要完整微服务治理功能的场景</li>
<li>使用 CloudWeGo 生态（与 Hertz 配合）</li>
</ul><p><strong>学习路径建议</strong>：</p><ol>
<li>先掌握 Thrift IDL 语法，理解代码生成机制</li>
<li>实现一个简单的 RPC 服务，熟悉客户端和服务端 API</li>
<li>学习中间件机制，实现日志、监控等功能</li>
<li>集成服务发现和负载均衡，构建完整的微服务调用链</li>
<li>与 Hertz 集成，构建网关层</li>
</ol><p>通过本文，你应该已经掌握了 Kitex 的核心原理和使用方法。结合 Hertz 和 Thrift，你可以构建高性能的微服务架构。</p></section>]]></content>
    <author><name>Zowely</name></author>
    <category term="项目开发" />
    <category term="项目开发" />
    <category term="Golang" />
    <category term="gRPC" />
    <category term="CloudWeGo" />
    <category term="Kitex" />
  </entry>
  <entry>
    <title>CloudWeGo-Thrift</title>
    <link href="https://www.lansganbs.cn/posts/%E9%A1%B9%E7%9B%AE%E5%BC%80%E5%8F%91/cloudwego-thrift/" rel="alternate" type="text/html" />
    <id>https://www.lansganbs.cn/posts/%E9%A1%B9%E7%9B%AE%E5%BC%80%E5%8F%91/cloudwego-thrift/</id>
    <published>2026-02-03T00:00:00.000Z</published>
    <updated>2026-02-04T22:15:25.000Z</updated>
    <summary>深入解析 CloudWeGo-Thrift 跨语言 RPC 框架的核心原理，包括 IDL 语法、序列化协议、传输层、代码生成与使用，以及与 ProtoBuf 的对比分析。从 Thrift 的设计哲学到实战技巧，全面掌握 Thrift 的使用方法和性能优化方案。</summary>
    <content type="html"><![CDATA[<section><h1>1. 为什么需要 Thrift<a href="#1-为什么需要-thrift"><span>#</span></a></h1><p>如果你已经熟悉 ProtoBuf，可能会问：为什么还需要 Thrift？</p><section><h2>1.1 ProtoBuf 的局限性<a href="#11-protobuf-的局限性"><span>#</span></a></h2><p>ProtoBuf 是优秀的序列化协议，但它有一些限制：</p><ul>
<li><strong>仅定义数据结构</strong>：ProtoBuf 主要关注 Message 定义，服务定义（Service）是后来才加入的</li>
<li><strong>RPC 框架绑定</strong>：使用 gRPC 时，你被绑定到 Google 的生态（HTTP/2、gRPC 协议）</li>
<li><strong>类型系统有限</strong>：不支持某些复杂类型（如 Set）</li>
</ul></section><section><h2>1.2 Thrift 的核心优势<a href="#12-thrift-的核心优势"><span>#</span></a></h2><p>Thrift 是 Facebook 开源的跨语言 RPC 框架，它的设计目标是：</p><p><strong>底层逻辑</strong>：Thrift 不仅是序列化协议，更是完整的 RPC 解决方案。它包含：</p><ul>
<li><strong>IDL 层</strong>：定义数据结构和服务接口</li>
<li><strong>协议层</strong>：多种序列化协议（Binary/Compact/JSON）可选</li>
<li><strong>传输层</strong>：多种传输方式（Socket/HTTP/FramedTransport）可选</li>
<li><strong>服务端模型</strong>：多种并发模型（Simple/ThreadPool/NonBlocking）可选</li>
</ul><p><strong>与 ProtoBuf 的核心差异</strong>：</p><ul>
<li>Thrift 是”全家桶”，ProtoBuf 是”单一工具”</li>
<li>Thrift 的协议层和传输层可以自由组合</li>
<li>Thrift 原生支持更丰富的类型系统（Set、Exception）</li>
</ul></section></section>
<section><h1>2. Thrift IDL 语法<a href="#2-thrift-idl-语法"><span>#</span></a></h1><section><h2>2.1 基础类型<a href="#21-基础类型"><span>#</span></a></h2><p>Thrift 支持的基础类型比 ProtoBuf 更丰富：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>// 布尔型</span></div></div><div><div><div>2</div></div><div><span>bool is_active</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>// 整数型（有符号）</span></div></div><div><div><div>5</div></div><div><span>i8  tiny_num    // 8位，对应 Go 的 int8</span></div></div><div><div><div>6</div></div><div><span>i16 small_num   // 16位，对应 Go 的 int16</span></div></div><div><div><div>7</div></div><div><span>i32 normal_num  // 32位，对应 Go 的 int32</span></div></div><div><div><div>8</div></div><div><span>i64 big_num     // 64位，对应 Go 的 int64</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span>// 浮点型</span></div></div><div><div><div>11</div></div><div><span>double price    // 64位浮点数，对应 Go 的 float64</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span>// 字符串和二进制</span></div></div><div><div><div>14</div></div><div><span>string username // UTF-8 字符串</span></div></div><div><div><div>15</div></div><div><span>binary avatar   // 二进制数据（对应 Go 的 []byte）</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p><strong>底层逻辑</strong>：Thrift 的类型系统直接映射到各语言的原生类型，不像 ProtoBuf 需要通过 varint 编码优化。</p></section><section><h2>2.2 容器类型<a href="#22-容器类型"><span>#</span></a></h2><p>Thrift 支持三种容器类型：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>// List：有序列表，允许重复</span></div></div><div><div><div>2</div></div><div><span>list&lt;string&gt; hobbies</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>// Set：无序集合，不允许重复</span></div></div><div><div><div>5</div></div><div><span>set&lt;i64&gt; follower_ids</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>// Map：键值对映射</span></div></div><div><div><div>8</div></div><div><span>map&lt;string, string&gt; metadata</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>与 ProtoBuf 的差异</strong>：</p><ul>
<li>ProtoBuf 只有 <code>repeated</code>（对应 List）和 <code>map</code></li>
<li>Thrift 原生支持 <code>set</code>，ProtoBuf 需要用 <code>repeated</code> + 去重逻辑模拟</li>
</ul></section><section><h2>2.3 结构体定义<a href="#23-结构体定义"><span>#</span></a></h2><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>struct User {</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>1: required i64 id           // 必填字段</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>2: required string username  // 必填字段</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>3: optional string email     // 可选字段</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>4: optional bool is_active = true  // 可选字段，带默认值</span></div></div><div><div><div>6</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>关键概念</strong>：</p><ul>
<li><strong>字段编号</strong>：<code>1:</code>, <code>2:</code> 是字段的唯一标识，类似 ProtoBuf 的 Tag</li>
<li><strong>required/optional</strong>：显式声明字段是否必填
<ul>
<li><code>required</code>：序列化时必须有值，反序列化时必须存在</li>
<li><code>optional</code>：可以不设置，反序列化时缺失不会报错</li>
</ul>
</li>
</ul><p><strong>底层逻辑</strong>：Thrift 的 <code>required</code> 会在运行时检查，如果缺失会抛出异常。ProtoBuf 在 proto3 中移除了 <code>required</code>，所有字段都是可选的。</p></section><section><h2>2.4 枚举和异常<a href="#24-枚举和异常"><span>#</span></a></h2><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>// 枚举定义</span></div></div><div><div><div>2</div></div><div><span>enum UserStatus {</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>NORMAL = 1,</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>BANNED = 2,</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>DELETED = 3</span></div></div><div><div><div>6</div></div><div><span>}</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>// 异常定义（类似结构体，但用于错误处理）</span></div></div><div><div><div>9</div></div><div><span>exception UserNotFoundException {</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>1: string message</span></div></div><div><div><div>11</div></div><div><span><span>    </span></span><span>2: i64 user_id</span></div></div><div><div><div>12</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>底层逻辑</strong>：Thrift 的 <code>exception</code> 会被映射到各语言的异常机制：</p><ul>
<li>Go：生成实现 <code>error</code> 接口的结构体</li>
<li>Java：生成继承 <code>Exception</code> 的类</li>
<li>Python：生成继承 <code>Exception</code> 的类</li>
</ul></section><section><h2>2.5 服务定义<a href="#25-服务定义"><span>#</span></a></h2><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>service UserService {</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>// 定义 RPC 方法</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>User getUser(1: i64 user_id) throws (1: UserNotFoundException e)</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>// void 返回类型</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>void deleteUser(1: i64 user_id)</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>// oneway：不等待响应（异步调用）</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>oneway void logEvent(1: string event)</span></div></div><div><div><div>10</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>关键概念</strong>：</p><ul>
<li><strong>throws</strong>：声明可能抛出的异常</li>
<li><strong>oneway</strong>：客户端发送请求后立即返回，不等待服务端响应（类似消息队列）</li>
</ul><p><strong>底层逻辑</strong>：<code>oneway</code> 方法在网络层不会等待响应包，适合日志、监控等场景。</p></section></section>
<section><h1>3. 序列化协议<a href="#3-序列化协议"><span>#</span></a></h1><p>Thrift 的核心优势是协议层可插拔，支持多种序列化格式。</p><section><h2>3.1 Binary Protocol<a href="#31-binary-protocol"><span>#</span></a></h2><p>最常用的协议，紧凑且高效。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>transport </span><span>:=</span><span> thrift.</span><span>NewTSocket</span><span>(</span><span>"localhost:9090"</span><span>)</span></div></div><div><div><div>2</div></div><div><span>protocol </span><span>:=</span><span> thrift.</span><span>NewTBinaryProtocol</span><span>(transport, </span><span>true</span><span>, </span><span>true</span><span>)</span></div></div><div><div><div>3</div></div><div><span>client </span><span>:=</span><span> userservice.</span><span>NewUserServiceClient</span><span>(thrift.</span><span>NewTStandardClient</span><span>(protocol, protocol))</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>底层逻辑</strong>：</p><ul>
<li>使用固定长度编码（i32 占 4 字节，i64 占 8 字节）</li>
<li>字段按顺序编码：<code>[字段类型][字段ID][字段值]</code></li>
<li>不压缩，但解析速度快</li>
</ul></section><section><h2>3.2 Compact Protocol<a href="#32-compact-protocol"><span>#</span></a></h2><p>压缩版本，牺牲少量性能换取更小的体积。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>protocol </span><span>:=</span><span> thrift.</span><span>NewTCompactProtocol</span><span>(transport)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>底层逻辑</strong>：</p><ul>
<li>使用 varint 编码整数（类似 ProtoBuf）</li>
<li>字段 ID 使用 zigzag 编码</li>
<li>体积比 Binary 小 20-30%，但编解码稍慢</li>
</ul></section><section><h2>3.3 JSON Protocol<a href="#33-json-protocol"><span>#</span></a></h2><p>人类可读的格式，用于调试或与前端交互。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>protocol </span><span>:=</span><span> thrift.</span><span>NewTJSONProtocol</span><span>(transport)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>底层逻辑</strong>：</p><ul>
<li>直接序列化为 JSON 字符串</li>
<li>体积最大，性能最差</li>
<li>适合调试或跨语言兼容性要求高的场景</li>
</ul></section><section><h2>3.4 协议对比<a href="#34-协议对比"><span>#</span></a></h2>
































<table><thead><tr><th>协议</th><th>体积</th><th>速度</th><th>可读性</th><th>适用场景</th></tr></thead><tbody><tr><td>Binary</td><td>中等</td><td>最快</td><td>不可读</td><td>生产环境（默认）</td></tr><tr><td>Compact</td><td>最小</td><td>较快</td><td>不可读</td><td>带宽敏感场景</td></tr><tr><td>JSON</td><td>最大</td><td>最慢</td><td>可读</td><td>调试、前端交互</td></tr></tbody></table></section></section>
<section><h1>4. 传输层<a href="#4-传输层"><span>#</span></a></h1><p>Thrift 的传输层负责数据的读写，也是可插拔的。</p><section><h2>4.1 TSocket<a href="#41-tsocket"><span>#</span></a></h2><p>最基础的 TCP Socket 传输。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>transport </span><span>:=</span><span> thrift.</span><span>NewTSocket</span><span>(</span><span>"localhost:9090"</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>底层逻辑</strong>：直接使用 TCP 连接，每次 RPC 调用对应一次完整的请求-响应。</p></section><section><h2>4.2 TFramedTransport<a href="#42-tframedtransport"><span>#</span></a></h2><p>带帧头的传输，用于非阻塞服务器。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>transport </span><span>:=</span><span> thrift.</span><span>NewTFramedTransport</span><span>(thrift.</span><span>NewTSocket</span><span>(</span><span>"localhost:9090"</span><span>))</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>底层逻辑</strong>：</p><ul>
<li>每个消息前加 4 字节的长度头：<code>[长度][消息体]</code></li>
<li>服务端可以先读取长度，再读取完整消息体</li>
<li>适合 NonBlocking Server（基于 epoll/kqueue）</li>
</ul></section><section><h2>4.3 TBufferedTransport<a href="#43-tbufferedtransport"><span>#</span></a></h2><p>带缓冲的传输，减少系统调用次数。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>transport </span><span>:=</span><span> thrift.</span><span>NewTBufferedTransport</span><span>(thrift.</span><span>NewTSocket</span><span>(</span><span>"localhost:9090"</span><span>), </span><span>8192</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>底层逻辑</strong>：在内存中维护读写缓冲区，积累到一定大小再调用 <code>write()</code>，减少系统调用开销。</p></section></section>
<section><h1>5. 代码生成与使用<a href="#5-代码生成与使用"><span>#</span></a></h1><section><h2>5.1 生成 Go 代码<a href="#51-生成-go-代码"><span>#</span></a></h2><p>假设你有一个 <code>user.thrift</code> 文件：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>namespace go user</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>struct User {</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>1: required i64 id</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>2: required string username</span></div></div><div><div><div>6</div></div><div><span>}</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>service UserService {</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>User getUser(1: i64 user_id)</span></div></div><div><div><div>10</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>生成代码：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>thrift</span><span> </span><span>--gen</span><span> </span><span>go</span><span> </span><span>user.thrift</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li><code>gen-go/user/user.go</code>：所有 Thrift 生成的接口、客户端、服务端代码都在这一个 user.go 文件中。</li>
</ul></section><section><h2>5.2 实现服务端<a href="#52-实现服务端"><span>#</span></a></h2><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>package</span><span> </span><span>main</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>import</span><span> (</span></div></div><div><div><div>4</div></div><div><span>    </span><span>"</span><span>context</span><span>"</span></div></div><div><div><div>5</div></div><div><span>    </span><span>"</span><span>fmt</span><span>"</span></div></div><div><div><div>6</div></div><div><span>    </span><span>"</span><span>github.com/apache/thrift/lib/go/thrift</span><span>"</span></div></div><div><div><div>7</div></div><div><span>    </span><span>"</span><span>your-module/gen-go/user</span><span>"</span></div></div><div><div><div>8</div></div><div><span>)</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span>// 实现服务接口</span></div></div><div><div><div>11</div></div><div><span>type</span><span> </span><span>UserServiceHandler</span><span> </span><span>struct</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span>func</span><span> (</span><span>h </span><span>*</span><span>UserServiceHandler</span><span>) </span><span>GetUser</span><span>(</span><span>ctx</span><span> </span><span>context</span><span>.</span><span>Context</span><span>, </span><span>userID</span><span> </span><span>int64</span><span>) (</span><span>*</span><span>user</span><span>.</span><span>User</span><span>, </span><span>error</span><span>) {</span></div></div><div><div><div>14</div></div><div><span>    </span><span>return</span><span> </span><span>&amp;</span><span>user</span><span>.</span><span>User</span><span>{</span></div></div><div><div><div>15</div></div><div><span><span>        </span></span><span>ID:       userID,</span></div></div><div><div><div>16</div></div><div><span><span>        </span></span><span>Username: </span><span>"TestUser"</span><span>,</span></div></div><div><div><div>17</div></div><div><span><span>    </span></span><span>}, </span><span>nil</span></div></div><div><div><div>18</div></div><div><span>}</span></div></div><div><div><div>19</div></div><div>
</div></div><div><div><div>20</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>21</div></div><div><span><span>    </span></span><span>handler </span><span>:=</span><span> </span><span>&amp;</span><span>UserServiceHandler</span><span>{}</span></div></div><div><div><div>22</div></div><div><span><span>    </span></span><span>processor </span><span>:=</span><span> user.</span><span>NewUserServiceProcessor</span><span>(handler)</span></div></div><div><div><div>23</div></div><div>
</div></div><div><div><div>24</div></div><div><span><span>    </span></span><span>transport, _ </span><span>:=</span><span> thrift.</span><span>NewTServerSocket</span><span>(</span><span>":9090"</span><span>)</span></div></div><div><div><div>25</div></div><div><span><span>    </span></span><span>server </span><span>:=</span><span> thrift.</span><span>NewTSimpleServer4</span><span>(</span></div></div><div><div><div>26</div></div><div><span><span>        </span></span><span>processor,</span></div></div><div><div><div>27</div></div><div><span><span>        </span></span><span>transport,</span></div></div><div><div><div>28</div></div><div><span><span>        </span></span><span>thrift.</span><span>NewTBufferedTransportFactory</span><span>(</span><span>8192</span><span>),</span></div></div><div><div><div>29</div></div><div><span><span>        </span></span><span>thrift.</span><span>NewTBinaryProtocolFactoryDefault</span><span>(),</span></div></div><div><div><div>30</div></div><div><span><span>    </span></span><span>)</span></div></div><div><div><div>31</div></div><div>
</div></div><div><div><div>32</div></div><div><span><span>    </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"Starting server on :9090"</span><span>)</span></div></div><div><div><div>33</div></div><div><span><span>    </span></span><span>server.</span><span>Serve</span><span>()</span></div></div><div><div><div>34</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p><strong>底层逻辑</strong>：</p><ul>
<li><code>Processor</code>：负责解析请求、调用 Handler、序列化响应</li>
<li><code>Transport</code>：负责网络 I/O</li>
<li><code>ProtocolFactory</code>：负责创建序列化协议实例</li>
</ul></section></section>
<section><h1>6. Thrift vs ProtoBuf<a href="#6-thrift-vs-protobuf"><span>#</span></a></h1><section><h2>6.1 核心差异<a href="#61-核心差异"><span>#</span></a></h2>







































<table><thead><tr><th>特性</th><th>Thrift</th><th>ProtoBuf</th></tr></thead><tbody><tr><td>定位</td><td>完整 RPC 框架</td><td>序列化协议</td></tr><tr><td>类型系统</td><td>支持 Set、Exception</td><td>仅支持基础类型</td></tr><tr><td>协议层</td><td>可选（Binary/Compact/JSON）</td><td>固定（Protobuf Wire Format）</td></tr><tr><td>传输层</td><td>可选（Socket/Framed/HTTP）</td><td>依赖 gRPC</td></tr><tr><td>服务端模型</td><td>多种（Simple/ThreadPool/NonBlocking）</td><td>依赖 gRPC</td></tr><tr><td>字段修饰符</td><td>required/optional</td><td>proto3 全部可选</td></tr></tbody></table></section><section><h2>6.2 选择建议<a href="#62-选择建议"><span>#</span></a></h2><p><strong>选择 Thrift 的场景</strong>：</p><ul>
<li>需要灵活的协议层和传输层组合</li>
<li>需要使用 Set 类型</li>
<li>需要显式的 required 字段校验</li>
<li>已有 Thrift 生态（如使用 Kitex）</li>
</ul><p><strong>选择 ProtoBuf 的场景</strong>：</p><ul>
<li>需要与 gRPC 生态集成</li>
<li>需要更好的向后兼容性（proto3 的设计更宽松）</li>
<li>需要更广泛的语言支持（ProtoBuf 社区更活跃）</li>
</ul></section></section>
<section><h1>7. 实战技巧<a href="#7-实战技巧"><span>#</span></a></h1><section><h2>7.1 字段演化<a href="#71-字段演化"><span>#</span></a></h2><p>Thrift 支持字段的增删改，但需要遵循规则：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>struct User {</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>1: required i64 id</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>2: required string username</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>// 3: string old_field  // 已废弃，不要删除编号</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>4: optional string email  // 新增字段，使用新编号</span></div></div><div><div><div>6</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>底层逻辑</strong>：</p><ul>
<li>不要修改已有字段的编号</li>
<li>不要将 <code>optional</code> 改为 <code>required</code>（会导致旧客户端反序列化失败）</li>
<li>废弃字段保留编号，避免未来冲突</li>
</ul></section><section><h2>7.2 性能优化<a href="#72-性能优化"><span>#</span></a></h2><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>// 使用对象池复用 Transport</span></div></div><div><div><div>2</div></div><div><span>var</span><span> transportPool </span><span>=</span><span> </span><span>sync</span><span>.</span><span>Pool</span><span>{</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>New: </span><span>func</span><span>() </span><span>interface</span><span>{} {</span></div></div><div><div><div>4</div></div><div><span><span>        </span></span><span>transport, _ </span><span>:=</span><span> thrift.</span><span>NewTSocket</span><span>(</span><span>"localhost:9090"</span><span>)</span></div></div><div><div><div>5</div></div><div><span>        </span><span>return</span><span> thrift.</span><span>NewTBufferedTransport</span><span>(transport, </span><span>8192</span><span>)</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>},</span></div></div><div><div><div>7</div></div><div><span>}</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>func</span><span> </span><span>callRPC</span><span>() {</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>transport </span><span>:=</span><span> transportPool.</span><span>Get</span><span>().(</span><span>thrift</span><span>.</span><span>TTransport</span><span>)</span></div></div><div><div><div>11</div></div><div><span>    </span><span>defer</span><span> transportPool.</span><span>Put</span><span>(transport)</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span><span>    </span></span><span>transport.</span><span>Open</span><span>()</span></div></div><div><div><div>14</div></div><div><span>    </span><span>defer</span><span> transport.</span><span>Close</span><span>()</span></div></div><div><div><div>15</div></div><div>
</div></div><div><div><div>16</div></div><div><span><span>    </span></span><span>protocol </span><span>:=</span><span> thrift.</span><span>NewTBinaryProtocol</span><span>(transport, </span><span>true</span><span>, </span><span>true</span><span>)</span></div></div><div><div><div>17</div></div><div><span><span>    </span></span><span>client </span><span>:=</span><span> user.</span><span>NewUserServiceClient</span><span>(thrift.</span><span>NewTStandardClient</span><span>(protocol, protocol))</span></div></div><div><div><div>18</div></div><div>
</div></div><div><div><div>19</div></div><div><span><span>    </span></span><span>result, _ </span><span>:=</span><span> client.</span><span>GetUser</span><span>(context.</span><span>Background</span><span>(), </span><span>123</span><span>)</span></div></div><div><div><div>20</div></div><div><span><span>    </span></span><span>fmt.</span><span>Println</span><span>(result)</span></div></div><div><div><div>21</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p><strong>底层逻辑</strong>：复用 Transport 对象可以减少连接建立开销和内存分配。</p></section></section>
<section><h1>8. 总结<a href="#8-总结"><span>#</span></a></h1><p>Thrift 是一个功能完整的 RPC 框架，核心优势在于：</p><ol>
<li><strong>灵活的分层设计</strong>：协议层、传输层、服务端模型可自由组合</li>
<li><strong>丰富的类型系统</strong>：原生支持 Set、Exception 等类型</li>
<li><strong>跨语言支持</strong>：支持 20+ 种编程语言</li>
</ol><p><strong>适用场景</strong>：</p><ul>
<li>需要灵活选择序列化协议的场景（如带宽敏感时用 Compact）</li>
<li>需要使用 Set 类型或显式 required 校验</li>
<li>使用 CloudWeGo 生态（Kitex 基于 Thrift）</li>
</ul><p><strong>学习路径建议</strong>：</p><ol>
<li>先掌握 IDL 语法，理解 required/optional 的区别</li>
<li>了解三种序列化协议的适用场景</li>
<li>理解传输层的选择（Framed 用于 NonBlocking Server）</li>
<li>在实际项目中尝试使用 Kitex（对 Thrift 的高性能封装）</li>
</ol><p>通过本文，你应该已经掌握了 Thrift 的核心原理和使用方法。接下来可以学习 Kitex，它是对 Thrift 的高性能封装，提供了更好的开发体验。</p></section>]]></content>
    <author><name>Zowely</name></author>
    <category term="项目开发" />
    <category term="项目开发" />
    <category term="Golang" />
    <category term="gRPC" />
    <category term="CloudWeGo" />
    <category term="Thrift" />
  </entry>
  <entry>
    <title>CloudWeGo-Hertz</title>
    <link href="https://www.lansganbs.cn/posts/%E9%A1%B9%E7%9B%AE%E5%BC%80%E5%8F%91/cloudwego-hertz/" rel="alternate" type="text/html" />
    <id>https://www.lansganbs.cn/posts/%E9%A1%B9%E7%9B%AE%E5%BC%80%E5%8F%91/cloudwego-hertz/</id>
    <published>2026-02-01T00:00:00.000Z</published>
    <updated>2026-02-03T23:24:58.000Z</updated>
    <summary>深入解析 CloudWeGo-Hertz 高性能 HTTP 框架的核心原理，包括基于 Netpoll 的网络层优化、对象池化机制、路由树实现、中间件执行流程以及与 Kitex 的集成方案。从 Gin 迁移到 Hertz 的完整指南。</summary>
    <content type="html"><![CDATA[<section><h1>1. 为什么选择 Hertz<a href="#1-为什么选择-hertz"><span>#</span></a></h1><p>如果你已经熟悉 Gin，可能会问：为什么还需要 Hertz？</p><section><h2>1.1 Gin 的局限性<a href="#11-gin-的局限性"><span>#</span></a></h2><p>Gin 基于 Go 标准库的 <code>net/http</code>，这意味着：</p><ul>
<li><strong>网络模型</strong>：使用 Go 原生的 goroutine-per-connection 模型，在高并发场景下会产生大量协程切换开销</li>
<li><strong>内存分配</strong>：每次请求都会分配新的 <code>http.Request</code> 和 <code>http.ResponseWriter</code>，GC 压力较大</li>
<li><strong>扩展性</strong>：标准库的抽象层次较高，难以深度优化网络层</li>
</ul></section><section><h2>1.2 Hertz 的核心优势<a href="#12-hertz-的核心优势"><span>#</span></a></h2><p>Hertz 是字节跳动开源的 HTTP 框架，属于 CloudWeGo 微服务生态的一部分。它的设计目标是：</p><p><strong>底层逻辑</strong>：Hertz 不依赖 <code>net/http</code>，而是基于自研的网络库 <strong>Netpoll</strong>（epoll/kqueue 封装），实现了：</p><ul>
<li><strong>零拷贝</strong>：通过 <code>nocopy</code> API 减少内存拷贝</li>
<li><strong>对象池化</strong>：<code>RequestContext</code> 等核心对象复用，降低 GC 压力</li>
<li><strong>协议层优化</strong>：HTTP/1.1 解析器手写优化，性能超过标准库</li>
</ul><p><strong>与 Gin 的 API 兼容性</strong>：Hertz 刻意保持了与 Gin 相似的 API 设计，迁移成本低。</p></section></section>
<section><h1>2. 核心架构<a href="#2-核心架构"><span>#</span></a></h1><section><h2>2.1 分层设计<a href="#21-分层设计"><span>#</span></a></h2><p>Hertz 采用三层架构：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>应用层 (Handler/Middleware)</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>↓</span></div></div><div><div><div>3</div></div><div><span>协议层 (HTTP Parser)</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>↓</span></div></div><div><div><div>5</div></div><div><span>网络层 (Netpoll/Go Net)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>底层逻辑</strong>：这种分层使得 Hertz 可以灵活切换网络库。默认使用 Netpoll，但也支持降级到标准库（通过 <code>WithTransport</code> 选项）。</p></section><section><h2>2.2 核心数据结构<a href="#22-核心数据结构"><span>#</span></a></h2><section><h3>RequestContext<a href="#requestcontext"><span>#</span></a></h3><p>Hertz 的核心是 <code>app.RequestContext</code>，它对应 Gin 的 <code>gin.Context</code>。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>type</span><span> </span><span>RequestContext</span><span> </span><span>struct</span><span> {</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>conn     </span><span>network</span><span>.</span><span>Conn</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>Request  </span><span>protocol</span><span>.</span><span>Request</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>Response </span><span>protocol</span><span>.</span><span>Response</span></div></div><div><div><div>5</div></div><div><span>    </span><span>// ...</span></div></div><div><div><div>6</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>关键差异</strong>：</p><ul>
<li>Gin 的 <code>Context</code> 包装了标准库的 <code>*http.Request</code></li>
<li>Hertz 的 <code>RequestContext</code> 直接持有底层连接和自定义的 <code>Request</code>/<code>Response</code> 对象</li>
</ul><p><strong>底层逻辑</strong>：这种设计让 Hertz 可以：</p><ol>
<li>复用 <code>RequestContext</code> 对象（通过 <code>sync.Pool</code>）</li>
<li>直接操作底层 buffer，避免多次拷贝</li>
<li>支持流式读写（标准库的 <code>http.Request.Body</code> 只能读一次）</li>
</ol></section></section></section>
<section><h1>3. 快速上手<a href="#3-快速上手"><span>#</span></a></h1><section><h2>3.1 基础示例<a href="#31-基础示例"><span>#</span></a></h2><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>package</span><span> </span><span>main</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>import</span><span> (</span></div></div><div><div><div>4</div></div><div><span>    </span><span>"</span><span>context</span><span>"</span></div></div><div><div><div>5</div></div><div><span>    </span><span>"</span><span>github.com/cloudwego/hertz/pkg/app</span><span>"</span></div></div><div><div><div>6</div></div><div><span>    </span><span>"</span><span>github.com/cloudwego/hertz/pkg/app/server</span><span>"</span></div></div><div><div><div>7</div></div><div><span>)</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>h </span><span>:=</span><span> server.</span><span>Default</span><span>()</span></div></div><div><div><div>11</div></div><div>
</div></div><div><div><div>12</div></div><div><span><span>    </span></span><span>h.</span><span>GET</span><span>(</span><span>"/ping"</span><span>, </span><span>func</span><span>(</span><span>ctx</span><span> </span><span>context</span><span>.</span><span>Context</span><span>, </span><span>c</span><span> </span><span>*</span><span>app</span><span>.</span><span>RequestContext</span><span>) {</span></div></div><div><div><div>13</div></div><div><span><span>        </span></span><span>c.</span><span>JSON</span><span>(</span><span>200</span><span>, </span><span>map</span><span>[</span><span>string</span><span>]</span><span>string</span><span>{</span><span>"msg"</span><span>: </span><span>"pong"</span><span>})</span></div></div><div><div><div>14</div></div><div><span><span>    </span></span><span>})</span></div></div><div><div><div>15</div></div><div>
</div></div><div><div><div>16</div></div><div><span><span>    </span></span><span>h.</span><span>Spin</span><span>() </span><span>// 启动服务</span></div></div><div><div><div>17</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p><strong>与 Gin 的对比</strong>：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>// Gin</span></div></div><div><div><div>2</div></div><div><span>r.</span><span>GET</span><span>(</span><span>"/ping"</span><span>, </span><span>func</span><span>(</span><span>c</span><span> </span><span>*</span><span>gin</span><span>.</span><span>Context</span><span>) {</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>c.</span><span>JSON</span><span>(</span><span>200</span><span>, </span><span>gin</span><span>.</span><span>H</span><span>{</span><span>"msg"</span><span>: </span><span>"pong"</span><span>})</span></div></div><div><div><div>4</div></div><div><span>})</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>// Hertz</span></div></div><div><div><div>7</div></div><div><span>h.</span><span>GET</span><span>(</span><span>"/ping"</span><span>, </span><span>func</span><span>(</span><span>ctx</span><span> </span><span>context</span><span>.</span><span>Context</span><span>, </span><span>c</span><span> </span><span>*</span><span>app</span><span>.</span><span>RequestContext</span><span>) {</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>c.</span><span>JSON</span><span>(</span><span>200</span><span>, </span><span>map</span><span>[</span><span>string</span><span>]</span><span>string</span><span>{</span><span>"msg"</span><span>: </span><span>"pong"</span><span>})</span></div></div><div><div><div>9</div></div><div><span>})</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>核心差异</strong>：Hertz 的 Handler 多了一个 <code>context.Context</code> 参数，这是为了：</p><ul>
<li>支持超时控制（通过 <code>ctx.Done()</code>）</li>
<li>传递链路追踪信息（OpenTelemetry）</li>
<li>与 Kitex（gRPC 框架）保持一致的接口风格</li>
</ul></section><section><h2>3.2 参数绑定<a href="#32-参数绑定"><span>#</span></a></h2><p>Hertz 支持多种参数绑定方式，与 Gin 类似但更强大。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>type</span><span> </span><span>LoginReq</span><span> </span><span>struct</span><span> {</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>Username </span><span>string</span><span> </span><span>`json:"username" query:"username"`</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>Password </span><span>string</span><span> </span><span>`json:"password" query:"password"`</span></div></div><div><div><div>4</div></div><div><span>}</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>h.</span><span>POST</span><span>(</span><span>"/login"</span><span>, </span><span>func</span><span>(</span><span>ctx</span><span> </span><span>context</span><span>.</span><span>Context</span><span>, </span><span>c</span><span> </span><span>*</span><span>app</span><span>.</span><span>RequestContext</span><span>) {</span></div></div><div><div><div>7</div></div><div><span>    </span><span>var</span><span> req </span><span>LoginReq</span></div></div><div><div><div>8</div></div><div><span>    </span><span>// 自动根据 Content-Type 选择绑定方式</span></div></div><div><div><div>9</div></div><div><span>    </span><span>if</span><span> err </span><span>:=</span><span> c.</span><span>Bind</span><span>(</span><span>&amp;</span><span>req); err </span><span>!=</span><span> </span><span>nil</span><span> {</span></div></div><div><div><div>10</div></div><div><span><span>        </span></span><span>c.</span><span>JSON</span><span>(</span><span>400</span><span>, </span><span>map</span><span>[</span><span>string</span><span>]</span><span>string</span><span>{</span><span>"error"</span><span>: err.</span><span>Error</span><span>()})</span></div></div><div><div><div>11</div></div><div><span>        </span><span>return</span></div></div><div><div><div>12</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>13</div></div><div><span><span>    </span></span><span>c.</span><span>JSON</span><span>(</span><span>200</span><span>, req)</span></div></div><div><div><div>14</div></div><div><span>})</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>底层逻辑</strong>：<code>Bind</code> 方法会检查 <code>Content-Type</code> 头：</p><ul>
<li><code>application/json</code> → JSON 解析</li>
<li><code>application/x-www-form-urlencoded</code> → Form 解析</li>
<li><code>multipart/form-data</code> → Multipart 解析</li>
</ul><p><strong>零拷贝优化</strong>：Hertz 提供 <code>c.Query()</code> 和 <code>c.PostForm()</code> 等方法，返回的字符串直接引用底层 buffer，避免拷贝。但要注意：这些字符串在请求结束后会失效，如需持久化需要手动拷贝。</p></section></section>
<section><h1>4. 源码走读<a href="#4-源码走读"><span>#</span></a></h1><section><h2>4.1 Engine 结构<a href="#41-engine-结构"><span>#</span></a></h2><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>type</span><span> </span><span>Engine</span><span> </span><span>struct</span><span> {</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>trees            </span><span>MethodTrees</span><span>  </span><span>// 路由树</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>maxParams        </span><span>uint16</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>allNoRoute       </span><span>HandlersChain</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>allNoMethod      </span><span>HandlersChain</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>noRoute          </span><span>HandlersChain</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>noMethod         </span><span>HandlersChain</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>pool             </span><span>sync</span><span>.</span><span>Pool</span><span>    </span><span>// RequestContext 对象池</span></div></div><div><div><div>9</div></div><div><span>    </span><span>// ...</span></div></div><div><div><div>10</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>核心字段解析</strong>：</p><ul>
<li><code>trees</code>：每个 HTTP 方法（GET/POST/…）对应一棵前缀树（Radix Tree）</li>
<li><code>pool</code>：<code>RequestContext</code> 对象池，每次请求从池中取出，处理完归还</li>
</ul><p><strong>底层逻辑</strong>：对象池化是 Hertz 性能优化的关键。每次请求的处理流程：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>1. 从 pool 获取 RequestContext</span></div></div><div><div><div>2</div></div><div><span>2. 重置字段（清空上次请求的数据）</span></div></div><div><div><div>3</div></div><div><span>3. 解析 HTTP 请求</span></div></div><div><div><div>4</div></div><div><span>4. 路由匹配 + 执行 Handler</span></div></div><div><div><div>5</div></div><div><span>5. 写回响应</span></div></div><div><div><div>6</div></div><div><span>6. 归还 RequestContext 到 pool</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>4.2 路由树结构<a href="#42-路由树结构"><span>#</span></a></h2><p>Hertz 使用 <strong>Radix Tree</strong>（基数树）存储路由，与 Gin 相同。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>type</span><span> </span><span>node</span><span> </span><span>struct</span><span> {</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>path      </span><span>string</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>indices   </span><span>string</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>children  []</span><span>*</span><span>node</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>handlers  </span><span>HandlersChain</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>priority  </span><span>uint32</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>nType     </span><span>nodeType</span></div></div><div><div><div>8</div></div><div><span>    </span><span>// ...</span></div></div><div><div><div>9</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>示例</strong>：注册以下路由</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>h.</span><span>GET</span><span>(</span><span>"/user/:id"</span><span>, handler1)</span></div></div><div><div><div>2</div></div><div><span>h.</span><span>GET</span><span>(</span><span>"/user/:id/profile"</span><span>, handler2)</span></div></div><div><div><div>3</div></div><div><span>h.</span><span>GET</span><span>(</span><span>"/user/admin"</span><span>, handler3)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>会构建如下树结构：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>root</span></div></div><div><div><div>2</div></div><div><span><span> </span></span><span>└─ /user/</span></div></div><div><div><div>3</div></div><div><span><span>     </span></span><span>├─ :id (handler1)</span></div></div><div><div><div>4</div></div><div><span><span>     </span></span><span>│   └─ /profile (handler2)</span></div></div><div><div><div>5</div></div><div><span><span>     </span></span><span>└─ admin (handler3)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>底层逻辑</strong>：</p><ul>
<li><code>:id</code> 是参数节点，可以匹配任意值</li>
<li><code>admin</code> 是静态节点，优先级更高（通过 <code>priority</code> 字段控制）</li>
<li>匹配时先尝试静态节点，再尝试参数节点</li>
</ul></section></section>
<section><h1>5. 中间件机制<a href="#5-中间件机制"><span>#</span></a></h1><section><h2>5.1 中间件执行流程<a href="#51-中间件执行流程"><span>#</span></a></h2><p>Hertz 的中间件模型与 Gin 完全一致，使用 <code>Next()</code> 方法控制执行流程。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> </span><span>Logger</span><span>() </span><span>app</span><span>.</span><span>HandlerFunc</span><span> {</span></div></div><div><div><div>2</div></div><div><span>    </span><span>return</span><span> </span><span>func</span><span>(</span><span>ctx</span><span> </span><span>context</span><span>.</span><span>Context</span><span>, </span><span>c</span><span> </span><span>*</span><span>app</span><span>.</span><span>RequestContext</span><span>) {</span></div></div><div><div><div>3</div></div><div><span><span>        </span></span><span>start </span><span>:=</span><span> time.</span><span>Now</span><span>()</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span><span>        </span></span><span>c.</span><span>Next</span><span>(ctx) </span><span>// 执行后续中间件和 Handler</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span><span>        </span></span><span>latency </span><span>:=</span><span> time.</span><span>Since</span><span>(start)</span></div></div><div><div><div>8</div></div><div><span><span>        </span></span><span>fmt.</span><span>Printf</span><span>(</span><span>"[</span><span>%s</span><span>] </span><span>%s</span><span> </span><span>%v\n</span><span>"</span><span>, c.</span><span>Method</span><span>(), c.</span><span>Path</span><span>(), latency)</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>10</div></div><div><span>}</span></div></div><div><div><div>11</div></div><div>
</div></div><div><div><div>12</div></div><div><span>h.</span><span>Use</span><span>(</span><span>Logger</span><span>())</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>底层逻辑</strong>：中间件和 Handler 被存储在 <code>HandlersChain</code>（<code>[]HandlerFunc</code>）中。执行时通过索引遍历：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>type</span><span> </span><span>RequestContext</span><span> </span><span>struct</span><span> {</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>handlers </span><span>HandlersChain</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>index    </span><span>int8</span><span>  </span><span>// 当前执行到第几个 Handler</span></div></div><div><div><div>4</div></div><div><span>    </span><span>// ...</span></div></div><div><div><div>5</div></div><div><span>}</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>func</span><span> (</span><span>c </span><span>*</span><span>RequestContext</span><span>) </span><span>Next</span><span>(</span><span>ctx</span><span> </span><span>context</span><span>.</span><span>Context</span><span>) {</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>c.index</span><span>++</span></div></div><div><div><div>9</div></div><div><span>    </span><span>for</span><span> c.index </span><span>&lt;</span><span> </span><span>int8</span><span>(</span><span>len</span><span>(c.handlers)) {</span></div></div><div><div><div>10</div></div><div><span><span>        </span></span><span>c.</span><span>handlers</span><span>[</span><span>c</span><span>.</span><span>index</span><span>](ctx, c)</span></div></div><div><div><div>11</div></div><div><span><span>        </span></span><span>c.index</span><span>++</span></div></div><div><div><div>12</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>13</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>调用 <code>c.Next(ctx)</code> 会递增索引并执行下一个 Handler，形成洋葱模型。</p></section><section><h2>5.2 中间件注册方式<a href="#52-中间件注册方式"><span>#</span></a></h2><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>// 全局中间件</span></div></div><div><div><div>2</div></div><div><span>h.</span><span>Use</span><span>(middleware1, middleware2)</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>// 路由组中间件</span></div></div><div><div><div>5</div></div><div><span>v1 </span><span>:=</span><span> h.</span><span>Group</span><span>(</span><span>"/v1"</span><span>, authMiddleware)</span></div></div><div><div><div>6</div></div><div><span>{</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>v1.</span><span>GET</span><span>(</span><span>"/users"</span><span>, getUsers)</span></div></div><div><div><div>8</div></div><div><span>}</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span>// 单个路由中间件</span></div></div><div><div><div>11</div></div><div><span>h.</span><span>GET</span><span>(</span><span>"/admin"</span><span>, adminMiddleware, adminHandler)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>底层逻辑</strong>：注册时，中间件会被合并到 <code>HandlersChain</code> 中：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>[全局中间件...] + [路由组中间件...] + [路由中间件...] + [Handler]</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section>
<section><h1>6. 与 Kitex 集成<a href="#6-与-kitex-集成"><span>#</span></a></h1><p>Hertz 和 Kitex 是 CloudWeGo 生态的两大支柱，分别处理 HTTP 和 RPC。</p><section><h2>6.1 为什么需要集成<a href="#61-为什么需要集成"><span>#</span></a></h2><p>在微服务架构中，常见场景：</p><ul>
<li><strong>网关层</strong>：使用 Hertz 接收 HTTP 请求</li>
<li><strong>服务层</strong>：使用 Kitex 进行 RPC 调用</li>
</ul></section><section><h2>6.2 集成示例<a href="#62-集成示例"><span>#</span></a></h2><p>假设你有一个 Kitex 定义的用户服务：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>// 在 Hertz Handler 中调用 Kitex 客户端</span></div></div><div><div><div>2</div></div><div><span>func</span><span> </span><span>GetUserHandler</span><span>(</span><span>ctx</span><span> </span><span>context</span><span>.</span><span>Context</span><span>, </span><span>c</span><span> </span><span>*</span><span>app</span><span>.</span><span>RequestContext</span><span>) {</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>userID </span><span>:=</span><span> c.</span><span>Param</span><span>(</span><span>"id"</span><span>)</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>    </span><span>// 调用 Kitex 客户端</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>client, _ </span><span>:=</span><span> userservice.</span><span>NewClient</span><span>(</span><span>"user-service"</span><span>)</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>resp, err </span><span>:=</span><span> client.</span><span>GetUser</span><span>(ctx, </span><span>&amp;</span><span>user</span><span>.</span><span>GetUserRequest</span><span>{</span></div></div><div><div><div>8</div></div><div><span><span>        </span></span><span>UserId: userID,</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>})</span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span>    </span><span>if</span><span> err </span><span>!=</span><span> </span><span>nil</span><span> {</span></div></div><div><div><div>12</div></div><div><span><span>        </span></span><span>c.</span><span>JSON</span><span>(</span><span>500</span><span>, </span><span>map</span><span>[</span><span>string</span><span>]</span><span>string</span><span>{</span><span>"error"</span><span>: err.</span><span>Error</span><span>()})</span></div></div><div><div><div>13</div></div><div><span>        </span><span>return</span></div></div><div><div><div>14</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>15</div></div><div>
</div></div><div><div><div>16</div></div><div><span><span>    </span></span><span>c.</span><span>JSON</span><span>(</span><span>200</span><span>, resp)</span></div></div><div><div><div>17</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p><strong>底层逻辑</strong>：</p><ul>
<li>Hertz 的 <code>context.Context</code> 可以直接传递给 Kitex，保持链路追踪信息</li>
<li>Kitex 客户端会自动进行服务发现、负载均衡、超时控制</li>
</ul></section><section><h2>6.3 统一的 IDL 管理<a href="#63-统一的-idl-管理"><span>#</span></a></h2><p>推荐使用 Thrift 或 Protobuf 定义接口，同时生成 Hertz 和 Kitex 代码：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span># 生成 Kitex 服务端代码</span></div></div><div><div><div>2</div></div><div><span>kitex</span><span> </span><span>-module</span><span> </span><span>example.com/project</span><span> </span><span>-service</span><span> </span><span>user-service</span><span> </span><span>user.thrift</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span># 生成 Hertz HTTP 接口代码（基于同一份 IDL）</span></div></div><div><div><div>5</div></div><div><span>hz</span><span> </span><span>new</span><span> </span><span>-module</span><span> </span><span>example.com/project</span><span> </span><span>-idl</span><span> </span><span>user.thrift</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这样可以保证 HTTP 接口和 RPC 接口的数据结构完全一致。</p></section></section>
<section><h1>7. 性能优化要点<a href="#7-性能优化要点"><span>#</span></a></h1><section><h2>7.1 零拷贝 API<a href="#71-零拷贝-api"><span>#</span></a></h2><p>Hertz 提供了 <code>nocopy</code> 系列 API，避免不必要的内存拷贝。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>// 不推荐：会拷贝字符串</span></div></div><div><div><div>2</div></div><div><span>username </span><span>:=</span><span> c.</span><span>Query</span><span>(</span><span>"username"</span><span>)</span></div></div><div><div><div>3</div></div><div><span>go</span><span> </span><span>func</span><span>() {</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>fmt.</span><span>Println</span><span>(username) </span><span>// 危险！请求结束后 username 可能失效</span></div></div><div><div><div>5</div></div><div><span>}()</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>// 推荐：手动拷贝</span></div></div><div><div><div>8</div></div><div><span>username </span><span>:=</span><span> </span><span>string</span><span>(c.</span><span>Query</span><span>(</span><span>"username"</span><span>))</span></div></div><div><div><div>9</div></div><div><span>go</span><span> </span><span>func</span><span>() {</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>fmt.</span><span>Println</span><span>(username) </span><span>// 安全</span></div></div><div><div><div>11</div></div><div><span>}()</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>底层逻辑</strong>：Hertz 的 <code>Query()</code>、<code>PostForm()</code> 等方法返回的字符串直接引用底层 buffer。这个 buffer 在请求结束后会被复用，因此：</p><ul>
<li>在当前请求的生命周期内使用是安全的</li>
<li>如果需要在 goroutine 中使用或持久化存储，必须手动拷贝</li>
</ul></section><section><h2>7.2 流式处理<a href="#72-流式处理"><span>#</span></a></h2><p>对于大文件上传/下载，使用流式 API 避免一次性加载到内存。</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>h.</span><span>POST</span><span>(</span><span>"/upload"</span><span>, </span><span>func</span><span>(</span><span>ctx</span><span> </span><span>context</span><span>.</span><span>Context</span><span>, </span><span>c</span><span> </span><span>*</span><span>app</span><span>.</span><span>RequestContext</span><span>) {</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>file, err </span><span>:=</span><span> c.</span><span>FormFile</span><span>(</span><span>"file"</span><span>)</span></div></div><div><div><div>3</div></div><div><span>    </span><span>if</span><span> err </span><span>!=</span><span> </span><span>nil</span><span> {</span></div></div><div><div><div>4</div></div><div><span><span>        </span></span><span>c.</span><span>String</span><span>(</span><span>400</span><span>, </span><span>"upload failed"</span><span>)</span></div></div><div><div><div>5</div></div><div><span>        </span><span>return</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>    </span><span>// 流式保存文件</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>src, _ </span><span>:=</span><span> file.</span><span>Open</span><span>()</span></div></div><div><div><div>10</div></div><div><span>    </span><span>defer</span><span> src.</span><span>Close</span><span>()</span></div></div><div><div><div>11</div></div><div>
</div></div><div><div><div>12</div></div><div><span><span>    </span></span><span>dst, _ </span><span>:=</span><span> os.</span><span>Create</span><span>(</span><span>"./uploads/"</span><span> </span><span>+</span><span> file.Filename)</span></div></div><div><div><div>13</div></div><div><span>    </span><span>defer</span><span> dst.</span><span>Close</span><span>()</span></div></div><div><div><div>14</div></div><div>
</div></div><div><div><div>15</div></div><div><span><span>    </span></span><span>io.</span><span>Copy</span><span>(dst, src) </span><span>// 流式拷贝，不会占用大量内存</span></div></div><div><div><div>16</div></div><div><span><span>    </span></span><span>c.</span><span>String</span><span>(</span><span>200</span><span>, </span><span>"success"</span><span>)</span></div></div><div><div><div>17</div></div><div><span>})</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section><section><h2>7.3 连接池配置<a href="#73-连接池配置"><span>#</span></a></h2><p>Hertz 默认使用 Netpoll，可以通过选项优化连接池：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>h </span><span>:=</span><span> server.</span><span>New</span><span>(</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>server.</span><span>WithMaxRequestBodySize</span><span>(</span><span>4</span><span> </span><span>*</span><span> </span><span>1024</span><span> </span><span>*</span><span> </span><span>1024</span><span>), </span><span>// 限制请求体大小</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>server.</span><span>WithIdleTimeout</span><span>(</span><span>60</span><span> </span><span>*</span><span> time.Second),       </span><span>// 空闲连接超时</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>server.</span><span>WithReadTimeout</span><span>(</span><span>3</span><span> </span><span>*</span><span> time.Second),        </span><span>// 读超时</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>server.</span><span>WithWriteTimeout</span><span>(</span><span>3</span><span> </span><span>*</span><span> time.Second),       </span><span>// 写超时</span></div></div><div><div><div>6</div></div><div><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>底层逻辑</strong>：</p><ul>
<li><code>MaxRequestBodySize</code>：防止恶意大请求耗尽内存</li>
<li><code>IdleTimeout</code>：及时回收空闲连接，减少资源占用</li>
<li><code>ReadTimeout</code>/<code>WriteTimeout</code>：防止慢客户端攻击</li>
</ul></section><section><h2>7.4 对象池化<a href="#74-对象池化"><span>#</span></a></h2><p>除了 <code>RequestContext</code>，你也可以为自己的对象使用 <code>sync.Pool</code>：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>var</span><span> bufferPool </span><span>=</span><span> </span><span>sync</span><span>.</span><span>Pool</span><span>{</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>New: </span><span>func</span><span>() </span><span>interface</span><span>{} {</span></div></div><div><div><div>3</div></div><div><span>        </span><span>return</span><span> </span><span>new</span><span>(</span><span>bytes</span><span>.</span><span>Buffer</span><span>)</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>},</span></div></div><div><div><div>5</div></div><div><span>}</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>func</span><span> </span><span>handler</span><span>(</span><span>ctx</span><span> </span><span>context</span><span>.</span><span>Context</span><span>, </span><span>c</span><span> </span><span>*</span><span>app</span><span>.</span><span>RequestContext</span><span>) {</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>buf </span><span>:=</span><span> bufferPool.</span><span>Get</span><span>().(</span><span>*</span><span>bytes</span><span>.</span><span>Buffer</span><span>)</span></div></div><div><div><div>9</div></div><div><span>    </span><span>defer</span><span> </span><span>func</span><span>() {</span></div></div><div><div><div>10</div></div><div><span><span>        </span></span><span>buf.</span><span>Reset</span><span>()</span></div></div><div><div><div>11</div></div><div><span><span>        </span></span><span>bufferPool.</span><span>Put</span><span>(buf)</span></div></div><div><div><div>12</div></div><div><span><span>    </span></span><span>}()</span></div></div><div><div><div>13</div></div><div>
</div></div><div><div><div>14</div></div><div><span>    </span><span>// 使用 buf 进行操作</span></div></div><div><div><div>15</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section></section>
<section><h1>8. Hertz vs Gin：迁移指南<a href="#8-hertz-vs-gin迁移指南"><span>#</span></a></h1><p>如果你要从 Gin 迁移到 Hertz，主要改动点：</p>


































<table><thead><tr><th>特性</th><th>Gin</th><th>Hertz</th></tr></thead><tbody><tr><td>Handler 签名</td><td><code>func(*gin.Context)</code></td><td><code>func(context.Context, *app.RequestContext)</code></td></tr><tr><td>启动方法</td><td><code>r.Run(":8080")</code></td><td><code>h.Spin()</code></td></tr><tr><td>参数绑定</td><td><code>c.ShouldBind(&amp;req)</code></td><td><code>c.Bind(&amp;req)</code></td></tr><tr><td>获取参数</td><td><code>c.Query("key")</code></td><td><code>c.Query("key")</code> (相同)</td></tr><tr><td>JSON 响应</td><td><code>c.JSON(200, data)</code></td><td><code>c.JSON(200, data)</code> (相同)</td></tr></tbody></table><p><strong>核心差异</strong>：</p><ol>
<li>Handler 多了 <code>context.Context</code> 参数</li>
<li>字符串可能引用底层 buffer（需注意生命周期）</li>
<li>默认使用 Netpoll（可切换到标准库）</li>
</ol></section>
<section><h1>9. 总结<a href="#9-总结"><span>#</span></a></h1><p>Hertz 是为高性能场景设计的 HTTP 框架，核心优势在于：</p><ol>
<li><strong>网络层优化</strong>：基于 Netpoll，支持 epoll/kqueue，减少协程切换开销</li>
<li><strong>内存优化</strong>：对象池化 + 零拷贝 API，降低 GC 压力</li>
<li><strong>生态集成</strong>：与 Kitex 无缝配合，适合构建微服务网关</li>
</ol><p><strong>适用场景</strong>：</p><ul>
<li>高并发 HTTP 服务（QPS &gt; 10k）</li>
<li>需要与 Kitex 集成的微服务网关</li>
<li>对延迟敏感的业务（P99 &lt; 10ms）</li>
</ul><p>通过本文，你应该已经掌握了 Hertz 的核心原理和使用方法。接下来可以在实际项目中尝试使用，体会它在高并发场景下的性能优势。</p></section>]]></content>
    <author><name>Zowely</name></author>
    <category term="项目开发" />
    <category term="项目开发" />
    <category term="Golang" />
    <category term="gRPC" />
    <category term="CloudWeGo" />
    <category term="Hertz" />
  </entry>
  <entry>
    <title>Redis 分布式锁实现原理</title>
    <link href="https://www.lansganbs.cn/posts/go%E6%BA%90%E7%A0%81/redis-%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86/" rel="alternate" type="text/html" />
    <id>https://www.lansganbs.cn/posts/go%E6%BA%90%E7%A0%81/redis-%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86/</id>
    <published>2026-01-25T00:00:00.000Z</published>
    <updated>2026-01-28T14:44:23.000Z</updated>
    <summary>本文将深入探讨 Golang 中分布式锁的实现原理，涵盖其基本概念、常见实现方式以及在实际应用中的优势和挑战。</summary>
    <content type="html"><![CDATA[<section><h1>0. 前言<a href="#0-前言"><span>#</span></a></h1><p><a href="https://www.lansganbs.cn/go-distributed-lock/">Golang 分布式锁实现原理</a></p></section>
<section><h1>1. redis 分布式锁实现原理<a href="#1-redis-分布式锁实现原理"><span>#</span></a></h1><section><h2>1.1 redis 实现分布式锁<a href="#11-redis-实现分布式锁"><span>#</span></a></h2></section><section><h2>1.2 过期时间不精准问题<a href="#12-过期时间不精准问题"><span>#</span></a></h2><p>使用 setnx 命令设置锁时，通常会设置一个过期时间以防止死锁。然而，由于网络延迟或其他因素，准确说是“客户端视角与服务端视角的时钟脱节” 以及 “进程停顿（STW）”，实际的过期时间可能会有所偏差，导致锁提前释放或延迟释放，从而引发并发问题。</p><p>一旦发生一锁多持现象，锁的基本性质 —— 独占性遭到破坏。这个可以通过看门狗策略来缓解，但是不能完全解决。</p></section><section><h2>1.3 数据弱一致性问题<a href="#13-数据弱一致性问题"><span>#</span></a></h2><p>当 Redis 挂了，可能会导致锁的状态不一致。例如，如果一个客户端持有锁并且正在执行关键操作，而 Redis 突然崩溃，其他客户端可能会认为锁已经释放，从而同时进入临界区，导致数据不一致。</p><p>Redis 挂了导致的状态不一致，本质是主从异步复制导致的锁丢失。Etcd/红锁从存储侧解决了‘锁状态的强一致性’，但依然无法从逻辑侧解决因客户端 STW 导致的一锁多持，后者需配合 Fencing Token 实现终极一致。</p></section></section>
<section><h1>2. redis 分布式锁实现源码<a href="#2-redis-分布式锁实现源码"><span>#</span></a></h1><a href="https://github.com/xiaoxuxiansheng/redis_lock" target="_blank"><div><div><div><div></div><div>xiaoxuxiansheng</div></div><div>/</div><div>redis_lock</div></div><div></div></div><div>Waiting for api.github.com...</div><div><div>00K</div><div>0K</div><div>0K</div><span>Waiting...</span></div></a></section>
<section><h1>2.1 基本框架<a href="#21-基本框架"><span>#</span></a></h1><p>在 Redis sdk 之上，基于 Redis 连接池模式，封装了一个简易的 Redis 客户端。</p><p></p><figure><img src="https://www.lansganbs.cn/images/article/Go%E6%BA%90%E7%A0%81/Redis%20%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81/redis-lock%E5%9F%BA%E6%9C%AC%E6%A1%86%E6%9E%B6.png" alt="redis-lock基本框架.png" /><figcaption>redis-lock基本框架.png</figcaption></figure><p></p><p>接下来，以分布式锁使用方的进程 id + 协程 id 拼接生成一个字符串，作为使用方的身份标识；在执行加锁操作时，通过 redis 的 setNEX 操作，把生成的字符串设置为 val，并且设定好锁数据的过期时间；</p><p>在执行解锁操作时，通过 lua 脚本原子化执行两个步骤：</p><ol>
<li>get 操作获取 val，查看是否和当前使用方身份一致；</li>
<li>倘若身份校验通过，执行 del 操作删除锁数据；</li>
</ol><p>在加锁/解锁操作之外，额外支持一个延长锁过期时间的操作. 该操作同样通过 lua 脚本实现，包括两个步骤：</p><ol>
<li>get 操作获取 val，查看是否和当前使用方身份一致；</li>
<li>倘若身份校验通过，执行 expire 完成锁数据的续期</li>
</ol><p></p><figure><img src="https://www.lansganbs.cn/images/article/Go%E6%BA%90%E7%A0%81/Redis%20%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81/redis-lock%E6%B5%81%E7%A8%8B.png" alt="redis-lock流程.png" /><figcaption>redis-lock流程.png</figcaption></figure><p></p></section>
<section><h1>3. watch dog 实现原理<a href="#3-watch-dog-实现原理"><span>#</span></a></h1><section><h2>3.1 续约机制<a href="#31-续约机制"><span>#</span></a></h2><p>在加锁成功后，启动一个后台协程，定时检查锁的剩余过期时间。如果发现剩余时间低于某个阈值（例如过期时间的一半），则调用续约函数，延长锁的过期时间。</p><p>基本上可以看为就是在 Redis 中实现了一个 etcd 的租约机制。</p><p>也就是说 Etcd 租约是 watch 回调型使用的，Redis 锁是主动轮询型用的</p></section><section><h2>3.2 redisson<a href="#32-redisson"><span>#</span></a></h2><p>Redisson 是一个基于 Redis 的 Java 分布式锁实现，上文提到的续约机制就是仿照 Redisson 实现的。</p><a href="https://github.com/redisson/redisson" target="_blank"><div><div><div><div></div><div>redisson</div></div><div>/</div><div>redisson</div></div><div></div></div><div>Waiting for api.github.com...</div><div><div>00K</div><div>0K</div><div>0K</div><span>Waiting...</span></div></a></section><section><h2>3.3 看门狗 watch dog<a href="#33-看门狗-watch-dog"><span>#</span></a></h2><p></p><figure><img src="https://www.lansganbs.cn/images/article/Go%E6%BA%90%E7%A0%81/Redis%20%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81/watch%20dog.png" alt="watch dog.png" /><figcaption>watch dog.png</figcaption></figure><p></p><p>下面聊聊看门狗模式的执行流程（其实和 etcd 的租约续约机制非常类似）：</p><ul>
<li>在执行 redis 分布式锁的上锁操作时，通过 setNEX 指令完成锁数据的设置，携带了一个默认的锁数据过期时间</li>
<li>确认上锁成功后，异步启动一个 watchDog 守护协程，按照锁默认过期时间 1/4 ~ 1/3 的节奏（可自由设置），持续地对锁数据进行 expire 续期操作</li>
<li>在解锁成功后，会负责关闭 watchDog，回收协程资源. （由于看门狗续期操作会先检查锁的所有权再延期数据，因此实际上使用方只要删除了锁数据，续期操作就不会生效了. 回收看门狗协程是为了规避协程泄漏问题）</li>
</ul></section></section>
<section><h1>4. RedLock实现原理<a href="#4-redlock实现原理"><span>#</span></a></h1><p>本文 1.3 小节提出了因 redis 走的是注重于高可用性的 AP 流派，因此存在数据弱一致的问题，进而导致 redis 分布式锁可能出现同时被多方持有的情况. 本章开始，基于 red lock 红锁机制尝试提出这个问题的解决方案。</p><section><h2>4.1 多数派原则<a href="#41-多数派原则"><span>#</span></a></h2><p>所谓多数派原则，就是做出一项决议之前，让所有的参与者进行投票表决，只有投赞同票的人数达到参与者总人数的一半以上成为多数派时，这项决议才被通过。</p><p>在红锁 RedLock 实现中，会基于多数派准则进行 CAP 中一致性 C 和可用性 A 之间矛盾的缓和，保证在 RedLock 下所有 redis 节点中达到半数以上节点可用时，整个红锁就能够正常提供服务。</p></section><section><h2>4.2 红锁 redLock<a href="#42-红锁-redlock"><span>#</span></a></h2><p>红锁 Redlock 全称 redis distribution lock，是 redis 作者 antirez 提出的一种分布式锁实现方案。加锁成功的判断条件和Raft中向Candidate节点发起投票选举Leader的过程类似，都是基于多数派原则。</p><p>在红锁的实现中：</p><ul>
<li>我们假定集群中有 2N+1个 redis 节点（通常将节点总数设置为奇数，有利于多数派原则的执行效率）</li>
<li>这些 redis 节点彼此间是相互独立的，不存在从属关系</li>
<li>每次客户端尝试进行加锁操作时，会同时对2N+1个节点发起加锁请求</li>
<li>每次客户端向一个节点发起加锁请求时，会设定一个很小的请求处理超时阈值</li>
<li>客户端依次对2N+1个节点发起加锁请求，只有在小于请求处理超时阈值的时间内完成了加锁操作，才视为一笔加锁成功的请求</li>
<li>过完2N+1个节点后，统计加锁成功的请求数量</li>
<li>倘若加锁请求成功数量大于等于N+1（多数派），则视为红锁加锁成功</li>
<li>倘若加锁请求成功数量小于N+1，视为红锁加锁失败，此时会遍历2N+1个节点进行解锁操作，有利于资源回收，提供后续使用方的取锁效率</li>
</ul></section></section>]]></content>
    <author><name>Zowely</name></author>
    <category term="Go源码" />
    <category term="Golang" />
    <category term="源码解析" />
    <category term="分布式系统" />
    <category term="并发编程" />
    <category term="面试" />
    <category term="Redis" />
  </entry>
  <entry>
    <title>Golang 分布式锁实现原理</title>
    <link href="https://www.lansganbs.cn/posts/go%E6%BA%90%E7%A0%81/golang-%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86/" rel="alternate" type="text/html" />
    <id>https://www.lansganbs.cn/posts/go%E6%BA%90%E7%A0%81/golang-%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86/</id>
    <published>2026-01-22T00:00:00.000Z</published>
    <updated>2026-01-24T22:24:12.000Z</updated>
    <summary>本文将深入探讨 Golang 中分布式锁的实现原理，涵盖其基本概念、常见实现方式以及在实际应用中的优势和挑战。</summary>
    <content type="html"><![CDATA[<section><h1>1. 分布式锁<a href="#1-分布式锁"><span>#</span></a></h1><section><h2>1.1 使用场景<a href="#11-使用场景"><span>#</span></a></h2><p>在分布式场景中，有时我们需要跨域多个物理节点执行加锁操作，因此我们就需要依赖到类似于 redis、mysql 这样的状态存储组件，在此基础之上实现所谓的“分布式锁”技术。</p></section><section><h2>1.2 核心性质<a href="#12-核心性质"><span>#</span></a></h2><p>分布式锁需要满足以下几个核心性质：</p><ul>
<li><strong>独占性</strong>：同一时刻只能有一个取锁方占用。</li>
<li><strong>健壮性</strong>：锁持有者在崩溃后，锁能够被其他节点重新获取。</li>
<li><strong>对称性</strong>：获取锁和释放锁的操作必须为同一身份，不允许非法释放他人持有的锁。</li>
<li><strong>可用性</strong>：当提供分布式的基础组件存在少量节点发生故障时，不应该影响到分布式锁的正常使用。</li>
</ul></section><section><h2>1.3 实现类型<a href="#13-实现类型"><span>#</span></a></h2><p></p><figure><img src="https://www.lansganbs.cn/images/article/Go%E6%BA%90%E7%A0%81/Golang%20%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86/%E5%AE%9E%E7%8E%B0%E7%B1%BB%E5%9E%8B.png" alt="实现类型.png" /><figcaption>实现类型.png</figcaption></figure><p></p><p>分布式锁的实现类型主要有以下几种：</p><ul>
<li><strong>主动轮询型</strong>：客户端通过不断地向分布式存储组件发送请求，尝试获取锁。类似于单机锁的 CAS + 自旋。</li>
<li><strong>watch回调型</strong>：客户端在获取锁失败后，会创建 watcher 监听锁的释放事件，随后不再主动获取锁；当锁被释放时，watcher 会收到通知，从而尝试重新获取锁。</li>
</ul></section></section>
<section><h1>2. 主动轮询型<a href="#2-主动轮询型"><span>#</span></a></h1><section><h2>2.1 实现思路<a href="#21-实现思路"><span>#</span></a></h2><p></p><figure><img src="https://www.lansganbs.cn/images/article/Go%E6%BA%90%E7%A0%81/Golang%20%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86/%E4%B8%BB%E5%8A%A8%E8%BD%AE%E8%AF%A2%E5%9E%8B.png" alt="主动轮询型.png" /><figcaption>主动轮询型.png</figcaption></figure><p></p><p>主动轮询型分布式锁的实现思路为：</p><ul>
<li>针对于同一把分布式锁，使用同一条数据进行标识（以 redis 为例，则为同一个 key 对应的 kv 数据记录）</li>
<li>假如在存储介质成功插入了该条数据（要求之前该 key 对应的数据不存在），则被认定为加锁成功</li>
<li>把从存储介质中删除该条数据这一行为理解为释放锁操作</li>
<li>倘若在插入该条数据时，发现数据已经存在（锁已被他人持有），则持续轮询，直到数据被他人删除（他人释放锁），并由自身完成数据插入动作为止（取锁成功）</li>
<li>由于是并发场景，需要保证
<ul>
<li>检查数据是否已被插入</li>
<li>数据不存在则插入数据</li>
<li>这两个步骤之间是原子化不可拆分的（在 redis 中是 set only if not exist —— SETNX 操作）</li>
</ul>
</li>
</ul></section><section><h2>2.2 技术选型<a href="#22-技术选型"><span>#</span></a></h2><p>常见的分布式存储组件有 redis、mysql、zookeeper 等。</p><p>setnx 使用文档：<a href="https://redis.io/commands/setnx/" target="_blank">https://redis.io/commands/setnx/</a> （事实上，在 redis 2.6.12 版本之后，setnx 操作已经被弃置，官方推荐大家使用 set 指令并附加 nx 参数来实现与 setnx 指令相同的效果）</p><p>此外，redis 还支持使用 lua 脚本自定义组装同一个 redis 节点下的多笔操作形成一个具备原子性的事务。</p></section><section><h2>2.3 死锁问题<a href="#23-死锁问题"><span>#</span></a></h2><p>这项能力在 mysql 中显得捉襟见肘，不过在使用 redis 时，我们可以通过过期时间 expire time 机制得以保证。 我们通常会在插入分布式锁对应的 kv 数据时设置一个过期时间 expire time，这样即便使用方因为异常原因导致无法正常解锁，锁对应的数据项也会在达到过期时间阈值后被自动删除，实现释放分布式锁的效果。</p><p>这种过期机制的引入也带来了新的问题：因为锁的持有者并不能精确预判到自己持锁后处理业务逻辑的实际耗时，因此此处设置的过期时间只能是一个偏向于保守的经验值，假如因为一些异常情况导致占有锁的使用方在业务处理流程中的耗时超过了设置的过期时间阈值，就会导致锁被提前释放，其他取锁方可能取锁成功，最终引起数据不一致的并发问题。</p><p>针对于这个问题，在分布式锁工具 redisson 中给出了解决方案——看门狗策略（watch dog strategy）：在锁的持有方未完成业务逻辑的处理时，会持续对分布式锁的过期阈值进行延期操作。</p></section><section><h2>2.4 弱一致性问题<a href="#24-弱一致性问题"><span>#</span></a></h2><p>Redis 走的是 AP 架构，为了保证服务的可用性和吞吐量，Redis 在进行数据的主从同步时采用的是异步执行机制。</p><p>到这里问题就来了，试想一种场景：倘若使用方 A 在 Redis master 节点加锁成功，但是对应的 kv 记录在同步到 slave 之前，master 节点就宕机了。 此时未同步到这项数据的 slave 节点升为 master，这样分布式锁被 A 持有的“凭证” 就这样凭空消失了。 于是不知情的使用方 B C D 都可能加锁成功，于是就出现了一把锁被多方同时持有的问题，导致分布式锁最基本的独占性遭到破坏。也就是发生了<strong>一锁多持</strong>的问题。</p><p>关于这个问题，一个比较经典的解决方案是：RedLock 红锁。它采用了公式算法，同时也是 Redis 官方推荐的分布式锁实现方案，但是这种方法在发布之初就受到了不少质疑，比如时钟漂移问题、网络分区问题等。</p></section></section>
<section><h1>3. redis 分布式锁<a href="#3-redis-分布式锁"><span>#</span></a></h1><section><h2>3.1 sdk 介绍<a href="#31-sdk-介绍"><span>#</span></a></h2><a href="https://github.com/gomodule/redigo" target="_blank"><div><div><div><div></div><div>gomodule</div></div><div>/</div><div>redigo</div></div><div></div></div><div>Waiting for api.github.com...</div><div><div>00K</div><div>0K</div><div>0K</div><span>Waiting...</span></div></a></section></section>
<section><h1>4. watch 回调型<a href="#4-watch-回调型"><span>#</span></a></h1><section><h2>4.1 实现思路<a href="#41-实现思路"><span>#</span></a></h2><p></p><figure><img src="https://www.lansganbs.cn/images/article/Go%E6%BA%90%E7%A0%81/Golang%20%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86/watch%20%E5%9B%9E%E8%B0%83%E5%9E%8B.png" alt="watch 回调型.png" /><figcaption>watch 回调型.png</figcaption></figure><p></p><p>对于实现 watch 回调型分布式锁，一些基本要点和 2.1 小节中聊到的主动轮询型分布式锁类似：</p><ul>
<li>针对于同一把分布式锁，使用一条相同的数据进行标识（唯一、明确的 key）</li>
<li>倘若在存储介质内成功插入该条数据（要求 key 对应的数据不存在），则这一行为被认定为加锁成功</li>
<li>把从存储介质中删除该条数据这行为理解为解锁操作</li>
</ul><p>与主动轮询型分布式锁不同的是，在取锁失败时，watch 回调型分布式锁不会持续轮询，而是会 watch 监听锁的删除事件：</p><ul>
<li>倘若在插入数据时，发现该条记录已经存在，说明锁已被他人持有，此时选择监听这条数据记录的删除事件，当对应事件发生时说明锁被释放了，此时才继续尝试取锁</li>
</ul></section><section><h2>4.2 技术选型<a href="#42-技术选型"><span>#</span></a></h2><p>在实现上，我们需要依赖于提供了 watch 机制的状态存储组件，不仅能支持数据的存储和去重，还需要利用到其中的 watch 监听回调功能进行锁释放事件的订阅感知。</p><p>为满足上述诉求，我们常用的技术组件包括 etcd 和 zookeeper。</p><p>etcd 文档：<a href="https://etcd.io/" target="_blank">https://etcd.io/</a></p><p>etcd 是一款适合用于共享配置和服务发现的分布式 kv 存储组件，底层基于分布式共识算法 raft 协议保证了存储服务的强一致和高可用。</p><p>在 etcd 中提供了 watch 监听器的功能，即针对于指定范围的数据，通过与 etcd 服务端节点创建 grpc 长连接的方式持续监听变更事件。</p><p>此外，etcd 中写入数据时，还支持通过版本 revision 机制进行取锁秩序的统筹协调，是一款很适合用于实现分布式锁的组件。</p></section><section><h2>4.3 死锁问题<a href="#43-死锁问题"><span>#</span></a></h2><p>为避免死锁问题的产生，etcd 中提供了租约 lease 机制。租约，顾名思义，是一份具有时效性的协议，一旦达到租约上规定的截止时间，租约就会失去效力。 同时，etcd 中还提供了续约机制（keepAlive），用户可以通过续约操作来延迟租约的过期时间。</p><p>那么，我们如何来利用租约 lease 机制解决分布式锁中可能存在的死锁问题呢？实现思路如下：</p><ul>
<li>用户可以先申请一份租约，设定好租约的截止时间</li>
<li>异步启动一个续约协程，负责在业务逻辑处理完成前，按照一定的时间节奏持续进行续约操作</li>
<li>在执行取锁动作，将对应于锁的 kv 数据和租约进行关联绑定，使得锁数据和租约拥有相同的过期时间属性</li>
</ul><p>上面这个思路就类似于 Redis 的看门狗机制。在这样的设定之下，倘若分布式锁的持有者出现异常状况导致无法正常解锁，则可以通过租约的过期机制完成对分布式锁的释放，死锁问题因此得以规避。 此外，锁的使用方可以将租约的初始过期时间设定为一个偏小的值，并通过续约机制来对租约的生效周期进行动态延长。 可以看到，此处 etcd 中的租约及续约机制，实现了与 redisson 中 watch dog 机制类似的效果。</p></section><section><h2>4.4 惊群效应<a href="#44-惊群效应"><span>#</span></a></h2><p>在 watch 回调型分布式锁中，惊群效应主要体现在当锁被释放时，所有监听该锁释放事件的客户端都会收到通知，从而引发一场“抢锁大战”，这可能会导致系统资源的过度消耗和性能下降。</p><p>为了解决惊群效应问题，etcd 中提供了前缀 prefix 机制以及版本 revision 机制。 这和 zookeeper 中的临时顺序节点（ephemeral sequential node）有异曲同工之妙。</p><ul>
<li>对于同一把分布式锁，锁记录数据的 key 拥有共同的前缀 prefix，作为锁的标识</li>
<li>每个取锁方取锁时，会以锁前缀 prefix 拼接上自身的身份标识（租约 id），生成完整的 lock key。 因此各取锁方完整的 lock key 都是互不相同的（只是有着相同的前缀），理论上所有取锁方都能成功把锁记录数据插入到 etcd 中</li>
<li>每个取锁方插入锁记录数据时，会获得自身 lock key 处在锁前缀 prefix 范围下唯一且递增的版本号 revision</li>
<li>取锁方插入加锁记录数据不意味着加锁成功，而是需要在插入数据后查询一次锁前缀 prefix 下的记录列表，判定自身 lock key 对应的 revision 是不是其中最小的，如果是的话，才表示加锁成功</li>
<li>如果锁被他人占用，取锁方会 watch 监听 revision 小于自己但最接近自己的那个 lock key 的删除事件。</li>
</ul><p>这样所有的取锁方就会在 revision 机制的协调下，根据取锁序号（revision）的先后顺序排成一条队列，每当锁被释放，只会惊动到下一顺位的取锁方，惊群问题得以避免。</p><p></p><figure><img src="https://www.lansganbs.cn/images/article/Go%E6%BA%90%E7%A0%81/Golang%20%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86/%E6%83%8A%E7%BE%A4%E6%95%88%E5%BA%94.png" alt="惊群效应.png" /><figcaption>惊群效应.png</figcaption></figure><p></p></section></section>
<section><h1>5. etcd 分布式锁<a href="#5-etcd-分布式锁"><span>#</span></a></h1><section><h2>5.1 sdk 介绍<a href="#51-sdk-介绍"><span>#</span></a></h2><p>etcd 开源地址</p><a href="https://github.com/etcd-io/etcd" target="_blank"><div><div><div><div></div><div>etcd-io</div></div><div>/</div><div>etcd</div></div><div></div></div><div>Waiting for api.github.com...</div><div><div>00K</div><div>0K</div><div>0K</div><span>Waiting...</span></div></a></section></section>]]></content>
    <author><name>Zowely</name></author>
    <category term="Go源码" />
    <category term="Golang" />
    <category term="源码解析" />
    <category term="分布式系统" />
    <category term="并发编程" />
    <category term="面试" />
    <category term="Redis" />
    <category term="Etcd" />
  </entry>
  <entry>
    <title>单机锁实现原理</title>
    <link href="https://www.lansganbs.cn/posts/go%E6%BA%90%E7%A0%81/%E5%8D%95%E6%9C%BA%E9%94%81%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86/" rel="alternate" type="text/html" />
    <id>https://www.lansganbs.cn/posts/go%E6%BA%90%E7%A0%81/%E5%8D%95%E6%9C%BA%E9%94%81%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86/</id>
    <published>2026-01-18T00:00:00.000Z</published>
    <updated>2026-01-18T16:04:53.000Z</updated>
    <summary>介绍 Go 语言中单机锁（sync.Mutex）的实现原理，包括其内部结构、锁的获取与释放机制，以及饥饿模式的引入原因。</summary>
    <content type="html"><![CDATA[<section><h1>1. Sync.Mutex<a href="#1-syncmutex"><span>#</span></a></h1><section><h2>1.1 单机锁实现框架<a href="#11-单机锁实现框架"><span>#</span></a></h2><section><h3>1.1.1 上锁/解锁<a href="#111-上锁解锁"><span>#</span></a></h3><p>通过<code>Mutex</code>内的一个状态值<code>state</code>来表示锁的状态：上锁把0变成1，解锁把1变成0。</p><p>上锁时假设<code>state</code>为0，使用原子操作将<code>state</code>从0变成1，如果成功则表示上锁成功。如果失败，说明<code>state</code>不为0，表示锁已经被其他协程持有，此时需要进入等待队列等待。</p><p>这是一个原子操作，保证了多个协程同时尝试上锁时不会出现竞态条件。</p></section><section><h3>1.1.2 抢占锁的两种策略<a href="#112-抢占锁的两种策略"><span>#</span></a></h3><ul>
<li>阻塞/唤醒：当协程尝试上锁失败时，会将自己加入等待队列，并进入阻塞状态，直到被唤醒。这是悲观锁。</li>
<li>自旋+CAS：协程在尝试上锁失败后，不立即阻塞，而是进行一段时间的忙等待（自旋），尝试再次获取锁。如果在自旋期间锁被释放，则可以直接获取锁，避免了阻塞和唤醒的开销。这是乐观锁。</li>
</ul><p>正常模式下，Goroutine会先进行自旋4次尝试获取锁，如果自旋失败则进入阻塞状态。
饥饿模式下，Goroutine会直接进入阻塞状态，到阻塞队列末尾，不进行自旋。</p><p>需要注意的是，不管在哪种模式下，阻塞队列队头的Goroutine在被唤醒后都会得到不同程度的优待：在正常模式下，队头Goroutine会进行正常抢占锁，但是抢占失败依然会被放在队头；在饥饿模式下，队头Goroutine会直接获取锁。</p></section><section><h3>1.1.3 饥饿模式<a href="#113-饥饿模式"><span>#</span></a></h3><p>饥饿模式：阻塞队列中的锁超过 1ms 而不得的Goroutine会被标记为饥饿，整个锁进入饥饿模式，将抢锁流程由非公平机制转为公平机制。饥饿模式下，保证FIFO，阻塞队列中的头部Goroutine获取锁，避免饥饿现象。</p><p>饥饿模式和正常模式的切换条件就是看阻塞队列中是否有Goroutine超过1ms而不得。</p></section></section><section><h2>1.2 数据结构<a href="#12-数据结构"><span>#</span></a></h2><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>type</span><span> </span><span>Mutex</span><span> </span><span>struct</span><span> {</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>state </span><span>int32</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>sema  </span><span>uint32</span></div></div><div><div><div>4</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li><code>state</code>：表示锁的状态，包括是否被锁定、是否有等待的Goroutine、是否处于饥饿模式等信息。</li>
<li><code>sema</code>：用于实现阻塞和唤醒机制的信号量。</li>
</ul><section><h3>1.2.1 几个全集变量<a href="#121-几个全集变量"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>const</span><span> (</span></div></div><div><div><div>2</div></div><div><span>    </span><span>mutexLocked</span><span> </span><span>=</span><span> </span><span>1</span><span> </span><span>&lt;&lt;</span><span> </span><span>iota</span><span> </span><span>// mutex is locked</span></div></div><div><div><div>3</div></div><div><span>    </span><span>mutexWoken</span></div></div><div><div><div>4</div></div><div><span>    </span><span>mutexStarving</span></div></div><div><div><div>5</div></div><div><span>    </span><span>mutexWaiterShift</span><span> </span><span>=</span><span> </span><span>iota</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>    </span><span>starvationThresholdNs</span><span> </span><span>=</span><span> </span><span>1</span><span>e</span><span>6</span></div></div><div><div><div>8</div></div><div><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li><code>mutexLocked</code>：表示锁被持有。</li>
<li><code>mutexWoken</code>：表示有Goroutine被唤醒。</li>
<li><code>mutexStarving</code>：表示锁处于饥饿模式。</li>
<li><code>mutexWaiterShift</code>：表示等待Goroutine的数量在state中的偏移量。</li>
<li><code>starvationThresholdNs</code>：表示饥饿模式的阈值，超过这个时间的Goroutine会被标记为饥饿。</li>
</ul></section><section><h3>1.2.2 state 字段详述<a href="#122-state-字段详述"><span>#</span></a></h3><p><code>state</code>字段是一个32位整数，其中每一位或一组位表示不同的状态信息：</p><ul>
<li>第0位（<code>mutexLocked</code>）：表示锁是否被持有。如果为1，表示锁被持有；如果为0，表示锁未被持有。</li>
<li>第1位（<code>mutexWoken</code>）：表示是否有Goroutine被唤醒。如果为1，表示有Goroutine被唤醒；如果为0，表示没有Goroutine被唤醒。</li>
<li>第2位（<code>mutexStarving</code>）：表示锁是否处于饥饿模式。如果为1，表示锁处于饥饿模式；如果为0，表示锁未处于饥饿模式。</li>
<li>第3位及之后的位（<code>mutexWaiterShift</code>及之后的位）：表示等待Goroutine的数量。通过将等待Goroutine的数量左移<code>mutexWaiterShift</code>位，可以将其存储在<code>state</code>字段的高位部分。最多可以表示 <span><span>229−12^{29}-1</span><span><span><span></span><span><span>2</span><span><span><span><span><span><span></span><span><span><span>29</span></span></span></span></span></span></span></span></span><span></span><span>−</span><span></span></span><span><span></span><span>1</span></span></span></span> 个等待Goroutine。</li>
</ul></section></section><section><h2>1.3 Mutex.Lock()<a href="#13-mutexlock"><span>#</span></a></h2><section><h3>1.3.1 Lock 方法主干<a href="#131-lock-方法主干"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> (</span><span>m </span><span>*</span><span>Mutex</span><span>) </span><span>Lock</span><span>() {</span></div></div><div><div><div>2</div></div><div><span>    </span><span>if</span><span> atomic.</span><span>CompareAndSwapInt32</span><span>(</span><span>&amp;</span><span>m.state, </span><span>0</span><span>, mutexLocked) {</span></div></div><div><div><div>3</div></div><div><span>        </span><span>return</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>5</div></div><div><span>    </span><span>// Slow path (outlined so that the fast path can be inlined)</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>m.</span><span>lockSlow</span><span>()</span></div></div><div><div><div>7</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>首先尝试CAS获取锁，如果失败，说明锁已经被其他协程持有，进入慢路径<code>lockSlow</code>。这是在正常模式下的上锁流程，因为<code>atomic.CompareAndSwapInt32(&amp;m.state, 0, mutexLocked)</code>保证了state为0，一定不是饥饿模式。</p></section><section><h3>1.3.2 Mutex.lockSlow()<a href="#132-mutexlockslow"><span>#</span></a></h3><section><h4>1.3.2.1 几个局部变量<a href="#1321-几个局部变量"><span>#</span></a></h4><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> (</span><span>m </span><span>*</span><span>Mutex</span><span>) </span><span>lockSlow</span><span>() {</span></div></div><div><div><div>2</div></div><div><span>    </span><span>var</span><span> waitStartTime </span><span>int64</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>starving </span><span>:=</span><span> </span><span>false</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>awoke </span><span>:=</span><span> </span><span>false</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>iter </span><span>:=</span><span> </span><span>0</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>old </span><span>:=</span><span> m.state</span></div></div><div><div><div>7</div></div><div><span>    </span><span>for</span><span> {</span></div></div><div><div><div>8</div></div><div><span>        </span><span>// ...</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>10</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li><code>waitStartTime</code>：记录Goroutine开始等待的时间，用于判断是否进入饥饿模式。</li>
<li><code>starving</code>：表示当前Goroutine是否处于饥饿状态。</li>
<li><code>awoke</code>：表示当前Goroutine是否被唤醒。</li>
<li><code>iter</code>：表示当前Goroutine尝试获取锁的次数，用于控制自旋次数。</li>
<li><code>old</code>：记录当前的state值，用于后续的CAS操作。</li>
</ul></section><section><h4>1.3.2.2 自旋空转<a href="#1322-自旋空转"><span>#</span></a></h4><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> (</span><span>m </span><span>*</span><span>Mutex</span><span>) </span><span>lockSlow</span><span>() {</span></div></div><div><div><div>2</div></div><div><span>    </span><span>// ...</span></div></div><div><div><div>3</div></div><div><span>    </span><span>for</span><span> {</span></div></div><div><div><div>4</div></div><div><span>        </span><span>// 进入该 if 分支，说明抢锁失败，处于正常模式，且仍满足自旋条件</span></div></div><div><div><div>5</div></div><div><span>        </span><span>if</span><span> old</span><span>&amp;</span><span>(mutexLocked</span><span>|</span><span>mutexStarving) </span><span>==</span><span> mutexLocked </span><span>&amp;&amp;</span><span> </span><span>runtime_canSpin</span><span>(iter) {</span></div></div><div><div><div>6</div></div><div><span>            </span><span>// 进入自旋后处理环节</span></div></div><div><div><div>7</div></div><div><span>            </span><span>// ...</span></div></div><div><div><div>8</div></div><div><span>            </span><span>continue</span></div></div><div><div><div>9</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>10</div></div><div><span>        </span><span>// ...</span></div></div><div><div><div>11</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>12</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li>假如满足三个条件：1. 当前锁处于正常模式 2. 锁被持有 3. 自旋次数未达上限，则进行自旋。则进入自旋后处理环节。</li>
<li>进入自旋后处理环节中，假如当前锁有尚未唤醒的阻塞协程，则通过 CAS 操作将 state 的 mutexWoken 标识置为 1，将局部变量 awoke 置为 true。</li>
<li>调用 runtime_doSpin 告知调度器 P 当前处于自旋模式。</li>
<li>更新自旋次数 iter 和锁状态值 old。</li>
<li>通过 continue 语句进入下一轮尝试。</li>
</ul></section><section><h4>1.3.2.3 state 新值构造<a href="#1323-state-新值构造"><span>#</span></a></h4><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> (</span><span>m </span><span>*</span><span>Mutex</span><span>) </span><span>lockSlow</span><span>() {</span></div></div><div><div><div>2</div></div><div><span>    </span><span>// ...</span></div></div><div><div><div>3</div></div><div><span>    </span><span>for</span><span> {</span></div></div><div><div><div>4</div></div><div><span>        </span><span>// 自旋抢锁失败后处理 ...</span></div></div><div><div><div>5</div></div><div><span><span>        </span></span><span>new </span><span>:=</span><span> old</span></div></div><div><div><div>6</div></div><div><span>        </span><span>if</span><span> old</span><span>&amp;</span><span>mutexStarving </span><span>==</span><span> </span><span>0</span><span> {</span></div></div><div><div><div>7</div></div><div><span><span>            </span></span><span>new </span><span>|=</span><span> mutexLocked</span></div></div><div><div><div>8</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>9</div></div><div><span>        </span><span>if</span><span> old</span><span>&amp;</span><span>(mutexLocked</span><span>|</span><span>mutexStarving) </span><span>!=</span><span> </span><span>0</span><span> {</span></div></div><div><div><div>10</div></div><div><span><span>            </span></span><span>new </span><span>+=</span><span> </span><span>1</span><span> </span><span>&lt;&lt;</span><span> mutexWaiterShift</span></div></div><div><div><div>11</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>12</div></div><div><span>        </span><span>if</span><span> starving </span><span>&amp;&amp;</span><span> old</span><span>&amp;</span><span>mutexLocked </span><span>!=</span><span> </span><span>0</span><span> {</span></div></div><div><div><div>13</div></div><div><span><span>            </span></span><span>new </span><span>|=</span><span> mutexStarving</span></div></div><div><div><div>14</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>15</div></div><div><span>        </span><span>if</span><span> awoke {</span></div></div><div><div><div>16</div></div><div><span><span>            </span></span><span>new </span><span>&amp;^=</span><span> mutexWoken</span></div></div><div><div><div>17</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>18</div></div><div><span>        </span><span>// ...</span></div></div><div><div><div>19</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>20</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><ul>
<li>从自旋中走出来后，会存在两种分支，要么加锁成功，要么陷入自锁，不论是何种情形，都会先对 sync.Mutex 的状态新值 new 进行更新；</li>
<li>倘若当前是非饥饿模式，则在新值 new 中置为已加锁，即尝试抢锁；</li>
<li>倘若旧值为已加锁或者处于饥饿模式，则当前 goroutine 在这一轮注定无法抢锁成功，可以直接令新值的阻塞协程数加1；</li>
<li>倘若当前进入饥饿模式且旧值已加锁，则将新值置为饥饿模式；</li>
<li>倘若局部变量标识是已有唤醒协程抢锁，说明 Mutex.state 中的 mutexWoken 是被当前 goroutine 置为 1 的，但由于当前 goroutine 接下来要么抢锁成功，要么被阻塞挂起，因此需要在新值中将该 mutexWoken 标识更新置 0.</li>
</ul></section><section><h4>1.3.2.4 state 新旧值替换<a href="#1324-state-新旧值替换"><span>#</span></a></h4><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> (</span><span>m </span><span>*</span><span>Mutex</span><span>) </span><span>lockSlow</span><span>() {</span></div></div><div><div><div>2</div></div><div><span>    </span><span>// ...</span></div></div><div><div><div>3</div></div><div><span>    </span><span>for</span><span> {</span></div></div><div><div><div>4</div></div><div><span>        </span><span>// 自旋抢锁失败后处理 ...</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>        </span><span>// new/old 状态值更新 ...</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>        </span><span>if</span><span> atomic.</span><span>CompareAndSwapInt32</span><span>(</span><span>&amp;</span><span>m.state, old, new) {</span></div></div><div><div><div>9</div></div><div><span>            </span><span>// case1 加锁成功</span></div></div><div><div><div>10</div></div><div><span>            </span><span>// case2 将当前 goroutine 挂起</span></div></div><div><div><div>11</div></div><div>
</div></div><div><div><div>12</div></div><div><span>            </span><span>// ...</span></div></div><div><div><div>13</div></div><div><span><span>        </span></span><span>} </span><span>else</span><span> {</span></div></div><div><div><div>14</div></div><div><span><span>            </span></span><span>old </span><span>=</span><span> m.state</span></div></div><div><div><div>15</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>16</div></div><div><span>        </span><span>// ...</span></div></div><div><div><div>17</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>18</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><ul>
<li>通过 CAS 操作，用构造的新值替换旧值；</li>
<li>倘若失败（即旧值被其他 goroutine 介入提前修改导致不符合预期），则将旧值更新为此刻的 <code>Mutex.state</code>，并开启一轮新的循环；</li>
<li>倘若 CAS 替换成功，则进入最后一轮的二择一局面：
<ul>
<li>I）倘若当前 goroutine 加锁成功，则返回；</li>
<li>II）倘若失败，则将 goroutine 挂起并添加到阻塞队列。</li>
</ul>
</li>
</ul></section><section><h4>1.3.2.5 上锁成功分支<a href="#1325-上锁成功分支"><span>#</span></a></h4><p>加锁成功的分支</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> (</span><span>m </span><span>*</span><span>Mutex</span><span>) </span><span>lockSlow</span><span>() {</span></div></div><div><div><div>2</div></div><div><span>    </span><span>// ...</span></div></div><div><div><div>3</div></div><div><span>    </span><span>for</span><span> {</span></div></div><div><div><div>4</div></div><div><span>        </span><span>// 自旋抢锁失败后处理 ...</span></div></div><div><div><div>5</div></div><div><span>        </span><span>// new/old 状态值更新 ...</span></div></div><div><div><div>6</div></div><div><span>        </span><span>if</span><span> atomic.</span><span>CompareAndSwapInt32</span><span>(</span><span>&amp;</span><span>m.state, old, new) {</span></div></div><div><div><div>7</div></div><div><span>            </span><span>if</span><span> old</span><span>&amp;</span><span>(mutexLocked</span><span>|</span><span>mutexStarving) </span><span>==</span><span> </span><span>0</span><span> {</span></div></div><div><div><div>8</div></div><div><span>                </span><span>break</span></div></div><div><div><div>9</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span>            </span><span>// ...</span></div></div><div><div><div>12</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>13</div></div><div><span>        </span><span>// ...</span></div></div><div><div><div>14</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>15</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><ul>
<li>延续 1.3.2.4 的思路，此时已经成功将 <code>Mutex.state</code> 由旧值替换为新值；</li>
<li>接下来进行判断：倘若旧值是未加锁状态且为正常模式，则意味着加锁标识位正是由当前 goroutine 完成的更新，说明加锁成功，返回即可；</li>
<li>倘若旧值中锁未释放或者处于饥饿模式，则当前 goroutine 需要进入阻塞队列挂起。</li>
</ul></section><section><h4>1.3.2.6 阻塞挂起<a href="#1326-阻塞挂起"><span>#</span></a></h4><p>阻塞挂起 goroutine</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> (</span><span>m </span><span>*</span><span>Mutex</span><span>) </span><span>lockSlow</span><span>() {</span></div></div><div><div><div>2</div></div><div><span>    </span><span>// ...</span></div></div><div><div><div>3</div></div><div><span>    </span><span>for</span><span> {</span></div></div><div><div><div>4</div></div><div><span>        </span><span>// 自旋抢锁失败后处理 ...</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>        </span><span>// new/old 状态值更新 ...</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>        </span><span>if</span><span> atomic.</span><span>CompareAndSwapInt32</span><span>(</span><span>&amp;</span><span>m.state, old, new) {</span></div></div><div><div><div>9</div></div><div><span>            </span><span>// 加锁成功后返回的逻辑分支 ...</span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span><span>            </span></span><span>queueLifo </span><span>:=</span><span> waitStartTime </span><span>!=</span><span> </span><span>0</span></div></div><div><div><div>12</div></div><div><span>            </span><span>if</span><span> waitStartTime </span><span>==</span><span> </span><span>0</span><span> {</span></div></div><div><div><div>13</div></div><div><span><span>                </span></span><span>waitStartTime </span><span>=</span><span> </span><span>runtime_nanotime</span><span>()</span></div></div><div><div><div>14</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>15</div></div><div><span>            </span><span>runtime_SemacquireMutex</span><span>(</span><span>&amp;</span><span>m.sema, queueLifo, </span><span>1</span><span>)</span></div></div><div><div><div>16</div></div><div><span>            </span><span>// ...</span></div></div><div><div><div>17</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>18</div></div><div><span>        </span><span>// ...</span></div></div><div><div><div>19</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>20</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>承接上节，走到此处的情形有两种：要么是抢锁失败，要么是锁已处于饥饿模式，而当前 goroutine 不是从阻塞队列被唤起的协程。不论处于哪种情形，当前 goroutine 都面临被阻塞挂起的命运。</p><ul>
<li>基于 <code>queueLifo</code> 标识当前 goroutine 是从阻塞队列被唤起的“老客”还是新进流程的“新客”；</li>
<li>倘若等待的起始时间为 0，则为新客；倘若非 0，则为老客；</li>
<li>倘若是新客，则对等待的起始时间进行更新，置为当前时刻的 ns 时间戳；</li>
<li>将当前 goroutine 添加到阻塞队列中：倘若是老客则挂入队头；倘若是新客则挂入队尾；</li>
<li>挂起当前 goroutine。</li>
</ul></section><section><h4>1.3.2.7 从阻塞态被唤醒<a href="#1327-从阻塞态被唤醒"><span>#</span></a></h4><p>goroutine 被唤醒后</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> (</span><span>m </span><span>*</span><span>Mutex</span><span>) </span><span>lockSlow</span><span>() {</span></div></div><div><div><div>2</div></div><div><span>    </span><span>// ...</span></div></div><div><div><div>3</div></div><div><span>    </span><span>for</span><span> {</span></div></div><div><div><div>4</div></div><div><span>        </span><span>// 自旋抢锁失败后处理...</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>        </span><span>// new/old 状态值更新 ...</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>        </span><span>if</span><span> atomic.</span><span>CompareAndSwapInt32</span><span>(</span><span>&amp;</span><span>m.state, old, new) {</span></div></div><div><div><div>9</div></div><div><span>            </span><span>// 加锁成功后返回的逻辑分支 ...</span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span>            </span><span>// 挂起前处理 ...</span></div></div><div><div><div>12</div></div><div><span>            </span><span>runtime_SemacquireMutex</span><span>(</span><span>&amp;</span><span>m.sema, queueLifo, </span><span>1</span><span>)</span></div></div><div><div><div>13</div></div><div>
</div></div><div><div><div>14</div></div><div><span>            </span><span>// 从阻塞队列被唤醒了</span></div></div><div><div><div>15</div></div><div><span><span>            </span></span><span>starving </span><span>=</span><span> starving </span><span>||</span><span> </span><span>runtime_nanotime</span><span>()</span><span>-</span><span>waitStartTime </span><span>&gt;</span><span> starvationThresholdNs</span></div></div><div><div><div>16</div></div><div><span><span>            </span></span><span>old </span><span>=</span><span> m.state</span></div></div><div><div><div>17</div></div><div><span>            </span><span>if</span><span> old</span><span>&amp;</span><span>mutexStarving </span><span>!=</span><span> </span><span>0</span><span> {</span></div></div><div><div><div>18</div></div><div><span><span>                </span></span><span>delta </span><span>:=</span><span> </span><span>int32</span><span>(mutexLocked </span><span>-</span><span> </span><span>1</span><span>&lt;&lt;</span><span>mutexWaiterShift)</span></div></div><div><div><div>19</div></div><div><span>                </span><span>if</span><span> </span><span>!</span><span>starving </span><span>||</span><span> old</span><span>&gt;&gt;</span><span>mutexWaiterShift </span><span>==</span><span> </span><span>1</span><span> {</span></div></div><div><div><div>20</div></div><div><span><span>                    </span></span><span>delta </span><span>-=</span><span> mutexStarving</span></div></div><div><div><div>21</div></div><div><span><span>                </span></span><span>}</span></div></div><div><div><div>22</div></div><div><span><span>                </span></span><span>atomic.</span><span>AddInt32</span><span>(</span><span>&amp;</span><span>m.state, delta)</span></div></div><div><div><div>23</div></div><div><span>                </span><span>break</span></div></div><div><div><div>24</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>25</div></div><div><span><span>            </span></span><span>awoke </span><span>=</span><span> </span><span>true</span></div></div><div><div><div>26</div></div><div><span><span>            </span></span><span>iter </span><span>=</span><span> </span><span>0</span></div></div><div><div><div>27</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>28</div></div><div><span>        </span><span>// ...</span></div></div><div><div><div>29</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>30</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><ul>
<li>走入此处，说明当前 goroutine 是从 <code>Mutex</code> 的阻塞队列中被唤起的；</li>
<li>判断是否需要进入饥饿模式：倘若当前 goroutine 在阻塞队列中等待时间长达 1ms，则更新 <code>starving</code> 变量，并在下一轮循环中完成对 <code>Mutex.state</code> 中 <code>mutexStarving</code> 标识位的更新；</li>
<li>获取此时锁的状态，通过 <code>old</code> 存储；</li>
<li>倘若此时锁是饥饿模式，则当前 goroutine 无需竞争可以直接获得锁；</li>
<li>饥饿模式下，goroutine 获取锁前需要更新锁的状态，包含 <code>mutexLocked</code>、阻塞队列等待 goroutine 数以及 <code>mutexStarving</code> 三个信息；均通过 <code>delta</code> 变量记录差值，最终通过原子操作添加到 <code>Mutex.state</code> 中；</li>
<li><code>mutexStarving</code> 的更新要作前置判断：倘若当前 <code>starving</code> 为 false，或者当前 goroutine 就是阻塞队列的最后一个 goroutine，则将 <code>Mutex.state</code> 置为正常模式。</li>
</ul></section></section></section><section><h2>1.4 Unlock<a href="#14-unlock"><span>#</span></a></h2><section><h3>1.4.1 Unlock 方法主干<a href="#141-unlock-方法主干"><span>#</span></a></h3><p>Unlock方法主流程</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> (</span><span>m </span><span>*</span><span>Mutex</span><span>) </span><span>Unlock</span><span>() {</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>new </span><span>:=</span><span> atomic.</span><span>AddInt32</span><span>(</span><span>&amp;</span><span>m.state, </span><span>-</span><span>mutexLocked)</span></div></div><div><div><div>3</div></div><div><span>    </span><span>if</span><span> new </span><span>!=</span><span> </span><span>0</span><span> {</span></div></div><div><div><div>4</div></div><div><span><span>        </span></span><span>m.</span><span>unlockSlow</span><span>(new)</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>6</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li>通过原子操作解锁；</li>
<li>倘若解锁时发现，目前参与竞争的仅有自身一个 goroutine，则直接返回即可；</li>
<li>倘若发现锁中还有阻塞协程，则走入 <code>unlockSlow</code> 分支。</li>
</ul></section><section><h3>1.4.2 unlockSlow<a href="#142-unlockslow"><span>#</span></a></h3><p>解锁时唤醒等锁的阻塞 goroutine</p><section><h4>1.4.2.1 未加锁的异常情形<a href="#1421-未加锁的异常情形"><span>#</span></a></h4><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> (</span><span>m </span><span>*</span><span>Mutex</span><span>) </span><span>unlockSlow</span><span>(</span><span>new</span><span> </span><span>int32</span><span>) {</span></div></div><div><div><div>2</div></div><div><span>    </span><span>if</span><span> (new</span><span>+</span><span>mutexLocked)</span><span>&amp;</span><span>mutexLocked </span><span>==</span><span> </span><span>0</span><span> {</span></div></div><div><div><div>3</div></div><div><span>        </span><span>fatal</span><span>(</span><span>"sync: unlock of unlocked mutex"</span><span>)</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>5</div></div><div><span>    </span><span>// ...</span></div></div><div><div><div>6</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>解锁时倘若发现 <code>Mutex</code> 此前未加锁，直接抛出 fatal。</p></section><section><h4>1.4.2.2 正常模式<a href="#1422-正常模式"><span>#</span></a></h4><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> (</span><span>m </span><span>*</span><span>Mutex</span><span>) </span><span>unlockSlow</span><span>(</span><span>new</span><span> </span><span>int32</span><span>) {</span></div></div><div><div><div>2</div></div><div><span>    </span><span>// ...</span></div></div><div><div><div>3</div></div><div><span>    </span><span>if</span><span> new</span><span>&amp;</span><span>mutexStarving </span><span>==</span><span> </span><span>0</span><span> {</span></div></div><div><div><div>4</div></div><div><span><span>        </span></span><span>old </span><span>:=</span><span> new</span></div></div><div><div><div>5</div></div><div><span>        </span><span>for</span><span> {</span></div></div><div><div><div>6</div></div><div><span>            </span><span>if</span><span> old</span><span>&gt;&gt;</span><span>mutexWaiterShift </span><span>==</span><span> </span><span>0</span><span> </span><span>||</span><span> old</span><span>&amp;</span><span>(mutexLocked</span><span>|</span><span>mutexWoken</span><span>|</span><span>mutexStarving) </span><span>!=</span><span> </span><span>0</span><span> {</span></div></div><div><div><div>7</div></div><div><span>                </span><span>return</span></div></div><div><div><div>8</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span><span>            </span></span><span>new </span><span>=</span><span> (old </span><span>-</span><span> </span><span>1</span><span>&lt;&lt;</span><span>mutexWaiterShift) </span><span>|</span><span> mutexWoken</span></div></div><div><div><div>11</div></div><div><span>            </span><span>if</span><span> atomic.</span><span>CompareAndSwapInt32</span><span>(</span><span>&amp;</span><span>m.state, old, new) {</span></div></div><div><div><div>12</div></div><div><span>                </span><span>runtime_Semrelease</span><span>(</span><span>&amp;</span><span>m.sema, </span><span>false</span><span>, </span><span>1</span><span>)</span></div></div><div><div><div>13</div></div><div><span>                </span><span>return</span></div></div><div><div><div>14</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>15</div></div><div><span><span>            </span></span><span>old </span><span>=</span><span> m.state</span></div></div><div><div><div>16</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>17</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>18</div></div><div><span>    </span><span>// ...</span></div></div><div><div><div>19</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><ul>
<li>倘若阻塞队列内无 goroutine 或者 <code>mutexLocked</code>、<code>mutexStarving</code>、<code>mutexWoken</code> 标识位任一不为零，三者均说明此时有其他活跃协程已介入，自身无需关心后续流程；</li>
<li>基于 CAS 操作将 <code>Mutex.state</code> 中的阻塞协程数减 1，倘若成功，则唤起阻塞队列头部的 goroutine，并退出；</li>
<li>倘若减少阻塞协程数的 CAS 操作失败，则更新此时的 <code>Mutex.state</code> 为新的 old 值，开启下一轮循环。</li>
</ul></section><section><h4>1.4.2.3 饥饿模式<a href="#1423-饥饿模式"><span>#</span></a></h4><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> (</span><span>m </span><span>*</span><span>Mutex</span><span>) </span><span>unlockSlow</span><span>(</span><span>new</span><span> </span><span>int32</span><span>) {</span></div></div><div><div><div>2</div></div><div><span>    </span><span>// ...</span></div></div><div><div><div>3</div></div><div><span>    </span><span>if</span><span> new</span><span>&amp;</span><span>mutexStarving </span><span>==</span><span> </span><span>0</span><span> {</span></div></div><div><div><div>4</div></div><div><span>        </span><span>// ...</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>} </span><span>else</span><span> {</span></div></div><div><div><div>6</div></div><div><span>        </span><span>runtime_Semrelease</span><span>(</span><span>&amp;</span><span>m.sema, </span><span>true</span><span>, </span><span>1</span><span>)</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>8</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>饥饿模式下，直接唤醒阻塞队列头部的 goroutine 即可。</p></section></section></section></section>
<section><h1>2. Sync.RWMutex<a href="#2-syncrwmutex"><span>#</span></a></h1><section><h2>2.1 核心机制<a href="#21-核心机制"><span>#</span></a></h2><ul>
<li>从逻辑上，可以把 <code>RWMutex</code> 理解为一把读锁加一把写锁；</li>
<li>写锁具有严格的排他性，当其被占用，其他试图取写锁或者读锁的 goroutine 均阻塞；</li>
<li>读锁具有有限的共享性，当其被占用，试图取写锁的 goroutine 会阻塞，试图取读锁的 goroutine 可与当前 goroutine 共享读锁；</li>
<li>综上可见，<code>RWMutex</code> 适用于读多写少的场景，最理想化的情况，当所有操作均使用读锁，则可实现去无化；最悲观的情况，倘若所有操作均使用写锁，则 <code>RWMutex</code> 退化为普通的 <code>Mutex</code>。</li>
</ul></section><section><h2>2.2 数据结构<a href="#22-数据结构"><span>#</span></a></h2><p>RWLock数据结构</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>const</span><span> </span><span>rwmutexMaxReaders</span><span> </span><span>=</span><span> </span><span>1</span><span> </span><span>&lt;&lt;</span><span> </span><span>30</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>type</span><span> </span><span>RWMutex</span><span> </span><span>struct</span><span> {</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>w           </span><span>Mutex</span><span>  </span><span>// held if there are pending writers</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>writerSem   </span><span>uint32</span><span> </span><span>// semaphore for writers to wait for completing readers</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>readerSem   </span><span>uint32</span><span> </span><span>// semaphore for readers to wait for completing writers</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>readerCount </span><span>int32</span><span>  </span><span>// number of pending readers</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>readerWait  </span><span>int32</span><span>  </span><span>// number of departing readers</span></div></div><div><div><div>9</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li><code>rwmutexMaxReaders</code>：共享读锁的 goroutine 数量上限，值为 <span><span>2292^{29}</span><span><span><span></span><span><span>2</span><span><span><span><span><span><span></span><span><span><span>29</span></span></span></span></span></span></span></span></span></span></span></span>；</li>
<li><code>w</code>：<code>RWMutex</code> 内置的一把普通互斥锁 <code>sync.Mutex</code>；</li>
<li><code>writerSem</code>：关联写锁阻塞队列的信号量；</li>
<li><code>readerSem</code>：关联读锁阻塞队列的信号量；</li>
<li><code>readerCount</code>：正常情况下等于介入读锁流程的 goroutine 数量；当 goroutine 接入写锁流程时，该值为实际介入读锁流程的 goroutine 数量减 <code>rwmutexMaxReaders</code>；</li>
<li><code>readerWait</code>：记录在当前 goroutine 获取写锁前，还需要等待多少个 goroutine 释放读锁。</li>
</ul></section><section><h2>2.3 读锁流程<a href="#23-读锁流程"><span>#</span></a></h2><section><h3>2.3.1 RLock<a href="#231-rlock"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> (</span><span>rw </span><span>*</span><span>RWMutex</span><span>) </span><span>RLock</span><span>() {</span></div></div><div><div><div>2</div></div><div><span>    </span><span>if</span><span> atomic.</span><span>AddInt32</span><span>(</span><span>&amp;</span><span>rw.readerCount, </span><span>1</span><span>) </span><span>&lt;</span><span> </span><span>0</span><span> {</span></div></div><div><div><div>3</div></div><div><span>        </span><span>runtime_SemacquireMutex</span><span>(</span><span>&amp;</span><span>rw.readerSem, </span><span>false</span><span>, </span><span>0</span><span>)</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>5</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li>基于原子操作，将 <code>RWMutex</code> 的 <code>readCount</code> 变量加一，表示占用或等待读锁的 goroutine 数加一；</li>
<li>倘若 <code>RWMutex.readCount</code> 的新值仍小于 0，说明有 goroutine 未释放写锁，因此将当前 goroutine 添加到读锁的阻塞队列中并阻塞挂起。</li>
</ul></section><section><h3>2.3.2 RUnlock<a href="#232-runlock"><span>#</span></a></h3><section><h4>2.3.2.1 RUnlock 方法主干<a href="#2321-runlock-方法主干"><span>#</span></a></h4><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> (</span><span>rw </span><span>*</span><span>RWMutex</span><span>) </span><span>RUnlock</span><span>() {</span></div></div><div><div><div>2</div></div><div><span>    </span><span>if</span><span> r </span><span>:=</span><span> atomic.</span><span>AddInt32</span><span>(</span><span>&amp;</span><span>rw.readerCount, </span><span>-</span><span>1</span><span>); r </span><span>&lt;</span><span> </span><span>0</span><span> {</span></div></div><div><div><div>3</div></div><div><span><span>        </span></span><span>rw.</span><span>rUnlockSlow</span><span>(r)</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>5</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li>基于原子操作，将 <code>RWMutex</code> 的 <code>readCount</code> 变量加一，表示占用或等待读锁的 goroutine 数减一；</li>
<li>倘若 <code>RWMutex.readCount</code> 的新值小于 0，说明有 goroutine 在等待获取写锁，则走入 <code>RWMutex.rUnlockSlow</code> 的流程中。</li>
</ul></section><section><h4>2.3.2.2 rUnlockSlow<a href="#2322-runlockslow"><span>#</span></a></h4><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> (</span><span>rw </span><span>*</span><span>RWMutex</span><span>) </span><span>rUnlockSlow</span><span>(</span><span>r</span><span> </span><span>int32</span><span>) {</span></div></div><div><div><div>2</div></div><div><span>    </span><span>if</span><span> r</span><span>+</span><span>1</span><span> </span><span>==</span><span> </span><span>0</span><span> </span><span>||</span><span> r</span><span>+</span><span>1</span><span> </span><span>==</span><span> </span><span>-</span><span>rwmutexMaxReaders {</span></div></div><div><div><div>3</div></div><div><span>        </span><span>fatal</span><span>(</span><span>"sync: RUnlock of unlocked RWMutex"</span><span>)</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>5</div></div><div><span>    </span><span>if</span><span> atomic.</span><span>AddInt32</span><span>(</span><span>&amp;</span><span>rw.readerWait, </span><span>-</span><span>1</span><span>) </span><span>==</span><span> </span><span>0</span><span> {</span></div></div><div><div><div>6</div></div><div><span>        </span><span>runtime_Semrelease</span><span>(</span><span>&amp;</span><span>rw.writerSem, </span><span>false</span><span>, </span><span>1</span><span>)</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>8</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li>对 <code>RWMutex.readerCount</code> 进行校验，倘若发现当前协程此前未抢占过读锁，或者介入读锁流程的 goroutine 数量达到上限，则抛出 fatal；</li>
<li>(倘若 <code>r+1 == -rwmutexMaxReaders</code>，说明此时有 goroutine 介入写锁流程，但当前此前未加过读锁，具体原因见 2.3 小节；倘若 <code>r+1==0</code>，则要么此前未加过读锁，要么介入读锁流程的 goroutine 数量达到上限，具体原因见 2.3 小节。)</li>
<li>基于原子操作，对 <code>RWMutex.readerWait</code> 进行减一操作，倘若其新值为 0，说明当前 goroutine 是最后一个介入读锁流程的协程，因此需要唤醒一个等待写锁的阻塞队列的 goroutine。（综合 <code>RWMutex.readerCount</code> 为负值，可以确定存在等待写锁的 goroutine，具体原因见 2.3 小节。）</li>
</ul></section></section></section><section><h2>2.4 写锁流程<a href="#24-写锁流程"><span>#</span></a></h2><section><h3>2.4.1 Lock<a href="#241-lock"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> (</span><span>rw </span><span>*</span><span>RWMutex</span><span>) </span><span>Lock</span><span>() {</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>rw.w.</span><span>Lock</span><span>()</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>r </span><span>:=</span><span> atomic.</span><span>AddInt32</span><span>(</span><span>&amp;</span><span>rw.readerCount, </span><span>-</span><span>rwmutexMaxReaders) </span><span>+</span><span> rwmutexMaxReaders</span></div></div><div><div><div>4</div></div><div><span>    </span><span>if</span><span> r </span><span>!=</span><span> </span><span>0</span><span> </span><span>&amp;&amp;</span><span> atomic.</span><span>AddInt32</span><span>(</span><span>&amp;</span><span>rw.readerWait, r) </span><span>!=</span><span> </span><span>0</span><span> {</span></div></div><div><div><div>5</div></div><div><span>        </span><span>runtime_SemacquireMutex</span><span>(</span><span>&amp;</span><span>rw.writerSem, </span><span>false</span><span>, </span><span>0</span><span>)</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>7</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li>对 <code>RWMutex</code> 内置的互斥锁进行加锁操作；</li>
<li>基于原子操作，对 <code>RWMutex.readerCount</code> 进行减少 <code>-rwmutexMaxReaders</code> 的操作；</li>
<li>倘若此时存在未释放读锁的 gouroutine，则基于原子操作在 <code>RWMutex.readerWait</code> 的基础上加上介入读锁流程的 goroutine 数量，并将当前 goroutine 添加到写锁的阻塞队列中挂起。</li>
</ul></section><section><h3>2.4.2 Unlock<a href="#242-unlock"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> (</span><span>rw </span><span>*</span><span>RWMutex</span><span>) </span><span>Unlock</span><span>() {</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>r </span><span>:=</span><span> atomic.</span><span>AddInt32</span><span>(</span><span>&amp;</span><span>rw.readerCount, rwmutexMaxReaders)</span></div></div><div><div><div>3</div></div><div><span>    </span><span>if</span><span> r </span><span>&gt;=</span><span> rwmutexMaxReaders {</span></div></div><div><div><div>4</div></div><div><span>        </span><span>fatal</span><span>(</span><span>"sync: Unlock of unlocked RWMutex"</span><span>)</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>6</div></div><div><span>    </span><span>for</span><span> i </span><span>:=</span><span> </span><span>0</span><span>; i </span><span>&lt;</span><span> </span><span>int</span><span>(r); i</span><span>++</span><span> {</span></div></div><div><div><div>7</div></div><div><span>        </span><span>runtime_Semrelease</span><span>(</span><span>&amp;</span><span>rw.readerSem, </span><span>false</span><span>, </span><span>0</span><span>)</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>rw.w.</span><span>Unlock</span><span>()</span></div></div><div><div><div>10</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li>基于原子操作，将 <code>RWMutex.readerCount</code> 的值加上 <code>rwmutexMaxReaders</code>；</li>
<li>倘若发现 <code>RWMutex.readerCount</code> 的新值大于 <code>rwmutexMaxReaders</code>，则说明要么当前 <code>RWMutex</code> 未上过写锁，要么介入读锁流程的 goroutine 数量已经超限，因此直接抛出 fatal；</li>
<li>因此唤醒读锁阻塞队列中的所有 goroutine；(可见，竞争读锁的 goroutine 更具备优势)</li>
<li>解开 <code>RWMutex</code> 内置的互斥锁。</li>
</ul></section></section></section>]]></content>
    <author><name>Zowely</name></author>
    <category term="Go源码" />
    <category term="Golang" />
    <category term="源码解析" />
    <category term="并发编程" />
    <category term="面试" />
  </entry>
  <entry>
    <title>遇到的一些八股</title>
    <link href="https://www.lansganbs.cn/posts/%E9%9D%A2%E8%AF%95%E7%9B%B8%E5%85%B3/%E9%81%87%E5%88%B0%E7%9A%84%E4%B8%80%E4%BA%9B%E5%85%AB%E8%82%A1/" rel="alternate" type="text/html" />
    <id>https://www.lansganbs.cn/posts/%E9%9D%A2%E8%AF%95%E7%9B%B8%E5%85%B3/%E9%81%87%E5%88%B0%E7%9A%84%E4%B8%80%E4%BA%9B%E5%85%AB%E8%82%A1/</id>
    <published>2026-01-16T00:00:00.000Z</published>
    <updated>2026-01-16T22:27:42.000Z</updated>
    <summary>记录遇到的和看到的一些八股</summary>
    <content type="html"><![CDATA[<section><h1>Go<a href="#go"><span>#</span></a></h1><ol>
<li>Goroutine 出现 panic 会发生什么</li>
<li>Golang 的 GC 什么时候会出现卡顿</li>
<li>清理过程会出现 STW 吗？</li>
<li>Golang 中的 GMP 调度模型是如何工作的？P的作用是什么？</li>
<li>Golang 的逃逸分析机制是什么？如何进行内存分配优化？</li>
<li>Sync.Map 是如何实现并发安全的？</li>
<li>Golang 的反射原理是什么？</li>
<li>Golang 的栈增长机制？为什么 Goroutine 初始栈 2KB 而线程栈是 MB 级？</li>
<li>Sync.Mutex 的实现原理（1.18前后差异）？为什么要引入饥饿模式？</li>
<li>for range 遍历 slice/map 时变量地址是否变化？“赋值不对”的原因？</li>
<li>interface{} 空接口底层实现？为什么可接收任意类型？断言区别？</li>
<li>Golang 的 GMP 模型，以及各组件之间的协作流程？</li>
<li>Golang 的 GC 机制是什么？</li>
<li>map的底层实现？并发读写是否 panic？如何解决？</li>
<li>slice 和 array 的区别？底层与扩容机制</li>
<li>关闭后的 Channel 读写行为？如何安全关闭？</li>
<li>Go 中的 Channel 及其并发控制流程？</li>
<li>使用 Channel 控制图片下载并发数（限流）？</li>
<li>介绍 Go 的 Context 功能？</li>
<li>Context 取消后子协程如何感知？</li>
<li>Go 结构体方法与指针方法的区别？</li>
<li>Go 的继承（组合）与接口？</li>
<li>介绍 GMP 模型？</li>
<li>协程（G）创建的时机？</li>
<li>M 的创建与销毁时机？</li>
<li>P 的创建时机、数量确定及初始化参数？</li>
</ol></section>
<section><h1>MySQL<a href="#mysql"><span>#</span></a></h1><ol>
<li>MySQL 的索引类型？如何优化慢查询？</li>
<li>MySQL 的事务隔离级别；如何解决幻读；Mysql 如何给单行数据加锁；对数据库的复制、备份有了解吗；半同步复制两种模式</li>
</ol></section>
<section><h1>计网<a href="#计网"><span>#</span></a></h1><ol>
<li>TCP 的拥塞控制算法有哪些？BBR 算法的工作原理？</li>
<li>Websocket 如何实现全双工通信？与长轮询区别？</li>
</ol></section>
<section><h1>操作系统<a href="#操作系统"><span>#</span></a></h1><ol>
<li>I/O 多路复用说一下</li>
<li>为什么 select 的文件描述符集合是有序的？</li>
<li>异步 I/O 和非阻塞 I/O？</li>
<li>Linux 进程的虚拟地址空间是如何布局的？</li>
<li>什么是 CPU 的 NUMA 架构？对程序性能的影响？</li>
</ol></section>
<section><h1>数据结构<a href="#数据结构"><span>#</span></a></h1><ol>
<li>为什么 epoll 底层用红黑树管理？</li>
<li>如何判断一个数是否为 2 的幂次方？给出最优解</li>
<li>统计海量数据中出现次数最多的前 K 个元素</li>
<li>实现一个循环队列，支持并发安全的入队/出队</li>
<li>介绍堆（大顶堆）的插入、删除和调整过程？</li>
<li>数据流的中位数思路？</li>
</ol></section>
<section><h1>MQ<a href="#mq"><span>#</span></a></h1><ol>
<li>为什么要使用消息队列？</li>
<li>保证消息有序，一个 topic 只能有一个 partition 吗？</li>
<li>业务突然增长，导致消息消费不过来怎么办？</li>
<li>生产者收到写入成功响应后消息一定不会丢失吗？</li>
<li>高并发场景下怎么保证消息不会重复消费？</li>
<li>如何保证消息的可靠性？</li>
<li>各大消息队列中间件对比及使用场景</li>
</ol></section>
<section><h1>分布式<a href="#分布式"><span>#</span></a></h1><ol>
<li>分布式一致性哈希算法是如何工作的？</li>
<li>什么是 Paxos 算法？Basic 与 Multi 的区别？</li>
<li>分布式系统如何实现服务发现和健康检查？</li>
<li>什么是分布式系统的脑裂问题？如何避免？</li>
<li>分布式 ID 生成方案？</li>
<li>高并发场景下，如何保证接口的幂等性？</li>
<li>如何解决缓存雪崩、穿透、击穿问题？</li>
<li>Redis 分布式锁实现、原子性及 Lua 脚本？</li>
<li>Redis 底层数据结构、Zset 跳表及复杂度？</li>
<li>Redis 布隆过滤器？</li>
</ol></section>
<section><h1>Linux/Git<a href="#linuxgit"><span>#</span></a></h1><ol>
<li>文件描述符是什么</li>
<li>什么是零拷贝技术？在 Golang 中如何实现？</li>
<li>容器与虚拟机的区别？如何实现资源隔离？</li>
<li>如何使用 Linux 命令查看一个服务是否在运行？</li>
<li>K8s 有用过吗？</li>
</ol></section>]]></content>
    <author><name>Zowely</name></author>
    <category term="面试相关" />
    <category term="面试" />
    <category term="Golang" />
    <category term="数据库" />
    <category term="数据结构" />
    <category term="Redis" />
  </entry>
  <entry>
    <title>Channel 实现原理</title>
    <link href="https://www.lansganbs.cn/posts/go%E6%BA%90%E7%A0%81/channel-%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86/" rel="alternate" type="text/html" />
    <id>https://www.lansganbs.cn/posts/go%E6%BA%90%E7%A0%81/channel-%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86/</id>
    <published>2026-01-15T00:00:00.000Z</published>
    <updated>2026-01-16T23:26:29.000Z</updated>
    <summary>本文深入探讨 Go 语言中 Channel 的实现原理，涵盖其数据结构、同步机制以及在并发编程中的应用。</summary>
    <content type="html"><![CDATA[<section><h1>1. 核心数据结构<a href="#1-核心数据结构"><span>#</span></a></h1><p>数据存储在一个环形数组中，并且维护了两个等待队列，分别用于存放因读写操作而阻塞的协程。</p><p></p><figure><img src="https://www.lansganbs.cn/images/article/Go%E6%BA%90%E7%A0%81/Channel%20%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86/Channel.png" alt="Channel.png" /><figcaption>Channel.png</figcaption></figure><p></p><section><h2>1.1 hchan<a href="#11-hchan"><span>#</span></a></h2><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>type</span><span> </span><span>hchan</span><span> </span><span>struct</span><span> {</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>qcount   </span><span>uint</span><span>           </span><span>// total data in the queue</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>dataqsiz </span><span>uint</span><span>           </span><span>// size of the circular queue</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>buf      </span><span>unsafe</span><span>.</span><span>Pointer</span><span> </span><span>// points to an array of dataqsiz elements</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>elemsize </span><span>uint16</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>closed   </span><span>uint32</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>elemtype </span><span>*</span><span>_type</span><span> </span><span>// element type</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>sendx    </span><span>uint</span><span>   </span><span>// send index</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>recvx    </span><span>uint</span><span>   </span><span>// receive index</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>recvq    </span><span>waitq</span><span>  </span><span>// list of recv waiters</span></div></div><div><div><div>11</div></div><div><span><span>    </span></span><span>sendq    </span><span>waitq</span><span>  </span><span>// list of send waiters</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span><span>    </span></span><span>lock </span><span>mutex</span></div></div><div><div><div>14</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><code>hchan</code>：channel 数据结构。</p><ul>
<li><code>qcount</code>：当前 channel 中存在多少个元素；</li>
<li><code>dataqsize</code>: 当前 channel 能存放的元素容量；</li>
<li><code>buf</code>：channel 中用于存放元素的环形缓冲区；</li>
<li><code>elemsize</code>：channel 元素类型的大小；</li>
<li><code>closed</code>：标识 channel 是否关闭；</li>
<li><code>elemtype</code>：channel 元素类型；</li>
<li><code>sendx</code>：发送元素进入环形缓冲区的 index；</li>
<li><code>recvx</code>：接收元素所处的环形缓冲区的 index；</li>
<li><code>recvq</code>：因接收而陷入阻塞的协程队列；</li>
<li><code>sendq</code>：因发送而陷入阻塞的协程队列。</li>
</ul></section><section><h2>1.2 waitq<a href="#12-waitq"><span>#</span></a></h2><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>type</span><span> </span><span>waitq</span><span> </span><span>struct</span><span> {</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>first </span><span>*</span><span>sudog</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>last  </span><span>*</span><span>sudog</span></div></div><div><div><div>4</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><code>waitq</code>：阻塞的协程队列。</p><ul>
<li><code>first</code>：队列头部</li>
<li><code>last</code>：队列尾部</li>
</ul></section><section><h2>1.3 sudog<a href="#13-sudog"><span>#</span></a></h2><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>type</span><span> </span><span>sudog</span><span> </span><span>struct</span><span> {</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>g </span><span>*</span><span>g</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>next </span><span>*</span><span>sudog</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>prev </span><span>*</span><span>sudog</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>elem </span><span>unsafe</span><span>.</span><span>Pointer</span><span> </span><span>// data element (may point to stack)</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>isSelect </span><span>bool</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>c        </span><span>*</span><span>hchan</span></div></div><div><div><div>11</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><code>sudog</code>：用于包装协程的节点。</p><ul>
<li><code>g</code>：goroutine，协程；</li>
<li><code>next</code>：队列中的下一个节点；</li>
<li><code>prev</code>：队列中的前一个节点；</li>
<li><code>elem</code>: 读取/写入 channel 的数据的容器;</li>
<li><code>isSelect</code>：标识当前协程是否处在 select 多路复用的流程中；</li>
<li><code>c</code>：标识与当前 sudog 交互的 chan.</li>
</ul></section></section>
<section><h1>2. 构造器函数<a href="#2-构造器函数"><span>#</span></a></h1><p>几种类型的 channel</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> </span><span>makechan</span><span>(</span><span>t</span><span> </span><span>*</span><span>chantype</span><span>, </span><span>size</span><span> </span><span>int</span><span>) </span><span>*</span><span>hchan</span><span> {</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>elem </span><span>:=</span><span> t.elem</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>    </span><span>// ...</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>mem, overflow </span><span>:=</span><span> math.</span><span>MulUintptr</span><span>(elem.size, </span><span>uintptr</span><span>(size))</span></div></div><div><div><div>6</div></div><div><span>    </span><span>if</span><span> overflow </span><span>||</span><span> mem </span><span>&gt;</span><span> maxAlloc</span><span>-</span><span>hchanSize </span><span>||</span><span> size </span><span>&lt;</span><span> </span><span>0</span><span> {</span></div></div><div><div><div>7</div></div><div><span>        </span><span>panic</span><span>(</span><span>plainError</span><span>(</span><span>"makechan: size out of range"</span><span>))</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span>    </span><span>var</span><span> c </span><span>*</span><span>hchan</span></div></div><div><div><div>11</div></div><div><span>    </span><span>switch</span><span> {</span></div></div><div><div><div>12</div></div><div><span>    </span><span>case</span><span> mem </span><span>==</span><span> </span><span>0</span><span>:</span></div></div><div><div><div>13</div></div><div><span>        </span><span>// Queue or element size is zero.</span></div></div><div><div><div>14</div></div><div><span><span>        </span></span><span>c </span><span>=</span><span> (</span><span>*</span><span>hchan</span><span>)(</span><span>mallocgc</span><span>(hchanSize, </span><span>nil</span><span>, </span><span>true</span><span>))</span></div></div><div><div><div>15</div></div><div><span>        </span><span>// Race detector uses this location for synchronization.</span></div></div><div><div><div>16</div></div><div><span><span>        </span></span><span>c.buf </span><span>=</span><span> c.</span><span>raceaddr</span><span>()</span></div></div><div><div><div>17</div></div><div><span>    </span><span>case</span><span> elem.ptrdata </span><span>==</span><span> </span><span>0</span><span>:</span></div></div><div><div><div>18</div></div><div><span>        </span><span>// Elements do not contain pointers.</span></div></div><div><div><div>19</div></div><div><span>        </span><span>// Allocate hchan and buf in one call.</span></div></div><div><div><div>20</div></div><div><span><span>        </span></span><span>c </span><span>=</span><span> (</span><span>*</span><span>hchan</span><span>)(</span><span>mallocgc</span><span>(hchanSize</span><span>+</span><span>mem, </span><span>nil</span><span>, </span><span>true</span><span>))</span></div></div><div><div><div>21</div></div><div><span><span>        </span></span><span>c.buf </span><span>=</span><span> </span><span>add</span><span>(unsafe.</span><span>Pointer</span><span>(c), hchanSize)</span></div></div><div><div><div>22</div></div><div><span>    </span><span>default</span><span>:</span></div></div><div><div><div>23</div></div><div><span>        </span><span>// Elements contain pointers.</span></div></div><div><div><div>24</div></div><div><span><span>        </span></span><span>c </span><span>=</span><span> </span><span>new</span><span>(</span><span>hchan</span><span>)</span></div></div><div><div><div>25</div></div><div><span><span>        </span></span><span>c.buf </span><span>=</span><span> </span><span>mallocgc</span><span>(mem, elem, </span><span>true</span><span>)</span></div></div><div><div><div>26</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>27</div></div><div>
</div></div><div><div><div>28</div></div><div><span><span>    </span></span><span>c.elemsize </span><span>=</span><span> </span><span>uint16</span><span>(elem.size)</span></div></div><div><div><div>29</div></div><div><span><span>    </span></span><span>c.elemtype </span><span>=</span><span> elem</span></div></div><div><div><div>30</div></div><div><span><span>    </span></span><span>c.dataqsiz </span><span>=</span><span> </span><span>uint</span><span>(size)</span></div></div><div><div><div>31</div></div><div>
</div></div><div><div><div>32</div></div><div><span>    </span><span>lockInit</span><span>(</span><span>&amp;</span><span>c.lock, lockRankHchan)</span></div></div><div><div><div>33</div></div><div>
</div></div><div><div><div>34</div></div><div><span>    </span><span>return</span></div></div><div><div><div>35</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><ul>
<li>判断申请内存空间大小是否越界，<code>mem</code> 大小为 element 类型大小与 element 个数相乘后得到，仅当无缓冲型 channel 时，因个数为 0 导致大小为 0；</li>
<li>根据类型，初始 channel，分为 无缓冲型、有缓冲元素为 struct 型、有缓冲元素为 pointer 型 channel;</li>
<li>倘若为无缓冲型，则仅申请一个大小为默认值 96 的空间；</li>
<li>如若有缓冲的 struct 型，则一次性分配好 <code>96 + mem</code> 大小的空间，并且调整 chan 的 <code>buf</code> 指向 <code>mem</code> 的起始位置；</li>
<li>倘若为有缓冲的 pointer 型，则分别申请 chan 和 <code>buf</code> 的空间，两者无需连续；</li>
<li>对 channel 的其余字段进行初始化，包括元素类型大小、元素类型、容量以及锁的初始化。</li>
</ul></section>
<section><h1>3. 写流程<a href="#3-写流程"><span>#</span></a></h1><section><h2>3.1 两类异常情况处理<a href="#31-两类异常情况处理"><span>#</span></a></h2><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> </span><span>chansend1</span><span>(</span><span>c</span><span> </span><span>*</span><span>hchan</span><span>, </span><span>elem</span><span> </span><span>unsafe</span><span>.</span><span>Pointer</span><span>) {</span></div></div><div><div><div>2</div></div><div><span>    </span><span>chansend</span><span>(c, elem, </span><span>true</span><span>, </span><span>getcallerpc</span><span>())</span></div></div><div><div><div>3</div></div><div><span>}</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>func</span><span> </span><span>chansend</span><span>(</span><span>c</span><span> </span><span>*</span><span>hchan</span><span>, </span><span>ep</span><span> </span><span>unsafe</span><span>.</span><span>Pointer</span><span>, </span><span>block</span><span> </span><span>bool</span><span>, </span><span>callerpc</span><span> </span><span>uintptr</span><span>) </span><span>bool</span><span> {</span></div></div><div><div><div>6</div></div><div><span>    </span><span>if</span><span> c </span><span>==</span><span> </span><span>nil</span><span> {</span></div></div><div><div><div>7</div></div><div><span>        </span><span>gopark</span><span>(</span><span>nil</span><span>, </span><span>nil</span><span>, waitReasonChanSendNilChan, traceEvGoStop, </span><span>2</span><span>)</span></div></div><div><div><div>8</div></div><div><span>        </span><span>throw</span><span>(</span><span>"unreachable"</span><span>)</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span>    </span><span>lock</span><span>(</span><span>&amp;</span><span>c.lock)</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span>    </span><span>if</span><span> c.closed </span><span>!=</span><span> </span><span>0</span><span> {</span></div></div><div><div><div>14</div></div><div><span>        </span><span>unlock</span><span>(</span><span>&amp;</span><span>c.lock)</span></div></div><div><div><div>15</div></div><div><span>        </span><span>panic</span><span>(</span><span>plainError</span><span>(</span><span>"send on closed channel"</span><span>))</span></div></div><div><div><div>16</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>17</div></div><div>
</div></div><div><div><div>18</div></div><div><span>    </span><span>// ...</span></div></div><div><div><div>19</div></div><div><span>    </span><span>// ...</span></div></div><div><div><div>20</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><ul>
<li>对于未初始化的 chan，写入操作会引发死锁；</li>
<li>对于已关闭的 chan，写入操作会引发 panic。</li>
</ul></section><section><h2>3.2 case1：写时存在阻塞读协程<a href="#32-case1写时存在阻塞读协程"><span>#</span></a></h2><p>直接写入阻塞读协程</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> </span><span>chansend</span><span>(</span><span>c</span><span> </span><span>*</span><span>hchan</span><span>, </span><span>ep</span><span> </span><span>unsafe</span><span>.</span><span>Pointer</span><span>, </span><span>block</span><span> </span><span>bool</span><span>, </span><span>callerpc</span><span> </span><span>uintptr</span><span>) </span><span>bool</span><span> {</span></div></div><div><div><div>2</div></div><div><span>    </span><span>// ...</span></div></div><div><div><div>3</div></div><div><span>    </span><span>lock</span><span>(</span><span>&amp;</span><span>c.lock)</span></div></div><div><div><div>4</div></div><div><span>    </span><span>// ...</span></div></div><div><div><div>5</div></div><div><span>    </span><span>if</span><span> sg </span><span>:=</span><span> c.recvq.</span><span>dequeue</span><span>(); sg </span><span>!=</span><span> </span><span>nil</span><span> {</span></div></div><div><div><div>6</div></div><div><span>        </span><span>// Found a waiting receiver. We pass the value we want to send</span></div></div><div><div><div>7</div></div><div><span>        </span><span>// directly to the receiver, bypassing the channel buffer (if any).</span></div></div><div><div><div>8</div></div><div><span>        </span><span>send</span><span>(c, sg, ep, </span><span>func</span><span>() { </span><span>unlock</span><span>(</span><span>&amp;</span><span>c.lock) }, </span><span>3</span><span>)</span></div></div><div><div><div>9</div></div><div><span>        </span><span>return</span><span> </span><span>true</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>11</div></div><div><span>    </span><span>// ...</span></div></div><div><div><div>12</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li>加锁；</li>
<li>从阻塞度协程队列中取出一个 goroutine 的封装对象 <code>sudog</code>；</li>
<li>在 <code>send</code> 方法中，会基于 <code>memmove</code> 方法，直接将元素拷贝交给 <code>sudog</code> 对应的 goroutine；</li>
<li>在 <code>send</code> 方法中会完成解锁动作。</li>
</ul><p></p><figure><img src="https://www.lansganbs.cn/images/article/Go%E6%BA%90%E7%A0%81/Channel%20%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86/WriteCase1.png" alt="WriteCase1.png" /><figcaption>WriteCase1.png</figcaption></figure><p></p></section><section><h2>3.3 case2：写时无阻塞读协程但环形缓冲区仍有空间<a href="#33-case2写时无阻塞读协程但环形缓冲区仍有空间"><span>#</span></a></h2><p>写入环形缓冲区</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> </span><span>chansend</span><span>(</span><span>c</span><span> </span><span>*</span><span>hchan</span><span>, </span><span>ep</span><span> </span><span>unsafe</span><span>.</span><span>Pointer</span><span>, </span><span>block</span><span> </span><span>bool</span><span>, </span><span>callerpc</span><span> </span><span>uintptr</span><span>) </span><span>bool</span><span> {</span></div></div><div><div><div>2</div></div><div><span>    </span><span>// ...</span></div></div><div><div><div>3</div></div><div><span>    </span><span>lock</span><span>(</span><span>&amp;</span><span>c.lock)</span></div></div><div><div><div>4</div></div><div><span>    </span><span>// ...</span></div></div><div><div><div>5</div></div><div><span>    </span><span>if</span><span> c.qcount </span><span>&lt;</span><span> c.dataqsiz {</span></div></div><div><div><div>6</div></div><div><span>        </span><span>// Space is available in the channel buffer. Enqueue the element to send.</span></div></div><div><div><div>7</div></div><div><span><span>        </span></span><span>qp </span><span>:=</span><span> </span><span>chanbuf</span><span>(c, c.sendx)</span></div></div><div><div><div>8</div></div><div><span>        </span><span>typedmemmove</span><span>(c.elemtype, qp, ep)</span></div></div><div><div><div>9</div></div><div><span><span>        </span></span><span>c.sendx</span><span>++</span></div></div><div><div><div>10</div></div><div><span>        </span><span>if</span><span> c.sendx </span><span>==</span><span> c.dataqsiz {</span></div></div><div><div><div>11</div></div><div><span><span>            </span></span><span>c.sendx </span><span>=</span><span> </span><span>0</span></div></div><div><div><div>12</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>13</div></div><div><span><span>        </span></span><span>c.qcount</span><span>++</span></div></div><div><div><div>14</div></div><div><span>        </span><span>unlock</span><span>(</span><span>&amp;</span><span>c.lock)</span></div></div><div><div><div>15</div></div><div><span>        </span><span>return</span><span> </span><span>true</span></div></div><div><div><div>16</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>17</div></div><div>
</div></div><div><div><div>18</div></div><div><span>    </span><span>// ...</span></div></div><div><div><div>19</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><ul>
<li>加锁；</li>
<li>将当前元素添加到环形缓冲区 <code>sendx</code> 对应的位置；</li>
<li><code>sendx++</code>;</li>
<li><code>qcount++</code>;</li>
<li>解锁，返回。</li>
</ul><p></p><figure><img src="https://www.lansganbs.cn/images/article/Go%E6%BA%90%E7%A0%81/Channel%20%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86/WriteCase2.png" alt="WriteCase2.png" /><figcaption>WriteCase2.png</figcaption></figure><p></p></section><section><h2>3.4 case3：写时无阻塞读协程且环形缓冲区无空间<a href="#34-case3写时无阻塞读协程且环形缓冲区无空间"><span>#</span></a></h2><p>阻塞写协程</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> </span><span>chansend</span><span>(</span><span>c</span><span> </span><span>*</span><span>hchan</span><span>, </span><span>ep</span><span> </span><span>unsafe</span><span>.</span><span>Pointer</span><span>, </span><span>block</span><span> </span><span>bool</span><span>, </span><span>callerpc</span><span> </span><span>uintptr</span><span>) </span><span>bool</span><span> {</span></div></div><div><div><div>2</div></div><div><span>    </span><span>// ...</span></div></div><div><div><div>3</div></div><div><span>    </span><span>lock</span><span>(</span><span>&amp;</span><span>c.lock)</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>    </span><span>// ...</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>gp </span><span>:=</span><span> </span><span>getg</span><span>()</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>mysg </span><span>:=</span><span> </span><span>acquireSudog</span><span>()</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>mysg.elem </span><span>=</span><span> ep</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>mysg.g </span><span>=</span><span> gp</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>mysg.c </span><span>=</span><span> c</span></div></div><div><div><div>11</div></div><div><span><span>    </span></span><span>gp.waiting </span><span>=</span><span> mysg</span></div></div><div><div><div>12</div></div><div><span><span>    </span></span><span>c.sendq.</span><span>enqueue</span><span>(mysg)</span></div></div><div><div><div>13</div></div><div>
</div></div><div><div><div>14</div></div><div><span><span>    </span></span><span>atomic.</span><span>Store8</span><span>(</span><span>&amp;</span><span>gp.parkingOnChan, </span><span>1</span><span>)</span></div></div><div><div><div>15</div></div><div><span>    </span><span>gopark</span><span>(chanparkcommit, unsafe.</span><span>Pointer</span><span>(</span><span>&amp;</span><span>c.lock), waitReasonChanSend, traceEvGoBlockSend, </span><span>2</span><span>)</span></div></div><div><div><div>16</div></div><div>
</div></div><div><div><div>17</div></div><div><span><span>    </span></span><span>gp.waiting </span><span>=</span><span> </span><span>nil</span></div></div><div><div><div>18</div></div><div><span><span>    </span></span><span>closed </span><span>:=</span><span> </span><span>!</span><span>mysg.success</span></div></div><div><div><div>19</div></div><div><span><span>    </span></span><span>gp.param </span><span>=</span><span> </span><span>nil</span></div></div><div><div><div>20</div></div><div><span><span>    </span></span><span>mysg.c </span><span>=</span><span> </span><span>nil</span></div></div><div><div><div>21</div></div><div><span>    </span><span>releaseSudog</span><span>(mysg)</span></div></div><div><div><div>22</div></div><div><span>    </span><span>return</span><span> </span><span>true</span></div></div><div><div><div>23</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><ul>
<li>加锁；</li>
<li>构造封装当前 goroutine 的 <code>sudog</code> 对象；</li>
<li>完成指针指向，建立 <code>sudog</code>、goroutine、channel 之间的指向关系；</li>
<li>把 <code>sudog</code> 添加到当前 channel 的阻塞写协程队列中；</li>
<li>park 当前协程；</li>
<li>倘若协程从 park 中被唤醒，则回收 <code>sudog</code>（<code>sudog</code> 能被唤醒，其对应的元素必然已经被读协程取走）；</li>
<li>解锁，返回。</li>
</ul><p></p><figure><img src="https://www.lansganbs.cn/images/article/Go%E6%BA%90%E7%A0%81/Channel%20%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86/WriteCase3.png" alt="WriteCase3.png" /><figcaption>WriteCase3.png</figcaption></figure><p></p></section><section><h2>3.5 写流程整体串联<a href="#35-写流程整体串联"><span>#</span></a></h2><p>写流程串联</p><p></p><figure><img src="https://www.lansganbs.cn/images/article/Go%E6%BA%90%E7%A0%81/Channel%20%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86/Write.png" alt="Write.png" /><figcaption>Write.png</figcaption></figure><p></p></section></section>
<section><h1>4. 读流程<a href="#4-读流程"><span>#</span></a></h1><section><h2>4.1 异常 case1：读空 channel<a href="#41-异常-case1读空-channel"><span>#</span></a></h2><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> </span><span>chanrecv</span><span>(</span><span>c</span><span> </span><span>*</span><span>hchan</span><span>, </span><span>ep</span><span> </span><span>unsafe</span><span>.</span><span>Pointer</span><span>, </span><span>block</span><span> </span><span>bool</span><span>) (</span><span>selected</span><span>, </span><span>received</span><span> </span><span>bool</span><span>) {</span></div></div><div><div><div>2</div></div><div><span>    </span><span>if</span><span> c </span><span>==</span><span> </span><span>nil</span><span> {</span></div></div><div><div><div>3</div></div><div><span>        </span><span>gopark</span><span>(</span><span>nil</span><span>, </span><span>nil</span><span>, waitReasonChanReceiveNilChan, traceEvGoStop, </span><span>2</span><span>)</span></div></div><div><div><div>4</div></div><div><span>        </span><span>throw</span><span>(</span><span>"unreachable"</span><span>)</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>6</div></div><div><span>    </span><span>// ...</span></div></div><div><div><div>7</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li>park 挂起，引起死锁。</li>
</ul></section><section><h2>4.2 异常 case2：channel 已关闭且内部无元素<a href="#42-异常-case2channel-已关闭且内部无元素"><span>#</span></a></h2><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> </span><span>chanrecv</span><span>(</span><span>c</span><span> </span><span>*</span><span>hchan</span><span>, </span><span>ep</span><span> </span><span>unsafe</span><span>.</span><span>Pointer</span><span>, </span><span>block</span><span> </span><span>bool</span><span>) (</span><span>selected</span><span>, </span><span>received</span><span> </span><span>bool</span><span>) {</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>    </span><span>lock</span><span>(</span><span>&amp;</span><span>c.lock)</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>    </span><span>if</span><span> c.closed </span><span>!=</span><span> </span><span>0</span><span> {</span></div></div><div><div><div>6</div></div><div><span>        </span><span>if</span><span> c.qcount </span><span>==</span><span> </span><span>0</span><span> {</span></div></div><div><div><div>7</div></div><div><span>            </span><span>unlock</span><span>(</span><span>&amp;</span><span>c.lock)</span></div></div><div><div><div>8</div></div><div><span>            </span><span>if</span><span> ep </span><span>!=</span><span> </span><span>nil</span><span> {</span></div></div><div><div><div>9</div></div><div><span>                </span><span>typedmemclr</span><span>(c.elemtype, ep)</span></div></div><div><div><div>10</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>11</div></div><div><span>            </span><span>return</span><span> </span><span>true</span><span>, </span><span>false</span></div></div><div><div><div>12</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>13</div></div><div><span>        </span><span>// The channel has been closed, but the channel's buffer have data.</span></div></div><div><div><div>14</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>15</div></div><div>
</div></div><div><div><div>16</div></div><div><span>    </span><span>// ...</span></div></div><div><div><div>17</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><ul>
<li>直接解锁返回即可。</li>
</ul></section><section><h2>4.3 case3：读时有阻塞的写协程<a href="#43-case3读时有阻塞的写协程"><span>#</span></a></h2><p>从阻塞写协程中读取</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> </span><span>chanrecv</span><span>(</span><span>c</span><span> </span><span>*</span><span>hchan</span><span>, </span><span>ep</span><span> </span><span>unsafe</span><span>.</span><span>Pointer</span><span>, </span><span>block</span><span> </span><span>bool</span><span>) (</span><span>selected</span><span>, </span><span>received</span><span> </span><span>bool</span><span>) {</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>    </span><span>lock</span><span>(</span><span>&amp;</span><span>c.lock)</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>    </span><span>if</span><span> sg </span><span>:=</span><span> c.sendq.</span><span>dequeue</span><span>(); sg </span><span>!=</span><span> </span><span>nil</span><span> {</span></div></div><div><div><div>6</div></div><div><span>        </span><span>recv</span><span>(c, sg, ep, </span><span>func</span><span>() { </span><span>unlock</span><span>(</span><span>&amp;</span><span>c.lock) }, </span><span>3</span><span>)</span></div></div><div><div><div>7</div></div><div><span>        </span><span>return</span><span> </span><span>true</span><span>, </span><span>true</span></div></div><div><div><div>8</div></div><div><span><span>     </span></span><span>}</span></div></div><div><div><div>9</div></div><div><span>     </span><span>// ...</span></div></div><div><div><div>10</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li>加锁；</li>
<li>从阻塞写协程队列中获取到一个写协程；</li>
<li>倘若 channel 无缓冲区，则直接读取写协程元素，并唤醒写协程；</li>
<li>倘若 channel 有缓冲区，则读取缓冲区头部元素，并将写协程元素写入缓冲区尾部后唤醒写写成；</li>
<li>解锁，返回。</li>
</ul></section><section><h2>4.4 case4：读时无阻塞写协程且缓冲区有元素<a href="#44-case4读时无阻塞写协程且缓冲区有元素"><span>#</span></a></h2><p>从环形缓冲区读取</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> </span><span>chanrecv</span><span>(</span><span>c</span><span> </span><span>*</span><span>hchan</span><span>, </span><span>ep</span><span> </span><span>unsafe</span><span>.</span><span>Pointer</span><span>, </span><span>block</span><span> </span><span>bool</span><span>) (</span><span>selected</span><span>, </span><span>received</span><span> </span><span>bool</span><span>) {</span></div></div><div><div><div>2</div></div><div><span>    </span><span>// ...</span></div></div><div><div><div>3</div></div><div><span>    </span><span>lock</span><span>(</span><span>&amp;</span><span>c.lock)</span></div></div><div><div><div>4</div></div><div><span>    </span><span>// ...</span></div></div><div><div><div>5</div></div><div><span>    </span><span>if</span><span> c.qcount </span><span>&gt;</span><span> </span><span>0</span><span> {</span></div></div><div><div><div>6</div></div><div><span>        </span><span>// Receive directly from queue</span></div></div><div><div><div>7</div></div><div><span><span>        </span></span><span>qp </span><span>:=</span><span> </span><span>chanbuf</span><span>(c, c.recvx)</span></div></div><div><div><div>8</div></div><div><span>        </span><span>if</span><span> ep </span><span>!=</span><span> </span><span>nil</span><span> {</span></div></div><div><div><div>9</div></div><div><span>            </span><span>typedmemmove</span><span>(c.elemtype, ep, qp)</span></div></div><div><div><div>10</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>11</div></div><div><span>        </span><span>typedmemclr</span><span>(c.elemtype, qp)</span></div></div><div><div><div>12</div></div><div><span><span>        </span></span><span>c.recvx</span><span>++</span></div></div><div><div><div>13</div></div><div><span>        </span><span>if</span><span> c.recvx </span><span>==</span><span> c.dataqsiz {</span></div></div><div><div><div>14</div></div><div><span><span>            </span></span><span>c.recvx </span><span>=</span><span> </span><span>0</span></div></div><div><div><div>15</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>16</div></div><div><span><span>        </span></span><span>c.qcount</span><span>--</span></div></div><div><div><div>17</div></div><div><span>        </span><span>unlock</span><span>(</span><span>&amp;</span><span>c.lock)</span></div></div><div><div><div>18</div></div><div><span>        </span><span>return</span><span> </span><span>true</span><span>, </span><span>true</span></div></div><div><div><div>19</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>20</div></div><div><span>    </span><span>// ...</span></div></div><div><div><div>21</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><ul>
<li>加锁；</li>
<li>获取到 <code>recvx</code> 对应位置的元素；</li>
<li><code>recvx++</code></li>
<li><code>qcount--</code></li>
<li>解锁，返回。</li>
</ul></section><section><h2>4.5 case5：读时无阻塞写协程且缓冲区无元素<a href="#45-case5读时无阻塞写协程且缓冲区无元素"><span>#</span></a></h2><p>阻塞读协程</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> </span><span>chanrecv</span><span>(</span><span>c</span><span> </span><span>*</span><span>hchan</span><span>, </span><span>ep</span><span> </span><span>unsafe</span><span>.</span><span>Pointer</span><span>, </span><span>block</span><span> </span><span>bool</span><span>) (</span><span>selected</span><span>, </span><span>received</span><span> </span><span>bool</span><span>) {</span></div></div><div><div><div>2</div></div><div><span>    </span><span>// ...</span></div></div><div><div><div>3</div></div><div><span>    </span><span>lock</span><span>(</span><span>&amp;</span><span>c.lock)</span></div></div><div><div><div>4</div></div><div><span>    </span><span>// ...</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>gp </span><span>:=</span><span> </span><span>getg</span><span>()</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>mysg </span><span>:=</span><span> </span><span>acquireSudog</span><span>()</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>mysg.elem </span><span>=</span><span> ep</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>gp.waiting </span><span>=</span><span> mysg</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>mysg.g </span><span>=</span><span> gp</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>mysg.c </span><span>=</span><span> c</span></div></div><div><div><div>11</div></div><div><span><span>    </span></span><span>gp.param </span><span>=</span><span> </span><span>nil</span></div></div><div><div><div>12</div></div><div><span><span>    </span></span><span>c.recvq.</span><span>enqueue</span><span>(mysg)</span></div></div><div><div><div>13</div></div><div><span><span>    </span></span><span>atomic.</span><span>Store8</span><span>(</span><span>&amp;</span><span>gp.parkingOnChan, </span><span>1</span><span>)</span></div></div><div><div><div>14</div></div><div><span>    </span><span>gopark</span><span>(chanparkcommit, unsafe.</span><span>Pointer</span><span>(</span><span>&amp;</span><span>c.lock), waitReasonChanReceive, traceEvGoBlockRecv, </span><span>2</span><span>)</span></div></div><div><div><div>15</div></div><div>
</div></div><div><div><div>16</div></div><div><span><span>    </span></span><span>gp.waiting </span><span>=</span><span> </span><span>nil</span></div></div><div><div><div>17</div></div><div><span><span>    </span></span><span>success </span><span>:=</span><span> mysg.success</span></div></div><div><div><div>18</div></div><div><span><span>    </span></span><span>gp.param </span><span>=</span><span> </span><span>nil</span></div></div><div><div><div>19</div></div><div><span><span>    </span></span><span>mysg.c </span><span>=</span><span> </span><span>nil</span></div></div><div><div><div>20</div></div><div><span>    </span><span>releaseSudog</span><span>(mysg)</span></div></div><div><div><div>21</div></div><div><span>    </span><span>return</span><span> </span><span>true</span><span>, success</span></div></div><div><div><div>22</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><ul>
<li>加锁；</li>
<li>构造封装当前 goroutine 的 <code>sudog</code> 对象；</li>
<li>完成指针指向，建立 <code>sudog</code>、goroutine、channel 之间的指向关系；</li>
<li>把 <code>sudog</code> 添加到当前 channel 的阻塞读协程队列中；</li>
<li>park 当前协程；</li>
<li>倘若协程从 park 中被唤醒，则回收 <code>sudog</code>（<code>sudog</code> 能被唤醒，其对应的元素必然已经被写入）；</li>
<li>解锁，返回。</li>
</ul></section><section><h2>4.6 读流程整体串联<a href="#46-读流程整体串联"><span>#</span></a></h2><p>读流程串联</p><p></p><figure><img src="https://www.lansganbs.cn/images/article/Go%E6%BA%90%E7%A0%81/Channel%20%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86/Read.png" alt="Read.png" /><figcaption>Read.png</figcaption></figure><p></p></section></section>
<section><h1>5. 阻塞与非阻塞模式<a href="#5-阻塞与非阻塞模式"><span>#</span></a></h1><p>在上述源码分析流程中，均是以阻塞模式为主线进行讲述，忽略非阻塞模式的有关处理逻辑. 此处阐明两个问题：</p><ul>
<li>非阻塞模式下，流程逻辑有何区别？</li>
<li>何时会进入非阻塞模式？</li>
</ul><section><h2>5.1 非阻塞模式逻辑区别<a href="#51-非阻塞模式逻辑区别"><span>#</span></a></h2><p>非阻塞模式下，读/写 channel 方法通过一个 bool 型的响应参数，用以标识是否读取/写入成功.</p><ul>
<li>所有需要使得当前 goroutine 被挂起的操作，在非阻塞模式下都会返回 <code>false</code>；</li>
<li>所有是的当前 goroutine 会进入死锁的操作，在非阻塞模式下都会返回 <code>false</code>；</li>
<li>所有能立即完成读取/写入操作的条件下，非阻塞模式下会返回 <code>true</code>。</li>
</ul></section><section><h2>5.2 何时进入非阻塞模式<a href="#52-何时进入非阻塞模式"><span>#</span></a></h2><p>默认情况下，读/写 channel 都是阻塞模式，只有在 <code>select</code> 语句组成的多路复用分支中，与 channel 的交互会变成非阻塞模式：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>ch </span><span>:=</span><span> </span><span>make</span><span>(</span><span>chan</span><span> </span><span>int</span><span>)</span></div></div><div><div><div>2</div></div><div><span>select</span><span>{</span></div></div><div><div><div>3</div></div><div><span>case</span><span> </span><span>&lt;-</span><span> ch:</span></div></div><div><div><div>4</div></div><div><span>default</span><span>:</span></div></div><div><div><div>5</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>5.3 代码一览<a href="#53-代码一览"><span>#</span></a></h2><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> </span><span>selectnbsend</span><span>(</span><span>c</span><span> </span><span>*</span><span>hchan</span><span>, </span><span>elem</span><span> </span><span>unsafe</span><span>.</span><span>Pointer</span><span>) (</span><span>selected</span><span> </span><span>bool</span><span>) {</span></div></div><div><div><div>2</div></div><div><span>    </span><span>return</span><span> </span><span>chansend</span><span>(c, elem, </span><span>false</span><span>, </span><span>getcallerpc</span><span>())</span></div></div><div><div><div>3</div></div><div><span>}</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>func</span><span> </span><span>selectnbrecv</span><span>(</span><span>elem</span><span> </span><span>unsafe</span><span>.</span><span>Pointer</span><span>, </span><span>c</span><span> </span><span>*</span><span>hchan</span><span>) (</span><span>selected</span><span>, </span><span>received</span><span> </span><span>bool</span><span>) {</span></div></div><div><div><div>6</div></div><div><span>    </span><span>return</span><span> </span><span>chanrecv</span><span>(c, elem, </span><span>false</span><span>)</span></div></div><div><div><div>7</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>在 <code>select</code> 语句包裹的多路复用分支中，读和写 channel 操作会被汇编为 <code>selectnbrecv</code> 和 <code>selectnbsend</code> 方法，底层同样复用 <code>chanrecv</code> 和 <code>chansend</code> 方法，但此时由于第三个入参 <code>block</code> 被设置为 <code>false</code>，导致后续会走进非阻塞的处理分支。</p></section></section>
<section><h1>6. 两种读 channel 的协议<a href="#6-两种读-channel-的协议"><span>#</span></a></h1><p>读取 channel 时，可以根据第二个 bool 型的返回值用以判断当前 channel 是否已处于关闭状态：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>ch </span><span>:=</span><span> </span><span>make</span><span>(</span><span>chan</span><span> </span><span>int</span><span>, </span><span>2</span><span>)</span></div></div><div><div><div>2</div></div><div><span>got1 </span><span>:=</span><span> </span><span>&lt;-</span><span> ch</span></div></div><div><div><div>3</div></div><div><span>got2,ok </span><span>:=</span><span> </span><span>&lt;-</span><span> ch</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>实现上述功能的原因是，两种格式下，读 channel 操作会被汇编成不同的方法：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> </span><span>chanrecv1</span><span>(</span><span>c</span><span> </span><span>*</span><span>hchan</span><span>, </span><span>elem</span><span> </span><span>unsafe</span><span>.</span><span>Pointer</span><span>) {</span></div></div><div><div><div>2</div></div><div><span>    </span><span>chanrecv</span><span>(c, elem, </span><span>true</span><span>)</span></div></div><div><div><div>3</div></div><div><span>}</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>//go:nosplit</span></div></div><div><div><div>6</div></div><div><span>func</span><span> </span><span>chanrecv2</span><span>(</span><span>c</span><span> </span><span>*</span><span>hchan</span><span>, </span><span>elem</span><span> </span><span>unsafe</span><span>.</span><span>Pointer</span><span>) (</span><span>received</span><span> </span><span>bool</span><span>) {</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>_, received </span><span>=</span><span> </span><span>chanrecv</span><span>(c, elem, </span><span>true</span><span>)</span></div></div><div><div><div>8</div></div><div><span>    </span><span>return</span></div></div><div><div><div>9</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section>
<section><h1>7. 关闭<a href="#7-关闭"><span>#</span></a></h1><p>关闭 channel 流程</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> </span><span>closechan</span><span>(</span><span>c</span><span> </span><span>*</span><span>hchan</span><span>) {</span></div></div><div><div><div>2</div></div><div><span>    </span><span>if</span><span> c </span><span>==</span><span> </span><span>nil</span><span> {</span></div></div><div><div><div>3</div></div><div><span>        </span><span>panic</span><span>(</span><span>plainError</span><span>(</span><span>"close of nil channel"</span><span>))</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>    </span><span>lock</span><span>(</span><span>&amp;</span><span>c.lock)</span></div></div><div><div><div>7</div></div><div><span>    </span><span>if</span><span> c.closed </span><span>!=</span><span> </span><span>0</span><span> {</span></div></div><div><div><div>8</div></div><div><span>        </span><span>unlock</span><span>(</span><span>&amp;</span><span>c.lock)</span></div></div><div><div><div>9</div></div><div><span>        </span><span>panic</span><span>(</span><span>plainError</span><span>(</span><span>"close of closed channel"</span><span>))</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>11</div></div><div>
</div></div><div><div><div>12</div></div><div><span><span>    </span></span><span>c.closed </span><span>=</span><span> </span><span>1</span></div></div><div><div><div>13</div></div><div>
</div></div><div><div><div>14</div></div><div><span>    </span><span>var</span><span> glist </span><span>gList</span></div></div><div><div><div>15</div></div><div><span>    </span><span>// release all readers</span></div></div><div><div><div>16</div></div><div><span>    </span><span>for</span><span> {</span></div></div><div><div><div>17</div></div><div><span><span>        </span></span><span>sg </span><span>:=</span><span> c.recvq.</span><span>dequeue</span><span>()</span></div></div><div><div><div>18</div></div><div><span>        </span><span>if</span><span> sg </span><span>==</span><span> </span><span>nil</span><span> {</span></div></div><div><div><div>19</div></div><div><span>            </span><span>break</span></div></div><div><div><div>20</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>21</div></div><div><span>        </span><span>if</span><span> sg.elem </span><span>!=</span><span> </span><span>nil</span><span> {</span></div></div><div><div><div>22</div></div><div><span>            </span><span>typedmemclr</span><span>(c.elemtype, sg.elem)</span></div></div><div><div><div>23</div></div><div><span><span>            </span></span><span>sg.elem </span><span>=</span><span> </span><span>nil</span></div></div><div><div><div>24</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>25</div></div><div><span><span>        </span></span><span>gp </span><span>:=</span><span> sg.g</span></div></div><div><div><div>26</div></div><div><span><span>        </span></span><span>gp.param </span><span>=</span><span> unsafe.</span><span>Pointer</span><span>(sg)</span></div></div><div><div><div>27</div></div><div><span><span>        </span></span><span>sg.success </span><span>=</span><span> </span><span>false</span></div></div><div><div><div>28</div></div><div><span><span>        </span></span><span>glist.</span><span>push</span><span>(gp)</span></div></div><div><div><div>29</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>30</div></div><div>
</div></div><div><div><div>31</div></div><div><span>    </span><span>// release all writers (they will panic)</span></div></div><div><div><div>32</div></div><div><span>    </span><span>for</span><span> {</span></div></div><div><div><div>33</div></div><div><span><span>        </span></span><span>sg </span><span>:=</span><span> c.sendq.</span><span>dequeue</span><span>()</span></div></div><div><div><div>34</div></div><div><span>        </span><span>if</span><span> sg </span><span>==</span><span> </span><span>nil</span><span> {</span></div></div><div><div><div>35</div></div><div><span>            </span><span>break</span></div></div><div><div><div>36</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>37</div></div><div><span><span>        </span></span><span>sg.elem </span><span>=</span><span> </span><span>nil</span></div></div><div><div><div>38</div></div><div><span><span>        </span></span><span>gp </span><span>:=</span><span> sg.g</span></div></div><div><div><div>39</div></div><div><span><span>        </span></span><span>gp.param </span><span>=</span><span> unsafe.</span><span>Pointer</span><span>(sg)</span></div></div><div><div><div>40</div></div><div><span><span>        </span></span><span>sg.success </span><span>=</span><span> </span><span>false</span></div></div><div><div><div>41</div></div><div><span><span>        </span></span><span>glist.</span><span>push</span><span>(gp)</span></div></div><div><div><div>42</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>43</div></div><div><span>    </span><span>unlock</span><span>(</span><span>&amp;</span><span>c.lock)</span></div></div><div><div><div>44</div></div><div>
</div></div><div><div><div>45</div></div><div><span>    </span><span>// Ready all Gs now that we've dropped the channel lock.</span></div></div><div><div><div>46</div></div><div><span>    </span><span>for</span><span> </span><span>!</span><span>glist.</span><span>empty</span><span>() {</span></div></div><div><div><div>47</div></div><div><span><span>        </span></span><span>gp </span><span>:=</span><span> glist.</span><span>pop</span><span>()</span></div></div><div><div><div>48</div></div><div><span><span>        </span></span><span>gp.schedlink </span><span>=</span><span> </span><span>0</span></div></div><div><div><div>49</div></div><div><span>        </span><span>goready</span><span>(gp, </span><span>3</span><span>)</span></div></div><div><div><div>50</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>51</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><ul>
<li>关闭未初始化过的 channel 会 panic；</li>
<li>加锁；</li>
<li>重复关闭 channel 会 panic；</li>
<li>将阻塞读协程队列中的协程节点统一添加到 <code>glist</code>；</li>
<li>将阻塞写协程队列中的协程节点统一添加到 <code>glist</code>；</li>
<li>唤醒 <code>glist</code> 当中的所有协程。</li>
</ul></section>]]></content>
    <author><name>Zowely</name></author>
    <category term="Go源码" />
    <category term="Golang" />
    <category term="源码解析" />
    <category term="并发编程" />
    <category term="面试" />
  </entry>
  <entry>
    <title>Golang map 实现原理</title>
    <link href="https://www.lansganbs.cn/posts/go%E6%BA%90%E7%A0%81/golang-map-%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86/" rel="alternate" type="text/html" />
    <id>https://www.lansganbs.cn/posts/go%E6%BA%90%E7%A0%81/golang-map-%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86/</id>
    <published>2026-01-12T00:00:00.000Z</published>
    <updated>2026-01-28T14:57:53.000Z</updated>
    <summary>本文将深入探讨 Golang 中 map 的实现原理，涵盖其基本结构、哈希函数、冲突解决机制以及在并发环境下的使用注意事项。</summary>
    <content type="html"><![CDATA[<section><h1>1. 基本用法<a href="#1-基本用法"><span>#</span></a></h1><section><h2>1.1 概述<a href="#11-概述"><span>#</span></a></h2><p>map 又称字典，是一种常用的数据结构，核心特征包含下述三点：</p><p>（1）存储基于 key-value 对映射的模式；</p><p>（2）基于 key 维度实现存储数据的去重；</p><p>（3）读、写、删操作控制，时间复杂度 <span><span>O(1)O(1)</span><span><span><span></span><span>O</span><span>(</span><span>1</span><span>)</span></span></span></span>.</p></section><section><h2>1.2 初始化<a href="#12-初始化"><span>#</span></a></h2><section><h3>1.2.1 几种初始化方法<a href="#121-几种初始化方法"><span>#</span></a></h3><p>golang 中，对 map 的初始化分为以下几种方式：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>myMap1 </span><span>:=</span><span> </span><span>make</span><span>(</span><span>map</span><span>[</span><span>int</span><span>]</span><span>int</span><span>,</span><span>2</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>通过 make 关键字进行初始化，同时指定 map 预分配的容量.</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>myMap2 </span><span>:=</span><span> </span><span>make</span><span>(</span><span>map</span><span>[</span><span>int</span><span>]</span><span>int</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>通过 make 关键字进行初始化，不显式声明容量，因此默认容量 为 0.</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>myMap3 </span><span>:=map</span><span>[</span><span>int</span><span>]</span><span>int</span><span>{</span></div></div><div><div><div>2</div></div><div><span>    </span><span>1</span><span>:</span><span>2</span><span>,</span></div></div><div><div><div>3</div></div><div><span>    </span><span>3</span><span>:</span><span>4</span><span>,</span></div></div><div><div><div>4</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>初始化操作连带赋值，一气呵成.</p></section><section><h3>1.2.2 key 的类型要求<a href="#122-key-的类型要求"><span>#</span></a></h3><p>map 中，key 的数据类型必须为可比较的类型，chan、map、func不可比较</p></section></section><section><h2>1.3 读<a href="#13-读"><span>#</span></a></h2><p>读 map 分为下面两种方式：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>v1 </span><span>:=</span><span> myMap[</span><span>10</span><span>]</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>第一种方式是直接读，倘若 key 存在，则获取到对应的 val，倘若 key 不存在或者 map 未初始化，会返回 val 类型的零值作为兜底.</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>v2,ok </span><span>:=</span><span> myMap[</span><span>10</span><span>]</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>第二种方式是读的同时添加一个 bool 类型的 flag 标识是否读取成功. 倘若 ok == false，说明读取失败， key 不存在，或者 map 未初始化.</p><p>此处同一种语法能够实现不同返回值类型的适配，是由于代码在汇编时，会根据返回参数类型的区别，映射到不同的实现方法.</p></section><section><h2>1.4 写<a href="#14-写"><span>#</span></a></h2><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>myMap[</span><span>5</span><span>] </span><span>=</span><span> </span><span>6</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>写操作的语法如上. 须注意的一点是，倘若 map 未初始化，直接执行写操作会导致 panic：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>const</span><span> </span><span>plainError</span><span> </span><span>string</span></div></div><div><div><div>2</div></div><div><span>panic</span><span>(</span><span>plainError</span><span>(</span><span>"assignment to entry in nil map"</span><span>))</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>1.5 删<a href="#15-删"><span>#</span></a></h2><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>delete</span><span>(myMap,</span><span>5</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>执行 delete 方法时，倘若 key 存在，则会从 map 中将对应的 key-value 对删除；倘若 key 不存在或 map 未初始化，则方法直接结束，不会产生显式提示.</p></section><section><h2>1.6 遍历<a href="#16-遍历"><span>#</span></a></h2><p>遍历分为下面两种方式：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>for</span><span> k,v </span><span>:=</span><span> </span><span>range</span><span> myMap{</span></div></div><div><div><div>2</div></div><div><span>    </span><span>// ...</span></div></div><div><div><div>3</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>基于 k,v 依次承接 map 中的 key-value 对；</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>for</span><span> k </span><span>:=</span><span> </span><span>range</span><span> myMap{</span></div></div><div><div><div>2</div></div><div><span>    </span><span>// ...</span></div></div><div><div><div>3</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>基于 k 依次承接 map 中的 key，不关注 val 的取值.</p><p>需要注意的是，在执行 map 遍历操作时，获取的 key-value 对并没有一个固定的顺序，因此前后两次遍历顺序可能存在差异.</p></section><section><h2>1.7 并发冲突<a href="#17-并发冲突"><span>#</span></a></h2><p>map 不是并发安全的数据结构，倘若存在并发读写行为，会抛出 fatal error.</p><p>具体规则是：</p><p>（1）并发读没有问题；</p><p>（2）并发读写中的”写”是广义上的，包含写入、更新、删除等操作；</p><p>（3）读的时候发现其他 goroutine 在并发写，抛出 fatal error；</p><p>（4）写的时候发现其他 goroutine 在并发写，抛出 fatal error.</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>fatal</span><span>(</span><span>"concurrent map read and map write"</span><span>)</span></div></div><div><div><div>2</div></div><div><span>fatal</span><span>(</span><span>"concurrent map writes"</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>需要关注，此处并发读写会引发 fatal error，是一种比 panic 更严重的错误，无法使用 recover 操作捕获.</p></section></section>
<section><h1>2. 核心原理<a href="#2-核心原理"><span>#</span></a></h1><p>map 又称为 hash map，在算法上基于 hash 实现 key 的映射和寻址；在数据结构上基于桶数组实现 key-value 对的存储.</p><p>以一组 key-value 对写入 map 的流程为例进行简述：</p><p>（1）通过哈希方法取得 key 的 hash 值；</p><p>（2）hash 值对桶数组长度取模，确定其所属的桶；</p><p>（3）在桶中插入 key-value 对.</p><p>hash 的性质，保证了相同的 key 必然产生相同的 hash 值，因此能映射到相同的桶中，通过桶内遍历的方式锁定对应的 key-value 对.</p><p>因此，只要在宏观流程上，控制每个桶中 key-value 对的数量，就能保证 map 的几项操作都限制为常数级别的时间复杂度.</p><section><h2>2.1 hash<a href="#21-hash"><span>#</span></a></h2><p>hash 译作散列，是一种将任意长度的输入压缩到某一固定长度的输出摘要的过程，由于这种转换属于压缩映射，输入空间远大于输出空间，因此不同输入可能会映射成相同的输出结果. 此外，hash在压缩过程中会存在部分信息的遗失，因此这种映射关系具有不可逆的特质.</p><p>（1）hash 的可重入性：相同的 key，必然产生相同的 hash 值；</p><p>（2）hash 的离散性：只要两个 key 不相同，不论其相似度的高低，产生的 hash 值会在整个输出域内均匀地离散化；</p><p>（3）hash 的单向性：企图通过 hash 值反向映射回 key 是无迹可寻的.</p><p>（4）hash 冲突：由于输入域（key）无穷大，输出域（hash 值）有限，因此必然存在不同 key 映射到相同 hash 值的情况，称之为 hash 冲突.</p></section><section><h2>2.2 桶数组<a href="#22-桶数组"><span>#</span></a></h2><p></p><figure><img src="https://www.lansganbs.cn/images/article/Go%E6%BA%90%E7%A0%81/Golang%20map%20%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86/%E6%A1%B6%E6%95%B0%E7%BB%84.png" alt="桶数组.png" /><figcaption>桶数组.png</figcaption></figure><p></p><p>map 中，会通过长度为 2 的整数次幂的桶数组进行 key-value 对的存储：</p><p>（1）每个桶固定可以存放 8 个 key-value 对；</p><p>（2）倘若超过 8 个 key-value 对打到桶数组的同一个索引当中，此时会通过创建桶链表的方式来化解这一问题.</p></section><section><h2>2.3 拉链法解决 hash 冲突<a href="#23-拉链法解决-hash-冲突"><span>#</span></a></h2><p>首先，由于 hash 冲突的存在，不同 key 可能存在相同的 hash 值；</p><p>再者，hash 值会对桶数组长度取模，因此不同 hash 值可能被打到同一个桶中.</p><p>综上，不同的 key-value 可能被映射到 map 的同一个桶当中.</p><p>此时最经典的解决手段分为两种：拉链法和开放寻址法.</p><p><strong>（1）拉链法</strong></p><p>拉链法中，将命中同一个桶的元素通过链表的形式进行链接，因此很便于动态扩展.</p><p><strong>（2）开放寻址法</strong></p><p>开放寻址法中，在插入新条目时，会基于一定的探测策略持续寻找，直到找到一个可用于存放数据的空位为止.</p><p>对标拉链还有开放寻址法，两者的优劣对比：</p>
















<table><thead><tr><th>方法</th><th>优点</th></tr></thead><tbody><tr><td>拉链法</td><td>简单常用；无需预先为元素分配内存.</td></tr><tr><td>开放寻址法</td><td>无需额外的指针用于链接元素；内存地址完全连续，可以基于局部性原理，充分利用 CPU 高速缓存.</td></tr></tbody></table><p>在 map 解决 hash /分桶 冲突问题时，实际上结合了拉链法和开放寻址法两种思路. 以 map 的插入写流程为例，进行思路阐述：</p><p>（1）桶数组中的每个桶，严格意义上是一个单向桶链表，以桶为节点进行串联；</p><p>（2）每个桶固定可以存放 8 个 key-value 对；</p><p>（3）当 key 命中一个桶时，首先根据开放寻址法，在桶的 8 个位置中寻找空位进行插入；</p><p>（4）倘若桶的 8 个位置都已被占满，则基于桶的溢出桶指针，找到下一个桶，重复第（3）步；</p><p>（5）倘若遍历到链表尾部，仍未找到空位，则基于拉链法，在桶链表尾部续接新桶，并插入 key-value 对.</p><p></p><figure><img src="https://www.lansganbs.cn/images/article/Go%E6%BA%90%E7%A0%81/Golang%20map%20%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86/%E6%8B%89%E9%93%BE%E6%B3%95.png" alt="拉链法.png" /><figcaption>拉链法.png</figcaption></figure><p></p></section><section><h2>2.4 扩容优化性能<a href="#24-扩容优化性能"><span>#</span></a></h2><p>倘若 map 的桶数组长度固定不变，那么随着 key-value 对数量的增长，当一个桶下挂载的 key-value 达到一定的量级，此时操作的时间复杂度会趋于线性，无法满足诉求.</p><p>因此在实现上，map 桶数组的长度会随着 key-value 对数量的变化而实时调整，以保证每个桶内的 key-value 对数量始终控制在常量级别，满足各项操作为 O(1) 时间复杂度的要求.</p><p>map 扩容机制的核心点包括：</p><p>（1）扩容分为增量扩容和等量扩容；</p><p>（2）当桶内 key-value 总数/桶数组长度 &gt; 6.5 时发生增量扩容，桶数组长度增长为原值的两倍；</p><p>（3）当桶内溢出桶数量大于等于 2^B 时( B 为桶数组长度的指数，B 最大取 15)，发生等量扩容，桶的长度保持为原值；</p><p>（4）采用渐进扩容的方式，当桶被实际操作到时，由使用者负责完成数据迁移，避免因为一次性的全量数据迁移引发性能抖动.</p></section></section>
<section><h1>3. 数据结构<a href="#3-数据结构"><span>#</span></a></h1><section><h2>3.1 hmap<a href="#31-hmap"><span>#</span></a></h2><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>type</span><span> </span><span>hmap</span><span> </span><span>struct</span><span> {</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>count     </span><span>int</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>flags     </span><span>uint8</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>B         </span><span>uint8</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>noverflow </span><span>uint16</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>hash0     </span><span>uint32</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>buckets    </span><span>unsafe</span><span>.</span><span>Pointer</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>oldbuckets </span><span>unsafe</span><span>.</span><span>Pointer</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>nevacuate  </span><span>uintptr</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>extra </span><span>*</span><span>mapextra</span></div></div><div><div><div>11</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>（1）count：map 中的 key-value 总数；</p><p>（2）flags：map 状态标识，可以标识出 map 是否被 goroutine 并发读写；</p><p>（3）B：桶数组长度的指数，桶数组长度为 2^B；</p><p>（4）noverflow：map 中溢出桶的数量；</p><p>（5）hash0：hash 随机因子，生成 key 的 hash 值时会使用到；</p><p>（6）buckets：桶数组；</p><p>（7）oldbuckets：扩容过程中老的桶数组；</p><p>（8）nevacuate：扩容时的进度标识，index 小于 nevacuate 的桶都已经由老桶转移到新桶中；</p><p>（9）extra：预申请的溢出桶.</p></section><section><h2>3.2 mapextra<a href="#32-mapextra"><span>#</span></a></h2><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>type</span><span> </span><span>mapextra</span><span> </span><span>struct</span><span> {</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>overflow    </span><span>*</span><span>[]</span><span>*</span><span>bmap</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>oldoverflow </span><span>*</span><span>[]</span><span>*</span><span>bmap</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>nextOverflow </span><span>*</span><span>bmap</span></div></div><div><div><div>5</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>在 map 初始化时，倘若容量过大，会提前申请好一批溢出桶，以供后续使用，这部分溢出桶存放在 hmap.mapextra 当中：</p><p>（1）mapextra.overflow：供桶数组 buckets 使用的溢出桶；</p><p>（2）mapextra.oldoverFlow: 扩容流程中，供老桶数组 oldBuckets 使用的溢出桶；</p><p>（3）mapextra.nextOverflow：下一个可用的溢出桶.</p></section><section><h2>3.3 bmap<a href="#33-bmap"><span>#</span></a></h2><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>const</span><span> </span><span>bucketCnt</span><span> </span><span>=</span><span> </span><span>8</span></div></div><div><div><div>2</div></div><div><span>type</span><span> </span><span>bmap</span><span> </span><span>struct</span><span> {</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>tophash [</span><span>bucketCnt</span><span>]</span><span>uint8</span></div></div><div><div><div>4</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>（1）bmap 就是 map 中的桶，可以存储 8 组 key-value 对的数据，以及一个指向下一个溢出桶的指针；</p><p>（2）每组 key-value 对数据包含 key 高 8 位 hash 值 tophash，key 和 val 三部分；</p><p>（3）在代码层面只展示了 tophash 部分，但由于 tophash、key 和 val 的数据长度固定，因此可以通过内存地址偏移的方式寻找到后续的 key 数组、val 数组以及溢出桶指针；</p><p>（4）为方便理解，把完整的 bmap 类声明代码补充如下：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>type</span><span> </span><span>bmap</span><span> </span><span>struct</span><span> {</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>tophash [</span><span>bucketCnt</span><span>]</span><span>uint8</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>keys [</span><span>bucketCnt</span><span>]</span><span>T</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>values [</span><span>bucketCnt</span><span>]</span><span>T</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>overflow </span><span>uint8</span></div></div><div><div><div>6</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section>
<section><h1>4. 构造方法<a href="#4-构造方法"><span>#</span></a></h1><p>创建 map 时，实际上会调用 runtime/map.go 文件中的 makemap 方法，下面对源码展开分析：</p><section><h2>4.1 makemap<a href="#41-makemap"><span>#</span></a></h2><p>方法主干源码一览：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> </span><span>makemap</span><span>(</span><span>t</span><span> </span><span>*</span><span>maptype</span><span>, </span><span>hint</span><span> </span><span>int</span><span>, </span><span>h</span><span> </span><span>*</span><span>hmap</span><span>) </span><span>*</span><span>hmap</span><span> {</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>mem, overflow </span><span>:=</span><span> math.</span><span>MulUintptr</span><span>(</span><span>uintptr</span><span>(hint), t.bucket.size)</span></div></div><div><div><div>3</div></div><div><span>    </span><span>if</span><span> overflow </span><span>||</span><span> mem </span><span>&gt;</span><span> maxAlloc {</span></div></div><div><div><div>4</div></div><div><span><span>        </span></span><span>hint </span><span>=</span><span> </span><span>0</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>    </span><span>if</span><span> h </span><span>==</span><span> </span><span>nil</span><span> {</span></div></div><div><div><div>8</div></div><div><span><span>        </span></span><span>h </span><span>=</span><span> </span><span>new</span><span>(</span><span>hmap</span><span>)</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>h.hash0 </span><span>=</span><span> </span><span>fastrand</span><span>()</span></div></div><div><div><div>11</div></div><div>
</div></div><div><div><div>12</div></div><div><span><span>    </span></span><span>B </span><span>:=</span><span> </span><span>uint8</span><span>(</span><span>0</span><span>)</span></div></div><div><div><div>13</div></div><div><span>    </span><span>for</span><span> </span><span>overLoadFactor</span><span>(hint, B) {</span></div></div><div><div><div>14</div></div><div><span><span>        </span></span><span>B</span><span>++</span></div></div><div><div><div>15</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>16</div></div><div><span><span>    </span></span><span>h.B </span><span>=</span><span> B</span></div></div><div><div><div>17</div></div><div>
</div></div><div><div><div>18</div></div><div><span>    </span><span>if</span><span> h.B </span><span>!=</span><span> </span><span>0</span><span> {</span></div></div><div><div><div>19</div></div><div><span>        </span><span>var</span><span> nextOverflow </span><span>*</span><span>bmap</span></div></div><div><div><div>20</div></div><div><span><span>        </span></span><span>h.buckets, nextOverflow </span><span>=</span><span> </span><span>makeBucketArray</span><span>(t, h.B, </span><span>nil</span><span>)</span></div></div><div><div><div>21</div></div><div><span>        </span><span>if</span><span> nextOverflow </span><span>!=</span><span> </span><span>nil</span><span> {</span></div></div><div><div><div>22</div></div><div><span><span>            </span></span><span>h.extra </span><span>=</span><span> </span><span>new</span><span>(</span><span>mapextra</span><span>)</span></div></div><div><div><div>23</div></div><div><span><span>            </span></span><span>h.extra.nextOverflow </span><span>=</span><span> nextOverflow</span></div></div><div><div><div>24</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>25</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>26</div></div><div>
</div></div><div><div><div>27</div></div><div><span>    </span><span>return</span><span> h</span></div></div><div><div><div>28</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>（1）hint 为 map 拟分配的容量；在分配前，会提前对拟分配的内存大小进行判断，倘若超限，会将 hint 置为零；</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>mem, overflow </span><span>:=</span><span> math.</span><span>MulUintptr</span><span>(</span><span>uintptr</span><span>(hint), t.bucket.size)</span></div></div><div><div><div>2</div></div><div><span>if</span><span> overflow </span><span>||</span><span> mem </span><span>&gt;</span><span> maxAlloc {</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>hint </span><span>=</span><span> </span><span>0</span></div></div><div><div><div>4</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>（2）通过 new 方法初始化 hmap；</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>if</span><span> h </span><span>==</span><span> </span><span>nil</span><span> {</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>h </span><span>=</span><span> </span><span>new</span><span>(</span><span>hmap</span><span>)</span></div></div><div><div><div>3</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>（3）调用 fastrand，构造 hash 因子：hmap.hash0；</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>h.hash0 </span><span>=</span><span> </span><span>fastrand</span><span>()</span><span>`</span></div></div><div><div><div>2</div></div><div><span>``</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>（4）大致上基于 log2(B) &gt;= hint 的思路（具体见 4.2 小节 overLoadFactor 方法的介绍），计算桶数组的容量 B；</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>```</span><span>go</span></div></div><div><div><div>8</div></div><div><span>B </span><span>:=</span><span> </span><span>uint8</span><span>(</span><span>0</span><span>)</span></div></div><div><div><div>9</div></div><div><span>for</span><span> </span><span>overLoadFactor</span><span>(hint, B) {</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>B</span><span>++</span></div></div><div><div><div>11</div></div><div><span>}</span></div></div><div><div><div>12</div></div><div><span>h.B </span><span>=</span><span> B</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>（5）调用 makeBucketArray 方法，初始化桶数组 hmap.buckets；</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>var</span><span> nextOverflow </span><span>*</span><span>bmap</span></div></div><div><div><div>2</div></div><div><span>h.buckets, nextOverflow </span><span>=</span><span> </span><span>makeBucketArray</span><span>(t, h.B, </span><span>nil</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>（6）倘若 map 容量较大，会提前申请一批溢出桶 hmap.extra.</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>if</span><span> nextOverflow </span><span>!=</span><span> </span><span>nil</span><span> {</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>h.extra </span><span>=</span><span> </span><span>new</span><span>(</span><span>mapextra</span><span>)</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>h.extra.nextOverflow </span><span>=</span><span> nextOverflow</span></div></div><div><div><div>4</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>4.2 overLoadFactor<a href="#42-overloadfactor"><span>#</span></a></h2><p>通过 overLoadFactor 方法，对 map 预分配容量和桶数组长度指数进行判断，决定是否仍需要增长 B 的数值：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>const</span><span> </span><span>loadFactorNum</span><span> </span><span>=</span><span> </span><span>13</span></div></div><div><div><div>2</div></div><div><span>const</span><span> </span><span>loadFactorDen</span><span> </span><span>=</span><span> </span><span>2</span></div></div><div><div><div>3</div></div><div><span>const</span><span> </span><span>goarch</span><span>.</span><span>PtrSize</span><span> </span><span>=</span><span> </span><span>8</span></div></div><div><div><div>4</div></div><div><span>const</span><span> </span><span>bucketCnt</span><span> </span><span>=</span><span> </span><span>8</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>func</span><span> </span><span>overLoadFactor</span><span>(</span><span>count</span><span> </span><span>int</span><span>, </span><span>B</span><span> </span><span>uint8</span><span>) </span><span>bool</span><span> {</span></div></div><div><div><div>7</div></div><div><span>    </span><span>return</span><span> count </span><span>&gt;</span><span> bucketCnt </span><span>&amp;&amp;</span><span> </span><span>uintptr</span><span>(count) </span><span>&gt;</span><span> loadFactorNum</span><span>*</span><span>(</span><span>bucketShift</span><span>(B)</span><span>/</span><span>loadFactorDen)</span></div></div><div><div><div>8</div></div><div><span>}</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span>func</span><span> </span><span>bucketShift</span><span>(</span><span>b</span><span> </span><span>uint8</span><span>) </span><span>uintptr</span><span> {</span></div></div><div><div><div>11</div></div><div><span>    </span><span>return</span><span> </span><span>uintptr</span><span>(</span><span>1</span><span>) </span><span>&lt;&lt;</span><span> (b </span><span>&amp;</span><span> (goarch.PtrSize</span><span>*</span><span>8</span><span> </span><span>-</span><span> </span><span>1</span><span>))</span></div></div><div><div><div>12</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>（1）倘若 map 预分配容量小于等于 8，B 取 0，桶的个数为 1；</p><p>（2）保证 map 预分配容量小于等于桶数组长度 * 6.5.</p></section><section><h2>4.3 makeBucketArray<a href="#43-makebucketarray"><span>#</span></a></h2><p>makeBucketArray 方法会进行桶数组的初始化，并根据桶的数量决定是否需要提前作溢出桶的初始化. 方法主干代码如下：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> </span><span>makeBucketArray</span><span>(</span><span>t</span><span> </span><span>*</span><span>maptype</span><span>, </span><span>b</span><span> </span><span>uint8</span><span>, </span><span>dirtyalloc</span><span> </span><span>unsafe</span><span>.</span><span>Pointer</span><span>) (</span><span>buckets</span><span> </span><span>unsafe</span><span>.</span><span>Pointer</span><span>, </span><span>nextOverflow</span><span> </span><span>*</span><span>bmap</span><span>) {</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>base </span><span>:=</span><span> </span><span>bucketShift</span><span>(b)</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>nbuckets </span><span>:=</span><span> base</span></div></div><div><div><div>4</div></div><div><span>    </span><span>if</span><span> b </span><span>&gt;=</span><span> </span><span>4</span><span> {</span></div></div><div><div><div>5</div></div><div><span><span>        </span></span><span>nbuckets </span><span>+=</span><span> </span><span>bucketShift</span><span>(b </span><span>-</span><span> </span><span>4</span><span>)</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>buckets </span><span>=</span><span> </span><span>newarray</span><span>(t.bucket, </span><span>int</span><span>(nbuckets))</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span>    </span><span>if</span><span> base </span><span>!=</span><span> nbuckets {</span></div></div><div><div><div>11</div></div><div><span><span>        </span></span><span>nextOverflow </span><span>=</span><span> (</span><span>*</span><span>bmap</span><span>)(</span><span>add</span><span>(buckets, base</span><span>*uintptr</span><span>(t.bucketsize)))</span></div></div><div><div><div>12</div></div><div><span><span>        </span></span><span>last </span><span>:=</span><span> (</span><span>*</span><span>bmap</span><span>)(</span><span>add</span><span>(buckets, (nbuckets</span><span>-</span><span>1</span><span>)</span><span>*uintptr</span><span>(t.bucketsize)))</span></div></div><div><div><div>13</div></div><div><span><span>        </span></span><span>last.</span><span>setoverflow</span><span>(t, (</span><span>*</span><span>bmap</span><span>)(buckets))</span></div></div><div><div><div>14</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>15</div></div><div><span>    </span><span>return</span><span> buckets, nextOverflow</span></div></div><div><div><div>16</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>makeBucketArray 会为 map 的桶数组申请内存，在桶数组的指数 b &gt;= 4时（桶数组的容量 &gt;= 52 ），会需要提前创建溢出桶。</p><p>通过 base 记录桶数组的长度，不包含溢出桶；通过 nbuckets 记录累加上溢出桶后，桶数组的总长度.</p></section></section>
<section><h1>5. 读流程<a href="#5-读流程"><span>#</span></a></h1><section><h2>5.1 读流程梳理<a href="#51-读流程梳理"><span>#</span></a></h2><p>map 读流程主要分为以下几步：</p><p>（1）根据 key 取 hash 值；</p><p>（2）根据 hash 值对桶数组取模，确定所在的桶；</p><p>（3）沿着桶链表依次遍历各个桶内的 key-value 对；</p><p>（4）命中相同的 key，则返回 value；若 key 不存在，则返回零值.</p><p>map 读操作最终会走进 runtime/map.go 的 mapaccess 方法中，下面是关键实现和说明：</p></section><section><h2>5.2 mapaccess 方法（关键代码）<a href="#52-mapaccess-方法关键代码"><span>#</span></a></h2><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> </span><span>mapaccess1</span><span>(</span><span>t</span><span> </span><span>*</span><span>maptype</span><span>, </span><span>h</span><span> </span><span>*</span><span>hmap</span><span>, </span><span>key</span><span> </span><span>unsafe</span><span>.</span><span>Pointer</span><span>) </span><span>unsafe</span><span>.</span><span>Pointer</span><span> {</span></div></div><div><div><div>2</div></div><div><span>    </span><span>if</span><span> h </span><span>==</span><span> </span><span>nil</span><span> </span><span>||</span><span> h.count </span><span>==</span><span> </span><span>0</span><span> {</span></div></div><div><div><div>3</div></div><div><span>        </span><span>return</span><span> unsafe.</span><span>Pointer</span><span>(</span><span>&amp;</span><span>zeroVal[</span><span>0</span><span>])</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>5</div></div><div><span>    </span><span>if</span><span> h.flags</span><span>&amp;</span><span>hashWriting </span><span>!=</span><span> </span><span>0</span><span> {</span></div></div><div><div><div>6</div></div><div><span>        </span><span>fatal</span><span>(</span><span>"concurrent map read and map write"</span><span>)</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>hash </span><span>:=</span><span> t.</span><span>hasher</span><span>(key, </span><span>uintptr</span><span>(h.hash0))</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>m </span><span>:=</span><span> </span><span>bucketMask</span><span>(h.B)</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>b </span><span>:=</span><span> (</span><span>*</span><span>bmap</span><span>)(</span><span>add</span><span>(h.buckets, (hash</span><span>&amp;</span><span>m)</span><span>*uintptr</span><span>(t.bucketsize)))</span></div></div><div><div><div>11</div></div><div><span>    </span><span>if</span><span> c </span><span>:=</span><span> h.oldbuckets; c </span><span>!=</span><span> </span><span>nil</span><span> {</span></div></div><div><div><div>12</div></div><div><span>        </span><span>if</span><span> </span><span>!</span><span>h.</span><span>sameSizeGrow</span><span>() {</span></div></div><div><div><div>13</div></div><div><span><span>            </span></span><span>m </span><span>&gt;&gt;=</span><span> </span><span>1</span></div></div><div><div><div>14</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>15</div></div><div><span><span>        </span></span><span>oldb </span><span>:=</span><span> (</span><span>*</span><span>bmap</span><span>)(</span><span>add</span><span>(c, (hash</span><span>&amp;</span><span>m)</span><span>*uintptr</span><span>(t.bucketsize)))</span></div></div><div><div><div>16</div></div><div><span>        </span><span>if</span><span> </span><span>!</span><span>evacuated</span><span>(oldb) {</span></div></div><div><div><div>17</div></div><div><span><span>            </span></span><span>b </span><span>=</span><span> oldb</span></div></div><div><div><div>18</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>19</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>20</div></div><div><span><span>    </span></span><span>top </span><span>:=</span><span> </span><span>tophash</span><span>(hash)</span></div></div><div><div><div>21</div></div><div><span>bucketloop:</span></div></div><div><div><div>22</div></div><div><span>    </span><span>for</span><span> ; b </span><span>!=</span><span> </span><span>nil</span><span>; b </span><span>=</span><span> b.</span><span>overflow</span><span>(t) {</span></div></div><div><div><div>23</div></div><div><span>        </span><span>for</span><span> i </span><span>:=</span><span> </span><span>uintptr</span><span>(</span><span>0</span><span>); i </span><span>&lt;</span><span> bucketCnt; i</span><span>++</span><span> {</span></div></div><div><div><div>24</div></div><div><span>            </span><span>if</span><span> b.tophash[i] </span><span>!=</span><span> top {</span></div></div><div><div><div>25</div></div><div><span>                </span><span>if</span><span> b.tophash[i] </span><span>==</span><span> emptyRest {</span></div></div><div><div><div>26</div></div><div><span>                    </span><span>break</span><span> bucketloop</span></div></div><div><div><div>27</div></div><div><span><span>                </span></span><span>}</span></div></div><div><div><div>28</div></div><div><span>                </span><span>continue</span></div></div><div><div><div>29</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>30</div></div><div><span><span>            </span></span><span>k </span><span>:=</span><span> </span><span>add</span><span>(unsafe.</span><span>Pointer</span><span>(b), dataOffset</span><span>+</span><span>i</span><span>*uintptr</span><span>(t.keysize))</span></div></div><div><div><div>31</div></div><div><span>            </span><span>if</span><span> t.</span><span>indirectkey</span><span>() {</span></div></div><div><div><div>32</div></div><div><span><span>                </span></span><span>k </span><span>=</span><span> </span><span>*</span><span>((</span><span>*</span><span>unsafe</span><span>.</span><span>Pointer</span><span>)(k))</span></div></div><div><div><div>33</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>34</div></div><div><span>            </span><span>if</span><span> t.key.</span><span>equal</span><span>(key, k) {</span></div></div><div><div><div>35</div></div><div><span><span>                </span></span><span>e </span><span>:=</span><span> </span><span>add</span><span>(unsafe.</span><span>Pointer</span><span>(b), dataOffset</span><span>+</span><span>bucketCnt</span><span>*uintptr</span><span>(t.keysize)</span><span>+</span><span>i</span><span>*uintptr</span><span>(t.elemsize))</span></div></div><div><div><div>36</div></div><div><span>                </span><span>if</span><span> t.</span><span>indirectelem</span><span>() {</span></div></div><div><div><div>37</div></div><div><span><span>                    </span></span><span>e </span><span>=</span><span> </span><span>*</span><span>((</span><span>*</span><span>unsafe</span><span>.</span><span>Pointer</span><span>)(e))</span></div></div><div><div><div>38</div></div><div><span><span>                </span></span><span>}</span></div></div><div><div><div>39</div></div><div><span>                </span><span>return</span><span> e</span></div></div><div><div><div>40</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>41</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>42</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>43</div></div><div><span>    </span><span>return</span><span> unsafe.</span><span>Pointer</span><span>(</span><span>&amp;</span><span>zeroVal[</span><span>0</span><span>])</span></div></div><div><div><div>44</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>关键说明（简要）：</p><ul>
<li>未初始化或空 map：直接返回零值指针；</li>
<li>并发写检测：若发现写标记（hashWriting），抛出 fatal；</li>
<li>扩容兼容：若处于扩容（oldbuckets != nil），可能需要先在 oldbuckets 中取桶并判断是否已迁移；</li>
<li>使用 tophash（hash 的高 8 位，且避开 0-4 的保留值）进行快速筛选，匹配后再比较完整 key。</li>
</ul></section></section>
<section><h1>6. 写流程<a href="#6-写流程"><span>#</span></a></h1><section><h2>6.1 写流程梳理<a href="#61-写流程梳理"><span>#</span></a></h2><p>map 写流程主要分为：</p><ol>
<li>根据 key 取 hash 值；</li>
<li>根据 hash 值计算桶索引；</li>
<li>若处于扩容，帮助迁移命中的桶（渐进扩容）；</li>
<li>遍历桶链表查找匹配或空槽；</li>
<li>若已存在 key，则更新 value；否则插入；</li>
<li>若触发扩容条件，则开启扩容并重试定位。</li>
</ol></section><section><h2>6.2 mapassign（关键代码）<a href="#62-mapassign关键代码"><span>#</span></a></h2><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> </span><span>mapassign</span><span>(</span><span>t</span><span> </span><span>*</span><span>maptype</span><span>, </span><span>h</span><span> </span><span>*</span><span>hmap</span><span>, </span><span>key</span><span> </span><span>unsafe</span><span>.</span><span>Pointer</span><span>) </span><span>unsafe</span><span>.</span><span>Pointer</span><span> {</span></div></div><div><div><div>2</div></div><div><span>    </span><span>if</span><span> h </span><span>==</span><span> </span><span>nil</span><span> {</span></div></div><div><div><div>3</div></div><div><span>        </span><span>panic</span><span>(</span><span>plainError</span><span>(</span><span>"assignment to entry in nil map"</span><span>))</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>5</div></div><div><span>    </span><span>if</span><span> h.flags</span><span>&amp;</span><span>hashWriting </span><span>!=</span><span> </span><span>0</span><span> {</span></div></div><div><div><div>6</div></div><div><span>        </span><span>fatal</span><span>(</span><span>"concurrent map writes"</span><span>)</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>hash </span><span>:=</span><span> t.</span><span>hasher</span><span>(key, </span><span>uintptr</span><span>(h.hash0))</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>h.flags </span><span>^=</span><span> hashWriting</span></div></div><div><div><div>11</div></div><div>
</div></div><div><div><div>12</div></div><div><span>    </span><span>if</span><span> h.buckets </span><span>==</span><span> </span><span>nil</span><span> {</span></div></div><div><div><div>13</div></div><div><span><span>        </span></span><span>h.buckets </span><span>=</span><span> </span><span>newobject</span><span>(t.bucket)</span></div></div><div><div><div>14</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>15</div></div><div>
</div></div><div><div><div>16</div></div><div><span>again:</span></div></div><div><div><div>17</div></div><div><span><span>    </span></span><span>bucket </span><span>:=</span><span> hash </span><span>&amp;</span><span> </span><span>bucketMask</span><span>(h.B)</span></div></div><div><div><div>18</div></div><div><span>    </span><span>if</span><span> h.</span><span>growing</span><span>() {</span></div></div><div><div><div>19</div></div><div><span>        </span><span>growWork</span><span>(t, h, bucket)</span></div></div><div><div><div>20</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>21</div></div><div><span><span>    </span></span><span>b </span><span>:=</span><span> (</span><span>*</span><span>bmap</span><span>)(</span><span>add</span><span>(h.buckets, bucket</span><span>*uintptr</span><span>(t.bucketsize)))</span></div></div><div><div><div>22</div></div><div><span><span>    </span></span><span>top </span><span>:=</span><span> </span><span>tophash</span><span>(hash)</span></div></div><div><div><div>23</div></div><div>
</div></div><div><div><div>24</div></div><div><span>    </span><span>var</span><span> inserti </span><span>*uint8</span></div></div><div><div><div>25</div></div><div><span>    </span><span>var</span><span> insertk </span><span>unsafe</span><span>.</span><span>Pointer</span></div></div><div><div><div>26</div></div><div><span>    </span><span>var</span><span> elem </span><span>unsafe</span><span>.</span><span>Pointer</span></div></div><div><div><div>27</div></div><div><span>bucketloop:</span></div></div><div><div><div>28</div></div><div><span>    </span><span>for</span><span> {</span></div></div><div><div><div>29</div></div><div><span>        </span><span>for</span><span> i </span><span>:=</span><span> </span><span>uintptr</span><span>(</span><span>0</span><span>); i </span><span>&lt;</span><span> bucketCnt; i</span><span>++</span><span> {</span></div></div><div><div><div>30</div></div><div><span>            </span><span>if</span><span> b.tophash[i] </span><span>!=</span><span> top {</span></div></div><div><div><div>31</div></div><div><span>                </span><span>if</span><span> </span><span>isEmpty</span><span>(b.tophash[i]) </span><span>&amp;&amp;</span><span> inserti </span><span>==</span><span> </span><span>nil</span><span> {</span></div></div><div><div><div>32</div></div><div><span><span>                    </span></span><span>inserti </span><span>=</span><span> </span><span>&amp;</span><span>b.tophash[i]</span></div></div><div><div><div>33</div></div><div><span><span>                    </span></span><span>insertk </span><span>=</span><span> </span><span>add</span><span>(unsafe.</span><span>Pointer</span><span>(b), dataOffset</span><span>+</span><span>i</span><span>*uintptr</span><span>(t.keysize))</span></div></div><div><div><div>34</div></div><div><span><span>                    </span></span><span>elem </span><span>=</span><span> </span><span>add</span><span>(unsafe.</span><span>Pointer</span><span>(b), dataOffset</span><span>+</span><span>bucketCnt</span><span>*uintptr</span><span>(t.keysize)</span><span>+</span><span>i</span><span>*uintptr</span><span>(t.elemsize))</span></div></div><div><div><div>35</div></div><div><span><span>                </span></span><span>}</span></div></div><div><div><div>36</div></div><div><span>                </span><span>if</span><span> b.tophash[i] </span><span>==</span><span> emptyRest {</span></div></div><div><div><div>37</div></div><div><span>                    </span><span>break</span><span> bucketloop</span></div></div><div><div><div>38</div></div><div><span><span>                </span></span><span>}</span></div></div><div><div><div>39</div></div><div><span>                </span><span>continue</span></div></div><div><div><div>40</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>41</div></div><div><span><span>            </span></span><span>k </span><span>:=</span><span> </span><span>add</span><span>(unsafe.</span><span>Pointer</span><span>(b), dataOffset</span><span>+</span><span>i</span><span>*uintptr</span><span>(t.keysize))</span></div></div><div><div><div>42</div></div><div><span>            </span><span>if</span><span> t.</span><span>indirectkey</span><span>() {</span></div></div><div><div><div>43</div></div><div><span><span>                </span></span><span>k </span><span>=</span><span> </span><span>*</span><span>((</span><span>*</span><span>unsafe</span><span>.</span><span>Pointer</span><span>)(k))</span></div></div><div><div><div>44</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>45</div></div><div><span>            </span><span>if</span><span> </span><span>!</span><span>t.key.</span><span>equal</span><span>(key, k) {</span></div></div><div><div><div>46</div></div><div><span>                </span><span>continue</span></div></div><div><div><div>47</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>48</div></div><div><span>            </span><span>if</span><span> t.</span><span>needkeyupdate</span><span>() {</span></div></div><div><div><div>49</div></div><div><span>                </span><span>typedmemmove</span><span>(t.key, k, key)</span></div></div><div><div><div>50</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>51</div></div><div><span><span>            </span></span><span>elem </span><span>=</span><span> </span><span>add</span><span>(unsafe.</span><span>Pointer</span><span>(b), dataOffset</span><span>+</span><span>bucketCnt</span><span>*uintptr</span><span>(t.keysize)</span><span>+</span><span>i</span><span>*uintptr</span><span>(t.elemsize))</span></div></div><div><div><div>52</div></div><div><span>            </span><span>goto</span><span> done</span></div></div><div><div><div>53</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>54</div></div><div><span><span>        </span></span><span>ovf </span><span>:=</span><span> b.</span><span>overflow</span><span>(t)</span></div></div><div><div><div>55</div></div><div><span>        </span><span>if</span><span> ovf </span><span>==</span><span> </span><span>nil</span><span> {</span></div></div><div><div><div>56</div></div><div><span>            </span><span>break</span></div></div><div><div><div>57</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>58</div></div><div><span><span>        </span></span><span>b </span><span>=</span><span> ovf</span></div></div><div><div><div>59</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>60</div></div><div>
</div></div><div><div><div>61</div></div><div><span>    </span><span>if</span><span> </span><span>!</span><span>h.</span><span>growing</span><span>() </span><span>&amp;&amp;</span><span> (</span><span>overLoadFactor</span><span>(h.count</span><span>+</span><span>1</span><span>, h.B) </span><span>||</span><span> </span><span>tooManyOverflowBuckets</span><span>(h.noverflow, h.B)) {</span></div></div><div><div><div>62</div></div><div><span>        </span><span>hashGrow</span><span>(t, h)</span></div></div><div><div><div>63</div></div><div><span>        </span><span>goto</span><span> again</span></div></div><div><div><div>64</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>65</div></div><div>
</div></div><div><div><div>66</div></div><div><span>    </span><span>if</span><span> inserti </span><span>==</span><span> </span><span>nil</span><span> {</span></div></div><div><div><div>67</div></div><div><span><span>        </span></span><span>newb </span><span>:=</span><span> h.</span><span>newoverflow</span><span>(t, b)</span></div></div><div><div><div>68</div></div><div><span><span>        </span></span><span>inserti </span><span>=</span><span> </span><span>&amp;</span><span>newb.tophash[</span><span>0</span><span>]</span></div></div><div><div><div>69</div></div><div><span><span>        </span></span><span>insertk </span><span>=</span><span> </span><span>add</span><span>(unsafe.</span><span>Pointer</span><span>(newb), dataOffset)</span></div></div><div><div><div>70</div></div><div><span><span>        </span></span><span>elem </span><span>=</span><span> </span><span>add</span><span>(insertk, bucketCnt</span><span>*uintptr</span><span>(t.keysize))</span></div></div><div><div><div>71</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>72</div></div><div>
</div></div><div><div><div>73</div></div><div><span>    </span><span>if</span><span> t.</span><span>indirectkey</span><span>() {</span></div></div><div><div><div>74</div></div><div><span><span>        </span></span><span>kmem </span><span>:=</span><span> </span><span>newobject</span><span>(t.key)</span></div></div><div><div><div>75</div></div><div><span>        </span><span>*</span><span>(</span><span>*</span><span>unsafe</span><span>.</span><span>Pointer</span><span>)(insertk) </span><span>=</span><span> kmem</span></div></div><div><div><div>76</div></div><div><span><span>        </span></span><span>insertk </span><span>=</span><span> kmem</span></div></div><div><div><div>77</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>78</div></div><div><span>    </span><span>if</span><span> t.</span><span>indirectelem</span><span>() {</span></div></div><div><div><div>79</div></div><div><span><span>        </span></span><span>vmem </span><span>:=</span><span> </span><span>newobject</span><span>(t.elem)</span></div></div><div><div><div>80</div></div><div><span>        </span><span>*</span><span>(</span><span>*</span><span>unsafe</span><span>.</span><span>Pointer</span><span>)(elem) </span><span>=</span><span> vmem</span></div></div><div><div><div>81</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>82</div></div><div><span>    </span><span>typedmemmove</span><span>(t.key, insertk, key)</span></div></div><div><div><div>83</div></div><div><span>    </span><span>*</span><span>inserti </span><span>=</span><span> top</span></div></div><div><div><div>84</div></div><div><span><span>    </span></span><span>h.count</span><span>++</span></div></div><div><div><div>85</div></div><div>
</div></div><div><div><div>86</div></div><div><span>done:</span></div></div><div><div><div>87</div></div><div><span>    </span><span>if</span><span> h.flags</span><span>&amp;</span><span>hashWriting </span><span>==</span><span> </span><span>0</span><span> {</span></div></div><div><div><div>88</div></div><div><span>        </span><span>fatal</span><span>(</span><span>"concurrent map writes"</span><span>)</span></div></div><div><div><div>89</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>90</div></div><div><span><span>    </span></span><span>h.flags </span><span>&amp;^=</span><span> hashWriting</span></div></div><div><div><div>91</div></div><div><span>    </span><span>if</span><span> t.</span><span>indirectelem</span><span>() {</span></div></div><div><div><div>92</div></div><div><span><span>        </span></span><span>elem </span><span>=</span><span> </span><span>*</span><span>((</span><span>*</span><span>unsafe</span><span>.</span><span>Pointer</span><span>)(elem))</span></div></div><div><div><div>93</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>94</div></div><div><span>    </span><span>return</span><span> elem</span></div></div><div><div><div>95</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>要点：</p><ul>
<li>对未初始化的 map 进行写会 panic；</li>
<li>写前设置写标记（hashWriting），写后清除；</li>
<li>在插入前会检查是否需要触发扩容；</li>
<li>插入时优先复用第一个空槽（inserti），否则新建溢出桶。</li>
</ul></section></section>
<section><h1>7. 删除流程<a href="#7-删除流程"><span>#</span></a></h1><section><h2>7.1 删除流程梳理<a href="#71-删除流程梳理"><span>#</span></a></h2><p>删除流程与读、写类似：先定位桶，再遍历链表；命中后清除 key、value，并更新 tophash 与相邻空位标记，必要时帮助扩容迁移。</p></section><section><h2>7.2 mapdelete（关键代码）<a href="#72-mapdelete关键代码"><span>#</span></a></h2><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> </span><span>mapdelete</span><span>(</span><span>t</span><span> </span><span>*</span><span>maptype</span><span>, </span><span>h</span><span> </span><span>*</span><span>hmap</span><span>, </span><span>key</span><span> </span><span>unsafe</span><span>.</span><span>Pointer</span><span>) {</span></div></div><div><div><div>2</div></div><div><span>    </span><span>if</span><span> h </span><span>==</span><span> </span><span>nil</span><span> </span><span>||</span><span> h.count </span><span>==</span><span> </span><span>0</span><span> {</span></div></div><div><div><div>3</div></div><div><span>        </span><span>return</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>5</div></div><div><span>    </span><span>if</span><span> h.flags</span><span>&amp;</span><span>hashWriting </span><span>!=</span><span> </span><span>0</span><span> {</span></div></div><div><div><div>6</div></div><div><span>        </span><span>fatal</span><span>(</span><span>"concurrent map writes"</span><span>)</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>hash </span><span>:=</span><span> t.</span><span>hasher</span><span>(key, </span><span>uintptr</span><span>(h.hash0))</span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span><span>    </span></span><span>h.flags </span><span>^=</span><span> hashWriting</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span><span>    </span></span><span>bucket </span><span>:=</span><span> hash </span><span>&amp;</span><span> </span><span>bucketMask</span><span>(h.B)</span></div></div><div><div><div>14</div></div><div><span>    </span><span>if</span><span> h.</span><span>growing</span><span>() {</span></div></div><div><div><div>15</div></div><div><span>        </span><span>growWork</span><span>(t, h, bucket)</span></div></div><div><div><div>16</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>17</div></div><div><span><span>    </span></span><span>b </span><span>:=</span><span> (</span><span>*</span><span>bmap</span><span>)(</span><span>add</span><span>(h.buckets, bucket</span><span>*uintptr</span><span>(t.bucketsize)))</span></div></div><div><div><div>18</div></div><div><span><span>    </span></span><span>bOrig </span><span>:=</span><span> b</span></div></div><div><div><div>19</div></div><div><span><span>    </span></span><span>top </span><span>:=</span><span> </span><span>tophash</span><span>(hash)</span></div></div><div><div><div>20</div></div><div><span>search:</span></div></div><div><div><div>21</div></div><div><span>    </span><span>for</span><span> ; b </span><span>!=</span><span> </span><span>nil</span><span>; b </span><span>=</span><span> b.</span><span>overflow</span><span>(t) {</span></div></div><div><div><div>22</div></div><div><span>        </span><span>for</span><span> i </span><span>:=</span><span> </span><span>uintptr</span><span>(</span><span>0</span><span>); i </span><span>&lt;</span><span> bucketCnt; i</span><span>++</span><span> {</span></div></div><div><div><div>23</div></div><div><span>            </span><span>if</span><span> b.tophash[i] </span><span>!=</span><span> top {</span></div></div><div><div><div>24</div></div><div><span>                </span><span>if</span><span> b.tophash[i] </span><span>==</span><span> emptyRest {</span></div></div><div><div><div>25</div></div><div><span>                    </span><span>break</span><span> search</span></div></div><div><div><div>26</div></div><div><span><span>                </span></span><span>}</span></div></div><div><div><div>27</div></div><div><span>                </span><span>continue</span></div></div><div><div><div>28</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>29</div></div><div><span><span>            </span></span><span>k </span><span>:=</span><span> </span><span>add</span><span>(unsafe.</span><span>Pointer</span><span>(b), dataOffset</span><span>+</span><span>i</span><span>*uintptr</span><span>(t.keysize))</span></div></div><div><div><div>30</div></div><div><span><span>            </span></span><span>k2 </span><span>:=</span><span> k</span></div></div><div><div><div>31</div></div><div><span>            </span><span>if</span><span> t.</span><span>indirectkey</span><span>() {</span></div></div><div><div><div>32</div></div><div><span><span>                </span></span><span>k2 </span><span>=</span><span> </span><span>*</span><span>((</span><span>*</span><span>unsafe</span><span>.</span><span>Pointer</span><span>)(k2))</span></div></div><div><div><div>33</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>34</div></div><div><span>            </span><span>if</span><span> </span><span>!</span><span>t.key.</span><span>equal</span><span>(key, k2) {</span></div></div><div><div><div>35</div></div><div><span>                </span><span>continue</span></div></div><div><div><div>36</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>37</div></div><div><span>            </span><span>// Only clear key if there are pointers in it.</span></div></div><div><div><div>38</div></div><div><span>            </span><span>if</span><span> t.</span><span>indirectkey</span><span>() {</span></div></div><div><div><div>39</div></div><div><span>                </span><span>*</span><span>(</span><span>*</span><span>unsafe</span><span>.</span><span>Pointer</span><span>)(k) </span><span>=</span><span> </span><span>nil</span></div></div><div><div><div>40</div></div><div><span><span>            </span></span><span>} </span><span>else</span><span> </span><span>if</span><span> t.key.ptrdata </span><span>!=</span><span> </span><span>0</span><span> {</span></div></div><div><div><div>41</div></div><div><span>                </span><span>memclrHasPointers</span><span>(k, t.key.size)</span></div></div><div><div><div>42</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>43</div></div><div><span><span>            </span></span><span>e </span><span>:=</span><span> </span><span>add</span><span>(unsafe.</span><span>Pointer</span><span>(b), dataOffset</span><span>+</span><span>bucketCnt</span><span>*uintptr</span><span>(t.keysize)</span><span>+</span><span>i</span><span>*uintptr</span><span>(t.elemsize))</span></div></div><div><div><div>44</div></div><div><span>            </span><span>if</span><span> t.</span><span>indirectelem</span><span>() {</span></div></div><div><div><div>45</div></div><div><span>                </span><span>*</span><span>(</span><span>*</span><span>unsafe</span><span>.</span><span>Pointer</span><span>)(e) </span><span>=</span><span> </span><span>nil</span></div></div><div><div><div>46</div></div><div><span><span>            </span></span><span>} </span><span>else</span><span> </span><span>if</span><span> t.elem.ptrdata </span><span>!=</span><span> </span><span>0</span><span> {</span></div></div><div><div><div>47</div></div><div><span>                </span><span>memclrHasPointers</span><span>(e, t.elem.size)</span></div></div><div><div><div>48</div></div><div><span><span>            </span></span><span>} </span><span>else</span><span> {</span></div></div><div><div><div>49</div></div><div><span>                </span><span>memclrNoHeapPointers</span><span>(e, t.elem.size)</span></div></div><div><div><div>50</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>51</div></div><div><span><span>            </span></span><span>b.tophash[i] </span><span>=</span><span> emptyOne</span></div></div><div><div><div>52</div></div><div><span>            </span><span>// 如果是末位或后续为空，则向前合并为 emptyRest</span></div></div><div><div><div>53</div></div><div><span>            </span><span>if</span><span> i </span><span>==</span><span> bucketCnt</span><span>-</span><span>1</span><span> {</span></div></div><div><div><div>54</div></div><div><span>                </span><span>if</span><span> b.</span><span>overflow</span><span>(t) </span><span>!=</span><span> </span><span>nil</span><span> </span><span>&amp;&amp;</span><span> b.</span><span>overflow</span><span>(t).tophash[</span><span>0</span><span>] </span><span>!=</span><span> emptyRest {</span></div></div><div><div><div>55</div></div><div><span>                    </span><span>goto</span><span> notLast</span></div></div><div><div><div>56</div></div><div><span><span>                </span></span><span>}</span></div></div><div><div><div>57</div></div><div><span><span>            </span></span><span>} </span><span>else</span><span> {</span></div></div><div><div><div>58</div></div><div><span>                </span><span>if</span><span> b.tophash[i</span><span>+</span><span>1</span><span>] </span><span>!=</span><span> emptyRest {</span></div></div><div><div><div>59</div></div><div><span>                    </span><span>goto</span><span> notLast</span></div></div><div><div><div>60</div></div><div><span><span>                </span></span><span>}</span></div></div><div><div><div>61</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>62</div></div><div><span>            </span><span>for</span><span> {</span></div></div><div><div><div>63</div></div><div><span><span>                </span></span><span>b.tophash[i] </span><span>=</span><span> emptyRest</span></div></div><div><div><div>64</div></div><div><span>                </span><span>if</span><span> i </span><span>==</span><span> </span><span>0</span><span> {</span></div></div><div><div><div>65</div></div><div><span>                    </span><span>if</span><span> b </span><span>==</span><span> bOrig {</span></div></div><div><div><div>66</div></div><div><span>                        </span><span>break</span></div></div><div><div><div>67</div></div><div><span><span>                    </span></span><span>}</span></div></div><div><div><div>68</div></div><div><span><span>                    </span></span><span>c </span><span>:=</span><span> b</span></div></div><div><div><div>69</div></div><div><span>                    </span><span>for</span><span> b </span><span>=</span><span> bOrig; b.</span><span>overflow</span><span>(t) </span><span>!=</span><span> c; b </span><span>=</span><span> b.</span><span>overflow</span><span>(t) {</span></div></div><div><div><div>70</div></div><div><span><span>                    </span></span><span>}</span></div></div><div><div><div>71</div></div><div><span><span>                    </span></span><span>i </span><span>=</span><span> bucketCnt </span><span>-</span><span> </span><span>1</span></div></div><div><div><div>72</div></div><div><span><span>                </span></span><span>} </span><span>else</span><span> {</span></div></div><div><div><div>73</div></div><div><span><span>                    </span></span><span>i</span><span>--</span></div></div><div><div><div>74</div></div><div><span><span>                </span></span><span>}</span></div></div><div><div><div>75</div></div><div><span>                </span><span>if</span><span> b.tophash[i] </span><span>!=</span><span> emptyOne {</span></div></div><div><div><div>76</div></div><div><span>                    </span><span>break</span></div></div><div><div><div>77</div></div><div><span><span>                </span></span><span>}</span></div></div><div><div><div>78</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>79</div></div><div><span><span>        </span></span><span>notLast:</span></div></div><div><div><div>80</div></div><div><span><span>            </span></span><span>h.count</span><span>--</span></div></div><div><div><div>81</div></div><div><span>            </span><span>if</span><span> h.count </span><span>==</span><span> </span><span>0</span><span> {</span></div></div><div><div><div>82</div></div><div><span><span>                </span></span><span>h.hash0 </span><span>=</span><span> </span><span>fastrand</span><span>()</span></div></div><div><div><div>83</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>84</div></div><div><span>            </span><span>break</span><span> search</span></div></div><div><div><div>85</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>86</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>87</div></div><div>
</div></div><div><div><div>88</div></div><div><span>    </span><span>if</span><span> h.flags</span><span>&amp;</span><span>hashWriting </span><span>==</span><span> </span><span>0</span><span> {</span></div></div><div><div><div>89</div></div><div><span>        </span><span>fatal</span><span>(</span><span>"concurrent map writes"</span><span>)</span></div></div><div><div><div>90</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>91</div></div><div><span><span>    </span></span><span>h.flags </span><span>&amp;^=</span><span> hashWriting</span></div></div><div><div><div>92</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section></section>
<section><h1>8. 遍历流程<a href="#8-遍历流程"><span>#</span></a></h1><p>map 的遍历首先创建迭代器（hiter），随机选择起始桶与起始偏移，然后通过 mapiternext 实现按顺序遍历（兼容扩容场景）。</p><section><h2>8.1 迭代器结构（hiter）<a href="#81-迭代器结构hiter"><span>#</span></a></h2><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>type</span><span> </span><span>hiter</span><span> </span><span>struct</span><span> {</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>key         </span><span>unsafe</span><span>.</span><span>Pointer</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>elem        </span><span>unsafe</span><span>.</span><span>Pointer</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>t           </span><span>*</span><span>maptype</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>h           </span><span>*</span><span>hmap</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>buckets     </span><span>unsafe</span><span>.</span><span>Pointer</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>bptr        </span><span>*</span><span>bmap</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>overflow    </span><span>*</span><span>[]</span><span>*</span><span>bmap</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>oldoverflow </span><span>*</span><span>[]</span><span>*</span><span>bmap</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>startBucket </span><span>uintptr</span></div></div><div><div><div>11</div></div><div><span><span>    </span></span><span>offset      </span><span>uint8</span></div></div><div><div><div>12</div></div><div><span><span>    </span></span><span>wrapped     </span><span>bool</span></div></div><div><div><div>13</div></div><div><span><span>    </span></span><span>B           </span><span>uint8</span></div></div><div><div><div>14</div></div><div><span><span>    </span></span><span>i           </span><span>uint8</span></div></div><div><div><div>15</div></div><div><span><span>    </span></span><span>bucket      </span><span>uintptr</span></div></div><div><div><div>16</div></div><div><span><span>    </span></span><span>checkBucket </span><span>uintptr</span></div></div><div><div><div>17</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section><section><h2>8.2 mapiterinit / mapiternext（要点）<a href="#82-mapiterinit--mapiternext要点"><span>#</span></a></h2><ul>
<li>mapiterinit 初始化迭代器并随机选取 startBucket 与 offset；若 t.bucket.ptrdata == 0，会预取 overflow 信息供迭代器使用；</li>
<li>mapiternext 在遍历时检测并发写（hashWriting），并在遇到扩容时通过 checkBucket 兼容旧桶未迁移的数据顺序；</li>
<li>遍历遇到被标记为 evacuated 的条目时，会通过 mapaccessK 等方法完成安全读取。</li>
</ul></section></section>
<section><h1>9. 扩容流程<a href="#9-扩容流程"><span>#</span></a></h1><section><h2>9.1 扩容类型<a href="#91-扩容类型"><span>#</span></a></h2><ul>
<li>增量扩容（grow bigger）：桶数组长度翻倍；</li>
<li>等量扩容（same size grow）：保持桶数量不变，但降低溢出桶数量，提高填充率。</li>
</ul></section><section><h2>9.2 何时扩容<a href="#92-何时扩容"><span>#</span></a></h2><ul>
<li>只有写操作可能触发扩容；</li>
<li>overLoadFactor 判断（count &gt; bucketCnt 且 count &gt; loadFactorNum*(bucketShift(B)/loadFactorDen)）会触发增量扩容；</li>
<li>tooManyOverflowBuckets 判断溢出桶过多会触发等量扩容。</li>
</ul><p>相关常量与函数：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>const</span><span> </span><span>loadFactorNum</span><span> </span><span>=</span><span> </span><span>13</span></div></div><div><div><div>2</div></div><div><span>const</span><span> </span><span>loadFactorDen</span><span> </span><span>=</span><span> </span><span>2</span></div></div><div><div><div>3</div></div><div><span>const</span><span> </span><span>bucketCnt</span><span> </span><span>=</span><span> </span><span>8</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>func</span><span> </span><span>overLoadFactor</span><span>(</span><span>count</span><span> </span><span>int</span><span>, </span><span>B</span><span> </span><span>uint8</span><span>) </span><span>bool</span><span> {</span></div></div><div><div><div>6</div></div><div><span>    </span><span>return</span><span> count </span><span>&gt;</span><span> bucketCnt </span><span>&amp;&amp;</span><span> </span><span>uintptr</span><span>(count) </span><span>&gt;</span><span> loadFactorNum</span><span>*</span><span>(</span><span>bucketShift</span><span>(B)</span><span>/</span><span>loadFactorDen)</span></div></div><div><div><div>7</div></div><div><span>}</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>func</span><span> </span><span>tooManyOverflowBuckets</span><span>(</span><span>noverflow</span><span> </span><span>uint16</span><span>, </span><span>B</span><span> </span><span>uint8</span><span>) </span><span>bool</span><span> {</span></div></div><div><div><div>10</div></div><div><span>    </span><span>if</span><span> B </span><span>&gt;</span><span> </span><span>15</span><span> { B </span><span>=</span><span> </span><span>15</span><span> }</span></div></div><div><div><div>11</div></div><div><span>    </span><span>return</span><span> noverflow </span><span>&gt;=</span><span> </span><span>uint16</span><span>(</span><span>1</span><span>)</span><span>&lt;&lt;</span><span>(B</span><span>&amp;</span><span>15</span><span>)</span></div></div><div><div><div>12</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>9.3 开启扩容（hashGrow 要点）<a href="#93-开启扩容hashgrow-要点"><span>#</span></a></h2><ul>
<li>根据 overLoadFactor 决定 bigger（0 或 1）；</li>
<li>将旧 buckets 赋给 oldbuckets，创建新 buckets，并初始化 nevacuate 等字段；</li>
<li>如果存在预分配的 overflow，会把它们提升为 oldoverflow。</li>
</ul></section><section><h2>9.4 扩容迁移规则与渐进式迁移<a href="#94-扩容迁移规则与渐进式迁移"><span>#</span></a></h2><ul>
<li>在增量扩容中，旧桶中的条目要么迁到 x 区（同索引），要么迁到 y 区（索引 + oldBucketsLen），取决于 hash 的低位；</li>
<li>map 采用渐进式扩容：每次写/删会迁移两组桶 —— 当前命中的桶与最小未迁移桶（h.nevacuate）；</li>
<li>evacuate 会遍历旧桶，把条目按规则搬到目标桶（并标记已搬迁），在适当时机释放旧桶引用。</li>
</ul></section></section>]]></content>
    <author><name>Zowely</name></author>
    <category term="Go源码" />
    <category term="Golang" />
    <category term="源码解析" />
    <category term="面试" />
  </entry>
  <entry>
    <title>总结一些八股答案</title>
    <link href="https://www.lansganbs.cn/posts/%E9%9D%A2%E8%AF%95%E7%9B%B8%E5%85%B3/%E6%80%BB%E7%BB%93%E4%B8%80%E4%BA%9B%E5%85%AB%E8%82%A1%E7%AD%94%E6%A1%88/" rel="alternate" type="text/html" />
    <id>https://www.lansganbs.cn/posts/%E9%9D%A2%E8%AF%95%E7%9B%B8%E5%85%B3/%E6%80%BB%E7%BB%93%E4%B8%80%E4%BA%9B%E5%85%AB%E8%82%A1%E7%AD%94%E6%A1%88/</id>
    <published>2026-01-04T00:00:00.000Z</published>
    <updated>2026-01-28T20:49:42.000Z</updated>
    <summary>记录总结一些常见的八股面试题答案</summary>
    <content type="html"><![CDATA[<section><h1>怎么保证 MQ 消息顺序性<a href="#怎么保证-mq-消息顺序性"><span>#</span></a></h1><p>严格来说，无法保证。首先，这个 MQ 消息顺序性是得区分全局顺序还是局部顺序的。</p><p>如果是全局顺序，那这个需求肯定是不合理的。我们一般做的就是局部顺序。</p><p>那局部顺序因为一个 topic 下面有多个 queue，正常发送是肯定无法保证局部顺序的。所以，我们就需要给需要保证顺序的消息分组路由在同一个 queue 里面，这样就能保证消息的顺序性。</p><p>但是，如果说消费者是通过线程池并行访问的，这时又会出现问题。那消费者就可以维护内存中的阻塞队列，同样的把同一个订单的消息放在同一个阻塞队列，一个队列对应一个线程并行的处理。</p><p>那么问题又来了，顺序消费的 topic 里面的分区数和队列数需要是固定的，不能轻易扩容和缩容。顺序消费的 topic 分区数必须固定，一旦扩容和缩容，消息就可能被路由到其他的队列中或者其他的分区。扩容或者某个分区挂了，会触发 rebalance，那某个分区可能从 A 变 B，就会导致拉错消息，这也会导致乱序问题。</p><p>最后，消息消费失败了，会放到队列里重试。那这时候就涉及到两种情况：放到阻塞队列里，这直接就无法保证顺序性了；我们只能放在本地队列。但是放在本地队列，本地重试遇到 rebalance 的时候，就可能会被其他消费者处理，一样会出问题。</p><p>所以，MQ 本身就是一个高性能不太可靠的中间件，不应该完全依赖 MQ 去保障。极端情况下很有可能会出问题的，只能做好兜底措施，比如通过定时任务做好数据对账、数据补偿等等。</p></section>
<section><h1>如何保证消息可靠性？消息重试应该本地重试还是重试队列<a href="#如何保证消息可靠性消息重试应该本地重试还是重试队列"><span>#</span></a></h1><p>首先，消息丢失的原因主要有几种：1. 从生产者发送到 MQ 的过程中丢失；2. MQ 自己也会弄丢，比如说宕机重启等等的；3. MQ 把消息发送给消费者也可能会丢；4. 消费者处理完消息没有成功确认或者说线程重启了、崩溃了等等。</p><p>这些都会导致消息丢失。</p><p>首先，从生产者发送到 MQ 的过程中丢失，我们可以用 ACK 机制，MQ 收到直接给生产者一个 ACK 就可以了。</p><p>其次，MQ 自己丢失消息，这个我们可以通过开启消息持久化来解决。这样就算宕机重启也不会丢失消息，MQ 集群保证高可用，避免宕机。</p><p>最后，消费者还没消费就丢了，那就用消费者 ACK 机制。</p><p>如果说消费者消费失败了，这个时候就需要重试机制了。应该放在重试队列肯定是最好的，因为本地队列遇到 rebalance 会丢失消息。消费者数量变化了或者说 Topic 分区数变化了，都会导致 rebalance。本地重试就是在内存里，当队列被回收的时候，发生 rebalance，这些没有被确认和待处理的消息就和队列一起被丢弃了。所以说，本地重试是不靠谱的。</p><p>而且，重试队列还有其他的好处，因为它是独立的队列或者 topic，可以去做好监控、观测，重试的次数、延迟、堆积都能监控到，这样就能及时发现问题，进行补偿。</p><p>其次，在做好消息的幂等，加上消息的唯一 ID，再加上 Redis 的 setNX 去保证幂等，或者其他方式都是可以的。总之，就是避免消息丢失带来的重复消费问题。</p><p>但是，无论再怎么做，MQ 都可能出问题，就是可能因为 rebalance 加上本地重试造成消息丢失，就是可能因为幂等性没做好出问题，也可能因为服务抖动、业务异常等等出现一些现象。</p><p>所以，对于重要场景就是要做好兜底的措施。首先，就是死信队列，去保存拒绝、处理失败、超时的这些信息，便于后面复核、修复、重放，配合链路追踪去定位这些问题。</p><p>做好定时的消息对账，保证最终消息一致性，保证各种数据最终态没问题，这样才能真正的保证消息的可靠、消息的幂等。</p></section>
<section><h1>缓存击穿、穿透、雪崩怎么解决<a href="#缓存击穿穿透雪崩怎么解决"><span>#</span></a></h1><section><h2>缓存击穿<a href="#缓存击穿"><span>#</span></a></h2><p>就是一个高并发的 key 过期失效了，这时候大量请求直接打到数据库上，造成数据库压力过大，甚至宕机。</p><p>那缓存击穿有很多方案，甚至有很多公司搞了一个中间件或者框架去解决这个问题。主要就是有几种方法。</p><p>首先，就是互斥锁，也就是加锁，只有第一个请求去加载数据，其他请求等待。这样就能避免大量请求打到数据库上，但是这种方式会导致大量请求阻塞等待，影响响应时间。</p><p>然后，就可以用逻辑过期，设计缓存是永不过期的，数据内部去维护一个过期时间。请求来了之后拿到数据，判断这个过期时间是否过期，如果过期了，那就异步去更新缓存，其他的请求先返回旧数据。这样就能避免大量请求打到数据库上，但是这种方法数据一致性会差一些。</p><p>最后，就是一些中间件或者框架，比如快手的 RPC 服务，叫做 cache setter，通过一致性哈希让查同一个 key 的请求都路由到同一个 cache setter。出现了 miss 的情况，那就尝试从 map 里拿出一个 countDownLatch，拿不到就说明是第一个请求，那就给 map 中 put 一个 countDownLatch，然后去更新缓存。后续的请求过来就从 map 里拿到这个 countDownLatch，然后被 await 阻塞住，等待第一个请求更新完缓存之后，调用 countDown() 方法，其他线程就解除阻塞了。</p><p>还有京东的方案 jd hot key，通过检测 hot key，直接放在本地缓存里，这样就能避免击穿。</p></section><section><h2>缓存穿透<a href="#缓存穿透"><span>#</span></a></h2><p>就是请求一个根本不存在的数据，这个请求会直接打到数据库上，造成数据库压力过大。</p><p>这时候，主要就是对这些请求做好校验、拦截、限流、设置黑名单等等，尽量就在网关这一步拦住非法请求。</p><p>然后，就是缓存空值，对于不存在的数据，也缓存一个空值。这样下次请求来的时候，就不会打到数据库上了。</p><p>但是，每一次查询的值都不一样，这时候也会造成压力。所以，就上布隆过滤器，写数据库的时候同时写布隆过滤器，这样查询的时候先查布隆过滤器，如果不存在，直接拦截掉，不用查数据库。</p><p>但缺点还是有，一个是误判率，另外一个是布隆过滤器本身不支持删除。如果数据库做了删除操作，布隆过滤器还是存在的，这时候就需要定期重建布隆过滤器。</p></section><section><h2>缓存雪崩<a href="#缓存雪崩"><span>#</span></a></h2><p>就是缓存集中过期失效了，或者干脆 Redis 宕机了，这时候大量请求打到数据库上，造成数据库压力过大。</p><p>第一种方案，就是在固定的过期时间上，加上一个随机值，这样就能避免大量 key 在同一时间过期。</p><p>其次，就是针对几乎不太变化的数据，设置永不过期。这样就能避免这些数据过期之后，大量请求打到数据库上。如果说需要修改，那异步的去删除和更新缓存就可以了。</p><p>第三种，就是针对能够预知到的热点数据，进行预热。比如说双十一活动之前，把一些热点数据提前加载到缓存里，这样就能避免活动开始之后，大量请求打到数据库上。</p><p>最后，就是比如说 Redis 宕机了，内存不够了，或者说缓存里大量冷数据进来导致缓存被淘汰了，或者就是完全不可抗力因素，机房被水淹了导致的缓存雪崩。那就是做好 Redis 的高可用，该上主从上主从，该上集群上集群，多机房的容灾，同城双活，异地多活，该上上。对于不可控的意外情况做好兜底措施，做好服务降级、限流、熔断，避免服务被打垮了。</p></section></section>
<section><h1>Redis 的数据持久化<a href="#redis-的数据持久化"><span>#</span></a></h1><p>Redis 提供了两种持久化方式：RDB（Redis DataBase）和 AOF（Append Only File）。</p><ul>
<li>RDB 持久化：RDB 是 Redis 默认的持久化方式。它会在指定的时间间隔内生成数据快照，并将其保存到磁盘上的 RDB 文件中。RDB 持久化的优点是恢复速度快，占用空间小，但缺点是可能会丢失最近一段时间的数据，因为它是基于时间间隔进行持久化的。</li>
<li>AOF 持久化：AOF 是另一种持久化方式，它会将每个写操作追加到一个日志文件中。AOF 持久化的优点是数据恢复更完整，可以配置为每次写操作都进行持久化，但缺点是文件体积较大，恢复速度较慢。</li>
</ul><p>Redis 还支持混合持久化模式，即同时使用 RDB 和 AOF。在 AOF 重写的时候，把当前内存里的数据以 RDB 的方式写进去，把后面的新命令以 AOF 的形式写下来。这样前半段是 RDB 的二进制，后面是 AOF 的命令，可以在保证数据完整性的同时，提高恢复速度。</p></section>]]></content>
    <author><name>Zowely</name></author>
    <category term="面试相关" />
    <category term="面试" />
    <category term="Golang" />
    <category term="Redis" />
  </entry>
  <entry>
    <title>YAML 在 Go 语言中的使用</title>
    <link href="https://www.lansganbs.cn/posts/%E9%A1%B9%E7%9B%AE%E5%BC%80%E5%8F%91/yaml-%E5%9C%A8-go-%E8%AF%AD%E8%A8%80%E4%B8%AD%E7%9A%84%E4%BD%BF%E7%94%A8/" rel="alternate" type="text/html" />
    <id>https://www.lansganbs.cn/posts/%E9%A1%B9%E7%9B%AE%E5%BC%80%E5%8F%91/yaml-%E5%9C%A8-go-%E8%AF%AD%E8%A8%80%E4%B8%AD%E7%9A%84%E4%BD%BF%E7%94%A8/</id>
    <published>2025-11-07T00:00:00.000Z</published>
    <updated>2025-11-07T20:32:47.000Z</updated>
    <summary>本文系统讲解 YAML 语法、在 Go 中的使用方法，以及如何用 YAML 描述 OpenAPI 接口规范，助力 Go 项目开发。</summary>
    <content type="html"><![CDATA[<section><h1>YAML 与 OpenAPI 在 Go 项目中的系统讲解<a href="#yaml-与-openapi-在-go-项目中的系统讲解"><span>#</span></a></h1><section><h2>一、YAML 的语法与模型<a href="#一yaml-的语法与模型"><span>#</span></a></h2><section><h3>1. 基本数据结构<a href="#1-基本数据结构"><span>#</span></a></h3><p>YAML 的数据结构与 JSON 对应：</p><ul>
<li>对象（映射、map）：<code>key: value</code></li>
<li>数组（序列、list）：
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>fruits</span><span>:</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>- </span><span>apple</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>- </span><span>banana</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
</li>
<li>标量（scalar）：字符串、数字、布尔、null、时间等。</li>
</ul><p>YAML 强依赖缩进表达层级：只用空格，不要用 Tab。团队应统一 2 或 4 个空格缩进。</p></section><section><h3>2. 字符串与数字<a href="#2-字符串与数字"><span>#</span></a></h3><ul>
<li>
<p>普通字符串可不加引号；包含冒号、前导零、布尔词、特殊字符时建议加引号，避免被误解析：</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>code</span><span>: </span><span>"01"</span><span>        </span><span># 避免被当作数字</span></div></div><div><div><div>2</div></div><div><span>enabled</span><span>: </span><span>"off"</span><span>    </span><span># 避免被当作 false（YAML 有多种布尔字面量）</span></div></div><div><div><div>3</div></div><div><span>path</span><span>: </span><span>"/v1/users"</span><span> </span><span># 含斜杠更安全</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
</li>
<li>
<p>多行字符串有两种常用块标量：</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>desc</span><span>: </span><span>|</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>第一行</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>第二行</span></div></div><div><div><div>4</div></div><div><span>note</span><span>: </span><span>&gt;</span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>这一段</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>渲染为单行</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
<p><code>|</code> 保留换行；<code>&gt;</code> 折叠换行为空格。可用 <code>|-</code>、<code>|+</code> 控制末尾换行修剪。</p>
</li>
</ul></section><section><h3>3. 数组与对象的行内写法<a href="#3-数组与对象的行内写法"><span>#</span></a></h3><ul>
<li>
<p>行内（flow）风格与 JSON 类似：</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>obj</span><span>: {</span><span>a</span><span>: </span><span>1</span><span>, </span><span>b</span><span>: </span><span>2</span><span>}</span></div></div><div><div><div>2</div></div><div><span>arr</span><span>: [</span><span>1</span><span>, </span><span>2</span><span>, </span><span>3</span><span>]</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
</li>
<li>
<p>多数配置建议使用缩进（block）风格，可读性更高。</p>
</li>
</ul></section><section><h3>4. 引用、锚点与合并（进阶）<a href="#4-引用锚点与合并进阶"><span>#</span></a></h3><ul>
<li>
<p>复用片段：</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>base</span><span>: </span><span>&amp;</span><span>base</span></div></div><div><div><div>2</div></div><div><span>  </span><span>retries</span><span>: </span><span>3</span></div></div><div><div><div>3</div></div><div><span>  </span><span>timeout</span><span>: </span><span>5s</span></div></div><div><div><div>4</div></div><div><span>server</span><span>:</span></div></div><div><div><div>5</div></div><div><span>  </span><span>&lt;&lt;</span><span>: </span><span>*</span><span>base</span></div></div><div><div><div>6</div></div><div><span>  </span><span>timeout</span><span>: </span><span>10s</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
<p>合并键 <code>&lt;&lt;</code> 会把 <code>base</code> 的键值并入 <code>server</code>。
注意：不同工具对锚点/合并支持不完全一致，写 OpenAPI 时尽量谨慎使用。</p>
</li>
</ul></section><section><h3>5. 注释与文件后缀<a href="#5-注释与文件后缀"><span>#</span></a></h3><ul>
<li>注释用 <code>#</code>。</li>
<li><code>.yaml</code> 与 <code>.yml</code> 完全等价，二者只是扩展名不同。实践中各生态有约定俗成：如 <code>docker-compose.yml</code>、Kubernetes 常见 <code>.yaml</code>。</li>
</ul><hr /></section></section><section><h2>二、在 Go 中读取 YAML<a href="#二在-go-中读取-yaml"><span>#</span></a></h2><section><h3>1. 使用 <code>gopkg.in/yaml.v3</code><a href="#1-使用-gopkginyamlv3"><span>#</span></a></h3><p>适合直接把 YAML 映射到结构体。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>type</span><span> </span><span>Server</span><span> </span><span>struct</span><span> {</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>Host </span><span>string</span><span> </span><span>`yaml:"host"`</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>Port </span><span>int</span><span>    </span><span>`yaml:"port"`</span></div></div><div><div><div>4</div></div><div><span>}</span></div></div><div><div><div>5</div></div><div><span>type</span><span> </span><span>Config</span><span> </span><span>struct</span><span> {</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>Server </span><span>Server</span><span> </span><span>`yaml:"server"`</span></div></div><div><div><div>7</div></div><div><span>}</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>data, err </span><span>:=</span><span> os.</span><span>ReadFile</span><span>(</span><span>"config.yaml"</span><span>)</span></div></div><div><div><div>10</div></div><div><span>if</span><span> err </span><span>!=</span><span> </span><span>nil</span><span> { </span><span>/* handle */</span><span> }</span></div></div><div><div><div>11</div></div><div><span>var</span><span> cfg </span><span>Config</span></div></div><div><div><div>12</div></div><div><span>if</span><span> err </span><span>:=</span><span> yaml.</span><span>Unmarshal</span><span>(data, </span><span>&amp;</span><span>cfg); err </span><span>!=</span><span> </span><span>nil</span><span> { </span><span>/* handle */</span><span> }</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>要点与常见问题：</p><ul>
<li>结构体标签用 <code>yaml:"fieldName"</code>；未匹配的字段会被忽略。</li>
<li>建议在解析前设置默认值（或使用 viper 的默认值能力）。</li>
<li>对可能缺省的字段，使用指针类型可区分“没填”和“零值”。</li>
<li>时间、持续时间等：<code>time.Duration</code> 在 YAML 中以字符串表示（如 <code>"5s"</code>），解析时可用自定义类型或中间字符串再转换。</li>
<li>严格模式：社区常见做法是加载后对未识别键进行二次校验（如先反序列化到 <code>map[string]any</code> 做白名单检查），或在 CI 用 schema 校验配置。</li>
</ul></section><section><h3>2. 使用 <code>github.com/spf13/viper</code><a href="#2-使用-githubcomspf13viper"><span>#</span></a></h3><p>适合复杂项目的“配置中心”方案：多格式、环境变量覆盖、默认值、可选热加载。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>viper.</span><span>SetConfigName</span><span>(</span><span>"config"</span><span>)</span></div></div><div><div><div>2</div></div><div><span>viper.</span><span>SetConfigType</span><span>(</span><span>"yaml"</span><span>)</span></div></div><div><div><div>3</div></div><div><span>viper.</span><span>AddConfigPath</span><span>(</span><span>"."</span><span>)</span></div></div><div><div><div>4</div></div><div><span>if</span><span> err </span><span>:=</span><span> viper.</span><span>ReadInConfig</span><span>(); err </span><span>!=</span><span> </span><span>nil</span><span> { </span><span>/* handle */</span><span> }</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>viper.</span><span>SetDefault</span><span>(</span><span>"server.port"</span><span>, </span><span>8080</span><span>)</span></div></div><div><div><div>7</div></div><div><span>viper.</span><span>AutomaticEnv</span><span>()</span></div></div><div><div><div>8</div></div><div><span>viper.</span><span>SetEnvPrefix</span><span>(</span><span>"APP"</span><span>) </span><span>// APP_SERVER_PORT=9090</span></div></div><div><div><div>9</div></div><div><span>viper.</span><span>SetEnvKeyReplacer</span><span>(strings.</span><span>NewReplacer</span><span>(</span><span>"."</span><span>, </span><span>"_"</span><span>))</span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span>var</span><span> cfg </span><span>Config</span></div></div><div><div><div>12</div></div><div><span>if</span><span> err </span><span>:=</span><span> viper.</span><span>Unmarshal</span><span>(</span><span>&amp;</span><span>cfg); err </span><span>!=</span><span> </span><span>nil</span><span> { </span><span>/* handle */</span><span> }</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>实践建议：</p><ul>
<li>配置层叠：基础配置 + 环境覆盖。例如 <code>config.yaml</code>、<code>config.dev.yaml</code>，再用环境变量兜底。</li>
<li>明确配置优先级：默认值 &lt; 文件 &lt; 环境变量 &lt; 命令行参数。</li>
<li>若启用热加载，记得设计并发安全的配置读取方式（例如原子替换）。</li>
</ul><hr /></section></section><section><h2>三、OpenAPI 用 YAML 描述接口的心智模型<a href="#三openapi-用-yaml-描述接口的心智模型"><span>#</span></a></h2><p>OpenAPI 是一种“接口契约”规范，可以用 JSON 或 YAML 描述。核心结构（3.0 与 3.1 大体一致）：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>openapi</span><span>: </span><span>3.1.0</span></div></div><div><div><div>2</div></div><div><span>info</span><span>:            </span><span># 文档元信息</span></div></div><div><div><div>3</div></div><div><span>servers</span><span>:         </span><span># 服务基地址（可带变量）</span></div></div><div><div><div>4</div></div><div><span>paths</span><span>:           </span><span># 路由与操作（get/post/put/delete/...）</span></div></div><div><div><div>5</div></div><div><span>components</span><span>:      </span><span># 复用部件（schemas、parameters、responses、securitySchemes 等）</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>关键点：</p><ul>
<li><code>paths</code> 定义每个资源路径及其操作（参数、请求体、响应体、状态码、示例等）。</li>
<li><code>components</code> 存放可复用对象，通过 <code>$ref</code> 引用，减少重复。</li>
<li>OpenAPI 3.1 与 JSON Schema 2020-12 更接近；3.0 的 schema 子集略有差异。选择版本要与工具链匹配。</li>
</ul><hr /></section><section><h2>四、OpenAPI 常用关键字与结构说明（与 YAML 语法解耦）<a href="#四openapi-常用关键字与结构说明与-yaml-语法解耦"><span>#</span></a></h2><section><h3>1. 文档与服务层面<a href="#1-文档与服务层面"><span>#</span></a></h3><ul>
<li>
<p><code>openapi</code>: 规范版本。</p>
</li>
<li>
<p><code>info</code>: 文档元信息，含 <code>title</code>、<code>description</code>、<code>version</code> 等。</p>
</li>
<li>
<p><code>servers</code>: 一组基础 URL，可用 <code>variables</code> 定义模板变量及默认值。</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>servers</span><span>:</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>- </span><span>url</span><span>: </span><span>"https://{host}/api"</span></div></div><div><div><div>3</div></div><div><span>    </span><span>variables</span><span>:</span></div></div><div><div><div>4</div></div><div><span>      </span><span>host</span><span>:</span></div></div><div><div><div>5</div></div><div><span>        </span><span>default</span><span>: </span><span>"example.com"</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
</li>
</ul></section><section><h3>2. 路由与操作（<code>paths</code> 与 HTTP 动作）<a href="#2-路由与操作paths-与-http-动作"><span>#</span></a></h3><ul>
<li>
<p><code>paths</code> 的键是路径模板，形如 <code>/users/{id}</code>。</p>
</li>
<li>
<p>每个路径下可定义 <code>get</code>、<code>post</code>、<code>put</code>、<code>delete</code>、<code>patch</code> 等操作。</p>
</li>
<li>
<p>操作级字段常见：</p>
<ul>
<li><code>summary</code>、<code>description</code>：人类可读说明。</li>
<li><code>tags</code>：分组标签。</li>
<li><code>operationId</code>：全局唯一的操作标识，代码生成器常用它生成方法名。</li>
<li><code>parameters</code>：参数列表（path/query/header/cookie）。</li>
<li><code>requestBody</code>：请求体描述及媒体类型。</li>
<li><code>responses</code>：响应状态码映射。</li>
<li><code>security</code>：操作级安全策略（可覆盖全局）。</li>
<li><code>servers</code>：操作级覆盖的服务器地址（可选）。</li>
</ul>
</li>
</ul></section><section><h3>3. 参数（<code>parameters</code>）<a href="#3-参数parameters"><span>#</span></a></h3><ul>
<li>
<p>位置：<code>in</code> 为 <code>path</code>、<code>query</code>、<code>header</code>、<code>cookie</code>。</p>
</li>
<li>
<p>路径参数必须 <code>required: true</code>，且名称与路径模板占位符一致。</p>
</li>
<li>
<p>序列化控制：</p>
<ul>
<li><code>style</code>、<code>explode</code>、<code>allowReserved</code> 等影响数组/对象在查询串或头部的编码方式（如 <code>form</code>、<code>simple</code>、<code>pipeDelimited</code>、<code>spaceDelimited</code> 等）。</li>
</ul>
</li>
<li>
<p>类型通过 <code>schema</code> 指定，支持 <code>type</code>、<code>format</code>、<code>enum</code> 等。</p>
</li>
</ul><p>示例（查询参数数组的不同风格）：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>parameters</span><span>:</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>- </span><span>in</span><span>: </span><span>query</span></div></div><div><div><div>3</div></div><div><span>    </span><span>name</span><span>: </span><span>ids</span></div></div><div><div><div>4</div></div><div><span>    </span><span>schema</span><span>:</span></div></div><div><div><div>5</div></div><div><span>      </span><span>type</span><span>: </span><span>array</span></div></div><div><div><div>6</div></div><div><span>      </span><span>items</span><span>: { </span><span>type</span><span>: </span><span>string</span><span> }</span></div></div><div><div><div>7</div></div><div><span>    </span><span>style</span><span>: </span><span>form</span></div></div><div><div><div>8</div></div><div><span>    </span><span>explode</span><span>: </span><span>true</span><span>    </span><span># ?ids=a&amp;ids=b</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>4. 请求体（<code>requestBody</code>）<a href="#4-请求体requestbody"><span>#</span></a></h3><ul>
<li><code>required</code>: 是否必须。</li>
<li><code>content</code>: 媒体类型到 schema 的映射，例如 <code>application/json</code>、<code>multipart/form-data</code>、<code>application/x-www-form-urlencoded</code>、<code>text/plain</code>。</li>
<li>对上传文件，在 3.0 中常用 <code>type: string, format: binary</code>，配合 <code>multipart/form-data</code>。</li>
</ul></section><section><h3>5. 响应（<code>responses</code>）<a href="#5-响应responses"><span>#</span></a></h3><ul>
<li>键为状态码字符串（建议加引号避免 YAML 把 <code>200</code> 解析成数字）。</li>
<li><code>default</code> 为兜底响应。</li>
<li><code>headers</code> 可定义响应头。</li>
<li><code>content</code> 定义不同媒体类型的 schema。</li>
</ul><p>示例：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>responses</span><span>:</span></div></div><div><div><div>2</div></div><div><span>  </span><span>"200"</span><span>:</span></div></div><div><div><div>3</div></div><div><span>    </span><span>description</span><span>: </span><span>ok</span></div></div><div><div><div>4</div></div><div><span>    </span><span>content</span><span>:</span></div></div><div><div><div>5</div></div><div><span>      </span><span>application/json</span><span>:</span></div></div><div><div><div>6</div></div><div><span>        </span><span>schema</span><span>:</span></div></div><div><div><div>7</div></div><div><span>          </span><span>$ref</span><span>: </span><span>"#/components/schemas/User"</span></div></div><div><div><div>8</div></div><div><span>  </span><span>default</span><span>:</span></div></div><div><div><div>9</div></div><div><span>    </span><span>description</span><span>: </span><span>error</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>6. 复用部件（<code>components</code>）<a href="#6-复用部件components"><span>#</span></a></h3><p>常见子节：</p><ul>
<li><code>schemas</code>：数据模型（强烈建议集中管理）。</li>
<li><code>parameters</code>：可复用的参数定义。</li>
<li><code>responses</code>：可复用的响应。</li>
<li><code>requestBodies</code>、<code>headers</code>、<code>examples</code>、<code>securitySchemes</code>、<code>links</code>、<code>callbacks</code> 等。</li>
</ul><p><code>$ref</code> 用法：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>schema</span><span>:</span></div></div><div><div><div>2</div></div><div><span>  </span><span>$ref</span><span>: </span><span>"#/components/schemas/User"</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>OpenAPI 3.0 中 <code>$ref</code> 节点不能与并列的其他键共存；3.1 放宽，但生成器支持度不一，务必验证。</p><hr /></section></section><section><h2>五、Schema（数据模型）关键字详解（与 Go 类型映射）<a href="#五schema数据模型关键字详解与-go-类型映射"><span>#</span></a></h2><section><h3>1. 基本类型与格式<a href="#1-基本类型与格式"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>type</span><span>: </span><span>string</span></div></div><div><div><div>2</div></div><div><span>type</span><span>: </span><span>integer</span></div></div><div><div><div>3</div></div><div><span>  </span><span>format</span><span>: </span><span>int32</span><span>     </span><span># 也可 int64</span></div></div><div><div><div>4</div></div><div><span>type</span><span>: </span><span>number</span></div></div><div><div><div>5</div></div><div><span>  </span><span>format</span><span>: </span><span>float</span><span>     </span><span># 或 double</span></div></div><div><div><div>6</div></div><div><span>type</span><span>: </span><span>boolean</span></div></div><div><div><div>7</div></div><div><span>type</span><span>: </span><span>array</span></div></div><div><div><div>8</div></div><div><span>  </span><span>items</span><span>: { </span><span>$ref</span><span>: </span><span>"#/components/schemas/User"</span><span> }</span></div></div><div><div><div>9</div></div><div><span>type</span><span>: </span><span>object</span></div></div><div><div><div>10</div></div><div><span>  </span><span>properties</span><span>: { </span><span>...</span><span> }</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>Go 常见映射（生成器可能略有差异）：</p><ul>
<li><code>string</code> → <code>string</code></li>
<li><code>integer(int32)</code> → <code>int32</code> 或 <code>int</code></li>
<li><code>integer(int64)</code> → <code>int64</code></li>
<li><code>number(float/double)</code> → <code>float32/float64</code></li>
<li><code>boolean</code> → <code>bool</code></li>
<li><code>array[T]</code> → <code>[]T</code></li>
<li><code>object</code> → <code>struct</code> 或 <code>map[string]any</code>（有时 <code>additionalProperties</code> 决定是否为 map）</li>
</ul></section><section><h3>2. 约束与校验<a href="#2-约束与校验"><span>#</span></a></h3><ul>
<li>字符串：<code>minLength</code>、<code>maxLength</code>、<code>pattern</code>、<code>enum</code>、<code>format</code>（如 <code>date-time</code>、<code>uuid</code>、<code>email</code>）。</li>
<li>数值：<code>minimum</code>、<code>maximum</code>、<code>exclusiveMinimum</code>、<code>exclusiveMaximum</code>、<code>multipleOf</code>。</li>
<li>数组：<code>minItems</code>、<code>maxItems</code>、<code>uniqueItems</code>。</li>
<li>对象：<code>required</code>、<code>minProperties</code>、<code>maxProperties</code>、<code>additionalProperties</code>（控制是否允许未声明字段，及其类型）。</li>
</ul><p>示例：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>type</span><span>: </span><span>object</span></div></div><div><div><div>2</div></div><div><span>required</span><span>: [</span><span>id</span><span>, </span><span>name</span><span>]</span></div></div><div><div><div>3</div></div><div><span>properties</span><span>:</span></div></div><div><div><div>4</div></div><div><span>  </span><span>id</span><span>:</span></div></div><div><div><div>5</div></div><div><span>    </span><span>type</span><span>: </span><span>string</span></div></div><div><div><div>6</div></div><div><span>    </span><span>format</span><span>: </span><span>uuid</span></div></div><div><div><div>7</div></div><div><span>  </span><span>name</span><span>:</span></div></div><div><div><div>8</div></div><div><span>    </span><span>type</span><span>: </span><span>string</span></div></div><div><div><div>9</div></div><div><span>    </span><span>minLength</span><span>: </span><span>1</span></div></div><div><div><div>10</div></div><div><span>  </span><span>labels</span><span>:</span></div></div><div><div><div>11</div></div><div><span>    </span><span>type</span><span>: </span><span>object</span></div></div><div><div><div>12</div></div><div><span>    </span><span>additionalProperties</span><span>:</span></div></div><div><div><div>13</div></div><div><span>      </span><span>type</span><span>: </span><span>string</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>3. 组合与多态<a href="#3-组合与多态"><span>#</span></a></h3><ul>
<li><code>oneOf</code>: 多选一</li>
<li><code>anyOf</code>: 任一匹配</li>
<li><code>allOf</code>: 全部匹配（常用于“继承/扩展”）</li>
<li><code>not</code>: 排除</li>
</ul><p>Go 中对 <code>oneOf/anyOf</code> 的表达策略：</p><ul>
<li>手写自定义类型与 <code>json.Unmarshaler</code> 判别；</li>
<li>借助生成器的辅助类型；</li>
<li>通过 <code>discriminator</code> 明确区分字段，提高可生成性与可读性。</li>
</ul></section><section><h3>4. 可空、读写属性、弃用<a href="#4-可空读写属性弃用"><span>#</span></a></h3><ul>
<li>OpenAPI 3.0：<code>nullable: true</code>；3.1 更推荐 <code>type: ["string", "null"]</code> 等联合类型写法。</li>
<li><code>readOnly</code>：仅响应体现，客户端提交可省略。</li>
<li><code>writeOnly</code>：仅请求体现，响应应省略。</li>
<li><code>deprecated: true</code>：标记弃用，但不影响校验。</li>
</ul></section><section><h3>5. 示例与默认值<a href="#5-示例与默认值"><span>#</span></a></h3><ul>
<li><code>example</code>：单个示例。</li>
<li><code>examples</code>：命名示例集合。</li>
<li><code>default</code>：字段未提供时的默认值（规范层面的语义，具体是否生效依赖服务端实现与生成器支持）。</li>
</ul><hr /></section></section><section><h2>六、将 OpenAPI 应用到 Go 的工作流<a href="#六将-openapi-应用到-go-的工作流"><span>#</span></a></h2><section><h3>1. 验证与质量控制<a href="#1-验证与质量控制"><span>#</span></a></h3><ul>
<li>
<p>在 CI 中加入校验步骤：语法有效性、引用完整性、风格约束。</p>
</li>
<li>
<p>可以使用 Go 库（如 <code>kin-openapi</code>）在单测或启动时验证文档：</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>loader </span><span>:=</span><span> openapi3.</span><span>NewLoader</span><span>()</span></div></div><div><div><div>2</div></div><div><span>doc, err </span><span>:=</span><span> loader.</span><span>LoadFromFile</span><span>(</span><span>"openapi.yaml"</span><span>)</span></div></div><div><div><div>3</div></div><div><span>if</span><span> err </span><span>!=</span><span> </span><span>nil</span><span> { </span><span>/* handle */</span><span> }</span></div></div><div><div><div>4</div></div><div><span>if</span><span> err </span><span>:=</span><span> doc.</span><span>Validate</span><span>(context.</span><span>Background</span><span>()); err </span><span>!=</span><span> </span><span>nil</span><span> { </span><span>/* handle */</span><span> }</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
</li>
</ul></section><section><h3>2. 代码生成（服务端与客户端）<a href="#2-代码生成服务端与客户端"><span>#</span></a></h3><p>两条常见线路：</p><ul>
<li>
<p>轻量生成器（如 <code>deepmap/oapi-codegen</code>）
适合与 <code>chi</code>、<code>gin</code> 集成，能生成类型、服务接口、客户端、请求校验中间件。
典型做法：</p>
<ol>
<li>用 OpenAPI 定义契约；</li>
<li>生成 Go 类型与服务接口；</li>
<li>你只需实现接口方法并挂载到路由；</li>
<li>可用生成的校验器在入站请求处做 schema 校验。</li>
</ol>
</li>
<li>
<p>OpenAPI Generator（多语言生态广，需 Java 运行时）
适合同时生成多语言客户端或服务器桩代码。</p>
</li>
</ul><p>选择原则：以团队现有栈、生成代码质量、可维护性与工具链稳定性为首要考虑。</p></section><section><h3>3. 路由绑定与校验<a href="#3-路由绑定与校验"><span>#</span></a></h3><ul>
<li>生成器通常提供“把接口实现绑定到路由”的适配函数。</li>
<li>强烈建议在路由层启用“请求校验中间件”，确保落到业务逻辑之前已经满足 OpenAPI 的参数与 schema 约束。</li>
</ul></section><section><h3>4. 与 YAML 配置的关系<a href="#4-与-yaml-配置的关系"><span>#</span></a></h3><ul>
<li>
<p>OpenAPI 文档本身是 YAML 文件；服务运行配置（端口、数据库）也是 YAML。二者解耦：</p>
<ul>
<li>配置 YAML → 用 <code>yaml.v3/viper</code> 加载到结构体；</li>
<li>OpenAPI YAML → 仅用于契约、校验与代码生成，不直接参与运行时配置。</li>
</ul>
</li>
</ul></section></section><section><h2>七、一个示例<a href="#七一个示例"><span>#</span></a></h2><p>下面是一个在 Go 语言中的 YAML 示例：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>openapi</span><span>: </span><span>3.1.0</span></div></div><div><div><div>2</div></div><div><span>info</span><span>:</span></div></div><div><div><div>3</div></div><div><span>  </span><span>title</span><span>: </span><span>order service</span></div></div><div><div><div>4</div></div><div><span>  </span><span>description</span><span>: </span><span>order service</span></div></div><div><div><div>5</div></div><div><span>  </span><span>version</span><span>: </span><span>1.0.0</span></div></div><div><div><div>6</div></div><div><span>servers</span><span>:</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>- </span><span>url</span><span>: </span><span>'https://{hostname}/api'</span></div></div><div><div><div>8</div></div><div><span>    </span><span>variables</span><span>:</span></div></div><div><div><div>9</div></div><div><span>      </span><span>hostname</span><span>:</span></div></div><div><div><div>10</div></div><div><span>        </span><span>default</span><span>: </span><span>'127.0.0.1'</span></div></div><div><div><div>11</div></div><div>
</div></div><div><div><div>12</div></div><div><span>paths</span><span>:</span></div></div><div><div><div>13</div></div><div><span>  </span><span>/customer/{customerID}/orders/{orderID}</span><span>:</span></div></div><div><div><div>14</div></div><div><span>    </span><span>get</span><span>:</span></div></div><div><div><div>15</div></div><div><span>      </span><span>description</span><span>: </span><span>"get order"</span></div></div><div><div><div>16</div></div><div><span>      </span><span>parameters</span><span>:</span></div></div><div><div><div>17</div></div><div><span><span>        </span></span><span>- </span><span>in</span><span>: </span><span>path</span></div></div><div><div><div>18</div></div><div><span>          </span><span>name</span><span>: </span><span>customerID</span></div></div><div><div><div>19</div></div><div><span>          </span><span>schema</span><span>:</span></div></div><div><div><div>20</div></div><div><span>            </span><span>type</span><span>: </span><span>string</span></div></div><div><div><div>21</div></div><div><span>          </span><span>required</span><span>: </span><span>true</span></div></div><div><div><div>22</div></div><div>
</div></div><div><div><div>23</div></div><div><span><span>        </span></span><span>- </span><span>in</span><span>: </span><span>path</span></div></div><div><div><div>24</div></div><div><span>          </span><span>name</span><span>: </span><span>orderID</span></div></div><div><div><div>25</div></div><div><span>          </span><span>schema</span><span>:</span></div></div><div><div><div>26</div></div><div><span>            </span><span>type</span><span>: </span><span>string</span></div></div><div><div><div>27</div></div><div><span>          </span><span>required</span><span>: </span><span>true</span></div></div><div><div><div>28</div></div><div>
</div></div><div><div><div>29</div></div><div><span>      </span><span>responses</span><span>:</span></div></div><div><div><div>30</div></div><div><span>        </span><span>'200'</span><span>:</span></div></div><div><div><div>31</div></div><div><span>          </span><span>description</span><span>: </span><span>todo</span></div></div><div><div><div>32</div></div><div><span>          </span><span>content</span><span>:</span></div></div><div><div><div>33</div></div><div><span>            </span><span>application/json</span><span>:</span></div></div><div><div><div>34</div></div><div><span>              </span><span>schema</span><span>:</span></div></div><div><div><div>35</div></div><div><span>                </span><span>$ref</span><span>: </span><span>'#/components/schemas/Order'</span></div></div><div><div><div>36</div></div><div><span>        </span><span>default</span><span>:</span></div></div><div><div><div>37</div></div><div><span>          </span><span>description</span><span>: </span><span>todo</span></div></div><div><div><div>38</div></div><div><span>          </span><span>content</span><span>:</span></div></div><div><div><div>39</div></div><div><span>            </span><span>application/json</span><span>:</span></div></div><div><div><div>40</div></div><div><span>              </span><span>schema</span><span>:</span></div></div><div><div><div>41</div></div><div><span>                </span><span>$ref</span><span>: </span><span>'#/components/schemas/Error'</span></div></div><div><div><div>42</div></div><div>
</div></div><div><div><div>43</div></div><div><span>  </span><span>/customer/{customerID}/orders</span><span>:</span></div></div><div><div><div>44</div></div><div><span>    </span><span>post</span><span>:</span></div></div><div><div><div>45</div></div><div><span>      </span><span>description</span><span>: </span><span>"creat order"</span></div></div><div><div><div>46</div></div><div><span>      </span><span>parameters</span><span>:</span></div></div><div><div><div>47</div></div><div><span><span>        </span></span><span>- </span><span>in</span><span>: </span><span>path</span></div></div><div><div><div>48</div></div><div><span>          </span><span>name</span><span>: </span><span>customerID</span></div></div><div><div><div>49</div></div><div><span>          </span><span>schema</span><span>:</span></div></div><div><div><div>50</div></div><div><span>            </span><span>type</span><span>: </span><span>string</span></div></div><div><div><div>51</div></div><div><span>          </span><span>required</span><span>: </span><span>true</span></div></div><div><div><div>52</div></div><div><span>      </span><span>requestBody</span><span>:</span></div></div><div><div><div>53</div></div><div><span>        </span><span>required</span><span>: </span><span>true</span></div></div><div><div><div>54</div></div><div><span>        </span><span>content</span><span>:</span></div></div><div><div><div>55</div></div><div><span>          </span><span>application/json</span><span>:</span></div></div><div><div><div>56</div></div><div><span>            </span><span>schema</span><span>:</span></div></div><div><div><div>57</div></div><div><span>              </span><span>$ref</span><span>: </span><span>'#/components/schemas/CreateOrderRequest'</span></div></div><div><div><div>58</div></div><div>
</div></div><div><div><div>59</div></div><div><span>      </span><span>responses</span><span>:</span></div></div><div><div><div>60</div></div><div><span>        </span><span>'200'</span><span>:</span></div></div><div><div><div>61</div></div><div><span>          </span><span>description</span><span>: </span><span>todo</span></div></div><div><div><div>62</div></div><div><span>          </span><span>content</span><span>:</span></div></div><div><div><div>63</div></div><div><span>            </span><span>application/json</span><span>:</span></div></div><div><div><div>64</div></div><div><span>              </span><span>schema</span><span>:</span></div></div><div><div><div>65</div></div><div><span>                </span><span>$ref</span><span>: </span><span>'#/components/schemas/Order'</span></div></div><div><div><div>66</div></div><div><span>        </span><span>default</span><span>:</span></div></div><div><div><div>67</div></div><div><span>          </span><span>description</span><span>: </span><span>todo</span></div></div><div><div><div>68</div></div><div><span>          </span><span>content</span><span>:</span></div></div><div><div><div>69</div></div><div><span>            </span><span>application/json</span><span>:</span></div></div><div><div><div>70</div></div><div><span>              </span><span>schema</span><span>:</span></div></div><div><div><div>71</div></div><div><span>                </span><span>$ref</span><span>: </span><span>'#/components/schemas/Error'</span></div></div><div><div><div>72</div></div><div>
</div></div><div><div><div>73</div></div><div><span>components</span><span>:</span></div></div><div><div><div>74</div></div><div><span>  </span><span>schemas</span><span>:</span></div></div><div><div><div>75</div></div><div><span>    </span><span>Order</span><span>:</span></div></div><div><div><div>76</div></div><div><span>        </span><span>type</span><span>: </span><span>object</span></div></div><div><div><div>77</div></div><div><span>        </span><span>properties</span><span>:</span></div></div><div><div><div>78</div></div><div><span>            </span><span>id</span><span>:</span></div></div><div><div><div>79</div></div><div><span>              </span><span>type</span><span>: </span><span>string</span></div></div><div><div><div>80</div></div><div><span>            </span><span>customerID</span><span>:</span></div></div><div><div><div>81</div></div><div><span>              </span><span>type</span><span>: </span><span>string</span></div></div><div><div><div>82</div></div><div><span>            </span><span>status</span><span>:</span></div></div><div><div><div>83</div></div><div><span>              </span><span>type</span><span>: </span><span>string</span></div></div><div><div><div>84</div></div><div><span>            </span><span>items</span><span>:</span></div></div><div><div><div>85</div></div><div><span>              </span><span>type</span><span>: </span><span>array</span></div></div><div><div><div>86</div></div><div><span>              </span><span>items</span><span>:</span></div></div><div><div><div>87</div></div><div><span>                </span><span>$ref</span><span>: </span><span>'#/components/schemas/Item'</span></div></div><div><div><div>88</div></div><div><span>            </span><span>paymentLink</span><span>:</span></div></div><div><div><div>89</div></div><div><span>                </span><span>type</span><span>: </span><span>string</span></div></div><div><div><div>90</div></div><div>
</div></div><div><div><div>91</div></div><div><span>    </span><span>Item</span><span>:</span></div></div><div><div><div>92</div></div><div><span>      </span><span>type</span><span>: </span><span>object</span></div></div><div><div><div>93</div></div><div><span>      </span><span>properties</span><span>:</span></div></div><div><div><div>94</div></div><div><span>        </span><span>id</span><span>:</span></div></div><div><div><div>95</div></div><div><span>          </span><span>type</span><span>: </span><span>string</span></div></div><div><div><div>96</div></div><div><span>        </span><span>name</span><span>:</span></div></div><div><div><div>97</div></div><div><span>          </span><span>type</span><span>: </span><span>string</span></div></div><div><div><div>98</div></div><div><span>        </span><span>quantity</span><span>:</span></div></div><div><div><div>99</div></div><div><span>          </span><span>type</span><span>: </span><span>integer</span></div></div><div><div><div>100</div></div><div><span>          </span><span>format</span><span>: </span><span>int32</span></div></div><div><div><div>101</div></div><div><span>        </span><span>priceID</span><span>:</span></div></div><div><div><div>102</div></div><div><span>          </span><span>type</span><span>: </span><span>string</span></div></div><div><div><div>103</div></div><div>
</div></div><div><div><div>104</div></div><div><span>    </span><span>Error</span><span>:</span></div></div><div><div><div>105</div></div><div><span>      </span><span>type</span><span>: </span><span>object</span></div></div><div><div><div>106</div></div><div><span>      </span><span>properties</span><span>:</span></div></div><div><div><div>107</div></div><div><span>        </span><span>message</span><span>:</span></div></div><div><div><div>108</div></div><div><span>          </span><span>type</span><span>: </span><span>string</span></div></div><div><div><div>109</div></div><div>
</div></div><div><div><div>110</div></div><div><span>    </span><span>CreateOrderRequest</span><span>:</span></div></div><div><div><div>111</div></div><div><span>      </span><span>type</span><span>: </span><span>object</span></div></div><div><div><div>112</div></div><div><span>      </span><span>required</span><span>:</span></div></div><div><div><div>113</div></div><div><span><span>        </span></span><span>- </span><span>customerID</span></div></div><div><div><div>114</div></div><div><span><span>        </span></span><span>- </span><span>items</span></div></div><div><div><div>115</div></div><div><span>      </span><span>properties</span><span>:</span></div></div><div><div><div>116</div></div><div><span>        </span><span>customerID</span><span>:</span></div></div><div><div><div>117</div></div><div><span>          </span><span>type</span><span>: </span><span>string</span></div></div><div><div><div>118</div></div><div><span>        </span><span>items</span><span>:</span></div></div><div><div><div>119</div></div><div><span>          </span><span>type</span><span>: </span><span>array</span></div></div><div><div><div>120</div></div><div><span>          </span><span>items</span><span>:</span></div></div><div><div><div>121</div></div><div><span>            </span><span>$ref</span><span>: </span><span>'#/components/schemas/ItemWithQuantity'</span></div></div><div><div><div>122</div></div><div>
</div></div><div><div><div>123</div></div><div><span>    </span><span>ItemWithQuantity</span><span>:</span></div></div><div><div><div>124</div></div><div><span>      </span><span>type</span><span>: </span><span>object</span></div></div><div><div><div>125</div></div><div><span>      </span><span>properties</span><span>:</span></div></div><div><div><div>126</div></div><div><span>        </span><span>id</span><span>:</span></div></div><div><div><div>127</div></div><div><span>          </span><span>type</span><span>: </span><span>string</span></div></div><div><div><div>128</div></div><div><span>        </span><span>quantity</span><span>:</span></div></div><div><div><div>129</div></div><div><span>          </span><span>type</span><span>: </span><span>integer</span></div></div><div><div><div>130</div></div><div><span>          </span><span>format</span><span>: </span><span>int32</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section></section>]]></content>
    <author><name>Zowely</name></author>
    <category term="项目开发" />
    <category term="项目开发" />
    <category term="Golang" />
  </entry>
  <entry>
    <title>基于多邻国的单元单词背诵软件</title>
    <link href="https://www.lansganbs.cn/posts/%E9%A1%B9%E7%9B%AE%E5%BC%80%E5%8F%91/%E5%9F%BA%E4%BA%8E%E5%A4%9A%E9%82%BB%E5%9B%BD%E7%9A%84%E5%8D%95%E5%85%83%E5%8D%95%E8%AF%8D%E8%83%8C%E8%AF%B5%E8%BD%AF%E4%BB%B6/" rel="alternate" type="text/html" />
    <id>https://www.lansganbs.cn/posts/%E9%A1%B9%E7%9B%AE%E5%BC%80%E5%8F%91/%E5%9F%BA%E4%BA%8E%E5%A4%9A%E9%82%BB%E5%9B%BD%E7%9A%84%E5%8D%95%E5%85%83%E5%8D%95%E8%AF%8D%E8%83%8C%E8%AF%B5%E8%BD%AF%E4%BB%B6/</id>
    <published>2025-11-03T00:00:00.000Z</published>
    <updated>2025-11-03T19:37:46.000Z</updated>
    <summary>一个基于间隔重复（SM-2）的离线词汇学习工具，支持「日→中」与「中→日」两种模式、可选本地词典联想、学习统计可视化、单位（单元）管理与简洁的键盘操作。</summary>
    <content type="html"><![CDATA[<section><h1>基于多邻国单元的日语单词背诵软件<a href="#基于多邻国单元的日语单词背诵软件"><span>#</span></a></h1><a href="https://github.com/LANSGANBS/nihon-vocab-trainer" target="_blank"><div><div><div><div></div><div>LANSGANBS</div></div><div>/</div><div>nihon-vocab-trainer</div></div><div></div></div><div>Waiting for api.github.com...</div><div><div>00K</div><div>0K</div><div>0K</div><span>Waiting...</span></div></a><p>一个基于间隔重复（SM-2）的离线词汇学习工具，支持「日→中」与「中→日」两种模式、可选本地词典联想、学习统计可视化、单位（单元）管理与简洁的键盘操作。</p><section><h2>功能特性<a href="#功能特性"><span>#</span></a></h2><ul>
<li>复习队列：默认到期优先，支持“包含未到期/全部”回退</li>
<li>学习模式：日→中（评分）与中→日（输入判定），默认约 70%/30% 混排</li>
<li>假名辅助：输入罗马音即时生成平/片假名候选，一键写回并自动回填罗马音</li>
<li>单元总览：按单元浏览假名/汉字/罗马音/释义与重复次数</li>
<li>学习统计：总览、每日复习量与正确率、EF/重复次数分布；无 matplotlib 时自动退化为表格</li>
<li>可选本地词典：支持 CSV 词典反查，辅助中→日判定与补全</li>
<li>键盘友好：Space 展开/继续，数字键打分（1/2/3/4）</li>
<li>字体与 CJK 友好：自动装载项目内字体并设置回退链，matplotlib 统一 CJK 字体</li>
</ul></section><section><h2>环境要求<a href="#环境要求"><span>#</span></a></h2><ul>
<li>Python &gt;= 3.8（Windows 推荐 3.10+）</li>
<li>依赖
<ul>
<li>PyQt5</li>
<li>matplotlib（可选，用于图表）</li>
</ul>
</li>
<li>SQLite（标准库自带）</li>
</ul></section><section><h2>安装<a href="#安装"><span>#</span></a></h2><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span># 建议使用虚拟环境</span></div></div><div><div><div>2</div></div><div><span>python</span><span> </span><span>-m</span><span> </span><span>venv</span><span> </span><span>.venv</span></div></div><div><div><div>3</div></div><div><span>.venv\Scripts\activate</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span># 安装依赖</span></div></div><div><div><div>6</div></div><div><span>pip</span><span> </span><span>install</span><span> </span><span>-U</span><span> </span><span>pip</span></div></div><div><div><div>7</div></div><div><span>pip</span><span> </span><span>install</span><span> </span><span>PyQt5</span></div></div><div><div><div>8</div></div><div><span>pip</span><span> </span><span>install</span><span> </span><span>matplotlib</span><span>  </span><span># 可选</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>运行<a href="#运行"><span>#</span></a></h2><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span># 在项目根目录执行</span></div></div><div><div><div>2</div></div><div><span>python</span><span> </span><span>main.py</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>首次启动会自动设置应用字体与样式。Windows 下默认使用 <code>Microsoft YaHei UI</code>，并为 matplotlib 配置 CJK 字体。</p></section><section><h2>基本用法<a href="#基本用法"><span>#</span></a></h2><ul>
<li>添加词条：在主界面录入「日语词条」「汉字写法」「假名读音」「中文释义」，可选择「单元」
<ul>
<li>在“假名”框可输入罗马音，自动弹出平/片假名候选并写回</li>
</ul>
</li>
<li>复习：
<ul>
<li>点击开始复习，可选择单元与是否包含未到期</li>
<li>日→中：Space 展示释义后，用数字键评分（1=Again，2=Hard，3=Good，4=Easy）</li>
<li>中→日：输入假名或汉字后“确认”；也可直接点“没记住”</li>
</ul>
</li>
<li>统计：菜单中打开统计对话框；默认以“安全模式”纯表格展示（避免 Qt5Agg 崩溃），如安装了 matplotlib 会显示图表</li>
<li>单元总览：在列表中按单元查看词条的假名/汉字/罗马音/释义与重复次数</li>
</ul></section><section><h2>快捷键<a href="#快捷键"><span>#</span></a></h2><ul>
<li>Space：展开/收起释义；判定完成后进入下一题</li>
<li>数字键 1/2/3/4：日→中模式下评分</li>
<li>回车：在中→日模式“确认”答案</li>
</ul></section><section><h2>设置项<a href="#设置项"><span>#</span></a></h2><ul>
<li>包含未到期的词</li>
<li>每次会话上限（0 表示不限）</li>
<li>打开复习时自动打乱</li>
<li>表格行高与操作列宽</li>
<li>字号缩放</li>
<li>本地词典文件路径（CSV）</li>
<li>记住“中文释义→候选”的偏好</li>
</ul><p>所有设置存入应用配置，并在启动时动态应用，包括表格行高、列宽、字号缩放与本地词典热切换。</p></section><section><h2>本地词典（可选）<a href="#本地词典可选"><span>#</span></a></h2><ul>
<li>在设置中选择本地 CSV 文件路径</li>
<li>建议列格式：<code>term,kana,meaning</code>
<ul>
<li>例如：<code>食べる,たべる,吃</code></li>
</ul>
</li>
<li>用途
<ul>
<li>中→日判定兜底：当用户输入汉字但标准答案是“假名”时，按词典中的“该汉字的假名”与标准假名比对</li>
<li>输入联想与补全</li>
</ul>
</li>
</ul><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>term,</span><span>kana,</span><span>meaning</span></div></div><div><div><div>2</div></div><div><span>食べる,</span><span>たべる,</span><span>吃</span></div></div><div><div><div>3</div></div><div><span>今日,</span><span>きょう,</span><span>今天</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>学习统计<a href="#学习统计"><span>#</span></a></h2><ul>
<li>总览：总词条数、单元 Top10、最近新增趋势</li>
<li>复习活动：每日复习量、每日正确率</li>
<li>记忆质量：EF 分布、重复次数分布</li>
<li>无 matplotlib 时以表格形式展示；装有 matplotlib 时显示柱状/折线/直方图，并应用 CJK 字体</li>
</ul></section><section><h2>复习与排程<a href="#复习与排程"><span>#</span></a></h2><ul>
<li>记忆算法：基于 SM-2，保存 <code>interval/repetition/ef/last_review</code></li>
<li>事件记录：每次判定写入 <code>reviews</code> 表，包含日期、模式与质量分</li>
<li>模式与组卷：按队列大小约 70% 日→中、30% 中→日，随机混排</li>
<li>队列来源：优先到期卡片；若无到期则回退到全部；按单元过滤</li>
</ul></section><section><h2>字体与显示<a href="#字体与显示"><span>#</span></a></h2><ul>
<li>项目内置 <code>ZenMaruGothic-Medium.ttf</code> 与 <code>Hanalei-Regular.ttf</code></li>
<li>应用默认字体：
<ul>
<li>Windows：<code>Microsoft YaHei UI</code></li>
<li>其它平台：<code>Zen Maru Gothic</code></li>
</ul>
</li>
<li>为 <code>Hanalei</code> 插入 CJK 回退链，避免缺字</li>
<li>matplotlib 统一设置 <code>font.sans\-serif</code>，关闭负号乱码</li>
</ul></section><section><h2>数据库<a href="#数据库"><span>#</span></a></h2><ul>
<li>使用 SQLite，核心表：
<ul>
<li><code>cards</code>：词条、单位、EF、重复次数、到期时间、创建时间等</li>
<li><code>reviews</code>：复习时间、模式（0=日→中/1=中→日）、质量分</li>
</ul>
</li>
<li>应用在复习与编辑时更新 <code>cards</code>，每次判定追加 <code>reviews</code></li>
</ul></section><section><h2>故障排除<a href="#故障排除"><span>#</span></a></h2><ul>
<li>统计图表崩溃（Qt5Agg）：应用已默认以“安全模式”打开统计（纯表格），或安装 <code>matplotlib</code> 并确保图形后端可用</li>
<li>字体缺字：确认项目根目录存在 <code>ZenMaruGothic-Medium.ttf</code>，Windows 下确保系统有 <code>Microsoft YaHei UI</code></li>
<li>词典无效：检查 CSV 列顺序为 <code>term,kana,meaning</code>，且在设置中选择了正确路径</li>
</ul></section><section><h2>开发与运行于<a href="#开发与运行于"><span>#</span></a></h2><ul>
<li>IDE：<code>PyCharm 2024.3.1.1</code></li>
<li>操作系统：Windows</li>
<li>启动入口：<code>main.py</code></li>
</ul></section><section><h2>软件打包<a href="#软件打包"><span>#</span></a></h2><section><h3>安装 <code>PyInstaller</code><a href="#安装-pyinstaller"><span>#</span></a></h3><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>pip</span><span> </span><span>install</span><span> </span><span>pyinstaller</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>进入脚本目录，执行打包命令<a href="#进入脚本目录执行打包命令"><span>#</span></a></h3><ul>
<li>
<p>打包最新版本：</p>
<div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>python</span><span> </span><span>build_vocab.py</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
</li>
<li>
<p>指定版本前缀：</p>
<div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>python</span><span> </span><span>build_vocab.py</span><span> </span><span>-v</span><span> </span><span>2.6</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
</li>
<li>
<p>显示控制台：</p>
<div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>python</span><span> </span><span>build_vocab.py</span><span> </span><span>--console</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
</li>
<li>
<p>使用 onedir 模式：</p>
<div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>python</span><span> </span><span>build_vocab.py</span><span> </span><span>--onedir</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
</li>
</ul></section></section><section><h2>版本历史<a href="#版本历史"><span>#</span></a></h2><ul>
<li>1.0
<ul>
<li>v1.0.0：基础版本（单元录入、学习与复习）</li>
</ul>
</li>
<li>2.0：
<ul>
<li>v2.0.0：优化前端展示与复习逻辑</li>
<li>v2.1.0：录入新增“汉字写法/罗马音”，接入本地词库自动补全</li>
<li>v2.2.0：新增导出 CSV 功能</li>
<li>v2.3.0：可以选中多个单元合并复习，修复部分环境下导出 CSV 文件名和内容错误问题</li>
<li>v2.4.0：更改 GUI 界面 ID 显示逻辑，优化 GUI 界面显示
<ul>
<li>v2.4.1：修复部分环境下更改词条导致程序崩溃问题</li>
<li>v2.4.2：优化 Bug 与大量潜在问题，为相对稳定版本</li>
<li>v2.4.3：更改单元单词总览界面和添加到题库界面样式显示逻辑和样式</li>
</ul>
</li>
<li>v2.5.0：添加单元单词总览界面，增加模糊查询。可以对选中单元单词打乱顺序，和按照特定规则排序
<ul>
<li>v2.5.1：修复导入单词后的单词页面重置问题</li>
</ul>
</li>
<li>v2.6.0 优化 GUI 界面，增加删除单元功能，修复新建单元后不会立即显示的 Bug
<ul>
<li>2.6.1 新增通过汉语释义自动补全假名、汉字写法和罗马音的功能</li>
<li>2.6.2 修复操作列点击后无法响应的 Bug</li>
<li>2.6.3 修复空格键无法继续复习的 Bug，对于复习页面的四个按钮新增快捷键支持</li>
</ul>
</li>
</ul>
</li>
<li>v3.0
<ul>
<li>v3.0.0 新增数据层升级 &amp; 迁移，复习引擎重构，优化表格与性能（Model/View 化），增加自动补全和词典（更“聪明”的输入）,增加导入/导出 &amp; 备份/恢复，设置中心 &amp; 个性化，新增统计与可视化
<ul>
<li>v3.0.1 删除 Windows 标题栏无用的 “What’s This” 模式</li>
</ul>
</li>
<li>v3.1.0 新增单元重命名功能，修复未选单元也提示“录入成功”的 Bug
<ul>
<li>v3.1.1 修复补全功能的部分 Bug，现在只有按下 Enter 键时才会触发补全</li>
</ul>
</li>
<li>v3.2.0 修复单元无法重命名问题，新增左侧拖拽单元改变顺序功能，优化单词自动补全逻辑，修复候选框无法主动退出的问题
<ul>
<li>v3.2.1 修复“英语外来语”在回填时被错当成“假名=英文”的典型错位问题</li>
<li>v3.2.2 删除右侧的单元选择下拉框，优化单元选择逻辑，现在与左侧进行绑定，修复学习统计的统计错误 Bug，为相对稳定版本</li>
</ul>
</li>
<li>v3.3.0 内置简单的日语输入法，可以通过模拟键盘输入自动补全假名
<ul>
<li>v3.3.1 删除录入成功的提示框</li>
<li>v3.3.2 新增平片假名学习界面</li>
<li>v3.3.3 更改全局字体</li>
<li>v3.3.4 学习界面新增“五十音图”，为相对稳定版本</li>
</ul>
</li>
</ul>
</li>
</ul><p>欢迎根据需要扩展单元管理、导入导出、更多统计类型或复习策略。如果有实际需要，可以在本界面下留言，或者去Github题issue或pr！</p></section></section>]]></content>
    <author><name>Zowely</name></author>
    <category term="项目开发" />
    <category term="项目开发" />
    <category term="Python" />
  </entry>
  <entry>
    <title>20251130测验题解</title>
    <link href="https://www.lansganbs.cn/posts/%E7%AE%97%E6%B3%95%E7%AB%9E%E8%B5%9B/20251130%E6%B5%8B%E9%AA%8C%E9%A2%98%E8%A7%A3%E4%B8%8Estd/" rel="alternate" type="text/html" />
    <id>https://www.lansganbs.cn/posts/%E7%AE%97%E6%B3%95%E7%AB%9E%E8%B5%9B/20251130%E6%B5%8B%E9%AA%8C%E9%A2%98%E8%A7%A3%E4%B8%8Estd/</id>
    <published>2025-09-15T00:00:00.000Z</published>
    <updated>2025-11-30T22:41:12.000Z</updated>
    <summary>20251130测验题解</summary>
    <content type="html"><![CDATA[<section><h2>A 猫猫之城<a href="#a-猫猫之城"><span>#</span></a></h2><blockquote><p>Problem tags: 图论, 最短路, 分层图</p></blockquote><p>Prediction Difficulty：High</p><p>Actual Difficulty：High</p><section><h3>Problem Statement<a href="#problem-statement"><span>#</span></a></h3><p>给定一个包含 <span><span>nn</span><span><span><span></span><span>n</span></span></span></span> 个点、<span><span>mm</span><span><span><span></span><span>m</span></span></span></span> 条边的有向图，边有正权。你需要从点 <span><span>11</span><span><span><span></span><span>1</span></span></span></span> 走到点 <span><span>nn</span><span><span><span></span><span>n</span></span></span></span>。你拥有一张 VIP 卡，可以将任意一条边的权值变为 <span><span>00</span><span><span><span></span><span>0</span></span></span></span>（只能使用一次）。求从 <span><span>11</span><span><span><span></span><span>1</span></span></span></span> 到 <span><span>nn</span><span><span><span></span><span>n</span></span></span></span> 的最短时间。</p></section><section><h3>Solution<a href="#solution"><span>#</span></a></h3><ul>
<li><strong>前后缀最短路（两次 Dijkstra）</strong>：我们考虑最终的最短路径，它一定是由三部分组成：</li>
</ul><span><span><span>1⇝u→免费v⇝n1 \leadsto u \xrightarrow{\text{免费}} v \leadsto n</span><span><span><span></span><span>1</span><span></span><span>⇝</span><span></span></span><span><span></span><span>u</span><span></span><span><span><span><span><span><span></span><span><span><span><span>免费</span></span></span></span></span><span><span></span><span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span><span></span><span>v</span><span></span><span>⇝</span><span></span></span><span><span></span><span>n</span></span></span></span></span><ul>
<li>其中 <span><span>(u,v)(u, v)</span><span><span><span></span><span>(</span><span>u</span><span>,</span><span></span><span>v</span><span>)</span></span></span></span> 是我们选择免费通过的那条边。路径的总耗时为：</li>
</ul><span><span><span>Cost=dist(1→u)+0+dist(v→n)Cost = \text{dist}(1 \to u) + 0 + \text{dist}(v \to n)</span><span><span><span></span><span>C</span><span>os</span><span>t</span><span></span><span>=</span><span></span></span><span><span></span><span><span>dist</span></span><span>(</span><span>1</span><span></span><span>→</span><span></span></span><span><span></span><span>u</span><span>)</span><span></span><span>+</span><span></span></span><span><span></span><span>0</span><span></span><span>+</span><span></span></span><span><span></span><span><span>dist</span></span><span>(</span><span>v</span><span></span><span>→</span><span></span></span><span><span></span><span>n</span><span>)</span></span></span></span></span><ul>
<li>为了快速求出任意一条边 <span><span>(u,v)(u, v)</span><span><span><span></span><span>(</span><span>u</span><span>,</span><span></span><span>v</span><span>)</span></span></span></span> 作为免费边时的总耗时，我们需要知道：从 <span><span>11</span><span><span><span></span><span>1</span></span></span></span> 到 <span><span>uu</span><span><span><span></span><span>u</span></span></span></span> 的最短距离和从 <span><span>vv</span><span><span><span></span><span>v</span></span></span></span> 到 <span><span>nn</span><span><span><span></span><span>n</span></span></span></span> 的最短距离。</li>
<li>从 <span><span>11</span><span><span><span></span><span>1</span></span></span></span> 到 <span><span>uu</span><span><span><span></span><span>u</span></span></span></span> 的最短距离：这可以通过以 <span><span>11</span><span><span><span></span><span>1</span></span></span></span> 为起点，在原图上跑一次 Dijkstra 得到，记为 <code>dis1[u]</code>。</li>
<li>从 <span><span>vv</span><span><span><span></span><span>v</span></span></span></span> 到 <span><span>nn</span><span><span><span></span><span>n</span></span></span></span> 的最短距离：在有向图中，求“任意点到终点”的最短路，等价于求“终点到任意点”的最短路，但需要在反向图（所有边方向取反）上进行。我们可以建立反图，以 <span><span>nn</span><span><span><span></span><span>n</span></span></span></span> 为起点跑一次 Dijkstra，记为 <code>dis2[v]</code>。</li>
<li>时间复杂度为 <span><span>O(mlog⁡n)\mathcal{O}(m \log n)</span><span><span><span></span><span>O</span><span>(</span><span>m</span><span></span><span>lo<span>g</span></span><span></span><span>n</span><span>)</span></span></span></span>。</li>
</ul></section></section>
<section><h2>B 井字棋<a href="#b-井字棋"><span>#</span></a></h2><blockquote><p>Problem tags: DFS, 枚举</p></blockquote><p>Prediction Difficulty：Medium</p><p>Actual Difficulty：Easy</p><section><h3>Problem Statement<a href="#problem-statement-1"><span>#</span></a></h3><p>给定一个 <span><span>3×33\times3</span><span><span><span></span><span>3</span><span></span><span>×</span><span></span></span><span><span></span><span>3</span></span></span></span> 的井字棋棋局，判断其当前状态属于哪一类。</p></section><section><h3>Solution<a href="#solution-1"><span>#</span></a></h3><ul>
<li>棋盘共有 <span><span>39=196833^9 = 19683</span><span><span><span></span><span><span>3</span><span><span><span><span><span><span></span><span><span>9</span></span></span></span></span></span></span></span><span></span><span>=</span><span></span></span><span><span></span><span>19683</span></span></span></span> 种状态，每格三种取值。</li>
<li><strong>方法一</strong>：从空棋盘出发按规则 DFS / 回溯生成所有可达的“合法”状态（轮流落子，出现胜负后不再继续扩展）。对每个搜索到的状态判断其属于哪一类并记录，未被搜索到的状态即视为 <code>inexistence</code>。总状态 <span><span>1968319683</span><span><span><span></span><span>19683</span></span></span></span>，其中有效状态约 <span><span>45204520</span><span><span><span></span><span>4520</span></span></span></span> 种。预处理完之后每次查询只需 <span><span>O(1)\mathcal{O}(1)</span><span><span><span></span><span>O</span><span>(</span><span>1</span><span>)</span></span></span></span>。</li>
<li><strong>方法二</strong>：
<ul>
<li>定义 <span><span>flagA,flagBflag_A, flag_B</span><span><span><span></span><span>f</span><span>l</span><span>a</span><span><span>g</span><span><span><span><span><span><span></span><span><span>A</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>,</span><span></span><span>f</span><span>l</span><span>a</span><span><span>g</span><span><span><span><span><span><span></span><span><span>B</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span> 表示 A、B 是否已经形成三连线，<span><span>cntA,cntBcnt_A, cnt_B</span><span><span><span></span><span>c</span><span>n</span><span><span>t</span><span><span><span><span><span><span></span><span><span>A</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>,</span><span></span><span>c</span><span>n</span><span><span>t</span><span><span><span><span><span><span></span><span><span>B</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span> 表示 A、B 的棋子数量；遍历棋盘一次即可统计。</li>
<li>若 A、B 同时获胜，或不满足 <span><span>cntA=cntBcnt_A = cnt_B</span><span><span><span></span><span>c</span><span>n</span><span><span>t</span><span><span><span><span><span><span></span><span><span>A</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>=</span><span></span></span><span><span></span><span>c</span><span>n</span><span><span>t</span><span><span><span><span><span><span></span><span><span>B</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span> 且不满足 <span><span>cntA=cntB+1cnt_A = cnt_B + 1</span><span><span><span></span><span>c</span><span>n</span><span><span>t</span><span><span><span><span><span><span></span><span><span>A</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>=</span><span></span></span><span><span></span><span>c</span><span>n</span><span><span>t</span><span><span><span><span><span><span></span><span><span>B</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>+</span><span></span></span><span><span></span><span>1</span></span></span></span>，则该局面一定不合法。</li>
<li>若只有 A 胜，则必须满足 <span><span>cntA=cntB+1cnt_A = cnt_B + 1</span><span><span><span></span><span>c</span><span>n</span><span><span>t</span><span><span><span><span><span><span></span><span><span>A</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>=</span><span></span></span><span><span></span><span>c</span><span>n</span><span><span>t</span><span><span><span><span><span><span></span><span><span>B</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>+</span><span></span></span><span><span></span><span>1</span></span></span></span>；若只有 B 胜，则必须满足 <span><span>cntA=cntBcnt_A = cnt_B</span><span><span><span></span><span>c</span><span>n</span><span><span>t</span><span><span><span><span><span><span></span><span><span>A</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>=</span><span></span></span><span><span></span><span>c</span><span>n</span><span><span>t</span><span><span><span><span><span><span></span><span><span>B</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span>，否则也不合法。</li>
<li>若棋盘已下满且前述不合法/胜负情况均排除，则为平局；否则是进行中的合法局面，此时由 <span><span>cntA,cntBcnt_A, cnt_B</span><span><span><span></span><span>c</span><span>n</span><span><span>t</span><span><span><span><span><span><span></span><span><span>A</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>,</span><span></span><span>c</span><span>n</span><span><span>t</span><span><span><span><span><span><span></span><span><span>B</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span> 判断轮到谁下（<span><span>cntA=cntBcnt_A=cnt_B</span><span><span><span></span><span>c</span><span>n</span><span><span>t</span><span><span><span><span><span><span></span><span><span>A</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>=</span><span></span></span><span><span></span><span>c</span><span>n</span><span><span>t</span><span><span><span><span><span><span></span><span><span>B</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span> 轮到 A，否则轮到 B）。整个判断在固定 <span><span>3×33\times3</span><span><span><span></span><span>3</span><span></span><span>×</span><span></span></span><span><span></span><span>3</span></span></span></span> 棋盘上完成，复杂度为 <span><span>O(1)\mathcal{O}(1)</span><span><span><span></span><span>O</span><span>(</span><span>1</span><span>)</span></span></span></span>。</li>
</ul>
</li>
</ul></section></section>
<section><h2>C 高等魂学<a href="#c-高等魂学"><span>#</span></a></h2><blockquote><p>Problem tags: 贪心, 排序, 模拟</p></blockquote><p>Prediction Difficulty：Easy</p><p>Actual Difficulty：Easy</p><section><h3>Problem Statement<a href="#problem-statement-2"><span>#</span></a></h3><p>BOSS 生命值为 <span><span>HH</span><span><span><span></span><span>H</span></span></span></span>、防御力为 <span><span>DD</span><span><span><span></span><span>D</span></span></span></span>，有 <span><span>nn</span><span><span><span></span><span>n</span></span></span></span> 把一次性武器，第 <span><span>ii</span><span><span><span></span><span>i</span></span></span></span> 把攻击力为 <span><span>aia_i</span><span><span><span></span><span><span>a</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span>，用给定破防公式计算单次伤害（已知对 Atk 单调不减），每把最多用一次，顺序可任意，累计伤害 <span><span>≥H\ge H</span><span><span><span></span><span>≥</span><span></span></span><span><span></span><span>H</span></span></span></span> 即击杀，求最少用多少把武器，不可击杀则输出 <span><span>−1-1</span><span><span><span></span><span>−</span><span>1</span></span></span></span>。</p></section><section><h3>Solution<a href="#solution-2"><span>#</span></a></h3><ul>
<li>由“伤害对 Atk 单调不减”，最优方案一定是优先用攻击力更大的武器。</li>
<li>将 <span><span>aia_i</span><span><span><span></span><span><span>a</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span> 降序排序，依次按公式算出每把的伤害并累加，第一次使累计伤害 <span><span>≥H\ge H</span><span><span><span></span><span>≥</span><span></span></span><span><span></span><span>H</span></span></span></span> 的位置即答案；若全部用完仍 <span><span>&lt;H&lt;H</span><span><span><span></span><span>&lt;</span><span></span></span><span><span></span><span>H</span></span></span></span> 则输出 <span><span>−1-1</span><span><span><span></span><span>−</span><span>1</span></span></span></span>。</li>
<li>时间复杂度 <span><span>O(nlog⁡n)\mathcal{O}(n\log n)</span><span><span><span></span><span>O</span><span>(</span><span>n</span><span></span><span>lo<span>g</span></span><span></span><span>n</span><span>)</span></span></span></span>。</li>
</ul></section></section>
<section><h2>D 走出迷宫<a href="#d-走出迷宫"><span>#</span></a></h2><blockquote><p>Problem tags: DFS, BFS</p></blockquote><p>Prediction Difficulty：Medium</p><p>Actual Difficulty：Medium</p><section><h3>Problem Statement<a href="#problem-statement-3"><span>#</span></a></h3><p>找出从当前位置走出迷宫的最少步数，如果无法走出则输出 <span><span>−1-1</span><span><span><span></span><span>−</span><span>1</span></span></span></span>。</p></section><section><h3>Solution<a href="#solution-3"><span>#</span></a></h3><ul>
<li>用 <span><span>dfsdfs</span><span><span><span></span><span>df</span><span>s</span></span></span></span> 或 <span><span>bfsbfs</span><span><span><span></span><span>b</span><span>f</span><span>s</span></span></span></span> 模拟这个过程即可，走到边界外的时候输出答案。</li>
<li>开全局数组需要注意清空。</li>
</ul></section></section>
<section><h2>E hc找朋友<a href="#e-hc找朋友"><span>#</span></a></h2><blockquote><p>Problem tags: DFS, LCA</p></blockquote><p>Prediction Difficulty：High</p><p>Actual Difficulty：Very High</p><section><h3>Problem Statement<a href="#problem-statement-4"><span>#</span></a></h3><p>在一个树上找两点之间的距离。</p></section><section><h3>Solution<a href="#solution-4"><span>#</span></a></h3><ul>
<li>题目没有指定根节点，那将 <span><span>11</span><span><span><span></span><span>1</span></span></span></span> 号节点设为根节点，先跑一遍 <span><span>dfsdfs</span><span><span><span></span><span>df</span><span>s</span></span></span></span> 得出 <span><span>sum[i]sum[i]</span><span><span><span></span><span>s</span><span>u</span><span>m</span><span>[</span><span>i</span><span>]</span></span></span></span> 为 <span><span>ii</span><span><span><span></span><span>i</span></span></span></span> 到根节点的距离和。</li>
<li>树上两点间的距离即为 <span><span>sum[s]+sum[t]−2×sum[lca(s,t)]sum[s]+sum[t]-2 \times sum[lca(s,t)]</span><span><span><span></span><span>s</span><span>u</span><span>m</span><span>[</span><span>s</span><span>]</span><span></span><span>+</span><span></span></span><span><span></span><span>s</span><span>u</span><span>m</span><span>[</span><span>t</span><span>]</span><span></span><span>−</span><span></span></span><span><span></span><span>2</span><span></span><span>×</span><span></span></span><span><span></span><span>s</span><span>u</span><span>m</span><span>[</span><span>l</span><span>c</span><span>a</span><span>(</span><span>s</span><span>,</span><span></span><span>t</span><span>)]</span></span></span></span>。</li>
<li>注意要开 <code>long long</code>。</li>
</ul></section></section>
<section><h2>F 沉默魔女的投资<a href="#f-沉默魔女的投资"><span>#</span></a></h2><blockquote><p>Problem tags: 贪心, 排序</p></blockquote><p>Prediction Difficulty：Easy</p><p>Actual Difficulty：Easy</p><section><h3>Problem Statement<a href="#problem-statement-5"><span>#</span></a></h3><p>给出 <span><span>nn</span><span><span><span></span><span>n</span></span></span></span> 天价格，只能买一次卖一次（可时间穿梭），求最大利润，不能盈利则输出 <span><span>00</span><span><span><span></span><span>0</span></span></span></span>。</p></section><section><h3>Solution<a href="#solution-5"><span>#</span></a></h3><ul>
<li>由于可任意选择买卖日，排序后，只需取 <span><span>max⁡(a)−min⁡(a)\max(a)-\min(a)</span><span><span><span></span><span>max</span><span>(</span><span>a</span><span>)</span><span></span><span>−</span><span></span></span><span><span></span><span>min</span><span>(</span><span>a</span><span>)</span></span></span></span>。</li>
<li>瓶颈在于排序，时间复杂度 <span><span>O(nlog⁡n)\mathcal{O}(n\log n)</span><span><span><span></span><span>O</span><span>(</span><span>n</span><span></span><span>lo<span>g</span></span><span></span><span>n</span><span>)</span></span></span></span>。</li>
</ul></section></section>
<section><h2>G 芙莉莲独自升级<a href="#g-芙莉莲独自升级"><span>#</span></a></h2><blockquote><p>Problem tags: 二分, 贪心, 排序, 双指针</p></blockquote><p>Prediction Difficulty：Very High</p><p>Actual Difficulty：Very High</p><section><h3>Problem Statement<a href="#problem-statement-6"><span>#</span></a></h3><p>给定多组数据，每组给出整数 <span><span>n,x,yn,x,y</span><span><span><span></span><span>n</span><span>,</span><span></span><span>x</span><span>,</span><span></span><span>y</span></span></span></span>（<span><span>y&gt;xy&gt;x</span><span><span><span></span><span>y</span><span></span><span>&gt;</span><span></span></span><span><span></span><span>x</span></span></span></span>）和 <span><span>nn</span><span><span><span></span><span>n</span></span></span></span> 个冒险者的魔力值 <span><span>a1,…,ana_1,\dots,a_n</span><span><span><span></span><span><span>a</span><span><span><span><span><span><span></span><span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>,</span><span></span><span>…</span><span></span><span>,</span><span></span><span><span>a</span><span><span><span><span><span><span></span><span><span>n</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span>。
初始魔力为 <span><span>xx</span><span><span><span></span><span>x</span></span></span></span>，每次选择一个 <span><span>ii</span><span><span><span></span><span>i</span></span></span></span>：若当前魔力 <span><span>≥ai\ge a_i</span><span><span><span></span><span>≥</span><span></span></span><span><span></span><span><span>a</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span>，则魔力变为 <span><span>x+1x+1</span><span><span><span></span><span>x</span><span></span><span>+</span><span></span></span><span><span></span><span>1</span></span></span></span>，否则变为 <span><span>x−1x-1</span><span><span><span></span><span>x</span><span></span><span>−</span><span></span></span><span><span></span><span>1</span></span></span></span>；并且整个过程中任意两名冒险者被挑战的次数之差不超过 <span><span>11</span><span><span><span></span><span>1</span></span></span></span>。
求使魔力达到 <span><span>yy</span><span><span><span></span><span>y</span></span></span></span> 所需的最少切磋次数。</p></section><section><h3>Solution<a href="#solution-6"><span>#</span></a></h3><ul>
<li>对冒险者魔力值排序，使每轮的挑战顺序最优。</li>
<li>预处理数组 <span><span>ti,bit_i,b_i</span><span><span><span></span><span><span>t</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>,</span><span></span><span><span>b</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span>，记录最低初始魔力及连胜后的魔力。</li>
<li>对当前魔力 <span><span>xx</span><span><span><span></span><span>x</span></span></span></span>，使用 <span><span>upper_bound\mathrm{upper\_bound}</span><span><span><span></span><span><span>upper_bound</span></span></span></span></span> 计算一轮内能连胜的数量 <span><span>pp</span><span><span><span></span><span>p</span></span></span></span>。</li>
<li>失败场数为 <span><span>m=n−pm=n-p</span><span><span><span></span><span>m</span><span></span><span>=</span><span></span></span><span><span></span><span>n</span><span></span><span>−</span><span></span></span><span><span></span><span>p</span></span></span></span>。若 <span><span>p−m≤0p-m \le 0</span><span><span><span></span><span>p</span><span></span><span>−</span><span></span></span><span><span></span><span>m</span><span></span><span>≤</span><span></span></span><span><span></span><span>0</span></span></span></span>，净收益非正，永远无法升到 <span><span>yy</span><span><span><span></span><span>y</span></span></span></span>，输出 <span><span>−1-1</span><span><span><span></span><span>−</span><span>1</span></span></span></span>。</li>
<li>若本轮中 <span><span>x+p≥yx+p \ge y</span><span><span><span></span><span>x</span><span></span><span>+</span><span></span></span><span><span></span><span>p</span><span></span><span>≥</span><span></span></span><span><span></span><span>y</span></span></span></span>，即可提前达到目标，直接输出答案。</li>
<li>设一轮净收益 <span><span>Δ=p−m\Delta = p - m</span><span><span><span></span><span>Δ</span><span></span><span>=</span><span></span></span><span><span></span><span>p</span><span></span><span>−</span><span></span></span><span><span></span><span>m</span></span></span></span>，多跑 <span><span>kk</span><span><span><span></span><span>k</span></span></span></span> 轮后魔力变为 <span><span>x+kΔx + k\Delta</span><span><span><span></span><span>x</span><span></span><span>+</span><span></span></span><span><span></span><span>k</span><span>Δ</span></span></span></span>。</li>
<li>求出两个限制轮数：
<ul>
<li><span><span>k1=⌈y−x−pp−m⌉k_1 = \left\lceil \frac{y - x - p}{p - m} \right\rceil</span><span><span><span></span><span><span>k</span><span><span><span><span><span><span></span><span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>=</span><span></span></span><span><span></span><span><span><span>⌈</span></span><span><span></span><span><span><span><span><span><span></span><span><span><span>p</span><span>−</span><span>m</span></span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>y</span><span>−</span><span>x</span><span>−</span><span>p</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span><span>⌉</span></span></span></span></span></span>（达到目标）</li>
<li><span><span>k2=⌈tp−xp−m⌉k_2 = \left\lceil \frac{t_p - x}{p - m} \right\rceil</span><span><span><span></span><span><span>k</span><span><span><span><span><span><span></span><span><span>2</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>=</span><span></span></span><span><span></span><span><span><span>⌈</span></span><span><span></span><span><span><span><span><span><span></span><span><span><span>p</span><span>−</span><span>m</span></span></span></span><span><span></span><span></span></span><span><span></span><span><span><span><span>t</span><span><span><span><span><span><span></span><span><span>p</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>−</span><span>x</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span><span>⌉</span></span></span></span></span></span>（连胜段扩张）</li>
</ul>
</li>
<li>取 <span><span>k=min⁡(k1,k2)k = \min(k_1,k_2)</span><span><span><span></span><span>k</span><span></span><span>=</span><span></span></span><span><span></span><span>min</span><span>(</span><span><span>k</span><span><span><span><span><span><span></span><span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>,</span><span></span><span><span>k</span><span><span><span><span><span><span></span><span><span>2</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>)</span></span></span></span>，一次性跳过 <span><span>kk</span><span><span><span></span><span>k</span></span></span></span> 个完整循环，避免逐轮模拟。</li>
<li>更新：魔力 <span><span>x←x+k(p−m)x \leftarrow x + k(p - m)</span><span><span><span></span><span>x</span><span></span><span>←</span><span></span></span><span><span></span><span>x</span><span></span><span>+</span><span></span></span><span><span></span><span>k</span><span>(</span><span>p</span><span></span><span>−</span><span></span></span><span><span></span><span>m</span><span>)</span></span></span></span>；答案 <span><span>ans←ans+knans \leftarrow ans + kn</span><span><span><span></span><span>an</span><span>s</span><span></span><span>←</span><span></span></span><span><span></span><span>an</span><span>s</span><span></span><span>+</span><span></span></span><span><span></span><span>k</span><span>n</span></span></span></span>；然后重新计算下一轮的连胜数量 <span><span>pp</span><span><span><span></span><span>p</span></span></span></span>。</li>
<li>随着 <span><span>xx</span><span><span><span></span><span>x</span></span></span></span> 增大，<span><span>pp</span><span><span><span></span><span>p</span></span></span></span> 只会上升，最多改变 <span><span>nn</span><span><span><span></span><span>n</span></span></span></span> 次，因此总复杂度为 <span><span>O(nlog⁡n)\mathcal{O}(n\log n)</span><span><span><span></span><span>O</span><span>(</span><span>n</span><span></span><span>lo<span>g</span></span><span></span><span>n</span><span>)</span></span></span></span>。</li>
</ul></section></section>
<section><h2>H 边狱巴士是一款大概率发生小概率事件的游戏 - Medium<a href="#h-边狱巴士是一款大概率发生小概率事件的游戏---medium"><span>#</span></a></h2><blockquote><p>Problem tags: 数学, 期望, 逆元</p></blockquote><p>Prediction Difficulty：Very High</p><p>Actual Difficulty：High</p><section><h3>Problem Statement<a href="#problem-statement-7"><span>#</span></a></h3><p>与 Easy 版本题意相同（硬币投掷）。求该技能造成的<strong>期望伤害</strong>，结果对 <span><span>998244353998244353</span><span><span><span></span><span>998244353</span></span></span></span> 取模。</p></section><section><h3>Solution<a href="#solution-7"><span>#</span></a></h3><ul>
<li>利用<strong>期望的线性性</strong>。总期望 = 普通硬币造成的期望伤害 + 不可摧毁硬币造成的期望伤害。</li>
<li>每一枚硬币投出正面的概率均为 <span><span>1/21/2</span><span><span><span></span><span>1/2</span></span></span></span>，在模意义下除以 <span><span>22</span><span><span><span></span><span>2</span></span></span></span> 等价于乘以 <span><span>22</span><span><span><span></span><span>2</span></span></span></span> 的逆元。</li>
<li>对于 <span><span>aa</span><span><span><span></span><span>a</span></span></span></span> 个普通硬币，只有投出正面才会有伤害。第 <span><span>ii</span><span><span><span></span><span>i</span></span></span></span> 个普通硬币（<span><span>1≤i≤a1 \le i \le a</span><span><span><span></span><span>1</span><span></span><span>≤</span><span></span></span><span><span></span><span>i</span><span></span><span>≤</span><span></span></span><span><span></span><span>a</span></span></span></span>）造成伤害的条件是正面（概率 <span><span>1/21/2</span><span><span><span></span><span>1/2</span></span></span></span>），此时威力为：基础 <span><span>atkatk</span><span><span><span></span><span>a</span><span>t</span><span>k</span></span></span></span> + 自身 <span><span>addadd</span><span><span><span></span><span>a</span><span>dd</span></span></span></span> + 前面 <span><span>i−1i-1</span><span><span><span></span><span>i</span><span></span><span>−</span><span></span></span><span><span></span><span>1</span></span></span></span> 个硬币贡献的期望 <span><span>addadd</span><span><span><span></span><span>a</span><span>dd</span></span></span></span>。</li>
<li>单个硬币 <span><span>ii</span><span><span><span></span><span>i</span></span></span></span> 的期望伤害 <span><span>EiE_i</span><span><span><span></span><span><span>E</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span> 为：</li>
</ul><span><span><span>Ei=12(atk+add+i−12add)E_i = \frac{1}{2} \left( atk + add + \frac{i-1}{2} add \right)</span><span><span><span></span><span><span>E</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>=</span><span></span></span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span>2</span></span></span><span><span></span><span></span></span><span><span></span><span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span></span><span><span><span>(</span></span><span>a</span><span>t</span><span>k</span><span></span><span>+</span><span></span><span>a</span><span>dd</span><span></span><span>+</span><span></span><span><span></span><span><span><span><span><span><span></span><span><span>2</span></span></span><span><span></span><span></span></span><span><span></span><span><span>i</span><span></span><span>−</span><span></span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span>a</span><span>dd</span><span><span>)</span></span></span></span></span></span></span><ul>
<li>对 <span><span>aa</span><span><span><span></span><span>a</span></span></span></span> 个硬币求和：</li>
</ul><span><span><span>∑i=1aEi=a(atk+add)2+a(a−1)add8\sum_{i=1}^a E_i = \frac{a(atk+add)}{2} + \frac{a(a-1)add}{8}</span><span><span><span></span><span><span><span><span><span><span></span><span><span><span>i</span><span>=</span><span>1</span></span></span></span><span><span></span><span><span>∑</span></span></span><span><span></span><span><span>a</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span><span><span>E</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>=</span><span></span></span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span>2</span></span></span><span><span></span><span></span></span><span><span></span><span><span>a</span><span>(</span><span>a</span><span>t</span><span>k</span><span></span><span>+</span><span></span><span>a</span><span>dd</span><span>)</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span></span><span>+</span><span></span></span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span>8</span></span></span><span><span></span><span></span></span><span><span></span><span><span>a</span><span>(</span><span>a</span><span></span><span>−</span><span></span><span>1</span><span>)</span><span>a</span><span>dd</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span></span></span></span></span><ul>
<li>接下来是 <span><span>bb</span><span><span><span></span><span>b</span></span></span></span> 个不可摧毁硬币。它们必定攻击（概率 <span><span>11</span><span><span><span></span><span>1</span></span></span></span>）。第 <span><span>jj</span><span><span><span></span><span>j</span></span></span></span> 个不可摧毁硬币（<span><span>1≤j≤b1 \le j \le b</span><span><span><span></span><span>1</span><span></span><span>≤</span><span></span></span><span><span></span><span>j</span><span></span><span>≤</span><span></span></span><span><span></span><span>b</span></span></span></span>）的基础威力受前面所有硬币影响（<span><span>aa</span><span><span><span></span><span>a</span></span></span></span> 个普通，<span><span>j−1j-1</span><span><span><span></span><span>j</span><span></span><span>−</span><span></span></span><span><span></span><span>1</span></span></span></span> 个不可摧毁，以及自身）。</li>
<li>第 <span><span>jj</span><span><span><span></span><span>j</span></span></span></span> 个硬币的期望伤害 <span><span>Ej′E'_j</span><span><span><span></span><span><span>E</span><span><span><span><span><span><span></span><span><span>j</span></span></span><span><span></span><span><span><span>′</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span> 为：</li>
</ul><span><span><span>Ej′=atk+(a+1)add2+(j−1)add2E'_j = atk + \frac{(a+1)add}{2} + \frac{(j-1)add}{2}</span><span><span><span></span><span><span>E</span><span><span><span><span><span><span></span><span><span>j</span></span></span><span><span></span><span><span><span>′</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>=</span><span></span></span><span><span></span><span>a</span><span>t</span><span>k</span><span></span><span>+</span><span></span></span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span>2</span></span></span><span><span></span><span></span></span><span><span></span><span><span>(</span><span>a</span><span></span><span>+</span><span></span><span>1</span><span>)</span><span>a</span><span>dd</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span></span><span>+</span><span></span></span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span>2</span></span></span><span><span></span><span></span></span><span><span></span><span><span>(</span><span>j</span><span></span><span>−</span><span></span><span>1</span><span>)</span><span>a</span><span>dd</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span></span></span></span></span><ul>
<li>对 <span><span>bb</span><span><span><span></span><span>b</span></span></span></span> 个不可摧毁硬币求和 <span><span>∑j=1bEj′\sum_{j=1}^b E'_j</span><span><span><span></span><span><span>∑</span><span><span><span><span><span><span></span><span><span><span>j</span><span>=</span><span>1</span></span></span></span><span><span></span><span><span>b</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span><span>E</span><span><span><span><span><span><span></span><span><span>j</span></span></span><span><span></span><span><span><span>′</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span>：</li>
</ul><span><span><span>∑j=1bEj′=b⋅atk+b⋅(a+1)add2+b(b−1)add4\sum_{j=1}^b E'_j = b \cdot atk + b \cdot \frac{(a+1)add}{2} + \frac{b(b-1)add}{4}</span><span><span><span></span><span><span><span><span><span><span></span><span><span><span>j</span><span>=</span><span>1</span></span></span></span><span><span></span><span><span>∑</span></span></span><span><span></span><span><span>b</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span><span><span>E</span><span><span><span><span><span><span></span><span><span>j</span></span></span><span><span></span><span><span><span>′</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>=</span><span></span></span><span><span></span><span>b</span><span></span><span>⋅</span><span></span></span><span><span></span><span>a</span><span>t</span><span>k</span><span></span><span>+</span><span></span></span><span><span></span><span>b</span><span></span><span>⋅</span><span></span></span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span>2</span></span></span><span><span></span><span></span></span><span><span></span><span><span>(</span><span>a</span><span></span><span>+</span><span></span><span>1</span><span>)</span><span>a</span><span>dd</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span></span><span>+</span><span></span></span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span>4</span></span></span><span><span></span><span></span></span><span><span></span><span><span>b</span><span>(</span><span>b</span><span></span><span>−</span><span></span><span>1</span><span>)</span><span>a</span><span>dd</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span></span></span></span></span><ul>
<li>将所有部分相加即为最终答案。</li>
<li>单组 <span><span>O(1)\mathcal{O}(1)</span><span><span><span></span><span>O</span><span>(</span><span>1</span><span>)</span></span></span></span>，总复杂度 <span><span>O(t)\mathcal{O}(t)</span><span><span><span></span><span>O</span><span>(</span><span>t</span><span>)</span></span></span></span>，计算时需注意使用 <code>long long</code> 来进行存储。</li>
</ul></section></section>
<section><h2>致谢名单<a href="#致谢名单"><span>#</span></a></h2><ul>
<li>
<p>Author：</p>
<p><span>C</span><span>henzq7</span>, <span>h</span><span>uangce</span>, <span>L</span><span>ANSGANBS</span></p>
</li>
<li>
<p>Tester：</p>
<p><span>D</span><span>KXTL</span>, <span>i</span><span>amyouqi</span>, <span>s</span><span>kycheng</span>, <span>s</span><span>uroooo</span>, <span>z</span><span>houzhy</span></p>
</li>
</ul><div><div><div></div><div>Note</div></div><div><p>排名按照字典序排序。</p></div></div><p>非常感谢各位 Tester 对本场比赛出题的帮助！</p></section>]]></content>
    <author><name>Zowely</name></author>
    <category term="算法竞赛" />
    <category term="算法竞赛" />
    <category term="题解" />
    <category term="C++" />
  </entry>
  <entry>
    <title>20251026测验题解与STD</title>
    <link href="https://www.lansganbs.cn/posts/%E7%AE%97%E6%B3%95%E7%AB%9E%E8%B5%9B/20251026%E6%B5%8B%E9%AA%8C%E9%A2%98%E8%A7%A3%E4%B8%8Estd/" rel="alternate" type="text/html" />
    <id>https://www.lansganbs.cn/posts/%E7%AE%97%E6%B3%95%E7%AB%9E%E8%B5%9B/20251026%E6%B5%8B%E9%AA%8C%E9%A2%98%E8%A7%A3%E4%B8%8Estd/</id>
    <published>2025-08-30T00:00:00.000Z</published>
    <updated>2025-08-30T16:04:35.000Z</updated>
    <summary>20251026测验题解与STD</summary>
    <content type="html"><![CDATA[<section><h1>20251026测验题解与STD<a href="#20251026测验题解与std"><span>#</span></a></h1><section><h2>赛时公告<a href="#赛时公告"><span>#</span></a></h2><p>全场首杀：</p><p>2504020207 网络25-2 - 阳灿 E题 00:05&lt;01&gt;</p><p>顽强拼搏奖:</p><p>2504010606 计算机25-6 - 刘誉聪 02:59&lt;43&gt;</p><p>最快解题奖：</p><p>A: 2504010610 计算机25-6 - 何俊杰 00:31&lt;57&gt;</p><p>B: 2504090208 计算机双学士25-2 - 闫迪 00:29&lt;08&gt;</p><p>C: 2504020207 网络25-2 - 阳灿 02:35&lt;07&gt;</p><p>D: 2504020207 网络25-2 - 阳灿 00:45&lt;43&gt;</p><p>E: 2504020207 网络25-2 - 阳灿 00:05&lt;01&gt;</p><p>F: 2504010317 计算机25-3 - 刘亭瑞 00:20&lt;53&gt;</p><p>G: 2504090208 计算机双学士25-2 - 闫迪 01:05&lt;51&gt;</p><p>H: 2504090208 计算机双学士25-2 - 闫迪 00:59&lt;41&gt;</p></section><section><h2>A BLG回来了<a href="#a-blg回来了"><span>#</span></a></h2><blockquote><p>Problem tags: 贪心 排序</p></blockquote><p>Prediction Difficulty：Medium</p><p>Actual Difficulty：Medium</p><section><h3>Problem Statement<a href="#problem-statement"><span>#</span></a></h3><p>16个队伍每轮自行排列进行1对1比赛，按照战力评胜负。赛制其实就是<a href="https://www.bilibili.com/video/BV1bW421X71i/" target="_blank">瑞士轮赛制</a>，相同比分的队伍在同一组别比赛，3胜晋级，3败出局。目的让第二个队伍“T1”能够淘汰。</p></section><section><h3>Solution<a href="#solution"><span>#</span></a></h3><p>考虑贪心，最差情况就是：</p><ul>
<li>
<p>0-0组别最强的队伍打T1，第二强的队伍和第三强的队伍打，可以让T1和第三强的队伍掉到0-1组别。</p>
</li>
<li>
<p>0-1组别安排T1和第三强的队伍比赛，让T1进入0-2组别，第三强的队伍进入1-1组别；与此同时1-0组别让最强的队伍和第二强的队伍比赛，让第二强的队伍进入1-1组别。</p>
</li>
<li>
<p>1-1组别让第三强的队伍和第二强的队伍比赛，让第三强的队伍进入1-2组别。0-2组别让T1跟任意比他弱的队伍打，进入1-2组别。</p>
</li>
<li>
<p>最后在1-2组别，让T1和第三强的队伍打，即可让T1达成3败淘汰。</p>
</li>
</ul><p>时间复杂度：<span><span>O(T)\mathcal{O}(T)</span><span><span><span></span><span>O</span><span>(</span><span>T</span><span>)</span></span></span></span>。</p></section><section><h3>Std<a href="#std"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>void</span><span> </span><span>solve</span><span>() {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>vector</span><span>&lt;</span><span>int</span><span>&gt; </span><span>a</span><span>(</span><span>16</span><span>);</span></div></div><div><div><div>3</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>0</span><span>; i </span><span>&lt;</span><span> </span><span>16</span><span>; i</span><span>++</span><span>) {</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>cin </span><span>&gt;&gt;</span><span> a[i];</span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>6</div></div><div><span>  </span><span>int</span><span> t1 </span><span>=</span><span> a[</span><span>1</span><span>], S </span><span>=</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>7</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>0</span><span>; i </span><span>&lt;</span><span> </span><span>16</span><span>; </span><span>++</span><span>i) {</span></div></div><div><div><div>8</div></div><div><span>    </span><span>if</span><span> (a[i] </span><span>&gt;</span><span> t1) {</span></div></div><div><div><div>9</div></div><div><span><span>      </span></span><span>S</span><span>++</span><span>;</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>11</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>12</div></div><div><span><span>  </span></span><span>cout </span><span>&lt;&lt;</span><span> (S </span><span>&gt;=</span><span> </span><span>3</span><span> </span><span>?</span><span> </span><span>"We are the champions."</span><span> </span><span>:</span><span> </span><span>"Faker is forever."</span><span>) </span><span>&lt;&lt;</span><span> endl;</span></div></div><div><div><div>13</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section><section><h2>B 求数<a href="#b-求数"><span>#</span></a></h2><blockquote><p>Problem tags: 递推 预处理</p></blockquote><p>Prediction Difficulty：Medium-Hard</p><p>Actual Difficulty：Medium</p><section><h3>Problem Statement<a href="#problem-statement-1"><span>#</span></a></h3><p>略。</p></section><section><h3>Solution<a href="#solution-1"><span>#</span></a></h3><p>如果每次询问时处理 <span><span>f(n)f(n)</span><span><span><span></span><span>f</span><span>(</span><span>n</span><span>)</span></span></span></span>，时间复杂度为 <span><span>O(tn)\mathcal{O}(tn)</span><span><span><span></span><span>O</span><span>(</span><span>t</span><span>n</span><span>)</span></span></span></span>。
考虑预处理 <span><span>11</span><span><span><span></span><span>1</span></span></span></span> 到 <span><span>nn</span><span><span><span></span><span>n</span></span></span></span> 的 <span><span>f(n)f(n)</span><span><span><span></span><span>f</span><span>(</span><span>n</span><span>)</span></span></span></span> 值，每次 <span><span>O(1)\mathcal{O}(1)</span><span><span><span></span><span>O</span><span>(</span><span>1</span><span>)</span></span></span></span> 询问答案。预处理时，每算出一个 <span><span>f(i)f(i)</span><span><span><span></span><span>f</span><span>(</span><span>i</span><span>)</span></span></span></span>，就更新 <span><span>maxmax</span><span><span><span></span><span>ma</span><span>x</span></span></span></span> 和 <span><span>minmin</span><span><span><span></span><span>min</span></span></span></span> 的值。总时间复杂度 <span><span>O(1e6+t)\mathcal{O}(1e6+t)</span><span><span><span></span><span>O</span><span>(</span><span>1</span><span>e</span><span>6</span><span></span><span>+</span><span></span></span><span><span></span><span>t</span><span>)</span></span></span></span>。
注意要开 long long。</p></section><section><h3>Std<a href="#std-1"><span>#</span></a></h3><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>const</span><span> </span><span>int</span><span> N</span><span>=</span><span>1</span><span>e</span><span>6</span><span>+</span><span>7</span><span>;</span></div></div><div><div><div>2</div></div><div><span>int</span><span> f[N];</span></div></div><div><div><div>3</div></div><div><span>void</span><span> </span><span>init</span><span>()</span></div></div><div><div><div>4</div></div><div><span>{</span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>f[</span><span>1</span><span>]</span><span>=</span><span>1</span><span>;</span></div></div><div><div><div>6</div></div><div><span>  </span><span>int</span><span> maxn</span><span>=</span><span>f[</span><span>1</span><span>],minn</span><span>=</span><span>f[</span><span>1</span><span>];</span></div></div><div><div><div>7</div></div><div><span>  </span><span>int</span><span> sign</span><span>=</span><span>1</span><span>;</span></div></div><div><div><div>8</div></div><div><span>  </span><span>for</span><span>(</span><span>int</span><span> i</span><span>=</span><span>2</span><span>;i</span><span>&lt;</span><span>N;i</span><span>++</span><span>)</span></div></div><div><div><div>9</div></div><div><span><span>  </span></span><span>{</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>f[i]</span><span>=</span><span>f[i</span><span>-</span><span>1</span><span>]</span><span>+</span><span>sign</span><span>*</span><span>(maxn</span><span>-</span><span>minn)</span><span>+</span><span>i;</span></div></div><div><div><div>11</div></div><div><span><span>    </span></span><span>sign</span><span>=-</span><span>sign;</span><span> // 正负互换</span></div></div><div><div><div>12</div></div><div><span><span>    </span></span><span>maxn</span><span>=</span><span>max</span><span>(maxn,f[i]);</span></div></div><div><div><div>13</div></div><div><span><span>    </span></span><span>minn</span><span>=</span><span>min</span><span>(minn,f[i]);</span></div></div><div><div><div>14</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>15</div></div><div><span>}</span></div></div><div><div><div>16</div></div><div><span>int</span><span> n;</span></div></div><div><div><div>17</div></div><div><span>void</span><span> </span><span>solve</span><span>()</span></div></div><div><div><div>18</div></div><div><span>{</span></div></div><div><div><div>19</div></div><div><span><span>  </span></span><span>cin</span><span>&gt;&gt;</span><span>n;</span></div></div><div><div><div>20</div></div><div><span><span>  </span></span><span>cout</span><span>&lt;&lt;</span><span>f[n]</span><span>&lt;&lt;</span><span>endl;</span></div></div><div><div><div>21</div></div><div><span>}</span></div></div><div><div><div>22</div></div><div><span>signed</span><span> </span><span>main</span><span>()</span></div></div><div><div><div>23</div></div><div><span>{</span></div></div><div><div><div>24</div></div><div><span>  </span><span>ios</span><span>::</span><span>sync_with_stdio</span><span>(</span><span>0</span><span>);cin.</span><span>tie</span><span>(</span><span>0</span><span>);cout.</span><span>tie</span><span>(</span><span>0</span><span>);</span></div></div><div><div><div>25</div></div><div><span>  </span><span>init</span><span>();</span></div></div><div><div><div>26</div></div><div><span>  </span><span>int</span><span> T</span><span>=</span><span>1</span><span>; cin</span><span>&gt;&gt;</span><span>T;</span></div></div><div><div><div>27</div></div><div><span>  </span><span>while</span><span>(T</span><span>--</span><span>){</span><span>solve</span><span>();}</span></div></div><div><div><div>28</div></div><div><span>  </span><span>return</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>29</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section></section><section><h2>C 刀剑神域：艾恩葛朗特的评分机关<a href="#c-刀剑神域艾恩葛朗特的评分机关"><span>#</span></a></h2><blockquote><p>Problem tags: 二分 双指针 贪心</p></blockquote><p>Prediction Difficulty：Hard</p><p>Actual Difficulty：Hard</p><section><h3>Problem Statement<a href="#problem-statement-2"><span>#</span></a></h3><p>给定一个非降序整数序列 <span><span>[a1,a2,…,an][a_1,a_2,\ldots,a_n]</span><span><span><span></span><span>[</span><span><span>a</span><span><span><span><span><span><span></span><span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>,</span><span></span><span><span>a</span><span><span><span><span><span><span></span><span><span>2</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>,</span><span></span><span>…</span><span></span><span>,</span><span></span><span><span>a</span><span><span><span><span><span><span></span><span><span>n</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>]</span></span></span></span>。定义子序列的评分为 <span><span>s1⋅s2⋅…⋅sdd!\frac{s_1 \cdot s_2 \cdot \ldots \cdot s_d}{d!}</span><span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span><span>d</span><span>!</span></span></span></span><span><span></span><span></span></span><span><span></span><span><span><span><span>s</span><span><span><span><span><span><span></span><span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>⋅</span><span><span>s</span><span><span><span><span><span><span></span><span><span>2</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>⋅</span><span>…</span><span>⋅</span><span><span>s</span><span><span><span><span><span><span></span><span><span>d</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span></span></span></span>，空序列评分为 1。序列的配置长度 = 在所有评分最大的子序列中，长度的最大值。</p><p>求：对每个前缀 <span><span>[a1,a2,…,ak][a_1,a_2,\ldots,a_k]</span><span><span><span></span><span>[</span><span><span>a</span><span><span><span><span><span><span></span><span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>,</span><span></span><span><span>a</span><span><span><span><span><span><span></span><span><span>2</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>,</span><span></span><span>…</span><span></span><span>,</span><span></span><span><span>a</span><span><span><span><span><span><span></span><span><span>k</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>]</span></span></span></span>，输出它的配置长度。</p></section><section><h3>Solution1<a href="#solution1"><span>#</span></a></h3><p>我们先来看如何求一个非降序序列 <span><span>s1,s2,…,sℓs_1, s_2, \ldots, s_\ell</span><span><span><span></span><span><span>s</span><span><span><span><span><span><span></span><span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>,</span><span></span><span><span>s</span><span><span><span><span><span><span></span><span><span>2</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>,</span><span></span><span>…</span><span></span><span>,</span><span></span><span><span>s</span><span><span><span><span><span><span></span><span><span>ℓ</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span> 的配置长度。</p><p>如果我们选择一个长度为 <span><span>kk</span><span><span><span></span><span>k</span></span></span></span> 的子序列，为了获得最大评分，最优的选择就是取序列中最大的 <span><span>kk</span><span><span><span></span><span>k</span></span></span></span> 个元素。由于序列是非降序的，最大的 <span><span>kk</span><span><span><span></span><span>k</span></span></span></span> 个元素就是最后的 <span><span>kk</span><span><span><span></span><span>k</span></span></span></span> 个元素。因此，所有可能成为答案的候选子序列都是序列的后缀。</p><p>现在我们把从右往左数的第 <span><span>ii</span><span><span><span></span><span>i</span></span></span></span> 个元素除以 <span><span>ii</span><span><span><span></span><span>i</span></span></span></span>。于是这个序列变成了：</p><span><span><span>s1ℓ,  s2ℓ−1,  …,  sℓ−12,  sℓ1.\frac{s_1}{\ell}, \; \frac{s_2}{\ell-1}, \; \ldots, \; \frac{s_{\ell-1}}{2}, \; \frac{s_\ell}{1}.</span><span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span>ℓ</span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>s</span><span><span><span><span><span><span></span><span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span>,</span><span></span><span></span><span><span></span><span><span><span><span><span><span></span><span><span>ℓ</span><span></span><span>−</span><span></span><span>1</span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>s</span><span><span><span><span><span><span></span><span><span>2</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span>,</span><span></span><span></span><span>…</span><span></span><span>,</span><span></span><span></span><span><span></span><span><span><span><span><span><span></span><span><span>2</span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>s</span><span><span><span><span><span><span></span><span><span><span>ℓ</span><span>−</span><span>1</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span>,</span><span></span><span></span><span><span></span><span><span><span><span><span><span></span><span><span>1</span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>s</span><span><span><span><span><span><span></span><span><span>ℓ</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span>.</span></span></span></span></span><p>注意到，原序列某个后缀的评分，等于在这个新序列中对应后缀的乘积。</p><p>原序列满足</p><span><span><span>s1≤s2≤…≤sℓ−1≤sℓ.s_1 \leq s_2 \leq \ldots \leq s_{\ell-1} \leq s_\ell.</span><span><span><span></span><span><span>s</span><span><span><span><span><span><span></span><span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>≤</span><span></span></span><span><span></span><span><span>s</span><span><span><span><span><span><span></span><span><span>2</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>≤</span><span></span></span><span><span></span><span>…</span><span></span><span>≤</span><span></span></span><span><span></span><span><span>s</span><span><span><span><span><span><span></span><span><span><span>ℓ</span><span>−</span><span>1</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>≤</span><span></span></span><span><span></span><span><span>s</span><span><span><span><span><span><span></span><span><span>ℓ</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>.</span></span></span></span></span><p>而分母部分满足</p><span><span><span>1ℓ≤1ℓ−1≤…≤12≤11.\frac{1}{\ell} \leq \frac{1}{\ell-1} \leq \ldots \leq \frac{1}{2} \leq \frac{1}{1}.</span><span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span>ℓ</span></span></span><span><span></span><span></span></span><span><span></span><span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span></span><span>≤</span><span></span></span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span>ℓ</span><span></span><span>−</span><span></span><span>1</span></span></span><span><span></span><span></span></span><span><span></span><span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span></span><span>≤</span><span></span></span><span><span></span><span>…</span><span></span><span>≤</span><span></span></span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span>2</span></span></span><span><span></span><span></span></span><span><span></span><span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span></span><span>≤</span><span></span></span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span>1</span></span></span><span><span></span><span></span></span><span><span></span><span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span>.</span></span></span></span></span><p>把这两个条件结合起来，可以得到：</p><span><span><span>s1ℓ≤s2ℓ−1≤…≤sℓ−12≤sℓ1,\frac{s_1}{\ell} \leq \frac{s_2}{\ell-1} \leq \ldots \leq \frac{s_{\ell-1}}{2} \leq \frac{s_\ell}{1},</span><span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span>ℓ</span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>s</span><span><span><span><span><span><span></span><span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span></span><span>≤</span><span></span></span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span>ℓ</span><span></span><span>−</span><span></span><span>1</span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>s</span><span><span><span><span><span><span></span><span><span>2</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span></span><span>≤</span><span></span></span><span><span></span><span>…</span><span></span><span>≤</span><span></span></span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span>2</span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>s</span><span><span><span><span><span><span></span><span><span><span>ℓ</span><span>−</span><span>1</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span></span><span>≤</span><span></span></span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span>1</span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>s</span><span><span><span><span><span><span></span><span><span>ℓ</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span>,</span></span></span></span></span><p>因此，新序列依然是非降序的。</p><p>为了使新序列后缀的乘积最大，我们会选择新序列中所有 <span><span>≥1\geq 1</span><span><span><span></span><span>≥</span><span></span></span><span><span></span><span>1</span></span></span></span> 的元素（这些元素必然形成一个后缀，因为新序列是非降序的）。如果新序列中有元素恰好等于 1，那么我们必须把它们也选进去，这样才能保证在最大评分的所有子序列中长度最大。</p><p>因此，序列 <span><span>s1,s2,…,sℓs_1, s_2, \ldots, s_\ell</span><span><span><span></span><span><span>s</span><span><span><span><span><span><span></span><span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>,</span><span></span><span><span>s</span><span><span><span><span><span><span></span><span><span>2</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>,</span><span></span><span>…</span><span></span><span>,</span><span></span><span><span>s</span><span><span><span><span><span><span></span><span><span>ℓ</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span> 的配置长度，就是新序列</p><span><span><span>s1ℓ,  s2ℓ−1,  …,  sℓ−12,  sℓ1\frac{s_1}{\ell}, \; \frac{s_2}{\ell-1}, \; \ldots, \; \frac{s_{\ell-1}}{2}, \; \frac{s_\ell}{1}</span><span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span>ℓ</span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>s</span><span><span><span><span><span><span></span><span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span>,</span><span></span><span></span><span><span></span><span><span><span><span><span><span></span><span><span>ℓ</span><span></span><span>−</span><span></span><span>1</span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>s</span><span><span><span><span><span><span></span><span><span>2</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span>,</span><span></span><span></span><span>…</span><span></span><span>,</span><span></span><span></span><span><span></span><span><span><span><span><span><span></span><span><span>2</span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>s</span><span><span><span><span><span><span></span><span><span><span>ℓ</span><span>−</span><span>1</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span>,</span><span></span><span></span><span><span></span><span><span><span><span><span><span></span><span><span>1</span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>s</span><span><span><span><span><span><span></span><span><span>ℓ</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span></span></span></span></span><p>的最长后缀长度，并且这个后缀中的每个元素都不小于 1。</p><p>接下来，我们要计算给定序列 <span><span>[a1,a2,…,an][a_1,a_2,\ldots,a_n]</span><span><span><span></span><span>[</span><span><span>a</span><span><span><span><span><span><span></span><span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>,</span><span></span><span><span>a</span><span><span><span><span><span><span></span><span><span>2</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>,</span><span></span><span>…</span><span></span><span>,</span><span></span><span><span>a</span><span><span><span><span><span><span></span><span><span>n</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>]</span></span></span></span> 的所有前缀的配置长度。</p><p>对某个前缀 <span><span>[a1,a2,…,ak][a_1,a_2,\ldots,a_k]</span><span><span><span></span><span>[</span><span><span>a</span><span><span><span><span><span><span></span><span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>,</span><span></span><span><span>a</span><span><span><span><span><span><span></span><span><span>2</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>,</span><span></span><span>…</span><span></span><span>,</span><span></span><span><span>a</span><span><span><span><span><span><span></span><span><span>k</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>]</span></span></span></span>，它的配置长度就是新序列</p><span><span><span>a1k,  a2k−1,  …,  ak−12,  ak1\frac{a_1}{k}, \; \frac{a_2}{k-1}, \; \ldots, \; \frac{a_{k-1}}{2}, \; \frac{a_k}{1}</span><span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span>k</span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>a</span><span><span><span><span><span><span></span><span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span>,</span><span></span><span></span><span><span></span><span><span><span><span><span><span></span><span><span>k</span><span></span><span>−</span><span></span><span>1</span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>a</span><span><span><span><span><span><span></span><span><span>2</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span>,</span><span></span><span></span><span>…</span><span></span><span>,</span><span></span><span></span><span><span></span><span><span><span><span><span><span></span><span><span>2</span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>a</span><span><span><span><span><span><span></span><span><span><span>k</span><span>−</span><span>1</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span>,</span><span></span><span></span><span><span></span><span><span><span><span><span><span></span><span><span>1</span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>a</span><span><span><span><span><span><span></span><span><span>k</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span></span></span></span></span><p>的最长后缀长度，其中的每个元素都不小于 1。</p><p>我们可以用二分查找来求这个长度。注意不能显式构造每个前缀对应的“变换序列”，那样会太慢。相反，在二分的每一步，我们可以直接计算该位置对应的值，判断它是否 <span><span>≥1\geq 1</span><span><span><span></span><span>≥</span><span></span></span><span><span></span><span>1</span></span></span></span>。这样就能在 <span><span>O(log⁡n)\mathcal{O}(\log n)</span><span><span><span></span><span>O</span><span>(</span><span>lo<span>g</span></span><span></span><span>n</span><span>)</span></span></span></span> 的时间内求出每个前缀的配置长度。</p><p>时间复杂度： 每个测试用例的时间复杂度为 <span><span>O(nlog⁡n)\mathcal{O}(n \log n)</span><span><span><span></span><span>O</span><span>(</span><span>n</span><span></span><span>lo<span>g</span></span><span></span><span>n</span><span>)</span></span></span></span>。</p></section><section><h3>Std1<a href="#std1"><span>#</span></a></h3><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>void</span><span> </span><span>slu</span><span>() {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>int</span><span> n;</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>cin </span><span>&gt;&gt;</span><span> n;</span></div></div><div><div><div>4</div></div><div><span>  </span><span>vector</span><span>&lt;</span><span>int</span><span>&gt; </span><span>a</span><span>(n);</span></div></div><div><div><div>5</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>0</span><span>; i </span><span>&lt;</span><span> n; i</span><span>++</span><span>) {</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>cin </span><span>&gt;&gt;</span><span> a[i];</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>8</div></div><div><span>  </span><span>vector</span><span>&lt;</span><span>int</span><span>&gt; res;</span></div></div><div><div><div>9</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>0</span><span>; i </span><span>&lt;</span><span> n; i</span><span>++</span><span>) {</span></div></div><div><div><div>10</div></div><div><span>    </span><span>int</span><span> l </span><span>=</span><span> </span><span>1</span><span>, r </span><span>=</span><span> i </span><span>+</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>11</div></div><div><span>    </span><span>while</span><span> (l </span><span>&lt;=</span><span> r) {</span></div></div><div><div><div>12</div></div><div><span>      </span><span>int</span><span> m </span><span>=</span><span> (l </span><span>+</span><span> r) </span><span>/</span><span> </span><span>2</span><span>;</span></div></div><div><div><div>13</div></div><div><span>      </span><span>if</span><span> (a[i </span><span>-</span><span> m </span><span>+</span><span> </span><span>1</span><span>] </span><span>&gt;=</span><span> m) {</span></div></div><div><div><div>14</div></div><div><span><span>        </span></span><span>l </span><span>=</span><span> m </span><span>+</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>15</div></div><div><span><span>      </span></span><span>} </span><span>else</span><span> {</span></div></div><div><div><div>16</div></div><div><span><span>        </span></span><span>r </span><span>=</span><span> m </span><span>-</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>17</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>18</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>19</div></div><div><span><span>    </span></span><span>res.</span><span>push_back</span><span>(r);</span></div></div><div><div><div>20</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>21</div></div><div><span>  </span><span>for</span><span> (</span><span>auto</span><span> </span><span>&amp;</span><span>x : res) {</span></div></div><div><div><div>22</div></div><div><span><span>    </span></span><span>cout </span><span>&lt;&lt;</span><span> x </span><span>&lt;&lt;</span><span> </span><span>' '</span><span>;</span></div></div><div><div><div>23</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>24</div></div><div><span><span>  </span></span><span>cout </span><span>&lt;&lt;</span><span> endl;</span></div></div><div><div><div>25</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section><section><h3>Solution2<a href="#solution2"><span>#</span></a></h3><p>对前缀 <span><span>A[1..k]A[1..k]</span><span><span><span></span><span>A</span><span>[</span><span>1..</span><span>k</span><span>]</span></span></span></span> 构造的「变换序列」</p><span><span><span>A1k,  A2k−1,  …,  Ak1\frac{A_1}{k},\;\frac{A_2}{k-1},\;\ldots,\;\frac{A_k}{1}</span><span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span>k</span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>A</span><span><span><span><span><span><span></span><span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span>,</span><span></span><span></span><span><span></span><span><span><span><span><span><span></span><span><span>k</span><span></span><span>−</span><span></span><span>1</span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>A</span><span><span><span><span><span><span></span><span><span>2</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span>,</span><span></span><span></span><span>…</span><span></span><span>,</span><span></span><span></span><span><span></span><span><span><span><span><span><span></span><span><span>1</span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>A</span><span><span><span><span><span><span></span><span><span>k</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span></span></span></span></span><p>是非降序的（证明见 Solution 1）。
我们要找的是最长后缀满足其中所有元素 <span><span>≥1\geq 1</span><span><span><span></span><span>≥</span><span></span></span><span><span></span><span>1</span></span></span></span>。
设该后缀长度为 <span><span>dkd_k</span><span><span><span></span><span><span>d</span><span><span><span><span><span><span></span><span><span>k</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span>，则</p><span><span><span>Ak−dk+1dk≥1,  Ak−dk+2dk−1≥1,  …,  Ak1≥1.\frac{A_{k-d_k+1}}{d_k}\geq 1,\;
\frac{A_{k-d_k+2}}{d_k-1}\geq 1,\;\ldots,\;
\frac{A_k}{1}\geq 1.</span><span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span><span>d</span><span><span><span><span><span><span></span><span><span>k</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>A</span><span><span><span><span><span><span></span><span><span><span>k</span><span>−</span><span><span>d</span><span><span><span><span><span><span></span><span><span>k</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>+</span><span>1</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span></span><span>≥</span><span></span></span><span><span></span><span>1</span><span>,</span><span></span><span></span><span><span></span><span><span><span><span><span><span></span><span><span><span>d</span><span><span><span><span><span><span></span><span><span>k</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>−</span><span></span><span>1</span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>A</span><span><span><span><span><span><span></span><span><span><span>k</span><span>−</span><span><span>d</span><span><span><span><span><span><span></span><span><span>k</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>+</span><span>2</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span></span><span>≥</span><span></span></span><span><span></span><span>1</span><span>,</span><span></span><span></span><span>…</span><span></span><span>,</span><span></span><span></span><span><span></span><span><span><span><span><span><span></span><span><span>1</span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>A</span><span><span><span><span><span><span></span><span><span>k</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span></span><span>≥</span><span></span></span><span><span></span><span>1.</span></span></span></span></span><p>由于序列非降，最左边的限制最强，因此只需</p><span><span><span>Ak−dk+1≥dk.(*)A_{k-d_k+1}\geq d_k. \tag{*}</span><span><span><span></span><span><span>A</span><span><span><span><span><span><span></span><span><span><span>k</span><span>−</span><span><span>d</span><span><span><span><span><span><span></span><span><span>k</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>+</span><span>1</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>≥</span><span></span></span><span><span></span><span><span>d</span><span><span><span><span><span><span></span><span><span>k</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>.</span></span><span><span></span><span><span>(</span><span><span>*</span></span><span>)</span></span></span></span></span></span><p>我们按 <span><span>k=1..nk=1..n</span><span><span><span></span><span>k</span><span></span><span>=</span><span></span></span><span><span></span><span>1..</span><span>n</span></span></span></span> 顺序处理，维护当前最优长度 <span><span>dd</span><span><span><span></span><span>d</span></span></span></span>（即 <span><span>dkd_k</span><span><span><span></span><span><span>d</span><span><span><span><span><span><span></span><span><span>k</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span>）。
当 <span><span>kk</span><span><span><span></span><span>k</span></span></span></span> 增加 1 时，先令 <span><span>dd</span><span><span><span></span><span>d</span></span></span></span> 不变，然后尝试向前扩展一位：</p><p>只要 <span><span>d&lt;kd&lt;k</span><span><span><span></span><span>d</span><span></span><span>&lt;</span><span></span></span><span><span></span><span>k</span></span></span></span> 且新的「最左元素」满足</p><span><span><span>Ak−d≥d+1A_{k-d}\geq d+1</span><span><span><span></span><span><span>A</span><span><span><span><span><span><span></span><span><span><span>k</span><span>−</span><span>d</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>≥</span><span></span></span><span><span></span><span>d</span><span></span><span>+</span><span></span></span><span><span></span><span>1</span></span></span></span></span><p>就说明能把第 <span><span>k−dk-d</span><span><span><span></span><span>k</span><span></span><span>−</span><span></span></span><span><span></span><span>d</span></span></span></span> 个元素也拉进来，于是 <span><span>d←d+1d\leftarrow d+1</span><span><span><span></span><span>d</span><span></span><span>←</span><span></span></span><span><span></span><span>d</span><span></span><span>+</span><span></span></span><span><span></span><span>1</span></span></span></span>。
因为序列非降，每次扩展后仍保证 <span><span>(∗)(*)</span><span><span><span></span><span>(</span><span>∗</span><span>)</span></span></span></span> 对新的 <span><span>dd</span><span><span><span></span><span>d</span></span></span></span> 成立；否则停止扩展。</p><p>时间复杂度：<span><span>O(n)\mathcal{O}(n)</span><span><span><span></span><span>O</span><span>(</span><span>n</span><span>)</span></span></span></span>。</p></section><section><h3>Std2<a href="#std2"><span>#</span></a></h3><p>略。</p></section></section><section><h2>D 吃馒头<a href="#d-吃馒头"><span>#</span></a></h2><blockquote><p>Problem tags: 二分</p></blockquote><p>Prediction Difficulty：Medium-Hard</p><p>Actual Difficulty：Hard</p><section><h3>Problem Statement<a href="#problem-statement-3"><span>#</span></a></h3><p>有 <span><span>nn</span><span><span><span></span><span>n</span></span></span></span> 个人要一起吃掉 <span><span>kk</span><span><span><span></span><span>k</span></span></span></span> 个馒头。第 <span><span>ii</span><span><span><span></span><span>i</span></span></span></span> 个人吃一个馒头要 <span><span>tit_i</span><span><span><span></span><span><span>t</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span> 秒，一次最多能连续吃 <span><span>aia_i</span><span><span><span></span><span><span>a</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span> 个，然后必须休息 <span><span>bib_i</span><span><span><span></span><span><span>b</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span> 秒，再重复吃。所有人同时开始吃时，吃完全部 <span><span>kk</span><span><span><span></span><span>k</span></span></span></span> 个馒头所需的最短时间。</p></section><section><h3>Solution<a href="#solution-2"><span>#</span></a></h3><p>看到最大值最小或者最小值最大，我们就要考虑二分答案。
假设当前答案时间为 <span><span>pp</span><span><span><span></span><span>p</span></span></span></span> 秒，对于第 <span><span>ii</span><span><span><span></span><span>i</span></span></span></span> 个人，设<span><span>Ti=ti×ai+biT_i = t_i \times a_i + b_i</span><span><span><span></span><span><span>T</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>=</span><span></span></span><span><span></span><span><span>t</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>×</span><span></span></span><span><span></span><span><span>a</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>+</span><span></span></span><span><span></span><span><span>b</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span>，</p><p>在每 <span><span>TiT_i</span><span><span><span></span><span><span>T</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span> 秒可以吃掉 <span><span>aia_i</span><span><span><span></span><span><span>a</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span> 个馒头。则在 <span><span>⌊pTi⌋Ti\left\lfloor \dfrac{p}{T_i} \right\rfloor T_i</span><span><span><span></span><span><span><span>⌊</span></span><span><span></span><span><span><span><span><span><span></span><span><span><span>T</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span><span><span></span><span></span></span><span><span></span><span><span>p</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span><span>⌋</span></span></span><span></span><span><span>T</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span> 秒内可以吃掉 <span><span>⌊pTi⌋ai\left\lfloor \dfrac{p}{T_i} \right\rfloor a_i</span><span><span><span></span><span><span><span>⌊</span></span><span><span></span><span><span><span><span><span><span></span><span><span><span>T</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span><span><span></span><span></span></span><span><span></span><span><span>p</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span><span>⌋</span></span></span><span></span><span><span>a</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span> 个馒头，
还剩下 <span><span>ri=p mod Tir_i = p \bmod T_i</span><span><span><span></span><span><span>r</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>=</span><span></span></span><span><span></span><span>p</span><span></span><span></span><span><span><span>mod</span></span></span><span></span><span></span></span><span><span></span><span><span>T</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span> 秒可以吃馒头，
在最后的 <span><span>rir_i</span><span><span><span></span><span><span>r</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span> 秒内可以吃掉 <span><span>min⁡!(⌊riti⌋,,ai)\min!\left(\left\lfloor \dfrac{r_i}{t_i} \right\rfloor,, a_i\right)</span><span><span><span></span><span>min</span><span>!</span><span></span><span><span><span>(</span></span><span><span><span>⌊</span></span><span><span></span><span><span><span><span><span><span></span><span><span><span>t</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>r</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span><span>⌋</span></span></span><span></span><span>,,</span><span></span><span><span>a</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span><span>)</span></span></span></span></span></span> 个馒头，
加到当前吃馒头的数量。
以此可以推出 <span><span>nn</span><span><span><span></span><span>n</span></span></span></span> 个人在 <span><span>pp</span><span><span><span></span><span>p</span></span></span></span> 秒内吃掉的馒头总和：</p><span><span><span>F(p)=∑i=1n(⌊pTi⌋ai+min⁡!(⌊p mod Titi⌋,,ai))F(p) = \sum_{i=1}^{n} \left( \left\lfloor \dfrac{p}{T_i} \right\rfloor a_i + \min!\left(\left\lfloor \dfrac{p \bmod T_i}{t_i} \right\rfloor,, a_i\right) \right)</span><span><span><span></span><span>F</span><span>(</span><span>p</span><span>)</span><span></span><span>=</span><span></span></span><span><span></span><span><span><span><span><span><span></span><span><span><span>i</span><span>=</span><span>1</span></span></span></span><span><span></span><span><span>∑</span></span></span><span><span></span><span><span><span>n</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span><span><span><span>(</span></span><span><span><span>⌊</span></span><span><span></span><span><span><span><span><span><span></span><span><span><span>T</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span><span><span></span><span></span></span><span><span></span><span><span>p</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span><span>⌋</span></span></span><span></span><span><span>a</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>+</span><span></span><span>min</span><span>!</span><span></span><span><span><span>(</span></span><span><span><span>⌊</span></span><span><span></span><span><span><span><span><span><span></span><span><span><span>t</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span><span><span></span><span></span></span><span><span></span><span><span>p</span><span></span><span></span><span><span><span>mod</span></span></span><span></span><span></span><span><span>T</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span><span>⌋</span></span></span><span></span><span>,,</span><span></span><span><span>a</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span><span>)</span></span></span><span><span>)</span></span></span></span></span></span></span><p>然后将 <span><span>F(p)F(p)</span><span><span><span></span><span>F</span><span>(</span><span>p</span><span>)</span></span></span></span> 与 <span><span>kk</span><span><span><span></span><span>k</span></span></span></span> 比较大小，写一个 <code>check</code> 函数即可。</p></section><section><h3>Std<a href="#std-2"><span>#</span></a></h3><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>#include</span><span> </span><span>&lt;bits/stdc++.h&gt;</span></div></div><div><div><div>2</div></div><div><span>#define</span><span> </span><span>ll</span><span> </span><span>long</span><span> </span><span>long</span></div></div><div><div><div>3</div></div><div><span>#define</span><span> </span><span>int</span><span> ll</span></div></div><div><div><div>4</div></div><div><span>using</span><span> </span><span>namespace</span><span> </span><span>std</span><span>;</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>const</span><span> </span><span>int</span><span> N </span><span>=</span><span> </span><span>2</span><span>e</span><span>5</span><span> </span><span>+</span><span> </span><span>7</span><span>;</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>int</span><span> n, k;</span></div></div><div><div><div>9</div></div><div><span>int</span><span> t[N], a[N], b[N];</span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span>bool</span><span> </span><span>check</span><span>(</span><span>int</span><span> </span><span>x</span><span>)</span></div></div><div><div><div>12</div></div><div><span>{</span></div></div><div><div><div>13</div></div><div><span>    </span><span>int</span><span> ans </span><span>=</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>14</div></div><div><span>    </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>1</span><span>; i </span><span>&lt;=</span><span> n; i</span><span>++</span><span>)</span></div></div><div><div><div>15</div></div><div><span><span>    </span></span><span>{</span></div></div><div><div><div>16</div></div><div><span>        </span><span>int</span><span> T </span><span>=</span><span> (a[i] </span><span>*</span><span> t[i] </span><span>+</span><span> b[i]);</span></div></div><div><div><div>17</div></div><div><span><span>        </span></span><span>ans </span><span>+=</span><span> (x </span><span>/</span><span> T) </span><span>*</span><span> a[i];</span></div></div><div><div><div>18</div></div><div><span>        </span><span>int</span><span> y </span><span>=</span><span> x </span><span>%</span><span> T;</span></div></div><div><div><div>19</div></div><div><span>        </span><span>if</span><span> (a[i] </span><span>*</span><span> t[i] </span><span>&gt;=</span><span> y)</span></div></div><div><div><div>20</div></div><div><span><span>        </span></span><span>{</span></div></div><div><div><div>21</div></div><div><span><span>            </span></span><span>ans </span><span>+=</span><span> y </span><span>/</span><span> t[i];</span></div></div><div><div><div>22</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>23</div></div><div><span>        </span><span>else</span></div></div><div><div><div>24</div></div><div><span><span>            </span></span><span>ans </span><span>+=</span><span> a[i];</span></div></div><div><div><div>25</div></div><div><span>        </span><span>if</span><span> (ans </span><span>&gt;=</span><span> k)</span></div></div><div><div><div>26</div></div><div><span>            </span><span>return</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>27</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>28</div></div><div><span>    </span><span>return</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>29</div></div><div><span>}</span></div></div><div><div><div>30</div></div><div>
</div></div><div><div><div>31</div></div><div><span>signed</span><span> </span><span>main</span><span>()</span></div></div><div><div><div>32</div></div><div><span>{</span></div></div><div><div><div>33</div></div><div><span>    </span><span>ios</span><span> ::</span><span>sync_with_stdio</span><span>(</span><span>false</span><span>), cin.</span><span>tie</span><span>(</span><span>nullptr</span><span>), cout.</span><span>tie</span><span>(</span><span>nullptr</span><span>);</span></div></div><div><div><div>34</div></div><div><span>    </span><span>int</span><span> TT </span><span>=</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>35</div></div><div><span><span>    </span></span><span>cin </span><span>&gt;&gt;</span><span> TT;</span></div></div><div><div><div>36</div></div><div><span>    </span><span>while</span><span> (TT</span><span>--</span><span>)</span></div></div><div><div><div>37</div></div><div><span><span>    </span></span><span>{</span></div></div><div><div><div>38</div></div><div><span><span>        </span></span><span>cin </span><span>&gt;&gt;</span><span> n </span><span>&gt;&gt;</span><span> k;</span></div></div><div><div><div>39</div></div><div><span>        </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>1</span><span>; i </span><span>&lt;=</span><span> n; i</span><span>++</span><span>)</span></div></div><div><div><div>40</div></div><div><span><span>        </span></span><span>{</span></div></div><div><div><div>41</div></div><div><span><span>            </span></span><span>cin </span><span>&gt;&gt;</span><span> t[i];</span></div></div><div><div><div>42</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>43</div></div><div><span>        </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>1</span><span>; i </span><span>&lt;=</span><span> n; i</span><span>++</span><span>)</span></div></div><div><div><div>44</div></div><div><span><span>        </span></span><span>{</span></div></div><div><div><div>45</div></div><div><span><span>            </span></span><span>cin </span><span>&gt;&gt;</span><span> a[i];</span></div></div><div><div><div>46</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>47</div></div><div><span>        </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>1</span><span>; i </span><span>&lt;=</span><span> n; i</span><span>++</span><span>)</span></div></div><div><div><div>48</div></div><div><span><span>        </span></span><span>{</span></div></div><div><div><div>49</div></div><div><span><span>            </span></span><span>cin </span><span>&gt;&gt;</span><span> b[i];</span></div></div><div><div><div>50</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>51</div></div><div><span>        </span><span>int</span><span> l </span><span>=</span><span> </span><span>0</span><span>, r </span><span>=</span><span> </span><span>1</span><span>e</span><span>18</span><span> </span><span>+</span><span> </span><span>7</span><span>;</span></div></div><div><div><div>52</div></div><div><span>        </span><span>while</span><span> (l </span><span>&lt;</span><span> r)</span></div></div><div><div><div>53</div></div><div><span><span>        </span></span><span>{</span></div></div><div><div><div>54</div></div><div><span>            </span><span>int</span><span> mid </span><span>=</span><span> (l </span><span>+</span><span> r) </span><span>/</span><span> </span><span>2</span><span>;</span></div></div><div><div><div>55</div></div><div><span>            </span><span>if</span><span> (</span><span>check</span><span>(mid))</span></div></div><div><div><div>56</div></div><div><span><span>                </span></span><span>r </span><span>=</span><span> mid;</span></div></div><div><div><div>57</div></div><div><span>            </span><span>else</span></div></div><div><div><div>58</div></div><div><span><span>                </span></span><span>l </span><span>=</span><span> mid </span><span>+</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>59</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>60</div></div><div><span><span>        </span></span><span>cout </span><span>&lt;&lt;</span><span> l </span><span>&lt;&lt;</span><span> </span><span>'</span><span>\n</span><span>'</span><span>;</span></div></div><div><div><div>61</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>62</div></div><div><span>    </span><span>return</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>63</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section></section><section><h2>E 计算器<a href="#e-计算器"><span>#</span></a></h2><blockquote><p>Problem tags: 分支结构</p></blockquote><p>Prediction Difficulty：Easy</p><p>Actual Difficulty：Easy</p><section><h3>Problem Statement<a href="#problem-statement-4"><span>#</span></a></h3><p>模拟两个数字的五种运算。</p></section><section><h3>Solution<a href="#solution-3"><span>#</span></a></h3><p>模拟即可。注意乘法会爆 int，需要 long long 来进行存储。</p></section><section><h3>Std<a href="#std-3"><span>#</span></a></h3><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>int</span><span> </span><span>main</span><span>()</span></div></div><div><div><div>2</div></div><div><span>{</span></div></div><div><div><div>3</div></div><div><span>  </span><span>ios</span><span> :: </span><span>sync_with_stdio</span><span>(</span><span>0</span><span>), cin.</span><span>tie</span><span>(</span><span>0</span><span>), cout.</span><span>tie</span><span>(</span><span>0</span><span>);</span></div></div><div><div><div>4</div></div><div><span>  </span><span>int</span><span> TT;</span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>cin </span><span>&gt;&gt;</span><span> TT;</span></div></div><div><div><div>6</div></div><div><span>  </span><span>while</span><span> (TT</span><span>--</span><span>)</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>{</span></div></div><div><div><div>8</div></div><div><span>    </span><span>long</span><span> </span><span>long</span><span> a, b;</span></div></div><div><div><div>9</div></div><div><span>    </span><span>char</span><span> op;</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>cin </span><span>&gt;&gt;</span><span> a </span><span>&gt;&gt;</span><span> b </span><span>&gt;&gt;</span><span> op;</span></div></div><div><div><div>11</div></div><div><span>    </span><span>if</span><span> ((op </span><span>==</span><span> </span><span>'%'</span><span> </span><span>||</span><span> op </span><span>==</span><span> </span><span>'/'</span><span>) </span><span>&amp;&amp;</span><span> (b </span><span>==</span><span> </span><span>0</span><span>))</span></div></div><div><div><div>12</div></div><div><span><span>    </span></span><span>{</span></div></div><div><div><div>13</div></div><div><span><span>      </span></span><span>cout </span><span>&lt;&lt;</span><span> </span><span>"No"</span><span> </span><span>&lt;&lt;</span><span> </span><span>'</span><span>\n</span><span>'</span><span>;</span></div></div><div><div><div>14</div></div><div><span>      </span><span>continue</span><span>;</span></div></div><div><div><div>15</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>16</div></div><div><span>    </span><span>if</span><span> (op </span><span>==</span><span> </span><span>'+'</span><span>)</span></div></div><div><div><div>17</div></div><div><span><span>    </span></span><span>{</span></div></div><div><div><div>18</div></div><div><span><span>      </span></span><span>cout </span><span>&lt;&lt;</span><span> a </span><span>+</span><span> b </span><span>&lt;&lt;</span><span> </span><span>'</span><span>\n</span><span>'</span><span>;</span></div></div><div><div><div>19</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>20</div></div><div><span>    </span><span>else</span><span> </span><span>if</span><span> (op </span><span>==</span><span> </span><span>'-'</span><span>)</span></div></div><div><div><div>21</div></div><div><span><span>    </span></span><span>{</span></div></div><div><div><div>22</div></div><div><span><span>      </span></span><span>cout </span><span>&lt;&lt;</span><span> a </span><span>-</span><span> b </span><span>&lt;&lt;</span><span> </span><span>'</span><span>\n</span><span>'</span><span>;</span></div></div><div><div><div>23</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>24</div></div><div><span>    </span><span>else</span><span> </span><span>if</span><span> (op </span><span>==</span><span> </span><span>'*'</span><span>)</span></div></div><div><div><div>25</div></div><div><span><span>    </span></span><span>{</span></div></div><div><div><div>26</div></div><div><span><span>      </span></span><span>cout </span><span>&lt;&lt;</span><span> a </span><span>*</span><span> b </span><span>&lt;&lt;</span><span> </span><span>'</span><span>\n</span><span>'</span><span>;</span></div></div><div><div><div>27</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>28</div></div><div><span>    </span><span>else</span><span> </span><span>if</span><span> (op </span><span>==</span><span> </span><span>'/'</span><span>)</span></div></div><div><div><div>29</div></div><div><span><span>    </span></span><span>{</span></div></div><div><div><div>30</div></div><div><span><span>      </span></span><span>cout </span><span>&lt;&lt;</span><span> a </span><span>/</span><span> b </span><span>&lt;&lt;</span><span> </span><span>'</span><span>\n</span><span>'</span><span>;</span></div></div><div><div><div>31</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>32</div></div><div><span>    </span><span>else</span></div></div><div><div><div>33</div></div><div><span><span>    </span></span><span>{</span></div></div><div><div><div>34</div></div><div><span><span>      </span></span><span>cout </span><span>&lt;&lt;</span><span> a </span><span>%</span><span> b </span><span>&lt;&lt;</span><span> </span><span>'</span><span>\n</span><span>'</span><span>;</span></div></div><div><div><div>35</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>36</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>37</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section></section><section><h2>F 边狱巴士是一款大概率发生小概率事件的游戏<a href="#f-边狱巴士是一款大概率发生小概率事件的游戏"><span>#</span></a></h2><blockquote><p>Problem tags: 顺序结构 数学 贪心</p></blockquote><p>Prediction Difficulty：Easy-Medium</p><p>Actual Difficulty：Easy-Medium</p><section><h3>Problem Statement<a href="#problem-statement-5"><span>#</span></a></h3><p>给定 <span><span>atk,add,n,a,b(a+b=n)atk,add,n,a,b(a+b=n)</span><span><span><span></span><span>a</span><span>t</span><span>k</span><span>,</span><span></span><span>a</span><span>dd</span><span>,</span><span></span><span>n</span><span>,</span><span></span><span>a</span><span>,</span><span></span><span>b</span><span>(</span><span>a</span><span></span><span>+</span><span></span></span><span><span></span><span>b</span><span></span><span>=</span><span></span></span><span><span></span><span>n</span><span>)</span></span></span></span>，第 <span><span>ii</span><span><span><span></span><span>i</span></span></span></span> 次发生攻击时的伤害为 <span><span>atk+i⋅addatk+i\cdot add</span><span><span><span></span><span>a</span><span>t</span><span>k</span><span></span><span>+</span><span></span></span><span><span></span><span>i</span><span></span><span>⋅</span><span></span></span><span><span></span><span>a</span><span>dd</span></span></span></span>。普通硬币反面不攻击；不可摧毁硬币无论正反都攻击。求此技能总伤害的最小值与最大值。</p></section><section><h3>Solution1<a href="#solution1-1"><span>#</span></a></h3><p>记 <span><span>Di=atk+i⋅addD_i=atk+i\cdot add</span><span><span><span></span><span><span>D</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>=</span><span></span></span><span><span></span><span>a</span><span>t</span><span>k</span><span></span><span>+</span><span></span></span><span><span></span><span>i</span><span></span><span>⋅</span><span></span></span><span><span></span><span>a</span><span>dd</span></span></span></span>。
最大值：令所有 <span><span>nn</span><span><span><span></span><span>n</span></span></span></span> 次都发生攻击（把普通硬币全为正面），有：</p><span><span><span>max=n⋅atk+add⋅n(n+1)2\text{max}=n\cdot atk+add\cdot\frac{n(n+1)}{2}</span><span><span><span></span><span><span>max</span></span><span></span><span>=</span><span></span></span><span><span></span><span>n</span><span></span><span>⋅</span><span></span></span><span><span></span><span>a</span><span>t</span><span>k</span><span></span><span>+</span><span></span></span><span><span></span><span>a</span><span>dd</span><span></span><span>⋅</span><span></span></span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span>2</span></span></span><span><span></span><span></span></span><span><span></span><span><span>n</span><span>(</span><span>n</span><span></span><span>+</span><span></span><span>1</span><span>)</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span></span></span></span></span><p>最小值：仅让 <span><span>bb</span><span><span><span></span><span>b</span></span></span></span> 次不可摧毁硬币发生攻击并占据最小序号 <span><span>1…b1\ldots b</span><span><span><span></span><span>1</span><span></span><span>…</span><span></span><span>b</span></span></span></span>，有 <span><span>min=b⋅atk\text{min}=b\cdot atk</span><span><span><span></span><span><span>min</span></span><span></span><span>=</span><span></span></span><span><span></span><span>b</span><span></span><span>⋅</span><span></span></span><span><span></span><span>a</span><span>t</span><span>k</span></span></span></span>，即输出：<span><span>max=n⋅atk+add⋅n(n+1)2\text{max}=n\cdot atk+add\cdot\frac{n(n+1)}{2}</span><span><span><span></span><span><span>max</span></span><span></span><span>=</span><span></span></span><span><span></span><span>n</span><span></span><span>⋅</span><span></span></span><span><span></span><span>a</span><span>t</span><span>k</span><span></span><span>+</span><span></span></span><span><span></span><span>a</span><span>dd</span><span></span><span>⋅</span><span></span></span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span><span>2</span></span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>n</span><span>(</span><span>n</span><span>+</span><span>1</span><span>)</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span></span></span></span>、<span><span>min=b⋅atk\text{min}=b\cdot atk</span><span><span><span></span><span><span>min</span></span><span></span><span>=</span><span></span></span><span><span></span><span>b</span><span></span><span>⋅</span><span></span></span><span><span></span><span>a</span><span>t</span><span>k</span></span></span></span></p><p>时间复杂度：<span><span>O(1)\mathcal{O}(1)</span><span><span><span></span><span>O</span><span>(</span><span>1</span><span>)</span></span></span></span>。</p></section><section><h3>Std1<a href="#std1-1"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>void</span><span> </span><span>slu</span><span>() {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>int</span><span> atk, add, n, a, b;</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>cin </span><span>&gt;&gt;</span><span> atk </span><span>&gt;&gt;</span><span> add </span><span>&gt;&gt;</span><span> n </span><span>&gt;&gt;</span><span> a </span><span>&gt;&gt;</span><span> b;</span></div></div><div><div><div>4</div></div><div><span>  </span><span>int</span><span> mn </span><span>=</span><span> b </span><span>*</span><span> atk;</span></div></div><div><div><div>5</div></div><div><span>  </span><span>int</span><span> mx </span><span>=</span><span> n </span><span>*</span><span> atk </span><span>+</span><span> add </span><span>*</span><span> (n </span><span>*</span><span> (n </span><span>+</span><span> </span><span>1</span><span>) </span><span>/</span><span> </span><span>2</span><span>);</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>cout </span><span>&lt;&lt;</span><span> mn </span><span>&lt;&lt;</span><span> </span><span>" "</span><span> </span><span>&lt;&lt;</span><span> mx </span><span>&lt;&lt;</span><span> endl;</span></div></div><div><div><div>7</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>Solution2<a href="#solution2-1"><span>#</span></a></h3><p>按攻击发生的序号从 <span><span>i=1i=1</span><span><span><span></span><span>i</span><span></span><span>=</span><span></span></span><span><span></span><span>1</span></span></span></span> 递增累加 <span><span>atk+i⋅addatk+i\cdot add</span><span><span><span></span><span>a</span><span>t</span><span>k</span><span></span><span>+</span><span></span></span><span><span></span><span>i</span><span></span><span>⋅</span><span></span></span><span><span></span><span>a</span><span>dd</span></span></span></span>：最大值累加 <span><span>i=1…ni=1\ldots n</span><span><span><span></span><span>i</span><span></span><span>=</span><span></span></span><span><span></span><span>1</span><span></span><span>…</span><span></span><span>n</span></span></span></span>，最小值累加 <span><span>i=1…bi=1\ldots b</span><span><span><span></span><span>i</span><span></span><span>=</span><span></span></span><span><span></span><span>1</span><span></span><span>…</span><span></span><span>b</span></span></span></span>。</p><p>由输出量级：</p><span><span><span>max=n⋅atk+add⋅n(n+1)2,min=b⋅atk\text{max}=n\cdot atk+add\cdot\frac{n(n+1)}{2} , \text{min}=b\cdot atk</span><span><span><span></span><span><span>max</span></span><span></span><span>=</span><span></span></span><span><span></span><span>n</span><span></span><span>⋅</span><span></span></span><span><span></span><span>a</span><span>t</span><span>k</span><span></span><span>+</span><span></span></span><span><span></span><span>a</span><span>dd</span><span></span><span>⋅</span><span></span></span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span>2</span></span></span><span><span></span><span></span></span><span><span></span><span><span>n</span><span>(</span><span>n</span><span></span><span>+</span><span></span><span>1</span><span>)</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span>,</span><span></span><span><span>min</span></span><span></span><span>=</span><span></span></span><span><span></span><span>b</span><span></span><span>⋅</span><span></span></span><span><span></span><span>a</span><span>t</span><span>k</span></span></span></span></span><p>可知，数据会爆 int，但不会爆 long long，所以需要使用64位 long long 进行存储。</p><p>时间复杂度：<span><span>O(n)\mathcal{O}(n)</span><span><span><span></span><span>O</span><span>(</span><span>n</span><span>)</span></span></span></span>。</p></section><section><h3>Std2<a href="#std2-1"><span>#</span></a></h3><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>void</span><span> </span><span>slu</span><span>() {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>int</span><span> atk, add, n, a, b;</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>cin </span><span>&gt;&gt;</span><span> atk </span><span>&gt;&gt;</span><span> add </span><span>&gt;&gt;</span><span> n </span><span>&gt;&gt;</span><span> a </span><span>&gt;&gt;</span><span> b;</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>{</span></div></div><div><div><div>6</div></div><div><span>    </span><span>int</span><span> w </span><span>=</span><span> atk, ans </span><span>=</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>7</div></div><div><span>    </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>0</span><span>; i </span><span>&lt;</span><span> b; i</span><span>++</span><span>) {</span></div></div><div><div><div>8</div></div><div><span><span>      </span></span><span>ans </span><span>+=</span><span> w;</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>cout </span><span>&lt;&lt;</span><span> ans </span><span>&lt;&lt;</span><span> </span><span>' '</span><span>;</span></div></div><div><div><div>11</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span><span>  </span></span><span>{</span></div></div><div><div><div>14</div></div><div><span>    </span><span>int</span><span> w </span><span>=</span><span> atk, ans </span><span>=</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>15</div></div><div><span>    </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>0</span><span>; i </span><span>&lt;</span><span> n; i</span><span>++</span><span>) {</span></div></div><div><div><div>16</div></div><div><span><span>      </span></span><span>w </span><span>+=</span><span> add;</span></div></div><div><div><div>17</div></div><div><span><span>      </span></span><span>ans </span><span>+=</span><span> w;</span></div></div><div><div><div>18</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>19</div></div><div><span><span>    </span></span><span>cout </span><span>&lt;&lt;</span><span> ans </span><span>&lt;&lt;</span><span> endl;</span></div></div><div><div><div>20</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>21</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section></section><section><h2>G hc_orz<a href="#g-hc_orz"><span>#</span></a></h2><blockquote><p>Problem tags: 后缀和</p></blockquote><p>Prediction Difficulty：Medium</p><p>Actual Difficulty：Medium-Hard</p><section><h3>Problem Statement<a href="#problem-statement-6"><span>#</span></a></h3><p>找出每个 <span><span>hchc</span><span><span><span></span><span>h</span><span>c</span></span></span></span> 右边有多少 <span><span>orzorz</span><span><span><span></span><span>or</span><span>z</span></span></span></span> 出现的总和。</p></section><section><h3>Solution<a href="#solution-4"><span>#</span></a></h3><p>若考虑暴力求每个 <span><span>hchc</span><span><span><span></span><span>h</span><span>c</span></span></span></span> 能匹配的个数，时间复杂度为  <span><span>O(n2)\mathcal{O}(n^2)</span><span><span><span></span><span>O</span><span>(</span><span><span>n</span><span><span><span><span><span><span></span><span><span>2</span></span></span></span></span></span></span></span><span>)</span></span></span></span> ，会超时。</p><p>我们可以从后往前用一个 <span><span>cntcnt</span><span><span><span></span><span>c</span><span>n</span><span>t</span></span></span></span> 计算 <span><span>orzorz</span><span><span><span></span><span>or</span><span>z</span></span></span></span> 的数量，每次碰到 <span><span>hchc</span><span><span><span></span><span>h</span><span>c</span></span></span></span> 就加一次 <span><span>cntcnt</span><span><span><span></span><span>c</span><span>n</span><span>t</span></span></span></span>，也就是此位置 <span><span>hchc</span><span><span><span></span><span>h</span><span>c</span></span></span></span> 能匹配的 <span><span>orzorz</span><span><span><span></span><span>or</span><span>z</span></span></span></span> 数量。时间复杂度 <span><span>O(n)\mathcal{O}(n)</span><span><span><span></span><span>O</span><span>(</span><span>n</span><span>)</span></span></span></span>。</p></section><section><h3>Std<a href="#std-4"><span>#</span></a></h3><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>const</span><span> </span><span>int</span><span> N</span><span>=</span><span>1</span><span>e</span><span>6</span><span>+</span><span>7</span><span>;</span></div></div><div><div><div>2</div></div><div><span>int</span><span> n;</span></div></div><div><div><div>3</div></div><div><span>string</span><span> s;</span></div></div><div><div><div>4</div></div><div><span>int</span><span> a[N],m;</span><span> // 用于记录 hc和 orz的位置</span></div></div><div><div><div>5</div></div><div><span>int</span><span> suf;</span><span> // 记录后缀的 orz数量</span></div></div><div><div><div>6</div></div><div><span>void</span><span> </span><span>solve</span><span>()</span></div></div><div><div><div>7</div></div><div><span>{</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>cin</span><span>&gt;&gt;</span><span>n;</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>cin</span><span>&gt;&gt;</span><span>s;</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>s</span><span>=</span><span>' '</span><span>+</span><span>s;</span><span> // 保证字符串从 1开始遍历</span></div></div><div><div><div>11</div></div><div><span><span>    </span></span><span>suf</span><span>=</span><span>m</span><span>=</span><span>0</span><span>;</span></div></div><div><div><div>12</div></div><div><span>    </span><span>for</span><span>(</span><span>int</span><span> i</span><span>=</span><span>1</span><span>;i</span><span>&lt;=</span><span>n;i</span><span>++</span><span>)</span></div></div><div><div><div>13</div></div><div><span><span>    </span></span><span>{</span></div></div><div><div><div>14</div></div><div><span>        </span><span>if</span><span>(i</span><span>+</span><span>1</span><span>&lt;=</span><span>n</span><span>&amp;&amp;</span><span>s[i]</span><span>==</span><span>'h'</span><span>&amp;&amp;</span><span>s[i</span><span>+</span><span>1</span><span>]</span><span>==</span><span>'c'</span><span>) a[</span><span>++</span><span>m]</span><span>=</span><span>i;</span></div></div><div><div><div>15</div></div><div><span>        </span><span>if</span><span>(i</span><span>+</span><span>2</span><span>&lt;=</span><span>n</span><span>&amp;&amp;</span><span>s[i]</span><span>==</span><span>'o'</span><span>&amp;&amp;</span><span>s[i</span><span>+</span><span>1</span><span>]</span><span>==</span><span>'r'</span><span>&amp;&amp;</span><span>s[i</span><span>+</span><span>2</span><span>]</span><span>==</span><span>'z'</span><span>) a[</span><span>++</span><span>m]</span><span>=</span><span>i;</span></div></div><div><div><div>16</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>17</div></div><div>
</div></div><div><div><div>18</div></div><div><span>    </span><span>int</span><span> ans</span><span>=</span><span>0</span><span>;</span></div></div><div><div><div>19</div></div><div><span>    </span><span>for</span><span>(</span><span>int</span><span> i</span><span>=</span><span>m;i</span><span>&gt;=</span><span>1</span><span>;i</span><span>--</span><span>)</span></div></div><div><div><div>20</div></div><div><span><span>    </span></span><span>{</span></div></div><div><div><div>21</div></div><div><span>        </span><span>if</span><span>(s[a[i]]</span><span>==</span><span>'o'</span><span>) suf</span><span>++</span><span>;</span></div></div><div><div><div>22</div></div><div><span>        </span><span>else</span><span> ans</span><span>+=</span><span>suf;</span></div></div><div><div><div>23</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>24</div></div><div><span><span>    </span></span><span>cout</span><span>&lt;&lt;</span><span>ans</span><span>&lt;&lt;</span><span>endl;</span></div></div><div><div><div>25</div></div><div><span>}</span></div></div><div><div><div>26</div></div><div><span>signed</span><span> </span><span>main</span><span>()</span></div></div><div><div><div>27</div></div><div><span>{</span></div></div><div><div><div>28</div></div><div><span>    </span><span>ios</span><span>::</span><span>sync_with_stdio</span><span>(</span><span>0</span><span>);cin.</span><span>tie</span><span>(</span><span>0</span><span>);cout.</span><span>tie</span><span>(</span><span>0</span><span>);</span></div></div><div><div><div>29</div></div><div><span>    </span><span>int</span><span> T</span><span>=</span><span>1</span><span>; cin</span><span>&gt;&gt;</span><span>T;</span></div></div><div><div><div>30</div></div><div><span>    </span><span>while</span><span>(T</span><span>--</span><span>){</span><span>solve</span><span>();}</span></div></div><div><div><div>31</div></div><div><span>    </span><span>return</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>32</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section></section><section><h2>H pre and suf<a href="#h-pre-and-suf"><span>#</span></a></h2><blockquote><p>Problem tags: 暴力枚举</p></blockquote><p>Prediction Difficulty：Easy-Medium</p><p>Actual Difficulty：Medium-Hard</p><section><h3>Problem Statement<a href="#problem-statement-7"><span>#</span></a></h3><p>给定一个数组 <span><span>aa</span><span><span><span></span><span>a</span></span></span></span>，与询问的 <span><span>bb</span><span><span><span></span><span>b</span></span></span></span> 数组拼接在一起，找出拼接后的最大公共前后缀。</p></section><section><h3>Solution<a href="#solution-5"><span>#</span></a></h3><p>设拼接后的数组为 <span><span>cc</span><span><span><span></span><span>c</span></span></span></span>，不管是长度为多少的最大公共前后缀，我们都要从前往后遍历 <span><span>cc</span><span><span><span></span><span>c</span></span></span></span> 数组，找出最大的 <span><span>ll</span><span><span><span></span><span>l</span></span></span></span> 满足 <span><span>c[l]=c[2n−l+1]c[l] = c[2n-l+1]</span><span><span><span></span><span>c</span><span>[</span><span>l</span><span>]</span><span></span><span>=</span><span></span></span><span><span></span><span>c</span><span>[</span><span>2</span><span>n</span><span></span><span>−</span><span></span></span><span><span></span><span>l</span><span></span><span>+</span><span></span></span><span><span></span><span>1</span><span>]</span></span></span></span>，此时 <span><span>ll</span><span><span><span></span><span>l</span></span></span></span> 则为答案。
因此遍历一遍 <span><span>cc</span><span><span><span></span><span>c</span></span></span></span> 数组即可，时间复杂度 <span><span>O(n)\mathcal{O}(n)</span><span><span><span></span><span>O</span><span>(</span><span>n</span><span>)</span></span></span></span>。</p></section><section><h3>Std<a href="#std-5"><span>#</span></a></h3><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>const</span><span> </span><span>int</span><span> N</span><span>=</span><span>1</span><span>e</span><span>6</span><span>+</span><span>7</span><span>;</span></div></div><div><div><div>2</div></div><div><span>int</span><span> n,q;</span></div></div><div><div><div>3</div></div><div><span>int</span><span> a[N];</span></div></div><div><div><div>4</div></div><div><span>void</span><span> </span><span>solve</span><span>()</span></div></div><div><div><div>5</div></div><div><span>{</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>cin</span><span>&gt;&gt;</span><span>n</span><span>&gt;&gt;</span><span>q;</span></div></div><div><div><div>7</div></div><div><span>  </span><span>for</span><span>(</span><span>int</span><span> i</span><span>=</span><span>1</span><span>;i</span><span>&lt;=</span><span>n;i</span><span>++</span><span>) cin</span><span>&gt;&gt;</span><span>a[i];</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>  </span><span>while</span><span>(q</span><span>--</span><span>)</span></div></div><div><div><div>10</div></div><div><span><span>  </span></span><span>{</span></div></div><div><div><div>11</div></div><div><span>    </span><span>for</span><span>(</span><span>int</span><span> i</span><span>=</span><span>n</span><span>+</span><span>1</span><span>;i</span><span>&lt;=</span><span>2</span><span>*</span><span>n;i</span><span>++</span><span>) cin</span><span>&gt;&gt;</span><span>a[i];</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span>    </span><span>int</span><span> l</span><span>=</span><span>0</span><span>;</span></div></div><div><div><div>14</div></div><div><span>    </span><span>while</span><span>(a[l</span><span>+</span><span>1</span><span>]</span><span>==</span><span>a[</span><span>2</span><span>*</span><span>n</span><span>-</span><span>l]</span><span>&amp;&amp;</span><span>l</span><span>+</span><span>1</span><span>&lt;=</span><span>2</span><span>*</span><span>n) l</span><span>++</span><span>;</span></div></div><div><div><div>15</div></div><div><span><span>    </span></span><span>cout</span><span>&lt;&lt;</span><span>l</span><span>&lt;&lt;</span><span>endl;</span></div></div><div><div><div>16</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>17</div></div><div><span>}</span></div></div><div><div><div>18</div></div><div><span>signed</span><span> </span><span>main</span><span>()</span></div></div><div><div><div>19</div></div><div><span>{</span></div></div><div><div><div>20</div></div><div><span>  </span><span>ios</span><span>::</span><span>sync_with_stdio</span><span>(</span><span>0</span><span>);cin.</span><span>tie</span><span>(</span><span>0</span><span>);cout.</span><span>tie</span><span>(</span><span>0</span><span>);</span></div></div><div><div><div>21</div></div><div><span>  </span><span>int</span><span> T</span><span>=</span><span>1</span><span>; cin</span><span>&gt;&gt;</span><span>T;</span></div></div><div><div><div>22</div></div><div><span>  </span><span>while</span><span>(T</span><span>--</span><span>){</span><span>solve</span><span>();}</span></div></div><div><div><div>23</div></div><div><span>  </span><span>return</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>24</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section></section><section><h2>致谢名单<a href="#致谢名单"><span>#</span></a></h2><ul>
<li>
<p>Author：</p>
<p><span>C</span><span>henzq7</span>, <span>h</span><span>uangce</span>, <span>L</span><span>ANSGANBS</span></p>
</li>
<li>
<p>Tester：</p>
<p><span>D</span><span>KXTL</span>, <span>l</span><span>ry666</span>, <span>s</span><span>kycheng</span>, <span>s</span><span>uroooo</span>, <span>y</span><span>ihang_01</span></p>
</li>
</ul><div><div><div></div><div>Note</div></div><div><p>排名按照字典序排序。</p></div></div><p>非常感谢各位 Tester 对本场比赛出题的帮助！</p></section></section>]]></content>
    <author><name>Zowely</name></author>
    <category term="算法竞赛" />
    <category term="算法竞赛" />
    <category term="题解" />
    <category term="C++" />
  </entry>
  <entry>
    <title>贪心算法</title>
    <link href="https://www.lansganbs.cn/posts/%E7%AE%97%E6%B3%95%E7%AB%9E%E8%B5%9B/%E8%B4%AA%E5%BF%83/" rel="alternate" type="text/html" />
    <id>https://www.lansganbs.cn/posts/%E7%AE%97%E6%B3%95%E7%AB%9E%E8%B5%9B/%E8%B4%AA%E5%BF%83/</id>
    <published>2025-08-30T00:00:00.000Z</published>
    <updated>2025-10-03T22:28:14.000Z</updated>
    <summary>2025秋哈尔滨理工大学新生第7周第二次算法培训</summary>
    <content type="html"><![CDATA[<section><h1>贪心算法<a href="#贪心算法"><span>#</span></a></h1><section><h2>0. 核心思想<a href="#0-核心思想"><span>#</span></a></h2><section><h3>什么是贪心？<a href="#什么是贪心"><span>#</span></a></h3><p>贪心算法（英语：greedy algorithm），是用计算机来模拟一个「贪心」的人做出决策的过程。这个人十分贪婪，每一步行动总是按某种指标选取最优的操作。而且他目光短浅，总是只看眼前，并不考虑以后可能造成的影响。</p><p>可想而知，并不是所有的时候贪心法都能获得最优解，所以一般使用贪心法的时候，都要确保自己能证明其正确性。</p></section><section><h3>贪心为什么能成功？<a href="#贪心为什么能成功"><span>#</span></a></h3><p>贪心算法在有最优子结构的问题中尤为有效。最优子结构的意思是问题能够分解成子问题来解决，子问题的最优解能递推到最终问题的最优解。</p><p>贪心算法之所以能在某些问题上得到最优解，是因为这些问题恰好具有两个关键性质：</p><ol>
<li>
<p><strong>贪心选择性质 (Greedy Choice Property)</strong>
这听起来很专业，但意思很简单：<strong>你当前做出的局部最优选择，一定也包含在全局最优解里</strong>。换句话说，你的每一步“贪心”都不会让你走上歪路，它确实是通往最终正确答案的一步。你不必“后悔”之前的选择。</p>
</li>
<li>
<p><strong>最优子结构 (Optimal Substructure)</strong>
这个性质意味着：<strong>当你做出一个贪心选择后，剩下的问题，仍然是同类型的、规模更小的问题</strong>。
例如，在区间问题中，你选了一个区间后，剩下的问题就变成了“在更少的、符合条件的区间里，继续做选择”，问题的本质没有变。</p>
</li>
</ol></section><section><h3>我们怎么想出贪心策略？<a href="#我们怎么想出贪心策略"><span>#</span></a></h3><p>在做题时，我们一般遵循两步：</p><ol>
<li>
<p><strong>猜</strong>：</p>
<ul>
<li>分析问题，寻找一种简单的、局部的最优衡量标准。</li>
<li>“怎样才算‘最好’？”是“结束最早”？“价值最高”？“耗时最短”？</li>
<li>这个标准通常就是<strong>排序的依据</strong>。例如，看到“最多”、“最少”、“最大”等字眼，就可以思考是不是可以按某个属性排序来贪心。</li>
</ul>
</li>
<li>
<p><strong>证</strong>：</p>
<ul>
<li>在脑海里简单地验证一下你的猜想。最常用的方法是<strong>反证法</strong>或<strong>交换论证</strong>。</li>
<li>如果不按这个规则来，会怎么样？</li>
<li>例如，在排队接水问题中，如果你想证明“时间短的优先”是正确的，就可以想：“如果有一个时间长的人排在了时间短的人前面，我把他们俩换一下位置，总等待时间是变长了还是变短了？” 如果交换后结果更优，就说明不符合你规则的情况肯定不是最优解。</li>
</ul>
</li>
</ol><hr /></section></section><section><h2>1. 经典贪心模型<a href="#1-经典贪心模型"><span>#</span></a></h2><section><h3>1.1 区间不相交：选出最多不重叠的区间<a href="#11-区间不相交选出最多不重叠的区间"><span>#</span></a></h3><ul>
<li><strong>问题</strong>：给你很多区间，选出尽可能多的区间，让它们互不重叠。</li>
<li><strong>贪心策略</strong>：<strong>按结束时间升序排序</strong>，优先选结束早的。</li>
<li><strong>思路</strong>：为了给后面的区间留下尽可能多的空间，我们当然应该选一个能“最快完事”的区间。</li>
<li><strong>实现步骤</strong>：
<ol>
<li>将所有区间按结束时间 <code>r</code> 从小到大排序。</li>
<li>初始化已选区间的结束时间 <code>last_end = -∞</code>。</li>
<li>遍历排序后的区间，如果当前区间的开始时间 <code>l &gt;= last_end</code>，就选中它，并更新 <code>last_end</code>。</li>
</ol>
</li>
</ul><hr /></section><section><h3>1.2 区间覆盖：用最少区间覆盖目标范围 <code>[S, T]</code><a href="#12-区间覆盖用最少区间覆盖目标范围-s-t"><span>#</span></a></h3><ul>
<li><strong>问题</strong>：用最少的区间，把目标范围 <code>[S, T]</code> 完全盖住。</li>
<li><strong>贪心策略</strong>：从当前位置出发，选择一个能<strong>让你延伸到最远</strong>的区间。</li>
<li><strong>思路</strong>：每一步都想“迈得最远”，这样总步数自然就少了。</li>
<li><strong>实现步骤</strong>：
<ol>
<li>将所有区间按开始时间 <code>l</code> 从小到大排序。</li>
<li>设当前已覆盖的右边界为 <code>cur</code>（初始为 <code>S</code>）。</li>
<li>在所有能覆盖 <code>cur</code> 的区间里（即 <code>l &lt;= cur</code>），找到那个右端点 <code>r</code> 最大的区间。</li>
<li>用这个最大的 <code>r</code> 更新 <code>cur</code>，重复此过程直到 <code>cur &gt;= T</code>。</li>
</ol>
</li>
</ul><hr /></section><section><h3>1.3 部分背包：物品可分割，求最大价值<a href="#13-部分背包物品可分割求最大价值"><span>#</span></a></h3><ul>
<li><strong>问题</strong>：背包容量有限，物品可以只拿一部分，怎么拿价值最高？</li>
<li><strong>贪心策略</strong>：<strong>按性价比（单位重量的价值 <span><span>v/wv/w</span><span><span><span></span><span>v</span><span>/</span><span>w</span></span></span></span>）降序</strong>拿。</li>
<li><strong>思路</strong>：当然是先拿最“值钱”的东西，每一寸空间都要用在刀刃上。</li>
<li><strong>实现步骤</strong>：
<ol>
<li>计算所有物品的性价比 <code>v/w</code>。</li>
<li>按性价比从高到低排序。</li>
<li>依次将物品装入背包，如果装不下整个，就装一部分直到背包装满。</li>
</ol>
</li>
</ul><hr /></section><section><h3>1.4 排队接水：如何让总等待时间最短<a href="#14-排队接水如何让总等待时间最短"><span>#</span></a></h3><ul>
<li><strong>问题</strong>：<span><span>nn</span><span><span><span></span><span>n</span></span></span></span> 个人接水，用时不同，怎么排队大家等的总时间最少？</li>
<li><strong>贪心策略</strong>：<strong>用时短的人排在前面</strong> (Shortest Processing Time First)。</li>
<li><strong>思路</strong>：让耗时久的人排在后面，可以减少他影响到的人数。排在前面的人，会影响到后面所有人的等待时间，所以这个位置要留给“贡献”等待时间最少的人。</li>
</ul><hr /></section><section><h3>1.5 纪念品分组：两人一组，限重，求最少组数<a href="#15-纪念品分组两人一组限重求最少组数"><span>#</span></a></h3><ul>
<li><strong>问题</strong>：<span><span>nn</span><span><span><span></span><span>n</span></span></span></span> 件物品，每组最多两人，有重量上限，求最少组数。</li>
<li><strong>贪心策略</strong>：<strong>排序后，让最重的和最轻的尝试配对</strong>。</li>
<li><strong>思路</strong>：最重的人是最“难搞”的，我们优先处理他。如果连最轻的都不能和他配对，那别人更不行了。所以，要么他和最轻的配对，要么他自己一组。</li>
<li><strong>实现步骤</strong>：
<ol>
<li>按重量排序。</li>
<li>用双指针 <code>i</code> 指向最轻，<code>j</code> 指向最重。</li>
<li>若 <code>w[i] + w[j] &lt;= limit</code>，则配对成功，<code>i</code> 和 <code>j</code> 都移动。</li>
<li>否则，最重的 <code>w[j]</code> 只能单独一组，<code>j</code> 移动。</li>
</ol>
</li>
</ul><hr /></section><section><h3>1.6 合并果子：每次合并两堆，求最小成本<a href="#16-合并果子每次合并两堆求最小成本"><span>#</span></a></h3><ul>
<li><strong>问题</strong>：每次合并两堆，成本是两堆重量和。求合并成一堆的最小总成本。</li>
<li><strong>贪心策略</strong>：<strong>每次都合并当前最小的两堆</strong>。</li>
<li><strong>思路</strong>：这和哈夫曼树的思想一样。重量越大的果子，越希望它被加的次数少。让小的数在底层被反复加，大的数在顶层最后加，总和才最小。</li>
<li><strong>实现步骤</strong>：用一个小根堆（优先队列）来维护所有果子堆，每次取出两个最小的，合并后再放回去。</li>
</ul><hr /></section><section><h3>1.7 删数问题：删 <span><span>kk</span><span><span><span></span><span>k</span></span></span></span> 个数，使剩余的数最小<a href="#17-删数问题删-kkk-个数使剩余的数最小"><span>#</span></a></h3><ul>
<li><strong>问题</strong>：从一个数字字符串中删掉 <span><span>kk</span><span><span><span></span><span>k</span></span></span></span> 个字符，使结果最小。</li>
<li><strong>贪心策略</strong>：维护一个<strong>单调递增</strong>的序列。</li>
<li><strong>思路</strong>：要让数字小，就要让高位的数字尽可能小。从左到右遍历，如果发现当前数字比前面的数字小，就应该把前面的大数删掉，换上这个小数。</li>
<li><strong>实现步骤</strong>：用栈。如果当前数字比栈顶小且还能删（<span><span>k&gt;0k&gt;0</span><span><span><span></span><span>k</span><span></span><span>&gt;</span><span></span></span><span><span></span><span>0</span></span></span></span>），就弹栈。最后，如果 <span><span>kk</span><span><span><span></span><span>k</span></span></span></span> 没用完，从尾部删。</li>
</ul><hr /></section><section><h3>1.8 货仓选址：找一个点，到所有点的距离和最小<a href="#18-货仓选址找一个点到所有点的距离和最小"><span>#</span></a></h3><ul>
<li><strong>问题</strong>：数轴上很多点，找一个仓库位置，让所有点到仓库的距离总和最小。</li>
<li><strong>贪心策略</strong>：<strong>选在中位数</strong>。</li>
<li><strong>思路</strong>：中位数是平衡点。仓库位置偏左，右边的点会把它“往右拉”；偏右，左边的点会把它“往左拉”。只有在中位数，两边的“拉力”才均衡。</li>
</ul><hr /></section></section><section><h2>2. 总结<a href="#2-总结"><span>#</span></a></h2><ul>
<li>贪心的本质是<strong>寻找一个简单的、确定的规则</strong>，来简化复杂决策。</li>
<li>常见的贪心套路往往伴随着<strong>排序</strong>。</li>
<li>想不清楚时，<strong>举几个例子</strong>模拟一下贪心过程，或者想想<strong>如果不这么选，会不会更差</strong>。</li>
</ul></section></section>]]></content>
    <author><name>Zowely</name></author>
    <category term="算法竞赛" />
    <category term="算法竞赛" />
    <category term="算法教程" />
    <category term="C++" />
  </entry>
  <entry>
    <title>模拟与暴力枚举</title>
    <link href="https://www.lansganbs.cn/posts/%E7%AE%97%E6%B3%95%E7%AB%9E%E8%B5%9B/%E6%A8%A1%E6%8B%9F%E4%B8%8E%E6%9A%B4%E5%8A%9B%E6%9E%9A%E4%B8%BE/" rel="alternate" type="text/html" />
    <id>https://www.lansganbs.cn/posts/%E7%AE%97%E6%B3%95%E7%AB%9E%E8%B5%9B/%E6%A8%A1%E6%8B%9F%E4%B8%8E%E6%9A%B4%E5%8A%9B%E6%9E%9A%E4%B8%BE/</id>
    <published>2025-08-28T00:00:00.000Z</published>
    <updated>2025-10-16T16:24:49.000Z</updated>
    <summary>2025秋哈尔滨理工大学新生第7周第一次算法培训</summary>
    <content type="html"><![CDATA[<section><h1>模拟与暴力枚举<a href="#模拟与暴力枚举"><span>#</span></a></h1><section><h2>0. 引入<a href="#0-引入"><span>#</span></a></h2><p>模拟就是用计算机来模拟题目中要求的操作。</p><p>模拟题目通常具有码量大、操作多、思路繁复的特点。由于它码量大，经常会出现难以查错的情况，如果在考试中写错是相当浪费时间的。</p><hr /></section><section><h2>1. 技巧<a href="#1-技巧"><span>#</span></a></h2><p>写模拟题时，遵循以下的建议有可能会提升做题速度：</p><ul>
<li>在动手写代码之前，在草纸上尽可能地写好要实现的流程。</li>
<li>在代码中，尽量把每个部分模块化，写成函数、结构体或类。</li>
<li>对于一些可能重复用到的概念，可以统一转化，方便处理：如，某题给你 “YY-MM-DD 时：分” 把它抽取到一个函数，处理成秒，会减少概念混淆。</li>
<li>调试时分块调试。模块化的好处就是可以方便的单独调某一部分。</li>
<li>写代码的时候一定要思路清晰，不要想到什么写什么，要按照落在纸上的步骤写。</li>
</ul><p>实际上，上述步骤在解决其它类型的题目时也是很有帮助的。</p></section><section><h2>2. 一些简单的模拟<a href="#2-一些简单的模拟"><span>#</span></a></h2><ol>
<li>枚举无序对 (i,j)，i &lt; j，避免重复</li>
</ol><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>0</span><span>; i </span><span>&lt;</span><span> n; </span><span>++</span><span>i)</span></div></div><div><div><div>2</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> j </span><span>=</span><span> i </span><span>+</span><span> </span><span>1</span><span>; j </span><span>&lt;</span><span> n; </span><span>++</span><span>j)</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>// 用 a[i], a[j]</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ol>
<li>枚举三元组 (i,j,k)，i &lt; j &lt; k</li>
</ol><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>0</span><span>; i </span><span>&lt;</span><span> n; </span><span>++</span><span>i)</span></div></div><div><div><div>2</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> j </span><span>=</span><span> i </span><span>+</span><span> </span><span>1</span><span>; j </span><span>&lt;</span><span> n; </span><span>++</span><span>j)</span></div></div><div><div><div>3</div></div><div><span>    </span><span>for</span><span> (</span><span>int</span><span> k </span><span>=</span><span> j </span><span>+</span><span> </span><span>1</span><span>; k </span><span>&lt;</span><span> n; </span><span>++</span><span>k)</span></div></div><div><div><div>4</div></div><div><span><span>      </span></span><span>// 用 a[i], a[j], a[k]</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ol>
<li>子集枚举（n ≤ 20 常用）</li>
</ol><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>for</span><span> (</span><span>int</span><span> mask </span><span>=</span><span> </span><span>0</span><span>; mask </span><span>&lt;</span><span> (</span><span>1</span><span> </span><span>&lt;&lt;</span><span> n); </span><span>++</span><span>mask) {</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>// if (__builtin_popcount((unsigned)mask) == k) ...</span></div></div><div><div><div>3</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>0</span><span>; i </span><span>&lt;</span><span> n; </span><span>++</span><span>i)</span></div></div><div><div><div>4</div></div><div><span>    </span><span>if</span><span> (mask </span><span>&gt;&gt;</span><span> i </span><span>&amp;</span><span> </span><span>1</span><span>) {</span><span> /* 选了第 i 个 */</span><span> }</span></div></div><div><div><div>5</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ol>
<li>全排列（next_permutation）</li>
</ol><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>vector</span><span>&lt;int&gt;</span><span> p </span><span>=</span><span> {</span><span>1</span><span>,</span><span>2</span><span>,</span><span>3</span><span>,</span><span>4</span><span>,</span><span>5</span><span>};</span></div></div><div><div><div>2</div></div><div><span>do</span><span> {</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>// 使用当前排列 p</span></div></div><div><div><div>4</div></div><div><span>} </span><span>while</span><span> (</span><span>next_permutation</span><span>(p.</span><span>begin</span><span>(), p.</span><span>end</span><span>()));</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section><section><h2>3. 基础例子<a href="#3-基础例子"><span>#</span></a></h2><section><h3>3.1 P1909《买铅笔》（枚举三种方案）<a href="#31-p1909买铅笔枚举三种方案"><span>#</span></a></h3><p>题意：买 n 支铅笔，给出 3 种“每包数量+价格”，问最小花费。</p><p>思路：每种方案都算一次 ceil(n / pack) × price，取 min。</p><hr /></section><section><h3>3.2 P1047《校门外的树》（差分优化的模拟）<a href="#32-p1047校门外的树差分优化的模拟"><span>#</span></a></h3><p>题意：路段 [0..L] 初始有树；给出 M 个区间要“砍掉”，问剩余多少棵。</p><p>方法一（朴素）：bool 标记 + 区间打掉，O(L×M)，很慢。</p><p>方法二（差分）：对每个 [l, r] 做 diff[l]++, diff[r + 1]—，最后前缀和&gt;0 表示被砍。</p><hr /></section><section><h3>3.3 P1008《三连击》（全排列枚举）<a href="#33-p1008三连击全排列枚举"><span>#</span></a></h3><p>题意：找出 3 个三位数 A、B、C，使得 A:B = 1:2&lt;3&gt;，且 1..9 每个数字恰好出现一次。</p><p>思路：对 1..9 的全排列，每 3 位拼一个数，检查比例。</p><hr /></section><section><h3>3.4 P1042《乒乓球》（字符串模拟）<a href="#34-p1042乒乓球字符串模拟"><span>#</span></a></h3><p>题意：输入一串 ‘W’/‘L’/‘E’，输出 11 分制与 21 分制两套比分。</p><p>做法：遍历字符，遇 E 结束，胜负分到达 limit 且相差 ≥2 就结算一局。</p><hr /></section><section><h3>3.5 P1980《计数问题》（枚举数位 + 进阶做法）<a href="#35-p1980计数问题枚举数位--进阶做法"><span>#</span></a></h3><p>题意：统计 1..n 中数字 x 出现了多少次。</p><p>做法 A：朴素枚举每个数的每一位（n×位数）。</p><p>做法 B（进阶，推荐）：逐位统计（高位/当前位/低位法，<span><span>O(logn)O(log n)</span><span><span><span></span><span>O</span><span>(</span><span>l</span><span>o</span><span>g</span><span>n</span><span>)</span></span></span></span>）</p><p>要点：0 的处理与前导零有关，按题意选择写法。</p><hr /></section><section><h3>3.6 P1328《生活大爆炸版石头剪刀布》（规则模拟）<a href="#36-p1328生活大爆炸版石头剪刀布规则模拟"><span>#</span></a></h3><p>题意：两人出拳模式循环给出，进行 n 次对决，规则是 5 种手势的胜负表。<br />
做法：预先写好胜负矩阵 win[i][j]，轮流取序列 a[i%la], b[i%lb] 比较。</p><hr /></section></section><section><h2>4. 进阶枚举技巧<a href="#4-进阶枚举技巧"><span>#</span></a></h2><section><h3>4.1 去重与剪枝<a href="#41-去重与剪枝"><span>#</span></a></h3><ul>
<li>两元对去重：强制 i &lt; j；</li>
<li>三元组去重：i &lt; j &lt; k；或排序后跳过相同值；</li>
<li>非降约束：枚举划分/组合时，下一步从“当前选择及之后”开始；</li>
<li>上下界剪枝：已超出目标就提前 break；</li>
<li>对称剪枝：如果方案与镜像等价，固定一侧。</li>
</ul><p>示例：枚举三元组满足 a[i]+a[j]+a[k]=T, <span><span>O(n3)O(n^3)</span><span><span><span></span><span>O</span><span>(</span><span><span>n</span><span><span><span><span><span><span></span><span><span>3</span></span></span></span></span></span></span></span><span>)</span></span></span></span></p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>0</span><span>; i </span><span>&lt;</span><span> n; </span><span>++</span><span>i)</span></div></div><div><div><div>2</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> j </span><span>=</span><span> i </span><span>+</span><span> </span><span>1</span><span>; j </span><span>&lt;</span><span> n; </span><span>++</span><span>j)</span></div></div><div><div><div>3</div></div><div><span>    </span><span>for</span><span> (</span><span>int</span><span> k </span><span>=</span><span> j </span><span>+</span><span> </span><span>1</span><span>; k </span><span>&lt;</span><span> n; </span><span>++</span><span>k)</span></div></div><div><div><div>4</div></div><div><span>      </span><span>if</span><span> (a[i] </span><span>+</span><span> a[j] </span><span>+</span><span> a[k] </span><span>==</span><span> T) </span><span>++</span><span>ans;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>可剪枝</p><ul>
<li>提前排序后，若最小可能和 &gt; T 或最大可能和 &lt; T，则可 break / continue；</li>
<li>固定 i, j，二分或双指针找 k。</li>
</ul></section><section><h3>4.2 子集枚举的两种写法<a href="#42-子集枚举的两种写法"><span>#</span></a></h3><ul>
<li>位掩码（通用）</li>
<li>递归/回溯（可加剪枝）</li>
</ul><p>位掩码统计子集和为 T 的个数</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>long</span><span> </span><span>long</span><span> </span><span>count_subset_sum</span><span>(</span><span>const</span><span> </span><span>vector</span><span>&lt;</span><span>int</span><span>&gt;</span><span>&amp;</span><span> </span><span>a</span><span>, </span><span>int</span><span> </span><span>T</span><span>) {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>int</span><span> n </span><span>=</span><span> a.</span><span>size</span><span>();</span></div></div><div><div><div>3</div></div><div><span>  </span><span>long</span><span> </span><span>long</span><span> ans </span><span>=</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>4</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> mask </span><span>=</span><span> </span><span>0</span><span>; mask </span><span>&lt;</span><span> (</span><span>1</span><span> </span><span>&lt;&lt;</span><span> n); </span><span>++</span><span>mask) {</span></div></div><div><div><div>5</div></div><div><span>    </span><span>long</span><span> </span><span>long</span><span> s </span><span>=</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>6</div></div><div><span>    </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>0</span><span>; i </span><span>&lt;</span><span> n; </span><span>++</span><span>i)</span></div></div><div><div><div>7</div></div><div><span>      </span><span>if</span><span> (mask </span><span>&gt;&gt;</span><span> i </span><span>&amp;</span><span> </span><span>1</span><span>) s </span><span>+=</span><span> a[i];</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>ans </span><span>+=</span><span> (s </span><span>==</span><span> T);</span></div></div><div><div><div>9</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>10</div></div><div><span>  </span><span>return</span><span> ans;</span></div></div><div><div><div>11</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>4.3 区间/子串枚举<a href="#43-区间子串枚举"><span>#</span></a></h3><ul>
<li>所有子数组 <span><span>O(n2)O(n^2)</span><span><span><span></span><span>O</span><span>(</span><span><span>n</span><span><span><span><span><span><span></span><span><span>2</span></span></span></span></span></span></span></span><span>)</span></span></span></span>：</li>
</ul><div><figure><figcaption><span>[l..r]</span></figcaption><pre><code><div><div><div>1</div></div><div><span>for</span><span> (</span><span>int</span><span> l </span><span>=</span><span> </span><span>0</span><span>; l </span><span>&lt;</span><span> n; </span><span>++</span><span>l)</span></div></div><div><div><div>2</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> r </span><span>=</span><span> l; r </span><span>&lt;</span><span> n; </span><span>++</span><span>r)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li>所有子串 <span><span>O(n2)O(n^2)</span><span><span><span></span><span>O</span><span>(</span><span><span>n</span><span><span><span><span><span><span></span><span><span>2</span></span></span></span></span></span></span></span><span>)</span></span></span></span>：</li>
</ul><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>0</span><span>; i </span><span>&lt;</span><span> (</span><span>int</span><span>)s.</span><span>size</span><span>(); </span><span>++</span><span>i)</span></div></div><div><div><div>2</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> len </span><span>=</span><span> </span><span>1</span><span>; i </span><span>+</span><span> len </span><span>&lt;=</span><span> (</span><span>int</span><span>)s.</span><span>size</span><span>(); </span><span>++</span><span>len)</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>// s.substr(i, len)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>配合前缀和，可以 <span><span>O(1)O(1)</span><span><span><span></span><span>O</span><span>(</span><span>1</span><span>)</span></span></span></span> 求区间和，整体 <span><span>O(n2)O(n^{2})</span><span><span><span></span><span>O</span><span>(</span><span><span>n</span><span><span><span><span><span><span></span><span><span><span>2</span></span></span></span></span></span></span></span></span><span>)</span></span></span></span>。</p></section><section><h3>4.4 数位枚举与“回文/质数/进制”<a href="#44-数位枚举与回文质数进制"><span>#</span></a></h3><ul>
<li>判断质数：</li>
</ul><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>bool</span><span> </span><span>is_prime</span><span>(</span><span>long</span><span> </span><span>long</span><span> </span><span>x</span><span>) {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>if</span><span> (x </span><span>&lt;</span><span> </span><span>2</span><span>) </span><span>return</span><span> </span><span>false</span><span>;</span></div></div><div><div><div>3</div></div><div><span>  </span><span>for</span><span> (</span><span>long</span><span> </span><span>long</span><span> d </span><span>=</span><span> </span><span>2</span><span>; d </span><span>*</span><span> d </span><span>&lt;=</span><span> x; </span><span>++</span><span>d)</span></div></div><div><div><div>4</div></div><div><span>    </span><span>if</span><span> (x </span><span>%</span><span> d </span><span>==</span><span> </span><span>0</span><span>) </span><span>return</span><span> </span><span>false</span><span>;</span></div></div><div><div><div>5</div></div><div><span>  </span><span>return</span><span> </span><span>true</span><span>;</span></div></div><div><div><div>6</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li>判断回文（字符串法）：</li>
</ul><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>bool</span><span> </span><span>is_pal</span><span>(</span><span>const</span><span> </span><span>string</span><span>&amp;</span><span> </span><span>t</span><span>) {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>0</span><span>, j </span><span>=</span><span> (</span><span>int</span><span>)t.</span><span>size</span><span>()</span><span>-</span><span>1</span><span>; i </span><span>&lt;</span><span> j; </span><span>++</span><span>i, </span><span>--</span><span>j)</span></div></div><div><div><div>3</div></div><div><span>    </span><span>if</span><span> (t[i] </span><span>!=</span><span> t[j]) </span><span>return</span><span> </span><span>false</span><span>;</span></div></div><div><div><div>4</div></div><div><span>  </span><span>return</span><span> </span><span>true</span><span>;</span></div></div><div><div><div>5</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li>反转整数（去前导零）</li>
</ul><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>long</span><span> </span><span>long</span><span> </span><span>rev_num</span><span>(</span><span>long</span><span> </span><span>long</span><span> </span><span>x</span><span>) {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>long</span><span> </span><span>long</span><span> y </span><span>=</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>3</div></div><div><span>  </span><span>while</span><span> (x) { y </span><span>=</span><span> y</span><span>*</span><span>10</span><span> </span><span>+</span><span> x</span><span>%</span><span>10</span><span>; x</span><span>/=</span><span>10</span><span>; }</span></div></div><div><div><div>4</div></div><div><span>  </span><span>return</span><span> y;</span></div></div><div><div><div>5</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section></section><section><h2>5. 复杂度估算与常用优化<a href="#5-复杂度估算与常用优化"><span>#</span></a></h2><ul>
<li>粗略估算
<ul>
<li>1e7 级别操作：通常能过</li>
<li>1e8 边缘，需注意常数/IO</li>
<li>1e9 基本会超时</li>
</ul>
</li>
<li>常用优化
<ul>
<li>预处理：前缀和、差分、筛素数</li>
<li>剪枝：越界/不可能就提前 break/continue</li>
<li>IO 加速：ios::sync_with_stdio(false); cin.tie(nullptr);</li>
<li>数据结构选择：数组/静态容器优于频繁 new/delete</li>
<li>减少重复计算：把不变的量移出循环</li>
</ul>
</li>
</ul><p>例如：计数问题从 <span><span>O(nlogn)O(n log n)</span><span><span><span></span><span>O</span><span>(</span><span>n</span><span>l</span><span>o</span><span>g</span><span>n</span><span>)</span></span></span></span>（逐数拆位）降到 <span><span>O(logn)O(log n)</span><span><span><span></span><span>O</span><span>(</span><span>l</span><span>o</span><span>g</span><span>n</span><span>)</span></span></span></span>（逐位统计）。</p><hr /></section><section><h2>6. 对拍（双程序验证）<a href="#6-对拍双程序验证"><span>#</span></a></h2><section><h3>6.1 什么是对拍？<a href="#61-什么是对拍"><span>#</span></a></h3><p>在算法竞赛中，我们有时会写两个版本的程序来解决同一道题目：</p><ul>
<li><strong>暴力程序</strong>（通常写得很简单，但复杂度高，只能跑小数据）；</li>
<li><strong>优化程序</strong>（复杂度优秀，但实现复杂，容易写错）。</li>
</ul><p><strong>对拍（double check / stress test）</strong> 的思想就是：</p><ol>
<li>
<p>写一个随机数据生成器（<code>gen_input()</code>）。</p>
</li>
<li>
<p>用同一份输入分别喂给两个程序（如 <code>1.exe</code> 和 <code>2.exe</code>）。</p>
</li>
<li>
<p>比较输出是否一致。</p>
<ul>
<li>如果一致，多测几次，就能增加对优化程序正确性的信心；</li>
<li>如果不一致，就保存输入，方便 Debug。</li>
</ul>
</li>
</ol><p>这种方法在调试优化算法时非常有用，尤其是在数据范围较小，可以轻松跑完的情况下。</p><hr /></section><section><h3>6.2 基本流程<a href="#62-基本流程"><span>#</span></a></h3><p>一个对拍脚本主要分为三步：</p><ol>
<li><strong>生成输入</strong></li>
</ol><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>std</span><span>::</span><span>string</span><span> </span><span>gen_input</span><span>() {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>std</span><span>::</span><span>ostringstream</span><span> oss;</span></div></div><div><div><div>3</div></div><div><span>  </span><span>int</span><span> n </span><span>=</span><span> </span><span>randomInt</span><span>(</span><span>1</span><span>, </span><span>200</span><span>);</span></div></div><div><div><div>4</div></div><div><span><span>  </span></span><span>oss </span><span>&lt;&lt;</span><span> n </span><span>&lt;&lt;</span><span> </span><span>"</span><span>\n</span><span>"</span><span>;</span></div></div><div><div><div>5</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>0</span><span>; i </span><span>&lt;</span><span> n; </span><span>++</span><span>i) {</span></div></div><div><div><div>6</div></div><div><span>  </span><span>int</span><span> x </span><span>=</span><span> </span><span>randomInt</span><span>(</span><span>-</span><span>200</span><span>, </span><span>200</span><span>);</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>oss </span><span>&lt;&lt;</span><span> x </span><span>&lt;&lt;</span><span> </span><span>" </span><span>\n</span><span>"</span><span>[i </span><span>==</span><span> n </span><span>-</span><span> </span><span>1</span><span>];</span></div></div><div><div><div>8</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>9</div></div><div><span>  </span><span>return</span><span> oss.</span><span>str</span><span>();</span></div></div><div><div><div>10</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li>使用随机数生成器（如 <code>mt19937</code>）保证样例覆盖面广；</li>
<li>注意输入格式必须符合题目要求。</li>
</ul><ol>
<li><strong>运行并获取输出</strong></li>
</ol><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>auto</span><span> res1 </span><span>=</span><span> </span><span>run</span><span>(</span><span>"1.exe"</span><span>, input);</span></div></div><div><div><div>2</div></div><div><span>auto</span><span> res2 </span><span>=</span><span> </span><span>run</span><span>(</span><span>"2.exe"</span><span>, input);</span></div></div><div><div><div>3</div></div><div><span>std</span><span>::</span><span>string</span><span> output1 </span><span>=</span><span> res1.first;</span></div></div><div><div><div>4</div></div><div><span>std</span><span>::</span><span>string</span><span> output2 </span><span>=</span><span> res2.first;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><code>run(...)</code> 函数会写入临时输入文件，再调用系统命令运行可执行文件，并读取输出。</p><ol>
<li><strong>比较结果</strong></li>
</ol><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>if</span><span> (output1 </span><span>!=</span><span> output2) {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>std</span><span>::cout </span><span>&lt;&lt;</span><span> </span><span>"Mismatch found!</span><span>\n</span><span>"</span><span>;</span></div></div><div><div><div>3</div></div><div><span>  </span><span>std</span><span>::cout </span><span>&lt;&lt;</span><span> </span><span>"Input:</span><span>\n</span><span>"</span><span> </span><span>&lt;&lt;</span><span> input </span><span>&lt;&lt;</span><span> </span><span>'</span><span>\n</span><span>'</span><span>;</span></div></div><div><div><div>4</div></div><div><span>  </span><span>std</span><span>::cout </span><span>&lt;&lt;</span><span> </span><span>"Output1:</span><span>\n</span><span>"</span><span> </span><span>&lt;&lt;</span><span> output1;</span></div></div><div><div><div>5</div></div><div><span>  </span><span>std</span><span>::cout </span><span>&lt;&lt;</span><span> </span><span>"Output2:</span><span>\n</span><span>"</span><span> </span><span>&lt;&lt;</span><span> output2;</span></div></div><div><div><div>6</div></div><div><span>  </span><span>break</span><span>;</span></div></div><div><div><div>7</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section><section><h3>6.3 注意事项<a href="#63-注意事项"><span>#</span></a></h3><ul>
<li><strong>随机性</strong></li>
<li><strong>速度</strong></li>
<li><strong>输出一致性</strong></li>
<li><strong>定位 bug</strong></li>
</ul><hr /></section><section><h3>6.4 小结<a href="#64-小结"><span>#</span></a></h3><ul>
<li>对拍不是算法本身，而是一种<strong>调试工具</strong>；</li>
<li>它能帮助我们快速发现优化代码中的细节错误；</li>
<li>日常写题时，如果一直WA还找不出错误，可以尝试写一个暴力来对拍。</li>
</ul></section></section></section>]]></content>
    <author><name>Zowely</name></author>
    <category term="算法竞赛" />
    <category term="算法竞赛" />
    <category term="算法教程" />
    <category term="C++" />
  </entry>
  <entry>
    <title>分治思想（归并排序）</title>
    <link href="https://www.lansganbs.cn/posts/%E7%AE%97%E6%B3%95%E7%AB%9E%E8%B5%9B/%E5%88%86%E6%B2%BB%E6%80%9D%E6%83%B3%E5%BD%92%E5%B9%B6%E6%8E%92%E5%BA%8F/" rel="alternate" type="text/html" />
    <id>https://www.lansganbs.cn/posts/%E7%AE%97%E6%B3%95%E7%AB%9E%E8%B5%9B/%E5%88%86%E6%B2%BB%E6%80%9D%E6%83%B3%E5%BD%92%E5%B9%B6%E6%8E%92%E5%BA%8F/</id>
    <published>2025-08-25T00:00:00.000Z</published>
    <updated>2025-09-26T22:15:06.000Z</updated>
    <summary>2025秋哈尔滨理工大学新生第6周第二次算法培训</summary>
    <content type="html"><![CDATA[<section><h1>分治思想（归并排序）<a href="#分治思想归并排序"><span>#</span></a></h1><section><h2>0. 引入<a href="#0-引入"><span>#</span></a></h2><ul>
<li>分治（Divide and Conquer）= 分 → 治 → 合
<ul>
<li>分：把大问题拆成规模更小、形式相同的子问题；</li>
<li>治：递归地解决每个子问题；</li>
<li>合：把子问题的答案合并，得到原问题答案。</li>
</ul>
</li>
<li>归并排序是分治思想的经典案例：它的“合”步骤是把两个有序段“拉链式”合并。</li>
</ul><p>直观例子（升序）：</p><ul>
<li>把区间 <code>[l..r]</code> 二分成 <code>[l..mid]</code> 和 <code>[mid+1..r]</code>；</li>
<li>分别排好左段、右段；</li>
<li>用双指针合并这两个有序段。</li>
</ul><p>归并排序性质一览：</p><ul>
<li>时间复杂度：<span><span>O(nlogn)O(n log n)</span><span><span><span></span><span>O</span><span>(</span><span>n</span><span>l</span><span>o</span><span>g</span><span>n</span><span>)</span></span></span></span></li>
<li>额外空间：<span><span>O(n)O(n)</span><span><span><span></span><span>O</span><span>(</span><span>n</span><span>)</span></span></span></span></li>
<li>具有稳定性</li>
</ul><hr /></section><section><h2>1. 从“合并两个有序段”开始<a href="#1-从合并两个有序段开始"><span>#</span></a></h2><p>“拉链式合并”</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>// 合并两个有序数组 A, B 到 C（都升序）</span></div></div><div><div><div>2</div></div><div><span>vector</span><span>&lt;</span><span>int</span><span>&gt; </span><span>merge_two</span><span>(</span><span>const</span><span> </span><span>vector</span><span>&lt;</span><span>int</span><span>&gt;</span><span>&amp;</span><span> </span><span>A</span><span>, </span><span>const</span><span> </span><span>vector</span><span>&lt;</span><span>int</span><span>&gt;</span><span>&amp;</span><span> </span><span>B</span><span>) {</span></div></div><div><div><div>3</div></div><div><span>  </span><span>vector</span><span>&lt;</span><span>int</span><span>&gt; C;</span></div></div><div><div><div>4</div></div><div><span><span>  </span></span><span>C.</span><span>reserve</span><span>(A.</span><span>size</span><span>() </span><span>+</span><span> B.</span><span>size</span><span>());</span></div></div><div><div><div>5</div></div><div><span>  </span><span>size_t</span><span> i </span><span>=</span><span> </span><span>0</span><span>, j </span><span>=</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>6</div></div><div><span>  </span><span>while</span><span> (i </span><span>&lt;</span><span> A.</span><span>size</span><span>() </span><span>&amp;&amp;</span><span> j </span><span>&lt;</span><span> B.</span><span>size</span><span>()) {</span></div></div><div><div><div>7</div></div><div><span>    </span><span>if</span><span> (A[i] </span><span>&lt;=</span><span> B[j]) C.</span><span>push_back</span><span>(A[i</span><span>++</span><span>]);</span><span>   // 用 &lt;= 保证稳定性</span></div></div><div><div><div>8</div></div><div><span>    </span><span>else</span><span>              C.</span><span>push_back</span><span>(B[j</span><span>++</span><span>]);</span></div></div><div><div><div>9</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>10</div></div><div><span>  </span><span>while</span><span> (i </span><span>&lt;</span><span> A.</span><span>size</span><span>()) C.</span><span>push_back</span><span>(A[i</span><span>++</span><span>]);</span></div></div><div><div><div>11</div></div><div><span>  </span><span>while</span><span> (j </span><span>&lt;</span><span> B.</span><span>size</span><span>()) C.</span><span>push_back</span><span>(B[j</span><span>++</span><span>]);</span></div></div><div><div><div>12</div></div><div><span>  </span><span>return</span><span> C;</span></div></div><div><div><div>13</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section><section><h2>2. 归并排序1<a href="#2-归并排序1"><span>#</span></a></h2><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>void</span><span> </span><span>merge_sort</span><span>(</span><span>vector</span><span>&lt;</span><span>int</span><span>&gt;</span><span>&amp;</span><span> </span><span>a</span><span>, </span><span>int</span><span> </span><span>l</span><span>, </span><span>int</span><span> </span><span>r</span><span>, </span><span>vector</span><span>&lt;</span><span>int</span><span>&gt;</span><span>&amp;</span><span> </span><span>tmp</span><span>) {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>if</span><span> (l </span><span>&gt;=</span><span> r) </span><span>return</span><span>;</span></div></div><div><div><div>3</div></div><div><span>  </span><span>int</span><span> mid </span><span>=</span><span> l </span><span>+</span><span> (r </span><span>-</span><span> l) </span><span>/</span><span> </span><span>2</span><span>;</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>  </span><span>merge_sort</span><span>(a, l, mid, tmp);</span></div></div><div><div><div>6</div></div><div><span>  </span><span>merge_sort</span><span>(a, mid </span><span>+</span><span> </span><span>1</span><span>, r, tmp);</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span><span>  </span></span><span>// 小优化：如果两个段已经衔接有序，跳过合并</span></div></div><div><div><div>9</div></div><div><span>  </span><span>if</span><span> (a[mid] </span><span>&lt;=</span><span> a[mid </span><span>+</span><span> </span><span>1</span><span>]) </span><span>return</span><span>;</span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span>  </span><span>int</span><span> i </span><span>=</span><span> l, j </span><span>=</span><span> mid </span><span>+</span><span> </span><span>1</span><span>, k </span><span>=</span><span> l;</span></div></div><div><div><div>12</div></div><div><span>  </span><span>while</span><span> (i </span><span>&lt;=</span><span> mid </span><span>&amp;&amp;</span><span> j </span><span>&lt;=</span><span> r) {</span></div></div><div><div><div>13</div></div><div><span>    </span><span>if</span><span> (a[i] </span><span>&lt;=</span><span> a[j]) tmp[k</span><span>++</span><span>] </span><span>=</span><span> a[i</span><span>++</span><span>];</span><span>     // &lt;= 保证稳定</span></div></div><div><div><div>14</div></div><div><span>    </span><span>else</span><span>              tmp[k</span><span>++</span><span>] </span><span>=</span><span> a[j</span><span>++</span><span>];</span></div></div><div><div><div>15</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>16</div></div><div><span>  </span><span>while</span><span> (i </span><span>&lt;=</span><span> mid) tmp[k</span><span>++</span><span>] </span><span>=</span><span> a[i</span><span>++</span><span>];</span></div></div><div><div><div>17</div></div><div><span>  </span><span>while</span><span> (j </span><span>&lt;=</span><span> r)   tmp[k</span><span>++</span><span>] </span><span>=</span><span> a[j</span><span>++</span><span>];</span></div></div><div><div><div>18</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> x </span><span>=</span><span> l; x </span><span>&lt;=</span><span> r; </span><span>++</span><span>x) a[x] </span><span>=</span><span> tmp[x];</span></div></div><div><div><div>19</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><hr /></section><section><h2>3. 归并排序2<a href="#3-归并排序2"><span>#</span></a></h2><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>void</span><span> </span><span>merge_sort_iter</span><span>(</span><span>vector</span><span>&lt;</span><span>int</span><span>&gt;</span><span>&amp;</span><span> </span><span>a</span><span>) {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>int</span><span> n </span><span>=</span><span> (</span><span>int</span><span>)a.</span><span>size</span><span>();</span></div></div><div><div><div>3</div></div><div><span>  </span><span>vector</span><span>&lt;</span><span>int</span><span>&gt; </span><span>tmp</span><span>(n);</span></div></div><div><div><div>4</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> len </span><span>=</span><span> </span><span>1</span><span>; len </span><span>&lt;</span><span> n; len </span><span>&lt;&lt;=</span><span> </span><span>1</span><span>) {</span></div></div><div><div><div>5</div></div><div><span>    </span><span>for</span><span> (</span><span>int</span><span> l </span><span>=</span><span> </span><span>0</span><span>; l </span><span>&lt;</span><span> n; l </span><span>+=</span><span> (len </span><span>&lt;&lt;</span><span> </span><span>1</span><span>)) {</span></div></div><div><div><div>6</div></div><div><span>      </span><span>int</span><span> mid </span><span>=</span><span> </span><span>min</span><span>(l </span><span>+</span><span> len </span><span>-</span><span> </span><span>1</span><span>, n </span><span>-</span><span> </span><span>1</span><span>);</span></div></div><div><div><div>7</div></div><div><span>      </span><span>int</span><span> r   </span><span>=</span><span> </span><span>min</span><span>(l </span><span>+</span><span> (len </span><span>&lt;&lt;</span><span> </span><span>1</span><span>) </span><span>-</span><span> </span><span>1</span><span>, n </span><span>-</span><span> </span><span>1</span><span>);</span></div></div><div><div><div>8</div></div><div><span>      </span><span>if</span><span> (mid </span><span>&gt;=</span><span> r) </span><span>continue</span><span>;</span><span>                 // 右段不存在</span></div></div><div><div><div>9</div></div><div><span>      </span><span>int</span><span> i </span><span>=</span><span> l, j </span><span>=</span><span> mid </span><span>+</span><span> </span><span>1</span><span>, k </span><span>=</span><span> l;</span></div></div><div><div><div>10</div></div><div><span>      </span><span>while</span><span> (i </span><span>&lt;=</span><span> mid </span><span>&amp;&amp;</span><span> j </span><span>&lt;=</span><span> r) {</span></div></div><div><div><div>11</div></div><div><span>        </span><span>if</span><span> (a[i] </span><span>&lt;=</span><span> a[j]) tmp[k</span><span>++</span><span>] </span><span>=</span><span> a[i</span><span>++</span><span>];</span></div></div><div><div><div>12</div></div><div><span>        </span><span>else</span><span>              tmp[k</span><span>++</span><span>] </span><span>=</span><span> a[j</span><span>++</span><span>];</span></div></div><div><div><div>13</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>14</div></div><div><span>      </span><span>while</span><span> (i </span><span>&lt;=</span><span> mid) tmp[k</span><span>++</span><span>] </span><span>=</span><span> a[i</span><span>++</span><span>];</span></div></div><div><div><div>15</div></div><div><span>      </span><span>while</span><span> (j </span><span>&lt;=</span><span> r)   tmp[k</span><span>++</span><span>] </span><span>=</span><span> a[j</span><span>++</span><span>];</span></div></div><div><div><div>16</div></div><div><span>      </span><span>for</span><span> (</span><span>int</span><span> x </span><span>=</span><span> l; x </span><span>&lt;=</span><span> r; </span><span>++</span><span>x) a[x] </span><span>=</span><span> tmp[x];</span></div></div><div><div><div>17</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>18</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>19</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><hr /></section><section><h2>4. 复杂度<a href="#4-复杂度"><span>#</span></a></h2><ul>
<li>时间复杂度：<span><span>T(n)=2T(n2)+O(n)→O(nlogn)T(n) = 2T(\frac{n}{2}) + O(n) → O(n log n)</span><span><span><span></span><span>T</span><span>(</span><span>n</span><span>)</span><span></span><span>=</span><span></span></span><span><span></span><span>2</span><span>T</span><span>(</span><span><span></span><span><span><span><span><span><span></span><span><span><span>2</span></span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>n</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span>)</span><span></span><span>+</span><span></span></span><span><span></span><span>O</span><span>(</span><span>n</span><span>)</span><span></span><span>→</span><span></span></span><span><span></span><span>O</span><span>(</span><span>n</span><span>l</span><span>o</span><span>g</span><span>n</span><span>)</span></span></span></span>
<ul>
<li>递归树每层都处理 n 个元素，共约 <span><span>lognlogn</span><span><span><span></span><span>l</span><span>o</span><span>g</span><span>n</span></span></span></span> 层。</li>
</ul>
</li>
<li>空间复杂度：<span><span>O(n)O(n)</span><span><span><span></span><span>O</span><span>(</span><span>n</span><span>)</span></span></span></span> 额外空间存放临时数组；递归版另有 <span><span>O(logn)O(log n)</span><span><span><span></span><span>O</span><span>(</span><span>l</span><span>o</span><span>g</span><span>n</span><span>)</span></span></span></span> 栈。</li>
</ul><hr /></section><section><h2>5. 应用一：逆序对计数（分治在“合并阶段统计”）<a href="#5-应用一逆序对计数分治在合并阶段统计"><span>#</span></a></h2><p>题意（如：洛谷 P1908、POJ 2299）</p><ul>
<li>逆序对：i &lt; j 且 a[i] &gt; a[j] 的有序对个数。</li>
</ul><hr /></section><section><h2>6. 应用二：小和问题<a href="#6-应用二小和问题"><span>#</span></a></h2><p>定义</p><ul>
<li>对每个 a[i]，统计它右侧有多少元素 ≥ a[i]，贡献 a[i] × 这个数量；求总贡献。</li>
</ul><p>合并思路</p><ul>
<li>当 a[i] &lt;= a[j] 时，a[i] 对右段当前 j..r 的所有元素都满足 ≥ a[i]，一次性贡献 <span><span>(r−j+1)×a[i](r - j + 1) \times a[i]</span><span><span><span></span><span>(</span><span>r</span><span></span><span>−</span><span></span></span><span><span></span><span>j</span><span></span><span>+</span><span></span></span><span><span></span><span>1</span><span>)</span><span></span><span>×</span><span></span></span><span><span></span><span>a</span><span>[</span><span>i</span><span>]</span></span></span></span>。</li>
</ul><hr /></section><section><h2>7. 应用三：最大子段和（分治合并信息）<a href="#7-应用三最大子段和分治合并信息"><span>#</span></a></h2><p>设计四个量</p><ul>
<li>sum：区间总和；</li>
<li>lmax：以左端点开头的最大前缀和；</li>
<li>rmax：以右端点结尾的最大后缀和；</li>
<li>best：区间内的最大子段和。</li>
</ul><p>合并法则</p><ul>
<li>sum = L.sum + R.sum</li>
<li>lmax = max(L.lmax, L.sum + R.lmax)</li>
<li>rmax = max(R.rmax, R.sum + L.rmax)</li>
<li>best = max(L.best, R.best, L.rmax + R.lmax)</li>
</ul><hr /></section><section><h2>8. 链表归并排序<a href="#8-链表归并排序"><span>#</span></a></h2><p>为什么链表适合归并</p><ul>
<li>归并只需要顺序前进指针，不依赖随机访问；</li>
<li>快排/堆排对链表并不友好。</li>
</ul><p>代码</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>struct</span><span> </span><span>ListNode</span><span> {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>int</span><span> val;</span></div></div><div><div><div>3</div></div><div><span>  </span><span>ListNode</span><span>*</span><span> next;</span></div></div><div><div><div>4</div></div><div><span>  </span><span>ListNode</span><span>(</span><span>int</span><span> </span><span>v</span><span>): </span><span>val</span><span>(v), </span><span>next</span><span>(</span><span>nullptr</span><span>) {}</span></div></div><div><div><div>5</div></div><div><span>};</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>ListNode</span><span>*</span><span> </span><span>merge_lists</span><span>(</span><span>ListNode</span><span>*</span><span> </span><span>a</span><span>, </span><span>ListNode</span><span>*</span><span> </span><span>b</span><span>) {</span></div></div><div><div><div>8</div></div><div><span>  </span><span>ListNode</span><span> </span><span>dummy</span><span>(</span><span>0</span><span>);</span></div></div><div><div><div>9</div></div><div><span>  </span><span>ListNode</span><span>*</span><span> tail </span><span>=</span><span> </span><span>&amp;</span><span>dummy;</span></div></div><div><div><div>10</div></div><div><span>  </span><span>while</span><span> (a </span><span>&amp;&amp;</span><span> b) {</span></div></div><div><div><div>11</div></div><div><span>    </span><span>if</span><span> (a-&gt;val </span><span>&lt;=</span><span> b-&gt;val) { tail-&gt;next </span><span>=</span><span> a; a </span><span>=</span><span> a-&gt;next; }</span></div></div><div><div><div>12</div></div><div><span>    </span><span>else</span><span>                  { tail-&gt;next </span><span>=</span><span> b; b </span><span>=</span><span> b-&gt;next; }</span></div></div><div><div><div>13</div></div><div><span><span>    </span></span><span>tail </span><span>=</span><span> tail-&gt;next;</span></div></div><div><div><div>14</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>15</div></div><div><span><span>  </span></span><span>tail-&gt;next </span><span>=</span><span> a </span><span>?</span><span> a </span><span>:</span><span> b;</span></div></div><div><div><div>16</div></div><div><span>  </span><span>return</span><span> dummy.next;</span></div></div><div><div><div>17</div></div><div><span>}</span></div></div><div><div><div>18</div></div><div>
</div></div><div><div><div>19</div></div><div><span>ListNode</span><span>*</span><span> </span><span>split_mid</span><span>(</span><span>ListNode</span><span>*</span><span> </span><span>head</span><span>) {</span></div></div><div><div><div>20</div></div><div><span>  </span><span>ListNode</span><span>*</span><span> slow </span><span>=</span><span> head;</span></div></div><div><div><div>21</div></div><div><span>  </span><span>ListNode</span><span>*</span><span> fast </span><span>=</span><span> head-&gt;next;</span></div></div><div><div><div>22</div></div><div><span>  </span><span>while</span><span> (fast </span><span>&amp;&amp;</span><span> fast-&gt;next) {</span></div></div><div><div><div>23</div></div><div><span><span>    </span></span><span>slow </span><span>=</span><span> slow-&gt;next;</span></div></div><div><div><div>24</div></div><div><span><span>    </span></span><span>fast </span><span>=</span><span> fast-&gt;next-&gt;next;</span></div></div><div><div><div>25</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>26</div></div><div><span>  </span><span>ListNode</span><span>*</span><span> right </span><span>=</span><span> slow-&gt;next;</span></div></div><div><div><div>27</div></div><div><span><span>  </span></span><span>slow-&gt;next </span><span>=</span><span> </span><span>nullptr</span><span>;</span></div></div><div><div><div>28</div></div><div><span>  </span><span>return</span><span> right;</span></div></div><div><div><div>29</div></div><div><span>}</span></div></div><div><div><div>30</div></div><div>
</div></div><div><div><div>31</div></div><div><span>ListNode</span><span>*</span><span> </span><span>merge_sort_list</span><span>(</span><span>ListNode</span><span>*</span><span> </span><span>head</span><span>) {</span></div></div><div><div><div>32</div></div><div><span>  </span><span>if</span><span> (</span><span>!</span><span>head </span><span>||</span><span> </span><span>!</span><span>head-&gt;next) </span><span>return</span><span> head;</span></div></div><div><div><div>33</div></div><div><span>  </span><span>ListNode</span><span>*</span><span> right </span><span>=</span><span> </span><span>split_mid</span><span>(head);</span></div></div><div><div><div>34</div></div><div><span>  </span><span>ListNode</span><span>*</span><span> L </span><span>=</span><span> </span><span>merge_sort_list</span><span>(head);</span></div></div><div><div><div>35</div></div><div><span>  </span><span>ListNode</span><span>*</span><span> R </span><span>=</span><span> </span><span>merge_sort_list</span><span>(right);</span></div></div><div><div><div>36</div></div><div><span>  </span><span>return</span><span> </span><span>merge_lists</span><span>(L, R);</span></div></div><div><div><div>37</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><hr /></section><section><h2>9. 多路归并与外部排序概念<a href="#9-多路归并与外部排序概念"><span>#</span></a></h2><ul>
<li>k 路归并：有 k 个有序序列，合并成一个有序序列。</li>
<li>做法：小根堆维护每个序列的当前最小元素，弹出后推进该序列下一个。</li>
<li>复杂度：<span><span>O(nlogk)O(n log k)</span><span><span><span></span><span>O</span><span>(</span><span>n</span><span>l</span><span>o</span><span>g</span><span>k</span><span>)</span></span></span></span>。</li>
</ul><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>struct</span><span> </span><span>Item</span><span> {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>int</span><span> val, i, j;</span><span> // val 值；来自第 i 段的第 j 个</span></div></div><div><div><div>3</div></div><div><span>  </span><span>bool</span><span> </span><span>operator</span><span>&gt;</span><span>(</span><span>const</span><span> </span><span>Item</span><span>&amp;</span><span> </span><span>other</span><span>) </span><span>const</span><span> { </span><span>return</span><span> val </span><span>&gt;</span><span> other.val; }</span></div></div><div><div><div>4</div></div><div><span>};</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>vector</span><span>&lt;</span><span>int</span><span>&gt; </span><span>k_merge</span><span>(</span><span>const</span><span> </span><span>vector</span><span>&lt;</span><span>vector</span><span>&lt;</span><span>int</span><span>&gt;&gt;</span><span>&amp;</span><span> </span><span>arrs</span><span>) {</span></div></div><div><div><div>7</div></div><div><span>  </span><span>priority_queue</span><span>&lt;</span><span>Item</span><span>, </span><span>vector</span><span>&lt;</span><span>Item</span><span>&gt;, </span><span>greater</span><span>&lt;</span><span>Item</span><span>&gt;&gt; pq;</span></div></div><div><div><div>8</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>0</span><span>; i </span><span>&lt;</span><span> (</span><span>int</span><span>)arrs.</span><span>size</span><span>(); </span><span>++</span><span>i)</span></div></div><div><div><div>9</div></div><div><span>    </span><span>if</span><span> (</span><span>!</span><span>arrs[i].</span><span>empty</span><span>()) pq.</span><span>push</span><span>({arrs[i][</span><span>0</span><span>], i, </span><span>0</span><span>});</span></div></div><div><div><div>10</div></div><div><span>  </span><span>vector</span><span>&lt;</span><span>int</span><span>&gt; res;</span></div></div><div><div><div>11</div></div><div><span>  </span><span>while</span><span> (</span><span>!</span><span>pq.</span><span>empty</span><span>()) {</span></div></div><div><div><div>12</div></div><div><span>    </span><span>auto</span><span> [v, i, j] </span><span>=</span><span> pq.</span><span>top</span><span>(); pq.</span><span>pop</span><span>();</span></div></div><div><div><div>13</div></div><div><span><span>    </span></span><span>res.</span><span>push_back</span><span>(v);</span></div></div><div><div><div>14</div></div><div><span>    </span><span>if</span><span> (j </span><span>+</span><span> </span><span>1</span><span> </span><span>&lt;</span><span> (</span><span>int</span><span>)arrs[i].</span><span>size</span><span>()) pq.</span><span>push</span><span>({arrs[i][j </span><span>+</span><span> </span><span>1</span><span>], i, j </span><span>+</span><span> </span><span>1</span><span>});</span></div></div><div><div><div>15</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>16</div></div><div><span>  </span><span>return</span><span> res;</span></div></div><div><div><div>17</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section></section>]]></content>
    <author><name>Zowely</name></author>
    <category term="算法竞赛" />
    <category term="算法竞赛" />
    <category term="算法教程" />
    <category term="C++" />
  </entry>
  <entry>
    <title>递推与递归</title>
    <link href="https://www.lansganbs.cn/posts/%E7%AE%97%E6%B3%95%E7%AB%9E%E8%B5%9B/%E9%80%92%E6%8E%A8%E4%B8%8E%E9%80%92%E5%BD%92/" rel="alternate" type="text/html" />
    <id>https://www.lansganbs.cn/posts/%E7%AE%97%E6%B3%95%E7%AB%9E%E8%B5%9B/%E9%80%92%E6%8E%A8%E4%B8%8E%E9%80%92%E5%BD%92/</id>
    <published>2025-08-23T00:00:00.000Z</published>
    <updated>2025-10-04T21:15:28.000Z</updated>
    <summary>2025秋哈尔滨理工大学新生第6周第一次算法培训</summary>
    <content type="html"><![CDATA[<section><h1>递推与递归<a href="#递推与递归"><span>#</span></a></h1><section><h2>0. 引入<a href="#0-引入"><span>#</span></a></h2><ul>
<li>
<p><strong>递推</strong>：从小到大，自己“走过去”。实现多是<strong>循环</strong>。</p>
</li>
<li>
<p><strong>递归</strong>：把大问题交给“更小的自己”先做；有<b>递(下潜)</b>与<b>归(返回)</b>两阶段。</p>
</li>
</ul><p>举两个简单的例子：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>int</span><span> </span><span>sum</span><span>(</span><span>int</span><span> </span><span>n</span><span>) {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>int</span><span> res </span><span>=</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>3</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>1</span><span>; i </span><span>&lt;=</span><span> n; i</span><span>++</span><span>) {</span></div></div><div><div><div>4</div></div><div><span><span>      </span></span><span>res </span><span>+=</span><span> i;</span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>6</div></div><div><span>  </span><span>return</span><span> res;</span></div></div><div><div><div>7</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>int</span><span> </span><span>sum</span><span>(</span><span>int</span><span> </span><span>n</span><span>) {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>if</span><span> (n </span><span>==</span><span> </span><span>1</span><span>) </span><span>return</span><span> </span><span>1</span><span>;</span><span>   // 递归出口</span></div></div><div><div><div>3</div></div><div><span>  </span><span>return</span><span> </span><span>sum</span><span>(n </span><span>-</span><span> </span><span>1</span><span>) </span><span>+</span><span> n;</span><span>  // 先递，再归</span></div></div><div><div><div>4</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p></p><figure><img src="https://www.lansganbs.cn/images/article/%E7%AE%97%E6%B3%95%E7%AB%9E%E8%B5%9B/%E9%80%92%E6%8E%A8%E4%B8%8E%E9%80%92%E5%BD%92/%E9%80%92%E5%BD%92%E5%9B%BE.png" alt="alt text" /><figcaption>alt text</figcaption></figure><p></p><ul>
<li>把 <code>sum(1..n)</code> 写成<strong>递</strong>：求 <code>sum(1..n-1)</code>；<strong>归</strong>：再加 <code>n</code>。</li>
<li>一个 5 层小“<strong>调用栈</strong>”：<code>sum(5)</code> 压栈到 <code>sum(1)</code> 再一层层返回。</li>
</ul><p>把 <code>add(a, b)</code> 写成递归：</p><p></p><figure><img src="https://www.lansganbs.cn/images/article/%E7%AE%97%E6%B3%95%E7%AB%9E%E8%B5%9B/%E9%80%92%E6%8E%A8%E4%B8%8E%E9%80%92%E5%BD%92/add(a,%20b).png" alt="alt text" /><figcaption>alt text</figcaption></figure><p></p><p>例如：</p><p>我们高中熟悉的通项公式：</p><span><span><span>f(n)=10+2×(n−1)f(n) = 10 + 2\times(n-1)</span><span><span><span></span><span>f</span><span>(</span><span>n</span><span>)</span><span></span><span>=</span><span></span></span><span><span></span><span>10</span><span></span><span>+</span><span></span></span><span><span></span><span>2</span><span></span><span>×</span><span></span></span><span><span></span><span>(</span><span>n</span><span></span><span>−</span><span></span></span><span><span></span><span>1</span><span>)</span></span></span></span></span><p>递推公式（状态转移方程）：</p><span><span><span>{f(1)=10f(n)=f(n−1)+2n&gt;1\left\{\begin{matrix} 
    f(1) = 10
 \\ f(n)=f(n-1)+2 &amp; n&gt;1
\end{matrix}\right.</span><span><span><span></span><span><span><span>{</span></span><span><span><span><span><span><span><span><span></span><span><span>f</span><span>(</span><span>1</span><span>)</span><span></span><span>=</span><span></span><span>10</span></span></span><span><span></span><span><span>f</span><span>(</span><span>n</span><span>)</span><span></span><span>=</span><span></span><span>f</span><span>(</span><span>n</span><span></span><span>−</span><span></span><span>1</span><span>)</span><span></span><span>+</span><span></span><span>2</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span><span></span><span><span><span><span><span><span></span><span><span>n</span><span></span><span>&gt;</span><span></span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span><span></span></span></span></span></span></span><hr /></section><section><h2>1. 方法与模板<a href="#1-方法与模板"><span>#</span></a></h2><section><h3>1.1 三步方法<a href="#11-三步方法"><span>#</span></a></h3><ol>
<li><strong>定义清楚</strong>：<code>f[i]</code> 或 <code>solve(x)</code> 表示什么？</li>
<li><strong>边界</strong>：最小规模的答案？如 <code>n==0/1</code>、空集、叶子结点。</li>
<li><strong>关系</strong>：大的如何由小的组成？（先后/限制/组合）</li>
</ol></section><section><h3>1.2 三个模板<a href="#12-三个模板"><span>#</span></a></h3><p><strong>(A) 递推</strong></p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>// 以 f[i] 为例</span></div></div><div><div><div>2</div></div><div><span>vector</span><span>&lt;</span><span>int</span><span>&gt; </span><span>f</span><span>(</span><span>n</span><span>+</span><span>1</span><span>);</span></div></div><div><div><div>3</div></div><div><span>f[</span><span>0</span><span>] </span><span>=</span><span> 基值0; f[</span><span>1</span><span>] </span><span>=</span><span> 基值1;</span></div></div><div><div><div>4</div></div><div><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>2</span><span>; i </span><span>&lt;=</span><span> n; </span><span>++</span><span>i) {</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>f[i] </span><span>=</span><span> 由 f[i</span><span>-</span><span>1</span><span>], f[i</span><span>-</span><span>2</span><span>] ... 推出;</span></div></div><div><div><div>6</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>(B) 递归（带记忆化）</strong></p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>unordered_map</span><span>&lt;int</span><span>,</span><span>int&gt;</span><span> memo;</span></div></div><div><div><div>2</div></div><div><span>int</span><span> </span><span>solve</span><span>(</span><span>int</span><span> </span><span>x</span><span>){</span></div></div><div><div><div>3</div></div><div><span>    </span><span>if</span><span> (x </span><span>&lt;=</span><span> 边界) </span><span>return</span><span> 边界值;</span></div></div><div><div><div>4</div></div><div><span>    </span><span>if</span><span> (</span><span>auto</span><span> it </span><span>=</span><span> memo.</span><span>find</span><span>(x); it </span><span>!=</span><span> memo.</span><span>end</span><span>()) </span><span>return</span><span> it-&gt;second;</span></div></div><div><div><div>5</div></div><div><span>    </span><span>int</span><span> ans </span><span>=</span><span> 通过 </span><span>solve</span><span>(更小规模) 得到;</span></div></div><div><div><div>6</div></div><div><span>    </span><span>return</span><span> memo[x] </span><span>=</span><span> ans;</span><span>   // 仅为“去重复”</span></div></div><div><div><div>7</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>(C) 回溯（做选择 → 递归 → 撤销选择）</strong></p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>vector</span><span>&lt;int&gt;</span><span> path;</span><span> // 当前选择</span></div></div><div><div><div>2</div></div><div><span>void</span><span> </span><span>dfs</span><span>(</span><span>int</span><span> </span><span>u</span><span> </span><span>/*深度或下标*/</span><span>){</span></div></div><div><div><div>3</div></div><div><span>    </span><span>if</span><span> (到达目标</span><span>/</span><span>长度) { 记录答案; </span><span>return</span><span>; }</span></div></div><div><div><div>4</div></div><div><span>    </span><span>for</span><span> (</span><span>auto</span><span> choice : 当前可选集){</span></div></div><div><div><div>5</div></div><div><span><span>        </span></span><span>path.</span><span>push_back</span><span>(choice);</span><span>           // 做选择</span></div></div><div><div><div>6</div></div><div><span>        </span><span>dfs</span><span>(u </span><span>+</span><span> </span><span>1</span><span>);</span><span>                       // 递</span></div></div><div><div><div>7</div></div><div><span><span>        </span></span><span>path.</span><span>pop_back</span><span>();</span><span>                  // 撤销</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>9</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>一个语法<a href="#一个语法"><span>#</span></a></h3><ul>
<li>lambda表达式</li>
</ul><p>Lambda表达式是一种在被调用的位置或作为参数传递给函数的位置定义匿名函数对象（闭包）的简便方法。Lambda表达式的基本语法如下：</p><p><code>[capture list] (parameter list) -&gt; return type { function body }</code></p><p>下面一个例子就可以很好的理解：</p><ol>
<li><code>add(a, b)</code>的函数形式</li>
</ol><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>int</span><span> </span><span>add</span><span>(</span><span>int</span><span> </span><span>a</span><span>, </span><span>int</span><span> </span><span>b</span><span>) {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>return</span><span> a </span><span>+</span><span> b;</span></div></div><div><div><div>3</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ol>
<li><code>add(a, b)</code>的lambda表达式</li>
</ol><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>auto</span><span> add </span><span>=</span><span> [</span><span>&amp;</span><span>](</span><span>auto</span><span> </span><span>a</span><span>, </span><span>auto</span><span> </span><span>b</span><span>) {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>return</span><span> a </span><span>+</span><span> b;</span></div></div><div><div><div>3</div></div><div><span>};</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section></section><section><h2>2. 入门例子<a href="#2-入门例子"><span>#</span></a></h2><section><h3>2.1 斐波那契数列<a href="#21-斐波那契数列"><span>#</span></a></h3><p>定义：</p><span><span><span>f(0)=0,f(1)=1,f(n)=f(n−1)+f(n−2) (n≥2)f(0)=0,\quad f(1)=1,\quad f(n)=f(n-1)+f(n-2)\ (n\ge 2)</span><span><span><span></span><span>f</span><span>(</span><span>0</span><span>)</span><span></span><span>=</span><span></span></span><span><span></span><span>0</span><span>,</span><span></span><span></span><span>f</span><span>(</span><span>1</span><span>)</span><span></span><span>=</span><span></span></span><span><span></span><span>1</span><span>,</span><span></span><span></span><span>f</span><span>(</span><span>n</span><span>)</span><span></span><span>=</span><span></span></span><span><span></span><span>f</span><span>(</span><span>n</span><span></span><span>−</span><span></span></span><span><span></span><span>1</span><span>)</span><span></span><span>+</span><span></span></span><span><span></span><span>f</span><span>(</span><span>n</span><span></span><span>−</span><span></span></span><span><span></span><span>2</span><span>)</span><span> </span><span>(</span><span>n</span><span></span><span>≥</span><span></span></span><span><span></span><span>2</span><span>)</span></span></span></span></span><ul>
<li>递推 <span><span>O(n)O(n)</span><span><span><span></span><span>O</span><span>(</span><span>n</span><span>)</span></span></span></span></li>
</ul><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>long</span><span> </span><span>long</span><span> </span><span>fib_iter</span><span>(</span><span>int</span><span> </span><span>n</span><span>) {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>if</span><span> (n </span><span>&lt;</span><span> </span><span>2</span><span>) </span><span>return</span><span> n;</span></div></div><div><div><div>3</div></div><div><span>  </span><span>long</span><span> </span><span>long</span><span> a </span><span>=</span><span> </span><span>0</span><span>, b </span><span>=</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>4</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>2</span><span>; i </span><span>&lt;=</span><span> n; </span><span>++</span><span>i) {</span></div></div><div><div><div>5</div></div><div><span>    </span><span>long</span><span> </span><span>long</span><span> c </span><span>=</span><span> a </span><span>+</span><span> b;</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>a </span><span>=</span><span> b, b </span><span>=</span><span> c;</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>8</div></div><div><span>  </span><span>return</span><span> b;</span></div></div><div><div><div>9</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li>递归 + 记忆化 <span><span>O(n)O(n)</span><span><span><span></span><span>O</span><span>(</span><span>n</span><span>)</span></span></span></span></li>
</ul><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>long</span><span> </span><span>long</span><span> </span><span>fib_memo</span><span>(</span><span>int</span><span> </span><span>n</span><span>, </span><span>vector</span><span>&lt;</span><span>long</span><span> </span><span>long</span><span>&gt;</span><span>&amp;</span><span> </span><span>memo</span><span>) {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>if</span><span> (n </span><span>&lt;</span><span> </span><span>2</span><span>) </span><span>return</span><span> n;</span></div></div><div><div><div>3</div></div><div><span>  </span><span>if</span><span> (memo[n] </span><span>!=</span><span> </span><span>-</span><span>1</span><span>) </span><span>return</span><span> memo[n];</span></div></div><div><div><div>4</div></div><div><span>  </span><span>return</span><span> memo[n] </span><span>=</span><span> </span><span>fib_memo</span><span>(n </span><span>-</span><span> </span><span>1</span><span>, memo) </span><span>+</span><span> </span><span>fib_memo</span><span>(n </span><span>-</span><span> </span><span>2</span><span>, memo);</span></div></div><div><div><div>5</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li>快速倍增 <span><span>O(logn)O(logn)</span><span><span><span></span><span>O</span><span>(</span><span>l</span><span>o</span><span>g</span><span>n</span><span>)</span></span></span></span></li>
</ul><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>pair</span><span>&lt;</span><span>long</span><span> </span><span>long</span><span>,</span><span>long</span><span> </span><span>long</span><span>&gt; </span><span>fib_fast</span><span>(</span><span>long</span><span> </span><span>long</span><span> </span><span>n</span><span>) {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>if</span><span> (n </span><span>==</span><span> </span><span>0</span><span>) </span><span>return</span><span> {</span><span>0</span><span>, </span><span>1</span><span>};</span></div></div><div><div><div>3</div></div><div><span>  </span><span>auto</span><span> [a, b] </span><span>=</span><span> </span><span>fib_fast</span><span>(n </span><span>&gt;&gt;</span><span> </span><span>1</span><span>);</span><span>     // a=F(k), b=F(k+1)</span></div></div><div><div><div>4</div></div><div><span>  </span><span>long</span><span> </span><span>long</span><span> c </span><span>=</span><span> a </span><span>*</span><span> (</span><span>2</span><span>*</span><span>b </span><span>-</span><span> a);</span><span>        // F(2k)</span></div></div><div><div><div>5</div></div><div><span>  </span><span>long</span><span> </span><span>long</span><span> d </span><span>=</span><span> a</span><span>*</span><span>a </span><span>+</span><span> b</span><span>*</span><span>b;</span><span>            // F(2k+1)</span></div></div><div><div><div>6</div></div><div><span>  </span><span>if</span><span> ((n </span><span>&amp;</span><span> </span><span>1</span><span>) </span><span>==</span><span> </span><span>0</span><span>) </span><span>return</span><span> {c, d};</span></div></div><div><div><div>7</div></div><div><span>  </span><span>else</span><span>              </span><span>return</span><span> {d, c </span><span>+</span><span> d};</span></div></div><div><div><div>8</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>递归树：</p><p></p><figure><img src="https://www.lansganbs.cn/images/article/%E7%AE%97%E6%B3%95%E7%AB%9E%E8%B5%9B/%E9%80%92%E6%8E%A8%E4%B8%8E%E9%80%92%E5%BD%92/%E9%80%92%E5%BD%92%E6%A0%91.png" alt="alt text" /><figcaption>alt text</figcaption></figure><p></p><p>朴素递归会重复计算同一子问题（重叠子问题），导致指数级调用次数。
记忆化避免重复计算，每个 n 只算一次。</p><p>小结</p><ul>
<li>依赖前几个状态 → 优先迭代（快且省内存）。</li>
<li>需要自顶向下表达式清晰 → 递归+记忆化。</li>
<li>超大 n 或取模 → 快速倍增。</li>
</ul><hr /></section><section><h3>2.2 爬楼梯（基础 + 常见变体）<a href="#22-爬楼梯基础--常见变体"><span>#</span></a></h3><p>基础版：每次上 1 或 2 阶，求到第 n 阶的方法数。</p><ul>
<li>dp[i] = dp[i-1] + dp[i-2]，dp[1]=1, dp[2]=2</li>
</ul><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>long</span><span> </span><span>long</span><span> </span><span>climb2</span><span>(</span><span>int</span><span> </span><span>n</span><span>) {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>if</span><span> (n </span><span>&lt;=</span><span> </span><span>2</span><span>) </span><span>return</span><span> n;</span></div></div><div><div><div>3</div></div><div><span>  </span><span>long</span><span> </span><span>long</span><span> a </span><span>=</span><span> </span><span>1</span><span>, b </span><span>=</span><span> </span><span>2</span><span>;</span></div></div><div><div><div>4</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>3</span><span>; i </span><span>&lt;=</span><span> n; </span><span>++</span><span>i) {</span></div></div><div><div><div>5</div></div><div><span>    </span><span>long</span><span> </span><span>long</span><span> c </span><span>=</span><span> a </span><span>+</span><span> b;</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>a </span><span>=</span><span> b, b </span><span>=</span><span> c;</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>8</div></div><div><span>  </span><span>return</span><span> b;</span></div></div><div><div><div>9</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section><section><h3>2.3 数字三角形（递推与记忆化二选一）<a href="#23-数字三角形递推与记忆化二选一"><span>#</span></a></h3><ul>
<li>递推</li>
</ul><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>int</span><span> n;</span></div></div><div><div><div>2</div></div><div><span>cin </span><span>&gt;&gt;</span><span> n;</span></div></div><div><div><div>3</div></div><div><span>vector</span><span>&lt;</span><span>vector</span><span>&lt;</span><span>int</span><span>&gt;&gt; </span><span>a</span><span>(</span><span>n</span><span>+</span><span>1</span><span>, </span><span>vector</span><span>&lt;</span><span>int</span><span>&gt;(n</span><span>+</span><span>1</span><span>, </span><span>0</span><span>));</span></div></div><div><div><div>4</div></div><div><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>1</span><span>; i </span><span>&lt;=</span><span> n; </span><span>++</span><span>i)</span></div></div><div><div><div>5</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> j </span><span>=</span><span> </span><span>1</span><span>; j </span><span>&lt;=</span><span> i; </span><span>++</span><span>j)</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>cin </span><span>&gt;&gt;</span><span> a[i][j];</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> n </span><span>-</span><span> </span><span>1</span><span>; i </span><span>&gt;=</span><span> </span><span>1</span><span>; </span><span>--</span><span>i)</span></div></div><div><div><div>9</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> j </span><span>=</span><span> </span><span>1</span><span>; j </span><span>&lt;=</span><span> i; </span><span>++</span><span>j)</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>a[i][j] </span><span>+=</span><span> </span><span>max</span><span>(a[i</span><span>+</span><span>1</span><span>][j], a[i</span><span>+</span><span>1</span><span>][j</span><span>+</span><span>1</span><span>]);</span></div></div><div><div><div>11</div></div><div>
</div></div><div><div><div>12</div></div><div><span>cout </span><span>&lt;&lt;</span><span> a[</span><span>1</span><span>][</span><span>1</span><span>] </span><span>&lt;&lt;</span><span> </span><span>'</span><span>\n</span><span>'</span><span>;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li>记忆化</li>
</ul><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>int</span><span> n;</span></div></div><div><div><div>2</div></div><div><span>vector</span><span>&lt;</span><span>vector</span><span>&lt;int&gt;&gt;</span><span> a, memo;</span></div></div><div><div><div>3</div></div><div><span>int</span><span> </span><span>solve</span><span>(</span><span>int</span><span> </span><span>i</span><span>, </span><span>int</span><span> </span><span>j</span><span>) {</span></div></div><div><div><div>4</div></div><div><span>  </span><span>if</span><span> (i </span><span>==</span><span> n) </span><span>return</span><span> a[i][j];</span></div></div><div><div><div>5</div></div><div><span>  </span><span>int</span><span> </span><span>&amp;</span><span>res </span><span>=</span><span> memo[i][j];</span></div></div><div><div><div>6</div></div><div><span>  </span><span>if</span><span> (res </span><span>!=</span><span> INT_MIN) </span><span>return</span><span> res;</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>res </span><span>=</span><span> a[i][j] </span><span>+</span><span> </span><span>max</span><span>(</span><span>solve</span><span>(i</span><span>+</span><span>1</span><span>,j), </span><span>solve</span><span>(i</span><span>+</span><span>1</span><span>,j</span><span>+</span><span>1</span><span>));</span></div></div><div><div><div>8</div></div><div><span>  </span><span>return</span><span> res;</span></div></div><div><div><div>9</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section><section><h3>2.4 快速幂<a href="#24-快速幂"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>long</span><span> </span><span>long</span><span> </span><span>qpow</span><span>(</span><span>long</span><span> </span><span>long</span><span> </span><span>a</span><span>, </span><span>long</span><span> </span><span>long</span><span> </span><span>b</span><span>, </span><span>long</span><span> </span><span>long</span><span> </span><span>mod</span><span>) {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>long</span><span> </span><span>long</span><span> res </span><span>=</span><span> </span><span>1</span><span> </span><span>%</span><span> mod;</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>a </span><span>%=</span><span> mod;</span></div></div><div><div><div>4</div></div><div><span>  </span><span>while</span><span> (b </span><span>&gt;</span><span> </span><span>0</span><span>) {</span></div></div><div><div><div>5</div></div><div><span>    </span><span>if</span><span> (b </span><span>&amp;</span><span> </span><span>1</span><span>) res </span><span>=</span><span> res </span><span>*</span><span> a </span><span>%</span><span> mod;</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>a </span><span>=</span><span> a </span><span>*</span><span> a </span><span>%</span><span> mod;</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>b </span><span>&gt;&gt;=</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>8</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>9</div></div><div><span>  </span><span>return</span><span> res;</span></div></div><div><div><div>10</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section></section><section><h2>3. 重点例题：P1028《数的计算》<a href="#3-重点例题p1028数的计算"><span>#</span></a></h2><p>题意：以 n 开头，后一项 ≤ 前一项的一半，问序列个数（“只取自己”也算一种）。</p><p>推导：</p><span><span><span>f(n)=1+∑1n2f(i)f(n) = 1 + \sum_{1}^{\frac{n}{2} }  f(i)</span><span><span><span></span><span>f</span><span>(</span><span>n</span><span>)</span><span></span><span>=</span><span></span></span><span><span></span><span>1</span><span></span><span>+</span><span></span></span><span><span></span><span><span><span><span><span><span></span><span><span><span>1</span></span></span></span><span><span></span><span><span>∑</span></span></span><span><span></span><span><span><span><span></span><span><span><span><span><span><span></span><span><span><span>2</span></span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>n</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span><span>f</span><span>(</span><span>i</span><span>)</span></span></span></span></span><p>方法 A：记忆化</p><p>方法 B：递推 + 前缀和 <span><span>O(n)O(n)</span><span><span><span></span><span>O</span><span>(</span><span>n</span><span>)</span></span></span></span></p><hr /></section><section><h2>4. 回溯入门：P1036《选数》<a href="#4-回溯入门p1036选数"><span>#</span></a></h2><p>题意：n 个数里选 k 个，使和是素数，求方案数（n ≤ 20）。</p><ul>
<li>
<p>组合回溯</p>
</li>
<li>
<p>拓展：位枚举（非递归）</p>
</li>
</ul><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>int</span><span> </span><span>count_prime_sum</span><span>(</span><span>const</span><span> </span><span>vector</span><span>&lt;</span><span>int</span><span>&gt;</span><span>&amp;</span><span> </span><span>a</span><span>, </span><span>int</span><span> </span><span>k</span><span>) {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>int</span><span> n </span><span>=</span><span> a.</span><span>size</span><span>(), ans </span><span>=</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>3</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> mask </span><span>=</span><span> </span><span>0</span><span>; mask </span><span>&lt;</span><span> (</span><span>1</span><span> </span><span>&lt;&lt;</span><span> n); </span><span>++</span><span>mask) {</span></div></div><div><div><div>4</div></div><div><span>    </span><span>if</span><span> (</span><span>__builtin_popcount</span><span>((</span><span>unsigned</span><span>)mask) </span><span>!=</span><span> k) </span><span>continue</span><span>;</span></div></div><div><div><div>5</div></div><div><span>    </span><span>int</span><span> s </span><span>=</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>6</div></div><div><span>    </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>0</span><span>; i </span><span>&lt;</span><span> n; </span><span>++</span><span>i)</span></div></div><div><div><div>7</div></div><div><span>      </span><span>if</span><span> (mask </span><span>&gt;&gt;</span><span> i </span><span>&amp;</span><span> </span><span>1</span><span>) s </span><span>+=</span><span> a[i];</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>ans </span><span>+=</span><span> </span><span>is_prime</span><span>(s);</span></div></div><div><div><div>9</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>10</div></div><div><span>  </span><span>return</span><span> ans;</span></div></div><div><div><div>11</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section><section><h2>5. 一个经典问题<a href="#5-一个经典问题"><span>#</span></a></h2><section><h3>5.1 汉诺塔<a href="#51-汉诺塔"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>function</span><span>&lt;void</span><span>(</span><span>int</span><span>,</span><span>char</span><span>,</span><span>char</span><span>,</span><span>char</span><span>)</span><span>&gt;</span><span> hanoi </span><span>=</span><span> [</span><span>&amp;</span><span>](</span><span>int</span><span> </span><span>n</span><span>, </span><span>char</span><span> </span><span>A</span><span>, </span><span>char</span><span> </span><span>B</span><span>, </span><span>char</span><span> </span><span>C</span><span>) {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>if</span><span> (n </span><span>==</span><span> </span><span>0</span><span>) </span><span>return</span><span>;</span></div></div><div><div><div>3</div></div><div><span>  </span><span>hanoi</span><span>(n </span><span>-</span><span> </span><span>1</span><span>, A, C, B);</span></div></div><div><div><div>4</div></div><div><span><span>  </span></span><span>cout </span><span>&lt;&lt;</span><span> A </span><span>&lt;&lt;</span><span> </span><span>" -&gt; "</span><span> </span><span>&lt;&lt;</span><span> C </span><span>&lt;&lt;</span><span> </span><span>'</span><span>\n</span><span>'</span><span>;</span></div></div><div><div><div>5</div></div><div><span>  </span><span>hanoi</span><span>(n </span><span>-</span><span> </span><span>1</span><span>, B, A, C);</span></div></div><div><div><div>6</div></div><div><span>};</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section></section><section><h2>6. 拓展练习<a href="#6-拓展练习"><span>#</span></a></h2><section><h3>6.1 P1464《函数 w》<a href="#61-p1464函数-w"><span>#</span></a></h3><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>long</span><span> </span><span>long</span><span> memo[</span><span>21</span><span>][</span><span>21</span><span>][</span><span>21</span><span>];</span></div></div><div><div><div>2</div></div><div><span>bool</span><span> vis[</span><span>21</span><span>][</span><span>21</span><span>][</span><span>21</span><span>];</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>long</span><span> </span><span>long</span><span> </span><span>w</span><span>(</span><span>int</span><span> </span><span>a</span><span>, </span><span>int</span><span> </span><span>b</span><span>, </span><span>int</span><span> </span><span>c</span><span>) {</span></div></div><div><div><div>5</div></div><div><span>  </span><span>if</span><span> (a </span><span>&lt;=</span><span> </span><span>0</span><span> </span><span>||</span><span> b </span><span>&lt;=</span><span> </span><span>0</span><span> </span><span>||</span><span> c </span><span>&lt;=</span><span> </span><span>0</span><span>) </span><span>return</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>6</div></div><div><span>  </span><span>if</span><span> (a </span><span>&gt;</span><span> </span><span>20</span><span> </span><span>||</span><span> b </span><span>&gt;</span><span> </span><span>20</span><span> </span><span>||</span><span> c </span><span>&gt;</span><span> </span><span>20</span><span>) </span><span>return</span><span> </span><span>w</span><span>(</span><span>20</span><span>, </span><span>20</span><span>, </span><span>20</span><span>);</span></div></div><div><div><div>7</div></div><div><span>  </span><span>if</span><span> (vis[a][b][c]) </span><span>return</span><span> memo[a][b][c];</span></div></div><div><div><div>8</div></div><div><span><span>  </span></span><span>vis[a][b][c] </span><span>=</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>9</div></div><div><span>  </span><span>long</span><span> </span><span>long</span><span> </span><span>&amp;</span><span>res </span><span>=</span><span> memo[a][b][c];</span></div></div><div><div><div>10</div></div><div><span>  </span><span>if</span><span> (a </span><span>&lt;</span><span> b </span><span>&amp;&amp;</span><span> b </span><span>&lt;</span><span> c)</span></div></div><div><div><div>11</div></div><div><span><span>    </span></span><span>res </span><span>=</span><span> </span><span>w</span><span>(a, b, c</span><span>-</span><span>1</span><span>) </span><span>+</span><span> </span><span>w</span><span>(a, b</span><span>-</span><span>1</span><span>, c</span><span>-</span><span>1</span><span>) </span><span>-</span><span> </span><span>w</span><span>(a, b</span><span>-</span><span>1</span><span>, c);</span></div></div><div><div><div>12</div></div><div><span>  </span><span>else</span></div></div><div><div><div>13</div></div><div><span><span>    </span></span><span>res </span><span>=</span><span> </span><span>w</span><span>(a</span><span>-</span><span>1</span><span>, b, c) </span><span>+</span><span> </span><span>w</span><span>(a</span><span>-</span><span>1</span><span>, b</span><span>-</span><span>1</span><span>, c) </span><span>+</span><span> </span><span>w</span><span>(a</span><span>-</span><span>1</span><span>, b, c</span><span>-</span><span>1</span><span>) </span><span>-</span><span> </span><span>w</span><span>(a</span><span>-</span><span>1</span><span>, b</span><span>-</span><span>1</span><span>, c</span><span>-</span><span>1</span><span>);</span></div></div><div><div><div>14</div></div><div><span>  </span><span>return</span><span> res;</span></div></div><div><div><div>15</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section><section><h3>6.2 P1025《数的划分》<a href="#62-p1025数的划分"><span>#</span></a></h3><p>把 n 划分成 k 个正整数的和（顺序不计，非降约束）</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>int</span><span> n, k, ans;</span></div></div><div><div><div>2</div></div><div><span>void</span><span> </span><span>dfs</span><span>(</span><span>int</span><span> </span><span>pos</span><span>, </span><span>int</span><span> </span><span>last</span><span>, </span><span>int</span><span> </span><span>sum</span><span>) {</span></div></div><div><div><div>3</div></div><div><span>  </span><span>if</span><span> (pos </span><span>==</span><span> k) { ans </span><span>+=</span><span> (sum </span><span>==</span><span> n); </span><span>return</span><span>; }</span></div></div><div><div><div>4</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> x </span><span>=</span><span> last; x </span><span>&lt;=</span><span> n; </span><span>++</span><span>x) {</span></div></div><div><div><div>5</div></div><div><span>    </span><span>if</span><span> (sum </span><span>+</span><span> x </span><span>+</span><span> (k </span><span>-</span><span> pos </span><span>-</span><span> </span><span>1</span><span>) </span><span>*</span><span> x </span><span>&gt;</span><span> n) </span><span>break</span><span>;</span><span> // 剪枝：最小填充也超了</span></div></div><div><div><div>6</div></div><div><span>    </span><span>dfs</span><span>(pos </span><span>+</span><span> </span><span>1</span><span>, x, sum </span><span>+</span><span> x);</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>8</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>6.3 括号生成（回溯，约束计数）<a href="#63-括号生成回溯约束计数"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>void</span><span> </span><span>gen_paren</span><span>(</span><span>int</span><span> </span><span>n</span><span>) {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>string</span><span> path; vector</span><span>&lt;</span><span>string</span><span>&gt;</span><span> ans;</span></div></div><div><div><div>3</div></div><div><span>  </span><span>function</span><span>&lt;</span><span>void</span><span>(</span><span>int</span><span>,</span><span>int</span><span>)&gt; bt </span><span>=</span><span> [</span><span>&amp;</span><span>](</span><span>int</span><span> </span><span>open</span><span>, </span><span>int</span><span> </span><span>close</span><span>) {</span></div></div><div><div><div>4</div></div><div><span>    </span><span>if</span><span> ((</span><span>int</span><span>)path.</span><span>size</span><span>() </span><span>==</span><span> </span><span>2</span><span>*</span><span>n) { ans.</span><span>push_back</span><span>(path); </span><span>return</span><span>; }</span></div></div><div><div><div>5</div></div><div><span>    </span><span>if</span><span> (open </span><span>&lt;</span><span> n) { path.</span><span>push_back</span><span>(</span><span>'('</span><span>); </span><span>bt</span><span>(open</span><span>+</span><span>1</span><span>, close); path.</span><span>pop_back</span><span>(); }</span></div></div><div><div><div>6</div></div><div><span>    </span><span>if</span><span> (close </span><span>&lt;</span><span> open) { path.</span><span>push_back</span><span>(</span><span>')'</span><span>); </span><span>bt</span><span>(open, close</span><span>+</span><span>1</span><span>); path.</span><span>pop_back</span><span>(); }</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>};</span></div></div><div><div><div>8</div></div><div><span>  </span><span>bt</span><span>(</span><span>0</span><span>, </span><span>0</span><span>);</span></div></div><div><div><div>9</div></div><div><span><span>  </span></span><span>// 输出 ans</span></div></div><div><div><div>10</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>6.4 全排列（回溯就地交换）<a href="#64-全排列回溯就地交换"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>void</span><span> </span><span>perm</span><span>(</span><span>vector</span><span>&lt;</span><span>int</span><span>&gt; </span><span>a</span><span>, </span><span>int</span><span> </span><span>u</span><span>, </span><span>vector</span><span>&lt;</span><span>vector</span><span>&lt;</span><span>int</span><span>&gt;&gt;</span><span>&amp;</span><span> </span><span>all</span><span>) {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>if</span><span> (u </span><span>==</span><span> (</span><span>int</span><span>)a.</span><span>size</span><span>()) { all.</span><span>push_back</span><span>(a); </span><span>return</span><span>; }</span></div></div><div><div><div>3</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> u; i </span><span>&lt;</span><span> (</span><span>int</span><span>)a.</span><span>size</span><span>(); </span><span>++</span><span>i) {</span></div></div><div><div><div>4</div></div><div><span>    </span><span>if</span><span> (i </span><span>!=</span><span> u </span><span>&amp;&amp;</span><span> a[i] </span><span>==</span><span> a[u]) </span><span>continue</span><span>;</span><span> // 若需要去重：先排序 + 这一行</span></div></div><div><div><div>5</div></div><div><span>    </span><span>swap</span><span>(a[i], a[u]);</span></div></div><div><div><div>6</div></div><div><span>    </span><span>perm</span><span>(a, u </span><span>+</span><span> </span><span>1</span><span>, all);</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>8</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section></section>]]></content>
    <author><name>Zowely</name></author>
    <category term="算法竞赛" />
    <category term="算法竞赛" />
    <category term="算法教程" />
    <category term="C++" />
  </entry>
  <entry>
    <title>米津玄师《打上花火》歌词及罗马音发音</title>
    <link href="https://www.lansganbs.cn/posts/%E6%AD%8C%E6%9B%B2/%E7%B1%B3%E6%B4%A5%E7%8E%84%E5%B8%88%E6%89%93%E4%B8%8A%E8%8A%B1%E7%81%AB%E6%AD%8C%E8%AF%8D%E5%8F%8A%E7%BD%97%E9%A9%AC%E9%9F%B3%E5%8F%91%E9%9F%B3/" rel="alternate" type="text/html" />
    <id>https://www.lansganbs.cn/posts/%E6%AD%8C%E6%9B%B2/%E7%B1%B3%E6%B4%A5%E7%8E%84%E5%B8%88%E6%89%93%E4%B8%8A%E8%8A%B1%E7%81%AB%E6%AD%8C%E8%AF%8D%E5%8F%8A%E7%BD%97%E9%A9%AC%E9%9F%B3%E5%8F%91%E9%9F%B3/</id>
    <published>2025-07-03T00:00:00.000Z</published>
    <updated>2025-07-03T21:59:42.000Z</updated>
    <summary>【歌词及罗马音发音】《打ち上げ花火》 - Daoko、米津玄师</summary>
    <content type="html"><![CDATA[<p>【歌词及罗马音发音】《打ち上げ花火》 - Daoko、米津玄师</p>
<p>填词：米津玄师</p>
<p>谱曲：米津玄师</p>
<p>编曲：田中隼人、米津玄师</p>
<p>歌曲原唱：Daoko、米津玄师</p>
<p></p><figure><img src="https://www.lansganbs.cn/images/article/%E6%97%A5%E8%AF%AD%E6%AD%8C%E6%9B%B2/%E6%89%93%E4%B8%8A%E8%8A%B1%E7%81%AB/%E6%89%93%E4%B8%8A%E8%8A%B1%E7%81%AB.png" alt="alt text" /><figcaption>alt text</figcaption></figure><p></p>
<p><a href="https://music.apple.com/cn/album/uchiagehanabi/1263790414?i=1263790415" target="_blank">打上花火</a></p>
<hr />
<p>a no hi   mi wa ta shi ta   na gi sa wo</p>
<p>あの日(ひ)见（み）わたした渚（なぎさ）を</p>
<p>那天眺望着的海岸</p>
<p>i ma moo mo <u>i da</u> sun da</p>
<p>今（いま）も思（おも）い出（だ）すんだ</p>
<p>时至今日仍印在心间</p>
<p>su na no~<u>u e</u> ni   ki za <span>n</span> da ko to ba</p>
<p>砂（すな）の上（うえ）に刻（きざ）んだ言葉（ことば）</p>
<p>沙滩上刻下的那些话</p>
<p>ki mi no <span>u</span> shi~ro su ga ta</p>
<p>君（きみ）の後（うし）ろ姿（すがた）</p>
<p>与你的背影</p>
<p>yo ri ka <span>e</span>   su na mi ga</p>
<p>寄（よ）り返（かえ）す波（なみ）が</p>
<p>那翻涌的海浪</p>
<p>a shi mo to wo yo gi ri na ni ka wo sa ra u</p>
<p>足元（あしもと）をよぎり，何（なに）かをさらう</p>
<p>到底从脚边掠走了什么</p>
<p>yuu~na   gi no na ka</p>
<p>夕凪（ゆうなぎ）の中（なか）</p>
<p>风平浪静之中</p>
<p>hi gu re da ke ga   too ri su gi te yu ku</p>
<p>日暮（ひぐ）れだけが通（とお）り过（す）ぎてゆく</p>
<p>只有夕阳渐渐消逝</p>
<p>pa tto hi ka tte sa i ta</p>
<p>パッと光（ひか）って咲（さ）いた</p>
<p>怦的一下 光芒绽放</p>
<p>ha na bi wo mi te i ta</p>
<p>花火（はなび）を见（み）ていた</p>
<p>烟花焰火映入眼帘</p>
<p>ki tto ma da   o wa ra <u>na i</u> na tsu ga</p>
<p>きっとまだ终（お）わらない夏（なつ）が</p>
<p>夏天一定还没有结束吧</p>
<p><u>a i</u> <u>ma i</u> na ko ko ro wo   too ka shi te tsu na i da</p>
<p>暧昧（あいまい）な心（こころ）を透过（とう）かして繋（つな）いだ</p>
<p>暧昧的心结 解开后又紧紧相连</p>
<p><u>ko no yo ru ga</u>   <u>tsu zu <span>i</span></u>   te ho shi ka tta</p>
<p>この夜（よる）が続（つず）いて欲（ほ）しかった</p>
<p>多希望今夜不会结束</p>
<hr />
<p>a to nan   do ki mi   to o na   ji ha na   bi wo mi   <u>ra re ru ka na tte</u></p>
<p>あと何度（なんど）君（きみ）と同（おな）じ花火（はなび）を见（み）られるかなってう</p>
<p>还能再与你看几次同样的焰火呢</p>
<p><u>wa ra u ka o</u>   ni na ni   ga de ki   ru da roo ka</p>
<p>笑（わら）う颜（かお）に何（なに）ができるだろうか</p>
<p>看着你的笑颜 我又该做些什么呢</p>
<p>ki zu tsu~ku ko to   yo ro ko~bu ko to</p>
<p>伤（きず）つくこと 喜（よろこ）ぶこと</p>
<p>伤痛亦或是喜悦</p>
<p>ku ri ka <u>e su</u>   na mi to jyo o dou</p>
<p>缲（く）り返（かえ）す波（なみ）と情动（じょうどう）</p>
<p>循环往复的海浪与情愫萌动</p>
<p>syou sou   sai syuu re ssha no <span>o</span> to</p>
<p>焦燥（しょうそう）　最终列车（さいしゅうれっしゃ）の音（おと）</p>
<p>最后一班列车焦躁的笛声</p>
<p>nan do de mo   ko to ba   <u>ni shi te</u>   ki mi wo   yo bu yo</p>
<p>何度（なんど）でも言葉（ことば）にして君（きみ）を呼（よ）ぶよ</p>
<p>无数次我呼唤着你</p>
<p>na mi ma <u>wo e ra bi</u>   moo i~<u>chi do</u> do do do do</p>
<p>波間（なみま）を選（えら）びもう一度（いちど）</p>
<p>再度向着那海浪</p>
<p>moo ni do to   <u>ka na shi</u>~<u>ma zu ni</u>   su mu yo o ni</p>
<p>もう二度（にど）と悲（かな）しまずに　済（す）むように</p>
<p>祈愿不再感受那般悲伤</p>
<hr />
<p>ha tto i ki wo no me ba</p>
<p>ハッと息（いき）を呑（の）めば</p>
<p>一瞬间屏住了呼吸</p>
<p>ki <span>e</span> cha i soo na   <u>hi ka ri</u> ga</p>
<p>消（き）えちゃいそうな光（ひかり）が</p>
<p>和那将要消逝的光芒</p>
<p>ki tto ma da   mu ne   ni sun   de i ta</p>
<p>きっとまだ胸（むね）に住（す）んでいた</p>
<p>一定能留在心中的吧</p>
<p><u>te wo no ba</u>   se ba   hu re ta</p>
<p>手（て）を伸（の）ばせば触（ふ）れた</p>
<p>触手可及的</p>
<p>a tta ka   i mi   ra i wa</p>
<p>あったかい未来（みらい）は</p>
<p>那温暖的未来</p>
<p><u>hi so ka ni</u> hu~ta~ri wo mi te <span>i</span> ta</p>
<p>密（ひそ）かにふたりを见（み）ていた</p>
<p>在悄悄守候着两人啊</p>
<p>pa tto ha na bi ga   pa tto ha na bi ga</p>
<p>パッと花火（はなび）が「パッと花火（はなび）が」</p>
<p>那怦然绽放的花火啊</p>
<p>yo ru ni sa i ta   yo ru ni sa i ta</p>
<p>夜（よる）に咲（さ）いた「夜（よる）に咲（さ）いた」</p>
<p>在夜空中绽放后</p>
<p>yo ru ni sa i te   yo ru ni sa i te</p>
<p>夜（よる）に咲（さ）いて「夜（よる）に咲（さ）いて」</p>
<p>在夜空中绽放后</p>
<p>shi <u>zu ka ni</u> ki e ta   shi <u>zu ka ni</u> ki e ta</p>
<p>静（しず）かに消（き）えた「静（しず）かに消（き）えた」　</p>
<p>又静静地消失了</p>
<p>ha na sa na i de   ha na re na i de</p>
<p>离（はな）さないで「离（はな）れないで」</p>
<p>请不要离开啊</p>
<p>moo su ko shi da ke   moo su ko shi da ke</p>
<p>もう少（すこ）しだけ「もう少（すこ）しだけ」</p>
<p>再稍微多一会儿吧</p>
<p>moo su ko shi da ke   <u>ko no</u> ma ma de</p>
<p>もう少（すこ）しだけ このままで</p>
<p>再稍微多一会儿吧</p>
<hr />
<p>a no hi   mi wa ta shi ta   na gi sa wo</p>
<p>あの日(ひ)见（み）わたした渚（なぎさ）を</p>
<p>那天眺望着的海岸</p>
<p>i ma moo mo <u>i da</u> sun da</p>
<p>今（いま）も思（おも）い出（だ）すんだ</p>
<p>直至今日仍能想起</p>
<p>su na no~<u>u e</u> ni   ki za <span>n</span> da ko to ba</p>
<p>砂（すな）の上（うえ）に刻（きざ）んだ言葉（ことば）</p>
<p>在沙滩上刻下的话语</p>
<p>ki mi no <span>u</span> shi~ro su ga ta</p>
<p>君（きみ）の後（うし）ろ姿（すがた）</p>
<p>寄宿于此地</p>
<p>pa tto hi ka tte sa i ta</p>
<p>パッと光（ひかり）って咲（さ）いた</p>
<p>不为人知 虚幻凋零的结局</p>
<p>ha na bi wo mi te i ta</p>
<p>花火（はなび）を见（み）てた</p>
<p>烟花映入眼帘</p>
<p>ki tto ma da   o wa ra <u>na i</u> na tsu ga</p>
<p>きっとまだ终（お）わらない夏（なつ）が</p>
<p>还未结束的夏天 一定会将</p>
<p><u>a i</u> <u>ma i</u> na ko ko ro wo   too ka shi te tsu na i da</p>
<p>暧昧（あいまい）な心（こころ）を透过（とうか）して繋（つな）いだ</p>
<p>暧昧的心结融化相连在一起</p>
<p><u>ko no yo ru ga</u>   <u>tsu zu <span>i</span></u>   te ho shi ka tta</p>
<p>この夜（よる）が続（つづ）いて欲（ほ）しかった　</p>
<p>愿今夜永不结束</p>]]></content>
    <author><name>Zowely</name></author>
    <category term="日语歌曲" />
    <category term="日语歌曲" />
    <category term="动漫音乐" />
    <category term="歌词翻译" />
  </entry>
  <entry>
    <title>孤独摇滚!插曲《ギターと孤独と蒼い惑星》日语及罗马音发音</title>
    <link href="https://www.lansganbs.cn/posts/%E6%AD%8C%E6%9B%B2/%E5%AD%A4%E7%8B%AC%E6%91%87%E6%BB%9A-%E6%8F%92%E6%9B%B2%E3%82%AE%E3%82%BF%E3%83%BC%E3%81%A8%E5%AD%A4%E7%8B%AC%E3%81%A8%E8%92%BC%E3%81%84%E6%83%91%E6%98%9F%E6%97%A5%E8%AF%AD%E5%8F%8A%E7%BD%97%E9%A9%AC%E9%9F%B3%E5%8F%91%E9%9F%B3/" rel="alternate" type="text/html" />
    <id>https://www.lansganbs.cn/posts/%E6%AD%8C%E6%9B%B2/%E5%AD%A4%E7%8B%AC%E6%91%87%E6%BB%9A-%E6%8F%92%E6%9B%B2%E3%82%AE%E3%82%BF%E3%83%BC%E3%81%A8%E5%AD%A4%E7%8B%AC%E3%81%A8%E8%92%BC%E3%81%84%E6%83%91%E6%98%9F%E6%97%A5%E8%AF%AD%E5%8F%8A%E7%BD%97%E9%A9%AC%E9%9F%B3%E5%8F%91%E9%9F%B3/</id>
    <published>2025-07-02T00:00:00.000Z</published>
    <updated>2025-07-02T18:03:52.000Z</updated>
    <summary>【歌词及罗马音发音】孤独摇滚!插曲 - 「ギターと孤独と蒼い惑星」 / 纽带乐队</summary>
    <content type="html"><![CDATA[<p>【歌词及罗马音发音】孤独摇滚!插曲 - 「ギターと孤独と蒼い惑星」 / 纽带乐队</p>
<p>填词：ZAQ</p>
<p>谱曲：音羽-otoha-</p>
<p>编曲：akkin</p>
<p>歌曲原唱：纽带乐队</p>
<p></p><figure><img src="https://www.lansganbs.cn/images/article/%E6%97%A5%E8%AF%AD%E6%AD%8C%E6%9B%B2/%E5%AD%A4%E7%8B%AC%E6%91%87%E6%BB%9A!%E6%8F%92%E6%9B%B2%E3%80%8A%E3%82%AE%E3%82%BF%E3%83%BC%E3%81%A8%E5%AD%A4%E7%8B%AC%E3%81%A8%E8%92%BC%E3%81%84%E6%83%91%E6%98%9F%E3%80%8B%E6%97%A5%E8%AF%AD%E5%8F%8A%E7%BD%97%E9%A9%AC%E9%9F%B3%E5%8F%91%E9%9F%B3/%E5%AD%A4%E7%8B%AC%E6%91%87%E6%BB%9A.png" alt="alt text" /><figcaption>alt text</figcaption></figure><p></p>
<p><a href="https://music.apple.com/cn/album/%E3%82%AE%E3%82%BF%E3%83%BC%E3%81%A8%E5%AD%A4%E7%8B%AC%E3%81%A8%E8%92%BC%E3%81%84%E6%83%91%E6%98%9F/1650726162?i=1650726430" target="_blank">ギターと孤独と蒼い惑星</a></p>
<hr />
<p>to tsu <u>ze n</u>   <u>hu ru yu u</u>~da chi a a</p>
<p>突然(とつぜん)降(ふ)る夕立(ゆうだち)あぁ</p>
<p>突然下起的骤雨</p>
<p>ka sa   mo na   i ya i ya</p>
<p>傘(かさ) もな いや嫌(いや)</p>
<p>讨厌没带雨伞的自己</p>
<p>so ra no go ki ge <span>n</span> na <span>n</span> ka shi ra~<u>na i</u></p>
<p>空(そら)のご機嫌(きげん)なんか知(し)らない</p>
<p>才懒得在乎天空的心情</p>
<p>ki <u>se tsu</u>   <u>no ka wa ri</u>   me no hu ku wa</p>
<p>季節(きせつ)の変(か)わり目(め)の服(ふく)は</p>
<p>季节交换之际</p>
<p>na ni   ki rya   i~<u>i n</u> da ro</p>
<p>何(なに) 着(き)リゃ いいんだろ</p>
<p>该换上什么外衣</p>
<p>ha ru to a ki   do ko i ccha tta <span>n</span> da yo</p>
<p>春(はる)と秋(あき) どこいっちゃったんだよ</p>
<p>春天和秋天 究竟消失去了何处</p>
<p>i ki mo   de ki   na i   jyo o ho <span>o</span> no a tsu ryo ku</p>
<p>息(いき)も出来(でき)ない 情報(じょうほう)の圧力(あつリょく)</p>
<p>资讯时代的压力 令人喘不过气</p>
<p><u>me ma</u> i no   ra se <span>n</span> da   wa ta shi wa do ko ni <span>i</span> ru</p>
<p>めまいの  螺旋(らせん)だ  わたしはどこにいる</p>
<p>陷入了令人晕眩的螺旋之中 我又到底身在何方</p>
<p>ko n na ni   ko n na ni   i ki no o to ga su ru no ni</p>
<p>こんなに こんなに 息(いき)の音(おと)がするのに</p>
<p>明明充满如此多的 如此多的呼吸声</p>
<p><u>he n</u>~da ne    se   ka i no   o to ga   shi na i</p>
<p>変(へん)だね 世界(せかい)の音(おと)が しない</p>
<p>真奇怪啊 世界却悄然沉寂</p>
<p>ta ri na <span>i</span>   ta ri na <span>i</span></p>
<p>足(た)リない 足(た)リない</p>
<p>还不够 还不够</p>
<p>da re ni mo ki zu ka re na i</p>
<p>誰(だれ)にも気(き~)づかれない</p>
<p>还没有任何人发现</p>
<p><u>na gu ri ga ki</u>   mi ta <span>i</span>   na o to</p>
<p>殴(なぐ)リ書(が)きみたい な音(おと)</p>
<p>杂乱无章的音色</p>
<p>da se na i  jyo o ta i de sa ke n da yo</p>
<p>出(だ)せない 状態(じょうたい)で叫(さけ)んだよ</p>
<p>不成声低咆哮着</p>
<p>a ri   no ma ma   <u>na n</u> te</p>
<p>「あリのまま」なんて</p>
<p>所谓的”真实自我”</p>
<p>da re ni mi se   <span>lu</span> <span>n</span>~da</p>
<p>誰(だれ)に見(み)せ るんだ</p>
<p>又该让谁看见</p>
<p>ba ka   na wa <u>ta shi wa</u>   u ta u da ke</p>
<p>馬鹿(ばか)なわたしは歌(うた)うだけ</p>
<p>傻瓜如我唯有放声高歌</p>
<p>bu chi ma ke ccha <u>o o</u> ka   <u>ho shi</u> ni</p>
<p>ぶちまけちゃおうか 星(ほし)に</p>
<p>对着星星倾吐一切吧</p>
<p>e ri ku sa   ni ha ri ka e   <u>ru sa gyo <span>u</span></u>   mo na nn   to na   <u>ku na nn</u> da</p>
<p>エリクサーに張(は)リ替(か)える作業(さぎょう)もなんとなくなんだ</p>
<p>即便换上伊利克斯琴弦音色也不尽人意</p>
<p>ka ke ta tsu ne wo su ko shi sa <u>wa ru</u></p>
<p>欠(か)けた爪(つめ)を少(すこ)し触(さわ)る</p>
<p>轻抚那边缘缺损的指甲</p>
<p>ha nn ke <span>i</span> <u>sa nn</u>   <u>bya ku</u> mi ri no <u>ka ra da</u>   de hi   sshi ni   na i te ru</p>
<p>半径(はんけい)300(さんびゃく)mm(みリ)の体(からだ)で必死(ひっし)に嗚(な)いてる</p>
<p>以这半径300mm的身躯拼命奏响吉他</p>
<p><u>o nn</u> <u>ga ku ni</u> to ccya ko ko ga ti <u>kyu u</u> <u>da na</u></p>
<p>音楽(おんがく)にとっちゃココが地球(ちきゅう)だな</p>
<p>对于音乐来说这副身躯便是地球啊</p>
<p><u>ku u</u> ki wo   ni gi (i) tte   so ra wo na gu ru yo</p>
<p>空気(くうき)を握(にぎ)って 空(そら)を殴(なぐ)るよ</p>
<p>握紧空气 挥向天空</p>
<p><u>na <span>nn</span></u> ni mo   o ki na i   wa ta shi wa mu ryo ku sa</p>
<p>なんにも起(お)きない わたしは無力(むリょく)さ</p>
<p>却无事发生 我是如此的无力啊</p>
<p>da ke do sa   so no te de   ko no te tsu wo ha ji <span>i</span> da ra</p>
<p>だけどさ その手(て)でこの鉄(てつ)を弾(はじ)いたら</p>
<p>但是啊 用这手弹起这铁弦时</p>
<p>na <u>ni ka ga</u>   ka wa (ra) tte   mi e da   yo u na</p>
<p>何(なに)かが変(か)わって見(み)えた ような</p>
<p>就好像能看到有什么发生变化一样啊</p>
<p>ma bu shi <span>i</span>   ma bu shi <span>i</span></p>
<p>眩(まぶ)しい 眩(まぶ)しい</p>
<p>眼前的万丈光芒</p>
<p>so <span>nn</span> na ni hi ka ru na yo</p>
<p>そんなに光(ひか)るなよ</p>
<p>请不要如此明亮啊</p>
<p><u>wa ta shi no da</u>   <u>sa <span>i</span></u>   <u>ka ge ga</u></p>
<p>わたしのダサい影(かげ)が</p>
<p>那只会更加凸显出</p>
<p><u>yo ri i</u>   <u>ro ko ku na</u>   tte shi ma <span>u</span> da ro</p>
<p>よリ色濃(いろこ)くなってしまうだろ</p>
<p>我那丑陋影子 不是吗</p>
<p>na nn de   ko nn na   a tsu ku   na ccya tte <span>nn</span> da to   ma <span>nn</span> na i</p>
<p>なんでこんな熱(あつ)くなっちゃってんだ 止(と)まんない</p>
<p>为何我内心会变得如此炽热 无法停止</p>
<p>ba ka   na wa <u>ta shi wa</u>   u ta u da ke</p>
<p>馬鹿(ばか)なわたしは歌(うた)うだけ</p>
<p>傻瓜如我唯有放声高歌</p>
<p>u ru sa i nn da tte   <u>shi nn</u> zo <span>u</span></p>
<p>うるさいんだって 心臓(しんぞう)</p>
<p>鼓动愈加喧嚣的心脏</p>
<p>a <u>o i</u>   wa ku se i hi to <u>ri bo</u> (o) chi</p>
<p>蒼(あお)い惑星(わくせい)ひとリぼっち</p>
<p>我在这蓝色星球上孤独一人</p>
<p>i <u>ppa i no</u>   o to wo ki i te~ki ta</p>
<p>いっぱいの音(おと)を聞(き)いてきた</p>
<p>聆听到了众多声音</p>
<p>ma wa   ri tsu zu ke te i <u>ku o</u> <u>ku ne <span>nn</span></u></p>
<p>回(まわ)リ続(つづ)けて  幾(いく)億(おく)年(ねん)</p>
<p>地球自转数亿年</p>
<p>i ssyu nn   de mo <span>i</span> <span>i</span> ka ra~a~a~</p>
<p>一瞬(いっしゅん) でもいいから ああ</p>
<p>所以一瞬也好啊</p>
<p>ki i te   ki ke yo</p>
<p>聞(き)いて 聴(き)けよ</p>
<p>请聆听 聆听我的声音啊</p>
<p>wa ta shi   wa ta shi   wa ta shi wa ko ko ni <span>i</span> ru</p>
<p>わたし わたし わたしはここにいる</p>
<p>孤独的 孤独的我就存在于此啊</p>
<p><u>na gu ri ga ki</u>   mi <u>ta i</u>   na o to</p>
<p>殴(なぐ)リ書(が)きみたい な音(おと)</p>
<p>杂乱无章的音色</p>
<p>da se <u>na i</u>   jyo o ta i de   sa ke n da yo</p>
<p>出(だ)せない 状態(じょうたい)で叫(さけ)んだよ</p>
<p>不成声低咆哮着</p>
<p><u>na nn ka ni</u> <u>na ru ta i</u> <u>na ru ta i</u></p>
<p>なんかになリたいなリたい</p>
<p>仅是想成为 想成为</p>
<p>na ni mo no ka de~i~i~</p>
<p>何者(なにもの)かでいい</p>
<p>无论什么人都好</p>
<p>ba ka   na wa <u>ta shi wa</u>   u ta u da ke</p>
<p>馬鹿(ばか)なわたしは歌(うた)うだけ</p>
<p>傻瓜如我唯有放声高歌</p>
<p>bu chi ma ke ccha <u>o o</u> ka   <u>ho shi</u> ni</p>
<p>ぶちまけちゃおうか 星(ほし)に</p>
<p>对着星星倾吐一切吧</p>]]></content>
    <author><name>Zowely</name></author>
    <category term="日语歌曲" />
    <category term="日语歌曲" />
    <category term="动漫音乐" />
    <category term="歌词翻译" />
  </entry>
  <entry>
    <title>中岛美嘉《僕が死のうと思ったのは》歌词及罗马音发音</title>
    <link href="https://www.lansganbs.cn/posts/%E6%AD%8C%E6%9B%B2/%E4%B8%AD%E5%B2%9B%E7%BE%8E%E5%98%89%E5%83%95%E3%81%8C%E6%AD%BB%E3%81%AE%E3%81%86%E3%81%A8%E6%80%9D%E3%81%A3%E3%81%9F%E3%81%AE%E3%81%AF%E6%AD%8C%E8%AF%8D%E5%8F%8A%E7%BD%97%E9%A9%AC%E9%9F%B3%E5%8F%91%E9%9F%B3/" rel="alternate" type="text/html" />
    <id>https://www.lansganbs.cn/posts/%E6%AD%8C%E6%9B%B2/%E4%B8%AD%E5%B2%9B%E7%BE%8E%E5%98%89%E5%83%95%E3%81%8C%E6%AD%BB%E3%81%AE%E3%81%86%E3%81%A8%E6%80%9D%E3%81%A3%E3%81%9F%E3%81%AE%E3%81%AF%E6%AD%8C%E8%AF%8D%E5%8F%8A%E7%BD%97%E9%A9%AC%E9%9F%B3%E5%8F%91%E9%9F%B3/</id>
    <published>2025-06-09T00:00:00.000Z</published>
    <updated>2025-06-09T14:41:37.000Z</updated>
    <summary>【歌词及罗马音发音】『僕が死のうと思ったのは（曾经我也想过一了百了）』 - 中岛美嘉</summary>
    <content type="html"><![CDATA[<p>【歌词及罗马音发音】『僕が死のうと思ったのは（曾经我也想过一了百了）』 - 中岛美嘉</p>
<p>填词：秋田弘</p>
<p>谱曲：秋田弘</p>
<p>编曲：出羽良彰</p>
<p>歌曲原唱：中岛美嘉</p>
<p></p><figure><img src="https://www.lansganbs.cn/images/article/%E6%97%A5%E8%AF%AD%E6%AD%8C%E6%9B%B2/%E6%88%91%E6%9B%BE%E7%BB%8F%E4%B9%9F%E6%83%B3%E8%BF%87%E4%B8%80%E4%BA%86%E7%99%BE%E4%BA%86/%E6%88%91%E6%9B%BE%E7%BB%8F%E4%B9%9F%E6%83%B3%E8%BF%87%E4%B8%80%E4%BA%86%E7%99%BE%E4%BA%86.png" alt="alt text" /><figcaption>alt text</figcaption></figure><p></p>
<p><a href="https://music.apple.com/cn/album/%E5%83%95%E3%81%8C%E6%AD%BB%E3%81%AE%E3%81%86%E3%81%A8%E6%80%9D%E3%81%A3%E3%81%9F%E3%81%AE%E3%81%AF-from-the-first-take/1598331321?i=1598331322" target="_blank">僕が死のうと思ったのは - From THE FIRST TAKE</a></p>
<hr />
<p>bo ku ga   <u>shi no</u> o to   <u>o mo</u> tta no wa</p>
<p>僕（ぼく）が死（し）のうと思（おも）ったのは</p>
<p>曾经我也想过一了百了</p>
<p><u>u mi ne ko ga sa nn~ba shi de</u>   na i ta ka ra</p>
<p>ウミネコが桟桥（さんばし）で鸣（な）いたから</p>
<p>因为有海猫在码头悲鸣</p>
<p>na mi no~<u>ma ni</u> <u>ma ni</u>   u ka nn de ki e ru</p>
<p>波（なみ）の随意（まにま）に浮（う）かんで消（き）える</p>
<p>随着浪花起伏消没</p>
<p>ka ko mo tu i <u>ba nn</u>~de   to nn~de yo ke</p>
<p>过去（かこ）も啄（つい）ばんで飞（と）んでいけ</p>
<p>叼啄着往昔飞离不见</p>
<p>bo ku ga   <u>shi no</u> o to   <u>o mo</u> tta no wa</p>
<p>仆（ぼく）が死（し）のうと思（おも）ったのは</p>
<p>曾经我也想过一了百了</p>
<p>ta nn <u>jyo o bi ni</u> <u>a nn</u> zu no <u>ha na ga sa i ta ka ra</u></p>
<p>诞生日（たんじょうび）に杏（あんず）の花（はな）が咲（さ）いたから</p>
<p>因为生日那天杏花开放</p>
<p>so no ko <u>mo re</u> <u>bi de</u>   u <u>ta ta</u> ne shi ta ra</p>
<p>その木漏（こも）れ日（び）でうたた寝（ね）したら</p>
<p>若是在那洒下的阳光里打盹</p>
<p>mu shi no shi <u>ga i</u> to   <u>tsu chi ni na re</u>~ru ka na</p>
<p>虫（むし）の死骸（しがい）と土（つち）になれるかな</p>
<p>能否与虫之死骸一同化为尘土呢</p>
<p>ha   <u>kka a</u> me   gyo <u>ko o</u> no <u>to o</u> <u>da i</u></p>
<p>薄荷饴（はっかあめ）渔港（ぎょこう）の灯台（とうだい）</p>
<p>薄荷糖 渔港的灯塔</p>
<p>sa bi ta <span>a</span> <u>chi kyo o</u>   <u>su te ta</u> ji te nn sya</p>
<p>锖（さ）びたアーチ桥（きょう） 舍（す）てた自転车（じてんしゃ）</p>
<p>生锈的拱桥 丢弃的自行车</p>
<p>mo ku <u>zo o</u> <u>no e ki no</u> <u>su to o</u> bu no ma e de</p>
<p>木造（もくぞう）の駅（えき）のストーブの前（まえ）で</p>
<p>木造车站的暖炉前</p>
<p><u>do ko ni mo</u>   <u>ta bi da te</u>   <u><span>na i</span> ko ko ro</u></p>
<p>どこにも旅立（たびだ）てない心（こころ）</p>
<p>无处可去的心灵</p>
<p><u>kyo o wa ma ru de</u>   <u>ki no <span>o</span> mi ta i da</u></p>
<p>今日（きょう）はまるで昨日（きのう）みたいだ</p>
<p>今天与昨天如此相像</p>
<p><u>a su wo</u>   <u>ka e ru na ra</u>   <u>kyo o <span>wo</span></u>   <u>ka e na kya</u></p>
<p>明日（あす）を変（か）えるなら 今日（きょう）を変（か）えなきゃ</p>
<p>想改变明天 须改变今天</p>
<p><u>wa ka</u> tte ru   <u>wa ka</u> tte ru   <u>ke re</u> do</p>
<p>分（わ）かってる 分（わ）かってる    けれど</p>
<p>我知道  我知道  但是</p>
<p>bo ku ga   <u>shi no</u> o to   <u>o mo</u> tta no wa</p>
<p>仆（ぼく）が死（し）のうと思（おも）ったのは</p>
<p>曾经我也想过一了百了</p>
<p>ko ko ro ga ka ra   ppo ni   na   tta ka ra</p>
<p>心（こころ）が空（から）っぽになったから</p>
<p>因为心中已空无一物</p>
<p>mi ta sa re na i to   na <u>i te</u>   <u>i ru</u> <u>no wa</u></p>
<p>満（み）たされないと泣（な）いているのは</p>
<p>感到空虚而哭泣</p>
<p>ki <u>tto mi ta sa re ta i to</u>   <u>ne ga</u> u ka ra</p>
<p>きっと満（み）たされたいと愿（ねが）うから</p>
<p>一定是渴望得到充实</p>
<hr />
<p>bo ku ga   <u>shi no</u> o to   <u>o mo</u> tta no wa</p>
<p>仆（ぼく）が死（し）のうと思（おも）ったのは</p>
<p>曾经我也想过一了百了</p>
<p>ku <u>tsu hi mo ga</u>   ho do ke   ta ka ra</p>
<p>靴纽（くつひも）が解（と）けたから</p>
<p>因为鞋带松开了</p>
<p>mu su bi na o <span>su</span> no wa   ni ga te na nn da yo</p>
<p>結（むす）びなおすのは苦手（にがて）なんだよ</p>
<p>不擅长重新系起</p>
<p>hi to to no   <u>tsu na ga ri mo ma ta</u>   <u>shi ka ri</u></p>
<p>人（ひ）との繋（つな）がりもまた然（しか）り</p>
<p>与人的牵绊亦是如此</p>
<p>bo ku ga   <u>shi no</u> o to   <u>o mo</u> tta no wa</p>
<p>仆（ぼく）が死（し）のうと思（おも）ったのは</p>
<p>曾经我也想过一了百了</p>
<p>syo o ne nn <u>ga bo ku wo</u>   <u>mi tsu me te i</u> ta ka ra</p>
<p>少年（しょうねん）が僕（ぼく）を見（み）つめていたから</p>
<p>因为少年深情凝视着我</p>
<p>be~ddo no u e de   do ge za shi te ru yo</p>
<p>ベッドの上（うえ）で土下座（どげざ）してるよ</p>
<p>抱膝跪在床上</p>
<p><u>a no hi no bo ku ni</u>   go me nn na sa i to</p>
<p>あの日（ひ）の僕（ぼく）にごめんなさいと</p>
<p>向那天的我说抱歉</p>
<p>pa so <u>ko nn</u> no   <u>u su</u> a ka ri</p>
<p>パソコンの薄明（うすあ）かり</p>
<p>屏幕的微光</p>
<p><u>jyo o</u> <u>ka i</u> <u>no he ya no</u> <u>se e</u> ka tsu o nn</p>
<p>上階（じょうかい）の部屋（へや）の生活音（せいかつおん）
楼上的噪音</p>
<p>i nn ta-<u>fo nn</u> no <u>cha i</u> mu no o to</p>
<p>インターフォンのチャイムの音（おと）</p>
<p>电话的铃声</p>
<p><u>mi mi wo</u>   <u>hu sa gu to ri</u>   <u>ka go no</u>   <u>syo o ne nn</u></p>
<p>耳（みみ）を塞（ふさ）ぐ鳥（とり）かごの少年（しょうねん）</p>
<p>紧塞住双耳 那笼中的少年</p>
<p><u>mi e na i te ki to</u>   <u>ta ta ka~tte ru</u></p>
<p>見（み）えない敵（てき）と戦（たたか）ってる</p>
<p>与看不见的敌人战斗着</p>
<p><u>ro ku jyo o</u>   <u>hi to ma no</u>   <u>do nn ki ho-te</u></p>
<p>六畳一間（ろくじょうひとま）のドンキホーテ</p>
<p>六畳一间的堂吉诃德</p>
<p>go-ru wa   <u>do o~se mi ni ku i</u>   mo no sa</p>
<p>ゴールはどうせ醜（みにく）いものさ</p>
<p>反正目的也是一样丑陋</p>
<p>bo ku ga   <u>shi no</u> o to   <u>o mo</u> tta no wa</p>
<p>僕（ぼく）が死（し）のうと思（おも）ったのは</p>
<p>曾经我也想过一了百了</p>
<p>tsu me ta i hi to to   <u>i wa re</u>   ta ka ra</p>
<p>冷（つめ）たい人（ひと）と言（い）われたから</p>
<p>因为被人说是冷血</p>
<p>a i sa re ta i to   na i te i ru no wa</p>
<p>愛（あい）されたいと泣（な）いているのは</p>
<p>想要被爱而哭泣</p>
<p><u>hi to no nu ku mo ri wo shi</u>   <u>tte shi ma</u> tta ka ra</p>
<p>人（ひと）の温（ぬく）もりを知（し）ってしまったから</p>
<p>是因为尝到了人的温暖</p>
<hr />
<p>bo ku ga   <u>shi no</u> o to   <u>o mo</u> tta no wa</p>
<p>僕（ぼく）が死（し）のうと思（おも）ったのは</p>
<p>曾经我也想过一了百了</p>
<p>a na ta ga   ki <u>re e</u> ni   wa ra u ka ra</p>
<p>あなたが綺麗（きれい）に笑（わら）うから</p>
<p>因为你灿烂的笑容</p>
<p>shi nu ko to ba ka ri   <u>ka nn ga <span>e</span> </u> te shi ma <span>u</span> no wa</p>
<p>死（し）ぬことばかり考（かんが）えてしまうのは</p>
<p>尽考虑着死的事</p>
<p>ki <u>tto i ki ru ko to ni</u>   <u>ma ji me</u> <u>su gi ru ka ra</u></p>
<p>きっと生（い）きる事（こと）に真面目（まじめ）すぎるから</p>
<p>一定是因为太过认真地活</p>
<p>bo ku ga   <u>shi no</u> o to   <u>o mo</u> tta no wa</p>
<p>僕（ぼく）が死（し）のうと思（おも）ったのは</p>
<p>曾经我也想过一了百了</p>
<p>ma da a na ta ni de a tte   na ka   tta ka ra</p>
<p>まだあなたに出会（であ）ってなかったから</p>
<p>因为还未与你相遇</p>
<p>a na ta~no yo o na   hi to ga~u ma re ta</p>
<p>あなたのような人（ひと）が生（う）まれた</p>
<p>因为有像你这样的人出生</p>
<p><u>se ka i wo su ko shi</u>   <u>su ki ni</u> na tta yo</p>
<p>世界（せかい）を少（すこ）し好（す）きになったよ</p>
<p>我对世界稍微有了好感</p>
<p>a na ta no yo o na   hi to ga i ki te ru</p>
<p>あなたのような人（ひと）が生（い）きてる</p>
<p>因为有像你这样的人活在这个世上</p>
<p>se ka <u>i ni su ko shi</u>   ki ta i~  su ru   yo~</p>
<p>世界（せかい）に少（すこ）し期待（きたい）するよ</p>
<p>我对世界稍微有了期待</p>]]></content>
    <author><name>Zowely</name></author>
    <category term="日语歌曲" />
    <category term="日语歌曲" />
    <category term="歌词翻译" />
  </entry>
  <entry>
    <title>米津玄师《Lemon》歌词及罗马音发音</title>
    <link href="https://www.lansganbs.cn/posts/%E6%AD%8C%E6%9B%B2/%E7%B1%B3%E6%B4%A5%E7%8E%84%E5%B8%88lemon%E6%AD%8C%E8%AF%8D%E5%8F%8A%E7%BD%97%E9%A9%AC%E9%9F%B3%E5%8F%91%E9%9F%B3/" rel="alternate" type="text/html" />
    <id>https://www.lansganbs.cn/posts/%E6%AD%8C%E6%9B%B2/%E7%B1%B3%E6%B4%A5%E7%8E%84%E5%B8%88lemon%E6%AD%8C%E8%AF%8D%E5%8F%8A%E7%BD%97%E9%A9%AC%E9%9F%B3%E5%8F%91%E9%9F%B3/</id>
    <published>2025-05-20T00:00:00.000Z</published>
    <updated>2025-05-20T16:43:28.000Z</updated>
    <summary>【歌词及罗马音发音】《Lemon》 - 米津玄师</summary>
    <content type="html"><![CDATA[<p>【歌词及罗马音发音】《Lemon》 - 米津玄师</p>
<p>填词：米津玄师</p>
<p>谱曲：米津玄师</p>
<p>编曲：米津玄师</p>
<p>歌曲原唱：米津玄师</p>
<p></p><figure><img src="https://www.lansganbs.cn/images/article/%E6%97%A5%E8%AF%AD%E6%AD%8C%E6%9B%B2/lemon/lemon.png" alt="alt text" /><figcaption>alt text</figcaption></figure><p></p>
<p><a href="https://music.apple.com/cn/album/lemon/1537460610?i=1537460612" target="_blank">Lemon</a></p>
<hr />
<p>yu me na~ra ba   do~re ho do   yo~ka tta de sho o</p>
<p>梦（ゆめ）ならば   どれほど   よかったでしょう</p>
<p>如果这一切都是梦境该有多好</p>
<p>i ma da   <span>ni</span>~a na ta   no~ko to wo   <u>yu <span>me</span></u>~ni mi ru</p>
<p>未（いま）だ   にあなた   のことを   梦（ゆめ）にみる</p>
<p>至今仍能与你在梦中相遇</p>
<p>wa su re~ta mo   no~wo to ri   ni~ka e ru   <u>yo <span>o</span></u> ni</p>
<p>忘（わす）れた物（もの）   を取（と）り   に 帰（かえ）る   ように</p>
<p>如同取回遗忘之物一般</p>
<p>fu ru bi~  ta o mo i de no <span>ho</span> ko   ri wo~ha <u>ra <span>u</span></u></p>
<p>古（ふる）びた思（おも）い出（で）の埃（ほこり）  を払（はら）う</p>
<p>细细拂去将回忆覆盖的尘埃</p>
<p>mo do ra~na i   shi~a wa se   ga~a ru ko   to~wo</p>
<p>戻（もど）らない     幸（しあわ）せ    があることを</p>
<p>这世间亦有无法挽回的幸福</p>
<p>sa i go   ni~a na ta~ga   o shi e   te~ku re ta</p>
<p>最後（さいご）  にあなた   が教（おし）え   てくれた</p>
<p>最终是你让我懂得这一点</p>
<p>i e zu~ni ka   ku~shi te ta   ku~ra i ka ko~mo</p>
<p>言（い）えずに隠（かく）   してた   昏（くら）い過去（かこ）も</p>
<p>那些未对他人提及过的黑暗往事</p>
<p>a na ta   ga~<u>i na kya</u>   e i e <span>n</span> <span>ni</span>   ku ra   <span>i</span>~ma ma</p>
<p>あなた   がいなきゃ 永远（えいえん）に   昏（くら）いまま</p>
<p>如果不曾有你的话 它们将永远沉睡在黑暗中</p>
<p>ki tto <u>mo <span>o</span></u>   ko re i~jyo o   ki~zu tsu   ku ko to na do</p>
<p>きっともうこれ以上（いじょう）  傷（きず）つくことなど</p>
<p>比这更令人难过的事情</p>
<p>a~<u>ri wa</u>   shi na i~to wa   ka tte i ru</p>
<p>ありはしないとわ   かっている</p>
<p>我知道不可能存在</p>
<p>a no hi no ka na   shi mi   sa e</p>
<p>あの日（ひ）の悲（かな）   しみさえ</p>
<p>那日的悲伤</p>
<p>a no hi no ku ru   shi mi   sa e</p>
<p>あの日（ひ）の苦（くる）   しみさえ</p>
<p>与那日的痛苦</p>
<p>so no su be te wo   a i   shi <span>te</span> ta   a <span>na</span>   ta to   to mo ni</p>
<p>そのすべてを   爱（あい）してた   あなたとともに</p>
<p>连同深爱着这一切的你</p>
<p>mu ne ni no   ko ri   ha na   re na</p>
<p>胸（むね）に残（のこ）り   離（はな）れない</p>
<p>化作了深深烙印在我心中的</p>
<p>i ni ga i <u>re mo n</u>   no ni~o i</p>
<p>苦（にが）いレモン   の匂（にお）い</p>
<p>苦涩柠檬的香气</p>
<p>a me ga   fu ri ya mu ma <span>de</span> wa   ka e   re na i</p>
<p>雨（あめ）が降（ふ）り止（や）むまでは      帰（かえ）れない</p>
<p>在雨过天晴前都无法归去</p>
<p>i ma de mo   a na ta <span>wa</span>   wa ta   shi no   hi ka~ri</p>
<p>今（いま）でもあなたはわたしの光（ひかり）</p>
<p>时至今日 你仍是我的光芒</p>
<hr />
<p>ku ra ya mi de   a na ta no   se wo na zo~tta</p>
<p>暗闇（くらやみ）で   あなたの   背（せ）をなぞった</p>
<p>在黑暗中追寻着你的身影</p>
<p>so no <u>ri <span>n</span></u>   <span>ka</span> ku wo <u>se <span>n</span></u>   <u>me <span>i</span></u> ni o bo   <span>e</span> te i ru</p>
<p>その輪郭（りんかく）を鮮明（せんめい）   に 覚（おぼ）   えている</p>
<p>那轮廓至今仍鲜明地刻印于心</p>
<p>u ke to me ki   re <u>na <span>i</span></u> mo no   to de a u   ta bi</p>
<p>受（う）け止（と）めき   れないもの   と出会（であ）うたび</p>
<p>每当遇到无法承受的苦痛时</p>
<p>a fu re   te ya ma na i no <span>wa</span>   na <span>mi</span>   da da~ke</p>
<p>溢（あふ）れ   てやまないのは   涙（なみだ）だけ</p>
<p>总不禁泪如泉涌</p>
<p>na <u>ni wo</u>   shi te i ta no   na <u>ni wo</u>   mi te i ta no</p>
<p>何（なに）をしていたの 何（なに）を見（み）ていたの</p>
<p>你都经历过什么 又目睹过什么呢</p>
<p>wa~ta shi   no shi ra na i yo ko   ga o de</p>
<p>わたし   の知（し）らない   横顔（よこがお）で</p>
<p>脸上浮现着我不曾见过的神情</p>
<p>do ko ka de a na   ta ga   i ma</p>
<p>どこかであな   たが今（いま）</p>
<p>如果你正在什么地方</p>
<p>wa ta shi to o na   ji yo   o na</p>
<p>わたしと同（おな）   じ 様（よう）な</p>
<p>与我一样</p>
<p>na mi da ni ku re   sa bi   <u> shi <span>sa</span> no</u>   na <span>ka</span> ni i ru na ra</p>
<p>涙（なみだ）にくれ   淋（さび）しさの   中（なか）にいるなら</p>
<p>终日过着以泪洗面的寂寞生活的话</p>
<p>wa ta shi no ko to   na do do u   ka~<u>wa su re te</u>   ku da sa i</p>
<p>わたしのこと   などどう   か忘（わす）れてください</p>
<p>就请你将我的一切全部遗忘吧</p>
<p>so n na ko to <span>wo</span>   ko ko ro ka ra   <u>ne ga u</u>   ho do~ni</p>
<p>そんなことを   心（こころ）から   願（ねが）う   ほどに</p>
<p>这是我发自内深处唯一的祈愿</p>
<p>i ma de mo   a na ta wa   wa ta   shi no   hi ka~ri</p>
<p>今（いま）でも   あなたはわたしの光（ひかり）</p>
<p>时至今日 你仍是我的光芒</p>
<p>ji   bu   n   <u>ga o</u>   <u>mo u</u>   yo   ri</p>
<p>自分（じぶん）が思（おも）うより</p>
<p>超出了我自己的想象</p>
<p><u>ko i</u>   wo   <u>shi te <span>i</span></u>   <u>ta a</u>   na   ta   ni</p>
<p>恋（こい）をしていたあなたに</p>
<p>我深深地恋慕着你</p>
<p>a   re   ka   <u>ra o</u>   mo u   yo u   ni</p>
<p>あれから思（おも）うように</p>
<p>自此每当想起你</p>
<p>i   ki   ga   de   ki   na   i</p>
<p>息（いき）ができない</p>
<p>都如同窒息般痛苦</p>
<p><u>a n na ni</u>   so    ba   <u>ni i</u>   ta   no   ni</p>
<p>あんなに   侧（そば）にいたのに</p>
<p>你曾亲密伴我身旁</p>
<p>ma   ru   de   u   so   mi   <u>ta i</u></p>
<p>まるで嘘（うそ）みたい</p>
<p>如今却如烟云般消散</p>
<p><u>to te mo</u>   wa~<u>su re</u>~ra   re   na   i</p>
<p>とても   忘（わす）れられない</p>
<p>我永远都不会将你遗忘</p>
<p>so   re   da   ke   ga   ta   shi   ka</p>
<p>それだけが確（たし）か</p>
<p>这是唯一能确定的</p>
<p>a no hi no ka na   shi mi   sa e</p>
<p>あの日（ひ）の悲（かな）しみさえ</p>
<p>那日的悲伤</p>
<p>a no hi no ku ru   shi mi   sa e</p>
<p>あの日（ひ）の苦（くる）しみさえ</p>
<p>与那日的痛苦</p>
<p>so no su be te wo   a i   shi <span>te</span> ta   a <span>na</span>   ta to   to mo ni</p>
<p>そのすべてを   爱（あい）してた   あなたと   ともに</p>
<p>连同深爱着这一切的你</p>
<p>mu ne ni no ko ri   ha na   re na</p>
<p>胸（むね）に残（のこ）り   離（はな）れない</p>
<p>化作了深深烙印在我心中的</p>
<p>i ni ga i <u> <span>re</span> mo n</u>   no ni~o i</p>
<p>苦（にが）いレモン   の匂（にお）い</p>
<p>苦涩柠檬的香气</p>
<p>a me ga   hu ri ya mu ma de wa   ka e~re na i</p>
<p>雨（あめ）が   降（ふ）り止（や）むまでは   帰（かえ）れない</p>
<p>在雨过天晴前都无法归去</p>
<p>ki ri wa ke ta   ka ji tsu   no ka   <u>ta ho <span>u</span></u>   no <span> <u>yo u</u> </span> ni</p>
<p>切（き）り分（わ）け   た果実（かじつ）の片方（かたほう）</p>
<p>の様（よう）に</p>
<p>如同被切开的半个柠檬一般</p>
<p>i ma de mo   a na ta wa   wa ta   shi no~hi ka~ri</p>
<p>今（いま）でも   あなたはわたしの光（ひかり）</p>
<p>时至今日 你仍是我的光芒</p>]]></content>
    <author><name>Zowely</name></author>
    <category term="日语歌曲" />
    <category term="日语歌曲" />
    <category term="歌词翻译" />
  </entry>
  <entry>
    <title>SEU 2025 SUMMER PRETEST STD</title>
    <link href="https://www.lansganbs.cn/posts/%E7%AE%97%E6%B3%95%E7%AB%9E%E8%B5%9B/seu-2025-summer-pretest-std/" rel="alternate" type="text/html" />
    <id>https://www.lansganbs.cn/posts/%E7%AE%97%E6%B3%95%E7%AB%9E%E8%B5%9B/seu-2025-summer-pretest-std/</id>
    <published>2025-05-20T00:00:00.000Z</published>
    <updated>2025-05-20T19:24:52.000Z</updated>
    <summary>东南大学校赛验题STD</summary>
    <content type="html"><![CDATA[<p><a href="https://codeforces.com/gym/105895" target="_blank">The 21st Southeast University Programming Contest (Summer)</a></p>
<p><strong>下文链接均为验题链接</strong>，题目序号并<strong>不与正赛题目一一对应</strong>，请在上述正赛链接中<strong>自行对照</strong>。</p>
<p><strong>题目难度</strong>：</p>
<p>A &lt; E J M &lt; I &lt; N F &lt; D G B &lt; L H</p>
<p><strong>Created And Written By</strong>:</p>
<p>Huan_YP2002, limithym, jackylova_fan_fan_fan</p>
<p><strong>Tester</strong>:</p>
<p>Tobo, Anonyme, thisislike_fan, Zowely, sakuya_maid_fans, xukuan, 9756, king_of_gamers</p>
<p><strong>Std</strong>:</p>
<p>Anonyme:</p>
<ul>
<li><a href="https://codeforces.com/gym/574109/problem/D" target="_blank">Kings Game (Hard Version)</a></li>
<li><a href="https://codeforces.com/gym/574109/problem/I" target="_blank">So Far Away</a></li>
</ul>
<p>Tobo:</p>
<ul>
<li><a href="https://codeforces.com/gym/574109/problem/L" target="_blank">LRU is Best? (Hard Version)</a></li>
<li><a href="https://codeforces.com/gym/574109/problem/N" target="_blank">混沌数字</a></li>
</ul>
<p>thisislike_fan:</p>
<ul>
<li><a href="https://codeforces.com/gym/574109/problem/B" target="_blank">Royale Bataille</a></li>
<li><a href="https://codeforces.com/gym/574109/problem/F" target="_blank">Yuhina City</a></li>
</ul>
<p>Zowely:</p>
<ul>
<li><a href="https://codeforces.com/gym/574109/problem/A" target="_blank">一卡通</a></li>
<li><a href="https://codeforces.com/gym/574109/problem/M" target="_blank">猫娘部署</a></li>
<li><a href="https://codeforces.com/gym/574109/problem/E" target="_blank">十六行诗</a></li>
<li><a href="https://codeforces.com/gym/574109/problem/J" target="_blank">MEX Should Be Same</a></li>
</ul>
<section><h1><a href="https://codeforces.com/gym/574109/problem/A" target="_blank">一卡通</a><a href="#一卡通"><span>#</span></a></h1><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>void</span><span> </span><span>solve</span><span>() {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>string</span><span> s;</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>cin </span><span>&gt;&gt;</span><span> s;</span></div></div><div><div><div>4</div></div><div><span>  </span><span>regex</span><span> </span><span>ss</span><span>(</span><span>"^21[34]</span><span>\\</span><span>d{2}(?!0000)</span><span>\\</span><span>d{4}$"</span><span>);</span></div></div><div><div><div>5</div></div><div><span>  </span><span>if</span><span> (</span><span>regex_match</span><span>(s, ss)) {</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>cout </span><span>&lt;&lt;</span><span> </span><span>"YES"</span><span> </span><span>&lt;&lt;</span><span> endl;</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>} </span><span>else</span><span> {</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>cout </span><span>&lt;&lt;</span><span> </span><span>"NO"</span><span> </span><span>&lt;&lt;</span><span> endl;</span></div></div><div><div><div>9</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>10</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section>
<section><h1><a href="https://codeforces.com/gym/574109/problem/B" target="_blank">Royale Bataille</a><a href="#royale-bataille"><span>#</span></a></h1><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>void</span><span> </span><span>solve</span><span>() {</span></div></div><div><div><div>2</div></div><div><span>    </span><span>int</span><span> n, m;</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>cin </span><span>&gt;&gt;</span><span> n </span><span>&gt;&gt;</span><span> m;</span></div></div><div><div><div>4</div></div><div><span>    </span><span>vector</span><span>&lt;</span><span>string</span><span>&gt; </span><span>a</span><span>(n);</span></div></div><div><div><div>5</div></div><div><span>    </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>0</span><span>; i </span><span>&lt;</span><span> n; i</span><span>++</span><span>) cin </span><span>&gt;&gt;</span><span> a[i];</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>    </span><span>const</span><span> </span><span>int</span><span> mod </span><span>=</span><span> </span><span>998244353</span><span>;</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>    </span><span>auto</span><span> process </span><span>=</span><span> [</span><span>&amp;</span><span>](</span><span>vector</span><span>&lt;</span><span>tuple</span><span>&lt;</span><span>int</span><span>, </span><span>int</span><span>, </span><span>int</span><span>, </span><span>int</span><span>, </span><span>int</span><span>, </span><span>int</span><span>&gt;&gt; </span><span>lim</span><span>, </span><span>int</span><span> </span><span>n</span><span>, </span><span>int</span><span> </span><span>m</span><span>) -&gt; </span><span>int</span><span> {</span></div></div><div><div><div>10</div></div><div><span>        </span><span>int</span><span> ans </span><span>=</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>11</div></div><div><span>        </span><span>auto</span><span> check </span><span>=</span><span> [</span><span>&amp;</span><span>](</span><span>int</span><span> </span><span>s</span><span>, </span><span>int</span><span> </span><span>t</span><span>, </span><span>int</span><span> </span><span>l</span><span>, </span><span>int</span><span> </span><span>r</span><span>) -&gt; </span><span>int</span><span> {</span></div></div><div><div><div>12</div></div><div><span>            </span><span>return</span><span> l </span><span>&lt;=</span><span> s </span><span>&amp;&amp;</span><span> r </span><span>&lt;=</span><span> t </span><span>&amp;&amp;</span><span> s </span><span>&lt;=</span><span> r;</span></div></div><div><div><div>13</div></div><div><span><span>        </span></span><span>};</span></div></div><div><div><div>14</div></div><div><span>        </span><span>int</span><span> flag1 </span><span>=</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>15</div></div><div><span>        </span><span>int</span><span> flag2 </span><span>=</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>16</div></div><div><span>        </span><span>int</span><span> last </span><span>=</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>17</div></div><div><span>        </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>0</span><span>; i </span><span>&lt;</span><span> lim.</span><span>size</span><span>(); i</span><span>++</span><span>) {</span></div></div><div><div><div>18</div></div><div><span>            </span><span>auto</span><span> [left, right, left_inner, right_inner, all_r, all_g] </span><span>=</span><span> lim[i];</span></div></div><div><div><div>19</div></div><div><span>            </span><span>if</span><span> (</span><span>!</span><span>all_g) last </span><span>=</span><span> i </span><span>+</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>20</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>21</div></div><div><span>        </span><span>vector</span><span>&lt;</span><span>vector</span><span>&lt;</span><span>int</span><span>&gt;&gt; </span><span>f</span><span>(m, </span><span>vector</span><span>&lt;</span><span>int</span><span>&gt;(m));</span></div></div><div><div><div>22</div></div><div><span>        </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>0</span><span>; i </span><span>&lt;</span><span> lim.</span><span>size</span><span>(); i</span><span>++</span><span>) {</span></div></div><div><div><div>23</div></div><div><span>            </span><span>auto</span><span> [left, right, left_inner, right_inner, all_r, all_g] </span><span>=</span><span> lim[i];</span></div></div><div><div><div>24</div></div><div>
</div></div><div><div><div>25</div></div><div><span>            </span><span>vector</span><span>&lt;</span><span>vector</span><span>&lt;</span><span>int</span><span>&gt;&gt; </span><span>tf</span><span>(m, </span><span>vector</span><span>&lt;</span><span>int</span><span>&gt;(m));</span></div></div><div><div><div>26</div></div><div><span>            </span><span>for</span><span> (</span><span>int</span><span> l1 </span><span>=</span><span> </span><span>0</span><span>; l1 </span><span>&lt;</span><span> m; l1</span><span>++</span><span>) {</span></div></div><div><div><div>27</div></div><div><span>                </span><span>for</span><span> (</span><span>int</span><span> r1 </span><span>=</span><span> l1; r1 </span><span>&lt;</span><span> m; r1</span><span>++</span><span>) {</span></div></div><div><div><div>28</div></div><div><span><span>                    </span></span><span>//l1 &lt;= r2 &lt;= r1</span></div></div><div><div><div>29</div></div><div><span><span>                    </span></span><span>//l2 &lt;= l1</span></div></div><div><div><div>30</div></div><div><span><span>                    </span></span><span>(tf[l1][r1] </span><span>+=</span><span> f[l1][r1]) </span><span>%=</span><span> mod;</span></div></div><div><div><div>31</div></div><div><span>                    </span><span>if</span><span> (l1) (tf[l1][l1 </span><span>-</span><span> </span><span>1</span><span>] </span><span>-=</span><span> f[l1][r1]) </span><span>%=</span><span> mod;</span></div></div><div><div><div>32</div></div><div><span><span>                </span></span><span>}</span></div></div><div><div><div>33</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>34</div></div><div><span>            </span><span>for</span><span> (</span><span>int</span><span> l </span><span>=</span><span> m </span><span>-</span><span> </span><span>1</span><span>; l </span><span>&gt;=</span><span> </span><span>0</span><span>; l</span><span>--</span><span>) {</span></div></div><div><div><div>35</div></div><div><span>                </span><span>for</span><span> (</span><span>int</span><span> r </span><span>=</span><span> m </span><span>-</span><span> </span><span>1</span><span>; r </span><span>&gt;=</span><span> </span><span>0</span><span>; r</span><span>--</span><span>) {</span></div></div><div><div><div>36</div></div><div><span>                    </span><span>if</span><span> (l </span><span>+</span><span> </span><span>1</span><span> </span><span>&lt;</span><span> m) (tf[l][r] </span><span>+=</span><span> tf[l </span><span>+</span><span> </span><span>1</span><span>][r]) </span><span>%=</span><span> mod;</span></div></div><div><div><div>37</div></div><div><span>                    </span><span>if</span><span> (r </span><span>+</span><span> </span><span>1</span><span> </span><span>&lt;</span><span> m) (tf[l][r] </span><span>+=</span><span> tf[l][r </span><span>+</span><span> </span><span>1</span><span>]) </span><span>%=</span><span> mod;</span></div></div><div><div><div>38</div></div><div><span>                    </span><span>if</span><span> (</span><span>max</span><span>(l, r) </span><span>+</span><span> </span><span>1</span><span> </span><span>&lt;</span><span> m) (tf[l][r] </span><span>-=</span><span> tf[l </span><span>+</span><span> </span><span>1</span><span>][r </span><span>+</span><span> </span><span>1</span><span>]) </span><span>%=</span><span> mod;</span></div></div><div><div><div>39</div></div><div><span><span>                </span></span><span>}</span></div></div><div><div><div>40</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>41</div></div><div><span>            </span><span>for</span><span> (</span><span>int</span><span> l </span><span>=</span><span> </span><span>0</span><span>; l </span><span>&lt;</span><span> m; l</span><span>++</span><span>) {</span></div></div><div><div><div>42</div></div><div><span>                </span><span>for</span><span> (</span><span>int</span><span> r </span><span>=</span><span> l; r </span><span>&lt;</span><span> m; r</span><span>++</span><span>) {</span></div></div><div><div><div>43</div></div><div><span>                    </span><span>if</span><span> (left </span><span>&lt;=</span><span> l </span><span>&amp;&amp;</span><span> l </span><span>&lt;=</span><span> left_inner </span><span>&amp;&amp;</span><span> right_inner </span><span>&lt;=</span><span> r </span><span>&amp;&amp;</span><span> r </span><span>&lt;=</span><span> right) {</span></div></div><div><div><div>44</div></div><div>
</div></div><div><div><div>45</div></div><div><span><span>                    </span></span><span>} </span><span>else</span><span> {</span></div></div><div><div><div>46</div></div><div><span><span>                        </span></span><span>tf[l][r] </span><span>=</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>47</div></div><div><span><span>                    </span></span><span>}</span></div></div><div><div><div>48</div></div><div><span><span>                </span></span><span>}</span></div></div><div><div><div>49</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>50</div></div><div><span>            </span><span>if</span><span> (flag1) {</span></div></div><div><div><div>51</div></div><div><span>                </span><span>for</span><span> (</span><span>int</span><span> l </span><span>=</span><span> left; l </span><span>&lt;=</span><span> left_inner; l</span><span>++</span><span>) {</span></div></div><div><div><div>52</div></div><div><span>                    </span><span>for</span><span> (</span><span>int</span><span> r </span><span>=</span><span> </span><span>max</span><span>(l, right_inner); r </span><span>&lt;=</span><span> right; r</span><span>++</span><span>) {</span></div></div><div><div><div>53</div></div><div><span>                        </span><span>if</span><span> (i </span><span>&amp;&amp;</span><span> r </span><span>+</span><span> </span><span>1</span><span> </span><span>&lt;</span><span> m) </span><span>continue</span><span>;</span></div></div><div><div><div>54</div></div><div><span><span>                        </span></span><span>tf[l][r]</span><span>++</span><span>;</span></div></div><div><div><div>55</div></div><div><span><span>                        </span></span><span>tf[l][r] </span><span>%=</span><span> mod;</span></div></div><div><div><div>56</div></div><div><span><span>                    </span></span><span>}</span></div></div><div><div><div>57</div></div><div><span><span>                </span></span><span>}</span></div></div><div><div><div>58</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>59</div></div><div><span>            </span><span>if</span><span> (i </span><span>+</span><span> </span><span>1</span><span> </span><span>&gt;=</span><span> last) {</span></div></div><div><div><div>60</div></div><div><span>                </span><span>for</span><span> (</span><span>int</span><span> l </span><span>=</span><span> </span><span>0</span><span>; l </span><span>&lt;</span><span> m; l</span><span>++</span><span>) {</span></div></div><div><div><div>61</div></div><div><span>                    </span><span>for</span><span> (</span><span>int</span><span> r </span><span>=</span><span> l; r </span><span>&lt;</span><span> m; r</span><span>++</span><span>) {</span></div></div><div><div><div>62</div></div><div><span>                        </span><span>if</span><span> (i </span><span>+</span><span> </span><span>1</span><span> </span><span>&lt;</span><span> n </span><span>&amp;&amp;</span><span> l) </span><span>continue</span><span>;</span></div></div><div><div><div>63</div></div><div><span><span>                        </span></span><span>ans </span><span>+=</span><span> tf[l][r];</span></div></div><div><div><div>64</div></div><div><span><span>                        </span></span><span>ans </span><span>%=</span><span> mod;</span></div></div><div><div><div>65</div></div><div><span><span>                    </span></span><span>}</span></div></div><div><div><div>66</div></div><div><span><span>                </span></span><span>}</span></div></div><div><div><div>67</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>68</div></div><div><span><span>            </span></span><span>flag1 </span><span>&amp;=</span><span> all_r;</span></div></div><div><div><div>69</div></div><div><span><span>            </span></span><span>flag2 </span><span>&amp;=</span><span> all_g;</span></div></div><div><div><div>70</div></div><div><span><span>            </span></span><span>f.</span><span>swap</span><span>(tf);</span></div></div><div><div><div>71</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>72</div></div><div><span><span>        </span></span><span>//out2(flag1, flag2);</span></div></div><div><div><div>73</div></div><div><span><span>        </span></span><span>ans </span><span>+=</span><span> flag1 </span><span>+</span><span> flag2;</span></div></div><div><div><div>74</div></div><div><span><span>        </span></span><span>ans </span><span>%=</span><span> mod;</span></div></div><div><div><div>75</div></div><div><span><span>        </span></span><span>ans </span><span>+=</span><span> mod;</span></div></div><div><div><div>76</div></div><div><span><span>        </span></span><span>ans </span><span>%=</span><span> mod;</span></div></div><div><div><div>77</div></div><div><span>        </span><span>return</span><span> ans;</span></div></div><div><div><div>78</div></div><div><span><span>    </span></span><span>};</span></div></div><div><div><div>79</div></div><div><span>    </span><span>auto</span><span> solve_row </span><span>=</span><span> [</span><span>&amp;</span><span>]() -&gt; </span><span>void</span><span> {</span></div></div><div><div><div>80</div></div><div><span>        </span><span>vector</span><span>&lt;</span><span>tuple</span><span>&lt;</span><span>int</span><span>, </span><span>int</span><span>, </span><span>int</span><span>, </span><span>int</span><span>, </span><span>int</span><span>, </span><span>int</span><span>&gt;&gt; ans;</span></div></div><div><div><div>81</div></div><div><span>        </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>0</span><span>; i </span><span>&lt;</span><span> m; i</span><span>++</span><span>) {</span></div></div><div><div><div>82</div></div><div><span>            </span><span>int</span><span> left </span><span>=</span><span> </span><span>0</span><span>, right </span><span>=</span><span> n </span><span>-</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>83</div></div><div><span>            </span><span>int</span><span> left_inner </span><span>=</span><span> n </span><span>-</span><span> </span><span>1</span><span>, right_inner </span><span>=</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>84</div></div><div><span>            </span><span>int</span><span> flag </span><span>=</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>85</div></div><div><span>            </span><span>for</span><span> (</span><span>int</span><span> j </span><span>=</span><span> </span><span>0</span><span>; j </span><span>&lt;</span><span> n; j</span><span>++</span><span>) {</span></div></div><div><div><div>86</div></div><div><span>                </span><span>if</span><span> (a[j][i] </span><span>!=</span><span> </span><span>'?'</span><span>) {</span></div></div><div><div><div>87</div></div><div><span>                    </span><span>if</span><span> (a[j][i] </span><span>==</span><span> </span><span>'0'</span><span>) {</span></div></div><div><div><div>88</div></div><div><span>                        </span><span>if</span><span> (</span><span>!</span><span>flag) {</span></div></div><div><div><div>89</div></div><div><span><span>                            </span></span><span>left_inner </span><span>=</span><span> right_inner </span><span>=</span><span> j;</span></div></div><div><div><div>90</div></div><div><span><span>                        </span></span><span>} </span><span>else</span><span> {</span></div></div><div><div><div>91</div></div><div><span><span>                            </span></span><span>right_inner </span><span>=</span><span> j;</span></div></div><div><div><div>92</div></div><div><span><span>                        </span></span><span>}</span></div></div><div><div><div>93</div></div><div><span><span>                        </span></span><span>flag </span><span>=</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>94</div></div><div><span><span>                    </span></span><span>} </span><span>else</span><span> </span><span>if</span><span> (a[j][i] </span><span>==</span><span> </span><span>'1'</span><span>) {</span></div></div><div><div><div>95</div></div><div><span><span>                        </span></span><span>left </span><span>=</span><span> </span><span>max</span><span>(left, j </span><span>+</span><span> </span><span>1</span><span>);</span></div></div><div><div><div>96</div></div><div><span><span>                    </span></span><span>} </span><span>else</span><span> </span><span>if</span><span> (a[j][i] </span><span>==</span><span> </span><span>'2'</span><span>) {</span></div></div><div><div><div>97</div></div><div><span><span>                        </span></span><span>right </span><span>=</span><span> </span><span>min</span><span>(right, j </span><span>-</span><span> </span><span>1</span><span>);</span></div></div><div><div><div>98</div></div><div><span><span>                    </span></span><span>}</span></div></div><div><div><div>99</div></div><div><span><span>                </span></span><span>}</span></div></div><div><div><div>100</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>101</div></div><div><span><span>            </span></span><span>ans.</span><span>emplace_back</span><span>(left, right, left_inner, right_inner, </span><span>!</span><span>flag </span><span>&amp;&amp;</span><span> right </span><span>==</span><span> n </span><span>-</span><span> </span><span>1</span><span>, </span><span>!</span><span>flag </span><span>&amp;&amp;</span><span> left </span><span>==</span><span> </span><span>0</span><span>);</span></div></div><div><div><div>102</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>103</div></div><div><span><span>        </span></span><span>cout </span><span>&lt;&lt;</span><span> </span><span>process</span><span>(ans, m, n) </span><span>&lt;&lt;</span><span> </span><span>'</span><span>\n</span><span>'</span><span>;</span></div></div><div><div><div>104</div></div><div><span><span>    </span></span><span>};</span></div></div><div><div><div>105</div></div><div><span>    </span><span>auto</span><span> solve_col </span><span>=</span><span> [</span><span>&amp;</span><span>]() -&gt; </span><span>void</span><span> {</span></div></div><div><div><div>106</div></div><div><span>        </span><span>vector</span><span>&lt;</span><span>tuple</span><span>&lt;</span><span>int</span><span>, </span><span>int</span><span>, </span><span>int</span><span>, </span><span>int</span><span>, </span><span>int</span><span>, </span><span>int</span><span>&gt;&gt; ans;</span></div></div><div><div><div>107</div></div><div><span>        </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>0</span><span>; i </span><span>&lt;</span><span> n; i</span><span>++</span><span>) {</span></div></div><div><div><div>108</div></div><div><span>            </span><span>int</span><span> left </span><span>=</span><span> </span><span>0</span><span>, right </span><span>=</span><span> m </span><span>-</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>109</div></div><div><span>            </span><span>int</span><span> left_inner </span><span>=</span><span> m </span><span>-</span><span> </span><span>1</span><span>, right_inner </span><span>=</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>110</div></div><div><span>            </span><span>int</span><span> flag </span><span>=</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>111</div></div><div><span>            </span><span>for</span><span> (</span><span>int</span><span> j </span><span>=</span><span> </span><span>0</span><span>; j </span><span>&lt;</span><span> m; j</span><span>++</span><span>) {</span></div></div><div><div><div>112</div></div><div><span>                </span><span>if</span><span> (a[i][j] </span><span>!=</span><span> </span><span>'?'</span><span>) {</span></div></div><div><div><div>113</div></div><div><span>                    </span><span>if</span><span> (a[i][j] </span><span>==</span><span> </span><span>'0'</span><span>) {</span></div></div><div><div><div>114</div></div><div><span>                        </span><span>if</span><span> (</span><span>!</span><span>flag) {</span></div></div><div><div><div>115</div></div><div><span><span>                            </span></span><span>left_inner </span><span>=</span><span> right_inner </span><span>=</span><span> j;</span></div></div><div><div><div>116</div></div><div><span><span>                        </span></span><span>} </span><span>else</span><span> {</span></div></div><div><div><div>117</div></div><div><span><span>                            </span></span><span>right_inner </span><span>=</span><span> j;</span></div></div><div><div><div>118</div></div><div><span><span>                        </span></span><span>}</span></div></div><div><div><div>119</div></div><div><span><span>                        </span></span><span>flag </span><span>=</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>120</div></div><div><span><span>                    </span></span><span>} </span><span>else</span><span> </span><span>if</span><span> (a[i][j] </span><span>==</span><span> </span><span>'1'</span><span>) {</span></div></div><div><div><div>121</div></div><div><span><span>                        </span></span><span>left </span><span>=</span><span> </span><span>max</span><span>(left, j </span><span>+</span><span> </span><span>1</span><span>);</span></div></div><div><div><div>122</div></div><div><span><span>                    </span></span><span>} </span><span>else</span><span> </span><span>if</span><span> (a[i][j] </span><span>==</span><span> </span><span>'2'</span><span>) {</span></div></div><div><div><div>123</div></div><div><span><span>                        </span></span><span>right </span><span>=</span><span> </span><span>min</span><span>(right, j </span><span>-</span><span> </span><span>1</span><span>);</span></div></div><div><div><div>124</div></div><div><span><span>                    </span></span><span>}</span></div></div><div><div><div>125</div></div><div><span><span>                </span></span><span>}</span></div></div><div><div><div>126</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>127</div></div><div><span><span>            </span></span><span>ans.</span><span>emplace_back</span><span>(left, right, left_inner, right_inner, </span><span>!</span><span>flag </span><span>&amp;&amp;</span><span> right </span><span>==</span><span> m </span><span>-</span><span> </span><span>1</span><span>, </span><span>!</span><span>flag </span><span>&amp;&amp;</span><span> left </span><span>==</span><span> </span><span>0</span><span>);</span></div></div><div><div><div>128</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>129</div></div><div><span><span>        </span></span><span>cout </span><span>&lt;&lt;</span><span> </span><span>process</span><span>(ans, n, m) </span><span>&lt;&lt;</span><span> </span><span>'</span><span>\n</span><span>'</span><span>;</span></div></div><div><div><div>130</div></div><div><span><span>    </span></span><span>};</span></div></div><div><div><div>131</div></div><div><span>    </span><span>if</span><span> (m </span><span>&lt;=</span><span> n) </span><span>solve_col</span><span>();</span></div></div><div><div><div>132</div></div><div><span>    </span><span>else</span><span> </span><span>solve_row</span><span>();</span></div></div><div><div><div>133</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section>
<section><h1><a href="https://codeforces.com/gym/574109/problem/D" target="_blank">Kings Game (Hard Version)</a><a href="#kings-game-hard-version"><span>#</span></a></h1><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>vc</span><span>&lt;int&gt;</span><span> pr, vis, mn;</span></div></div><div><div><div>2</div></div><div><span>void</span><span> </span><span>init</span><span>(</span><span>int</span><span> </span><span>n</span><span>) {</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>vis.</span><span>assign</span><span>(n </span><span>+</span><span> </span><span>1</span><span>, </span><span>0</span><span>);</span></div></div><div><div><div>4</div></div><div><span><span>  </span></span><span>mn.</span><span>assign</span><span>(n </span><span>+</span><span> </span><span>1</span><span>, </span><span>0</span><span>);</span></div></div><div><div><div>5</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>2</span><span>; i </span><span>&lt;=</span><span> n; i</span><span>++</span><span>) {</span></div></div><div><div><div>6</div></div><div><span>    </span><span>if</span><span> (</span><span>!</span><span>vis[i]) pr </span><span>+=</span><span> i, mn[i] </span><span>=</span><span> i;</span></div></div><div><div><div>7</div></div><div><span>    </span><span>for</span><span> (</span><span>auto</span><span> j : pr) {</span></div></div><div><div><div>8</div></div><div><span>      </span><span>if</span><span> (i </span><span>*</span><span> j </span><span>&gt;</span><span> n) </span><span>break</span><span>;</span></div></div><div><div><div>9</div></div><div><span><span>      </span></span><span>vis[i </span><span>*</span><span> j] </span><span>=</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>10</div></div><div><span><span>      </span></span><span>mn[i </span><span>*</span><span> j] </span><span>=</span><span> j;</span></div></div><div><div><div>11</div></div><div><span>      </span><span>if</span><span> (i </span><span>%</span><span> j </span><span>==</span><span> </span><span>0</span><span>) </span><span>break</span><span>;</span></div></div><div><div><div>12</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>13</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>14</div></div><div><span>}</span></div></div><div><div><div>15</div></div><div><span>const</span><span> </span><span>int</span><span> mod </span><span>=</span><span> </span><span>998244353</span><span>;</span></div></div><div><div><div>16</div></div><div><span>namespace</span><span> </span><span>MTool</span><span> {</span></div></div><div><div><div>17</div></div><div><span>  </span><span>inline</span><span> </span><span>int</span><span> </span><span>Cadd</span><span>(</span><span>int</span><span> </span><span>a</span><span>, </span><span>int</span><span> </span><span>b</span><span>) {</span><span>return</span><span> (i64)a </span><span>+</span><span> b </span><span>&gt;=</span><span> mod </span><span>?</span><span> (i64)a </span><span>+</span><span> b </span><span>-</span><span> mod </span><span>:</span><span> a </span><span>+</span><span> b;}</span></div></div><div><div><div>18</div></div><div><span>  </span><span>inline</span><span> </span><span>int</span><span> </span><span>Cdel</span><span>(</span><span>int</span><span> </span><span>a</span><span>, </span><span>int</span><span> </span><span>b</span><span>) {</span><span>return</span><span> a </span><span>-</span><span> b </span><span>&lt;</span><span> </span><span>0</span><span> </span><span>?</span><span> a </span><span>-</span><span> b </span><span>+</span><span> mod </span><span>:</span><span> a </span><span>-</span><span> b;}</span></div></div><div><div><div>19</div></div><div><span>  </span><span>inline</span><span> </span><span>int</span><span> </span><span>Cmul</span><span>(</span><span>int</span><span> </span><span>a</span><span>, </span><span>int</span><span> </span><span>b</span><span>) {</span><span>return</span><span> </span><span>1</span><span>ll</span><span> </span><span>*</span><span> a </span><span>*</span><span> b </span><span>%</span><span> mod;}</span></div></div><div><div><div>20</div></div><div><span>  </span><span>inline</span><span> </span><span>int</span><span> </span><span>sqr</span><span>(</span><span>int</span><span> </span><span>a</span><span>) {</span><span>return</span><span> </span><span>1</span><span>ll</span><span> </span><span>*</span><span> a </span><span>*</span><span> a </span><span>%</span><span> mod;}</span></div></div><div><div><div>21</div></div><div><span>  </span><span>inline</span><span> </span><span>void</span><span> </span><span>Madd</span><span>(</span><span>int</span><span> </span><span>&amp;</span><span>a</span><span>, </span><span>int</span><span> </span><span>b</span><span>) {a </span><span>=</span><span> (i64)a </span><span>+</span><span> b </span><span>&gt;=</span><span> mod </span><span>?</span><span> (i64)a </span><span>+</span><span> b </span><span>-</span><span> mod </span><span>:</span><span> a </span><span>+</span><span> b;}</span></div></div><div><div><div>22</div></div><div><span>  </span><span>inline</span><span> </span><span>void</span><span> </span><span>Mdel</span><span>(</span><span>int</span><span> </span><span>&amp;</span><span>a</span><span>, </span><span>int</span><span> </span><span>b</span><span>) {a </span><span>=</span><span> a </span><span>-</span><span> b </span><span>&lt;</span><span> </span><span>0</span><span> </span><span>?</span><span> a </span><span>-</span><span> b </span><span>+</span><span> mod </span><span>:</span><span> a </span><span>-</span><span> b;}</span></div></div><div><div><div>23</div></div><div><span>  </span><span>inline</span><span> </span><span>void</span><span> </span><span>Mmul</span><span>(</span><span>int</span><span> </span><span>&amp;</span><span>a</span><span>, </span><span>int</span><span> </span><span>b</span><span>) {a </span><span>=</span><span> </span><span>1</span><span>ll</span><span> </span><span>*</span><span> a </span><span>*</span><span> b </span><span>%</span><span> mod;}</span></div></div><div><div><div>24</div></div><div><span>  </span><span>inline</span><span> </span><span>int</span><span> </span><span>neg</span><span>(</span><span>int</span><span> </span><span>x</span><span>) {</span><span>return</span><span> </span><span>!</span><span>x </span><span>?</span><span> </span><span>0</span><span> </span><span>:</span><span> mod </span><span>-</span><span> x;}</span></div></div><div><div><div>25</div></div><div><span>  </span><span>inline</span><span> </span><span>int</span><span> </span><span>norm</span><span>(</span><span>i64</span><span> </span><span>x</span><span>) {</span><span>return</span><span> (x </span><span>%</span><span> mod </span><span>+</span><span> mod) </span><span>%</span><span> mod;}</span></div></div><div><div><div>26</div></div><div><span>  </span><span>template</span><span> &lt;</span><span>typename</span><span> ...</span><span>Args</span><span>&gt; </span><span>inline</span><span> </span><span>int</span><span> </span><span>Cadd</span><span>(</span><span>int</span><span> </span><span>a</span><span>, </span><span>int</span><span> </span><span>b</span><span>, </span><span>Args</span><span> ...</span><span>args</span><span>) {</span><span>return</span><span> </span><span>Cadd</span><span>(</span><span>Cadd</span><span>(a, b), args...);}</span></div></div><div><div><div>27</div></div><div><span>  </span><span>template</span><span> &lt;</span><span>typename</span><span> ...</span><span>Args</span><span>&gt; </span><span>inline</span><span> </span><span>int</span><span> </span><span>Cdel</span><span>(</span><span>int</span><span> </span><span>a</span><span>, </span><span>int</span><span> </span><span>b</span><span>, </span><span>Args</span><span> ...</span><span>args</span><span>) {</span><span>return</span><span> </span><span>Cdel</span><span>(</span><span>Cdel</span><span>(a, b), args...);}</span></div></div><div><div><div>28</div></div><div><span>  </span><span>template</span><span> &lt;</span><span>typename</span><span> ...</span><span>Args</span><span>&gt; </span><span>inline</span><span> </span><span>int</span><span> </span><span>Cmul</span><span>(</span><span>int</span><span> </span><span>a</span><span>, </span><span>int</span><span> </span><span>b</span><span>, </span><span>Args</span><span> ...</span><span>args</span><span>) {</span><span>return</span><span> </span><span>Cmul</span><span>(</span><span>Cmul</span><span>(a, b), args...);}</span></div></div><div><div><div>29</div></div><div><span>  </span><span>template</span><span> &lt;</span><span>typename</span><span> ...</span><span>Args</span><span>&gt; </span><span>inline</span><span> </span><span>void</span><span> </span><span>Madd</span><span>(</span><span>int</span><span> </span><span>&amp;</span><span>a</span><span>, </span><span>int</span><span> </span><span>b</span><span>, </span><span>Args</span><span> ...</span><span>args</span><span>) {</span><span>Madd</span><span>(a, b), </span><span>Madd</span><span>(a, args...);}</span></div></div><div><div><div>30</div></div><div><span>  </span><span>template</span><span> &lt;</span><span>typename</span><span> ...</span><span>Args</span><span>&gt; </span><span>inline</span><span> </span><span>void</span><span> </span><span>Mdel</span><span>(</span><span>int</span><span> </span><span>&amp;</span><span>a</span><span>, </span><span>int</span><span> </span><span>b</span><span>, </span><span>Args</span><span> ...</span><span>args</span><span>) {</span><span>Mdel</span><span>(a, b), </span><span>Mdel</span><span>(a, args...);}</span></div></div><div><div><div>31</div></div><div><span>  </span><span>template</span><span> &lt;</span><span>typename</span><span> ...</span><span>Args</span><span>&gt; </span><span>inline</span><span> </span><span>void</span><span> </span><span>Mmul</span><span>(</span><span>int</span><span> </span><span>&amp;</span><span>a</span><span>, </span><span>int</span><span> </span><span>b</span><span>, </span><span>Args</span><span> ...</span><span>args</span><span>) {</span><span>Mmul</span><span>(a, b), </span><span>Mmul</span><span>(a, args...);}</span></div></div><div><div><div>32</div></div><div><span>  </span><span>inline</span><span> </span><span>int</span><span> </span><span>ksm</span><span>(</span><span>int</span><span> </span><span>a</span><span>, </span><span>i64</span><span> </span><span>b</span><span>, </span><span>int</span><span> </span><span>Mod</span><span> </span><span>=</span><span> mod) {</span><span>int</span><span> ans </span><span>=</span><span> </span><span>1</span><span>; </span><span>for</span><span> (; b; b </span><span>&gt;&gt;=</span><span> </span><span>1</span><span>, a </span><span>=</span><span> </span><span>1</span><span>ll</span><span> </span><span>*</span><span> a </span><span>*</span><span> a </span><span>%</span><span> Mod) </span><span>if</span><span> (b </span><span>&amp;</span><span> </span><span>1</span><span>) ans </span><span>=</span><span> </span><span>1</span><span>ll</span><span> </span><span>*</span><span> ans </span><span>*</span><span> a </span><span>%</span><span> Mod; </span><span>return</span><span> ans;}</span></div></div><div><div><div>33</div></div><div><span>}</span></div></div><div><div><div>34</div></div><div><span>using</span><span> </span><span>namespace</span><span> </span><span>MTool</span><span>;</span></div></div><div><div><div>35</div></div><div>
</div></div><div><div><div>36</div></div><div><span>vc</span><span>&lt;int&gt;</span><span> p, tim;</span></div></div><div><div><div>37</div></div><div><span>void</span><span> </span><span>ins</span><span>(</span><span>int</span><span> </span><span>x</span><span>, </span><span>int</span><span> </span><span>id</span><span>) {</span></div></div><div><div><div>38</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>5</span><span>; i </span><span>&gt;=</span><span> </span><span>0</span><span>; i</span><span>--</span><span>) </span><span>if</span><span> (x </span><span>&gt;&gt;</span><span> i </span><span>&amp;</span><span> </span><span>1</span><span>) {</span></div></div><div><div><div>39</div></div><div><span>    </span><span>if</span><span> (</span><span>!</span><span>p[i]) {</span></div></div><div><div><div>40</div></div><div><span><span>      </span></span><span>p[i] </span><span>=</span><span> x, tim[i] </span><span>=</span><span> id;</span></div></div><div><div><div>41</div></div><div><span>      </span><span>return</span><span> ;</span></div></div><div><div><div>42</div></div><div><span><span>    </span></span><span>} </span><span>else</span><span> </span><span>if</span><span> (tim[i] </span><span>&lt;</span><span> id) {</span></div></div><div><div><div>43</div></div><div><span>      </span><span>swap</span><span>(p[i], x), </span><span>swap</span><span>(tim[i], id);</span></div></div><div><div><div>44</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>45</div></div><div><span><span>    </span></span><span>x </span><span>^=</span><span> p[i];</span></div></div><div><div><div>46</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>47</div></div><div><span>}</span></div></div><div><div><div>48</div></div><div><span>vc</span><span>&lt;int&gt;</span><span> pw;</span></div></div><div><div><div>49</div></div><div>
</div></div><div><div><div>50</div></div><div><span>void</span><span> </span><span>solve</span><span>() {</span></div></div><div><div><div>51</div></div><div><span>  </span><span>int</span><span> n, q;</span></div></div><div><div><div>52</div></div><div><span><span>  </span></span><span>cin </span><span>&gt;&gt;</span><span> n </span><span>&gt;&gt;</span><span> q;</span></div></div><div><div><div>53</div></div><div><span>  </span><span>vc</span><span>&lt;</span><span>int</span><span>&gt; </span><span>a</span><span>(n);</span></div></div><div><div><div>54</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>0</span><span>, x; i </span><span>&lt;</span><span> n; i</span><span>++</span><span>) {</span></div></div><div><div><div>55</div></div><div><span><span>    </span></span><span>cin </span><span>&gt;&gt;</span><span> x;</span></div></div><div><div><div>56</div></div><div><span>    </span><span>while</span><span> (x </span><span>!=</span><span> </span><span>1</span><span>) {</span></div></div><div><div><div>57</div></div><div><span><span>      </span></span><span>a[i]</span><span>++</span><span>;</span></div></div><div><div><div>58</div></div><div><span><span>      </span></span><span>x </span><span>/=</span><span> mn[x];</span></div></div><div><div><div>59</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>60</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>61</div></div><div><span>  </span><span>debug</span><span>(a);</span></div></div><div><div><div>62</div></div><div>
</div></div><div><div><div>63</div></div><div><span>  </span><span>vc</span><span>&lt;</span><span>int</span><span>&gt; </span><span>sum</span><span>(n);</span></div></div><div><div><div>64</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>0</span><span>; i </span><span>&lt;</span><span> n; i</span><span>++</span><span>)</span></div></div><div><div><div>65</div></div><div><span><span>    </span></span><span>sum[i] </span><span>=</span><span> (i </span><span>?</span><span> sum[i </span><span>-</span><span> </span><span>1</span><span>] </span><span>:</span><span> </span><span>0</span><span>) </span><span>+</span><span> (a[i] </span><span>==</span><span> </span><span>1</span><span>);</span></div></div><div><div><div>66</div></div><div><span>  </span><span>vc</span><span>&lt;</span><span>int</span><span>&gt; </span><span>rsum</span><span>(n);</span></div></div><div><div><div>67</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>0</span><span>; i </span><span>&lt;</span><span> n; i</span><span>++</span><span>)</span></div></div><div><div><div>68</div></div><div><span><span>    </span></span><span>rsum[i] </span><span>=</span><span> (i </span><span>?</span><span> rsum[i </span><span>-</span><span> </span><span>1</span><span>] </span><span>:</span><span> </span><span>0</span><span>) </span><span>+</span><span> (a[i] </span><span>==</span><span> </span><span>0</span><span>);</span></div></div><div><div><div>69</div></div><div>
</div></div><div><div><div>70</div></div><div><span>  </span><span>vc</span><span>&lt;</span><span>int</span><span>&gt; </span><span>ans</span><span>(q);</span></div></div><div><div><div>71</div></div><div><span>  </span><span>vc</span><span>&lt;</span><span>vc</span><span>&lt;</span><span>array</span><span>&lt;</span><span>int</span><span>, </span><span>2</span><span>&gt;&gt;&gt; </span><span>ask</span><span>(n);</span></div></div><div><div><div>72</div></div><div>
</div></div><div><div><div>73</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>0</span><span>; i </span><span>&lt;</span><span> q; i</span><span>++</span><span>) {</span></div></div><div><div><div>74</div></div><div><span>    </span><span>int</span><span> l, r;</span></div></div><div><div><div>75</div></div><div><span><span>    </span></span><span>cin </span><span>&gt;&gt;</span><span> l </span><span>&gt;&gt;</span><span> r;</span></div></div><div><div><div>76</div></div><div><span><span>    </span></span><span>l</span><span>--</span><span>, r</span><span>--</span><span>;</span></div></div><div><div><div>77</div></div><div><span>    </span><span>int</span><span> rc </span><span>=</span><span> rsum[r] </span><span>-</span><span> (l </span><span>?</span><span> rsum[l </span><span>-</span><span> </span><span>1</span><span>] </span><span>:</span><span> </span><span>0</span><span>), c </span><span>=</span><span> sum[r] </span><span>-</span><span> (l </span><span>?</span><span> sum[l </span><span>-</span><span> </span><span>1</span><span>] </span><span>:</span><span> </span><span>0</span><span>);</span></div></div><div><div><div>78</div></div><div><span>    </span><span>Madd</span><span>(ans[i], pw[r </span><span>-</span><span> l </span><span>+</span><span> </span><span>1</span><span>]);</span></div></div><div><div><div>79</div></div><div><span>    </span><span>Mdel</span><span>(ans[i], </span><span>1</span><span>);</span></div></div><div><div><div>80</div></div><div><span>    </span><span>if</span><span> (</span><span>!</span><span>c) </span><span>Madd</span><span>(ans[i], </span><span>Cdel</span><span>(pw[rc], </span><span>1</span><span>));</span></div></div><div><div><div>81</div></div><div><span>    </span><span>else</span><span> </span><span>Mdel</span><span>(ans[i], </span><span>1</span><span>);</span></div></div><div><div><div>82</div></div><div><span><span>    </span></span><span>ask[r] </span><span>+=</span><span> {l, i};</span></div></div><div><div><div>83</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>84</div></div><div><span><span>  </span></span><span>p.</span><span>assign</span><span>(</span><span>6</span><span>, </span><span>0</span><span>), tim.</span><span>assign</span><span>(</span><span>6</span><span>, </span><span>-</span><span>1</span><span>);</span></div></div><div><div><div>85</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>0</span><span>; i </span><span>&lt;</span><span> n; i</span><span>++</span><span>) {</span></div></div><div><div><div>86</div></div><div><span>    </span><span>ins</span><span>(a[i], i);</span></div></div><div><div><div>87</div></div><div><span>    </span><span>for</span><span> (</span><span>auto</span><span> [l, id] : ask[i]) {</span></div></div><div><div><div>88</div></div><div><span>      </span><span>int</span><span> c </span><span>=</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>89</div></div><div><span>      </span><span>for</span><span> (</span><span>int</span><span> j </span><span>=</span><span> </span><span>0</span><span>; j </span><span>&lt;=</span><span> </span><span>5</span><span>; j</span><span>++</span><span>)</span></div></div><div><div><div>90</div></div><div><span>        </span><span>if</span><span> (tim[j] </span><span>&gt;=</span><span> l) c</span><span>++</span><span>;</span></div></div><div><div><div>91</div></div><div><span>      </span><span>Mdel</span><span>(ans[id], </span><span>Cdel</span><span>(pw[i </span><span>-</span><span> l </span><span>+</span><span> </span><span>1</span><span> </span><span>-</span><span> c], </span><span>1</span><span>));</span></div></div><div><div><div>92</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>93</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>94</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>0</span><span>; i </span><span>&lt;</span><span> q; i</span><span>++</span><span>) cout </span><span>&lt;&lt;</span><span> ans[i] </span><span>&lt;&lt;</span><span> </span><span>'</span><span>\n</span><span>'</span><span>;</span></div></div><div><div><div>95</div></div><div><span>}</span></div></div><div><div><div>96</div></div><div>
</div></div><div><div><div>97</div></div><div><span>void</span><span> </span><span>main01</span><span>() {</span></div></div><div><div><div>98</div></div><div><span>  </span><span>#ifdef</span><span> </span><span>LOCAL</span></div></div><div><div><div>99</div></div><div><span>  </span><span>file</span><span>(</span><span>""</span><span>);</span></div></div><div><div><div>100</div></div><div><span><span>  </span></span><span>#endif</span></div></div><div><div><div>101</div></div><div><span><span>  </span></span><span>cin.</span><span>tie</span><span>(</span><span>nullptr</span><span>) -&gt; </span><span>sync_with_stdio</span><span>(</span><span>false</span><span>);</span></div></div><div><div><div>102</div></div><div><span>  </span><span>init</span><span>(</span><span>1</span><span>e</span><span>7</span><span>);</span></div></div><div><div><div>103</div></div><div><span><span>  </span></span><span>pw.</span><span>resize</span><span>(</span><span>1000005</span><span>);</span></div></div><div><div><div>104</div></div><div><span><span>  </span></span><span>pw[</span><span>0</span><span>] </span><span>=</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>105</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>1</span><span>; i </span><span>&lt;</span><span> </span><span>sz</span><span>(pw); i</span><span>++</span><span>) pw[i] </span><span>=</span><span> </span><span>Cadd</span><span>(pw[i </span><span>-</span><span> </span><span>1</span><span>], pw[i </span><span>-</span><span> </span><span>1</span><span>]);</span></div></div><div><div><div>106</div></div><div><span>  </span><span>int</span><span> t </span><span>=</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>107</div></div><div><span><span>  </span></span><span>cin </span><span>&gt;&gt;</span><span> t;</span></div></div><div><div><div>108</div></div><div><span>  </span><span>while</span><span> (t</span><span>--</span><span>) </span><span>solve</span><span>();</span></div></div><div><div><div>109</div></div><div><span>}}</span></div></div><div><div><div>110</div></div><div><span>signed</span><span> </span><span>main</span><span>() {</span><span>return</span><span> </span><span>Anonyme</span><span>::</span><span>main01</span><span>(), </span><span>0</span><span>;}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section>
<section><h1><a href="https://codeforces.com/gym/574109/problem/E" target="_blank">十六行诗</a><a href="#十六行诗"><span>#</span></a></h1><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>void</span><span> </span><span>solve</span><span>() {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>int</span><span> n;</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>cin </span><span>&gt;&gt;</span><span> n;</span></div></div><div><div><div>4</div></div><div><span>  </span><span>V</span><span>&lt;</span><span>int</span><span>&gt; </span><span>a</span><span>(n </span><span>+</span><span> </span><span>1</span><span>);</span></div></div><div><div><div>5</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>1</span><span>; i </span><span>&lt;=</span><span> n; i</span><span>++</span><span>) {</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>cin </span><span>&gt;&gt;</span><span> a[i];</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>8</div></div><div><span>  </span><span>V</span><span>&lt;</span><span>int</span><span>&gt; tmp </span><span>=</span><span> a;</span></div></div><div><div><div>9</div></div><div><span>  </span><span>remDup</span><span>(tmp);</span></div></div><div><div><div>10</div></div><div><span>  </span><span>int</span><span> d </span><span>=</span><span> tmp.</span><span>size</span><span>();</span></div></div><div><div><div>11</div></div><div><span>  </span><span>auto</span><span> get </span><span>=</span><span> [</span><span>&amp;</span><span>](</span><span>int</span><span> </span><span>x</span><span>) {</span></div></div><div><div><div>12</div></div><div><span>    </span><span>return</span><span> (</span><span>lower_bound</span><span>(tmp.</span><span>begin</span><span>() </span><span>+</span><span> </span><span>1</span><span>, tmp.</span><span>end</span><span>(), x) </span><span>-</span><span> (tmp.</span><span>begin</span><span>()));</span></div></div><div><div><div>13</div></div><div><span><span>  </span></span><span>};</span></div></div><div><div><div>14</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>1</span><span>; i </span><span>&lt;=</span><span> n; i</span><span>++</span><span>) {</span></div></div><div><div><div>15</div></div><div><span><span>    </span></span><span>a[i] </span><span>=</span><span> </span><span>get</span><span>(a[i]);</span></div></div><div><div><div>16</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>17</div></div><div><span>  </span><span>V</span><span>&lt;</span><span>V</span><span>&lt;</span><span>int</span><span>&gt;&gt; </span><span>nxt</span><span>(n </span><span>+</span><span> </span><span>2</span><span>, </span><span>V</span><span>&lt;</span><span>int</span><span>&gt;(d </span><span>+</span><span> </span><span>1</span><span>, n </span><span>+</span><span> </span><span>1</span><span>));</span></div></div><div><div><div>18</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> v </span><span>=</span><span> </span><span>1</span><span>; v </span><span>&lt;=</span><span> d; v</span><span>++</span><span>) {</span></div></div><div><div><div>19</div></div><div><span><span>    </span></span><span>nxt[n </span><span>+</span><span> </span><span>1</span><span>][v] </span><span>=</span><span> n </span><span>+</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>20</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>21</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> n; i </span><span>&gt;=</span><span> </span><span>1</span><span>; i</span><span>--</span><span>) {</span></div></div><div><div><div>22</div></div><div><span>    </span><span>for</span><span> (</span><span>int</span><span> v </span><span>=</span><span> </span><span>1</span><span>; v </span><span>&lt;=</span><span> d; v</span><span>++</span><span>) {</span></div></div><div><div><div>23</div></div><div><span><span>      </span></span><span>nxt[i][v] </span><span>=</span><span> nxt[i </span><span>+</span><span> </span><span>1</span><span>][v];</span></div></div><div><div><div>24</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>25</div></div><div><span><span>    </span></span><span>nxt[i][a[i]] </span><span>=</span><span> i;</span></div></div><div><div><div>26</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>27</div></div><div><span>  </span><span>V</span><span>&lt;</span><span>int</span><span>&gt; </span><span>dp</span><span>(n </span><span>+</span><span> </span><span>2</span><span>, </span><span>0</span><span>);</span></div></div><div><div><div>28</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> n; i </span><span>&gt;=</span><span> </span><span>1</span><span>; i</span><span>--</span><span>) {</span></div></div><div><div><div>29</div></div><div><span><span>    </span></span><span>dp[i] </span><span>=</span><span> dp[i </span><span>+</span><span> </span><span>1</span><span>];</span></div></div><div><div><div>30</div></div><div><span>    </span><span>int</span><span> j </span><span>=</span><span> nxt[i </span><span>+</span><span> </span><span>1</span><span>][a[i]];</span></div></div><div><div><div>31</div></div><div><span>    </span><span>if</span><span> (j </span><span>&lt;=</span><span> n) {</span></div></div><div><div><div>32</div></div><div><span>      </span><span>for</span><span> (</span><span>int</span><span> b </span><span>=</span><span> </span><span>1</span><span>; b </span><span>&lt;=</span><span> d; b</span><span>++</span><span>) {</span></div></div><div><div><div>33</div></div><div><span>        </span><span>int</span><span> k </span><span>=</span><span> nxt[j </span><span>+</span><span> </span><span>1</span><span>][b];</span></div></div><div><div><div>34</div></div><div><span>        </span><span>if</span><span> (k </span><span>&gt;</span><span> n) {</span></div></div><div><div><div>35</div></div><div><span>          </span><span>continue</span><span>;</span></div></div><div><div><div>36</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>37</div></div><div><span>        </span><span>int</span><span> l </span><span>=</span><span> nxt[k </span><span>+</span><span> </span><span>1</span><span>][b];</span></div></div><div><div><div>38</div></div><div><span>        </span><span>if</span><span> (l </span><span>&gt;</span><span> n) {</span></div></div><div><div><div>39</div></div><div><span>          </span><span>continue</span><span>;</span></div></div><div><div><div>40</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>41</div></div><div><span><span>        </span></span><span>dp[i] </span><span>=</span><span> </span><span>max</span><span>(dp[i], </span><span>1</span><span> </span><span>+</span><span> dp[l </span><span>+</span><span> </span><span>1</span><span>]);</span></div></div><div><div><div>42</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>43</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>44</div></div><div><span>    </span><span>for</span><span> (</span><span>int</span><span> j </span><span>=</span><span> i </span><span>+</span><span> </span><span>1</span><span>; j </span><span>&lt;=</span><span> n; j</span><span>++</span><span>) {</span></div></div><div><div><div>45</div></div><div><span>      </span><span>int</span><span> k </span><span>=</span><span> nxt[j </span><span>+</span><span> </span><span>1</span><span>][a[i]];</span></div></div><div><div><div>46</div></div><div><span>      </span><span>if</span><span> (k </span><span>&gt;</span><span> n) {</span></div></div><div><div><div>47</div></div><div><span>        </span><span>continue</span><span>;</span></div></div><div><div><div>48</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>49</div></div><div><span>      </span><span>int</span><span> l </span><span>=</span><span> nxt[k </span><span>+</span><span> </span><span>1</span><span>][a[j]];</span></div></div><div><div><div>50</div></div><div><span>      </span><span>if</span><span> (l </span><span>&gt;</span><span> n) {</span></div></div><div><div><div>51</div></div><div><span>        </span><span>continue</span><span>;</span></div></div><div><div><div>52</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>53</div></div><div><span><span>      </span></span><span>dp[i] </span><span>=</span><span> </span><span>max</span><span>(dp[i], </span><span>1</span><span> </span><span>+</span><span> dp[l </span><span>+</span><span> </span><span>1</span><span>]);</span></div></div><div><div><div>54</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>55</div></div><div><span>    </span><span>for</span><span> (</span><span>int</span><span> j </span><span>=</span><span> i </span><span>+</span><span> </span><span>1</span><span>; j </span><span>&lt;=</span><span> n; j</span><span>++</span><span>) {</span></div></div><div><div><div>56</div></div><div><span>      </span><span>int</span><span> k </span><span>=</span><span> nxt[j </span><span>+</span><span> </span><span>1</span><span>][a[j]];</span></div></div><div><div><div>57</div></div><div><span>      </span><span>if</span><span> (k </span><span>&gt;</span><span> n) {</span></div></div><div><div><div>58</div></div><div><span>        </span><span>continue</span><span>;</span></div></div><div><div><div>59</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>60</div></div><div><span>      </span><span>int</span><span> l </span><span>=</span><span> nxt[k </span><span>+</span><span> </span><span>1</span><span>][a[i]];</span></div></div><div><div><div>61</div></div><div><span>      </span><span>if</span><span> (l </span><span>&gt;</span><span> n) {</span></div></div><div><div><div>62</div></div><div><span>        </span><span>continue</span><span>;</span></div></div><div><div><div>63</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>64</div></div><div><span><span>      </span></span><span>dp[i] </span><span>=</span><span> </span><span>max</span><span>(dp[i], </span><span>1</span><span> </span><span>+</span><span> dp[l </span><span>+</span><span> </span><span>1</span><span>]);</span></div></div><div><div><div>65</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>66</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>67</div></div><div><span><span>  </span></span><span>cout </span><span>&lt;&lt;</span><span> dp[</span><span>1</span><span>] </span><span>&lt;&lt;</span><span> endl;</span></div></div><div><div><div>68</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section>
<section><h1><a href="https://codeforces.com/gym/574109/problem/F" target="_blank">Yuhina City</a><a href="#yuhina-city"><span>#</span></a></h1><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>void</span><span> </span><span>solve</span><span>() {</span></div></div><div><div><div>2</div></div><div><span>    </span><span>int</span><span> n, m, q;</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>n </span><span>=</span><span> </span><span>read</span><span>(), m </span><span>=</span><span> </span><span>read</span><span>(), q </span><span>=</span><span> </span><span>read</span><span>();</span></div></div><div><div><div>4</div></div><div><span>    </span><span>vector</span><span>&lt;</span><span>ll</span><span>&gt; </span><span>r</span><span>(n </span><span>+</span><span> </span><span>1</span><span>);</span></div></div><div><div><div>5</div></div><div><span>    </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>1</span><span>; i </span><span>&lt;=</span><span> n; i</span><span>++</span><span>) r[i] </span><span>=</span><span> </span><span>read_ll</span><span>();</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>    </span><span>vector</span><span>&lt;</span><span>vector</span><span>&lt;</span><span>pair</span><span>&lt;</span><span>int</span><span>, </span><span>int</span><span>&gt;&gt;&gt; </span><span>g</span><span>(n </span><span>+</span><span> </span><span>1</span><span>);</span></div></div><div><div><div>8</div></div><div><span>    </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>1</span><span>; i </span><span>&lt;=</span><span> m; i</span><span>++</span><span>) {</span></div></div><div><div><div>9</div></div><div><span>        </span><span>int</span><span> u, v, w;</span></div></div><div><div><div>10</div></div><div><span><span>        </span></span><span>u </span><span>=</span><span> </span><span>read</span><span>(), v </span><span>=</span><span> </span><span>read</span><span>(), w </span><span>=</span><span> </span><span>read</span><span>();</span></div></div><div><div><div>11</div></div><div><span><span>        </span></span><span>g[u].</span><span>emplace_back</span><span>(v, w);</span></div></div><div><div><div>12</div></div><div><span><span>        </span></span><span>g[v].</span><span>emplace_back</span><span>(u, w);</span></div></div><div><div><div>13</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>14</div></div><div>
</div></div><div><div><div>15</div></div><div><span><span>    </span></span><span>{</span></div></div><div><div><div>16</div></div><div><span>        </span><span>priority_queue</span><span>&lt;</span><span>pair</span><span>&lt;</span><span>ll</span><span>, </span><span>int</span><span>&gt;, </span><span>vector</span><span>&lt;</span><span>pair</span><span>&lt;</span><span>ll</span><span>, </span><span>int</span><span>&gt;&gt;, </span><span>less</span><span>&lt;</span><span>pair</span><span>&lt;</span><span>ll</span><span>, </span><span>int</span><span>&gt;&gt;&gt; q;</span></div></div><div><div><div>17</div></div><div><span>        </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>1</span><span>; i </span><span>&lt;=</span><span> n; i</span><span>++</span><span>) q.</span><span>push</span><span>({r[i], i});</span></div></div><div><div><div>18</div></div><div>
</div></div><div><div><div>19</div></div><div>
</div></div><div><div><div>20</div></div><div><span>        </span><span>while</span><span> (</span><span>!</span><span>q.</span><span>empty</span><span>()) {</span></div></div><div><div><div>21</div></div><div><span>            </span><span>auto</span><span> [d, u] </span><span>=</span><span> q.</span><span>top</span><span>(); q.</span><span>pop</span><span>();</span></div></div><div><div><div>22</div></div><div><span>            </span><span>if</span><span> (r[u] </span><span>!=</span><span> d) </span><span>continue</span><span>;</span></div></div><div><div><div>23</div></div><div><span>            </span><span>for</span><span> (</span><span>auto</span><span> [v, w] : g[u]) {</span></div></div><div><div><div>24</div></div><div><span>                </span><span>if</span><span> (d </span><span>-</span><span> w </span><span>&gt;</span><span> r[v]) {</span></div></div><div><div><div>25</div></div><div><span><span>                    </span></span><span>r[v] </span><span>=</span><span> d </span><span>-</span><span> w;</span></div></div><div><div><div>26</div></div><div><span><span>                    </span></span><span>q.</span><span>push</span><span>({r[v], v});</span></div></div><div><div><div>27</div></div><div><span><span>                </span></span><span>}</span></div></div><div><div><div>28</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>29</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>30</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>31</div></div><div><span>    </span><span>vector</span><span>&lt;</span><span>ll</span><span>&gt; </span><span>k</span><span>(r);</span></div></div><div><div><div>32</div></div><div><span>    </span><span>sort</span><span>(k.</span><span>begin</span><span>(), k.</span><span>end</span><span>());</span></div></div><div><div><div>33</div></div><div><span><span>    </span></span><span>k.</span><span>erase</span><span>(</span><span>unique</span><span>(k.</span><span>begin</span><span>(), k.</span><span>end</span><span>()), k.</span><span>end</span><span>());</span></div></div><div><div><div>34</div></div><div>
</div></div><div><div><div>35</div></div><div><span>    </span><span>auto</span><span> check </span><span>=</span><span> [</span><span>&amp;</span><span>](</span><span>int</span><span> </span><span>u</span><span>, </span><span>int</span><span> </span><span>v</span><span>, </span><span>ll</span><span> </span><span>f</span><span>, </span><span>int</span><span> </span><span>k</span><span>) -&gt; </span><span>int</span><span> {</span></div></div><div><div><div>36</div></div><div><span>        </span><span>vector</span><span>&lt;</span><span>int</span><span>&gt; </span><span>dist</span><span>(n </span><span>+</span><span> </span><span>1</span><span>, infi);</span></div></div><div><div><div>37</div></div><div><span><span>        </span></span><span>dist[u] </span><span>=</span><span> r[u] </span><span>&gt;</span><span> f;</span></div></div><div><div><div>38</div></div><div><span>        </span><span>deque</span><span>&lt;</span><span>pair</span><span>&lt;</span><span>int</span><span>, </span><span>int</span><span>&gt;&gt; que;</span></div></div><div><div><div>39</div></div><div><span><span>        </span></span><span>que.</span><span>push_back</span><span>({dist[u], u});</span></div></div><div><div><div>40</div></div><div>
</div></div><div><div><div>41</div></div><div><span>        </span><span>while</span><span> (</span><span>!</span><span>que.</span><span>empty</span><span>()) {</span></div></div><div><div><div>42</div></div><div><span>            </span><span>auto</span><span> [d, u] </span><span>=</span><span> que.</span><span>front</span><span>(); que.</span><span>pop_front</span><span>();</span></div></div><div><div><div>43</div></div><div><span>            </span><span>if</span><span> (dist[u] </span><span>!=</span><span> d) </span><span>continue</span><span>;</span></div></div><div><div><div>44</div></div><div><span>            </span><span>for</span><span> (</span><span>auto</span><span> [v, _] : g[u]) {</span></div></div><div><div><div>45</div></div><div><span>                </span><span>int</span><span> w </span><span>=</span><span> r[v] </span><span>&gt;</span><span> f;</span></div></div><div><div><div>46</div></div><div><span>                </span><span>if</span><span> (dist[v] </span><span>&gt;</span><span> d </span><span>+</span><span> w) {</span></div></div><div><div><div>47</div></div><div><span><span>                    </span></span><span>dist[v] </span><span>=</span><span> d </span><span>+</span><span> w;</span></div></div><div><div><div>48</div></div><div><span>                    </span><span>if</span><span> (w) que.</span><span>push_back</span><span>({dist[v], v});</span></div></div><div><div><div>49</div></div><div><span>                    </span><span>else</span><span> que.</span><span>push_front</span><span>({dist[v], v});</span></div></div><div><div><div>50</div></div><div><span><span>                </span></span><span>}</span></div></div><div><div><div>51</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>52</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>53</div></div><div><span>        </span><span>return</span><span> dist[v] </span><span>&lt;=</span><span> k;</span></div></div><div><div><div>54</div></div><div><span><span>    </span></span><span>};</span></div></div><div><div><div>55</div></div><div>
</div></div><div><div><div>56</div></div><div><span>    </span><span>while</span><span> (q</span><span>--</span><span>) {</span></div></div><div><div><div>57</div></div><div><span>        </span><span>int</span><span> u, v, c;</span></div></div><div><div><div>58</div></div><div><span><span>        </span></span><span>u </span><span>=</span><span> </span><span>read</span><span>(), v </span><span>=</span><span> </span><span>read</span><span>(), c </span><span>=</span><span> </span><span>read</span><span>();</span></div></div><div><div><div>59</div></div><div><span>        </span><span>int</span><span> left </span><span>=</span><span> </span><span>0</span><span>, right </span><span>=</span><span> k.</span><span>size</span><span>() </span><span>-</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>60</div></div><div><span>        </span><span>ll</span><span> ans </span><span>=</span><span> </span><span>-</span><span>1</span><span>;</span></div></div><div><div><div>61</div></div><div><span>        </span><span>while</span><span> (left </span><span>&lt;=</span><span> right) {</span></div></div><div><div><div>62</div></div><div><span>            </span><span>int</span><span> mid </span><span>=</span><span> left </span><span>+</span><span> (right </span><span>-</span><span> left) </span><span>/</span><span> </span><span>2</span><span>;</span></div></div><div><div><div>63</div></div><div><span>            </span><span>if</span><span> (</span><span>check</span><span>(u, v, k[mid], c)) {</span></div></div><div><div><div>64</div></div><div><span><span>                </span></span><span>ans </span><span>=</span><span> k[mid];</span></div></div><div><div><div>65</div></div><div><span><span>                </span></span><span>right </span><span>=</span><span> mid </span><span>-</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>66</div></div><div><span><span>            </span></span><span>} </span><span>else</span><span> {</span></div></div><div><div><div>67</div></div><div><span><span>                </span></span><span>left </span><span>=</span><span> mid </span><span>+</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>68</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>69</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>70</div></div><div><span>        </span><span>Print</span><span>(ans, </span><span>'</span><span>\n</span><span>'</span><span>);</span></div></div><div><div><div>71</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>72</div></div><div><span>    </span><span>Write</span><span>();</span></div></div><div><div><div>73</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section>
<section><h1><a href="https://codeforces.com/gym/574109/problem/I" target="_blank">So Far Away</a><a href="#so-far-away"><span>#</span></a></h1><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>void</span><span> </span><span>solve</span><span>() {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>int</span><span> n, q;</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>cin </span><span>&gt;&gt;</span><span> n </span><span>&gt;&gt;</span><span> q;</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>  </span><span>vc</span><span>&lt;</span><span>int</span><span>&gt; </span><span>a</span><span>(n);</span></div></div><div><div><div>6</div></div><div><span>  </span><span>for</span><span> (</span><span>auto</span><span> </span><span>&amp;</span><span>x : a) cin </span><span>&gt;&gt;</span><span> x;</span></div></div><div><div><div>7</div></div><div><span>  </span><span>set</span><span>&lt;</span><span>array</span><span>&lt;</span><span>int</span><span>, </span><span>2</span><span>&gt;&gt; s;</span></div></div><div><div><div>8</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>0</span><span>; i </span><span>&lt;</span><span> n; i</span><span>++</span><span>) s.</span><span>insert</span><span>({a[i], i});</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span>  </span><span>vc</span><span>&lt;</span><span>int</span><span>&gt; </span><span>idx</span><span>(n, </span><span>-</span><span>1</span><span>);</span></div></div><div><div><div>11</div></div><div><span>  </span><span>while</span><span> (q</span><span>--</span><span>) {</span></div></div><div><div><div>12</div></div><div><span>    </span><span>int</span><span> op;</span></div></div><div><div><div>13</div></div><div><span><span>    </span></span><span>cin </span><span>&gt;&gt;</span><span> op;</span></div></div><div><div><div>14</div></div><div><span>    </span><span>if</span><span> (op </span><span>==</span><span> </span><span>1</span><span>) {</span></div></div><div><div><div>15</div></div><div><span>      </span><span>int</span><span> x, w;</span></div></div><div><div><div>16</div></div><div><span><span>      </span></span><span>cin </span><span>&gt;&gt;</span><span> x </span><span>&gt;&gt;</span><span> w;</span></div></div><div><div><div>17</div></div><div><span><span>      </span></span><span>x</span><span>--</span><span>;</span></div></div><div><div><div>18</div></div><div><span><span>      </span></span><span>s.</span><span>erase</span><span>({a[x], x});</span></div></div><div><div><div>19</div></div><div><span><span>      </span></span><span>a[x] </span><span>=</span><span> w;</span></div></div><div><div><div>20</div></div><div><span><span>      </span></span><span>s.</span><span>insert</span><span>({a[x], x});</span></div></div><div><div><div>21</div></div><div><span><span>    </span></span><span>} </span><span>else</span><span> {</span></div></div><div><div><div>22</div></div><div><span>      </span><span>int</span><span> u, v, k;</span></div></div><div><div><div>23</div></div><div><span><span>      </span></span><span>cin </span><span>&gt;&gt;</span><span> u </span><span>&gt;&gt;</span><span> v </span><span>&gt;&gt;</span><span> k;</span></div></div><div><div><div>24</div></div><div><span><span>      </span></span><span>u</span><span>--</span><span>, v</span><span>--</span><span>;</span></div></div><div><div><div>25</div></div><div>
</div></div><div><div><div>26</div></div><div><span>      </span><span>vc</span><span>&lt;</span><span>int</span><span>&gt; pos;</span></div></div><div><div><div>27</div></div><div><span><span>      </span></span><span>pos </span><span>+=</span><span> u, pos </span><span>+=</span><span> v;</span></div></div><div><div><div>28</div></div><div><span><span>      </span></span><span>idx[u] </span><span>=</span><span> </span><span>0</span><span>, idx[v] </span><span>=</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>29</div></div><div><span>      </span><span>for</span><span> (</span><span>auto</span><span> [d, i] : s) </span><span>if</span><span> (i </span><span>!=</span><span> u </span><span>&amp;&amp;</span><span> i </span><span>!=</span><span> v) {</span></div></div><div><div><div>30</div></div><div><span><span>        </span></span><span>idx[i] </span><span>=</span><span> </span><span>sz</span><span>(pos), pos </span><span>+=</span><span> i;</span></div></div><div><div><div>31</div></div><div><span>        </span><span>if</span><span> (</span><span>sz</span><span>(pos) </span><span>==</span><span> </span><span>12</span><span>) </span><span>break</span><span>;</span></div></div><div><div><div>32</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>33</div></div><div>
</div></div><div><div><div>34</div></div><div><span>      </span><span>int</span><span> m </span><span>=</span><span> </span><span>sz</span><span>(pos);</span></div></div><div><div><div>35</div></div><div><span>      </span><span>vc</span><span>&lt;</span><span>vc</span><span>&lt;</span><span>int</span><span>&gt;&gt; </span><span>f</span><span>(m, </span><span>vc</span><span>&lt;</span><span>int</span><span>&gt; (m, inf32));</span></div></div><div><div><div>36</div></div><div><span>      </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>0</span><span>; i </span><span>&lt;</span><span> m; i</span><span>++</span><span>)</span></div></div><div><div><div>37</div></div><div><span>        </span><span>for</span><span> (</span><span>int</span><span> j </span><span>=</span><span> </span><span>0</span><span>; j </span><span>&lt;</span><span> m; j</span><span>++</span><span>)</span></div></div><div><div><div>38</div></div><div><span><span>          </span></span><span>f[i][j] </span><span>=</span><span> </span><span>min</span><span>(a[pos[i]], a[pos[j]]);</span></div></div><div><div><div>39</div></div><div>
</div></div><div><div><div>40</div></div><div><span>      </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>0</span><span>; i </span><span>&lt;</span><span> m; i</span><span>++</span><span>) f[i][i] </span><span>=</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>41</div></div><div><span>      </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>0</span><span>; i </span><span>&lt;</span><span> k; i</span><span>++</span><span>) {</span></div></div><div><div><div>42</div></div><div><span>        </span><span>int</span><span> x, y;</span></div></div><div><div><div>43</div></div><div><span><span>        </span></span><span>cin </span><span>&gt;&gt;</span><span> x </span><span>&gt;&gt;</span><span> y;</span></div></div><div><div><div>44</div></div><div><span><span>        </span></span><span>x</span><span>--</span><span>, y</span><span>--</span><span>;</span></div></div><div><div><div>45</div></div><div><span>        </span><span>if</span><span> (idx[x] </span><span>==</span><span> </span><span>-</span><span>1</span><span> </span><span>||</span><span> idx[y] </span><span>==</span><span> </span><span>-</span><span>1</span><span>) </span><span>continue</span><span>;</span></div></div><div><div><div>46</div></div><div><span><span>        </span></span><span>f[idx[x]][idx[y]] </span><span>=</span><span> f[idx[y]][idx[x]] </span><span>=</span><span> inf32;</span></div></div><div><div><div>47</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>48</div></div><div><span>      </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>0</span><span>; i </span><span>&lt;</span><span> m; i</span><span>++</span><span>)</span></div></div><div><div><div>49</div></div><div><span>        </span><span>for</span><span> (</span><span>int</span><span> j </span><span>=</span><span> </span><span>0</span><span>; j </span><span>&lt;</span><span> m; j</span><span>++</span><span>)</span></div></div><div><div><div>50</div></div><div><span>          </span><span>for</span><span> (</span><span>int</span><span> k </span><span>=</span><span> </span><span>0</span><span>; k </span><span>&lt;</span><span> m; k</span><span>++</span><span>)</span></div></div><div><div><div>51</div></div><div><span>            </span><span>ckmin</span><span>(f[j][k], f[j][i] </span><span>+</span><span> f[i][k]);</span></div></div><div><div><div>52</div></div><div>
</div></div><div><div><div>53</div></div><div><span><span>      </span></span><span>cout </span><span>&lt;&lt;</span><span> f[</span><span>0</span><span>][</span><span>1</span><span>] </span><span>&lt;&lt;</span><span> </span><span>'</span><span>\n</span><span>'</span><span>;</span></div></div><div><div><div>54</div></div><div><span>      </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>0</span><span>; i </span><span>&lt;</span><span> </span><span>sz</span><span>(pos); i</span><span>++</span><span>) idx[pos[i]] </span><span>=</span><span> </span><span>-</span><span>1</span><span>;</span></div></div><div><div><div>55</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>56</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>57</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section>
<section><h1><a href="https://codeforces.com/gym/574109/problem/J" target="_blank">MEX Should Be Same</a><a href="#mex-should-be-same"><span>#</span></a></h1><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>void</span><span> </span><span>solve</span><span>() {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>int</span><span> n;</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>cin </span><span>&gt;&gt;</span><span> n;</span></div></div><div><div><div>4</div></div><div><span>  </span><span>V</span><span>&lt;</span><span>V</span><span>&lt;</span><span>int</span><span>&gt;&gt; </span><span>b</span><span>(n, </span><span>V</span><span>&lt;</span><span>int</span><span>&gt;(n, </span><span>0</span><span>));</span></div></div><div><div><div>5</div></div><div><span>  </span><span>int</span><span> cnt </span><span>=</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>6</div></div><div><span>  </span><span>for</span><span> (</span><span>auto</span><span> </span><span>&amp;</span><span>x : b) {</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>cin </span><span>&gt;&gt;</span><span> x;</span></div></div><div><div><div>8</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>9</div></div><div><span>  </span><span>for</span><span> (</span><span>auto</span><span> </span><span>&amp;</span><span>x : b) {</span></div></div><div><div><div>10</div></div><div><span>    </span><span>for</span><span> (</span><span>auto</span><span> </span><span>&amp;</span><span>y : x) {</span></div></div><div><div><div>11</div></div><div><span>      </span><span>if</span><span> (y </span><span>==</span><span> </span><span>0</span><span>) {</span></div></div><div><div><div>12</div></div><div><span><span>        </span></span><span>cnt</span><span>++</span><span>;</span></div></div><div><div><div>13</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>14</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>15</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>16</div></div><div><span>  </span><span>int</span><span> maxx </span><span>=</span><span> </span><span>div</span><span>&lt;</span><span>int</span><span>&gt;(n </span><span>*</span><span> n, </span><span>2</span><span>, flase);</span></div></div><div><div><div>17</div></div><div><span>  </span><span>int</span><span> ok </span><span>=</span><span> (cnt </span><span>&lt;=</span><span> maxx </span><span>?</span><span> </span><span>1</span><span> </span><span>:</span><span> </span><span>0</span><span>);</span></div></div><div><div><div>18</div></div><div><span>  </span><span>for</span><span> (</span><span>auto</span><span> </span><span>&amp;</span><span>x : b) {</span></div></div><div><div><div>19</div></div><div><span>    </span><span>for</span><span> (</span><span>auto</span><span> </span><span>&amp;</span><span>y : x) {</span></div></div><div><div><div>20</div></div><div><span>      </span><span>if</span><span> ((ok </span><span>and</span><span> y </span><span>==</span><span> </span><span>0</span><span>) </span><span>or</span><span> (ok </span><span>==</span><span> </span><span>0</span><span> </span><span>and</span><span> y)) {</span></div></div><div><div><div>21</div></div><div><span><span>        </span></span><span>y </span><span>=</span><span> ok;</span></div></div><div><div><div>22</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>23</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>24</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>25</div></div><div><span>  </span><span>for</span><span> (</span><span>auto</span><span> </span><span>&amp;</span><span>x : b) {</span></div></div><div><div><div>26</div></div><div><span><span>    </span></span><span>cout </span><span>&lt;&lt;</span><span> x </span><span>&lt;&lt;</span><span> endl;</span></div></div><div><div><div>27</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>28</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section>
<section><h1><a href="https://codeforces.com/gym/574109/problem/L" target="_blank">LRU is Best? (Hard Version)</a><a href="#lru-is-best-hard-version"><span>#</span></a></h1><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>#include</span><span> </span><span>&lt;bits/stdc++.h&gt;</span></div></div><div><div><div>2</div></div><div><span>using</span><span> </span><span>namespace</span><span> </span><span>std</span><span>;</span></div></div><div><div><div>3</div></div><div><span>using</span><span> </span><span>i64</span><span> </span><span>=</span><span> </span><span>long</span><span> </span><span>long</span><span>;</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>const</span><span> </span><span>int</span><span> MAXN </span><span>=</span><span> </span><span>800</span><span> </span><span>*</span><span> </span><span>3</span><span>;</span></div></div><div><div><div>6</div></div><div><span>const</span><span> i64 INF </span><span>=</span><span> </span><span>1</span><span>e</span><span>18</span><span>;</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>struct</span><span> </span><span>Edge</span><span> {</span></div></div><div><div><div>9</div></div><div><span>    </span><span>int</span><span> to, cap, cost, rev;</span></div></div><div><div><div>10</div></div><div><span>};</span></div></div><div><div><div>11</div></div><div>
</div></div><div><div><div>12</div></div><div><span>vector</span><span>&lt;</span><span>Edge</span><span>&gt;</span><span> G[MAXN];</span></div></div><div><div><div>13</div></div><div><span>i64 dist[MAXN];</span></div></div><div><div><div>14</div></div><div><span>int</span><span> prevv[MAXN], preve[MAXN];</span></div></div><div><div><div>15</div></div><div>
</div></div><div><div><div>16</div></div><div><span>// 添加边的函数</span></div></div><div><div><div>17</div></div><div><span>void</span><span> </span><span>add_edge</span><span>(</span><span>int</span><span> </span><span>from</span><span>, </span><span>int</span><span> </span><span>to</span><span>, </span><span>int</span><span> </span><span>cap</span><span>, </span><span>int</span><span> </span><span>cost</span><span>) {</span></div></div><div><div><div>18</div></div><div><span><span>    </span></span><span>G[from].</span><span>push_back</span><span>({to, cap, cost, (</span><span>int</span><span>)G[to].</span><span>size</span><span>()});</span></div></div><div><div><div>19</div></div><div><span><span>    </span></span><span>G[to].</span><span>push_back</span><span>({from, </span><span>0</span><span>, </span><span>-</span><span>cost, (</span><span>int</span><span>)G[from].</span><span>size</span><span>() </span><span>-</span><span> </span><span>1</span><span>});</span></div></div><div><div><div>20</div></div><div><span>}</span></div></div><div><div><div>21</div></div><div>
</div></div><div><div><div>22</div></div><div><span>// 求解最大费用流</span></div></div><div><div><div>23</div></div><div><span>pair</span><span>&lt;</span><span>int</span><span>, </span><span>i64</span><span>&gt; </span><span>min_cost_flow</span><span>(</span><span>int</span><span> </span><span>s</span><span>, </span><span>int</span><span> </span><span>t</span><span>, </span><span>int</span><span> </span><span>f</span><span>) {</span></div></div><div><div><div>24</div></div><div><span>    </span><span>int</span><span> flow </span><span>=</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>25</div></div><div><span>    </span><span>i64</span><span> cost </span><span>=</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>26</div></div><div><span>    </span><span>while</span><span> (flow </span><span>&lt;</span><span> f) {</span></div></div><div><div><div>27</div></div><div><span>        </span><span>fill</span><span>(dist, dist </span><span>+</span><span> MAXN, </span><span>-</span><span>INF);</span></div></div><div><div><div>28</div></div><div><span><span>        </span></span><span>dist[s] </span><span>=</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>29</div></div><div><span>        </span><span>bool</span><span> update </span><span>=</span><span> </span><span>true</span><span>;</span></div></div><div><div><div>30</div></div><div><span>        </span><span>while</span><span> (update) {</span></div></div><div><div><div>31</div></div><div><span><span>            </span></span><span>update </span><span>=</span><span> </span><span>false</span><span>;</span></div></div><div><div><div>32</div></div><div><span>            </span><span>for</span><span> (</span><span>int</span><span> v </span><span>=</span><span> </span><span>0</span><span>; v </span><span>&lt;</span><span> MAXN; v</span><span>++</span><span>) {</span></div></div><div><div><div>33</div></div><div><span>                </span><span>if</span><span> (dist[v] </span><span>==</span><span> </span><span>-</span><span>INF) </span><span>continue</span><span>;</span></div></div><div><div><div>34</div></div><div><span>                </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>0</span><span>; i </span><span>&lt;</span><span> G[v].</span><span>size</span><span>(); i</span><span>++</span><span>) {</span></div></div><div><div><div>35</div></div><div><span>                    </span><span>Edge</span><span> </span><span>&amp;</span><span>e </span><span>=</span><span> G[v][i];</span></div></div><div><div><div>36</div></div><div><span>                    </span><span>if</span><span> (e.cap </span><span>&gt;</span><span> </span><span>0</span><span> </span><span>&amp;&amp;</span><span> dist[e.to] </span><span>&lt;</span><span> dist[v] </span><span>+</span><span> e.cost) {</span></div></div><div><div><div>37</div></div><div><span><span>                        </span></span><span>dist[e.to] </span><span>=</span><span> dist[v] </span><span>+</span><span> e.cost;</span></div></div><div><div><div>38</div></div><div><span><span>                        </span></span><span>prevv[e.to] </span><span>=</span><span> v;</span></div></div><div><div><div>39</div></div><div><span><span>                        </span></span><span>preve[e.to] </span><span>=</span><span> i;</span></div></div><div><div><div>40</div></div><div><span><span>                        </span></span><span>update </span><span>=</span><span> </span><span>true</span><span>;</span></div></div><div><div><div>41</div></div><div><span><span>                    </span></span><span>}</span></div></div><div><div><div>42</div></div><div><span><span>                </span></span><span>}</span></div></div><div><div><div>43</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>44</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>45</div></div><div>
</div></div><div><div><div>46</div></div><div><span>        </span><span>if</span><span> (dist[t] </span><span>==</span><span> </span><span>-</span><span>INF) {</span></div></div><div><div><div>47</div></div><div><span>            </span><span>return</span><span> {flow, cost};</span></div></div><div><div><div>48</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>49</div></div><div>
</div></div><div><div><div>50</div></div><div><span>        </span><span>int</span><span> d </span><span>=</span><span> f </span><span>-</span><span> flow;</span></div></div><div><div><div>51</div></div><div><span>        </span><span>for</span><span> (</span><span>int</span><span> v </span><span>=</span><span> t; v </span><span>!=</span><span> s; v </span><span>=</span><span> prevv[v]) {</span></div></div><div><div><div>52</div></div><div><span><span>            </span></span><span>d </span><span>=</span><span> </span><span>min</span><span>(d, G[prevv[v]][preve[v]].cap);</span></div></div><div><div><div>53</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>54</div></div><div>
</div></div><div><div><div>55</div></div><div><span>        </span><span>for</span><span> (</span><span>int</span><span> v </span><span>=</span><span> t; v </span><span>!=</span><span> s; v </span><span>=</span><span> prevv[v]) {</span></div></div><div><div><div>56</div></div><div><span>            </span><span>Edge</span><span> </span><span>&amp;</span><span>e </span><span>=</span><span> G[prevv[v]][preve[v]];</span></div></div><div><div><div>57</div></div><div><span><span>            </span></span><span>e.cap </span><span>-=</span><span> d;</span></div></div><div><div><div>58</div></div><div><span><span>            </span></span><span>G[v][e.rev].cap </span><span>+=</span><span> d;</span></div></div><div><div><div>59</div></div><div><span><span>            </span></span><span>cost </span><span>+=</span><span> </span><span>1</span><span>ll</span><span> </span><span>*</span><span> d </span><span>*</span><span> e.cost;</span></div></div><div><div><div>60</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>61</div></div><div><span><span>        </span></span><span>flow </span><span>+=</span><span> d;</span></div></div><div><div><div>62</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>63</div></div><div><span>    </span><span>return</span><span> {flow, cost};</span></div></div><div><div><div>64</div></div><div><span>}</span></div></div><div><div><div>65</div></div><div>
</div></div><div><div><div>66</div></div><div><span>int</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>67</div></div><div><span><span>    </span></span><span>cin.</span><span>tie</span><span>(</span><span>nullptr</span><span>)-&gt;</span><span>sync_with_stdio</span><span>(</span><span>false</span><span>);</span></div></div><div><div><div>68</div></div><div>
</div></div><div><div><div>69</div></div><div><span>    </span><span>int</span><span> t;</span></div></div><div><div><div>70</div></div><div><span><span>    </span></span><span>cin </span><span>&gt;&gt;</span><span> t;</span></div></div><div><div><div>71</div></div><div><span>    </span><span>while</span><span> (t</span><span>--</span><span>) {</span></div></div><div><div><div>72</div></div><div><span>        </span><span>int</span><span> n, m;</span></div></div><div><div><div>73</div></div><div><span><span>        </span></span><span>cin </span><span>&gt;&gt;</span><span> n </span><span>&gt;&gt;</span><span> m;</span></div></div><div><div><div>74</div></div><div><span>        </span><span>i64</span><span> ans </span><span>=</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>75</div></div><div><span>        </span><span>vector</span><span>&lt;</span><span>int</span><span>&gt; </span><span>a</span><span>(n </span><span>+</span><span> </span><span>1</span><span>), x </span><span>=</span><span> a, y </span><span>=</span><span> a, z </span><span>=</span><span> a;</span></div></div><div><div><div>76</div></div><div><span>        </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>1</span><span>; i </span><span>&lt;=</span><span> n; i</span><span>++</span><span>) {</span></div></div><div><div><div>77</div></div><div><span><span>            </span></span><span>cin </span><span>&gt;&gt;</span><span> a[i];</span></div></div><div><div><div>78</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>79</div></div><div><span>        </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>1</span><span>; i </span><span>&lt;=</span><span> n; i</span><span>++</span><span>) {</span></div></div><div><div><div>80</div></div><div><span><span>            </span></span><span>cin </span><span>&gt;&gt;</span><span> x[i];</span></div></div><div><div><div>81</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>82</div></div><div><span>        </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>1</span><span>; i </span><span>&lt;=</span><span> n; i</span><span>++</span><span>) {</span></div></div><div><div><div>83</div></div><div><span><span>            </span></span><span>cin </span><span>&gt;&gt;</span><span> y[i];</span></div></div><div><div><div>84</div></div><div><span><span>            </span></span><span>ans </span><span>-=</span><span> y[i];</span></div></div><div><div><div>85</div></div><div><span><span>            </span></span><span>x[i] </span><span>+=</span><span> y[i];</span></div></div><div><div><div>86</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>87</div></div><div><span>        </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>1</span><span>; i </span><span>&lt;=</span><span> n; i</span><span>++</span><span>) {</span></div></div><div><div><div>88</div></div><div><span><span>            </span></span><span>cin </span><span>&gt;&gt;</span><span> z[i];</span></div></div><div><div><div>89</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>90</div></div><div><span>        </span><span>int</span><span> tot </span><span>=</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>91</div></div><div><span>        </span><span>vector</span><span>&lt;</span><span>vector</span><span>&lt;</span><span>int</span><span>&gt;&gt; </span><span>id</span><span>(n </span><span>+</span><span> </span><span>1</span><span>, </span><span>vector</span><span>&lt;</span><span>int</span><span>&gt;(n </span><span>+</span><span> </span><span>1</span><span>));</span></div></div><div><div><div>92</div></div><div><span>        </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>1</span><span>; i </span><span>&lt;=</span><span> n; i</span><span>++</span><span>) {</span></div></div><div><div><div>93</div></div><div><span><span>            </span></span><span>id[i] </span><span>=</span><span> id[i </span><span>-</span><span> </span><span>1</span><span>];</span></div></div><div><div><div>94</div></div><div><span><span>            </span></span><span>id[i][</span><span>0</span><span>] </span><span>=</span><span> </span><span>++</span><span>tot;</span></div></div><div><div><div>95</div></div><div><span><span>            </span></span><span>id[i][a[i]] </span><span>=</span><span> </span><span>++</span><span>tot;</span></div></div><div><div><div>96</div></div><div><span>            </span><span>add_edge</span><span>(id[i][a[i]], id[i][</span><span>0</span><span>], </span><span>1</span><span>, </span><span>0</span><span>);</span></div></div><div><div><div>97</div></div><div><span>            </span><span>add_edge</span><span>(id[i][</span><span>0</span><span>], id[i][a[i]], </span><span>1</span><span>, </span><span>-</span><span>z[i]);</span></div></div><div><div><div>98</div></div><div><span>            </span><span>if</span><span> (i </span><span>&gt;</span><span> </span><span>1</span><span>) {</span></div></div><div><div><div>99</div></div><div><span>                </span><span>add_edge</span><span>(id[i </span><span>-</span><span> </span><span>1</span><span>][</span><span>0</span><span>], id[i][</span><span>0</span><span>], m, </span><span>0</span><span>);</span></div></div><div><div><div>100</div></div><div><span>                </span><span>add_edge</span><span>(id[i </span><span>-</span><span> </span><span>1</span><span>][a[i]], id[i][a[i]], </span><span>1</span><span>, x[i]);</span></div></div><div><div><div>101</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>102</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>103</div></div><div><span><span>        </span></span><span>cout </span><span>&lt;&lt;</span><span> ans </span><span>+</span><span> </span><span>min_cost_flow</span><span>(id[</span><span>1</span><span>][</span><span>0</span><span>], id[n][</span><span>0</span><span>], m).second </span><span>&lt;&lt;</span><span> </span><span>'</span><span>\n</span><span>'</span><span>;</span></div></div><div><div><div>104</div></div><div><span>        </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>1</span><span>; i </span><span>&lt;=</span><span> tot; i</span><span>++</span><span>) {</span></div></div><div><div><div>105</div></div><div><span><span>            </span></span><span>G[i].</span><span>clear</span><span>();</span></div></div><div><div><div>106</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>107</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>108</div></div><div><span>}</span></div></div><div><div><div>109</div></div><div><span>/*</span></div></div><div><div><div>110</div></div><div><span>1</span></div></div><div><div><div>111</div></div><div><span>2 2</span></div></div><div><div><div>112</div></div><div><span>1 2</span></div></div><div><div><div>113</div></div><div><span>1 1</span></div></div><div><div><div>114</div></div><div><span>0 0</span></div></div><div><div><div>115</div></div><div><span>0 0</span></div></div><div><div><div>116</div></div><div><span>*/</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section>
<section><h1><a href="https://codeforces.com/gym/574109/problem/M" target="_blank">猫娘部署</a><a href="#猫娘部署"><span>#</span></a></h1><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>void</span><span> </span><span>solve</span><span>() {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>int</span><span> n, m;</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>cin </span><span>&gt;&gt;</span><span> n </span><span>&gt;&gt;</span><span> m;</span></div></div><div><div><div>4</div></div><div><span>  </span><span>V</span><span>&lt;</span><span>string</span><span>&gt; </span><span>a</span><span>(n);</span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>cin </span><span>&gt;&gt;</span><span> a;</span></div></div><div><div><div>6</div></div><div><span>  </span><span>V</span><span>&lt;</span><span>int</span><span>&gt; </span><span>avi</span><span>(n, </span><span>0</span><span>);</span></div></div><div><div><div>7</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>0</span><span>; i </span><span>&lt;</span><span> n; i</span><span>++</span><span>) {</span></div></div><div><div><div>8</div></div><div><span>    </span><span>int</span><span> so </span><span>=</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>9</div></div><div><span>    </span><span>for</span><span> (</span><span>int</span><span> j </span><span>=</span><span> </span><span>0</span><span>; j </span><span>&lt;</span><span> m; j</span><span>++</span><span>) {</span></div></div><div><div><div>10</div></div><div><span>      </span><span>if</span><span> (a[i][j] </span><span>==</span><span> </span><span>'Y'</span><span>) {</span></div></div><div><div><div>11</div></div><div><span><span>        </span></span><span>so </span><span>|=</span><span> (</span><span>1</span><span> </span><span>&lt;&lt;</span><span> j);</span></div></div><div><div><div>12</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>13</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>14</div></div><div><span><span>    </span></span><span>avi[i] </span><span>=</span><span> so;</span></div></div><div><div><div>15</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>16</div></div><div><span>  </span><span>V</span><span>&lt;</span><span>V</span><span>&lt;</span><span>int</span><span>&gt;&gt; </span><span>valid</span><span>(n);</span></div></div><div><div><div>17</div></div><div><span>  </span><span>V</span><span>&lt;</span><span>V</span><span>&lt;</span><span>int</span><span>&gt;&gt; </span><span>cnt</span><span>(n);</span></div></div><div><div><div>18</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>0</span><span>; i </span><span>&lt;</span><span> n; i</span><span>++</span><span>) {</span></div></div><div><div><div>19</div></div><div><span>    </span><span>for</span><span> (</span><span>int</span><span> j </span><span>=</span><span> </span><span>0</span><span>; j </span><span>&lt;</span><span> (</span><span>1</span><span> </span><span>&lt;&lt;</span><span> m); j</span><span>++</span><span>) {</span></div></div><div><div><div>20</div></div><div><span>      </span><span>if</span><span> ((j </span><span>&amp;</span><span> avi[i]) </span><span>!=</span><span> j) {</span></div></div><div><div><div>21</div></div><div><span>        </span><span>continue</span><span>;</span></div></div><div><div><div>22</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>23</div></div><div><span>      </span><span>if</span><span> (j </span><span>&amp;</span><span> (j </span><span>&lt;&lt;</span><span> </span><span>1</span><span>)) {</span></div></div><div><div><div>24</div></div><div><span>        </span><span>continue</span><span>;</span></div></div><div><div><div>25</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>26</div></div><div><span><span>      </span></span><span>valid[i].</span><span>pb</span><span>(j);</span></div></div><div><div><div>27</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>28</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>29</div></div><div><span>  </span><span>int</span><span> lim </span><span>=</span><span> (</span><span>1</span><span> </span><span>&lt;&lt;</span><span> m);</span></div></div><div><div><div>30</div></div><div><span>  </span><span>V</span><span>&lt;</span><span>V</span><span>&lt;</span><span>int</span><span>&gt;&gt; </span><span>dp</span><span>(n, </span><span>V</span><span>&lt;</span><span>int</span><span>&gt;(lim, </span><span>-</span><span>1</span><span>));</span></div></div><div><div><div>31</div></div><div><span>  </span><span>for</span><span> (</span><span>auto</span><span> x : valid[</span><span>0</span><span>]) {</span></div></div><div><div><div>32</div></div><div><span><span>    </span></span><span>dp[</span><span>0</span><span>][x] </span><span>=</span><span> </span><span>__builtin_popcount</span><span>(x);</span></div></div><div><div><div>33</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>34</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>1</span><span>; i </span><span>&lt;</span><span> n; i</span><span>++</span><span>) {</span></div></div><div><div><div>35</div></div><div><span>    </span><span>for</span><span> (</span><span>auto</span><span> </span><span>&amp;</span><span>x : valid[i]) {</span></div></div><div><div><div>36</div></div><div><span>      </span><span>int</span><span> cur </span><span>=</span><span> </span><span>__builtin_popcount</span><span>(x);</span></div></div><div><div><div>37</div></div><div><span>      </span><span>for</span><span> (</span><span>auto</span><span> </span><span>&amp;</span><span>y : valid[i </span><span>-</span><span> </span><span>1</span><span>]) {</span></div></div><div><div><div>38</div></div><div><span>        </span><span>if</span><span> (dp[i </span><span>-</span><span> </span><span>1</span><span>][y] </span><span>&lt;</span><span> </span><span>0</span><span>) {</span></div></div><div><div><div>39</div></div><div><span>          </span><span>continue</span><span>;</span></div></div><div><div><div>40</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>41</div></div><div><span>        </span><span>if</span><span> (x </span><span>&amp;</span><span> y) {</span></div></div><div><div><div>42</div></div><div><span>          </span><span>continue</span><span>;</span></div></div><div><div><div>43</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>44</div></div><div><span><span>        </span></span><span>dp[i][x] </span><span>=</span><span> </span><span>max</span><span>(dp[i][x], dp[i </span><span>-</span><span> </span><span>1</span><span>][y] </span><span>+</span><span> cur);</span></div></div><div><div><div>45</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>46</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>47</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>48</div></div><div>
</div></div><div><div><div>49</div></div><div><span>  </span><span>int</span><span> ans </span><span>=</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>50</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>0</span><span>; i </span><span>&lt;</span><span> lim; i</span><span>++</span><span>) {</span></div></div><div><div><div>51</div></div><div><span>    </span><span>ckmax</span><span>(ans, dp[n</span><span>-</span><span>1</span><span>][i]);</span></div></div><div><div><div>52</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>53</div></div><div><span><span>  </span></span><span>cout </span><span>&lt;&lt;</span><span> ans </span><span>&lt;&lt;</span><span> endl;</span></div></div><div><div><div>54</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section>
<section><h1><a href="https://codeforces.com/gym/574109/problem/N" target="_blank">混沌数字</a><a href="#混沌数字"><span>#</span></a></h1><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>#include</span><span> </span><span>&lt;bits/stdc++.h&gt;</span></div></div><div><div><div>2</div></div><div><span>using</span><span> </span><span>namespace</span><span> </span><span>std</span><span>;</span></div></div><div><div><div>3</div></div><div><span>using</span><span> </span><span>i64</span><span> </span><span>=</span><span> </span><span>long</span><span> </span><span>long</span><span>;</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>int</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>cin.</span><span>tie</span><span>(</span><span>nullptr</span><span>)-&gt;</span><span>sync_with_stdio</span><span>(</span><span>false</span><span>);</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>// ifstream cin;</span></div></div><div><div><div>11</div></div><div><span><span>    </span></span><span>// cin.open("0.in");</span></div></div><div><div><div>12</div></div><div><span>    </span><span>int</span><span> n, m;</span></div></div><div><div><div>13</div></div><div><span><span>    </span></span><span>cin </span><span>&gt;&gt;</span><span> n </span><span>&gt;&gt;</span><span> m;</span></div></div><div><div><div>14</div></div><div><span>    </span><span>vector</span><span> </span><span>a</span><span>(n, </span><span>vector</span><span>(m, </span><span>int</span><span>(</span><span>0</span><span>)));</span></div></div><div><div><div>15</div></div><div><span>    </span><span>for</span><span> (</span><span>auto</span><span> </span><span>&amp;</span><span>t : a) {</span></div></div><div><div><div>16</div></div><div><span>        </span><span>string</span><span> s;</span></div></div><div><div><div>17</div></div><div><span><span>        </span></span><span>cin </span><span>&gt;&gt;</span><span> s;</span></div></div><div><div><div>18</div></div><div><span>        </span><span>for</span><span> (</span><span>int</span><span> j </span><span>=</span><span> </span><span>0</span><span>; j </span><span>&lt;</span><span> m; j</span><span>++</span><span>) {</span></div></div><div><div><div>19</div></div><div><span><span>            </span></span><span>t[j] </span><span>=</span><span> s[j] </span><span>-</span><span> </span><span>'0'</span><span>;</span></div></div><div><div><div>20</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>21</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>22</div></div><div><span><span>    </span></span><span>// cin.close();</span></div></div><div><div><div>23</div></div><div><span>    </span><span>vector</span><span>&lt;</span><span>int</span><span>&gt; </span><span>fa</span><span>(n </span><span>*</span><span> m), </span><span>siz</span><span>(n </span><span>*</span><span> m, </span><span>1</span><span>);</span></div></div><div><div><div>24</div></div><div><span>    </span><span>iota</span><span>(fa.</span><span>begin</span><span>(), fa.</span><span>end</span><span>(), </span><span>0</span><span>);</span></div></div><div><div><div>25</div></div><div><span>    </span><span>auto</span><span> find </span><span>=</span><span> [</span><span>&amp;</span><span>](</span><span>auto</span><span> </span><span>&amp;</span><span>find</span><span>, </span><span>int</span><span> </span><span>x</span><span>) -&gt; </span><span>int</span><span> {</span></div></div><div><div><div>26</div></div><div><span>        </span><span>if</span><span> (x </span><span>==</span><span> fa[x]) </span><span>return</span><span> x;</span></div></div><div><div><div>27</div></div><div><span>        </span><span>return</span><span> fa[x] </span><span>=</span><span> </span><span>find</span><span>(find, fa[x]);</span></div></div><div><div><div>28</div></div><div><span><span>    </span></span><span>};</span></div></div><div><div><div>29</div></div><div><span>    </span><span>auto</span><span> id </span><span>=</span><span> [</span><span>&amp;</span><span>](</span><span>int</span><span> </span><span>x</span><span>, </span><span>int</span><span> </span><span>y</span><span>) -&gt; </span><span>int</span><span> {</span></div></div><div><div><div>30</div></div><div><span>        </span><span>return</span><span> x </span><span>*</span><span> m </span><span>+</span><span> y;</span></div></div><div><div><div>31</div></div><div><span><span>    </span></span><span>};</span></div></div><div><div><div>32</div></div><div><span>    </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>0</span><span>; i </span><span>&lt;</span><span> n; i</span><span>++</span><span>) {</span></div></div><div><div><div>33</div></div><div><span>        </span><span>for</span><span> (</span><span>int</span><span> j </span><span>=</span><span> </span><span>0</span><span>; j </span><span>&lt;</span><span> m; j</span><span>++</span><span>) {</span></div></div><div><div><div>34</div></div><div><span>            </span><span>if</span><span> (</span><span>!</span><span>a[i][j]) </span><span>continue</span><span>;</span></div></div><div><div><div>35</div></div><div><span>            </span><span>if</span><span> (i </span><span>and</span><span> a[i </span><span>-</span><span> </span><span>1</span><span>][j]) {</span></div></div><div><div><div>36</div></div><div><span>                </span><span>int</span><span> fx </span><span>=</span><span> </span><span>find</span><span>(find, </span><span>id</span><span>(i </span><span>-</span><span> </span><span>1</span><span>, j));</span></div></div><div><div><div>37</div></div><div><span>                </span><span>int</span><span> fy </span><span>=</span><span> </span><span>find</span><span>(find, </span><span>id</span><span>(i, j));</span></div></div><div><div><div>38</div></div><div><span>                </span><span>if</span><span> (fx </span><span>==</span><span> fy) </span><span>continue</span><span>;</span></div></div><div><div><div>39</div></div><div><span><span>                </span></span><span>fa[fy] </span><span>=</span><span> fx;</span></div></div><div><div><div>40</div></div><div><span><span>                </span></span><span>siz[fx] </span><span>+=</span><span> siz[fy];</span></div></div><div><div><div>41</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>42</div></div><div><span>            </span><span>if</span><span> (j </span><span>and</span><span> a[i][j </span><span>-</span><span> </span><span>1</span><span>]) {</span></div></div><div><div><div>43</div></div><div><span>                </span><span>int</span><span> fx </span><span>=</span><span> </span><span>find</span><span>(find, </span><span>id</span><span>(i, j </span><span>-</span><span> </span><span>1</span><span>));</span></div></div><div><div><div>44</div></div><div><span>                </span><span>int</span><span> fy </span><span>=</span><span> </span><span>find</span><span>(find, </span><span>id</span><span>(i, j));</span></div></div><div><div><div>45</div></div><div><span>                </span><span>if</span><span> (fx </span><span>==</span><span> fy) </span><span>continue</span><span>;</span></div></div><div><div><div>46</div></div><div><span><span>                </span></span><span>fa[fy] </span><span>=</span><span> fx;</span></div></div><div><div><div>47</div></div><div><span><span>                </span></span><span>siz[fx] </span><span>+=</span><span> siz[fy];</span></div></div><div><div><div>48</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>49</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>50</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>51</div></div><div><span>    </span><span>string</span><span> ans;</span></div></div><div><div><div>52</div></div><div><span>    </span><span>for</span><span> (</span><span>int</span><span> j </span><span>=</span><span> </span><span>0</span><span>; j </span><span>&lt;</span><span> m; j</span><span>++</span><span>) {</span></div></div><div><div><div>53</div></div><div><span>        </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>0</span><span>; i </span><span>&lt;</span><span> n; i</span><span>++</span><span>) {</span></div></div><div><div><div>54</div></div><div><span>            </span><span>if</span><span> (</span><span>!</span><span>a[i][j]) </span><span>continue</span><span>;</span></div></div><div><div><div>55</div></div><div><span>            </span><span>int</span><span> cur </span><span>=</span><span> </span><span>id</span><span>(i, j);</span></div></div><div><div><div>56</div></div><div><span>            </span><span>if</span><span> (</span><span>find</span><span>(find, cur) </span><span>!=</span><span> cur) </span><span>continue</span><span>;</span></div></div><div><div><div>57</div></div><div><span>            </span><span>if</span><span> (siz[cur] </span><span>&lt;=</span><span> n </span><span>*</span><span> </span><span>2</span><span>) </span><span>continue</span><span>;</span></div></div><div><div><div>58</div></div><div><span>            </span><span>if</span><span> (siz[cur] </span><span>&gt;=</span><span> n </span><span>*</span><span> </span><span>15</span><span>) ans.</span><span>push_back</span><span>(</span><span>'0'</span><span>);</span></div></div><div><div><div>59</div></div><div><span>            </span><span>else</span><span> ans.</span><span>push_back</span><span>(</span><span>'1'</span><span>);</span></div></div><div><div><div>60</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>61</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>62</div></div><div><span><span>    </span></span><span>cout </span><span>&lt;&lt;</span><span> ans </span><span>&lt;&lt;</span><span> </span><span>'</span><span>\n</span><span>'</span><span>;</span></div></div><div><div><div>63</div></div><div><span>}</span></div></div><div><div><div>64</div></div><div><span>/*</span></div></div><div><div><div>65</div></div><div><span>*/</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section>]]></content>
    <author><name>Zowely</name></author>
    <category term="算法竞赛" />
    <category term="算法竞赛" />
    <category term="题解" />
  </entry>
  <entry>
    <title>葬送的芙莉莲ED《Anytime Anywhere》歌词及罗马音发音</title>
    <link href="https://www.lansganbs.cn/posts/%E6%AD%8C%E6%9B%B2/%E8%91%AC%E9%80%81%E7%9A%84%E8%8A%99%E8%8E%89%E8%8E%B2edanytime-anywhere%E6%AD%8C%E8%AF%8D%E5%8F%8A%E7%BD%97%E9%A9%AC%E9%9F%B3%E5%8F%91%E9%9F%B3/" rel="alternate" type="text/html" />
    <id>https://www.lansganbs.cn/posts/%E6%AD%8C%E6%9B%B2/%E8%91%AC%E9%80%81%E7%9A%84%E8%8A%99%E8%8E%89%E8%8E%B2edanytime-anywhere%E6%AD%8C%E8%AF%8D%E5%8F%8A%E7%BD%97%E9%A9%AC%E9%9F%B3%E5%8F%91%E9%9F%B3/</id>
    <published>2025-05-19T00:00:00.000Z</published>
    <updated>2025-05-19T13:18:19.000Z</updated>
    <summary>【歌词及罗马音发音】葬送的芙莉莲 ED 主题曲 - 「Anytime Anywhere」 / milet</summary>
    <content type="html"><![CDATA[<p>【歌词及罗马音发音】葬送的芙莉莲 ED 主题曲 - 「Anytime Anywhere」 / milet</p>
<p>填词：milet</p>
<p>谱曲：milet、中村泰辅、野村阳一郎</p>
<p>编曲：Evan Call</p>
<p>歌曲原唱：milet</p>
<p></p><figure><img src="https://www.lansganbs.cn/images/article/%E6%97%A5%E8%AF%AD%E6%AD%8C%E6%9B%B2/AnytimeAnywhere/AnytimeAnywhere.png" alt="alt text" /><figcaption>alt text</figcaption></figure><p></p>
<p><a href="https://music.apple.com/cn/album/anytime-anywhere/1708333821?i=1708333825" target="_blank">Anytime Anywhere</a></p>
<hr />
<p>And you alright</p>
<p>过得还好吗</p>
<p>Can you hear me</p>
<p>能看见我的声音吗</p>
<p>誰(だれ)もいない線路沿(せんろぞ)いをなぞってく</p>
<p>da re <u>mo~i</u> na i se <span>n</span> ro <u>zo~i</u> wo na zo~tte ku</p>
<p>沿着空无一人的轨道缓缓前行</p>
<p>大袈裟(おおげさ)に泣(な)いて</p>
<p><u>o o</u> ge sa ni na i te</p>
<p>希望你能痛快地哭泣</p>
<p>笑(わら)ってほしくて</p>
<p>wa ra   tte ho shi <span>ku</span> te</p>
<p>能肆意的欢笑</p>
<p>鮮明(せんめい)でいたい思(おも)い出(で)を抱(だ)きしめてる</p>
<p><u>se n</u> <u>me e</u> <u>de i</u> <u>ta i</u> <u>o mo</u>~i <u>de wo</u>~da ki shi me~te~ru</p>
<p>我希望那些回忆能够永不褪色</p>
<p>さよならよりずっと大切(たいせつ)な</p>
<p>sa yo na ra yo ri zu tto <u>ta i se tsu na</u></p>
<p>比起那句再见</p>
<p>言葉(ことば)で伝(つた)えたいんだ</p>
<p>ko~to ba de <u>tsu ta</u> e ta i n da</p>
<p>我有更重要的话要传达与你</p>
<p>あり触(ふ)れて でも特別(とくべつ)で</p>
<p>a ri fu   re te   <u>de mo to ku be</u>   tsu de</p>
<p>如此平凡 却又如此特别</p>
<p>ほら この目(め)じゃなければ</p>
<p>ho ra   ko no me jya   na ke re ba</p>
<p>看啊 如果不是这双眼</p>
<p>見(み)えなかったものが</p>
<p>mi e na ka tta~mo no ga</p>
<p>那些看不见的事物…</p>
<p>どうして？溢(あふ)れてく</p>
<p>do o shi te   a fu re te ku</p>
<p>为什么喷涌而出？</p>
<p>だから</p>
<p>da ka ra</p>
<p>所以</p>
<p>もう一度(いちど) 生(う)まれ変(か)わろうとも</p>
<p>mo o i chi do   <span>u</span> ma re <span>ka</span> wa ro~o to mo</p>
<p>即使再一次转世</p>
<p>また 私(わたし)はここを選(えら)ぶんだろう</p>
<p><span>ma</span> ta   wa ta shi wa   ko ko wo   <u><span>e</span> ra</u> bu~<span>n</span> da ro o</p>
<p>我一定会再次选择此处吧</p>
<p>だから</p>
<p>da <span>ka</span> ra</p>
<p>所以</p>
<p>あなたと また巡(めぐ)り逢(あ)ったら</p>
<p>a na <u>ta to</u>   <u>ma ta me</u>   gu ri~<span>a</span> tta ra</p>
<p>兜兜转转与你再相逢的话</p>
<p>もう離(はな)さない 今(いま)を選(えら)ぶんだろう</p>
<p>mo o ha na sa <u>na i</u>   <u>i ma wo e ra</u> bu~<span>n</span> da ro o</p>
<p>我一定会抓住现在不会放手吧</p>
<p>約束(やくそく)なんてなくても</p>
<p><u>ya ku so</u>~ku na n te na ku te mo</p>
<p>即使未曾有过任何约定</p>
<p>孤独(こどく)に迷(まよ)う日(ひ)でも</p>
<p><u>ko do ku ni</u> ma yo~u hi bi de mo</p>
<p>即便每日孤独迷茫</p>
<p>その涙(なみだ)だって大丈夫(だいじょうぶ)</p>
<p><u>so no</u> na mi da da tte da i jyo o <span>bu</span></p>
<p>泪水挂于脸庞也无妨</p>
<p>きっと夜(よ)が明(あ)けるよ</p>
<p>ki tto   <u>yo ga a</u> ke ru yo</p>
<p>毕竟长夜终将破晓</p>
<p>And I’m alright (I’ll be alright)</p>
<p>我一切安好（我会安然无恙）</p>
<p>Yeah I hear you (I care about you)</p>
<p>我听见你的声音（我依旧牵挂着你）</p>
<p>伸(の)びた髪(かみ)を風(かぜ)がからかってる</p>
<p>no bi ta~<u>ka mi</u>~wo ka~ze ga~ka <u>ra ka</u>~tte ru</p>
<p>徐徐微风吹拂长发</p>
<p>全部意味(ぜんぶいみ)があるよ</p>
<p>ze n bu <u>i mi ga a ru yo</u></p>
<p>所有一切都是有意义的</p>
<p>立ち止(ど)ま<span>っ</span>た日々(ひび)も</p>
<p><u>ta chi do</u> ma <s>tsu</s> tta hi bi mo</p>
<p>连同那些停滞的时光也一样</p>
<p>今(いま)さらわかってあなたに追(お)いついたよ</p>
<p><u>i ma sa ra wa</u> ka~tte <span>a</span> na~<u>ta ni</u>   o <span>i tsu i</span>~ta yo</p>
<p>直到现在我才想要追逐你的身影</p>
<p>ほら この目(め)じゃなければ</p>
<p>ho ra   ko no me jya   na ke re ba</p>
<p>看啊 如果不是这双眼</p>
<p>見(み)えなかったものが</p>
<p>mi~e na ka tta~mo <u>no ga</u></p>
<p>那些看不到的东西…</p>
<p>どうして？溢(あふ)れてく</p>
<p>do o shi te   a fu re te ku</p>
<p>为什么喷涌而出？</p>
<p>だから</p>
<p>da ka ra</p>
<p>所以</p>
<p>もう一度(いちど) 生(う)まれ変(か)わろうとも</p>
<p>mo o i chi do   <span>u</span> ma re <span>ka</span> wa ro~o to mo</p>
<p>即使再一次转世</p>
<p>また 私(わたし)はここを選(えら)ぶんだろう</p>
<p><span>ma</span> ta   wa ta shi wa   ko ko wo   <u>e ra</u> bu~<span>n</span> da ro o</p>
<p>我一定会再次选择此处吧</p>
<p>だから</p>
<p>da <span>ka</span> ra</p>
<p>所以</p>
<p>あなたと また巡(めぐ)り逢(あ)ったら</p>
<p>a na <u>ta to</u>   <u>ma ta me</u>   gu ri~<span>a</span> tta ra</p>
<p>兜兜转转与你再相逢的话</p>
<p>もう離(はな)さない 今(いま)を選(えら)ぶんだろう</p>
<p>mo o ha na sa <u>na i</u>   <u>i ma wo e ra</u> bu~<span>n</span> da ro o</p>
<p>我一定会抓住现在不会放手吧</p>
<p>（Anytime anywhere yah）  どこにいでも</p>
<p>do ko ni i~de mo</p>
<p>纵使在回忆中</p>
<p>（Anytime anywhere）      笑(わら)ってみせて</p>
<p>wa ra~<u>tte mi se</u>~te</p>
<p>也请对我微笑</p>
<p>（I’ll be there）             目(め)を閉(と)じれば いつも</p>
<p><u>me <span>wo</span></u> to ji re~ba   i~<u>tsu mo</u></p>
<p>闭上双眼 无论何时</p>
<p>（Anytime anywhere yah）   歩(ある)き出(だ)した</p>
<p><u>a ru</u> <span>ki</span> da shi ta</p>
<p>都要迈出这一步</p>
<p>（Anytime anywhere）       私(わたし)を見(み)てて</p>
<p>wa ta shi wo mi te~te</p>
<p>请看好我吧</p>
<p>せめて 会(あ)いたいよ</p>
<p><span>se</span> me te   a i ta i yo</p>
<p>至少还想见你一面</p>
<p>なんて言(い)わないから</p>
<p>na n de <u>i wa</u>~na i ka ra</p>
<p>当然我不会说出口的</p>
<p>ねえ 今日(きょう)だけは</p>
<p>ne e   kyo~o da~<u>ke wa</u></p>
<p>呐 仅仅今天</p>
<p>思(おも)い出(だ)していいかな</p>
<p>o mo i <u>da shi te</u> <u>i i</u> ka na</p>
<p>能让我想起你吗</p>
<p>だから</p>
<p>da <span>ka</span> ra</p>
<p>所以</p>
<p>あなたと また巡(めぐ)り逢(あ)ったら</p>
<p>a na <u>ta to</u>   <u>ma ta me</u>   gu ri~<span>a</span> tta ra</p>
<p>兜兜转转与你再相逢的话</p>
<p>もう迷(まよ)わない今(いま)を選(えら)ぶんだろう</p>
<p>mo o~<u>ma yo</u> wa na i i <u>ma wo</u> <u>e ra</u> bu~<span>n</span> da ro o</p>
<p>我一定会毫不犹豫的选择现在吧</p>
<p>約束(やくそく)なんてなくても</p>
<p>ya ku so ku na n te na <u>ku te</u> mo</p>
<p>即使未曾有过任何约定</p>
<p>孤独(こどく)に迷(まよ)う日(ひ)でも</p>
<p><u>ko do ku ni ma</u> yo~<span>u</span> hi de mo</p>
<p>即便每日孤独迷茫</p>
<p>こんなに胸(むね)が痛(いた)いのは</p>
<p><u>ko n na ni mu</u> <span>ne</span> ga i ta i no wa</p>
<p>我胸口的痛楚</p>
<p>あなたといた証(あかし)かな</p>
<p><u>a na ta to i</u> <u>ta a</u> ka shi ka na</p>
<p>是你与我相伴过的证明吗</p>
<p>絶対(ぜったい)なんてなくても</p>
<p>ze tta i  <u>na a de i</u> na <span>ku</span> de mo</p>
<p>即便那不是绝对的</p>
<p>いつでも届(とど)いてるから</p>
<p><u>i tsu de mo to do</u> <u>i te ru</u>~ka ra</p>
<p>我也切实感受到了你的心意</p>
<p>その涙(なみだ)だって大丈夫(だいじょうぶ)</p>
<p><u>so no</u> na mi da da tte da i jo o <span>bu</span></p>
<p>泪水挂于脸庞也无妨</p>
<p>きっと夜(よ)が明(あ)けるよ</p>
<p>ki tto yo <u>ga a</u>~ke ru yo</p>
<p>毕竟长夜终将破晓</p>
<p>I’m whispering our lullaby for you to come back home</p>
<p>我会轻声为你点亮灯火 照亮你回家的路</p>
<hr />
<p>杂谈：</p>
<p>《葬送的芙莉莲》作为可能是我近几年来最喜欢的动漫。显然这部作品中的反派不是魔族，而是时间，这部作品的主线也并不是芙莉莲的旅途，而是芙莉莲对于“辛美尔的爱意”的逐渐理解。这个漫画既不是像《鬼灭之刃》《咒术回战》《进击的巨人》一样的被动漫党都广为熟知的漫画，动画组MADHOUSE也并没有在近几年内作出我个人很喜欢的动漫，所以我最初并没有看好芙莉莲这部作品。</p>
<p>芙莉莲整部动漫的每一集都保持了最高的水准，第一季最后也完美收官。大多数人都喜欢看勇者讨伐魔王的故事，然而这部作品恰恰相反，讲述的是大家不喜欢看的勇者讨伐完魔王的故事。然而就是这种故事，让《葬送的芙莉莲》随着第28集的完美谢幕，让它成为了热度口碑双丰收的年度佳作，成为了近几年来唯一登上了<a href="https://bgm.tv/" target="_blank">Bangumi</a>动画分区Top100的动画。从刚开播时的“仅是满分”到结束时的“超越满分”。</p>
<p>动画组对于原作中寥寥无几的打斗片段，在动画中给出了完美的答卷，从仅有3也得修塔尔克屠龙，到26集芙莉莲战斗时经费的燃烧，无不体现着动漫组的用心。要知道，战斗从不是这部作品的卖点，也正是动画组对于这些部分的无私奉献，把作品带到了极高的高度。勇者不必拔出圣剑，僧侣不必严于律己，战士不必充满勇气，反套路的作品给大家也带来了眼前一亮的动画演出。辛美尔无疑是近些年来塑造最为成功的勇者，尤其是在“勇者战魔王”这个题材已经饱和的当下，如果一部作品想要脱颖而出，就必须给勇者加一些标新立异的设定，这也使得如今越来越多的勇者，也就名字跟勇者有关了，而辛美尔几乎集齐了勇者的全部优点，他的眼神清澈鉴定，待人温柔却不圣母，有勇者无所畏惧的一面，也有爱耍小脾气爱臭美的一面，他并不是被选中的最强大的勇者，却成为了击败魔王的英雄。</p>
<p>《葬送的芙莉莲》的片尾曲Anytime Anywhere作为我最喜欢的一首歌，每当旋律响起时，昭示着下一个七个世纪的计数开始，芙莉莲也逐渐明白长生种的痛苦。《葬送的芙莉莲》以这首宛转悠扬的歌曲作为结束，倒也算是一种不错的安慰，这首歌完全唱出了芙莉莲这部作品的精髓，无论是以新夫的视角，还是以师傅弟子的视角，通篇传达的一件事情，我们所有的一切的相遇，一切的经历，都是塑造了如今的我们无可取代的美好，“所以即使再次转世重生，我也会选择这里”。ED动画那绽放的生命力，也很好的辅佐了这种感觉。这首ED完美契合了番中辛美尔与芙莉莲跨越时间与生死的恋爱主题，悠扬的旋律与歌词中对爱的执着追求，映照出辛美尔对芙莉莲的爱意，芙莉莲作为长生种情感的迟钝以及遇见菲伦和修塔尔克之后情感的变化，成为整部作品情感升华的重要载体。就如海塔的那句话“正是你人生的那十分之一改变了你”。这首ED配得上作为十月神番的《葬送的芙莉莲》。</p>
<p>即使是这样的动漫，在结局的时候也难免不舍，但是伴随着ED的神插入，勇者辛美尔是这么说的：“但是 只要继续旅行 就还会有邂逅吧 流泪道别并不适合我们 因为 下次见面会很难为情的。”</p>]]></content>
    <author><name>Zowely</name></author>
    <category term="日语歌曲" />
    <category term="日语歌曲" />
    <category term="动漫音乐" />
    <category term="歌词翻译" />
  </entry>
  <entry>
    <title>BanG Dream! It&apos;s MyGO!!!!!插曲《春日影》平假名及罗马音发音</title>
    <link href="https://www.lansganbs.cn/posts/%E6%AD%8C%E6%9B%B2/bang-dream-its-mygo%E6%8F%92%E6%9B%B2%E6%98%A5%E6%97%A5%E5%BD%B1%E5%B9%B3%E5%81%87%E5%90%8D%E5%8F%8A%E7%BD%97%E9%A9%AC%E9%9F%B3%E5%8F%91%E9%9F%B3/" rel="alternate" type="text/html" />
    <id>https://www.lansganbs.cn/posts/%E6%AD%8C%E6%9B%B2/bang-dream-its-mygo%E6%8F%92%E6%9B%B2%E6%98%A5%E6%97%A5%E5%BD%B1%E5%B9%B3%E5%81%87%E5%90%8D%E5%8F%8A%E7%BD%97%E9%A9%AC%E9%9F%B3%E5%8F%91%E9%9F%B3/</id>
    <published>2025-05-17T00:00:00.000Z</published>
    <updated>2025-05-17T13:31:42.000Z</updated>
    <summary>BanG Dream! It&apos;s MyGO!!!!!插曲 - 《春日影》 / MyGO!!!!!的歌词及罗马音发音</summary>
    <content type="html"><![CDATA[<p>【歌词及罗马音发音】BanG Dream! It’s MyGO!!!!!插曲 - 《春日影》 / MyGO!!!!!</p>
<p>填词：织田飞鸟</p>
<p>谱曲：藤田淳平</p>
<p>编曲：藤田淳平</p>
<p>歌曲原唱：CRYCHIC</p>
<p></p><figure><img src="https://www.lansganbs.cn/images/article/%E6%97%A5%E8%AF%AD%E6%AD%8C%E6%9B%B2/%E6%98%A5%E6%97%A5%E5%BD%B1/%E6%98%A5%E6%97%A5%E5%BD%B1.png" alt="alt text" /><figcaption>alt text</figcaption></figure><p></p>
<p><a href="https://music.apple.com/cn/album/%E6%98%A5%E6%97%A5%E5%BD%B1-mygo-ver/1783644603?i=1783644617" target="_blank">春日影（MyGO!!!!! ver.）</a></p>
<hr />
<p>悴（かじか）んだ心（こころ） ふるえる眼差（まなざ）し世界（せかい）で</p>
<p><u>ka ji kan da</u> ko ko ro fu ru e <u>ru ma na</u> za shi se~<u>ka i de</u></p>
<p>僕（ぼく）はひとりぼっちだった   散（ち）ることしか   知（し）らない春（はる）は</p>
<p>bo ku wa <u>hi to</u>~ri bo~chi da~tta   chi ru ko to~shi ka   shi ra na i ha~ru wa</p>
<p>毎年（まいとし）冷（つめ）たくあしらう</p>
<p>ma i to~<u>shi tsu</u> me ta ku~a shi ra u</p>
<p>暗（くら）がりの中（なか） 一方通行（いっぽうつうこうに）ただ ただ</p>
<p><u>ku ra ga ri</u> no na ka i ppo o <u>tsu u</u> ko o ni ta~<u>da ta da</u></p>
<p>言葉（ことば）を書（か）き殴（なぐ）って</p>
<p>ko to ba wo ka ki na gu~tte</p>
<p>期待（きたい）するだけ</p>
<p>ki ta i su ru da ke</p>
<p>むなしいと分かっていても</p>
<p>mu na shi i to wa ka~<u>tte i te mo</u></p>
<p>救（すく）いを求（もと）め続（つづ）けた</p>
<p>su ku i <u>wo mo to me tsu zu</u>~ke ta</p>
<p>せつなくて いとおしい</p>
<p><span>se tsu na ku te i to~o shi i</span></p>
<p>今（いま）ならば分（わ）かる気（き）がする</p>
<p>i ma <u>na ra ba</u> wa ka ru ki~<u>ga su ru</u></p>
<p>しあわせで くるおしい</p>
<p><span>shi a wa se de ku ru o shi i</span></p>
<p>あの日（ひ）泣（な）けなかった   僕（ぼく）を</p>
<p>a no <u>hi na ke na</u> ka~tta   bo ku wo</p>
<p>光（ひかり）は   やさしく連（つ）れ立（だ）つよ</p>
<p>hi ka ri wa   ya sa shi <u>ku tsu re</u>~<u>da tsu yo</u></p>
<p>雲間（くもま）をぬって   きらり きらり   心満（こころみ）たしては溢（あふ）れ</p>
<p><u>ku mo ma wo nu</u>~tte   <u>ki ra ri</u> <u>ki ra ri</u>   <u>ko ko ro</u> mi ta shi te wa~<u>a fu re</u></p>
<p>いつしか頬（ほお）を   きらり きらり   熱（あつ）く 熱く 濡（ぬ）らしてゆく</p>
<p><u>i tsu shi ka ho ho wo</u>   <u>ki ra ri</u> <u>ki ra ri</u>   <u>a tsu ku</u> <u>a tsu ku</u> nu ra~<u>shi te yu ku</u></p>
<p>君（きみ）の手（て）は   どうして   こんなにも   温（あたた）かいの？</p>
<p><u>ki mi no</u>~te wa   do u shi te   <u>ko n</u> na ni mo   <u>a ta ta</u>~ka i no ?</p>
<p>ねえ お願（ねが）い   どうかこのまま</p>
<p><u>ne e</u> o ne ga i   do o ka ko no ma ma~</p>
<p>離（はな）さないでいて</p>
<p>ha na sa na i <u>de i te</u></p>
<p>縁（えん）を結（むす）んでは   ほどきほどかれ誰（だれ）しもが</p>
<p>e n wo mu su n de wa   ho do <u>ki ho</u> do ka re da~<u>re shi mo ga</u></p>
<p>それを喜（よろこ）び   悲（かな）しみながら   愛（あい）を数（かぞ）えてゆく</p>
<p>so re wo yo~ro ko~bi <u>ka na shi</u>   mi na ga ra a i wo   ka zo e te~yu ku</p>
<p>鼓動（こどう）を   確（たし）かめるように</p>
<p>ko do o wo   ta shi ka me~ru yo~o ni</p>
<p>うれしくてさびしくて</p>
<p><span>u re shi ku te sa bi shi ku te</span></p>
<p>今（いま）だからわかる気（き）がした</p>
<p>i ma <u>da ka ra</u> wa ka ru ki~<u>ga shi ta</u></p>
<p>たいせつでこわくって</p>
<p><span>ta i se tsu de ko wa ku~tte</span></p>
<p>あの日（ひ）泣（な）けなかった僕（ぼく）を</p>
<p>a no <u>hi na ke na ka</u>~tta bo ku wo</p>
<p>光（ひかり）は   やさしく抱（だ）きしめた</p>
<p>hi ka ri wa   ya sa shi <u>ku da ki</u>~shi me ta</p>
<hr />
<p>照（て）らされた世界（せかい）</p>
<p>te ra sa re ta se ka i</p>
<p>咲（さ）き誇（ほこ）る大切（たいせつ）な人（ひと）</p>
<p>sa ki ho ko ru ta i se <u>tsu na</u> hi to</p>
<p>あたたかさを知（し）った  </p>
<p>a ta ta ka sa wo shi tta  </p>
<p>春（はる）は 僕（ぼく）のため 君（きみ）のための   涙（なみだ）を流（なが）すよ</p>
<p>ha ru wa bo ku no ta me   ki mi no ta me no   <u>na mi da</u> wo <u>na ga</u> su yo</p>
<p>あぁ なんて眩（まぶ）しいんだろう</p>
<p>a a   na n te   <u>ma bu shi</u> <u>i n</u> da ro o</p>
<p>あぁ なんて美（うつく）しいんだろう</p>
<p>a a   na n te   <u>u tsu ku shi</u> <u>i n</u> da ro o …</p>
<p>雲間（くもま）をぬって   きらり きらり   心満（こころみ）たしては溢（あふ）れ</p>
<p><u>ku mo ma wo nu</u>~tte   <u>ki ra ri</u> <u>ki ra ri</u>   <u>ko ko ro</u> mi ta shi te wa~<u>a fu re</u></p>
<p>いつしか頬（ほお）を   きらり きらり   熱（あつ）く 熱く 濡（ぬ）らしてゆく</p>
<p><u>i tsu shi ka ho ho wo</u>   <u>ki ra ri</u> <u>ki ra ri</u>   <u>a tsu ku</u> <u>a tsu ku</u> nu ra~<u>shi te yu ku</u></p>
<p>君（きみ）の手（て）は   どうして   こんなにも 温（あたた）かいの？</p>
<p><u>ki mi no</u>~te wa   do u shi te   <u>ko n</u> na ni mo   <u>a ta ta</u>~ka i no ?</p>
<p>ねえ お願（ねが）い   どうかこのまま</p>
<p><u>ne e</u> on e gai   do o ka ko no ma ma~</p>
<p>離（はな）さないでいて</p>
<p>ha na sa na i <u>de i te</u></p>
<p>ずっと ずっと</p>
<p>zu tto zu~tto</p>
<p>離（はな）さないでいて</p>
<p>ha na sa na i <u>de i te</u></p>]]></content>
    <author><name>Zowely</name></author>
    <category term="日语歌曲" />
    <category term="日语歌曲" />
    <category term="动漫音乐" />
    <category term="歌词翻译" />
  </entry>
  <entry>
    <title>Bitset Hash Pbds</title>
    <link href="https://www.lansganbs.cn/posts/%E7%AE%97%E6%B3%95%E7%AB%9E%E8%B5%9B/bitset-hash-pbds/" rel="alternate" type="text/html" />
    <id>https://www.lansganbs.cn/posts/%E7%AE%97%E6%B3%95%E7%AB%9E%E8%B5%9B/bitset-hash-pbds/</id>
    <published>2025-03-30T00:00:00.000Z</published>
    <updated>2025-03-30T22:28:14.000Z</updated>
    <summary>published</summary>
    <content type="html"><![CDATA[<section><h1>Bitset<a href="#bitset"><span>#</span></a></h1><p>类模板 <code>bitset</code> 表示一个 <span><span>NN</span><span><span><span></span><span>N</span></span></span></span> 位的固定大小序列。可以用标准逻辑运算符操作 <code>bitset</code>，并将它与字符串和整数相互转换。对于字符串表示和移位操作的列举方向来说，这个序列被当做最低索引元素位于右侧，类似于整数的二进制表示。</p><p><span><span>bitsetbitset</span><span><span><span></span><span>bi</span><span>t</span><span>se</span><span>t</span></span></span></span> 满足可复制构造 (<span><span>CopyConstructibleCopyConstructible</span><span><span><span></span><span>C</span><span>o</span><span>p</span><span>y</span><span>C</span><span>o</span><span>n</span><span>s</span><span>t</span><span>r</span><span>u</span><span>c</span><span>t</span><span>ib</span><span>l</span><span>e</span></span></span></span>) 及可复制赋值 (<span><span>CopyAssignableCopyAssignable</span><span><span><span></span><span>C</span><span>o</span><span>p</span><span>y</span><span>A</span><span>ss</span><span>i</span><span>g</span><span>nab</span><span>l</span><span>e</span></span></span></span>) 的要求。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>template</span><span>&lt; </span><span>std</span><span>::</span><span>size_t</span><span> </span><span>N</span><span> &gt; </span><span>class</span><span> </span><span>bitset</span><span>;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><section><h2>模板形参<a href="#模板形参"><span>#</span></a></h2><p><span><span>NN</span><span><span><span></span><span>N</span></span></span></span>	- 要为 <span><span>bitsetbitset</span><span><span><span></span><span>bi</span><span>t</span><span>se</span><span>t</span></span></span></span> 分配存储的位数</p><p>eg: <code>bitset&lt;N&gt; ac</code>; 声明一个大小为<span><span>NN</span><span><span><span></span><span>N</span></span></span></span>的<code>bitset</code>，名称为<span><span>acac</span><span><span><span></span><span>a</span><span>c</span></span></span></span></p><ul>
<li><code>std::bitset</code> 的全部成员函数均为 <span><span>constexprconstexpr</span><span><span><span></span><span>co</span><span>n</span><span>s</span><span>t</span><span>e</span><span>x</span><span>p</span><span>r</span></span></span></span>：在常量表达式求值中创建并使用 <code>std::bitset</code> 对象是可能的。(C++23 起) 所以不要试图使用这种方式!!!</li>
</ul></section><section><h2>常用操作<a href="#常用操作"><span>#</span></a></h2><ol>
<li><code>count()</code> 返回 <span><span>11</span><span><span><span></span><span>1</span></span></span></span> 的个数，时间复杂度为 <span><span>O(nw)O(\frac{n}{w})</span><span><span><span></span><span>O</span><span>(</span><span><span></span><span><span><span><span><span><span></span><span><span><span>w</span></span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>n</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span>)</span></span></span></span>，其中 <span><span>ww</span><span><span><span></span><span>w</span></span></span></span> 为机器字长，一般为 <span><span>6464</span><span><span><span></span><span>64</span></span></span></span> 位，评测机一般为 <span><span>3232</span><span><span><span></span><span>32</span></span></span></span> 位；有一类似操作<code>__builtin_popcount(val)/__builtin_popcountll(val)</code>，时间复杂度相同（应该是）。</li>
<li><code>operator&lt;&lt;=</code> <code>operator&gt;&gt;=</code> <code>operator&lt;&lt;</code> <code>operator&gt;&gt;</code> 进行二进制左移和右移</li>
<li><code>operator=</code>  赋值</li>
<li><code>operator[]</code> 访问指定的位</li>
<li><code>operator&amp;=</code> <code>operator|=</code> <code>operator^=</code> <code>operator~</code> 进行二进制与、或、异或及非</li>
</ol></section><section><h2>注解<a href="#注解"><span>#</span></a></h2><p>若某个位集合在编译时大小未知，或者必须在运行时改变其大小，则可代之以使用 <code>std::vector&lt;bool&gt;</code> 或 <code>boost::dynamic_bitset</code> 之类的动态类型。</p>




























<table><thead><tr><th>功能特性测试宏</th><th>值</th><th>标准</th><th>功能特性</th></tr></thead><tbody><tr><td>__cpp_lib_constexpr_bitset</td><td>202207L</td><td>(C++23)</td><td>使 std::bitset 更 constexpr</td></tr><tr><td>__cpp_lib_bitset</td><td>202306L</td><td>(C++26)</td><td>std::bitset 的 std::string_view 接口</td></tr><tr><td></td><td></td><td></td><td></td></tr></tbody></table></section><section><h2>示例<a href="#示例"><span>#</span></a></h2><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>#include</span><span> </span><span>&lt;bitset&gt;</span></div></div><div><div><div>2</div></div><div><span>#include</span><span> </span><span>&lt;cassert&gt;</span></div></div><div><div><div>3</div></div><div><span>#include</span><span> </span><span>&lt;cstddef&gt;</span></div></div><div><div><div>4</div></div><div><span>#include</span><span> </span><span>&lt;iostream&gt;</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>int</span><span> </span><span>main</span><span>()</span></div></div><div><div><div>7</div></div><div><span>{</span></div></div><div><div><div>8</div></div><div><span>    </span><span>typedef</span><span> </span><span>std</span><span>::</span><span>size_t</span><span> </span><span>length_t</span><span>, </span><span>position_t</span><span>;</span><span> // 提示</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>// 构造函数：</span></div></div><div><div><div>11</div></div><div><span>    </span><span>constexpr</span><span> </span><span>std</span><span>::bitset</span><span>&lt;</span><span>4</span><span>&gt;</span><span> b1;</span></div></div><div><div><div>12</div></div><div><span>    </span><span>constexpr</span><span> </span><span>std</span><span>::</span><span>bitset</span><span>&lt;</span><span>4</span><span>&gt; b2{</span><span>0x</span><span>A</span><span>};</span><span> // == 0B1010</span></div></div><div><div><div>13</div></div><div><span>    </span><span>std</span><span>::</span><span>bitset</span><span>&lt;</span><span>4</span><span>&gt; b3{</span><span>"0011"</span><span>};</span><span> // C++23 起也可以为 constexpr</span></div></div><div><div><div>14</div></div><div><span>    </span><span>std</span><span>::</span><span>bitset</span><span>&lt;</span><span>8</span><span>&gt; b4{</span><span>"ABBA"</span><span>, </span><span>length_t</span><span>(</span><span>4</span><span>),</span><span> /*0:*/</span><span>'A'</span><span>,</span><span> /*1:*/</span><span>'B'</span><span>};</span><span> // == 0B0000'0110</span></div></div><div><div><div>15</div></div><div>
</div></div><div><div><div>16</div></div><div><span><span>    </span></span><span>// 能打印出 bitset 到流：</span></div></div><div><div><div>17</div></div><div><span>    </span><span>std</span><span>::cout </span><span>&lt;&lt;</span><span> </span><span>"b1:"</span><span> </span><span>&lt;&lt;</span><span> b1 </span><span>&lt;&lt;</span><span> </span><span>"; b2:"</span><span> </span><span>&lt;&lt;</span><span> b2 </span><span>&lt;&lt;</span><span> </span><span>"; b3:"</span><span> </span><span>&lt;&lt;</span><span> b3 </span><span>&lt;&lt;</span><span> </span><span>"; b4:"</span><span> </span><span>&lt;&lt;</span><span> b4 </span><span>&lt;&lt;</span><span> </span><span>'</span><span>\n</span><span>'</span><span>;</span></div></div><div><div><div>18</div></div><div>
</div></div><div><div><div>19</div></div><div><span><span>    </span></span><span>// bitset 支持逐位运算：</span></div></div><div><div><div>20</div></div><div><span><span>    </span></span><span>b3 </span><span>|=</span><span> </span><span>0b</span><span>0100</span><span>; </span><span>assert</span><span>(b3 </span><span>==</span><span> </span><span>0b</span><span>0111</span><span>);</span></div></div><div><div><div>21</div></div><div><span><span>    </span></span><span>b3 </span><span>&amp;=</span><span> </span><span>0b</span><span>0011</span><span>; </span><span>assert</span><span>(b3 </span><span>==</span><span> </span><span>0b</span><span>0011</span><span>);</span></div></div><div><div><div>22</div></div><div><span><span>    </span></span><span>b3 </span><span>^=</span><span> </span><span>std</span><span>::bitset</span><span>&lt;</span><span>4</span><span>&gt;</span><span>{</span><span>0b</span><span>1100</span><span>}; </span><span>assert</span><span>(b3 </span><span>==</span><span> </span><span>0b</span><span>1111</span><span>);</span></div></div><div><div><div>23</div></div><div>
</div></div><div><div><div>24</div></div><div><span><span>    </span></span><span>// 整个集合上的操作：</span></div></div><div><div><div>25</div></div><div><span><span>    </span></span><span>b3.</span><span>reset</span><span>(); </span><span>assert</span><span>(b3 </span><span>==</span><span> </span><span>0</span><span>);</span></div></div><div><div><div>26</div></div><div><span><span>    </span></span><span>b3.</span><span>set</span><span>(); </span><span>assert</span><span>(b3 </span><span>==</span><span> </span><span>0b</span><span>1111</span><span>);</span></div></div><div><div><div>27</div></div><div><span>    </span><span>assert</span><span>(b3.</span><span>all</span><span>() </span><span>&amp;&amp;</span><span> b3.</span><span>any</span><span>() </span><span>&amp;&amp;</span><span> </span><span>!</span><span>b3.</span><span>none</span><span>());</span></div></div><div><div><div>28</div></div><div><span><span>    </span></span><span>b3.</span><span>flip</span><span>(); </span><span>assert</span><span>(b3 </span><span>==</span><span> </span><span>0</span><span>);</span></div></div><div><div><div>29</div></div><div>
</div></div><div><div><div>30</div></div><div><span><span>    </span></span><span>// 单独位上的操作：</span></div></div><div><div><div>31</div></div><div><span><span>    </span></span><span>b3.</span><span>set</span><span>(</span><span>position_t</span><span>(</span><span>1</span><span>), </span><span>true</span><span>); </span><span>assert</span><span>(b3 </span><span>==</span><span> </span><span>0b</span><span>0010</span><span>);</span></div></div><div><div><div>32</div></div><div><span><span>    </span></span><span>b3.</span><span>set</span><span>(</span><span>position_t</span><span>(</span><span>1</span><span>), </span><span>false</span><span>); </span><span>assert</span><span>(b3 </span><span>==</span><span> </span><span>0</span><span>);</span></div></div><div><div><div>33</div></div><div><span><span>    </span></span><span>b3.</span><span>flip</span><span>(</span><span>position_t</span><span>(</span><span>2</span><span>)); </span><span>assert</span><span>(b3 </span><span>==</span><span> </span><span>0b</span><span>0100</span><span>);</span></div></div><div><div><div>34</div></div><div><span><span>    </span></span><span>b3.</span><span>reset</span><span>(</span><span>position_t</span><span>(</span><span>2</span><span>)); </span><span>assert</span><span>(b3 </span><span>==</span><span> </span><span>0</span><span>);</span></div></div><div><div><div>35</div></div><div>
</div></div><div><div><div>36</div></div><div><span><span>    </span></span><span>// 支持下标 operator[]：</span></div></div><div><div><div>37</div></div><div><span><span>    </span></span><span>b3[</span><span>2</span><span>] </span><span>=</span><span> </span><span>true</span><span>; </span><span>assert</span><span>(</span><span>true</span><span> </span><span>==</span><span> b3[</span><span>2</span><span>]);</span></div></div><div><div><div>38</div></div><div>
</div></div><div><div><div>39</div></div><div><span><span>    </span></span><span>// 其他操作：</span></div></div><div><div><div>40</div></div><div><span>    </span><span>assert</span><span>(b3.</span><span>count</span><span>() </span><span>==</span><span> </span><span>1</span><span>);</span></div></div><div><div><div>41</div></div><div><span>    </span><span>assert</span><span>(b3.</span><span>size</span><span>() </span><span>==</span><span> </span><span>4</span><span>);</span></div></div><div><div><div>42</div></div><div><span>    </span><span>assert</span><span>(b3.</span><span>to_ullong</span><span>() </span><span>==</span><span> </span><span>0b</span><span>0100</span><span>ULL</span><span>);</span></div></div><div><div><div>43</div></div><div><span>    </span><span>assert</span><span>(b3.</span><span>to_string</span><span>() </span><span>==</span><span> </span><span>"0100"</span><span>);</span></div></div><div><div><div>44</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>Output:</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>b1:0000; b2:1010; b3:0011; b4:00000110</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section>
<section><h1>Hash<a href="#hash"><span>#</span></a></h1><section><h2>普通哈希的优化<a href="#普通哈希的优化"><span>#</span></a></h2><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>template</span><span>&lt;</span><span>typename</span><span> </span><span>tp1</span><span>,</span><span>typename</span><span> </span><span>tp2</span><span>,</span><span>int</span><span> </span><span>N</span><span>&gt;</span></div></div><div><div><div>2</div></div><div><span>struct</span><span> </span><span>Htb</span><span>{</span></div></div><div><div><div>3</div></div><div><span>    </span><span>static</span><span> </span><span>constexpr</span><span> </span><span>int</span><span> M</span><span>=</span><span>1</span><span>e</span><span>7</span><span>+</span><span>19</span><span>;</span></div></div><div><div><div>4</div></div><div><span>    </span><span>int</span><span> hd[M</span><span>+</span><span>3</span><span>],to[N],ct;</span></div></div><div><div><div>5</div></div><div><span>    </span><span>tp1</span><span> ed[N];tp2 w[N];</span></div></div><div><div><div>6</div></div><div><span>    </span><span>static</span><span> </span><span>int</span><span> </span><span>hc</span><span>(</span><span>ul</span><span> </span><span>v</span><span>){</span></div></div><div><div><div>7</div></div><div><span><span>        </span></span><span>v</span><span>^=</span><span>v</span><span>&lt;&lt;</span><span>13</span><span>,v</span><span>^=</span><span>v</span><span>&gt;&gt;</span><span>7</span><span>;</span></div></div><div><div><div>8</div></div><div><span>        </span><span>return</span><span> (v</span><span>^</span><span>(v</span><span>&lt;&lt;</span><span>17</span><span>))</span><span>%</span><span>M;</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>10</div></div><div><span>    </span><span>void</span><span> </span><span>ins</span><span>(</span><span>tp1</span><span> </span><span>x</span><span>,</span><span>tp2</span><span> </span><span>y</span><span>){</span></div></div><div><div><div>11</div></div><div><span>        </span><span>int</span><span> </span><span>&amp;</span><span>p</span><span>=</span><span>hd[</span><span>hc</span><span>(x)];</span></div></div><div><div><div>12</div></div><div><span><span>        </span></span><span>ed[</span><span>++</span><span>ct]</span><span>=</span><span>x,to[ct]</span><span>=</span><span>p;</span></div></div><div><div><div>13</div></div><div><span><span>        </span></span><span>w[p</span><span>=</span><span>ct]</span><span>=</span><span>y;</span></div></div><div><div><div>14</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>15</div></div><div><span>    </span><span>int</span><span> </span><span>count</span><span>(</span><span>tp1</span><span> </span><span>x</span><span>){</span></div></div><div><div><div>16</div></div><div><span>        </span><span>for</span><span>(</span><span>int</span><span> i</span><span>=</span><span>hd[</span><span>hc</span><span>(x)];i;i</span><span>=</span><span>to[i])</span></div></div><div><div><div>17</div></div><div><span>            </span><span>if</span><span>(ed[i]</span><span>==</span><span>x)</span><span>return</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>18</div></div><div><span>        </span><span>return</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>19</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>20</div></div><div><span><span>    </span></span><span>pair</span><span>&lt;</span><span>tp2,</span><span>bool&gt;</span><span>find</span><span>(tp1 x){</span></div></div><div><div><div>21</div></div><div><span>        </span><span>for</span><span>(</span><span>int</span><span> i</span><span>=</span><span>hd[</span><span>hc</span><span>(x)];i;i</span><span>=</span><span>to[i])</span></div></div><div><div><div>22</div></div><div><span>            </span><span>if</span><span>(ed[i]</span><span>==</span><span>x)</span><span>return</span><span> </span><span>mkp</span><span>(w[i],</span><span>true</span><span>);</span></div></div><div><div><div>23</div></div><div><span>        </span><span>return</span><span> </span><span>mkp</span><span>(</span><span>tp2</span><span>(),</span><span>false</span><span>);</span></div></div><div><div><div>24</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>25</div></div><div><span>    </span><span>int</span><span> </span><span>operator</span><span>[]</span><span>(</span><span>tp1</span><span> </span><span>x</span><span>){</span></div></div><div><div><div>26</div></div><div><span>        </span><span>int</span><span> </span><span>&amp;</span><span>p</span><span>=</span><span>hd[</span><span>hc</span><span>(x)];</span></div></div><div><div><div>27</div></div><div><span>        </span><span>for</span><span>(</span><span>int</span><span> i</span><span>=</span><span>p;i;i</span><span>=</span><span>to[i])</span></div></div><div><div><div>28</div></div><div><span>            </span><span>if</span><span>(ed[i]</span><span>==</span><span>x)</span><span>return</span><span> i;</span></div></div><div><div><div>29</div></div><div><span><span>        </span></span><span>ed[</span><span>++</span><span>ct]</span><span>=</span><span>x,to[ct]</span><span>=</span><span>p;</span></div></div><div><div><div>30</div></div><div><span>        </span><span>return</span><span> p</span><span>=</span><span>ct;</span></div></div><div><div><div>31</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>32</div></div><div><span>    </span><span>void</span><span> </span><span>clear</span><span>(){</span><span>while</span><span>(ct)hd[</span><span>hc</span><span>(ed[ct</span><span>--</span><span>])]</span><span>=</span><span>0</span><span>;}</span></div></div><div><div><div>33</div></div><div><span>};</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>这种多维哈希相对于普通哈希的优点：</p><ol>
<li>内存局部性更好，有助于缓存友好。</li>
<li><span><span>clearclear</span><span><span><span></span><span>c</span><span>l</span><span>e</span><span>a</span><span>r</span></span></span></span> 操作可以非常高效，只需遍历已有的节点更新 <span><span>hdhd</span><span><span><span></span><span>h</span><span>d</span></span></span></span> 数组</li>
<li>代码中手动维护链表，便于对冲突处理和自定义哈希函数调优</li>
</ol><p>使用方法：</p><ol>
<li><code>struct hc</code>: <span><span>xorshiftxorshift</span><span><span><span></span><span>x</span><span>or</span><span>s</span><span>hi</span><span>f</span><span>t</span></span></span></span> 随机映射，把一个ul类型随机映射到<span><span>00</span><span><span><span></span><span>0</span></span></span></span> ~ <span><span>M−1M-1</span><span><span><span></span><span>M</span><span></span><span>−</span><span></span></span><span><span></span><span>1</span></span></span></span>中的一个随机数</li>
<li><code>struct ins</code>类似邻接表插入</li>
<li><code>count</code>是否找到</li>
<li><code>pair&lt;tp2,bool&gt;</code> 第一位查询得到的值，bool是否找到了</li>
<li><code>operator[]</code> 用于寻找第一维的标号，未找到则创建一个</li>
</ol><p>缺点:</p><ol>
<li>由于是 <span><span>xorshiftxorshift</span><span><span><span></span><span>x</span><span>or</span><span>s</span><span>hi</span><span>f</span><span>t</span></span></span></span>，在 <span><span>cfcf</span><span><span><span></span><span>c</span><span>f</span></span></span></span> 上写依然会被叉</li>
<li>第一维不能传 <span><span>stringstring</span><span><span><span></span><span>s</span><span>t</span><span>r</span><span>in</span><span>g</span></span></span></span>，可以另写<code>static ul hc(string s) {}</code></li>
</ol></section><section><h2>Hash的再优化<a href="#hash的再优化"><span>#</span></a></h2><p>为了解决上述 <span><span>HashHash</span><span><span><span></span><span>H</span><span>a</span><span>s</span><span>h</span></span></span></span> 的缺点，我们可以引入随机 <span><span>HashHash</span><span><span><span></span><span>H</span><span>a</span><span>s</span><span>h</span></span></span></span>，同时引入 <span><span>PbdsPbds</span><span><span><span></span><span>P</span><span>b</span><span>d</span><span>s</span></span></span></span> 库中封装好的<code>gp_hash_table</code>。</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>struct</span><span> </span><span>myhash</span><span> {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>static</span><span> </span><span>uint64_t</span><span> </span><span>hash</span><span>(</span><span>uint64_t</span><span> </span><span>x</span><span>) {</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>x </span><span>+=</span><span> </span><span>0x</span><span>9e3779b97f4a7c15</span><span>;</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>x </span><span>=</span><span> (x </span><span>^</span><span> (x </span><span>&gt;&gt;</span><span> </span><span>30</span><span>)) </span><span>*</span><span> </span><span>0x</span><span>bf58476d1ce4e5b9</span><span>;</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>x </span><span>=</span><span> (x </span><span>^</span><span> (x </span><span>&gt;&gt;</span><span> </span><span>27</span><span>)) </span><span>*</span><span> </span><span>0x</span><span>94d049bb133111eb</span><span>;</span></div></div><div><div><div>6</div></div><div><span>    </span><span>return</span><span> x </span><span>^</span><span> (x </span><span>&gt;&gt;</span><span> </span><span>31</span><span>);</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>8</div></div><div><span>  </span><span>size_t</span><span> </span><span>operator</span><span>()</span><span>(</span><span>uint64_t</span><span> </span><span>x</span><span>) </span><span>const</span><span> {</span></div></div><div><div><div>9</div></div><div><span>    </span><span>static</span><span> </span><span>const</span><span> </span><span>uint64_t</span><span> SEED </span><span>=</span></div></div><div><div><div>10</div></div><div><span>        </span><span>chrono</span><span>::</span><span>steady_clock</span><span>::</span><span>now</span><span>().</span><span>time_since_epoch</span><span>().</span><span>count</span><span>();</span></div></div><div><div><div>11</div></div><div><span>    </span><span>return</span><span> </span><span>hash</span><span>(x </span><span>+</span><span> SEED);</span></div></div><div><div><div>12</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>13</div></div><div><span>  </span><span>size_t</span><span> </span><span>operator</span><span>()</span><span>(</span><span>pair</span><span>&lt;</span><span>uint64_t</span><span>, </span><span>uint64_t</span><span>&gt; </span><span>x</span><span>) </span><span>const</span><span> {</span></div></div><div><div><div>14</div></div><div><span>    </span><span>static</span><span> </span><span>const</span><span> </span><span>uint64_t</span><span> SEED </span><span>=</span></div></div><div><div><div>15</div></div><div><span>        </span><span>chrono</span><span>::</span><span>steady_clock</span><span>::</span><span>now</span><span>().</span><span>time_since_epoch</span><span>().</span><span>count</span><span>();</span></div></div><div><div><div>16</div></div><div><span>    </span><span>return</span><span> </span><span>hash</span><span>(x.first </span><span>+</span><span> SEED) </span><span>^</span><span> (</span><span>hash</span><span>(x.second </span><span>+</span><span> SEED) </span><span>&gt;&gt;</span><span> </span><span>1</span><span>);</span></div></div><div><div><div>17</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>18</div></div><div><span>};</span></div></div><div><div><div>19</div></div><div><span>uint64_t</span><span> </span><span>string_hash</span><span>(</span><span>const</span><span> </span><span>string</span><span> </span><span>&amp;</span><span>s</span><span>) {</span></div></div><div><div><div>20</div></div><div><span>  </span><span>const</span><span> </span><span>uint64_t</span><span> BASE </span><span>=</span><span> </span><span>131</span><span>;</span></div></div><div><div><div>21</div></div><div><span>  </span><span>uint64_t</span><span> h </span><span>=</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>22</div></div><div><span>  </span><span>for</span><span> (</span><span>char</span><span> c : s) {</span></div></div><div><div><div>23</div></div><div><span><span>    </span></span><span>h </span><span>=</span><span> h </span><span>*</span><span> BASE </span><span>+</span><span> c;</span></div></div><div><div><div>24</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>25</div></div><div><span>  </span><span>return</span><span> h;</span></div></div><div><div><div>26</div></div><div><span>}</span></div></div><div><div><div>27</div></div><div><span>#define</span><span> </span><span>mymap</span><span> </span><span>__gnu_pbds</span><span>::gp_hash_table</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><section><h3>定义方法<a href="#定义方法"><span>#</span></a></h3><p><code>mymap&lt;int, int, myhash&gt; dic;</code></p><p><code>mymap&lt;int, null_type, myhash&gt; dic;</code></p><p><code>unordered_map&lt;int, int, myhash&gt; dic;</code></p><p>第二维表示存或者不存，若为<code>null_type</code>则不关心是否存值。</p></section></section></section>
<section><h1>Pbds<a href="#pbds"><span>#</span></a></h1><section><h2>介绍<a href="#介绍"><span>#</span></a></h2><p>什么是<code>__gnu_pbds</code>?<span><span>PolicyPolicy</span><span><span><span></span><span>P</span><span>o</span><span>l</span><span>i</span><span>cy</span></span></span></span> <span><span>basedbased</span><span><span><span></span><span>ba</span><span>se</span><span>d</span></span></span></span> <span><span>datadata</span><span><span><span></span><span>d</span><span>a</span><span>t</span><span>a</span></span></span></span> <span><span>structuresstructures</span><span><span><span></span><span>s</span><span>t</span><span>r</span><span>u</span><span>c</span><span>t</span><span>u</span><span>r</span><span>es</span></span></span></span>！简称平板电视 <span><span>pbdspbds</span><span><span><span></span><span>p</span><span>b</span><span>d</span><span>s</span></span></span></span>。在使用<span><span>pbdspbds</span><span><span><span></span><span>p</span><span>b</span><span>d</span><span>s</span></span></span></span>前，你需要：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>#include</span><span>&lt;ext/pb_ds/assoc_container.hpp&gt;</span></div></div><div><div><div>2</div></div><div><span>#include</span><span>&lt;ext/pb_ds/tree_policy.hpp&gt;</span><span>//用tree</span></div></div><div><div><div>3</div></div><div><span>#include</span><span>&lt;ext/pb_ds/hash_policy.hpp&gt;</span><span>//用hash</span></div></div><div><div><div>4</div></div><div><span>#include</span><span>&lt;ext/pb_ds/trie_policy.hpp&gt;</span><span>//用trie</span></div></div><div><div><div>5</div></div><div><span>#include</span><span>&lt;ext/pb_ds/priority_queue.hpp&gt;</span><span>//用priority_queue</span></div></div><div><div><div>6</div></div><div><span>using</span><span> </span><span>namespace</span><span> </span><span>__gnu_pbds</span><span>;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这么长？没事，有更简单的：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>#include</span><span>&lt;bits/extc++.h&gt;</span></div></div><div><div><div>2</div></div><div><span>using</span><span> </span><span>namespace</span><span> </span><span>__gnu_pbds</span><span>;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><span><span>PbdsPbds</span><span><span><span></span><span>P</span><span>b</span><span>d</span><span>s</span></span></span></span>中的所有内容均不在头文件<code>#include &lt;bits/stdc++.h&gt;</code>中，也不再 <span><span>stdstd</span><span><span><span></span><span>s</span><span>t</span><span>d</span></span></span></span> 命名空间中。</p><section><h3>pbds::hash<a href="#pbdshash"><span>#</span></a></h3><p>上面已经介绍过。</p><p><a href="https://www.luogu.com.cn/problem/P1333" target="_blank">P1333 瑞瑞的木棍</a></p><p>该题使用<span><span>PbdsPbds</span><span><span><span></span><span>P</span><span>b</span><span>d</span><span>s</span></span></span></span>中自带的<span><span>HashHash</span><span><span><span></span><span>H</span><span>a</span><span>s</span><span>h</span></span></span></span>，时间复杂度仅为<span><span>O(n)O(n)</span><span><span><span></span><span>O</span><span>(</span><span>n</span><span>)</span></span></span></span>。</p></section><section><h3>tree<a href="#tree"><span>#</span></a></h3><p>平衡树：使得集中的元素始终保持有序，并且支持快速插入和删除某个数，和快速索引排名为 <span><span>xx</span><span><span><span></span><span>x</span></span></span></span> 的数，作增删改查操作。</p><p>显然，<span><span>setset</span><span><span><span></span><span>se</span><span>t</span></span></span></span> 无法实现快速索引某个数的排名和快速索引第 <span><span>kk</span><span><span><span></span><span>k</span></span></span></span> 位数。</p><p>通俗且不严谨的说，<span><span>setset</span><span><span><span></span><span>se</span><span>t</span></span></span></span> 不像数组一样拥有“下标”，而 <span><span>pbdspbds</span><span><span><span></span><span>p</span><span>b</span><span>d</span><span>s</span></span></span></span> 库中的平衡树既有 <span><span>setset</span><span><span><span></span><span>se</span><span>t</span></span></span></span> 的功能，也有数组的下标。</p><section><h4>平衡树的tag<a href="#平衡树的tag"><span>#</span></a></h4><ol>
<li><code>rb_tree_tag</code>：红黑树</li>
<li><code>splay_tree_tag</code>：伸展树</li>
<li><code>ov_tree_tag</code>：有序向量树（不建议使用）</li>
</ol><p>一般推荐使用rb_tree_tag</p></section><section><h4>定义方式<a href="#定义方式"><span>#</span></a></h4><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>tree</span><span>&lt;int</span><span>, null_type, less</span><span>&lt;int&gt;</span><span>, rb_tree_tag, tree_order_statistics_node_update</span><span>&gt;</span><span> tr;</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h4>重要函数<a href="#重要函数"><span>#</span></a></h4><ol>
<li><code>tr.find_by_order(k)</code> 返回下标为 <span><span>kk</span><span><span><span></span><span>k</span></span></span></span> 的对象的指针</li>
<li><code>tr.order_of_key(val)</code> 返回小于等于 <span><span>valval</span><span><span><span></span><span>v</span><span>a</span><span>l</span></span></span></span> 的对象的个数</li>
<li><code>tr.lower(upper)_bound(val)</code> 返回大于等于（大于）<span><span>valval</span><span><span><span></span><span>v</span><span>a</span><span>l</span></span></span></span> 的数的对象的指针</li>
<li><code>tr.insert(val)</code> 向 <span><span>trtr</span><span><span><span></span><span>t</span><span>r</span></span></span></span> 中插入 <span><span>valval</span><span><span><span></span><span>v</span><span>a</span><span>l</span></span></span></span></li>
<li><code>A.join(B)</code> 将 <span><span>BB</span><span><span><span></span><span>B</span></span></span></span> 整个并入<span><span>AA</span><span><span><span></span><span>A</span></span></span></span></li>
<li><code>tr.size</code> 查询 <span><span>trtr</span><span><span><span></span><span>t</span><span>r</span></span></span></span> 的大小</li>
<li><code>tr.erase(tr.lower_bound(val))</code> 删除 <span><span>trtr</span><span><span><span></span><span>t</span><span>r</span></span></span></span> 中大于等于 <span><span>valval</span><span><span><span></span><span>v</span><span>a</span><span>l</span></span></span></span> 的值</li>
<li><code>tr.erase(val)</code> 删除 <span><span>trtr</span><span><span><span></span><span>t</span><span>r</span></span></span></span> 中值为 <span><span>valval</span><span><span><span></span><span>v</span><span>a</span><span>l</span></span></span></span> 的元素</li>
</ol></section><section><h4>创建multiset<a href="#创建multiset"><span>#</span></a></h4><p>一般来说有如下两种方法：</p><ol>
<li>插入时左移 <span><span>2020</span><span><span><span></span><span>20</span></span></span></span> 位，并加上一个独一无二的值，删除时右移 <span><span>2020</span><span><span><span></span><span>20</span></span></span></span> 位取出</li>
<li>在创建时，将 <code>less&lt;int&gt;</code> 改为 <code>less_equal&lt;int&gt;</code> 方便但非常不推荐!!!</li>
</ol></section></section><section><h3>priority_queue<a href="#priority_queue"><span>#</span></a></h3><section><h4>堆的tag<a href="#堆的tag"><span>#</span></a></h4><ol>
<li><code>pairing_heap_tag</code>：使用配对堆（<span><span>PairingHeapPairing Heap</span><span><span><span></span><span>P</span><span>ai</span><span>r</span><span>in</span><span>g</span><span>H</span><span>e</span><span>a</span><span>p</span></span></span></span>）实现。配对堆是一种具有良好理论性能和实际性能的堆结构，通常在实践中表现优异。</li>
<li><code>binary_heap_tag</code>：使用二叉堆（<span><span>BinaryHeapBinary Heap</span><span><span><span></span><span>B</span><span>ina</span><span>r</span><span>y</span><span>H</span><span>e</span><span>a</span><span>p</span></span></span></span>）实现。二叉堆是一种经典的堆结构，通常用于实现优先队列。</li>
<li><code>binomial_heap_tag</code>：使用二项堆（<span><span>BinomialHeapBinomial Heap</span><span><span><span></span><span>B</span><span>in</span><span>o</span><span>mia</span><span>l</span><span>H</span><span>e</span><span>a</span><span>p</span></span></span></span>）实现。二项堆是一种支持高效合并操作的堆结构。</li>
<li><code>rc_binomial_heap_tag</code>：使用放宽的二项堆（<span><span>RelaxedBinomialHeapRelaxed Binomial Heap</span><span><span><span></span><span>R</span><span>e</span><span>l</span><span>a</span><span>x</span><span>e</span><span>d</span><span>B</span><span>in</span><span>o</span><span>mia</span><span>l</span><span>H</span><span>e</span><span>a</span><span>p</span></span></span></span>）实现。这种堆结构在二项堆的基础上进行了优化，以提高某些操作的性能。</li>
<li><code>thin_heap_tag</code>：使用瘦堆（<span><span>ThinHeapThin Heap</span><span><span><span></span><span>T</span><span>hin</span><span>H</span><span>e</span><span>a</span><span>p</span></span></span></span>）实现。瘦堆是一种专门为<span><span>DijkstraDijkstra</span><span><span><span></span><span>D</span><span>ij</span><span>k</span><span>s</span><span>t</span><span>r</span><span>a</span></span></span></span>算法设计的堆结构，适用于图算法中的优先队列。</li>
</ol><p>一般推荐使用<code>pairing_heap_tag</code></p></section><section><h4>定义方式<a href="#定义方式-1"><span>#</span></a></h4><p><code>priority_queue&lt;int, less&lt;int&gt;, pairing_heap_tag&gt; q1, q2;</code></p></section><section><h4>重要函数<a href="#重要函数-1"><span>#</span></a></h4><ol>
<li><code>q1.push(val)</code></li>
<li><code>a1.size()</code></li>
<li><code>q1.pop()</code></li>
<li><code>q1.join(q2);</code></li>
<li><code>q1.empty();</code></li>
</ol></section><section><h4>提示<a href="#提示"><span>#</span></a></h4><p>使用<span><span>PbdsPbds</span><span><span><span></span><span>P</span><span>b</span><span>d</span><span>s</span></span></span></span>库中的<code>priority_queue</code>时，由于和 <span><span>stdstd</span><span><span><span></span><span>s</span><span>t</span><span>d</span></span></span></span> 库中的<code>priority_queue</code>重名，会报错，应当主动声明命名空间。</p></section></section></section></section>]]></content>
    <author><name>Zowely</name></author>
    <category term="算法竞赛" />
    <category term="算法竞赛" />
    <category term="算法教程" />
    <category term="C++" />
  </entry>
  <entry>
    <title>ProtoBuf 与 gRPC</title>
    <link href="https://www.lansganbs.cn/posts/%E9%A1%B9%E7%9B%AE%E5%BC%80%E5%8F%91/protobuf-%E4%B8%8E-grpc/" rel="alternate" type="text/html" />
    <id>https://www.lansganbs.cn/posts/%E9%A1%B9%E7%9B%AE%E5%BC%80%E5%8F%91/protobuf-%E4%B8%8E-grpc/</id>
    <published>2025-03-28T00:00:00.000Z</published>
    <updated>2025-12-28T13:11:25.000Z</updated>
    <summary>本文介绍如何使用 ProtoBuf 定义数据结构和服务接口，并在 Go 语言中实现 gRPC 服务端逻辑。</summary>
    <content type="html"><![CDATA[<p>本文介绍如何使用 ProtoBuf 定义数据结构和服务接口，并在 Go 语言中实现 gRPC 服务端逻辑。请事先配置好 <code>protoc</code> 和 Go 的 gRPC 插件环境。</p>
<section><h2>为什么我们需要 ProtoBuf？<a href="#为什么我们需要-protobuf"><span>#</span></a></h2><p>在微服务和网络通信中，我们需要一种方式来“打包”数据。你可能熟悉 JSON，它易读但体积大、解析慢。</p><p><strong>Protocol Buffers (ProtoBuf)</strong> 是 Google 开发的一种机制。你可以把它想象成一种<strong>强类型的、经过极致压缩的 JSON</strong>。</p><ul>
<li><strong>强类型</strong>：必须定义字段是 <code>int</code> 还是 <code>string</code>。</li>
<li><strong>契约优先</strong>：先写 <code>.proto</code> 文件定义数据结构和接口，再生成代码。</li>
</ul><hr /></section>
<section><h2>2. 编写你的第一个 ProtoBuf 文件<a href="#2-编写你的第一个-protobuf-文件"><span>#</span></a></h2><p>ProtoBuf 使用 <code>.proto</code> 文件来描述数据。我们以一个“用户服务”为例。</p><section><h3>2.1 头部声明<a href="#21-头部声明"><span>#</span></a></h3><p>每个 <code>.proto</code> 文件的开头都应该包含以下关键信息：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>// 声明使用 proto3 语法（目前的主流版本，必写）</span></div></div><div><div><div>2</div></div><div><span>syntax</span><span> </span><span>=</span><span> </span><span>"proto3"</span><span>;</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>// 定义包名，类似于 C++ 的 namespace 或 Java 的 package</span></div></div><div><div><div>5</div></div><div><span>// 这防止了不同项目之间的命名冲突</span></div></div><div><div><div>6</div></div><div><span>package</span><span> </span><span>user.v1</span><span>;</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>// 【关键】指定 Go 语言生成代码的路径</span></div></div><div><div><div>9</div></div><div><span>// 格式通常是： "模块名/路径;包名"</span></div></div><div><div><div>10</div></div><div><span>// - go_package 定义了生成的 .pb.go 文件属于哪个 Go包</span></div></div><div><div><div>11</div></div><div><span>option</span><span> </span><span>go_package</span><span> </span><span>=</span><span> </span><span>"[example.com/project/api/user;userpb](https://example.com/project/api/user;userpb)"</span><span>;</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>2.2 定义消息 (Message)<a href="#22-定义消息-message"><span>#</span></a></h3><p><code>message</code> 类似于 Go 中的 <code>struct</code>。它是数据传输的基本单元。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>// 定义一个用户实体</span></div></div><div><div><div>2</div></div><div><span>message</span><span> </span><span>User</span><span> {</span></div></div><div><div><div>3</div></div><div><span>  </span><span>// 字段格式：类型 字段名 = 唯一编号;</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>  </span><span>// 唯一编号（Tag）：1, 2, 3...</span></div></div><div><div><div>6</div></div><div><span>  </span><span>// 注意：这个数字非常重要，它是二进制编码时的字段标识，一旦定下来不要轻易修改！</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>  </span><span>int64</span><span> id </span><span>=</span><span> </span><span>1</span><span>;             </span><span>// 用户ID</span></div></div><div><div><div>9</div></div><div><span>  </span><span>string</span><span> username </span><span>=</span><span> </span><span>2</span><span>;      </span><span>// 用户名</span></div></div><div><div><div>10</div></div><div><span>  </span><span>bool</span><span> is_active </span><span>=</span><span> </span><span>3</span><span>;       </span><span>// 是否激活</span></div></div><div><div><div>11</div></div><div><span>  </span><span>float</span><span> balance </span><span>=</span><span> </span><span>4</span><span>;        </span><span>// 余额</span></div></div><div><div><div>12</div></div><div><span>  </span><span>bytes</span><span> avatar </span><span>=</span><span> </span><span>5</span><span>;         </span><span>// 头像（二进制数据）</span></div></div><div><div><div>13</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><blockquote><p><strong>注意</strong>：ProtoBuf 中字段名推荐使用 <strong>下划线命名法 (<code>snake_case</code>)</strong>，例如 <code>is_active</code>。生成的 Go 代码会自动转为 <strong>大驼峰 (<code>PascalCase</code>)</strong>，即 <code>IsActive</code>。</p></blockquote></section><section><h3>2.3 复杂类型：数组与映射<a href="#23-复杂类型数组与映射"><span>#</span></a></h3><p>在实际业务中，我们经常需要列表或字典结构。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>message</span><span> </span><span>UserProfile</span><span> {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>int64</span><span> user_id </span><span>=</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>  </span><span>// repeated 关键字表示数组（切片/List）</span></div></div><div><div><div>5</div></div><div><span>  </span><span>// 对应 Go 中的 []string</span></div></div><div><div><div>6</div></div><div><span>  </span><span>repeated</span><span> </span><span>string</span><span> hobbies </span><span>=</span><span> </span><span>2</span><span>;</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>  </span><span>// map 关键字表示映射</span></div></div><div><div><div>9</div></div><div><span>  </span><span>// 对应 Go 中的 map[string]string</span></div></div><div><div><div>10</div></div><div><span>  </span><span>map</span><span>&lt;</span><span>string</span><span>, </span><span>string</span><span>&gt; metadata </span><span>=</span><span> </span><span>3</span><span>;</span></div></div><div><div><div>11</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>2.4 枚举 (Enum)<a href="#24-枚举-enum"><span>#</span></a></h3><p>当字段只有固定的几个值时，使用枚举。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>// 枚举通常建议包含一个 0 值作为默认值</span></div></div><div><div><div>2</div></div><div><span>enum</span><span> </span><span>UserStatus</span><span> {</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>STATUS_UNKNOWN </span><span>=</span><span> </span><span>0</span><span>; </span><span>// 默认值，必须是0</span></div></div><div><div><div>4</div></div><div><span><span>  </span></span><span>STATUS_NORMAL </span><span>=</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>STATUS_BANNED </span><span>=</span><span> </span><span>2</span><span>;</span></div></div><div><div><div>6</div></div><div><span>}</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>message</span><span> </span><span>UserStatusRequest</span><span> {</span></div></div><div><div><div>9</div></div><div><span>  </span><span>int64</span><span> user_id </span><span>=</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>10</div></div><div><span>  </span><span>UserStatus</span><span> status </span><span>=</span><span> </span><span>2</span><span>; </span><span>// 使用上面定义的枚举</span></div></div><div><div><div>11</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section></section>
<section><h2>3. 定义服务 (Service) —— gRPC 的核心<a href="#3-定义服务-service--grpc-的核心"><span>#</span></a></h2><p>仅仅定义数据结构（Message）是不够的，我们需要定义<strong>方法</strong>。在 ProtoBuf 中，这叫做 <code>service</code>。</p><p>这相当于定义 Go 中的 <code>interface</code>。</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>// 定义 User 服务</span></div></div><div><div><div>2</div></div><div><span>service</span><span> </span><span>UserService</span><span> {</span></div></div><div><div><div>3</div></div><div><span>  </span><span>// 定义一个 RPC 方法：GetUser</span></div></div><div><div><div>4</div></div><div><span>  </span><span>// 接收 GetUserRequest，返回 GetUserResponse</span></div></div><div><div><div>5</div></div><div><span>  </span><span>rpc</span><span> </span><span>GetUser</span><span> (</span><span>GetUserRequest</span><span>) </span><span>returns</span><span> (</span><span>GetUserResponse</span><span>);</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>  </span><span>// 定义另一个方法：CreateUser</span></div></div><div><div><div>8</div></div><div><span>  </span><span>rpc</span><span> </span><span>CreateUser</span><span> (</span><span>CreateUserRequest</span><span>) </span><span>returns</span><span> (</span><span>CreateUserResponse</span><span>);</span></div></div><div><div><div>9</div></div><div><span>}</span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span>// ------------------------------------</span></div></div><div><div><div>12</div></div><div><span>// 配套的 Request 和 Response 消息定义</span></div></div><div><div><div>13</div></div><div><span>// ------------------------------------</span></div></div><div><div><div>14</div></div><div>
</div></div><div><div><div>15</div></div><div><span>message</span><span> </span><span>GetUserRequest</span><span> {</span></div></div><div><div><div>16</div></div><div><span>  </span><span>int64</span><span> id </span><span>=</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>17</div></div><div><span>}</span></div></div><div><div><div>18</div></div><div>
</div></div><div><div><div>19</div></div><div><span>message</span><span> </span><span>GetUserResponse</span><span> {</span></div></div><div><div><div>20</div></div><div><span>  </span><span>User</span><span> user </span><span>=</span><span> </span><span>1</span><span>; </span><span>// 引用上面定义的 User 消息</span></div></div><div><div><div>21</div></div><div><span>}</span></div></div><div><div><div>22</div></div><div>
</div></div><div><div><div>23</div></div><div><span>message</span><span> </span><span>CreateUserRequest</span><span> {</span></div></div><div><div><div>24</div></div><div><span>  </span><span>string</span><span> username </span><span>=</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>25</div></div><div><span>  </span><span>string</span><span> password </span><span>=</span><span> </span><span>2</span><span>;</span></div></div><div><div><div>26</div></div><div><span>}</span></div></div><div><div><div>27</div></div><div>
</div></div><div><div><div>28</div></div><div><span>message</span><span> </span><span>CreateUserResponse</span><span> {</span></div></div><div><div><div>29</div></div><div><span>  </span><span>int64</span><span> id </span><span>=</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>30</div></div><div><span>  </span><span>bool</span><span> success </span><span>=</span><span> </span><span>2</span><span>;</span></div></div><div><div><div>31</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><hr /></section>
<section><h2>4. 从 Proto 到 Go 的映射原理<a href="#4-从-proto-到-go-的映射原理"><span>#</span></a></h2><p>假设你已经运行了生成命令（<code>protoc</code>），生成了 <code>user.pb.go</code> (数据结构) 和 <code>user_grpc.pb.go</code> (服务接口)。</p><p>你需要理解生成的代码是什么样子的，才能知道怎么去写业务逻辑。</p><section><h3>4.1 Message  Struct<a href="#41-message--struct"><span>#</span></a></h3><p>ProtoBuf 定义的 <code>message</code> 会被转化为 Go 的 <code>struct</code>。</p><p><strong>Proto:</strong></p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>message</span><span> </span><span>User</span><span> {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>int64</span><span> id </span><span>=</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>3</div></div><div><span>  </span><span>string</span><span> username </span><span>=</span><span> </span><span>2</span><span>;</span></div></div><div><div><div>4</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>生成的 Go 代码 (近似):</strong></p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>type</span><span> </span><span>User</span><span> </span><span>struct</span><span> {</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>Id       </span><span>int64</span><span>  </span><span>`protobuf:"varint,1,..." json:"id,omitempty"`</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>Username </span><span>string</span><span> </span><span>`protobuf:"bytes,2,..." json:"username,omitempty"`</span></div></div><div><div><div>4</div></div><div><span>    </span><span>// 还有一些内部使用的字段...</span></div></div><div><div><div>5</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li>你可以直接像操作普通结构体一样操作它：<code>user.Id = 100</code>。</li>
</ul></section><section><h3>4.2 Service  Interface<a href="#42-service--interface"><span>#</span></a></h3><p>ProtoBuf 定义的 <code>service</code> 会生成两个关键部分：<strong>客户端接口</strong>（Client）和 <strong>服务端接口</strong>（Server）。我们主要关注服务端。</p><p><strong>Proto:</strong></p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>service</span><span> </span><span>UserService</span><span> {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>rpc</span><span> </span><span>GetUser</span><span> (</span><span>GetUserRequest</span><span>) </span><span>returns</span><span> (</span><span>GetUserResponse</span><span>);</span></div></div><div><div><div>3</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>生成的 Go 接口 (在 <code>_grpc.pb.go</code> 中)，统一在名为 <code>_ServiceServer</code> 的接口中，直接在此文件中查找:</strong></p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>// 这是你需要实现的接口！</span></div></div><div><div><div>2</div></div><div><span>type</span><span> </span><span>UserServiceServer</span><span> </span><span>interface</span><span> {</span></div></div><div><div><div>3</div></div><div><span>    </span><span>// 上下文 Context 永远是第一个参数，用于处理超时和取消</span></div></div><div><div><div>4</div></div><div><span>    </span><span>GetUser</span><span>(</span><span>context</span><span>.</span><span>Context</span><span>, </span><span>*</span><span>GetUserRequest</span><span>) (</span><span>*</span><span>GetUserResponse</span><span>, </span><span>error</span><span>)</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>    </span><span>// 这是一个为了向前兼容的安全方法，必须嵌入</span></div></div><div><div><div>7</div></div><div><span>    </span><span>mustEmbedUnimplementedUserServiceServer</span><span>()</span></div></div><div><div><div>8</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section></section>
<section><h2>5. 核心实战：如何在 Go 中实现接口<a href="#5-核心实战如何在-go-中实现接口"><span>#</span></a></h2><section><h3>步骤 1: 创建结构体<a href="#步骤-1-创建结构体"><span>#</span></a></h3><p>你需要定义一个结构体，作为服务的承载者。</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>package</span><span> </span><span>main</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>import</span><span> (</span></div></div><div><div><div>4</div></div><div><span>    </span><span>"</span><span>context</span><span>"</span></div></div><div><div><div>5</div></div><div><span>    </span><span>"</span><span>errors</span><span>"</span></div></div><div><div><div>6</div></div><div><span>    </span><span>// 引入生成的 proto 包，假设别名为 pb</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>pb </span><span>"</span><span>[example.com/project/api/user](https://example.com/project/api/user)</span><span>"</span></div></div><div><div><div>8</div></div><div><span>)</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span>// 1. 定义结构体</span></div></div><div><div><div>11</div></div><div><span>type</span><span> </span><span>server</span><span> </span><span>struct</span><span> {</span></div></div><div><div><div>12</div></div><div><span>    </span><span>// 2. 【必须】嵌入“未实现的服务端”结构体</span></div></div><div><div><div>13</div></div><div><span>    </span><span>// 这样做的好处是，如果你在 .proto 增加了新方法但还没写 Go 代码，</span></div></div><div><div><div>14</div></div><div><span>    </span><span>// 程序依然能编译通过（只不过调用新方法会报错“未实现”）。</span></div></div><div><div><div>15</div></div><div><span>    </span><span>pb</span><span>.</span><span>UnimplementedUserServiceServer</span></div></div><div><div><div>16</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section><section><h3>步骤 2: 实现方法<a href="#步骤-2-实现方法"><span>#</span></a></h3><p>完全照搬生成的接口签名来写方法，直接从 <code>type _Server interface</code> 粘贴复制来，并进行相应补全。</p><p>例如将 <code>GetUser(context.Context, *GetUserRequest) (*GetUserResponse, error)</code> 补全为 <code>func (s *server) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.GetUserResponse, error)</code> 并实现业务逻辑。</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>// 实现 GetUser 方法</span></div></div><div><div><div>2</div></div><div><span>// 参数：ctx (上下文), req (请求体指针)</span></div></div><div><div><div>3</div></div><div><span>// 返回：resp (响应体指针), err (错误信息)</span></div></div><div><div><div>4</div></div><div><span>func</span><span> (</span><span>s </span><span>*</span><span>server</span><span>) </span><span>GetUser</span><span>(</span><span>ctx</span><span> </span><span>context</span><span>.</span><span>Context</span><span>, </span><span>req</span><span> </span><span>*</span><span>pb</span><span>.</span><span>GetUserRequest</span><span>) (</span><span>*</span><span>pb</span><span>.</span><span>GetUserResponse</span><span>, </span><span>error</span><span>) {</span></div></div><div><div><div>5</div></div><div><span>    </span><span>// 1. 获取请求参数</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>userID </span><span>:=</span><span> req.</span><span>GetId</span><span>() </span><span>// 建议使用 GetId() 方法而不是 .Id，防空指针</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>    </span><span>// 2. 执行业务逻辑 (模拟查库)</span></div></div><div><div><div>9</div></div><div><span>    </span><span>if</span><span> userID </span><span>==</span><span> </span><span>0</span><span> {</span></div></div><div><div><div>10</div></div><div><span>        </span><span>return</span><span> </span><span>nil</span><span>, errors.</span><span>New</span><span>(</span><span>"用户ID不能为空"</span><span>)</span></div></div><div><div><div>11</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span>    </span><span>// 模拟数据</span></div></div><div><div><div>14</div></div><div><span><span>    </span></span><span>foundUser </span><span>:=</span><span> </span><span>&amp;</span><span>pb</span><span>.</span><span>User</span><span>{</span></div></div><div><div><div>15</div></div><div><span><span>        </span></span><span>Id:       userID,</span></div></div><div><div><div>16</div></div><div><span><span>        </span></span><span>Username: </span><span>"DeepLearningUser"</span><span>,</span></div></div><div><div><div>17</div></div><div><span><span>        </span></span><span>IsActive: </span><span>true</span><span>,</span></div></div><div><div><div>18</div></div><div><span><span>        </span></span><span>Hobbies:  []</span><span>string</span><span>{</span><span>"coding"</span><span>, </span><span>"reading"</span><span>},</span></div></div><div><div><div>19</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>20</div></div><div>
</div></div><div><div><div>21</div></div><div><span>    </span><span>// 3. 构造响应并返回</span></div></div><div><div><div>22</div></div><div><span>    </span><span>return</span><span> </span><span>&amp;</span><span>pb</span><span>.</span><span>GetUserResponse</span><span>{</span></div></div><div><div><div>23</div></div><div><span><span>        </span></span><span>User: foundUser,</span></div></div><div><div><div>24</div></div><div><span><span>    </span></span><span>}, </span><span>nil</span></div></div><div><div><div>25</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section><section><h3>步骤 3: 启动 gRPC Server<a href="#步骤-3-启动-grpc-server"><span>#</span></a></h3><p>最后，你需要编写 <code>main</code> 函数来监听端口并注册你的服务。</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>package</span><span> </span><span>main</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>import</span><span> (</span></div></div><div><div><div>4</div></div><div><span>    </span><span>"</span><span>log</span><span>"</span></div></div><div><div><div>5</div></div><div><span>    </span><span>"</span><span>net</span><span>"</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>    </span><span>"</span><span>google.golang.org/grpc</span><span>"</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>pb </span><span>"</span><span>[example.com/project/api/user](https://example.com/project/api/user)</span><span>"</span></div></div><div><div><div>9</div></div><div><span>)</span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>12</div></div><div><span>    </span><span>// 1. 监听 TCP 端口</span></div></div><div><div><div>13</div></div><div><span><span>    </span></span><span>lis, err </span><span>:=</span><span> net.</span><span>Listen</span><span>(</span><span>"tcp"</span><span>, </span><span>":50051"</span><span>)</span></div></div><div><div><div>14</div></div><div><span>    </span><span>if</span><span> err </span><span>!=</span><span> </span><span>nil</span><span> {</span></div></div><div><div><div>15</div></div><div><span><span>        </span></span><span>log.</span><span>Fatalf</span><span>(</span><span>"无法监听端口: </span><span>%v</span><span>"</span><span>, err)</span></div></div><div><div><div>16</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>17</div></div><div>
</div></div><div><div><div>18</div></div><div><span>    </span><span>// 2. 创建 gRPC 服务器实例</span></div></div><div><div><div>19</div></div><div><span><span>    </span></span><span>grpcServer </span><span>:=</span><span> grpc.</span><span>NewServer</span><span>()</span></div></div><div><div><div>20</div></div><div>
</div></div><div><div><div>21</div></div><div><span>    </span><span>// 3. 注册服务</span></div></div><div><div><div>22</div></div><div><span>    </span><span>// 将我们实现的 server{} 结构体注册到 gRPC 服务器上</span></div></div><div><div><div>23</div></div><div><span>    </span><span>// 这个 Register 函数是自动生成的代码提供的</span></div></div><div><div><div>24</div></div><div><span><span>    </span></span><span>pb.</span><span>RegisterUserServiceServer</span><span>(grpcServer, </span><span>&amp;</span><span>server</span><span>{})</span></div></div><div><div><div>25</div></div><div>
</div></div><div><div><div>26</div></div><div><span><span>    </span></span><span>log.</span><span>Printf</span><span>(</span><span>"gRPC 服务启动在 :50051 端口..."</span><span>)</span></div></div><div><div><div>27</div></div><div>
</div></div><div><div><div>28</div></div><div><span>    </span><span>// 4. 开始服务 (阻塞操作)</span></div></div><div><div><div>29</div></div><div><span>    </span><span>if</span><span> err </span><span>:=</span><span> grpcServer.</span><span>Serve</span><span>(lis); err </span><span>!=</span><span> </span><span>nil</span><span> {</span></div></div><div><div><div>30</div></div><div><span><span>        </span></span><span>log.</span><span>Fatalf</span><span>(</span><span>"服务启动失败: </span><span>%v</span><span>"</span><span>, err)</span></div></div><div><div><div>31</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>32</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><hr /></section></section>
<section><h2>6. 一个 <code>.bat</code> 脚本快速生成代码<a href="#6-一个-bat-脚本快速生成代码"><span>#</span></a></h2><p>这个脚本可以帮助你快速生成 <code>.pb.go</code>  <code>_grpc.pb.go</code> 和 <code>_validate.pb.go</code> 文件。</p><div><div><div><figure><figcaption><span></span><span>Terminal window</span><span>展开</span><span>收起</span></figcaption><pre><code><div><div><div>1</div></div><div><span>@echo</span><span> </span><span>off</span></div></div><div><div><div>2</div></div><div><span>setlocal</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>if</span><span> </span><span>"</span><span>%~1</span><span>"</span><span>==</span><span>""</span><span> (</span></div></div><div><div><div>5</div></div><div><span>    </span><span>echo</span><span> [USAGE] Please provide a proto file.</span></div></div><div><div><div>6</div></div><div><span>    </span><span>exit</span><span> /b </span><span>1</span></div></div><div><div><div>7</div></div><div><span>)</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>set</span><span> </span><span>"</span><span>TARGET_PATH</span><span>=</span><span>%~dp1</span><span>"</span></div></div><div><div><div>10</div></div><div><span>set</span><span> </span><span>"</span><span>FILE_NAME</span><span>=</span><span>%~nx1</span><span>"</span></div></div><div><div><div>11</div></div><div>
</div></div><div><div><div>12</div></div><div><span>pushd</span><span> </span><span>"</span><span>%TARGET_PATH%</span><span>"</span></div></div><div><div><div>13</div></div><div><span>echo</span><span> [INFO] Working Directory: %CD%</span></div></div><div><div><div>14</div></div><div>
</div></div><div><div><div>15</div></div><div><span>protoc -I. --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative --validate_out=</span><span>"lang=go:."</span><span> </span><span>"</span><span>%FILE_NAME%</span><span>"</span></div></div><div><div><div>16</div></div><div>
</div></div><div><div><div>17</div></div><div><span>if</span><span> %errorlevel% </span><span>neq</span><span> </span><span>0</span><span> (</span></div></div><div><div><div>18</div></div><div><span>    </span><span>echo</span><span> [FAIL] Compilation failed!</span></div></div><div><div><div>19</div></div><div><span>    </span><span>popd</span></div></div><div><div><div>20</div></div><div><span>    </span><span>exit</span><span> /b </span><span>1</span></div></div><div><div><div>21</div></div><div><span>)</span></div></div><div><div><div>22</div></div><div>
</div></div><div><div><div>23</div></div><div><span>echo</span><span> [SUCCESS] Done.</span></div></div><div><div><div>24</div></div><div><span>popd</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>cd 到你的 proto 文件目录，运行对应的 <code>.proto</code> 文件：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>makeproto</span><span> </span><span>user.proto</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section>
<section><h2>7. 提示<a href="#7-提示"><span>#</span></a></h2><ol>
<li><strong>零值处理</strong>：ProtoBuf (proto3) 不会在网络中传输默认值（如 0, false, ""）。</li>
</ol><ul>
<li>如果你在 Go 中收到 <code>0</code>，你无法区分它是“未设置”还是“故意设置为 0”。</li>
<li>如果必须区分，需要使用 <code>Wrapper</code> 类型（如 <code>google.protobuf.Int32Value</code>）或指针。</li>
</ul><ol>
<li><strong>字段编号</strong>：一旦上线，不要更改字段的编号（Tag）。如果你要把 <code>id = 1</code> 改成 <code>id = 2</code>，旧版本的客户端将无法正确解析数据。</li>
<li><strong>Getter 方法</strong>：在 Go 中读取 proto 结构体字段时，尽量使用生成的 <code>GetFieldName()</code> 方法（例如 <code>req.GetUsername()</code>）。如果 <code>req</code> 本身是 nil，直接访问 <code>req.Username</code> 会 Panic，而 <code>req.GetUsername()</code> 会安全返回空字符串。</li>
</ol><hr /></section>
<section><h2>8. 总结<a href="#8-总结"><span>#</span></a></h2><ol>
<li><strong>写契约</strong>：编写 <code>xxx.proto</code> 文件，定义 Message 和 Service。</li>
<li><strong>生成代码</strong>：运行 <code>protoc</code> 生成 Go 代码。</li>
<li><strong>定义 Struct</strong>：在 Go 中定义 <code>type server struct {}</code> 并嵌入 <code>Unimplemented...</code>。</li>
<li><strong>实现逻辑</strong>：为 <code>server</code> 实现接口定义的方法 <code>(ctx, req) -&gt; (resp, err)</code>。</li>
<li><strong>启动注册</strong>：在 <code>main</code> 中 <code>net.Listen</code> -&gt; <code>grpc.NewServer</code> -&gt; <code>Register...</code> -&gt; <code>Serve</code>。</li>
</ol></section>]]></content>
    <author><name>Zowely</name></author>
    <category term="项目开发" />
    <category term="项目开发" />
    <category term="Golang" />
    <category term="微服务" />
    <category term="gRPC" />
  </entry>
  <entry>
    <title>gRPC 获取客户端连接状态</title>
    <link href="https://www.lansganbs.cn/posts/%E9%A1%B9%E7%9B%AE%E5%BC%80%E5%8F%91/grpc-%E8%8E%B7%E5%8F%96%E5%AE%A2%E6%88%B7%E7%AB%AF%E8%BF%9E%E6%8E%A5%E7%8A%B6%E6%80%81/" rel="alternate" type="text/html" />
    <id>https://www.lansganbs.cn/posts/%E9%A1%B9%E7%9B%AE%E5%BC%80%E5%8F%91/grpc-%E8%8E%B7%E5%8F%96%E5%AE%A2%E6%88%B7%E7%AB%AF%E8%BF%9E%E6%8E%A5%E7%8A%B6%E6%80%81/</id>
    <published>2025-03-27T00:00:00.000Z</published>
    <updated>2025-11-28T16:28:30.000Z</updated>
    <summary>gRPC 中的 Metadata（元数据）、拦截器、认证机制、状态码与异常处理、超时机制，以及 Protobuf 生成的 Go 源文件分析。</summary>
    <content type="html"><![CDATA[<section><h1>gRPC 中的 Metadata（元数据）<a href="#grpc-中的-metadata元数据"><span>#</span></a></h1><section><h2>什么是 Metadata<a href="#什么是-metadata"><span>#</span></a></h2><p>Metadata 是 gRPC 提供的一种机制，用于在每次 <strong>RPC 调用</strong>中传递额外的、非业务相关的<strong>键值对</strong>数据。</p><ul>
<li>
<p><strong>作用：</strong> 允许 Client 和 Server 为对方提供关于本次调用的信息。</p>
</li>
<li>
<p><strong>类比：</strong> 类似于 HTTP 请求中的 Request Header 和 Response Header。</p>
</li>
<li>
<p><strong>生命周期：</strong> 仅限于<strong>一次 RPC 调用</strong>。</p>
</li>
<li>
<p><strong>存储结构：</strong> 以 <code>key-value</code> 形式存储。</p>
<ul>
<li>
<p><code>key</code>: <code>string</code> 类型。</p>
</li>
<li>
<p><code>value</code>: <code>[]string</code> 类型（字符串切片），即一个键可以对应多个值。</p>
</li>
</ul>
</li>
</ul></section><section><h2>Metadata 的两种创建方法<a href="#metadata-的两种创建方法"><span>#</span></a></h2><ol>
<li>
<p><strong><code>metadata.New()</code>：</strong> 从 <code>map[string]string</code> 创建，键值都是单一字符串。</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>md </span><span>:=</span><span> metadata.</span><span>New</span><span>(</span><span>map</span><span>[</span><span>string</span><span>]</span><span>string</span><span>{</span><span>"key1"</span><span>: </span><span>"val1"</span><span>, </span><span>"key2"</span><span>: </span><span>"val2"</span><span>})</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
</li>
<li>
<p><strong><code>metadata.Pairs()</code>：</strong> 从扁平的键值对序列创建，允许重复键，键会被统一转成小写。</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>md </span><span>:=</span><span> metadata.</span><span>Pairs</span><span>(</span></div></div><div><div><div>2</div></div><div><span>    </span><span>"key1"</span><span>, </span><span>"val1"</span><span>,</span></div></div><div><div><div>3</div></div><div><span>    </span><span>"key1"</span><span>, </span><span>"val1-2"</span><span>, </span><span>// key1 的值将是 []string{"val1", "val1-2"}</span></div></div><div><div><div>4</div></div><div><span>    </span><span>"key2"</span><span>, </span><span>"val2"</span><span>,</span></div></div><div><div><div>5</div></div><div><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
</li>
</ol></section><section><h2>发送 Metadata<a href="#发送-metadata"><span>#</span></a></h2><p>通过 <code>context</code> 在客户端发送元数据。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>// 1. 创建 metadata</span></div></div><div><div><div>2</div></div><div><span>md </span><span>:=</span><span> metadata.</span><span>Pairs</span><span>(</span><span>"appid"</span><span>, </span><span>"10101"</span><span>, </span><span>"appkey"</span><span>, </span><span>"i am key"</span><span>)</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>// 2. 新建一个带有 Outgoing metadata 的 context</span></div></div><div><div><div>5</div></div><div><span>ctx </span><span>:=</span><span> metadata.</span><span>NewOutgoingContext</span><span>(context.</span><span>Background</span><span>(), md)</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>// 3. 使用新的 context 调用 RPC</span></div></div><div><div><div>8</div></div><div><span>response, err </span><span>:=</span><span> client.</span><span>SomeRpc</span><span>(ctx, someRequest)</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>接收 Metadata<a href="#接收-metadata"><span>#</span></a></h2><p>在服务端通过 <code>context</code> 提取元数据。</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> (</span><span>s </span><span>*</span><span>server</span><span>) </span><span>SomeRPC</span><span>(</span><span>ctx</span><span> </span><span>context</span><span>.</span><span>Context</span><span>, </span><span>in</span><span> </span><span>*</span><span>pb</span><span>.</span><span>SomeRequest</span><span>) (</span><span>*</span><span>pb</span><span>.</span><span>SomeResponse</span><span>, </span><span>error</span><span>) {</span></div></div><div><div><div>2</div></div><div><span>    </span><span>// 1. 从 Incoming context 中提取 metadata</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>md, ok </span><span>:=</span><span> metadata.</span><span>FromIncomingContext</span><span>(ctx)</span></div></div><div><div><div>4</div></div><div><span>    </span><span>if</span><span> </span><span>!</span><span>ok {</span></div></div><div><div><div>5</div></div><div><span>        </span><span>// 错误处理，未找到 metadata</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>    </span><span>// 2. 遍历或获取指定键的值</span></div></div><div><div><div>9</div></div><div><span>    </span><span>for</span><span> key, val </span><span>:=</span><span> </span><span>range</span><span> md {</span></div></div><div><div><div>10</div></div><div><span><span>        </span></span><span>fmt.</span><span>Println</span><span>(key, val) </span><span>// key: string, val: []string</span></div></div><div><div><div>11</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span>    </span><span>// 3. 获取指定对象（注意 key 会被转为小写）</span></div></div><div><div><div>14</div></div><div><span>    </span><span>if</span><span> nameSlice, ok </span><span>:=</span><span> md[</span><span>"name"</span><span>]; ok {</span></div></div><div><div><div>15</div></div><div><span><span>        </span></span><span>fmt.</span><span>Println</span><span>(nameSlice[</span><span>0</span><span>]) </span><span>// 取第一个值</span></div></div><div><div><div>16</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>17</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><hr /></section></section>
<section><h1>二、Metadata 实战实例<a href="#二metadata-实战实例"><span>#</span></a></h1><ul>
<li>
<p><strong>目标：</strong> Client 通过 Metadata 传递 <code>name</code> 和 <code>password</code>，Server 接收并打印。</p>
</li>
<li>
<p><strong>关键点：</strong> Server 端通过 <code>metadata.FromIncomingContext(ctx)</code> 获取数据，并演示了如何遍历所有键值对和获取指定键的值。</p>
</li>
</ul><p>Client 端：使用 metadata.NewOutgoingContext 封装 context。</p><p>Server 端：使用 metadata.FromIncomingContext 解封 context。</p><hr /></section>
<section><h1>三、gRPC 拦截器（Interceptor）<a href="#三grpc-拦截器interceptor"><span>#</span></a></h1><p>拦截器（Interceptor）提供了类似 Web 框架中<strong>中间件</strong>的功能，允许在业务逻辑执行前后介入，统一处理如日志、认证、监控、限流等流程。</p><section><h2>服务端一元拦截器（Unary Server Interceptor）<a href="#服务端一元拦截器unary-server-interceptor"><span>#</span></a></h2><p>用于处理单个 RPC 调用（非 Stream）。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>// 拦截器签名</span></div></div><div><div><div>2</div></div><div><span>interceptor </span><span>:=</span><span> </span><span>func</span><span>(</span><span>ctx</span><span> </span><span>context</span><span>.</span><span>Context</span><span>, </span><span>req</span><span> </span><span>interface</span><span>{}, </span><span>info</span><span> </span><span>*</span><span>grpc</span><span>.</span><span>UnaryServerInfo</span><span>, </span><span>handler</span><span> </span><span>grpc</span><span>.</span><span>UnaryHandler</span><span>) (</span><span>resp</span><span> </span><span>interface</span><span>{}, </span><span>err</span><span> </span><span>error</span><span>) {</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"接收到了一个新的请求"</span><span>)</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>    </span><span>// 执行原有的业务逻辑 (handler 是业务方法 SayHello)</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>res, err </span><span>:=</span><span> </span><span>handler</span><span>(ctx, req)</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"请求已经完成"</span><span>)</span></div></div><div><div><div>9</div></div><div><span>    </span><span>return</span><span> res, err</span></div></div><div><div><div>10</div></div><div><span>}</span></div></div><div><div><div>11</div></div><div>
</div></div><div><div><div>12</div></div><div><span>// 注册拦截器</span></div></div><div><div><div>13</div></div><div><span>opt </span><span>:=</span><span> grpc.</span><span>UnaryInterceptor</span><span>(interceptor)</span></div></div><div><div><div>14</div></div><div><span>g </span><span>:=</span><span> grpc.</span><span>NewServer</span><span>(opt) </span><span>// 可传入多个拦截器：g := grpc.NewServer(opt1, opt2)</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>客户端一元拦截器（Unary Client Interceptor）<a href="#客户端一元拦截器unary-client-interceptor"><span>#</span></a></h2><p>用于在客户端调用 RPC 时统一处理请求。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>// 拦截器签名</span></div></div><div><div><div>2</div></div><div><span>interceptor </span><span>:=</span><span> </span><span>func</span><span>(</span><span>ctx</span><span> </span><span>context</span><span>.</span><span>Context</span><span>, </span><span>method</span><span> </span><span>string</span><span>, </span><span>req</span><span>, </span><span>reply</span><span> </span><span>interface</span><span>{}, </span><span>cc</span><span> </span><span>*</span><span>grpc</span><span>.</span><span>ClientConn</span><span>, </span><span>invoker</span><span> </span><span>grpc</span><span>.</span><span>UnaryInvoker</span><span>, </span><span>opts</span><span> </span><span>...</span><span>grpc</span><span>.</span><span>CallOption</span><span>) </span><span>error</span><span> {</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>start </span><span>:=</span><span> time.</span><span>Now</span><span>()</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>    </span><span>// 调用原有的执行业务</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>err </span><span>:=</span><span> </span><span>invoker</span><span>(ctx, method, req, reply, cc, opts</span><span>...</span><span>)</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>fmt.</span><span>Printf</span><span>(</span><span>"耗时：</span><span>%s\n</span><span>"</span><span>, time.</span><span>Since</span><span>(start)) </span><span>// 可用于计时/监控</span></div></div><div><div><div>9</div></div><div><span>    </span><span>return</span><span> err</span></div></div><div><div><div>10</div></div><div><span>}</span></div></div><div><div><div>11</div></div><div>
</div></div><div><div><div>12</div></div><div><span>// 注册拦截器</span></div></div><div><div><div>13</div></div><div><span>opts </span><span>=</span><span> </span><span>append</span><span>(opts, grpc.</span><span>WithUnaryInterceptor</span><span>(interceptor))</span></div></div><div><div><div>14</div></div><div><span>conn, err </span><span>:=</span><span> grpc.</span><span>Dial</span><span>(</span><span>"127.0.0.1:50051"</span><span>, opts</span><span>...</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section></section>
<section><h1>四、gRPC 的 Auth 认证机制<a href="#四grpc-的-auth-认证机制"><span>#</span></a></h1><p>认证通常在<strong>服务端拦截器</strong>中结合 <strong>Metadata</strong> 来实现。</p><section><h2>认证流程（服务端拦截器实现）<a href="#认证流程服务端拦截器实现"><span>#</span></a></h2><p>服务端拦截器是处理认证逻辑的首选位置，因为它能在业务逻辑执行前就阻断非法请求。</p><ol>
<li>
<p><strong>获取 Metadata：</strong> <code>md, ok := metadata.FromIncomingContext(ctx)</code></p>
</li>
<li>
<p><strong>检查 Token/Key：</strong> 提取 <code>appid</code> 和 <code>appkey</code>。</p>
</li>
<li>
<p><strong>验证凭证：</strong> 检查凭证是否有效 (<code>appid == "101010" &amp;&amp; appkey == "i am key"</code>)。</p>
</li>
<li>
<p><strong>失败返回：</strong> 如果验证失败，使用 <code>status.Error</code> 返回特定的状态码和错误信息。</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>return</span><span> resp, status.</span><span>Error</span><span>(codes.Unauthenticated, </span><span>"无token认证信息"</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
</li>
</ol></section><section><h2>客户端发送凭证的两种方法<a href="#客户端发送凭证的两种方法"><span>#</span></a></h2><ol>
<li>
<p><strong>在客户端拦截器中发送（常规方法）：</strong> 在每次调用前，在客户端拦截器中新建并注入 Metadata。</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>interceptor </span><span>:=</span><span> </span><span>func</span><span>(</span><span>...</span><span>) </span><span>error</span><span> {</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>md </span><span>:=</span><span> metadata.</span><span>New</span><span>(</span><span>map</span><span>[</span><span>string</span><span>]</span><span>string</span><span>{</span><span>"appid"</span><span>: </span><span>"10101"</span><span>, </span><span>"appkey"</span><span>: </span><span>"i am key"</span><span>})</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>ctx </span><span>=</span><span> metadata.</span><span>NewOutgoingContext</span><span>(context.</span><span>Background</span><span>(), md) </span><span>// 注入新的 ctx</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>err </span><span>:=</span><span> </span><span>invoker</span><span>(ctx, method, req, reply, cc, opts</span><span>...</span><span>)</span></div></div><div><div><div>5</div></div><div><span>    </span><span>return</span><span> err</span></div></div><div><div><div>6</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
</li>
<li>
<p><strong>使用 <code>grpc.WithPerRPCCredentials</code>（推荐方法）：</strong> 官方推荐的方式，实现 <code>credentials.PerRPCCredentials</code> 接口。这使得凭证的生成逻辑与 RPC 调用的代码分离。</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>// 1. 实现接口</span></div></div><div><div><div>2</div></div><div><span>type</span><span> </span><span>customCredential</span><span> </span><span>struct</span><span>{}</span></div></div><div><div><div>3</div></div><div><span>func</span><span> (</span><span>c </span><span>customCredential</span><span>) </span><span>GetRequestMetadata</span><span>(</span><span>ctx</span><span> </span><span>context</span><span>.</span><span>Context</span><span>, </span><span>uri</span><span> </span><span>...</span><span>string</span><span>) (</span><span>map</span><span>[</span><span>string</span><span>]</span><span>string</span><span>, </span><span>error</span><span>) {</span></div></div><div><div><div>4</div></div><div><span>    </span><span>return</span><span> </span><span>map</span><span>[</span><span>string</span><span>]</span><span>string</span><span>{</span></div></div><div><div><div>5</div></div><div><span>        </span><span>"appid"</span><span>: </span><span>"10101"</span><span>,</span></div></div><div><div><div>6</div></div><div><span>        </span><span>"appkey"</span><span>: </span><span>"i am key"</span><span>,</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>}, </span><span>nil</span></div></div><div><div><div>8</div></div><div><span>}</span></div></div><div><div><div>9</div></div><div><span>func</span><span> (</span><span>c </span><span>customCredential</span><span>) </span><span>RequireTransportSecurity</span><span>() </span><span>bool</span><span> { </span><span>return</span><span> </span><span>false</span><span> } </span><span>// 默认不要求 TLS</span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span>// 2. 注册到连接选项中</span></div></div><div><div><div>12</div></div><div><span>opts </span><span>=</span><span> </span><span>append</span><span>(opts, grpc.</span><span>WithPerRPCCredentials</span><span>(</span><span>customCredential</span><span>{}))</span></div></div><div><div><div>13</div></div><div><span>conn, err </span><span>:=</span><span> grpc.</span><span>Dial</span><span>(</span><span>"127.0.0.1:50051"</span><span>, opts</span><span>...</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
</li>
</ol><hr /></section></section>
<section><h1>五、gRPC 状态码与异常处理<a href="#五grpc-状态码与异常处理"><span>#</span></a></h1><p>Go 语言推荐返回 <code>(result, error)</code> 的模式。gRPC 通过 <code>status</code> 包实现了标准化错误码的传递。</p><section><h2>服务端抛出异常<a href="#服务端抛出异常"><span>#</span></a></h2><p>服务端使用 <code>status.Errorf</code> 构造一个带有 gRPC 标准状态码的 <code>error</code> 返回。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>// 抛出 NotFound 状态码和自定义信息</span></div></div><div><div><div>2</div></div><div><span>return</span><span> </span><span>nil</span><span>, status.</span><span>Errorf</span><span>(codes.NotFound, </span><span>"记录未找到：</span><span>%s</span><span>"</span><span>, request.Name)</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>客户端解析异常<a href="#客户端解析异常"><span>#</span></a></h2><p>客户端通过 <code>status.FromError</code> 解析返回的 <code>error</code>，获取状态码和错误信息。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>_, err </span><span>=</span><span> c.</span><span>SayHello</span><span>(context.</span><span>Background</span><span>(), </span><span>&amp;</span><span>proto</span><span>.</span><span>HelloRequest</span><span>{Name: </span><span>"bobby"</span><span>})</span></div></div><div><div><div>2</div></div><div><span>if</span><span> err </span><span>!=</span><span> </span><span>nil</span><span> {</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>st, ok </span><span>:=</span><span> status.</span><span>FromError</span><span>(err)</span></div></div><div><div><div>4</div></div><div><span>    </span><span>if</span><span> </span><span>!</span><span>ok {</span></div></div><div><div><div>5</div></div><div><span>        </span><span>// error 不是 gRPC 状态码错误</span></div></div><div><div><div>6</div></div><div><span>        </span><span>panic</span><span>(</span><span>"解析error失败"</span><span>)</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"错误信息:"</span><span>, st.</span><span>Message</span><span>()) </span><span>// "记录未找到: bobby"</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"状态码:"</span><span>, st.</span><span>Code</span><span>())    </span><span>// codes.NotFound (5)</span></div></div><div><div><div>10</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>gRPC 官方状态码参考：</strong> <a href="https://github.com/grpc/grpc/blob/master/doc/statuscodes.md" target="_blank"><code>/grpc/codes</code></a> (例如 <code>codes.Unauthenticated</code>, <code>codes.InvalidArgument</code>, <code>codes.Unavailable</code>, etc.)</p><hr /></section></section>
<section><h1>六、gRPC 的超时机制<a href="#六grpc-的超时机制"><span>#</span></a></h1><p>gRPC 的超时机制完全依赖于 Go 标准库的 <code>context</code>。</p><section><h2>客户端设置超时<a href="#客户端设置超时"><span>#</span></a></h2><p>客户端通过 <code>context.WithTimeout</code> 或 <code>context.WithDeadline</code> 来为单个 RPC 调用设置超时时间。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>// 设置 3 秒超时</span></div></div><div><div><div>2</div></div><div><span>ctx, cancel </span><span>:=</span><span> context.</span><span>WithTimeout</span><span>(context.</span><span>Background</span><span>(), time.Second</span><span>*</span><span>3</span><span>)</span></div></div><div><div><div>3</div></div><div><span>defer</span><span> </span><span>cancel</span><span>() </span><span>// 及时释放 context 资源</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>_, err </span><span>=</span><span> c.</span><span>SayHello</span><span>(ctx, </span><span>&amp;</span><span>proto</span><span>.</span><span>HelloRequest</span><span>{Name: </span><span>"bobby"</span><span>})</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>如果服务端（例如：<code>time.Sleep(time.Second * 5)</code>）处理时间超过 3 秒，客户端会立即返回错误。客户端解析到的状态码通常是 <code>codes.DeadlineExceeded</code>。</p><hr /></section></section>
<section><h1>七、Protobuf 生成的 Go 源文件分析<a href="#七protobuf-生成的-go-源文件分析"><span>#</span></a></h1><p>Protobuf 编译器（<code>protoc</code>）结合 Go 插件（<code>protoc-gen-go</code> 用于消息，<code>protoc-gen-go-grpc</code> 用于服务）生成 Go 代码。这些代码实现了 gRPC 的底层通信机制和接口契约。</p><p>我们以 <code>helloworld.proto</code> 中的 <code>service Greeter</code> 和 <code>rpc SayHello</code> 为例进行分析。</p><section><h2>1. 消息结构体 (<code>pb.go</code> 文件)<a href="#1-消息结构体-pbgo-文件"><span>#</span></a></h2><p><code>pb.go</code> 文件包含了 Proto 文件中定义的所有 <code>message</code> 结构体。</p><section><h3>结构体定义<a href="#结构体定义"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>type</span><span> </span><span>HelloRequest</span><span> </span><span>struct</span><span> {</span></div></div><div><div><div>2</div></div><div><span>    </span><span>// 内部字段，用于 Protobuf 运行时管理</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>state           </span><span>protoimpl</span><span>.</span><span>MessageState</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>sizeCache       </span><span>protoimpl</span><span>.</span><span>SizeCache</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>unknownFields   </span><span>protoimpl</span><span>.</span><span>UnknownFields</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>    </span><span>// 业务字段</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>Name </span><span>string</span><span> </span><span>`protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`</span></div></div><div><div><div>9</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>关键元素及作用<a href="#关键元素及作用"><span>#</span></a></h3>





























<table><thead><tr><th>元素</th><th>作用</th><th>使用方式</th></tr></thead><tbody><tr><td><strong><code>HelloRequest</code> 结构体</strong></td><td>对应 Protobuf 中的 <code>HelloRequest</code> 消息。它是 RPC 调用中请求数据的<strong>载体</strong>。</td><td>客户端构造请求时创建 <code>&amp;proto.HelloRequest{Name: "..."}</code>。</td></tr><tr><td><strong><code>Name</code> 字段</strong></td><td>存储具体的业务数据。</td><td></td></tr><tr><td><strong><code>protobuf:"..."</code> Tag</strong></td><td>Protobuf 编码/解码所必需的标签：<br />- <code>bytes,1</code>: 字段类型（字符串被视为 bytes），字段顺序号是 1。<br />- <code>opt</code>: 可选字段。<br />- <code>name=name</code>: Protobuf 字段名。</td><td>gRPC 框架在发送和接收数据时，通过反射读取这些标签进行<strong>高效的二进制序列化</strong>。</td></tr><tr><td><strong><code>json:"name,omitempty"</code> Tag</strong></td><td>Go 标准库 <code>encoding/json</code> 包使用的标签。</td><td>使该结构体可以方便地被 <strong>JSON 序列化或反序列化</strong>，常用于日志记录或与其他 HTTP 服务交互。</td></tr></tbody></table><hr /></section></section><section><h2>2. 服务端接口与注册 (<code>helloworld_grpc.pb.go</code> 文件)<a href="#2-服务端接口与注册-helloworld_grpcpbgo-文件"><span>#</span></a></h2><p>该文件定义了服务端必须遵守的契约。</p><section><h3>A. 服务端接口 (<code>GreeterServer</code>)<a href="#a-服务端接口-greeterserver"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>type</span><span> </span><span>GreeterServer</span><span> </span><span>interface</span><span> {</span></div></div><div><div><div>2</div></div><div><span>    </span><span>SayHello</span><span>(</span><span>context</span><span>.</span><span>Context</span><span>, </span><span>*</span><span>HelloRequest</span><span>) (</span><span>*</span><span>HelloReply</span><span>, </span><span>error</span><span>)</span></div></div><div><div><div>3</div></div><div><span>    </span><span>// 强制嵌入，用于兼容性检查</span></div></div><div><div><div>4</div></div><div><span>    </span><span>mustEmbedUnimplementedGreeterServer</span><span>()</span></div></div><div><div><div>5</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
























<table><thead><tr><th>元素</th><th>作用</th><th>如何使用</th></tr></thead><tbody><tr><td><strong><code>GreeterServer</code> 接口</strong></td><td>这是 gRPC 为你的服务自动生成的<strong>业务接口</strong>。它定义了服务可以响应的所有 RPC 方法签名。</td><td>你的 Server 结构体（如 <code>type Server struct { ... }</code>）必须实现此接口中的所有方法（例如 <code>SayHello</code>），才能成为一个合法的 gRPC 服务提供者。</td></tr><tr><td><strong><code>SayHello</code> 方法</strong></td><td>对应 Proto 文件中的 <code>rpc SayHello</code>。它接收 <code>context.Context</code> 和 <code>HelloRequest</code>，返回 <code>HelloReply</code> 和 <code>error</code>。</td><td><strong>业务逻辑的入口。</strong> 在这个方法中编写具体的处理代码。</td></tr><tr><td><strong><code>mustEmbedUnimplementedGreeterServer</code></strong></td><td>这是一个<strong>前向兼容机制</strong>。它强制要求开发者嵌入一个未实现的方法，确保如果未来 Protobuf 文件新增了 RPC 方法，但你的 Server 没有实现，编译器会立即报错，而不是运行时出错。</td><td>你的 Server 结构体通常应该嵌入 <code>proto.UnimplementedGreeterServer</code>。</td></tr></tbody></table></section><section><h3>B. 未实现结构体 (<code>UnimplementedGreeterServer</code>)<a href="#b-未实现结构体-unimplementedgreeterserver"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>type</span><span> </span><span>UnimplementedGreeterServer</span><span> </span><span>struct</span><span> {}</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>func</span><span> (</span><span>*</span><span>UnimplementedGreeterServer</span><span>) </span><span>SayHello</span><span>(</span><span>context</span><span>.</span><span>Context</span><span>, </span><span>*</span><span>HelloRequest</span><span>) (</span><span>*</span><span>HelloReply</span><span>, </span><span>error</span><span>) {</span></div></div><div><div><div>4</div></div><div><span>    </span><span>return</span><span> </span><span>nil</span><span>, status.</span><span>Errorf</span><span>(codes.Unimplemented, </span><span>"method SayHello not implemented"</span><span>)</span></div></div><div><div><div>5</div></div><div><span>}</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>func</span><span> (</span><span>*</span><span>UnimplementedGreeterServer</span><span>) </span><span>mustEmbedUnimplementedGreeterServer</span><span>() {}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li><strong>作用：</strong> 提供了所有方法的<strong>默认实现</strong>，如果你的 Server 结构体没有实现某个方法，调用该方法时将返回 <code>codes.Unimplemented</code> 错误。</li>
<li><strong>使用：</strong> 开发者在定义 Server 时通常会嵌入它：
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>type</span><span> </span><span>Server</span><span> </span><span>struct</span><span> {</span></div></div><div><div><div>2</div></div><div><span>    </span><span>proto</span><span>.</span><span>UnimplementedGreeterServer</span><span> </span><span>// 嵌入它，继承默认实现，并满足 mustEmbed... 检查</span></div></div><div><div><div>3</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
</li>
</ul></section><section><h3>C. 服务注册函数 (<code>RegisterGreeterServer</code>)<a href="#c-服务注册函数-registergreeterserver"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> </span><span>RegisterGreeterServer</span><span>(</span><span>s</span><span> </span><span>grpc</span><span>.</span><span>ServiceRegistrar</span><span>, </span><span>srv</span><span> </span><span>GreeterServer</span><span>) {</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>s.</span><span>RegisterService</span><span>(</span><span>&amp;</span><span>Greeter_ServiceDesc, srv)</span></div></div><div><div><div>3</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
























<table><thead><tr><th>元素</th><th>作用</th><th>如何使用</th></tr></thead><tbody><tr><td><strong><code>RegisterGreeterServer</code></strong></td><td><strong>核心注册方法</strong>。它负责将你的 Server 实例（实现了 <code>GreeterServer</code> 接口的对象）绑定到 gRPC 服务器运行时（<code>*grpc.Server</code>）。</td><td><strong>在 <code>main</code> 函数中调用</strong>：<code>proto.RegisterGreeterServer(g, &amp;Server{})</code>。</td></tr><tr><td><strong><code>grpc.ServiceRegistrar</code></strong></td><td>接口类型，通常由 <code>grpc.NewServer()</code> 创建的 <code>*grpc.Server</code> 实现。它提供了服务注册能力。</td><td></td></tr><tr><td><strong><code>Greeter_ServiceDesc</code></strong></td><td>自动生成的服务描述符，包含了服务名、所有 RPC 方法及其处理函数。这是 gRPC 运行时<strong>反射</strong>和<strong>路由</strong>请求的依据。</td><td></td></tr></tbody></table><hr /></section></section><section><h2>3. 客户端接口与实现 (<code>helloworld_grpc.pb.go</code> 文件)<a href="#3-客户端接口与实现-helloworld_grpcpbgo-文件"><span>#</span></a></h2><p>该文件提供了客户端连接和调用远程方法的能力。</p><section><h3>A. 客户端接口 (<code>GreeterClient</code>)<a href="#a-客户端接口-greeterclient"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>type</span><span> </span><span>GreeterClient</span><span> </span><span>interface</span><span> {</span></div></div><div><div><div>2</div></div><div><span>    </span><span>SayHello</span><span>(</span><span>ctx</span><span> </span><span>context</span><span>.</span><span>Context</span><span>, </span><span>in</span><span> </span><span>*</span><span>HelloRequest</span><span>, </span><span>opts</span><span> </span><span>...</span><span>grpc</span><span>.</span><span>CallOption</span><span>) (</span><span>*</span><span>HelloReply</span><span>, </span><span>error</span><span>)</span></div></div><div><div><div>3</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li><strong>作用：</strong> 定义了客户端可以调用的方法签名。客户端代码通常只依赖这个接口。</li>
</ul></section><section><h3>B. 客户端工厂方法 (<code>NewGreeterClient</code>)<a href="#b-客户端工厂方法-newgreeterclient"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>type</span><span> </span><span>greeterClient</span><span> </span><span>struct</span><span> {</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>cc </span><span>grpc</span><span>.</span><span>ClientConnInterface</span><span> </span><span>// 客户端连接接口</span></div></div><div><div><div>3</div></div><div><span>}</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>func</span><span> </span><span>NewGreeterClient</span><span>(</span><span>cc</span><span> </span><span>grpc</span><span>.</span><span>ClientConnInterface</span><span>) </span><span>GreeterClient</span><span> {</span></div></div><div><div><div>6</div></div><div><span>    </span><span>return</span><span> </span><span>&amp;</span><span>greeterClient</span><span>{cc}</span></div></div><div><div><div>7</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div>



















<table><thead><tr><th>元素</th><th>作用</th><th>如何使用</th></tr></thead><tbody><tr><td><strong><code>NewGreeterClient</code></strong></td><td><strong>客户端创建方法</strong>。它接收一个客户端连接对象，并返回实现了 <code>GreeterClient</code> 接口的结构体实例。</td><td>在客户端代码中调用：<br /><code>c := proto.NewGreeterClient(conn)</code></td></tr><tr><td><strong><code>grpc.ClientConnInterface</code> (<code>cc</code>)</strong></td><td>客户端连接接口。这是对底层 gRPC 连接（通过 <code>grpc.Dial</code> 创建）的抽象。它封装了连接池、负载均衡、连接状态管理等功能。</td><td><code>NewGreeterClient</code> 将这个连接嵌入到 <code>greeterClient</code> 结构体中，用于后续的 RPC 调用。</td></tr></tbody></table></section><section><h3>C. 客户端实际调用方法 (<code>greeterClient.SayHello</code>)<a href="#c-客户端实际调用方法-greeterclientsayhello"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> (</span><span>c </span><span>*</span><span>greeterClient</span><span>) </span><span>SayHello</span><span>(</span><span>ctx</span><span> </span><span>context</span><span>.</span><span>Context</span><span>, </span><span>in</span><span> </span><span>*</span><span>HelloRequest</span><span>, </span><span>opts</span><span> </span><span>...</span><span>grpc</span><span>.</span><span>CallOption</span><span>) (</span><span>*</span><span>HelloReply</span><span>, </span><span>error</span><span>) {</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>out </span><span>:=</span><span> </span><span>new</span><span>(</span><span>HelloReply</span><span>)</span></div></div><div><div><div>3</div></div><div><span>    </span><span>// 核心远程调用</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>err </span><span>:=</span><span> c.cc.</span><span>Invoke</span><span>(ctx, </span><span>"/proto.Greeter/SayHello"</span><span>, in, out, opts</span><span>...</span><span>)</span></div></div><div><div><div>5</div></div><div><span>    </span><span>if</span><span> err </span><span>!=</span><span> </span><span>nil</span><span> {</span></div></div><div><div><div>6</div></div><div><span>        </span><span>return</span><span> </span><span>nil</span><span>, err</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>8</div></div><div><span>    </span><span>return</span><span> out, </span><span>nil</span></div></div><div><div><div>9</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
















<table><thead><tr><th>元素</th><th>作用</th></tr></thead><tbody><tr><td><strong><code>*greeterClient</code> 结构体</strong></td><td>实现了 <code>GreeterClient</code> 接口，<strong>Go 鸭子类型</strong>的体现。</td></tr><tr><td><strong><code>c.cc.Invoke(...)</code></strong></td><td><strong>RPC 调用的核心</strong>。这是发起网络请求到 gRPC Server 的入口。<br />- <strong><code>ctx</code></strong>: 用于传递 Metadata、超时、截止日期等。 <br />- <strong><code>"/proto.Greeter/SayHello"</code></strong>: RPC 调用的<strong>全路径名</strong>。gRPC 框架根据这个字符串进行路由和分发请求。 <br />- <strong><code>in</code></strong>: 输入的 <code>*HelloRequest</code> 结构体，会被 Protobuf 序列化并发送。 <br />- <strong><code>out</code></strong>: 输出的 <code>*HelloReply</code> 结构体指针，用于接收服务端返回的、已被 Protobuf 反序列化的数据。 <br />- <strong><code>opts</code></strong>: 额外的调用选项，如客户端拦截器、认证凭证等。</td></tr></tbody></table></section></section></section>
<section><h1>八、gRPC 验证器（protoc-gen-validate）<a href="#八grpc-验证器protoc-gen-validate"><span>#</span></a></h1><ul>
<li>
<p><strong>用途：</strong> 自动生成消息结构体的校验代码，类似于 Web 开发中的表单验证。</p>
</li>
<li>
<p><strong>工具：</strong> <a href="https://github.com/envoyproxy/protoc-gen-validate" target="_blank">envoyproxy/protoc-gen-validate</a></p>
</li>
<li>
<p><strong>建议：</strong> 虽然功能强大，但在实际微服务开发中，简单的请求校验（如非空判断）在业务代码中手动处理可能更灵活。对于复杂的业务规则或大量服务的统一校验，可以考虑引入此工具。</p>
</li>
</ul></section>]]></content>
    <author><name>Zowely</name></author>
    <category term="项目开发" />
    <category term="项目开发" />
    <category term="Golang" />
    <category term="微服务" />
    <category term="gRPC" />
  </entry>
  <entry>
    <title>Codeforces Round 1000 (Div. 2) D题题解</title>
    <link href="https://www.lansganbs.cn/posts/%E7%AE%97%E6%B3%95%E7%AB%9E%E8%B5%9B/codeforces-round-1000-div-2-d%E9%A2%98%E9%A2%98%E8%A7%A3/" rel="alternate" type="text/html" />
    <id>https://www.lansganbs.cn/posts/%E7%AE%97%E6%B3%95%E7%AB%9E%E8%B5%9B/codeforces-round-1000-div-2-d%E9%A2%98%E9%A2%98%E8%A7%A3/</id>
    <published>2025-01-22T00:00:00.000Z</published>
    <updated>2025-10-12T15:28:59.000Z</updated>
    <summary>Codeforces Round 1000 (Div. 2) D题题解</summary>
    <content type="html"><![CDATA[<p><a href="https://codeforces.com/contest/2063/problem/D" target="_blank">D. Game With Triangles</a></p>
<p>Even Little John needs money to buy a house. But he recently lost his job; how will he earn money now? Of course, by playing a game that gives him money as a reward! Oh well, maybe not those kinds of games you are thinking about.</p>
<p>There are <span><span>n+mn+m</span><span><span><span></span><span>n</span><span></span><span>+</span><span></span></span><span><span></span><span>m</span></span></span></span> distinct points <span><span>(a_1,0),(a_2,0),…,(a_n,0),(b_1,2),(b_2,2),…,(b_m,2)(a\_{1},0), (a\_{2},0), \dots , (a\_{n},0), (b\_{1},2), (b\_{2},2), \dots, (b\_{m},2)</span><span><span><span></span><span>(</span><span>a</span><span>_</span><span><span>1</span></span><span>,</span><span></span><span>0</span><span>)</span><span>,</span><span></span><span>(</span><span>a</span><span>_</span><span><span>2</span></span><span>,</span><span></span><span>0</span><span>)</span><span>,</span><span></span><span>…</span><span></span><span>,</span><span></span><span>(</span><span>a</span><span>_</span><span><span>n</span></span><span>,</span><span></span><span>0</span><span>)</span><span>,</span><span></span><span>(</span><span>b</span><span>_</span><span><span>1</span></span><span>,</span><span></span><span>2</span><span>)</span><span>,</span><span></span><span>(</span><span>b</span><span>_</span><span><span>2</span></span><span>,</span><span></span><span>2</span><span>)</span><span>,</span><span></span><span>…</span><span></span><span>,</span><span></span><span>(</span><span>b</span><span>_</span><span><span>m</span></span><span>,</span><span></span><span>2</span><span>)</span></span></span></span> on the plane. Initially, your score is <span><span>00</span><span><span><span></span><span>0</span></span></span></span>. To increase your score, you can perform the following operation:</p>
<ul>
<li>Choose three distinct points which are not <a href="https://en.wikipedia.org/wiki/Collinearity" target="_blank">collinear</a>;</li>
<li>Increase your score by the area of the triangle formed by these three points;</li>
<li>Then, erase the three points from the plane.</li>
</ul>
<p><img src="https://espresso.codeforces.com/5f6a73286fffbc2708f1d388ed58ca5bc0d69d23.png" alt="" /> An instance of the game, where the operation is performed twice.</p>
<p>Let <span><span>k_maxk\_{max}</span><span><span><span></span><span>k</span><span>_</span><span><span>ma</span><span>x</span></span></span></span></span> be the maximum number of operations that can be performed. For example, if it is impossible to perform any operation, <span><span>k_max k\_{max}</span><span><span><span></span><span>k</span><span>_</span><span><span>ma</span><span>x</span></span></span></span></span> is <span><span>00</span><span><span><span></span><span>0</span></span></span></span>. Additionally, define <span><span>f(k)f(k)</span><span><span><span></span><span>f</span><span>(</span><span>k</span><span>)</span></span></span></span> as the maximum possible score achievable by performing the operation <strong>exactly <span><span>kk</span><span><span><span></span><span>k</span></span></span></span> times</strong>. Here, <span><span>f(k)f(k)</span><span><span><span></span><span>f</span><span>(</span><span>k</span><span>)</span></span></span></span> is defined for all integers <span><span>kk</span><span><span><span></span><span>k</span></span></span></span> such that <span><span>0≤k≤kmax0 \le k \le k_{max}</span><span><span><span></span><span>0</span><span></span><span>≤</span><span></span></span><span><span></span><span>k</span><span></span><span>≤</span><span></span></span><span><span></span><span><span>k</span><span><span><span><span><span><span></span><span><span><span>ma</span><span>x</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span>.</p>
<p>Find the value of <span><span>k_maxk\_{max}</span><span><span><span></span><span>k</span><span>_</span><span><span>ma</span><span>x</span></span></span></span></span>, and find the values of <span><span>f(x)f(x)</span><span><span><span></span><span>f</span><span>(</span><span>x</span><span>)</span></span></span></span> for all integers <span><span>x=1,2,…,kmaxx=1,2,\dots,k_{max}</span><span><span><span></span><span>x</span><span></span><span>=</span><span></span></span><span><span></span><span>1</span><span>,</span><span></span><span>2</span><span>,</span><span></span><span>…</span><span></span><span>,</span><span></span><span><span>k</span><span><span><span><span><span><span></span><span><span><span>ma</span><span>x</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span> independently.</p>
<p><strong>Input</strong></p>
<p>Each test contains multiple test cases. The first line contains the number of test cases <span><span>tt</span><span><span><span></span><span>t</span></span></span></span> <span><span>(1≤t≤3⋅104)(1 \le t \le 3 \cdot 10^4)</span><span><span><span></span><span>(</span><span>1</span><span></span><span>≤</span><span></span></span><span><span></span><span>t</span><span></span><span>≤</span><span></span></span><span><span></span><span>3</span><span></span><span>⋅</span><span></span></span><span><span></span><span>1</span><span><span>0</span><span><span><span><span><span><span></span><span><span>4</span></span></span></span></span></span></span></span><span>)</span></span></span></span>. The description of the test cases follows.</p>
<p>The first line of each test case contains two integers <span><span>nn</span><span><span><span></span><span>n</span></span></span></span> and <span><span>mm</span><span><span><span></span><span>m</span></span></span></span> <span><span>(1≤n,m≤2⋅105)(1 \le n,m \le 2 \cdot 10^5)</span><span><span><span></span><span>(</span><span>1</span><span></span><span>≤</span><span></span></span><span><span></span><span>n</span><span>,</span><span></span><span>m</span><span></span><span>≤</span><span></span></span><span><span></span><span>2</span><span></span><span>⋅</span><span></span></span><span><span></span><span>1</span><span><span>0</span><span><span><span><span><span><span></span><span><span>5</span></span></span></span></span></span></span></span><span>)</span></span></span></span>.</p>
<p>The second line of each test case contains <span><span>nn</span><span><span><span></span><span>n</span></span></span></span> pairwise distinct integers <span><span>a1,a2,…,ana_1,a_2,\ldots,a_n</span><span><span><span></span><span><span>a</span><span><span><span><span><span><span></span><span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>,</span><span></span><span><span>a</span><span><span><span><span><span><span></span><span><span>2</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>,</span><span></span><span>…</span><span></span><span>,</span><span></span><span><span>a</span><span><span><span><span><span><span></span><span><span>n</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span> — the points on <span><span>y=0y=0</span><span><span><span></span><span>y</span><span></span><span>=</span><span></span></span><span><span></span><span>0</span></span></span></span> <span><span>(−109≤ai≤109)(-10^9 \le a_i \le 10^9)</span><span><span><span></span><span>(</span><span>−</span><span>1</span><span><span>0</span><span><span><span><span><span><span></span><span><span>9</span></span></span></span></span></span></span></span><span></span><span>≤</span><span></span></span><span><span></span><span><span>a</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>≤</span><span></span></span><span><span></span><span>1</span><span><span>0</span><span><span><span><span><span><span></span><span><span>9</span></span></span></span></span></span></span></span><span>)</span></span></span></span>.</p>
<p>The third line of each test case contains <span><span>mm</span><span><span><span></span><span>m</span></span></span></span> pairwise distinct integers <span><span>b1,b2,…,bmb_1,b_2,\ldots,b_m</span><span><span><span></span><span><span>b</span><span><span><span><span><span><span></span><span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>,</span><span></span><span><span>b</span><span><span><span><span><span><span></span><span><span>2</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>,</span><span></span><span>…</span><span></span><span>,</span><span></span><span><span>b</span><span><span><span><span><span><span></span><span><span>m</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span> — the points on <span><span>y=2y=2</span><span><span><span></span><span>y</span><span></span><span>=</span><span></span></span><span><span></span><span>2</span></span></span></span> <span><span>(−109≤bi≤109)(-10^9 \le b_i \le 10^9)</span><span><span><span></span><span>(</span><span>−</span><span>1</span><span><span>0</span><span><span><span><span><span><span></span><span><span>9</span></span></span></span></span></span></span></span><span></span><span>≤</span><span></span></span><span><span></span><span><span>b</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>≤</span><span></span></span><span><span></span><span>1</span><span><span>0</span><span><span><span><span><span><span></span><span><span>9</span></span></span></span></span></span></span></span><span>)</span></span></span></span>.</p>
<p>It is guaranteed that both the sum of <span><span>nn</span><span><span><span></span><span>n</span></span></span></span> and the sum of <span><span>mm</span><span><span><span></span><span>m</span></span></span></span> over all test cases do not exceed <span><span>2⋅1052 \cdot 10^5</span><span><span><span></span><span>2</span><span></span><span>⋅</span><span></span></span><span><span></span><span>1</span><span><span>0</span><span><span><span><span><span><span></span><span><span>5</span></span></span></span></span></span></span></span></span></span></span>.</p>
<p><strong>Output</strong></p>
<p>For each test case, given that the maximum number of operations is <span><span>k_max⁡k\_{\max}</span><span><span><span></span><span>k</span><span>_</span><span><span>max</span></span></span></span></span>, you must output at most two lines:</p>
<ul>
<li>The first line contains the value of <span><span>k_max⁡k\_{\max}</span><span><span><span></span><span>k</span><span>_</span><span><span>max</span></span></span></span></span>;</li>
<li>The second line contains <span><span>k_max⁡k\_{\max}</span><span><span><span></span><span>k</span><span>_</span><span><span>max</span></span></span></span></span> integers denoting <span><span>f(1),f(2),…,f(k_max⁡)f(1),f(2),\ldots,f(k\_{\max})</span><span><span><span></span><span>f</span><span>(</span><span>1</span><span>)</span><span>,</span><span></span><span>f</span><span>(</span><span>2</span><span>)</span><span>,</span><span></span><span>…</span><span></span><span>,</span><span></span><span>f</span><span>(</span><span>k</span><span>_</span><span><span>max</span></span><span>)</span></span></span></span>. You are allowed to omit this line if <span><span>k_max⁡k\_{\max}</span><span><span><span></span><span>k</span><span>_</span><span><span>max</span></span></span></span></span> is <span><span>00</span><span><span><span></span><span>0</span></span></span></span>.</li>
</ul>
<p>Note that under the constraints of this problem, it can be shown that all values of <span><span>f(x)f(x)</span><span><span><span></span><span>f</span><span>(</span><span>x</span><span>)</span></span></span></span> are integers no greater than <span><span>101610^{16}</span><span><span><span></span><span>1</span><span><span>0</span><span><span><span><span><span><span></span><span><span><span>16</span></span></span></span></span></span></span></span></span></span></span></span>.</p>
<p><strong>Example</strong></p>
<p><strong>Input</strong></p>
<div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>5</span></div></div><div><div><div>2</div></div><div><span>1 3</span></div></div><div><div><div>3</div></div><div><span>0</span></div></div><div><div><div>4</div></div><div><span>0 1 -1</span></div></div><div><div><div>5</div></div><div><span>2 4</span></div></div><div><div><div>6</div></div><div><span>0 100</span></div></div><div><div><div>7</div></div><div><span>-100 -50 0 50</span></div></div><div><div><div>8</div></div><div><span>2 4</span></div></div><div><div><div>9</div></div><div><span>0 1000</span></div></div><div><div><div>10</div></div><div><span>-100 -50 0 50</span></div></div><div><div><div>11</div></div><div><span>6 6</span></div></div><div><div><div>12</div></div><div><span>20 1 27 100 43 42</span></div></div><div><div><div>13</div></div><div><span>100 84 1 24 22 77</span></div></div><div><div><div>14</div></div><div><span>8 2</span></div></div><div><div><div>15</div></div><div><span>564040265 -509489796 469913620 198872582 -400714529 553177666 131159391 -20796763</span></div></div><div><div><div>16</div></div><div><span>-1000000000 1000000000</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div>
<hr />
<p><strong>Output</strong></p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>1</span></div></div><div><div><div>2</div></div><div><span>2</span></div></div><div><div><div>3</div></div><div><span>2</span></div></div><div><div><div>4</div></div><div><span>150 200</span></div></div><div><div><div>5</div></div><div><span>2</span></div></div><div><div><div>6</div></div><div><span>1000 200</span></div></div><div><div><div>7</div></div><div><span>4</span></div></div><div><div><div>8</div></div><div><span>99 198 260 283</span></div></div><div><div><div>9</div></div><div><span>2</span></div></div><div><div><div>10</div></div><div><span>2000000000 2027422256</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
<p><strong>Note</strong></p>
<p>On the first test case, there are <span><span>1+3=41+3=4</span><span><span><span></span><span>1</span><span></span><span>+</span><span></span></span><span><span></span><span>3</span><span></span><span>=</span><span></span></span><span><span></span><span>4</span></span></span></span> points <span><span>(0,0),(0,2),(1,2),(−1,2)(0,0),(0,2),(1,2),(-1,2)</span><span><span><span></span><span>(</span><span>0</span><span>,</span><span></span><span>0</span><span>)</span><span>,</span><span></span><span>(</span><span>0</span><span>,</span><span></span><span>2</span><span>)</span><span>,</span><span></span><span>(</span><span>1</span><span>,</span><span></span><span>2</span><span>)</span><span>,</span><span></span><span>(</span><span>−</span><span>1</span><span>,</span><span></span><span>2</span><span>)</span></span></span></span>.</p>
<p>It can be shown that you cannot perform two or more operations. The value of <span><span>k_max⁡k\_{\max}</span><span><span><span></span><span>k</span><span>_</span><span><span>max</span></span></span></span></span> is <span><span>11</span><span><span><span></span><span>1</span></span></span></span>, and you are only asked for the value of <span><span>f(1)f(1)</span><span><span><span></span><span>f</span><span>(</span><span>1</span><span>)</span></span></span></span>.</p>
<p>You can choose <span><span>(0,0)(0,0)</span><span><span><span></span><span>(</span><span>0</span><span>,</span><span></span><span>0</span><span>)</span></span></span></span>, <span><span>(−1,2)(-1,2)</span><span><span><span></span><span>(</span><span>−</span><span>1</span><span>,</span><span></span><span>2</span><span>)</span></span></span></span>, and <span><span>(1,2)(1,2)</span><span><span><span></span><span>(</span><span>1</span><span>,</span><span></span><span>2</span><span>)</span></span></span></span> as the three vertices of the triangle. After that, your score is increased by the area of the triangle, which is <span><span>22</span><span><span><span></span><span>2</span></span></span></span>. Then, the three points are erased from the plane. It can be shown that the maximum value of your score after performing one operation is <span><span>22</span><span><span><span></span><span>2</span></span></span></span>. Therefore, the value of <span><span>f(1)f(1)</span><span><span><span></span><span>f</span><span>(</span><span>1</span><span>)</span></span></span></span> is <span><span>22</span><span><span><span></span><span>2</span></span></span></span>.</p>
<p>On the fifth test case, there are <span><span>8+2=108+2=10</span><span><span><span></span><span>8</span><span></span><span>+</span><span></span></span><span><span></span><span>2</span><span></span><span>=</span><span></span></span><span><span></span><span>10</span></span></span></span> points.</p>
<p>It can be shown that you cannot perform three or more operations. The value of <span><span>k_max⁡k\_{\max}</span><span><span><span></span><span>k</span><span>_</span><span><span>max</span></span></span></span></span> is <span><span>22</span><span><span><span></span><span>2</span></span></span></span>, and you are asked for the values <span><span>f(1)f(1)</span><span><span><span></span><span>f</span><span>(</span><span>1</span><span>)</span></span></span></span> and <span><span>f(2)f(2)</span><span><span><span></span><span>f</span><span>(</span><span>2</span><span>)</span></span></span></span>.</p>
<p>To maximize the score with only one operation, you can choose three points <span><span>(198 872 582,0)(198\,872\,582,0)</span><span><span><span></span><span>(</span><span>198</span><span></span><span>872</span><span></span><span>582</span><span>,</span><span></span><span>0</span><span>)</span></span></span></span>, <span><span>(−1 000 000 000,2)(-1\,000\,000\,000,2)</span><span><span><span></span><span>(</span><span>−</span><span>1</span><span></span><span>000</span><span></span><span>000</span><span></span><span>000</span><span>,</span><span></span><span>2</span><span>)</span></span></span></span>, and <span><span>(1 000 000 000,2)(1\,000\,000\,000,2)</span><span><span><span></span><span>(</span><span>1</span><span></span><span>000</span><span></span><span>000</span><span></span><span>000</span><span>,</span><span></span><span>2</span><span>)</span></span></span></span>. Then, the three points are erased from the plane. It can be shown that the maximum value of your score after performing one operation is <span><span>2 000 000 0002\,000\,000\,000</span><span><span><span></span><span>2</span><span></span><span>000</span><span></span><span>000</span><span></span><span>000</span></span></span></span>. Therefore, the value of <span><span>f(1)f(1)</span><span><span><span></span><span>f</span><span>(</span><span>1</span><span>)</span></span></span></span> is <span><span>2 000 000 0002\,000\,000\,000</span><span><span><span></span><span>2</span><span></span><span>000</span><span></span><span>000</span><span></span><span>000</span></span></span></span>.</p>
<p>To maximize the score with exactly two operations, you can choose the following sequence of operations.</p>
<ul>
<li>Choose three points <span><span>(−509 489 796,0)(-509\,489\,796,0)</span><span><span><span></span><span>(</span><span>−</span><span>509</span><span></span><span>489</span><span></span><span>796</span><span>,</span><span></span><span>0</span><span>)</span></span></span></span>, <span><span>(553 177 666,0)(553\,177\,666,0)</span><span><span><span></span><span>(</span><span>553</span><span></span><span>177</span><span></span><span>666</span><span>,</span><span></span><span>0</span><span>)</span></span></span></span>, and <span><span>(−1 000 000 000,2)(-1\,000\,000\,000,2)</span><span><span><span></span><span>(</span><span>−</span><span>1</span><span></span><span>000</span><span></span><span>000</span><span></span><span>000</span><span>,</span><span></span><span>2</span><span>)</span></span></span></span>. The three points are erased.</li>
<li>Choose three points <span><span>(−400 714 529,0)(-400\,714\,529,0)</span><span><span><span></span><span>(</span><span>−</span><span>400</span><span></span><span>714</span><span></span><span>529</span><span>,</span><span></span><span>0</span><span>)</span></span></span></span>, <span><span>(564 040 265,0)(564\,040\,265,0)</span><span><span><span></span><span>(</span><span>564</span><span></span><span>040</span><span></span><span>265</span><span>,</span><span></span><span>0</span><span>)</span></span></span></span>, and <span><span>(1 000 000 000,2)(1\,000\,000\,000,2)</span><span><span><span></span><span>(</span><span>1</span><span></span><span>000</span><span></span><span>000</span><span></span><span>000</span><span>,</span><span></span><span>2</span><span>)</span></span></span></span>. The three points are erased.</li>
</ul>
<p>Then, the score after two operations becomes <span><span>2 027 422 2562\,027\,422\,256</span><span><span><span></span><span>2</span><span></span><span>027</span><span></span><span>422</span><span></span><span>256</span></span></span></span>. It can be shown that the maximum value of your score after performing exactly two operations is <span><span>2 027 422 2562\,027\,422\,256</span><span><span><span></span><span>2</span><span></span><span>027</span><span></span><span>422</span><span></span><span>256</span></span></span></span>. Therefore, the value of <span><span>f(2)f(2)</span><span><span><span></span><span>f</span><span>(</span><span>2</span><span>)</span></span></span></span> is <span><span>2 027 422 2562\,027\,422\,256</span><span><span><span></span><span>2</span><span></span><span>027</span><span></span><span>422</span><span></span><span>256</span></span></span></span>.</p>
<p><strong>Solution</strong></p>
<ol>
<li>要构造满足非共线的三点，需要从 <span><span>y=0y=0</span><span><span><span></span><span>y</span><span></span><span>=</span><span></span></span><span><span></span><span>0</span></span></span></span> 的点中选 <span><span>22</span><span><span><span></span><span>2</span></span></span></span> 个、<span><span>y=2y=2</span><span><span><span></span><span>y</span><span></span><span>=</span><span></span></span><span><span></span><span>2</span></span></span></span> 的点中选 <span><span>11</span><span><span><span></span><span>1</span></span></span></span> 个，或反过来。</li>
<li>每次选出的一组三点形成的三角形面积即对应行上选出的 <span><span>22</span><span><span><span></span><span>2</span></span></span></span> 点的横坐标之差(因为高度为 <span><span>22</span><span><span><span></span><span>2</span></span></span></span>，<span><span>面积=12×底×高=差×1\text{面积}=\frac{1}{2}\times \text{底}\times \text{高}=\text{差}\times 1</span><span><span><span></span><span><span>面积</span></span><span></span><span>=</span><span></span></span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span><span>2</span></span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>1</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span></span><span>×</span><span></span></span><span><span></span><span><span>底</span></span><span></span><span>×</span><span></span></span><span><span></span><span><span>高</span></span><span></span><span>=</span><span></span></span><span><span></span><span><span>差</span></span><span></span><span>×</span><span></span></span><span><span></span><span>1</span></span></span></span>)。</li>
<li>先对 <span><span>y=0y=0</span><span><span><span></span><span>y</span><span></span><span>=</span><span></span></span><span><span></span><span>0</span></span></span></span> 的所有点进行排序，计算“前后配对”的最大差值之和形成前缀数组 <span><span>preApreA</span><span><span><span></span><span>p</span><span>r</span><span>e</span><span>A</span></span></span></span>；同理对 <span><span>y=2y=2</span><span><span><span></span><span>y</span><span></span><span>=</span><span></span></span><span><span></span><span>2</span></span></span></span> 的所有点生成前缀数组 <span><span>preBpreB</span><span><span><span></span><span>p</span><span>r</span><span>e</span><span>B</span></span></span></span>。<span><span>preA[i]preA[i]</span><span><span><span></span><span>p</span><span>r</span><span>e</span><span>A</span><span>[</span><span>i</span><span>]</span></span></span></span> 表示恰好在 <span><span>y=0y=0</span><span><span><span></span><span>y</span><span></span><span>=</span><span></span></span><span><span></span><span>0</span></span></span></span> 行完成 <span><span>ii</span><span><span><span></span><span>i</span></span></span></span> 次“2点配对”操作的总最大面积，同理 <span><span>preB[i]preB[i]</span><span><span><span></span><span>p</span><span>r</span><span>e</span><span>B</span><span>[</span><span>i</span><span>]</span></span></span></span> 针对 <span><span>y=2y=2</span><span><span><span></span><span>y</span><span></span><span>=</span><span></span></span><span><span></span><span>2</span></span></span></span> 行。</li>
<li>每次总共做 <span><span>kk</span><span><span><span></span><span>k</span></span></span></span> 次操作，需要在 <span><span>y=0y=0</span><span><span><span></span><span>y</span><span></span><span>=</span><span></span></span><span><span></span><span>0</span></span></span></span> 行做 <span><span>ii</span><span><span><span></span><span>i</span></span></span></span> 次、在 <span><span>y=2y=2</span><span><span><span></span><span>y</span><span></span><span>=</span><span></span></span><span><span></span><span>2</span></span></span></span> 行做 <span><span>(k−i)(k - i)</span><span><span><span></span><span>(</span><span>k</span><span></span><span>−</span><span></span></span><span><span></span><span>i</span><span>)</span></span></span></span> 次，要求 <span><span>2i+(k−i)≤n,i+2(k−i)≤m2i+(k-i) \le n, i+2(k-i) \le m</span><span><span><span></span><span>2</span><span>i</span><span></span><span>+</span><span></span></span><span><span></span><span>(</span><span>k</span><span></span><span>−</span><span></span></span><span><span></span><span>i</span><span>)</span><span></span><span>≤</span><span></span></span><span><span></span><span>n</span><span>,</span><span></span><span>i</span><span></span><span>+</span><span></span></span><span><span></span><span>2</span><span>(</span><span>k</span><span></span><span>−</span><span></span></span><span><span></span><span>i</span><span>)</span><span></span><span>≤</span><span></span></span><span><span></span><span>m</span></span></span></span>。</li>
<li>用三分搜索或其它方法在可行区间内寻找 <span><span>ii</span><span><span><span></span><span>i</span></span></span></span>，使得 <span><span>preA[i]+preB[k−i]preA[i] + preB[k-i]</span><span><span><span></span><span>p</span><span>r</span><span>e</span><span>A</span><span>[</span><span>i</span><span>]</span><span></span><span>+</span><span></span></span><span><span></span><span>p</span><span>r</span><span>e</span><span>B</span><span>[</span><span>k</span><span></span><span>−</span><span></span></span><span><span></span><span>i</span><span>]</span></span></span></span> 最大，记为 <span><span>f(k)f(k)</span><span><span><span></span><span>f</span><span>(</span><span>k</span><span>)</span></span></span></span>。</li>
<li>最后找出最大可行的 <span><span>kk</span><span><span><span></span><span>k</span></span></span></span> 值 <span><span>k_maxk\_{max}</span><span><span><span></span><span>k</span><span>_</span><span><span>ma</span><span>x</span></span></span></span></span>，并输出 <span><span>f(1)…f(k_max⁡)f(1) \dots f(k\_{\max})</span><span><span><span></span><span>f</span><span>(</span><span>1</span><span>)</span><span></span><span>…</span><span></span><span>f</span><span>(</span><span>k</span><span>_</span><span><span>max</span></span><span>)</span></span></span></span>。</li>
</ol>
<div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>vector</span><span>&lt;</span><span>int</span><span>&gt; </span><span>buildPrefix</span><span>(</span><span>vector</span><span>&lt;</span><span>int</span><span>&gt; </span><span>&amp;</span><span>arr</span><span>) {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>sort</span><span>(arr.</span><span>begin</span><span>(), arr.</span><span>end</span><span>());</span></div></div><div><div><div>3</div></div><div><span>  </span><span>int</span><span> half </span><span>=</span><span> arr.</span><span>size</span><span>() </span><span>/</span><span> </span><span>2</span><span>;</span></div></div><div><div><div>4</div></div><div><span>  </span><span>vector</span><span>&lt;</span><span>int</span><span>&gt; </span><span>diff</span><span>(half);</span></div></div><div><div><div>5</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>0</span><span>; i </span><span>&lt;</span><span> half; i</span><span>++</span><span>) {</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>diff[i] </span><span>=</span><span> arr[arr.</span><span>size</span><span>() </span><span>-</span><span> </span><span>1</span><span> </span><span>-</span><span> i] </span><span>-</span><span> arr[i];</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>8</div></div><div><span>  </span><span>sort</span><span>(diff.</span><span>begin</span><span>(), diff.</span><span>end</span><span>(), </span><span>greater</span><span>&lt;</span><span>int</span><span>&gt;());</span></div></div><div><div><div>9</div></div><div><span>  </span><span>vector</span><span>&lt;</span><span>int</span><span>&gt; </span><span>prefix</span><span>(half </span><span>+</span><span> </span><span>1</span><span>, </span><span>0</span><span>LL</span><span>);</span></div></div><div><div><div>10</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>1</span><span>; i </span><span>&lt;=</span><span> half; i</span><span>++</span><span>) {</span></div></div><div><div><div>11</div></div><div><span><span>    </span></span><span>prefix[i] </span><span>=</span><span> prefix[i </span><span>-</span><span> </span><span>1</span><span>] </span><span>+</span><span> diff[i </span><span>-</span><span> </span><span>1</span><span>];</span></div></div><div><div><div>12</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>13</div></div><div><span>  </span><span>return</span><span> prefix;</span></div></div><div><div><div>14</div></div><div><span>}</span></div></div><div><div><div>15</div></div><div>
</div></div><div><div><div>16</div></div><div><span>int</span><span> </span><span>ternaryMax</span><span>(</span><span>const</span><span> </span><span>vector</span><span>&lt;</span><span>int</span><span>&gt; </span><span>&amp;</span><span>A</span><span>, </span><span>const</span><span> </span><span>vector</span><span>&lt;</span><span>int</span><span>&gt; </span><span>&amp;</span><span>B</span><span>, </span><span>int</span><span> </span><span>k</span><span>, </span><span>int</span><span> </span><span>low</span><span>,</span></div></div><div><div><div>17</div></div><div><span>               </span><span>int</span><span> </span><span>high</span><span>) {</span></div></div><div><div><div>18</div></div><div><span>  </span><span>if</span><span> (low </span><span>&gt;</span><span> high) </span><span>return</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>19</div></div><div><span>  </span><span>auto</span><span> G </span><span>=</span><span> [</span><span>&amp;</span><span>](</span><span>int</span><span> </span><span>x</span><span>) { </span><span>return</span><span> A[x] </span><span>+</span><span> B[k </span><span>-</span><span> x]; };</span></div></div><div><div><div>20</div></div><div><span>  </span><span>while</span><span> (high </span><span>-</span><span> low </span><span>&gt;=</span><span> </span><span>3</span><span>) {</span></div></div><div><div><div>21</div></div><div><span>    </span><span>int</span><span> m1 </span><span>=</span><span> low </span><span>+</span><span> (high </span><span>-</span><span> low) </span><span>/</span><span> </span><span>3</span><span>;</span></div></div><div><div><div>22</div></div><div><span>    </span><span>int</span><span> m2 </span><span>=</span><span> high </span><span>-</span><span> (high </span><span>-</span><span> low) </span><span>/</span><span> </span><span>3</span><span>;</span></div></div><div><div><div>23</div></div><div><span>    </span><span>if</span><span> (</span><span>G</span><span>(m1) </span><span>&lt;</span><span> </span><span>G</span><span>(m2))</span></div></div><div><div><div>24</div></div><div><span><span>      </span></span><span>low </span><span>=</span><span> m1 </span><span>+</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>25</div></div><div><span>    </span><span>else</span></div></div><div><div><div>26</div></div><div><span><span>      </span></span><span>high </span><span>=</span><span> m2 </span><span>-</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>27</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>28</div></div><div><span>  </span><span>int</span><span> ret </span><span>=</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>29</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> low; i </span><span>&lt;=</span><span> high; i</span><span>++</span><span>) {</span></div></div><div><div><div>30</div></div><div><span><span>    </span></span><span>ret </span><span>=</span><span> </span><span>max</span><span>(ret, </span><span>G</span><span>(i));</span></div></div><div><div><div>31</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>32</div></div><div><span>  </span><span>return</span><span> ret;</span></div></div><div><div><div>33</div></div><div><span>}</span></div></div><div><div><div>34</div></div><div>
</div></div><div><div><div>35</div></div><div><span>void</span><span> </span><span>solve</span><span>() {</span></div></div><div><div><div>36</div></div><div><span>  </span><span>int</span><span> n, m;</span></div></div><div><div><div>37</div></div><div><span><span>  </span></span><span>cin </span><span>&gt;&gt;</span><span> n </span><span>&gt;&gt;</span><span> m;</span></div></div><div><div><div>38</div></div><div><span>  </span><span>vector</span><span>&lt;</span><span>int</span><span>&gt; </span><span>a</span><span>(n), </span><span>b</span><span>(m);</span></div></div><div><div><div>39</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>0</span><span>; i </span><span>&lt;</span><span> n; i</span><span>++</span><span>) {</span></div></div><div><div><div>40</div></div><div><span><span>    </span></span><span>cin </span><span>&gt;&gt;</span><span> a[i];</span></div></div><div><div><div>41</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>42</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> j </span><span>=</span><span> </span><span>0</span><span>; j </span><span>&lt;</span><span> m; j</span><span>++</span><span>) {</span></div></div><div><div><div>43</div></div><div><span><span>    </span></span><span>cin </span><span>&gt;&gt;</span><span> b[j];</span></div></div><div><div><div>44</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>45</div></div><div>
</div></div><div><div><div>46</div></div><div><span>  </span><span>vector</span><span>&lt;</span><span>int</span><span>&gt; preA </span><span>=</span><span> </span><span>buildPrefix</span><span>(a);</span></div></div><div><div><div>47</div></div><div><span>  </span><span>vector</span><span>&lt;</span><span>int</span><span>&gt; preB </span><span>=</span><span> </span><span>buildPrefix</span><span>(b);</span></div></div><div><div><div>48</div></div><div><span>  </span><span>int</span><span> maxPickA </span><span>=</span><span> preA.</span><span>size</span><span>() </span><span>-</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>49</div></div><div><span>  </span><span>int</span><span> maxPickB </span><span>=</span><span> preB.</span><span>size</span><span>() </span><span>-</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>50</div></div><div><span>  </span><span>int</span><span> kmax </span><span>=</span><span> </span><span>min</span><span>((n </span><span>+</span><span> m) </span><span>/</span><span> </span><span>3</span><span>LL</span><span>, maxPickA </span><span>+</span><span> maxPickB);</span></div></div><div><div><div>51</div></div><div><span>  </span><span>vector</span><span>&lt;</span><span>int</span><span>&gt; </span><span>f</span><span>(kmax </span><span>+</span><span> </span><span>1</span><span>, </span><span>0</span><span>LL</span><span>);</span></div></div><div><div><div>52</div></div><div>
</div></div><div><div><div>53</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> k </span><span>=</span><span> </span><span>1</span><span>; k </span><span>&lt;=</span><span> kmax; k</span><span>++</span><span>) {</span></div></div><div><div><div>54</div></div><div><span>    </span><span>int</span><span> low </span><span>=</span><span> </span><span>max</span><span>(</span><span>0</span><span>LL</span><span>, </span><span>max</span><span>(k </span><span>-</span><span> maxPickB, </span><span>2</span><span>LL</span><span> </span><span>*</span><span> k </span><span>-</span><span> m));</span></div></div><div><div><div>55</div></div><div><span>    </span><span>int</span><span> high </span><span>=</span><span> </span><span>min</span><span>(k, </span><span>min</span><span>(maxPickA, n </span><span>-</span><span> k));</span></div></div><div><div><div>56</div></div><div><span>    </span><span>if</span><span> (low </span><span>&lt;=</span><span> high) {</span></div></div><div><div><div>57</div></div><div><span>      </span><span>auto</span><span> bestVal </span><span>=</span><span> </span><span>ternaryMax</span><span>(preA, preB, k, low, high);</span></div></div><div><div><div>58</div></div><div><span><span>      </span></span><span>f[k] </span><span>=</span><span> bestVal;</span></div></div><div><div><div>59</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>60</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>61</div></div><div>
</div></div><div><div><div>62</div></div><div><span>  </span><span>while</span><span> (kmax </span><span>&gt;</span><span> </span><span>0</span><span> </span><span>&amp;&amp;</span><span> f[kmax] </span><span>==</span><span> </span><span>0</span><span>) kmax</span><span>--</span><span>;</span></div></div><div><div><div>63</div></div><div>
</div></div><div><div><div>64</div></div><div><span><span>  </span></span><span>cout </span><span>&lt;&lt;</span><span> kmax </span><span>&lt;&lt;</span><span> endl;</span></div></div><div><div><div>65</div></div><div><span>  </span><span>if</span><span> (kmax </span><span>&gt;</span><span> </span><span>0</span><span>) {</span></div></div><div><div><div>66</div></div><div><span>    </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>1</span><span>; i </span><span>&lt;=</span><span> kmax; i</span><span>++</span><span>) {</span></div></div><div><div><div>67</div></div><div><span><span>      </span></span><span>cout </span><span>&lt;&lt;</span><span> f[i] </span><span>&lt;&lt;</span><span> </span><span>" </span><span>\n</span><span>"</span><span>[i </span><span>==</span><span> kmax];</span></div></div><div><div><div>68</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>69</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>70</div></div><div><span>}</span></div></div><div><div><div>71</div></div><div>
</div></div><div><div><div>72</div></div><div><span>signed</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>73</div></div><div><span>  </span><span>setIO</span><span>();</span></div></div><div><div><div>74</div></div><div><span>  </span><span>int</span><span> tt </span><span>=</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>75</div></div><div><span><span>  </span></span><span>cin </span><span>&gt;&gt;</span><span> tt;</span></div></div><div><div><div>76</div></div><div><span>  </span><span>while</span><span> (tt</span><span>--</span><span>) {</span></div></div><div><div><div>77</div></div><div><span>    </span><span>solve</span><span>();</span></div></div><div><div><div>78</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>79</div></div><div><span>  </span><span>return</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>80</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div>]]></content>
    <author><name>Zowely</name></author>
    <category term="算法竞赛" />
    <category term="算法竞赛" />
    <category term="题解" />
    <category term="C++" />
  </entry>
  <entry>
    <title>写项目时遇到的相关问题汇总</title>
    <link href="https://www.lansganbs.cn/posts/%E9%A1%B9%E7%9B%AE%E5%BC%80%E5%8F%91/%E5%86%99%E9%A1%B9%E7%9B%AE%E6%97%B6%E9%81%87%E5%88%B0%E7%9A%84%E7%9B%B8%E5%85%B3%E9%97%AE%E9%A2%98%E6%B1%87%E6%80%BB/" rel="alternate" type="text/html" />
    <id>https://www.lansganbs.cn/posts/%E9%A1%B9%E7%9B%AE%E5%BC%80%E5%8F%91/%E5%86%99%E9%A1%B9%E7%9B%AE%E6%97%B6%E9%81%87%E5%88%B0%E7%9A%84%E7%9B%B8%E5%85%B3%E9%97%AE%E9%A2%98%E6%B1%87%E6%80%BB/</id>
    <published>2025-01-22T00:00:00.000Z</published>
    <updated>2026-01-10T23:09:32.000Z</updated>
    <summary>记录在写项目过程中遇到的一些相关问题及解决方法</summary>
    <content type="html"><![CDATA[<section><h1>Go 相关<a href="#go-相关"><span>#</span></a></h1><section><h2>包导入不了<a href="#包导入不了"><span>#</span></a></h2><p><code>go mod tidy</code> / 直接下载都无法导入，即使彻底删掉文件夹之后重来也无法导入。</p><p>在创建项目中选择 Go，而不是 Go (GOPATH)。如果选择了 Go (GOPATH)，设置 -&gt; Go 模块 -&gt; 启用 Go 模块集成</p></section><section><h2>YAPI 修改密码后显示密码错误<a href="#yapi-修改密码后显示密码错误"><span>#</span></a></h2><p>YAPI 只有在第一次初始化数据库时，才会读取 <code>YAPI_ADMIN_PASSWORD</code> 环境变量并创建账号。因为之前已经启动过一次，数据库文件（<code>./data/db</code>）已经生成了，并且写入了旧密码。当你修改 <code>docker-compose.yml</code> 并重启时，YAPI 发现数据库已经存在，就不会再次覆盖修改密码了。</p><p>需要在 <code>docker-YApi</code> 目录下依次执行：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>docker</span><span> </span><span>compose</span><span> </span><span>down</span></div></div><div><div><div>2</div></div><div><span>sudo</span><span> </span><span>rm</span><span> </span><span>-rf</span><span> </span><span>data</span></div></div><div><div><div>3</div></div><div><span>docker</span><span> </span><span>compose</span><span> </span><span>up</span><span> </span><span>-d</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>隔离开发环境和生产环境时设置环境变量不生效<a href="#隔离开发环境和生产环境时设置环境变量不生效"><span>#</span></a></h2><p>再设置环境变量之后，需要重启 Goland，而不能只重启终端，否则环境变量不会生效。如果有多个 Goland 窗口打开，需要先全部关闭后再打开。</p></section><section><h2>Nacos 容器相关问题<a href="#nacos-容器相关问题"><span>#</span></a></h2><section><h3>Nacos 容器无法启动<a href="#nacos-容器无法启动"><span>#</span></a></h3><p>虚拟机内存分配过小，对于个人来讲，可以在安装时适当减少内存分配，例如：</p><div><div><div><figure><figcaption><span></span><span>Terminal window</span><span>展开</span><span>收起</span></figcaption><pre><code><div><div><div>1</div></div><div><span>docker</span><span> </span><span>run</span><span> </span><span>-d</span><span> </span><span>\</span></div></div><div><div><div>2</div></div><div><span>    </span><span>--name</span><span> </span><span>nacos</span><span> </span><span>\</span></div></div><div><div><div>3</div></div><div><span>    </span><span>-p</span><span> </span><span>8848:8848</span><span> </span><span>\</span></div></div><div><div><div>4</div></div><div><span>    </span><span>-p</span><span> </span><span>9848:9848</span><span> </span><span>\</span></div></div><div><div><div>5</div></div><div><span>    </span><span>-p</span><span> </span><span>9849:9849</span><span> </span><span>\</span></div></div><div><div><div>6</div></div><div><span>    </span><span>-p</span><span> </span><span>8080:8080</span><span> </span><span>\</span></div></div><div><div><div>7</div></div><div><span>    </span><span>-e</span><span> </span><span>MODE=standalone</span><span> </span><span>\</span></div></div><div><div><div>8</div></div><div><span>    </span><span>-e</span><span> </span><span>SPRING_DATASOURCE_PLATFORM=mysql</span><span> </span><span>\</span></div></div><div><div><div>9</div></div><div><span>    </span><span>-e</span><span> </span><span>MYSQL_SERVICE_HOST=</span><span>192.168.63.131</span><span> </span><span>\</span></div></div><div><div><div>10</div></div><div><span>    </span><span>-e</span><span> </span><span>MYSQL_SERVICE_PORT=</span><span>3306</span><span> </span><span>\</span></div></div><div><div><div>11</div></div><div><span>    </span><span>-e</span><span> </span><span>MYSQL_SERVICE_DB_NAME=nacos</span><span> </span><span>\</span></div></div><div><div><div>12</div></div><div><span>    </span><span>-e</span><span> </span><span>MYSQL_SERVICE_USER=root</span><span> </span><span>\</span></div></div><div><div><div>13</div></div><div><span>    </span><span>-e</span><span> </span><span>MYSQL_SERVICE_PASSWORD=</span><span>123456</span><span> </span><span>\</span></div></div><div><div><div>14</div></div><div><span>    </span><span>-e</span><span> </span><span>NACOS_AUTH_ENABLE=</span><span>true</span><span> </span><span>\</span></div></div><div><div><div>15</div></div><div><span>    </span><span>-e</span><span> </span><span>NACOS_AUTH_TOKEN=U2VjcmV0S2V5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5</span><span> </span><span>\</span></div></div><div><div><div>16</div></div><div><span>    </span><span>-e</span><span> </span><span>NACOS_AUTH_IDENTITY_KEY=nacos_server_key</span><span> </span><span>\</span></div></div><div><div><div>17</div></div><div><span>    </span><span>-e</span><span> </span><span>NACOS_AUTH_IDENTITY_VALUE=nacos_server_value</span><span> </span><span>\</span></div></div><div><div><div>18</div></div><div><span>    </span><span>-e</span><span> </span><span>JVM_XMS=512m</span><span> </span><span>\</span></div></div><div><div><div>19</div></div><div><span>    </span><span>-e</span><span> </span><span>JVM_XMX=512m</span><span> </span><span>\</span></div></div><div><div><div>20</div></div><div><span>    </span><span>--restart=always</span><span> </span><span>\</span></div></div><div><div><div>21</div></div><div><span>    </span><span>nacos/nacos-server:latest</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section><section><h3>Nacos 容器启动后创建配置无法成功写入数据库<a href="#nacos-容器启动后创建配置无法成功写入数据库"><span>#</span></a></h3><p>换成 Chrome 无痕模式或者干脆换成 Edge 就可以了。尝试过清楚缓存并硬性加载也不行，关闭插件也不行，具体不清楚为什么。只有 Chorme 会遇到这个问题。</p></section></section><section><h2>proto相关<a href="#proto相关"><span>#</span></a></h2><section><h3>服务器实现结构体的强制性接口约束<a href="#服务器实现结构体的强制性接口约束"><span>#</span></a></h3><p>新版 gRPC 在生成的接口中包含了一个“私有”方法（例如 <code>mustEmbedUnimplementedUserServer()</code>）。如果不嵌入这个结构体，你的 <code>UserServer</code> 就没有这个方法，编译器会报错，提示它没有完全实现接口。例如正确写法例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>type</span><span> </span><span>server</span><span> </span><span>struct</span><span> {</span></div></div><div><div><div>2</div></div><div><span>    </span><span>proto</span><span>.</span><span>UnimplementedUserServer</span></div></div><div><div><div>3</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section><section><h2>proto 文件的同步<a href="#proto-文件的同步"><span>#</span></a></h2><p>通过　<code>.proto</code>　文件定义服务接口规范，需保持两端定义一致。<strong>字段编号</strong>必须严格一致，字段顺序可调整。字段编号冲突会导致数据错位。</p><p>接口变更需同步更新所有调用方的　<code>.proto</code>　文件，禁止手动修改自动生成的桩代码。</p></section></section>
<section><h1>Java相关<a href="#java相关"><span>#</span></a></h1><section><h2>java&lt;程序包org&gt;.aspectj.weaver.reflect不存在<a href="#javaaspectjweaverreflect不存在"><span>#</span></a></h2><p>更新Maven包，并在IDEA中同步所有Maven项目和重新加载所有Maven项目</p></section><section><h2>aspectjweaver-1.8.13.jar时出错<a href="#aspectjweaver-1813jar时出错"><span>#</span></a></h2><p>选择项目文件夹，在Maven中选择生成源代码并更新文件夹</p></section><section><h2>base64decoder 找不到符号<a href="#base64decoder-找不到符号"><span>#</span></a></h2><p>从Java 8开始，应使用java.util.Base64类来处理Base64编码和解码。这个类提供了Base64.Decoder和Base64.Encoder两个嵌套类：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>// Java 8及更高版本使用java.util.Base64</span></div></div><div><div><div>2</div></div><div><span>Base64.Decoder decoder </span><span>=</span><span> Base64.</span><span>getDecoder</span><span>();</span></div></div><div><div><div>3</div></div><div><span>byte</span><span>[] decodedBytes </span><span>=</span><span> decoder.</span><span>decode</span><span>(encodedString);</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>Base64.Encoder encoder </span><span>=</span><span> Base64.</span><span>getEncoder</span><span>();</span></div></div><div><div><div>6</div></div><div><span>String encodedString </span><span>=</span><span> encoder.</span><span>encodeToString</span><span>(byteArray);</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section>]]></content>
    <author><name>Zowely</name></author>
    <category term="项目开发" />
    <category term="项目开发" />
    <category term="Golang" />
    <category term="Java" />
    <category term="问题汇总" />
  </entry>
  <entry>
    <title>单调栈&amp;单调队列</title>
    <link href="https://www.lansganbs.cn/posts/%E7%AE%97%E6%B3%95%E7%AB%9E%E8%B5%9B/%E5%8D%95%E8%B0%83%E6%A0%88-%E5%8D%95%E8%B0%83%E9%98%9F%E5%88%97/" rel="alternate" type="text/html" />
    <id>https://www.lansganbs.cn/posts/%E7%AE%97%E6%B3%95%E7%AB%9E%E8%B5%9B/%E5%8D%95%E8%B0%83%E6%A0%88-%E5%8D%95%E8%B0%83%E9%98%9F%E5%88%97/</id>
    <published>2025-01-04T00:00:00.000Z</published>
    <updated>2025-01-04T21:28:14.000Z</updated>
    <summary>2025春哈尔滨理工大学新生算法培训</summary>
    <content type="html"><![CDATA[<section><h1>单调栈<a href="#单调栈"><span>#</span></a></h1><section><h2>基本概念<a href="#基本概念"><span>#</span></a></h2><p>单调栈，顾名思义，这个栈是单调递增/递减的。</p><ul>
<li>递增栈：越靠近栈顶的元素越大，如push顺序为 <span><span>11</span><span><span><span></span><span>1</span></span></span></span> <span><span>22</span><span><span><span></span><span>2</span></span></span></span> <span><span>33</span><span><span><span></span><span>3</span></span></span></span> <span><span>44</span><span><span><span></span><span>4</span></span></span></span> <span><span>55</span><span><span><span></span><span>5</span></span></span></span></li>
<li>递减栈：越靠近栈底的元素越大，如push顺序为 <span><span>55</span><span><span><span></span><span>5</span></span></span></span> <span><span>44</span><span><span><span></span><span>4</span></span></span></span> <span><span>33</span><span><span><span></span><span>3</span></span></span></span> <span><span>22</span><span><span><span></span><span>2</span></span></span></span> <span><span>11</span><span><span><span></span><span>1</span></span></span></span></li>
</ul></section><section><h2>问题引入<a href="#问题引入"><span>#</span></a></h2><p><a href="https://www.luogu.com.cn/problem/P5788" target="_blank">P5788 【模板】单调栈</a></p></section><section><h2>用途<a href="#用途"><span>#</span></a></h2><p>使用栈，维护一个单调的序列，通过栈顶的最值来进行计算或转移。</p></section><section><h2>操作<a href="#操作"><span>#</span></a></h2><p>对于每个元素，将其和栈顶-&gt;栈底的元素一次比较，不符合的元素出栈，栈顶为当前维护的栈的最值。</p><p>例如我们要维护一个降序序列，有push顺序为 <span><span>88</span><span><span><span></span><span>8</span></span></span></span> <span><span>44</span><span><span><span></span><span>4</span></span></span></span> <span><span>66</span><span><span><span></span><span>6</span></span></span></span> <span><span>11</span><span><span><span></span><span>1</span></span></span></span> <span><span>99</span><span><span><span></span><span>9</span></span></span></span> <span><span>22</span><span><span><span></span><span>2</span></span></span></span> <span><span>44</span><span><span><span></span><span>4</span></span></span></span> <span><span>00</span><span><span><span></span><span>0</span></span></span></span></p><p>那么第i步操作后栈的状态为：<span><span>88</span><span><span><span></span><span>8</span></span></span></span>;<span><span>88</span><span><span><span></span><span>8</span></span></span></span> <span><span>44</span><span><span><span></span><span>4</span></span></span></span>;<span><span>88</span><span><span><span></span><span>8</span></span></span></span> <span><span>66</span><span><span><span></span><span>6</span></span></span></span>;<span><span>88</span><span><span><span></span><span>8</span></span></span></span> <span><span>66</span><span><span><span></span><span>6</span></span></span></span> <span><span>11</span><span><span><span></span><span>1</span></span></span></span>;<span><span>99</span><span><span><span></span><span>9</span></span></span></span>;<span><span>99</span><span><span><span></span><span>9</span></span></span></span> <span><span>22</span><span><span><span></span><span>2</span></span></span></span>;<span><span>99</span><span><span><span></span><span>9</span></span></span></span> <span><span>44</span><span><span><span></span><span>4</span></span></span></span>;<span><span>99</span><span><span><span></span><span>9</span></span></span></span> <span><span>44</span><span><span><span></span><span>4</span></span></span></span> <span><span>00</span><span><span><span></span><span>0</span></span></span></span></p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>int</span><span> top </span><span>=</span><span> </span><span>0</span><span>;</span><span> // 栈顶指针初值</span></div></div><div><div><div>2</div></div><div><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>1</span><span>; i </span><span>&lt;=</span><span> n; i</span><span>++</span><span>) {</span></div></div><div><div><div>3</div></div><div><span>  </span><span>while</span><span> (top </span><span>&gt;</span><span> </span><span>0</span><span> </span><span>and</span><span> a[q[top]] </span><span>&lt;</span><span> a[i]) {</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>cout </span><span>&lt;&lt;</span><span> a[q[top]] </span><span>&lt;&lt;</span><span> endl;</span><span> // 输出栈顶值</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>top</span><span>--</span><span>;</span><span> // 栈顶出栈</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>q[</span><span>++</span><span>top] </span><span>=</span><span> i;</span><span> // 栈顶入栈</span></div></div><div><div><div>8</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>在单调栈入栈过程中，<strong>入栈的是元素的下标，而不是元素的值</strong>。但下文为了直观，记录的是元素值，<strong>实际写题时切记要记录下标</strong>。</p><p>对于单调栈而言，记录下标不仅能获知当前元素值，还能方便获取其前后元素位置信息，这在需要回溯或计算区间问题时非常重要。此外，入栈下标后，可追踪元素更新，并在栈顶位置快速找到需要的元素，从而保持栈中数据的单调性并简化维护过程。另外，引入下标可以处理重复出现的元素，用下标作为能唯一标识一条记录的属性组。</p><p>考虑如下数组构造单调栈<span><span>a=[1,1,4,5,1,4]a=[1,1,4,5,1,4]</span><span><span><span></span><span>a</span><span></span><span>=</span><span></span></span><span><span></span><span>[</span><span>1</span><span>,</span><span></span><span>1</span><span>,</span><span></span><span>4</span><span>,</span><span></span><span>5</span><span>,</span><span></span><span>1</span><span>,</span><span></span><span>4</span><span>]</span></span></span></span>，构造递增栈只记录元素的话是<span><span>[1,1,1,4][1,1,1,4]</span><span><span><span></span><span>[</span><span>1</span><span>,</span><span></span><span>1</span><span>,</span><span></span><span>1</span><span>,</span><span></span><span>4</span><span>]</span></span></span></span>，这个如果题目所求为输出第一个比当前元素小的元素，那可以找到，但是如果是第一个比当前元素大的元素的下标，那这时无法确定具体是第几个<span><span>11</span><span><span><span></span><span>1</span></span></span></span>比当前元素小。<strong>所以构造单调栈所记录的属性必须是下标，而不是元素值</strong>。</p><p>另外，在构造单调栈时，循环条件为<code>while (top &gt; 0 and a[q[top]] &lt; a[i])</code>，而不是<code>while (top &gt; 0 and a[q[top]] &lt;= a[i])</code>，<strong>循环条件是严格小于（或等于），相同元素必须全部记录，而不是当前元素小于等于栈顶元素就弹出栈顶元素</strong>。</p><p>考虑如下数组构造单调栈<span><span>a=[1,2,2,2,2,2]a=[1,2,2,2,2,2]</span><span><span><span></span><span>a</span><span></span><span>=</span><span></span></span><span><span></span><span>[</span><span>1</span><span>,</span><span></span><span>2</span><span>,</span><span></span><span>2</span><span>,</span><span></span><span>2</span><span>,</span><span></span><span>2</span><span>,</span><span></span><span>2</span><span>]</span></span></span></span>，正确构造递增栈为<span><span>a=[1,2,2,2,2,2]a=[1,2,2,2,2,2]</span><span><span><span></span><span>a</span><span></span><span>=</span><span></span></span><span><span></span><span>[</span><span>1</span><span>,</span><span></span><span>2</span><span>,</span><span></span><span>2</span><span>,</span><span></span><span>2</span><span>,</span><span></span><span>2</span><span>,</span><span></span><span>2</span><span>]</span></span></span></span>，可以找出第一个大于数组下标 <span><span>11</span><span><span><span></span><span>1</span></span></span></span> 的元素下标为 <span><span>22</span><span><span><span></span><span>2</span></span></span></span>（数组从1开始）；但是如果错误记录，元素和栈顶相等的话，那构造出来时<span><span>a=[1,2]a=[1,2]</span><span><span><span></span><span>a</span><span></span><span>=</span><span></span></span><span><span></span><span>[</span><span>1</span><span>,</span><span></span><span>2</span><span>]</span></span></span></span>，这个 <span><span>22</span><span><span><span></span><span>2</span></span></span></span> 是最后一个<span><span>22</span><span><span><span></span><span>2</span></span></span></span>，下标为 <span><span>66</span><span><span><span></span><span>6</span></span></span></span> ，但事实上第一个大于下标 <span><span>11</span><span><span><span></span><span>1</span></span></span></span> 的元素下标为 <span><span>22</span><span><span><span></span><span>2</span></span></span></span> ，是第一个 <span><span>22</span><span><span><span></span><span>2</span></span></span></span> ，而不是第五个 <span><span>22</span><span><span><span></span><span>2</span></span></span></span> 。</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>void</span><span> </span><span>solve</span><span>() {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>int</span><span> n;</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>cin </span><span>&gt;&gt;</span><span> n;</span></div></div><div><div><div>4</div></div><div><span>  </span><span>vector</span><span>&lt;</span><span>int</span><span>&gt; </span><span>a</span><span>(n </span><span>+</span><span> </span><span>1</span><span>), </span><span>q</span><span>(n </span><span>+</span><span> </span><span>1</span><span>), </span><span>ans</span><span>(n </span><span>+</span><span> </span><span>1</span><span>);</span></div></div><div><div><div>5</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>1</span><span>; i </span><span>&lt;=</span><span> n; i</span><span>++</span><span>) {</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>cin </span><span>&gt;&gt;</span><span> a[i];</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>8</div></div><div><span>  </span><span>int</span><span> top </span><span>=</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>9</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>1</span><span>; i </span><span>&lt;=</span><span> n; i</span><span>++</span><span>) {</span></div></div><div><div><div>10</div></div><div><span>    </span><span>while</span><span> (top </span><span>&gt;</span><span> </span><span>0</span><span> </span><span>and</span><span> a[q[top]] </span><span>&lt;</span><span> a[i]) {</span></div></div><div><div><div>11</div></div><div><span><span>      </span></span><span>ans[q[top]] </span><span>=</span><span> i;</span></div></div><div><div><div>12</div></div><div><span><span>      </span></span><span>top</span><span>--</span><span>;</span></div></div><div><div><div>13</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>14</div></div><div><span><span>    </span></span><span>q[</span><span>++</span><span>top] </span><span>=</span><span> i;</span></div></div><div><div><div>15</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>16</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>1</span><span>; i </span><span>&lt;=</span><span> n; i</span><span>++</span><span>) {</span></div></div><div><div><div>17</div></div><div><span><span>    </span></span><span>cout </span><span>&lt;&lt;</span><span> ans[i] </span><span>&lt;&lt;</span><span> </span><span>" </span><span>\n</span><span>"</span><span>[i </span><span>==</span><span> n];</span></div></div><div><div><div>18</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>19</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section></section>]]></content>
    <author><name>Zowely</name></author>
    <category term="算法竞赛" />
    <category term="算法竞赛" />
    <category term="算法教程" />
    <category term="C++" />
  </entry>
  <entry>
    <title>GoLang 基础</title>
    <link href="https://www.lansganbs.cn/posts/%E7%BC%96%E7%A8%8B%E8%AF%AD%E8%A8%80/golang-%E5%9F%BA%E7%A1%80/" rel="alternate" type="text/html" />
    <id>https://www.lansganbs.cn/posts/%E7%BC%96%E7%A8%8B%E8%AF%AD%E8%A8%80/golang-%E5%9F%BA%E7%A1%80/</id>
    <published>2025-01-01T00:00:00.000Z</published>
    <updated>2025-01-02T21:45:32.000Z</updated>
    <summary>Go 语言基础入门教程，涵盖变量声明与赋值、数据类型、控制结构、集合类型、函数和方法等核心概念，帮助初学者快速掌握 Go 语言编程技能。</summary>
    <content type="html"><![CDATA[<section><h1>01 数据类型<a href="#01-数据类型"><span>#</span></a></h1><section><h2>变量声明与赋值<a href="#变量声明与赋值"><span>#</span></a></h2><section><h3>变量声明<a href="#变量声明"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>package</span><span> </span><span>main</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>import</span><span> </span><span>"</span><span>fmt</span><span>"</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>6</div></div><div><span>    </span><span>var</span><span> i </span><span>int</span><span> </span><span>=</span><span> </span><span>10</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>fmt.</span><span>Println</span><span>(i)</span></div></div><div><div><div>8</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>Go 语言中的变量定义都是名称在前，类型在后，和 C++ 反着来</p><p>变量 i 也可以用如下方式声明，省略定义类型，来自行推导：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>var</span><span> i </span><span>=</span><span> </span><span>10</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>也可以一次声明多个变量，放在 <code>()</code> 中，并且同样可以进行自动推导：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>var</span><span> (</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>j </span><span>int</span><span> </span><span>=</span><span> </span><span>0</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>k </span><span>int</span><span> </span><span>=</span><span> </span><span>1</span></div></div><div><div><div>4</div></div><div><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>var</span><span> (</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>j </span><span>=</span><span> </span><span>0</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>k </span><span>=</span><span> </span><span>1</span></div></div><div><div><div>4</div></div><div><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>赋值<a href="#赋值"><span>#</span></a></h3><p>赋值语句的作用是修改变量，使用 <code>=</code> 来进行赋值</p></section><section><h3>变量的简短声明<a href="#变量的简短声明"><span>#</span></a></h3><p>Go语言可以使用 <code>变量名:=表达式</code> 来进行简短声明，如果能为变量初始化，那就选择简短声明方式</p><p>例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>i </span><span>:=</span><span> </span><span>10</span></div></div><div><div><div>2</div></div><div><span>bf </span><span>:=</span><span> </span><span>false</span></div></div><div><div><div>3</div></div><div><span>s1 </span><span>:=</span><span> </span><span>"Hello"</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section><section><h2>数据类型<a href="#数据类型"><span>#</span></a></h2><section><h3>整形<a href="#整形"><span>#</span></a></h3><p>有符号整形：int, int8, int16, int32 和 int64</p><p>无符号整形：uint, uint8, uint16, uint32 和 uint64</p><ul>
<li>字节 byte 类型等价于 uint8 类型，用于定义一个字节，也属于整形</li>
</ul></section><section><h3>浮点数<a href="#浮点数"><span>#</span></a></h3><p>最常用的是 float64，因为它的精度更高</p><p>浮点计算的结果相比于 float32 误差更小</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>package</span><span> </span><span>main</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>import</span><span> </span><span>"</span><span>fmt</span><span>"</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>6</div></div><div><span>    </span><span>var</span><span> f32 </span><span>float32</span><span> </span><span>=</span><span> </span><span>2.2</span></div></div><div><div><div>7</div></div><div><span>    </span><span>var</span><span> f64 </span><span>float64</span><span> </span><span>=</span><span> </span><span>10.3456</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>fmt.</span><span>Print</span><span>(</span><span>"f32 is "</span><span>, f32, </span><span>", f64 is "</span><span>, f64)</span></div></div><div><div><div>9</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>布尔型<a href="#布尔型"><span>#</span></a></h3><p>布尔型的值只有两种：true 和 false，使用 bool 定义</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>package</span><span> </span><span>main</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>import</span><span> </span><span>"</span><span>fmt</span><span>"</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>6</div></div><div><span>    </span><span>var</span><span> bf </span><span>bool</span><span> </span><span>=</span><span> </span><span>false</span></div></div><div><div><div>7</div></div><div><span>    </span><span>var</span><span> bt </span><span>bool</span><span> </span><span>=</span><span> </span><span>true</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>fmt.</span><span>Print</span><span>(</span><span>"bf is "</span><span>, bf, </span><span>", bt is "</span><span>, bt)</span></div></div><div><div><div>9</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>布尔型可以被用于一元操作符 <code>!</code> ，二元操作符 <code>&amp;&amp;</code> ，<code>||</code></p></section><section><h3>字符串<a href="#字符串"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>package</span><span> </span><span>main</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>import</span><span> </span><span>"</span><span>fmt</span><span>"</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>6</div></div><div><span>    </span><span>var</span><span> s1 </span><span>string</span><span> </span><span>=</span><span> </span><span>"Hello"</span></div></div><div><div><div>7</div></div><div><span>    </span><span>var</span><span> s2 </span><span>string</span><span> </span><span>=</span><span> </span><span>"World!"</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>fmt.</span><span>Println</span><span>(s1, s2)</span></div></div><div><div><div>9</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>可以通过 <code>+</code> 来把字符串连接起来，也可以使用 <code>+=</code>，<code>==</code> 等符号，类似 C++</p></section></section><section><h2>特殊值与常量<a href="#特殊值与常量"><span>#</span></a></h2><section><h3>零值<a href="#零值"><span>#</span></a></h3><p>一个变量的默认值</p></section><section><h3>常量<a href="#常量"><span>#</span></a></h3><p>关键字是 <code>const</code> ，一旦创建便不能修改，使用  <code>=</code> 来声明，比如</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>const</span><span> </span><span>name</span><span> </span><span>=</span><span> </span><span>"Zowely"</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>Go语言中只允许布尔型，字符串，数字类型这种基本类型作为常量</p></section><section><h3>iota<a href="#iota"><span>#</span></a></h3><p>这是一个常量生成器，初始化相似规则的常量，避免重复初始化</p><p>假如需要定义：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>const</span><span> (</span></div></div><div><div><div>2</div></div><div><span>    </span><span>one</span><span>   </span><span>=</span><span> </span><span>1</span></div></div><div><div><div>3</div></div><div><span>    </span><span>two</span><span>   </span><span>=</span><span> </span><span>2</span></div></div><div><div><div>4</div></div><div><span>    </span><span>three</span><span> </span><span>=</span><span> </span><span>3</span></div></div><div><div><div>5</div></div><div><span>    </span><span>four</span><span>  </span><span>=</span><span> </span><span>4</span></div></div><div><div><div>6</div></div><div><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>可以写作</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>const</span><span> (</span></div></div><div><div><div>2</div></div><div><span>    </span><span>one</span><span> </span><span>=</span><span> </span><span>iota</span><span> </span><span>+</span><span> </span><span>1</span></div></div><div><div><div>3</div></div><div><span>    </span><span>two</span></div></div><div><div><div>4</div></div><div><span>    </span><span>three</span></div></div><div><div><div>5</div></div><div><span>    </span><span>four</span></div></div><div><div><div>6</div></div><div><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section><section><h2>其他重要概念<a href="#其他重要概念"><span>#</span></a></h2><section><h3>指针<a href="#指针"><span>#</span></a></h3><p>指针对应变量在内存中的存储位置，也就是说指针的值就是变量的内存地址</p><p>例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>pi </span><span>:=</span><span> </span><span>&amp;</span><span>i</span></div></div><div><div><div>2</div></div><div><span>fmt.</span><span>Println</span><span>(</span><span>*</span><span>pi)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>pi 就是指向变量 i 的指针，想要获得指针 pi 指向的变量的值，则使用 *pi</p></section><section><h3>变量类型转换<a href="#变量类型转换"><span>#</span></a></h3><p>GO 语言是强类型语言，不同类型的变量无法相互使用计算</p><p>不同类型的变量在赋值和计算前，需要先进行类型转换</p><p>对于字符串和数字类型，可以使用：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>i2s </span><span>:=</span><span> strconv.</span><span>Itoa</span><span>(i)</span></div></div><div><div><div>2</div></div><div><span>s2i, err </span><span>:=</span><span> strconv.</span><span>Atoi</span><span>(i2s)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>对于数字类型，可以使用：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>i2f </span><span>:=</span><span> </span><span>float64</span><span>(i)</span></div></div><div><div><div>2</div></div><div><span>f2i </span><span>:=</span><span> </span><span>int</span><span>(i2f)</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>Strings 包<a href="#strings-包"><span>#</span></a></h3><p>strings 包适用于处理字符串的工具包，里面有很多常用函数。比如查找字符串，拆分字符串，去掉字符串的空格等。</p><p>例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>s1 </span><span>:=</span><span> </span><span>"Hello World"</span></div></div><div><div><div>2</div></div><div><span>// 判断 s1 的前缀是否是 "H"</span></div></div><div><div><div>3</div></div><div><span>fmt.</span><span>Println</span><span>(strings.</span><span>HasPrefix</span><span>(s1, </span><span>"H"</span><span>))</span></div></div><div><div><div>4</div></div><div><span>// 判断 s1 中查找字符 "o"</span></div></div><div><div><div>5</div></div><div><span>fmt.</span><span>Println</span><span>(strings.</span><span>Index</span><span>(s1, </span><span>"o"</span><span>))</span></div></div><div><div><div>6</div></div><div><span>// 把 s1 全部转换为大写</span></div></div><div><div><div>7</div></div><div><span>fmt.</span><span>Println</span><span>(strings.</span><span>ToUpper</span><span>(s1))</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>输出为：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>true</span></div></div><div><div><div>2</div></div><div><span>4</span></div></div><div><div><div>3</div></div><div><span>HELLO</span><span> </span><span>WORLD</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section></section>
<section><h1>02 控制结构<a href="#02-控制结构"><span>#</span></a></h1><section><h2>if 条件语句<a href="#if-条件语句"><span>#</span></a></h2><p>例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>i </span><span>:=</span><span> </span><span>10</span></div></div><div><div><div>3</div></div><div><span>    </span><span>if</span><span> i </span><span>&gt;</span><span> </span><span>10</span><span> {</span></div></div><div><div><div>4</div></div><div><span><span>       </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"i &gt; 10"</span><span>)</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>} </span><span>else</span><span> </span><span>if</span><span> i </span><span>&gt;</span><span> </span><span>0</span><span> </span><span>&amp;&amp;</span><span> i </span><span>&lt;</span><span> </span><span>10</span><span> {</span></div></div><div><div><div>6</div></div><div><span><span>       </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"0 &lt; i &lt;= 10"</span><span>)</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>} </span><span>else</span><span> {</span></div></div><div><div><div>8</div></div><div><span><span>       </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"i &lt;= 0"</span><span>)</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>10</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>和 C++ 不同的是：</p><ul>
<li>if 后面的条件表达式不需要使用 ()</li>
<li>每个条件分支中的大括号是必须的</li>
<li>if 紧跟的大括号不能独占一行，else 前后的大括号也不能独占一行</li>
</ul><p>上面的代码可以进行折叠，等价于：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>2</div></div><div><span>    </span><span>if</span><span> i </span><span>:=</span><span> </span><span>10</span><span>; i </span><span>&gt;</span><span> </span><span>10</span><span> {</span></div></div><div><div><div>3</div></div><div><span><span>       </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"i &gt; 10"</span><span>)</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>} </span><span>else</span><span> </span><span>if</span><span> i </span><span>&gt;</span><span> </span><span>0</span><span> </span><span>&amp;&amp;</span><span> i </span><span>&lt;</span><span> </span><span>10</span><span> {</span></div></div><div><div><div>5</div></div><div><span><span>       </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"0 &lt; i &lt;= 10"</span><span>)</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>} </span><span>else</span><span> {</span></div></div><div><div><div>7</div></div><div><span><span>       </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"i &lt;= 0"</span><span>)</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>9</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>switch 选择语句<a href="#switch-选择语句"><span>#</span></a></h2><p>上面的代码同样可以使用类似 C++ 的 switch 来替代，例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>2</div></div><div><span>    </span><span>switch</span><span> i </span><span>:=</span><span> </span><span>10</span><span>; {</span></div></div><div><div><div>3</div></div><div><span>    </span><span>case</span><span> i </span><span>&gt;</span><span> </span><span>10</span><span>:</span></div></div><div><div><div>4</div></div><div><span><span>       </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"i &gt; 10"</span><span>)</span></div></div><div><div><div>5</div></div><div><span>    </span><span>case</span><span> i </span><span>&gt;</span><span> </span><span>0</span><span> </span><span>&amp;&amp;</span><span> i </span><span>&lt;=</span><span> </span><span>10</span><span>:</span></div></div><div><div><div>6</div></div><div><span><span>       </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"0 &lt; i &lt;= 10"</span><span>)</span></div></div><div><div><div>7</div></div><div><span>    </span><span>default</span><span>:</span></div></div><div><div><div>8</div></div><div><span><span>       </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"i &lt;= 0"</span><span>)</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>10</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><blockquote><p>Go 语言的 switch 的 case 从上到下逐一进行判断，一旦满足条件，立即执行相对应的分支并返回（break），其余分支不进行判断，这与 C++ 不同</p></blockquote></section><section><h2>for 循环语句<a href="#for-循环语句"><span>#</span></a></h2><p>GO 语言的 for 循环，同样不需要 <code>()</code>，中间使用 <code>;</code> 进行分隔</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>sum </span><span>:=</span><span> </span><span>0</span></div></div><div><div><div>3</div></div><div><span>  </span><span>for</span><span> i </span><span>:=</span><span> </span><span>1</span><span>; i </span><span>&lt;=</span><span> </span><span>100</span><span>; i</span><span>++</span><span> {</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>sum </span><span>+=</span><span> i</span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>fmt.</span><span>Println</span><span>(sum)</span></div></div><div><div><div>7</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><blockquote><p>Go 语言中没有 while 循环，只能通过 for 循环来进行实现</p></blockquote><p>例如对于上述例子可以写为：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>sum </span><span>:=</span><span> </span><span>0</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>i </span><span>:=</span><span> </span><span>1</span></div></div><div><div><div>4</div></div><div><span>    </span><span>for</span><span> i </span><span>&lt;=</span><span> </span><span>100</span><span> {</span></div></div><div><div><div>5</div></div><div><span><span>       </span></span><span>sum </span><span>+=</span><span> i</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>fmt.</span><span>Println</span><span>(sum)</span></div></div><div><div><div>8</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>对于多组输入可以写为：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>t </span><span>:=</span><span> </span><span>100</span></div></div><div><div><div>3</div></div><div><span>    </span><span>for</span><span> t </span><span>&gt;</span><span> </span><span>0</span><span> {</span></div></div><div><div><div>4</div></div><div><span><span>       </span></span><span>t</span><span>--</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>6</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li>continue 可以跳出<strong>本次</strong>循环，继续执行下一个循环</li>
<li>break 可以跳出<strong>整个</strong>循环，哪怕 for 循环没有执行完</li>
</ul></section></section>
<section><h1>03 集合类型<a href="#03-集合类型"><span>#</span></a></h1><section><h2>Array 数组<a href="#array-数组"><span>#</span></a></h2><p>数组存放的是固定长度、相同类型的数据而且这些存放的元素是连续的，存放的数据类型没有限制</p><section><h3>数组声明<a href="#数组声明"><span>#</span></a></h3><p>数组的声明和变量类似：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>array </span><span>:=</span><span> [</span><span>5</span><span>]</span><span>string</span><span>{</span><span>"a"</span><span>, </span><span>"b"</span><span>, </span><span>"c"</span><span>, </span><span>"d"</span><span>, </span><span>"e"</span><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>如果数组的所有元素已经确定，那么 <code>[]</code> 可以省略，例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>array </span><span>:=</span><span> []</span><span>string</span><span>{</span><span>"a"</span><span>, </span><span>"b"</span><span>, </span><span>"c"</span><span>, </span><span>"d"</span><span>, </span><span>"e"</span><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>可以对特定元素进行初始化，例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>array </span><span>:=</span><span> []</span><span>string</span><span>{</span><span>1</span><span>: </span><span>"b"</span><span>, </span><span>3</span><span>: </span><span>"d"</span><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>对于未初始化的位置，初始都是数组类型的零值</p><p>对于访问数组元素，和 C++ 类似，使用 <code>[index]</code> 来访问</p></section><section><h3>数组循环<a href="#数组循环"><span>#</span></a></h3><p>大部分情况下，使用 for range 这种 Go 语言的新型循环，例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>array </span><span>:=</span><span> []</span><span>string</span><span>{</span><span>1</span><span>: </span><span>"a"</span><span>, </span><span>3</span><span>: </span><span>"c"</span><span>}</span></div></div><div><div><div>3</div></div><div><span>    </span><span>for</span><span> _, v </span><span>:=</span><span> </span><span>range</span><span> array {</span></div></div><div><div><div>4</div></div><div><span>       </span><span>println</span><span>(v)</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>6</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>类似于 C++ 的结构化绑定，对于上述循环，第一维表示下标，第二位表示数组的值，如果不需要某一维，使用 <code>_</code> 来丢弃</p></section></section><section><h2>Slice 切片<a href="#slice-切片"><span>#</span></a></h2><section><h3>基于数组生成切片<a href="#基于数组生成切片"><span>#</span></a></h3><p>可以理解动态数组，对任意数组分割，就可以得到一个切片，例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>array </span><span>:=</span><span> []</span><span>string</span><span>{</span><span>"a"</span><span>, </span><span>"b"</span><span>, </span><span>"c"</span><span>, </span><span>"d"</span><span>, </span><span>"e"</span><span>}</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>slice </span><span>:=</span><span> array[</span><span>2</span><span>:</span><span>5</span><span>]</span></div></div><div><div><div>4</div></div><div><span><span>  </span></span><span>fmt.</span><span>Println</span><span>(slice)</span></div></div><div><div><div>5</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>输出为：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>[c d e]</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这里的 slice 获取的是左闭右开区间的数组，切片和数组一样，也可以通过索引获取元素</p><p>需要注意的是，切片的索引范围对比原数组改变了，也是从 0 开始的</p><p>使用 slice 时，数组的 start 和 end 都是可以省略的，如果省略，则默认为 0 和数组的长度</p><p></p><figure><img src="https://www.lansganbs.cn/images/article/%E7%BC%96%E7%A8%8B%E8%AF%AD%E8%A8%80/GoLang%20%E5%9F%BA%E7%A1%80/slice.png" alt="alt text" /><figcaption>alt text</figcaption></figure><p></p></section><section><h3>切片修改<a href="#切片修改"><span>#</span></a></h3><p>和数组一样，可以使用 <code>=</code> 来修改</p></section><section><h3>切片声明<a href="#切片声明"><span>#</span></a></h3><p>可以使用 make 来声明切片，例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>slice1 </span><span>:=</span><span> </span><span>make</span><span>([]</span><span>string</span><span>, </span><span>4</span><span>, </span><span>8</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>4 为切片的长度，8 为切片的容量，第三个参数可以省略，默认容量等于长度</p><p>同时切片也可以是使用字面量的方式来进行初始化，和数组一样，例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>slice1 </span><span>:=</span><span> []</span><span>string</span><span>{</span><span>"a"</span><span>, </span><span>"b"</span><span>, </span><span>"c"</span><span>, </span><span>"d"</span><span>, </span><span>"e"</span><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>区别只在 <code>[]</code> 中的长度</p></section><section><h3>Append<a href="#append"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>新切片 </span><span>:=</span><span> </span><span>append</span><span>(原切片, 要添加的元素</span><span>/</span><span>元素列表</span><span>/</span><span>另一个切片</span><span>...</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>slice </span><span>:=</span><span> []</span><span>string</span><span>{</span><span>"Hello World"</span><span>}</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>slice1 </span><span>:=</span><span> []</span><span>string</span><span>{</span><span>"a"</span><span>, </span><span>"b"</span><span>, </span><span>"c"</span><span>, </span><span>"d"</span><span>, </span><span>"e"</span><span>}</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>slice1 </span><span>=</span><span> </span><span>append</span><span>(slice1, </span><span>"f"</span><span>, </span><span>"g"</span><span>, </span><span>"h"</span><span>)</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>slice2 </span><span>:=</span><span> </span><span>append</span><span>(slice1, </span><span>"i"</span><span>, </span><span>"j"</span><span>, </span><span>"k"</span><span>)</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>slice3 </span><span>:=</span><span> </span><span>append</span><span>(slice1, slice2</span><span>...</span><span>)</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>fmt.</span><span>Println</span><span>(slice)</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>fmt.</span><span>Println</span><span>(slice1)</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>fmt.</span><span>Println</span><span>(slice2)</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>fmt.</span><span>Println</span><span>(slice3)</span></div></div><div><div><div>11</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>输出：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>[Hello World]</span></div></div><div><div><div>2</div></div><div><span>[a b c d e f g h]</span></div></div><div><div><div>3</div></div><div><span>[a b c d e f g h i j k]</span></div></div><div><div><div>4</div></div><div><span>[a b c d e f g h a b c d e f g h i j k]</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>切片元素循环<a href="#切片元素循环"><span>#</span></a></h3><p>跟数组是相同的，可以使用 for 循环和 for range</p></section></section><section><h2>Map 映射<a href="#map-映射"><span>#</span></a></h2><p>map是一个无序的K-V键值对集合结构为 map[K]V，其中K对应Key，V对应Value</p><section><h3>Map 的声明初始化<a href="#map-的声明初始化"><span>#</span></a></h3><p>可以使用 make 来声明 map，例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>nameAgeMap </span><span>:=</span><span> </span><span>make</span><span>(</span><span>map</span><span>[</span><span>string</span><span>]</span><span>int</span><span>)</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>nameAgeMap[</span><span>"Zowely"</span><span>] </span><span>=</span><span> </span><span>20</span></div></div><div><div><div>4</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>同时 map 也可以是使用字面量的方式来进行初始化，和数组一样，例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>nameAgeMap </span><span>:=</span><span> </span><span>map</span><span>[</span><span>string</span><span>]</span><span>int</span><span>{</span><span>"Zowely"</span><span>: </span><span>20</span><span>}</span></div></div><div><div><div>3</div></div><div><span>    </span><span>println</span><span>(nameAgeMap[</span><span>"Zowely"</span><span>])</span></div></div><div><div><div>4</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>Map 的获取和删除<a href="#map-的获取和删除"><span>#</span></a></h3><p>获取类似数组的遍历，删除可以使用内置的 delete 函数</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>nameAgeMap </span><span>:=</span><span> </span><span>make</span><span>(</span><span>map</span><span>[</span><span>string</span><span>]</span><span>int</span><span>)</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>nameAgeMap[</span><span>"Zowely"</span><span>] </span><span>=</span><span> </span><span>20</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>age, ok </span><span>:=</span><span> nameAgeMap[</span><span>"Zowely"</span><span>]</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>    </span><span>if</span><span> ok {</span></div></div><div><div><div>8</div></div><div><span><span>       </span></span><span>fmt.</span><span>Println</span><span>(age)</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span>    </span><span>delete</span><span>(nameAgeMap, </span><span>"Zowely"</span><span>)</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span><span>    </span></span><span>age, ok </span><span>=</span><span> nameAgeMap[</span><span>"Zowely"</span><span>]</span></div></div><div><div><div>14</div></div><div>
</div></div><div><div><div>15</div></div><div><span>    </span><span>if</span><span> ok {</span></div></div><div><div><div>16</div></div><div><span><span>       </span></span><span>fmt.</span><span>Println</span><span>(age)</span></div></div><div><div><div>17</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>18</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>输出：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>20</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>遍历 Map<a href="#遍历-map"><span>#</span></a></h3><p>获取类似数组的遍历</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>nameAgeMap </span><span>:=</span><span> </span><span>make</span><span>(</span><span>map</span><span>[</span><span>string</span><span>]</span><span>int</span><span>)</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>nameAgeMap[</span><span>"Zowely"</span><span>] </span><span>=</span><span> </span><span>20</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>nameAgeMap[</span><span>"Zowely1"</span><span>] </span><span>=</span><span> </span><span>21</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>nameAgeMap[</span><span>"Zowely2"</span><span>] </span><span>=</span><span> </span><span>22</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>    </span><span>for</span><span> name, age </span><span>:=</span><span> </span><span>range</span><span> nameAgeMap {</span></div></div><div><div><div>9</div></div><div><span><span>       </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"name:"</span><span>, name, </span><span>" age:"</span><span>, age)</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>11</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>Map 的大小<a href="#map-的大小"><span>#</span></a></h3><p>可以使用内置的 len 函数来获取</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>fmt.</span><span>Println</span><span>(</span><span>len</span><span>(nameAgeMap))</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section><section><h2>String 和 []byte<a href="#string-和-byte"><span>#</span></a></h2><p>string 的操作类似 C++，可以使用 <code>[]</code> 来获取元素</p><blockquote><p>Go 语言的 string 是不可变的，类似于 Java 的 string</p></blockquote><p>如果需要修改字符串，可以通过以下方式间接实现：</p><ul>
<li>将字符串转换为<strong>字节切片（<code>[]byte</code>）</strong> 或<strong>字符切片（<code>[]rune</code>）</strong>，修改切片后再转换回字符串</li>
</ul><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>s </span><span>:=</span><span> </span><span>"hello"</span></div></div><div><div><div>2</div></div><div><span>bytes </span><span>:=</span><span> []</span><span>byte</span><span>(s)</span></div></div><div><div><div>3</div></div><div><span>bytes[</span><span>0</span><span>] </span><span>=</span><span> </span><span>'</span><span>H</span><span>'</span></div></div><div><div><div>4</div></div><div><span>s </span><span>=</span><span> </span><span>string</span><span>(bytes)</span></div></div><div><div><div>5</div></div><div><span>fmt.</span><span>Println</span><span>(s) </span><span>// 输出 "Hello"</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li>利用字符串拼接、切片操作等方式生成新的字符串</li>
</ul><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>s </span><span>:=</span><span> </span><span>"hello"</span></div></div><div><div><div>2</div></div><div><span>s </span><span>=</span><span> </span><span>"H"</span><span> </span><span>+</span><span> s[</span><span>1</span><span>:]</span></div></div><div><div><div>3</div></div><div><span>fmt.</span><span>Println</span><span>(s) </span><span>// 输出 "Hello"</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section>
<section><h1>04 函数和方法<a href="#04-函数和方法"><span>#</span></a></h1><p>通过函数，可以把开发任务分解成一个个小的单元，这些小单元可以被其他单元复用，进而提高开发效率、降低代码重合度</p><section><h2>函数<a href="#函数"><span>#</span></a></h2><ul>
<li>任何一个函数的定义都有一个 func 的关键字，用于声明一个函数</li>
<li>定义一个合法的函数名字</li>
<li>函数后的 <code>()</code> 是不可省略的，大括号 <code>{}</code> 也不可省略</li>
</ul><p>函数的定义如下：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> </span><span>funcName</span><span>(</span><span>params</span><span>) </span><span>result</span><span> {</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>body</span></div></div><div><div><div>3</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li>func 关键字</li>
<li>funcName 函数名</li>
<li>params 函数的参数</li>
<li>result 函数的返回值</li>
<li>body 函数体</li>
</ul><p>例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> </span><span>sum</span><span>(</span><span>a</span><span> </span><span>int</span><span>, </span><span>b</span><span> </span><span>int</span><span>) </span><span>int</span><span> {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>return</span><span> a </span><span>+</span><span> b</span></div></div><div><div><div>3</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><section><h3>多值返回<a href="#多值返回"><span>#</span></a></h3><p>Go语言的函数可以返回多个值，也就是多值返回</p><p>例如对于一个函数，第一个值返回函数的结果，第二个值返回函数出错的信息</p><p>例如，对于上述 sum 函数，我们不允许传参为负数，可以按照如下改造：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> </span><span>sum</span><span>(</span><span>a</span><span> </span><span>int</span><span>, </span><span>b</span><span> </span><span>int</span><span>) (</span><span>int</span><span>, </span><span>error</span><span>) {</span></div></div><div><div><div>2</div></div><div><span>    </span><span>if</span><span> a </span><span>&lt;</span><span> </span><span>0</span><span> </span><span>||</span><span> b </span><span>&lt;</span><span> </span><span>0</span><span> {</span></div></div><div><div><div>3</div></div><div><span>       </span><span>return</span><span> </span><span>0</span><span>, errors.</span><span>New</span><span>(</span><span>"a 或者 b 不能为负数"</span><span>)</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>5</div></div><div><span>    </span><span>return</span><span> a </span><span>+</span><span> b, </span><span>nil</span></div></div><div><div><div>6</div></div><div><span>}</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>result, err </span><span>:=</span><span> </span><span>sum</span><span>(</span><span>1</span><span>, </span><span>2</span><span>)</span></div></div><div><div><div>10</div></div><div><span>    </span><span>if</span><span> err </span><span>!=</span><span> </span><span>nil</span><span> {</span></div></div><div><div><div>11</div></div><div><span><span>       </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"错误:"</span><span>, err)</span></div></div><div><div><div>12</div></div><div><span><span>    </span></span><span>} </span><span>else</span><span> {</span></div></div><div><div><div>13</div></div><div><span><span>       </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"结果:"</span><span>, result)</span></div></div><div><div><div>14</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>15</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>需要注意的是，如果函数有多值返回，那么需要有多个变量来接受函数的值</p><p>同样如果接受的值不需要，可以使用 <code>_</code> 来忽略掉</p><p>例如：<code>result, _ := sum(1, 2)</code></p></section><section><h3>命名返回参数<a href="#命名返回参数"><span>#</span></a></h3><p>函数的返回值也可以有变量名称，例如：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> </span><span>sum</span><span>(</span><span>a</span><span> </span><span>int</span><span>, </span><span>b</span><span> </span><span>int</span><span>) (</span><span>sum</span><span> </span><span>int</span><span>, </span><span>err</span><span> </span><span>error</span><span>) {</span></div></div><div><div><div>2</div></div><div><span>    </span><span>if</span><span> a </span><span>&lt;</span><span> </span><span>0</span><span> </span><span>||</span><span> b </span><span>&lt;</span><span> </span><span>0</span><span> {</span></div></div><div><div><div>3</div></div><div><span>       </span><span>return</span><span> </span><span>0</span><span>, errors.</span><span>New</span><span>(</span><span>"a 或者 b 不能为负数"</span><span>)</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>sum </span><span>=</span><span> a </span><span>+</span><span> b</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>err </span><span>=</span><span> </span><span>nil</span></div></div><div><div><div>7</div></div><div><span>    </span><span>return</span></div></div><div><div><div>8</div></div><div><span>}</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>11</div></div><div><span><span>    </span></span><span>result, err </span><span>:=</span><span> </span><span>sum</span><span>(</span><span>114</span><span>, </span><span>514</span><span>)</span></div></div><div><div><div>12</div></div><div><span>    </span><span>if</span><span> err </span><span>!=</span><span> </span><span>nil</span><span> {</span></div></div><div><div><div>13</div></div><div><span><span>       </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"错误:"</span><span>, err)</span></div></div><div><div><div>14</div></div><div><span><span>    </span></span><span>} </span><span>else</span><span> {</span></div></div><div><div><div>15</div></div><div><span><span>       </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"结果:"</span><span>, result)</span></div></div><div><div><div>16</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>17</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>这样我们就可以在函数体内使用它们，这样只需要写一个 return 即可，因为<code>sum = a + b</code>  和 <code>err = nil</code> 就相当于 return 了这两个数</p><p>但这种方法并不常用</p></section><section><h3>可变参数<a href="#可变参数"><span>#</span></a></h3><p>函数的参数数量是可变的，类似 C++ 的折叠表达式，例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> </span><span>sum</span><span>(</span><span>a</span><span> </span><span>...int</span><span>) </span><span>int</span><span> {</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>sum </span><span>:=</span><span> </span><span>0</span></div></div><div><div><div>3</div></div><div><span>    </span><span>for</span><span> _, v </span><span>:=</span><span> </span><span>range</span><span> a {</span></div></div><div><div><div>4</div></div><div><span><span>       </span></span><span>sum </span><span>+=</span><span> v</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>6</div></div><div><span>    </span><span>return</span><span> sum</span></div></div><div><div><div>7</div></div><div><span>}</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>result </span><span>:=</span><span> </span><span>sum</span><span>(</span><span>114</span><span>, </span><span>514</span><span>)</span></div></div><div><div><div>11</div></div><div><span><span>    </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"结果:"</span><span>, result)</span></div></div><div><div><div>12</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这里相当于 sum 接受了一个切片 a，类型为 int，所以可以使用 for range 进行循环</p><p>如果传入的参数中，同时有可变参数和普通参数，那么可变参数一定要写在参数列表的最后</p></section><section><h3>包级函数<a href="#包级函数"><span>#</span></a></h3><p>不管是自定义的函数 sum、sum1，还是我们使用到的函数 Println，都会从属于一个包也就是 package</p><p>不同包的函数要被调用，那么函数的作用域必须是公有的也就是函数名称的首字母要大写</p><ul>
<li>函数名称首字母小写代表私有函数，只有在同一个包内才能调用</li>
<li>函数名称首字母大写表示共有函数，不同的包也可以调用</li>
<li>任何一个函数都会从属于一个包</li>
</ul><p>Go 语言中，没有类似 C++ 的 private，public来修饰，是通过首字母大小写来辨别的</p></section><section><h3>匿名函数和闭包<a href="#匿名函数和闭包"><span>#</span></a></h3><p>匿名函数就是没有名称的函数，类似于 C++ 的 lambda 表达式，例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>sum </span><span>:=</span><span> </span><span>func</span><span>(</span><span>a</span><span> </span><span>...int</span><span>) </span><span>int</span><span> {</span></div></div><div><div><div>3</div></div><div><span><span>       </span></span><span>sum </span><span>:=</span><span> </span><span>0</span></div></div><div><div><div>4</div></div><div><span>       </span><span>for</span><span> _, v </span><span>:=</span><span> </span><span>range</span><span> a {</span></div></div><div><div><div>5</div></div><div><span><span>          </span></span><span>sum </span><span>+=</span><span> v</span></div></div><div><div><div>6</div></div><div><span><span>       </span></span><span>}</span></div></div><div><div><div>7</div></div><div><span>       </span><span>return</span><span> sum</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>fmt.</span><span>Println</span><span>(</span><span>sum</span><span>(</span><span>114</span><span>, </span><span>514</span><span>))</span></div></div><div><div><div>10</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>在函数中再定义函数（函数嵌套），定义的这个匿名函数，也可以称为内部函数</p><p>更重要的是，在函数内定义的内部函数，可以使用外部函数的变量等，这种方式也称为闭包</p></section></section><section><h2>方法<a href="#方法"><span>#</span></a></h2><p>Go 的语法规定：<strong>方法的接收者类型必须是 “自定义类型”</strong></p><p>方法必须要有一个接收者，这个接收者是一个类型，这样方法就和这个类型绑定在一起，称为这个类型的方法，例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>type</span><span> </span><span>Age</span><span> </span><span>uint</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>func</span><span> (</span><span>age </span><span>Age</span><span>) </span><span>String</span><span>() {</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"the age is"</span><span>, age)</span></div></div><div><div><div>5</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>和函数不同，定义方法是会在关键字 func 和方法名 String() 之间加一个接受者，接受者使用 <code>()</code> 包围</p><p>接收者的定义和普通变量、函数参数等一样,前面是变量名，后面是接收者类型</p><p>此时 String() 就是类型 Age 的方法，定义之后，就可以通过 <code>.</code> 来调用方法，这类似 C++ 的 class，例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>age </span><span>:=</span><span> </span><span>Age</span><span>(</span><span>20</span><span>)</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>age.</span><span>String</span><span>()</span></div></div><div><div><div>4</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><section><h3>值类型接受者和指针类型接收者<a href="#值类型接受者和指针类型接收者"><span>#</span></a></h3><p>方法的接受者也可以传入指针类型，例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>type</span><span> </span><span>Age</span><span> </span><span>uint</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>func</span><span> (</span><span>age </span><span>*</span><span>Age</span><span>) </span><span>String</span><span>() {</span></div></div><div><div><div>4</div></div><div><span>  </span><span>*</span><span>age</span><span>++</span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"the age is"</span><span>, </span><span>*</span><span>age)</span></div></div><div><div><div>6</div></div><div><span>}</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>9</div></div><div><span><span>  </span></span><span>age </span><span>:=</span><span> </span><span>Age</span><span>(</span><span>20</span><span>)</span></div></div><div><div><div>10</div></div><div><span><span>  </span></span><span>age.</span><span>String</span><span>()</span></div></div><div><div><div>11</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li>如果使用一个值类型变量调用指针类型接收者的方法，Go语言编译器会自动帮我们取指针调用</li>
</ul><p>于是上面的方法等价于：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>type</span><span> </span><span>Age</span><span> </span><span>uint</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>func</span><span> (</span><span>age </span><span>*</span><span>Age</span><span>) </span><span>String</span><span>() {</span></div></div><div><div><div>4</div></div><div><span>    </span><span>*</span><span>age</span><span>++</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"the age is"</span><span>, </span><span>*</span><span>age)</span></div></div><div><div><div>6</div></div><div><span>}</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>age </span><span>:=</span><span> </span><span>Age</span><span>(</span><span>20</span><span>)</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>(</span><span>&amp;</span><span>age).</span><span>String</span><span>()</span></div></div><div><div><div>11</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li>如果使用一个指针类型变量调用值类型接收者的方法，Go语言编译器会自动帮我们解引用调用</li>
</ul></section><section><h3>方法表达式<a href="#方法表达式"><span>#</span></a></h3><p>方法赋值给变量，称之为方法表达式，例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>type</span><span> </span><span>Age</span><span> </span><span>uint</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>func</span><span> (</span><span>age </span><span>Age</span><span>) </span><span>String</span><span>() {</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"the age is"</span><span>, age)</span></div></div><div><div><div>5</div></div><div><span>}</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>age </span><span>:=</span><span> </span><span>Age</span><span>(</span><span>20</span><span>)</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>sm </span><span>:=</span><span> Age.String</span></div></div><div><div><div>10</div></div><div><span>    </span><span>sm</span><span>(age)</span></div></div><div><div><div>11</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>不管方法是否有参数，通过方法表达式调用，第一个参数必须是接受者，然后才是方法自身的参数，例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>type</span><span> </span><span>Age</span><span> </span><span>uint</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>func</span><span> (</span><span>age </span><span>Age</span><span>) </span><span>String</span><span>(</span><span>name</span><span> </span><span>string</span><span>) {</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"Age is:"</span><span>, age, </span><span>", Name is:"</span><span>, name)</span></div></div><div><div><div>5</div></div><div><span>}</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>age </span><span>:=</span><span> </span><span>Age</span><span>(</span><span>20</span><span>)</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>sm </span><span>:=</span><span> Age.String</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>name </span><span>:=</span><span> </span><span>"Zowely"</span></div></div><div><div><div>11</div></div><div><span>    </span><span>sm</span><span>(age, name)</span></div></div><div><div><div>12</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>对于 sm 必须先传入 age，再传入 name</p></section></section></section>
<section><h1>05 strust 和 interface<a href="#05-strust-和-interface"><span>#</span></a></h1><p>整形，字符串只能描述单一对象，如果是聚合对象，就无法描述了，这时需要用到结构体，接口，工厂函数等</p><section><h2>strust 结构体<a href="#strust-结构体"><span>#</span></a></h2><p>结构体是一种聚合类型，里面可以包含任意类型的值，这些值就是我们定义的结构体的成员，也称为字段</p><p>定义结构体需要用到 type + struct 关键词组合，例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>type</span><span> </span><span>person</span><span> </span><span>struct</span><span> {</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>name </span><span>string</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>age  </span><span>int</span></div></div><div><div><div>4</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><section><h3>结构体的声明使用<a href="#结构体的声明使用"><span>#</span></a></h3><p>定义之后，就可以通过 <code>.</code> 来调用方法，这类似 C++ 的 struct</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>type</span><span> </span><span>person</span><span> </span><span>struct</span><span> {</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>name </span><span>string</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>age  </span><span>int</span></div></div><div><div><div>4</div></div><div><span>}</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>7</div></div><div><span>    </span><span>var</span><span> p1 </span><span>person</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>fmt.</span><span>Println</span><span>(p1.name, p1.age)</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>p2 </span><span>:=</span><span> </span><span>person</span><span>{name: </span><span>"Alice"</span><span>, age: </span><span>30</span><span>}</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>fmt.</span><span>Println</span><span>(p2.name, p2.age)</span></div></div><div><div><div>11</div></div><div><span><span>    </span></span><span>p3 </span><span>:=</span><span> </span><span>person</span><span>{</span><span>"Bob"</span><span>, </span><span>25</span><span>}</span></div></div><div><div><div>12</div></div><div><span><span>    </span></span><span>fmt.</span><span>Println</span><span>(p3.name, p3.age)</span></div></div><div><div><div>13</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>如果在定义时明确结构体的字段，那么定义的顺序可以调换；否则需要严格对应</p></section><section><h3>字段结构体<a href="#字段结构体"><span>#</span></a></h3><p>可以理解为结构体嵌套结构体，例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>type</span><span> </span><span>person</span><span> </span><span>struct</span><span> {</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>name </span><span>string</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>age  </span><span>int</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>addr </span><span>address</span></div></div><div><div><div>5</div></div><div><span>}</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>type</span><span> </span><span>address</span><span> </span><span>struct</span><span> {</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>city  </span><span>string</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>state </span><span>string</span></div></div><div><div><div>10</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这里 person 嵌套了一个 address，初始化时对对应字段初始化即可，例如对于上述结构体初始化：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>p1 </span><span>:=</span><span> </span><span>person</span><span>{</span></div></div><div><div><div>3</div></div><div><span><span>       </span></span><span>name: </span><span>"Zowely"</span><span>,</span></div></div><div><div><div>4</div></div><div><span><span>       </span></span><span>age:  </span><span>20</span><span>,</span></div></div><div><div><div>5</div></div><div><span><span>       </span></span><span>addr: </span><span>address</span><span>{</span></div></div><div><div><div>6</div></div><div><span><span>          </span></span><span>city:  </span><span>"HRB"</span><span>,</span></div></div><div><div><div>7</div></div><div><span><span>          </span></span><span>state: </span><span>"HLJ"</span><span>,</span></div></div><div><div><div>8</div></div><div><span><span>       </span></span><span>},</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>fmt.</span><span>Println</span><span>(p1)</span></div></div><div><div><div>11</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section><section><h2>interface 接口<a href="#interface-接口"><span>#</span></a></h2><p>接口是和调用方的一种约定它是一个高度抽象的类型，不用和具体的实现细节绑定在一起，就是给不同类型定的统一标准，只要类型符合标准（实现了接口的所有方法），就能被需要这个标准的地方使用，不用管类型本身是什么。</p><p>定义结构体需要用到 type + interface 关键词组合，例如：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>package</span><span> </span><span>main</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>import</span><span> </span><span>"</span><span>fmt</span><span>"</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>type</span><span> </span><span>person</span><span> </span><span>struct</span><span> {</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>age  </span><span>int</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>name </span><span>string</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>addr </span><span>address</span></div></div><div><div><div>9</div></div><div><span>}</span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span>type</span><span> </span><span>address</span><span> </span><span>struct</span><span> {</span></div></div><div><div><div>12</div></div><div><span><span>    </span></span><span>city    </span><span>string</span></div></div><div><div><div>13</div></div><div><span><span>    </span></span><span>country </span><span>string</span></div></div><div><div><div>14</div></div><div><span>}</span></div></div><div><div><div>15</div></div><div>
</div></div><div><div><div>16</div></div><div><span>type</span><span> </span><span>Stringer</span><span> </span><span>interface</span><span> {</span></div></div><div><div><div>17</div></div><div><span>    </span><span>String</span><span>() </span><span>string</span></div></div><div><div><div>18</div></div><div><span>}</span></div></div><div><div><div>19</div></div><div>
</div></div><div><div><div>20</div></div><div><span>func</span><span> (</span><span>addr </span><span>address</span><span>) </span><span>String</span><span>() </span><span>string</span><span> {</span></div></div><div><div><div>21</div></div><div><span>    </span><span>return</span><span> fmt.</span><span>Sprintf</span><span>(</span><span>"the city is </span><span>%s</span><span>, country is </span><span>%s</span><span>"</span><span>, addr.city, addr.country)</span></div></div><div><div><div>22</div></div><div><span>}</span></div></div><div><div><div>23</div></div><div>
</div></div><div><div><div>24</div></div><div><span>func</span><span> (</span><span>p </span><span>person</span><span>) </span><span>String</span><span>() </span><span>string</span><span> {</span></div></div><div><div><div>25</div></div><div><span>    </span><span>return</span><span> fmt.</span><span>Sprintf</span><span>(</span><span>"the name is </span><span>%s</span><span>, age is </span><span>%d</span><span>"</span><span>, p.name, p.age)</span></div></div><div><div><div>26</div></div><div><span>}</span></div></div><div><div><div>27</div></div><div>
</div></div><div><div><div>28</div></div><div><span>func</span><span> </span><span>printString</span><span>(</span><span>s</span><span> </span><span>fmt</span><span>.</span><span>Stringer</span><span>) {</span></div></div><div><div><div>29</div></div><div><span><span>    </span></span><span>fmt.</span><span>Println</span><span>(s.</span><span>String</span><span>())</span></div></div><div><div><div>30</div></div><div><span>}</span></div></div><div><div><div>31</div></div><div>
</div></div><div><div><div>32</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>33</div></div><div><span><span>    </span></span><span>addr </span><span>:=</span><span> </span><span>address</span><span>{city: </span><span>"Beijing"</span><span>, country: </span><span>"China"</span><span>}</span></div></div><div><div><div>34</div></div><div>
</div></div><div><div><div>35</div></div><div><span>    </span><span>printString</span><span>(addr)</span></div></div><div><div><div>36</div></div><div>
</div></div><div><div><div>37</div></div><div><span><span>    </span></span><span>p </span><span>:=</span><span> </span><span>person</span><span>{</span></div></div><div><div><div>38</div></div><div><span><span>       </span></span><span>name: </span><span>"Alice"</span><span>,</span></div></div><div><div><div>39</div></div><div><span><span>       </span></span><span>age:  </span><span>20</span><span>,</span></div></div><div><div><div>40</div></div><div><span><span>       </span></span><span>addr: addr,</span></div></div><div><div><div>41</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>42</div></div><div>
</div></div><div><div><div>43</div></div><div><span>    </span><span>printString</span><span>(p)</span></div></div><div><div><div>44</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>对于上述代码做如下解释：</p><section><h3>核心接口：<code>fmt.Stringer</code><a href="#核心接口fmtstringer"><span>#</span></a></h3><p>代码中显式写出了 <code>fmt.Stringer</code> 接口的定义（它本质是 Go 标准库的内置接口，这里显式声明是为了更清晰）：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>type</span><span> </span><span>Stringer</span><span> </span><span>interface</span><span> {</span></div></div><div><div><div>2</div></div><div><span>    </span><span>String</span><span>() </span><span>string</span><span> </span><span>// 接口仅包含一个方法签名：无参数，返回 string 类型</span></div></div><div><div><div>3</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li>接口是<strong>方法签名的集合</strong>，它不关心类型的具体实现，只规定 “必须实现哪些方法”。</li>
<li><code>fmt.Stringer</code> 的核心作用是：为类型提供 “自定义字符串描述” 的标准方式，很多标准库函数（如 <code>fmt.Print</code> 系列）会自动调用该接口的 <code>String()</code> 方法。</li>
</ul></section><section><h3>接口的 “隐式实现” 规则<a href="#接口的-隐式实现-规则"><span>#</span></a></h3><p>Go 语言中，类型实现接口无需显式声明（比如 <code>type person struct implements Stringer</code>），只要满足一个条件：<strong>该类型实现了接口中所有的方法</strong>，就默认实现了这个接口。</p><section><h4><code>address</code> 类型实现了 <code>Stringer</code> 接口<a href="#address类型实现了stringer接口"><span>#</span></a></h4><p>代码中为 <code>address</code> 定义了 <code>String() string</code> 方法：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> (</span><span>addr </span><span>address</span><span>) </span><span>String</span><span>() </span><span>string</span><span> {</span></div></div><div><div><div>2</div></div><div><span>    </span><span>return</span><span> fmt.</span><span>Sprintf</span><span>(</span><span>"the city is </span><span>%s</span><span>, country is </span><span>%s</span><span>"</span><span>, addr.city, addr.country)</span></div></div><div><div><div>3</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li>该方法的签名（方法名、参数、返回值）与 <code>Stringer</code> 接口要求的完全一致。</li>
<li>因此，<code>address</code> 类型隐式实现了 <code>Stringer</code> 接口，<code>address</code> 实例可以被当作 <code>Stringer</code> 接口类型使用。</li>
</ul></section><section><h4><code>person</code> 类型也实现了 <code>Stringer</code> 接口<a href="#person类型也实现了stringer接口"><span>#</span></a></h4><p>同样，<code>person</code> 类型也定义了符合要求的 <code>String() string</code> 方法：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> (</span><span>p </span><span>person</span><span>) </span><span>String</span><span>() </span><span>string</span><span> {</span></div></div><div><div><div>2</div></div><div><span>    </span><span>return</span><span> fmt.</span><span>Sprintf</span><span>(</span><span>"the name is </span><span>%s</span><span>, age is </span><span>%d</span><span>"</span><span>, p.name, p.age)</span></div></div><div><div><div>3</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li>方法签名与 <code>Stringer</code> 接口完全匹配，<code>person</code> 类型也隐式实现了 <code>Stringer</code> 接口。</li>
</ul></section></section><section><h3>接口的 “多态” 特性<a href="#接口的-多态-特性"><span>#</span></a></h3><p>多态的核心是：<strong>同一接口类型的变量，可以接收不同的实现类型实例，调用方法时会执行对应类型的具体实现</strong>。</p><p>代码中的 <code>printString</code> 函数体现了这一点：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> </span><span>printString</span><span>(</span><span>s</span><span> </span><span>fmt</span><span>.</span><span>Stringer</span><span>) { </span><span>// 参数类型是 Stringer 接口</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>fmt.</span><span>Println</span><span>(s.</span><span>String</span><span>()) </span><span>// 调用接口的 String() 方法</span></div></div><div><div><div>3</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li>函数参数 <code>s</code> 是 <code>Stringer</code> 接口类型，它不绑定具体的实现类型，只要是实现了 <code>Stringer</code> 接口的类型，都能作为参数传入。</li>
<li>调用 <code>s.String()</code> 时，会根据传入的实际实例类型，执行该类型对应的 <code>String()</code> 方法（而非接口的 “默认实现”，因为接口本身没有实现）。</li>
</ul></section></section></section>
<section><h1>06 错误处理<a href="#06-错误处理"><span>#</span></a></h1><p>错误是可以预期的，并且不是非常严重，不会影响程序的进行</p><section><h2>error 接口<a href="#error-接口"><span>#</span></a></h2><p>在 Go 语言中，错误是通过内置的 error 接口表示的，例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>type</span><span> </span><span>error</span><span> </span><span>interface</span><span> {</span></div></div><div><div><div>2</div></div><div><span>    </span><span>Error</span><span>() </span><span>string</span></div></div><div><div><div>3</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>error 工厂函数<a href="#error-工厂函数"><span>#</span></a></h2><p>也可以在函数内部返回错误信息给调用者，例如之前提到的：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> </span><span>sum</span><span>(</span><span>a</span><span> </span><span>int</span><span>, </span><span>b</span><span> </span><span>int</span><span>) (</span><span>int</span><span>, </span><span>error</span><span>) {</span></div></div><div><div><div>2</div></div><div><span>    </span><span>if</span><span> a </span><span>&lt;</span><span> </span><span>0</span><span> </span><span>||</span><span> b </span><span>&lt;</span><span> </span><span>0</span><span> {</span></div></div><div><div><div>3</div></div><div><span>       </span><span>return</span><span> </span><span>0</span><span>, errors.</span><span>New</span><span>(</span><span>"a 或者 b 不能为负数"</span><span>)</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>5</div></div><div><span>    </span><span>return</span><span> a </span><span>+</span><span> b, </span><span>nil</span></div></div><div><div><div>6</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>自定义 error<a href="#自定义-error"><span>#</span></a></h2><p>采用工厂返回错误信息的方式只能传递一个字符串，如果想返回更多信息，可以自定义 error，先定义一个新类型（比如结构体），然后让这个类型实现 error 接口，如下面的代码所示：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>package</span><span> </span><span>main</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>import</span><span> </span><span>"</span><span>fmt</span><span>"</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>type</span><span> </span><span>commonError</span><span> </span><span>struct</span><span> {</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>errorCode </span><span>int</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>errorMsg  </span><span>string</span></div></div><div><div><div>8</div></div><div><span>}</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span>func</span><span> (</span><span>ce </span><span>*</span><span>commonError</span><span>) </span><span>Error</span><span>() </span><span>string</span><span> {</span></div></div><div><div><div>11</div></div><div><span>  </span><span>return</span><span> ce.errorMsg</span></div></div><div><div><div>12</div></div><div><span>}</span></div></div><div><div><div>13</div></div><div>
</div></div><div><div><div>14</div></div><div><span>func</span><span> </span><span>add</span><span>(</span><span>a</span><span>, </span><span>b</span><span> </span><span>int</span><span>) (</span><span>int</span><span>, </span><span>error</span><span>) {</span></div></div><div><div><div>15</div></div><div><span>  </span><span>if</span><span> a </span><span>&lt;</span><span> </span><span>0</span><span> </span><span>||</span><span> b </span><span>&lt;</span><span> </span><span>0</span><span> {</span></div></div><div><div><div>16</div></div><div><span>    </span><span>return</span><span> </span><span>0</span><span>, </span><span>&amp;</span><span>commonError</span><span>{</span></div></div><div><div><div>17</div></div><div><span><span>      </span></span><span>errorCode: </span><span>1</span><span>,</span></div></div><div><div><div>18</div></div><div><span><span>      </span></span><span>errorMsg:  </span><span>"a 或 b 不能为负数"</span><span>,</span></div></div><div><div><div>19</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>20</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>21</div></div><div><span>  </span><span>return</span><span> a </span><span>+</span><span> b, </span><span>nil</span></div></div><div><div><div>22</div></div><div><span>}</span></div></div><div><div><div>23</div></div><div>
</div></div><div><div><div>24</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>25</div></div><div><span><span>  </span></span><span>result, err </span><span>:=</span><span> </span><span>add</span><span>(</span><span>-</span><span>1</span><span>, </span><span>2</span><span>)</span></div></div><div><div><div>26</div></div><div><span>  </span><span>if</span><span> err </span><span>!=</span><span> </span><span>nil</span><span> {</span></div></div><div><div><div>27</div></div><div><span><span>    </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"Error:"</span><span>, err)</span></div></div><div><div><div>28</div></div><div><span><span>  </span></span><span>} </span><span>else</span><span> {</span></div></div><div><div><div>29</div></div><div><span><span>    </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"Result:"</span><span>, result)</span></div></div><div><div><div>30</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>31</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section><section><h2>error 断言 / 类型断言<a href="#error-断言--类型断言"><span>#</span></a></h2><p>有了自定义的 error，并且携带了更多的错误信息后就可以使用这些信息了，先把返回的 error 接口转换为自定义的错误类型，例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>sum, err </span><span>:=</span><span> </span><span>add</span><span>(</span><span>1</span><span>, </span><span>-</span><span>2</span><span>)</span></div></div><div><div><div>3</div></div><div><span>    </span><span>if</span><span> cm, ok </span><span>:=</span><span> err.(</span><span>*</span><span>commonError</span><span>); ok {</span></div></div><div><div><div>4</div></div><div><span>       </span><span>println</span><span>(</span><span>"错误代码:"</span><span>, cm.errorCode)</span></div></div><div><div><div>5</div></div><div><span>       </span><span>println</span><span>(</span><span>"错误信息:"</span><span>, cm.errorMsg)</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>} </span><span>else</span><span> {</span></div></div><div><div><div>7</div></div><div><span>       </span><span>println</span><span>(</span><span>"计算结果:"</span><span>, sum)</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>9</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>Error Wrapping<a href="#error-wrapping"><span>#</span></a></h2><p><code>Error Wrapping</code>（错误包装）是一种在 Go 1.13 中引入的机制，它允许你基于一个已存在的 <code>error</code> 生成一个新的 <code>error</code>，同时保留对原始错误的引用。这样做的好处是可以在不丢失原始错误信息的情况下，为错误添加更多的上下文信息，非常便于调试。</p><p>实现错误包装通常使用 <code>fmt.Errorf</code> 函数配合 <code>%w</code> 占位符，例如：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>package</span><span> </span><span>main</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>import</span><span> (</span></div></div><div><div><div>4</div></div><div><span>    </span><span>"</span><span>errors</span><span>"</span></div></div><div><div><div>5</div></div><div><span>    </span><span>"</span><span>fmt</span><span>"</span></div></div><div><div><div>6</div></div><div><span>    </span><span>"</span><span>os</span><span>"</span></div></div><div><div><div>7</div></div><div><span>)</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>func</span><span> </span><span>readFile</span><span>(</span><span>path</span><span> </span><span>string</span><span>) </span><span>error</span><span> {</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>_, err </span><span>:=</span><span> os.</span><span>Open</span><span>(path)</span></div></div><div><div><div>11</div></div><div><span>    </span><span>if</span><span> err </span><span>!=</span><span> </span><span>nil</span><span> {</span></div></div><div><div><div>12</div></div><div><span>        </span><span>return</span><span> err</span></div></div><div><div><div>13</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>14</div></div><div><span>    </span><span>return</span><span> </span><span>nil</span></div></div><div><div><div>15</div></div><div><span>}</span></div></div><div><div><div>16</div></div><div>
</div></div><div><div><div>17</div></div><div><span>func</span><span> </span><span>processFile</span><span>(</span><span>path</span><span> </span><span>string</span><span>) </span><span>error</span><span> {</span></div></div><div><div><div>18</div></div><div><span><span>    </span></span><span>err </span><span>:=</span><span> </span><span>readFile</span><span>(path)</span></div></div><div><div><div>19</div></div><div><span>    </span><span>if</span><span> err </span><span>!=</span><span> </span><span>nil</span><span> {</span></div></div><div><div><div>20</div></div><div><span>        </span><span>return</span><span> fmt.</span><span>Errorf</span><span>(</span><span>"处理文件失败: </span><span>%w</span><span>"</span><span>, err)</span></div></div><div><div><div>21</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>22</div></div><div><span>    </span><span>return</span><span> </span><span>nil</span></div></div><div><div><div>23</div></div><div><span>}</span></div></div><div><div><div>24</div></div><div>
</div></div><div><div><div>25</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>26</div></div><div><span><span>    </span></span><span>err </span><span>:=</span><span> </span><span>processFile</span><span>(</span><span>"non_existent_file.txt"</span><span>)</span></div></div><div><div><div>27</div></div><div><span>    </span><span>if</span><span> err </span><span>!=</span><span> </span><span>nil</span><span> {</span></div></div><div><div><div>28</div></div><div><span><span>        </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"捕获到错误:"</span><span>, err)</span></div></div><div><div><div>29</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>30</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>输出：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>捕获到错误:</span><span> </span><span>处理文件失败:</span><span> </span><span>open</span><span> </span><span>non_existent_file.txt:</span><span> </span><span>no</span><span> </span><span>such</span><span> </span><span>file</span><span> </span><span>or</span><span> </span><span>directory</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>在上面的例子中，<code>processFile</code> 函数捕获到 <code>readFile</code> 返回的原始错误，并使用 <code>fmt.Errorf</code> 和 <code>%w</code> 将其包装起来，形成了一个更具描述性的新错误。</p></section><section><h2>errors.Unwrap<a href="#errorsunwrap"><span>#</span></a></h2><p>Go 语言提供了 <code>errors.Unwrap</code> 函数，用于获取被包装（wrapped）的内部错误。如果一个错误没有包装其他错误，<code>errors.Unwrap</code> 会返回 <code>nil</code>。</p><p>我们可以利用这个函数来层层解析被包装的错误，直到找到最原始的那个，例如：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>package</span><span> </span><span>main</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>import</span><span> (</span></div></div><div><div><div>4</div></div><div><span>    </span><span>"</span><span>errors</span><span>"</span></div></div><div><div><div>5</div></div><div><span>    </span><span>"</span><span>fmt</span><span>"</span></div></div><div><div><div>6</div></div><div><span>    </span><span>"</span><span>os</span><span>"</span></div></div><div><div><div>7</div></div><div><span>)</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>originalErr </span><span>:=</span><span> os.ErrNotExist</span></div></div><div><div><div>11</div></div><div><span><span>    </span></span><span>wrappedErr1 </span><span>:=</span><span> fmt.</span><span>Errorf</span><span>(</span><span>"读取配置时出错: </span><span>%w</span><span>"</span><span>, originalErr)</span></div></div><div><div><div>12</div></div><div><span><span>    </span></span><span>wrappedErr2 </span><span>:=</span><span> fmt.</span><span>Errorf</span><span>(</span><span>"启动应用失败: </span><span>%w</span><span>"</span><span>, wrappedErr1)</span></div></div><div><div><div>13</div></div><div>
</div></div><div><div><div>14</div></div><div><span><span>    </span></span><span>err </span><span>:=</span><span> wrappedErr2</span></div></div><div><div><div>15</div></div><div><span>    </span><span>for</span><span> err </span><span>!=</span><span> </span><span>nil</span><span> {</span></div></div><div><div><div>16</div></div><div><span><span>        </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"当前错误:"</span><span>, err)</span></div></div><div><div><div>17</div></div><div><span><span>        </span></span><span>err </span><span>=</span><span> errors.</span><span>Unwrap</span><span>(err)</span></div></div><div><div><div>18</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>19</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>输出：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>当前错误:</span><span> </span><span>启动应用失败:</span><span> </span><span>读取配置时出错:</span><span> </span><span>file</span><span> </span><span>does</span><span> </span><span>not</span><span> </span><span>exist</span></div></div><div><div><div>2</div></div><div><span>当前错误:</span><span> </span><span>读取配置时出错:</span><span> </span><span>file</span><span> </span><span>does</span><span> </span><span>not</span><span> </span><span>exist</span></div></div><div><div><div>3</div></div><div><span>当前错误:</span><span> </span><span>file</span><span> </span><span>does</span><span> </span><span>not</span><span> </span><span>exist</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>errors.Is<a href="#errorsis"><span>#</span></a></h2><p><code>errors.Is</code> 函数用于判断一个错误链（error chain）中是否包含特定的目标错误。它会遍历被包装的错误，检查链上的任何一个错误是否与目标错误匹配。这比简单的 <code>err == targetErr</code> 更强大，因为它能处理错误包装的情况。</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>package</span><span> </span><span>main</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>import</span><span> (</span></div></div><div><div><div>4</div></div><div><span>    </span><span>"</span><span>errors</span><span>"</span></div></div><div><div><div>5</div></div><div><span>    </span><span>"</span><span>fmt</span><span>"</span></div></div><div><div><div>6</div></div><div><span>)</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>var</span><span> ErrDataAccess </span><span>=</span><span> errors.</span><span>New</span><span>(</span><span>"数据访问失败"</span><span>)</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span>func</span><span> </span><span>fetchData</span><span>() </span><span>error</span><span> {</span></div></div><div><div><div>11</div></div><div><span>    </span><span>return</span><span> fmt.</span><span>Errorf</span><span>(</span><span>"数据库连接错误: </span><span>%w</span><span>"</span><span>, ErrDataAccess)</span></div></div><div><div><div>12</div></div><div><span>}</span></div></div><div><div><div>13</div></div><div>
</div></div><div><div><div>14</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>15</div></div><div><span><span>    </span></span><span>err </span><span>:=</span><span> </span><span>fetchData</span><span>()</span></div></div><div><div><div>16</div></div><div>
</div></div><div><div><div>17</div></div><div><span>    </span><span>if</span><span> errors.</span><span>Is</span><span>(err, ErrDataAccess) {</span></div></div><div><div><div>18</div></div><div><span><span>        </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"错误是数据访问类型错误"</span><span>)</span></div></div><div><div><div>19</div></div><div><span><span>    </span></span><span>} </span><span>else</span><span> {</span></div></div><div><div><div>20</div></div><div><span><span>        </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"未知类型错误"</span><span>)</span></div></div><div><div><div>21</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>22</div></div><div>
</div></div><div><div><div>23</div></div><div><span>    </span><span>if</span><span> err </span><span>==</span><span> ErrDataAccess {</span></div></div><div><div><div>24</div></div><div><span><span>        </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"直接比较成功"</span><span>)</span></div></div><div><div><div>25</div></div><div><span><span>    </span></span><span>} </span><span>else</span><span> {</span></div></div><div><div><div>26</div></div><div><span><span>        </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"直接比较失败"</span><span>)</span></div></div><div><div><div>27</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>28</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>输出：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>错误是数据访问类型错误</span></div></div><div><div><div>2</div></div><div><span>直接比较失败</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>errors.As<a href="#errorsas"><span>#</span></a></h2><p><code>errors.As</code> 函数用于检查错误链中是否有特定类型的错误。如果找到，它会将该错误赋值给一个目标变量并返回 <code>true</code>。这类似于类型断言，但同样可以穿透错误包装。</p><p>这在我们使用自定义错误结构体时非常有用，因为它允许我们获取到自定义错误并访问其字段，例如：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>package</span><span> </span><span>main</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>import</span><span> (</span></div></div><div><div><div>4</div></div><div><span>    </span><span>"</span><span>errors</span><span>"</span></div></div><div><div><div>5</div></div><div><span>    </span><span>"</span><span>fmt</span><span>"</span></div></div><div><div><div>6</div></div><div><span>)</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>type</span><span> </span><span>commonError</span><span> </span><span>struct</span><span> {</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>errorCode </span><span>int</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>errorMsg  </span><span>string</span></div></div><div><div><div>11</div></div><div><span>}</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span>func</span><span> (</span><span>ce </span><span>*</span><span>commonError</span><span>) </span><span>Error</span><span>() </span><span>string</span><span> {</span></div></div><div><div><div>14</div></div><div><span>    </span><span>return</span><span> ce.errorMsg</span></div></div><div><div><div>15</div></div><div><span>}</span></div></div><div><div><div>16</div></div><div>
</div></div><div><div><div>17</div></div><div><span>func</span><span> </span><span>doSomething</span><span>() </span><span>error</span><span> {</span></div></div><div><div><div>18</div></div><div><span><span>    </span></span><span>baseErr </span><span>:=</span><span> </span><span>&amp;</span><span>commonError</span><span>{</span></div></div><div><div><div>19</div></div><div><span><span>        </span></span><span>errorCode: </span><span>1001</span><span>,</span></div></div><div><div><div>20</div></div><div><span><span>        </span></span><span>errorMsg:  </span><span>"权限不足"</span><span>,</span></div></div><div><div><div>21</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>22</div></div><div><span>    </span><span>return</span><span> fmt.</span><span>Errorf</span><span>(</span><span>"操作失败: </span><span>%w</span><span>"</span><span>, baseErr)</span></div></div><div><div><div>23</div></div><div><span>}</span></div></div><div><div><div>24</div></div><div>
</div></div><div><div><div>25</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>26</div></div><div><span><span>    </span></span><span>err </span><span>:=</span><span> </span><span>doSomething</span><span>()</span></div></div><div><div><div>27</div></div><div>
</div></div><div><div><div>28</div></div><div><span>    </span><span>var</span><span> ce </span><span>*</span><span>commonError</span></div></div><div><div><div>29</div></div><div><span>    </span><span>if</span><span> errors.</span><span>As</span><span>(err, </span><span>&amp;</span><span>ce) {</span></div></div><div><div><div>30</div></div><div><span><span>        </span></span><span>fmt.</span><span>Printf</span><span>(</span><span>"捕获到自定义错误! 错误码: </span><span>%d</span><span>, 错误信息: </span><span>%s\n</span><span>"</span><span>, ce.errorCode, ce.errorMsg)</span></div></div><div><div><div>31</div></div><div><span><span>    </span></span><span>} </span><span>else</span><span> {</span></div></div><div><div><div>32</div></div><div><span><span>        </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"未捕获到自定义错误"</span><span>)</span></div></div><div><div><div>33</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>34</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>输出：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>捕获到自定义错误!</span><span> </span><span>错误码:</span><span> </span><span>1001,</span><span> </span><span>错误信息:</span><span> </span><span>权限不足</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>defer<a href="#defer"><span>#</span></a></h2><p><code>defer</code> 关键字用于延迟一个函数或方法的执行。被 <code>defer</code> 的函数调用会在其所在的函数执行完毕即将返回之前被执行。<code>defer</code> 通常用于执行清理操作，如关闭文件、释放锁、关闭数据库连接等。</p><p>一个函数中可以有多个 <code>defer</code> 语句，它们的执行顺序是<strong>后进先出</strong>（LIFO, Last-In-First-Out），例如：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>package</span><span> </span><span>main</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>import</span><span> (</span></div></div><div><div><div>4</div></div><div><span>    </span><span>"</span><span>fmt</span><span>"</span></div></div><div><div><div>5</div></div><div><span>    </span><span>"</span><span>os</span><span>"</span></div></div><div><div><div>6</div></div><div><span>)</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>file, err </span><span>:=</span><span> os.</span><span>Create</span><span>(</span><span>"test.txt"</span><span>)</span></div></div><div><div><div>10</div></div><div><span>    </span><span>if</span><span> err </span><span>!=</span><span> </span><span>nil</span><span> {</span></div></div><div><div><div>11</div></div><div><span><span>        </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"创建文件失败:"</span><span>, err)</span></div></div><div><div><div>12</div></div><div><span>        </span><span>return</span></div></div><div><div><div>13</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>14</div></div><div><span>    </span><span>defer</span><span> file.</span><span>Close</span><span>()</span></div></div><div><div><div>15</div></div><div>
</div></div><div><div><div>16</div></div><div><span><span>    </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"defer 示例开始"</span><span>)</span></div></div><div><div><div>17</div></div><div><span>    </span><span>defer</span><span> fmt.</span><span>Println</span><span>(</span><span>"这是第一个 defer (最后执行)"</span><span>)</span></div></div><div><div><div>18</div></div><div><span>    </span><span>defer</span><span> fmt.</span><span>Println</span><span>(</span><span>"这是第二个 defer (先执行)"</span><span>)</span></div></div><div><div><div>19</div></div><div>
</div></div><div><div><div>20</div></div><div><span><span>    </span></span><span>file.</span><span>WriteString</span><span>(</span><span>"Hello, defer!"</span><span>)</span></div></div><div><div><div>21</div></div><div><span><span>    </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"文件写入成功"</span><span>)</span></div></div><div><div><div>22</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>输出：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>defer</span><span> </span><span>示例开始</span></div></div><div><div><div>2</div></div><div><span>文件写入成功</span></div></div><div><div><div>3</div></div><div><span>这是第二个</span><span> </span><span>defer</span><span> (先执行)</span></div></div><div><div><div>4</div></div><div><span>这是第一个</span><span> </span><span>defer</span><span> (最后执行)</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>Panic 异常<a href="#panic-异常"><span>#</span></a></h2><p><code>Panic</code> 是一种 Go 语言内置的函数，用于表示程序遇到了无法处理的、灾难性的错误，导致程序无法继续正常运行。当 <code>panic</code> 被调用时：</p><ol>
<li>程序的正常执行流程会立即停止。</li>
<li>开始执行当前 Goroutine 中所有被延迟（<code>defer</code>）的函数。</li>
<li>执行完所有 <code>defer</code> 后，程序崩溃并打印出 <code>panic</code> 信息和堆栈跟踪。</li>
</ol><p><code>Panic</code> 应该被非常谨慎地使用，它不应用于常规的错误处理。常规错误（如文件未找到、网络超时）应该通过返回 <code>error</code> 值来处理，严重错误例如：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>package</span><span> </span><span>main</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>import</span><span> </span><span>"</span><span>fmt</span><span>"</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>func</span><span> </span><span>riskyOperation</span><span>() {</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"开始执行危险操作..."</span><span>)</span></div></div><div><div><div>7</div></div><div><span>    </span><span>panic</span><span>(</span><span>"灾难发生了!"</span><span>)</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"这行代码永远不会被执行"</span><span>)</span></div></div><div><div><div>9</div></div><div><span>}</span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>12</div></div><div><span><span>    </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"程序开始"</span><span>)</span></div></div><div><div><div>13</div></div><div><span>    </span><span>riskyOperation</span><span>()</span></div></div><div><div><div>14</div></div><div><span><span>    </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"程序结束 (这行也不会被执行)"</span><span>)</span></div></div><div><div><div>15</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>输出：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>程序开始</span></div></div><div><div><div>2</div></div><div><span>开始执行危险操作...</span></div></div><div><div><div>3</div></div><div><span>panic:</span><span> </span><span>灾难发生了!</span></div></div><div><div><div>4</div></div><div><span>...</span><span>(</span><span>堆栈跟踪信息</span><span>)...</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>Recover 捕获 Panic 异常<a href="#recover-捕获-panic-异常"><span>#</span></a></h2><p><code>recover</code> 是一个内置函数，用于重新获得对一个正在 <code>panic</code> 的 Goroutine 的控制权。<strong><code>recover</code> 只有在 <code>defer</code> 函数内部被直接调用时才有效。</strong></p><p>当 <code>defer</code> 函数中的 <code>recover</code> 被调用时：</p><ul>
<li>它会捕获当前的 <code>panic</code> 值。</li>
<li>停止 <code>panic</code> 的继续传播。</li>
<li>函数的执行流程会从 <code>defer</code> 语句所在函数的返回点继续。</li>
</ul><p>这个 <code>panic/recover</code> 机制通常用于防止单个请求处理失败导致整个服务崩溃，或者用于将库的内部 <code>panic</code> 转换为一个 <code>error</code> 返回给调用者，例如：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>package</span><span> </span><span>main</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>import</span><span> </span><span>"</span><span>fmt</span><span>"</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>func</span><span> </span><span>protector</span><span>() {</span></div></div><div><div><div>6</div></div><div><span>    </span><span>defer</span><span> </span><span>func</span><span>() {</span></div></div><div><div><div>7</div></div><div><span><span>        </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"进入 defer..."</span><span>)</span></div></div><div><div><div>8</div></div><div><span>        </span><span>if</span><span> r </span><span>:=</span><span> </span><span>recover</span><span>(); r </span><span>!=</span><span> </span><span>nil</span><span> {</span></div></div><div><div><div>9</div></div><div><span><span>            </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"成功捕获到 panic:"</span><span>, r)</span></div></div><div><div><div>10</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>11</div></div><div><span><span>        </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"defer 执行完毕"</span><span>)</span></div></div><div><div><div>12</div></div><div><span><span>    </span></span><span>}()</span></div></div><div><div><div>13</div></div><div>
</div></div><div><div><div>14</div></div><div><span><span>    </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"执行 protector 函数"</span><span>)</span></div></div><div><div><div>15</div></div><div><span>    </span><span>panic</span><span>(</span><span>"触发一个 panic!"</span><span>)</span></div></div><div><div><div>16</div></div><div><span><span>    </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"这行代码不会执行"</span><span>)</span></div></div><div><div><div>17</div></div><div><span>}</span></div></div><div><div><div>18</div></div><div>
</div></div><div><div><div>19</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>20</div></div><div><span><span>    </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"主函数开始"</span><span>)</span></div></div><div><div><div>21</div></div><div><span>    </span><span>protector</span><span>()</span></div></div><div><div><div>22</div></div><div><span><span>    </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"主函数结束 (因为 panic 被捕获，所以这行会执行)"</span><span>)</span></div></div><div><div><div>23</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>输出：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>主函数开始</span></div></div><div><div><div>2</div></div><div><span>执行</span><span> </span><span>protector</span><span> </span><span>函数</span></div></div><div><div><div>3</div></div><div><span>进入</span><span> </span><span>defer...</span></div></div><div><div><div>4</div></div><div><span>成功捕获到</span><span> </span><span>panic:</span><span> </span><span>触发一个</span><span> </span><span>panic!</span></div></div><div><div><div>5</div></div><div><span>defer</span><span> </span><span>执行完毕</span></div></div><div><div><div>6</div></div><div><span>主函数结束</span><span> (因为 </span><span>panic</span><span> </span><span>被捕获，所以这行会执行</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><code>panic</code> 和 <code>recover</code> 提供了一种处理真正异常情况的机制，但不应滥用它来代替常规的 <code>error</code> 处理。最佳实践是，只在包的边界或者程序的最高层（如 <code>main</code> 函数或 HTTP 服务器的 handler）使用 <code>recover</code> 来防止程序崩溃。</p></section></section>
<section><h1>07 并发基础<a href="#07-并发基础"><span>#</span></a></h1><section><h2>什么是并发<a href="#什么是并发"><span>#</span></a></h2><p>同一时刻做了两件事，在编程中这就是并发，并发可以让你编写的程序，在同一时刻做多几件事情</p></section><section><h2>进程和线程<a href="#进程和线程"><span>#</span></a></h2><p>操作系统会为这个软件创建一个进程，这个进程是该软件的工作空间，它包含了软件运行所需的所有资源</p><p>线程是进程的执行空间，一个进程可以有多个线程，线程被操作系统调度执行</p></section><section><h2>Goroutine 协程<a href="#goroutine-协程"><span>#</span></a></h2><p>Go 语言没有线程的概念，只有协程</p><p>Go 语言的并发是由 Go 自己调度的，自己决定同时执行多少个 Goroutine，什么时候执行几个</p><p>协程使用 <code>go function()</code> 来启动，function 是一个方法或者函数的调用，例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>2</div></div><div><span>    </span><span>go</span><span> fmt.</span><span>Println</span><span>(</span><span>"Zowely"</span><span>)</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"我是 main goroutine"</span><span>)</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>time.</span><span>Sleep</span><span>(time.Second)</span></div></div><div><div><div>5</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>输出：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>我是</span><span> </span><span>main</span><span> </span><span>goroutine</span></div></div><div><div><div>2</div></div><div><span>Zowely</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>Channel 通道<a href="#channel-通道"><span>#</span></a></h2><p>如果启动了多个协程，它们之间可以通过 Channel 通道来进行通信</p><section><h3>声明一个 Channel<a href="#声明一个-channel"><span>#</span></a></h3><p>在 Go 语言中，声明一个通道很简单，使用 make 关键字，例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>ch </span><span>:=</span><span> </span><span>make</span><span>(</span><span>chan</span><span> </span><span>string</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>chan 是一个关键字，表示是 channel 类型，string 表示 channel 里的数据是 string 类型，chan 是一个集合类型</p><p>一个通道的操作只有两种，接受和发送</p><ul>
<li>接收：获取 chan 中的值，操作符为 <code>&lt;-chan</code></li>
<li>发送：向 chan 发送值，把值放在 chan 中，操作符为 <code>chan&lt;-</code></li>
</ul><p>例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>ch </span><span>:=</span><span> </span><span>make</span><span>(</span><span>chan</span><span> </span><span>string</span><span>)</span></div></div><div><div><div>3</div></div><div><span>  </span><span>go</span><span> </span><span>func</span><span>() {</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"Zowely"</span><span>)</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>ch </span><span>&lt;-</span><span> </span><span>"goroutine 完成"</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>}()</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"我是 main goroutine"</span><span>)</span></div></div><div><div><div>8</div></div><div><span><span>  </span></span><span>v </span><span>:=</span><span> </span><span>&lt;-</span><span>ch</span></div></div><div><div><div>9</div></div><div><span><span>  </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"接收到的 chan 中的值为："</span><span>, v)</span></div></div><div><div><div>10</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>输出：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>我是</span><span> </span><span>main</span><span> </span><span>goroutine</span></div></div><div><div><div>2</div></div><div><span>Zowely</span></div></div><div><div><div>3</div></div><div><span>接收到的</span><span> </span><span>chan</span><span> </span><span>中的值为：</span><span> </span><span>goroutine</span><span> </span><span>完成</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>之所以输出顺序是这样的，是因为通过 make 创建的 chan 中没有值，而 maingoroutine 又想从 chan 中获取值获取不到就一直等待，等到另一个 goroutine 向 chan 发送值为止</p></section><section><h3>无缓冲 Channel<a href="#无缓冲-channel"><span>#</span></a></h3><p>容量是 0，不能存储任何数据，所以无缓冲 channel 只起到传输数据的作用，数据并不会在 channel 中做任何停留，也可以称为同步 channel</p></section><section><h3>有缓冲 Channel<a href="#有缓冲-channel"><span>#</span></a></h3><p></p><figure><img src="https://www.lansganbs.cn/images/article/%E7%BC%96%E7%A8%8B%E8%AF%AD%E8%A8%80/GoLang%20%E5%9F%BA%E7%A1%80/%E6%9C%89%E7%BC%93%E5%86%B2Channel.png" alt="alt text" /><figcaption>alt text</figcaption></figure><p></p><ul>
<li>有缓冲 Channel 的内部有一个缓冲队列</li>
<li>发送操作是向队列的尾部插入元素如果队列已满，则阻塞等待，直到另一个 goroutine 执行，接收操作释放队列的空间</li>
<li>接收操作是从队列的头部获取元素并把它从队列中删除如果队列为空，则阻塞等待，直到另一个 goroutine 执行发送操作插入新的元素</li>
</ul><p>例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>cacheCh </span><span>:=</span><span> </span><span>make</span><span>(</span><span>chan</span><span> </span><span>int</span><span>, </span><span>5</span><span>)</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>cacheCh </span><span>&lt;-</span><span> </span><span>1</span></div></div><div><div><div>4</div></div><div><span><span>  </span></span><span>cacheCh </span><span>&lt;-</span><span> </span><span>2</span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>cacheCh </span><span>&lt;-</span><span> </span><span>3</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"cacheCh容量:"</span><span>, </span><span>cap</span><span>(cacheCh), </span><span>"元素个数:"</span><span>, </span><span>len</span><span>(cacheCh))</span></div></div><div><div><div>7</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>输出：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>cacheCh容量:</span><span> </span><span>5</span><span> </span><span>元素个数:</span><span> </span><span>3</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>关闭 Channel<a href="#关闭-channel"><span>#</span></a></h3><p>通道可以被创建，同时可以被关闭，使用 close() 来关闭</p><p>例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>close</span><span>(cacheCh)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>如果一个通道被关闭，就不能再向其中发送数据，如果发送就会引起异常；但是可以接收数据，如果通道里没有数据，那么接收到的数据就是 channel 存储类型的零值</p></section><section><h3>单向 Channel<a href="#单向-channel"><span>#</span></a></h3><p>限制一个 channel 只可以接收但是不能发送，或者限制一个 channel 只能发送但不能接收，这种 channel 称为单向 channel</p><p>在声明单向 Channel 的时候，只需要加上箭头操作符即可，例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>onlySend </span><span>:=</span><span> </span><span>make</span><span>(</span><span>chan&lt;-</span><span> </span><span>int</span><span>, </span><span>5</span><span>)</span></div></div><div><div><div>2</div></div><div><span>onlyReceive </span><span>:=</span><span> </span><span>make</span><span>(</span><span>&lt;-chan</span><span> </span><span>int</span><span>, </span><span>5</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>select + channel 示例<a href="#select--channel-示例"><span>#</span></a></h3><p>启动了 3 个 goroutine 进行下载，并把结果发送到 3 个 channel 中，哪个先下载好，就会使用哪个 channel 的结果</p><p>这时可以使用多路复用，声明如下所示：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>select</span><span> {</span></div></div><div><div><div>2</div></div><div><span>case</span><span> i1 </span><span>=</span><span> </span><span>&lt;-</span><span>c1:</span></div></div><div><div><div>3</div></div><div><span>    </span><span>// todo</span></div></div><div><div><div>4</div></div><div><span>case</span><span> i2 </span><span>=</span><span> </span><span>&lt;-</span><span>c2:</span></div></div><div><div><div>5</div></div><div><span>    </span><span>// todo</span></div></div><div><div><div>6</div></div><div><span>case</span><span> c3 </span><span>&lt;-</span><span> i3:</span></div></div><div><div><div>7</div></div><div><span>    </span><span>// todo</span></div></div><div><div><div>8</div></div><div><span>default</span><span>:</span></div></div><div><div><div>9</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>可以理解为， n 个通道中任意一个通道有数据产生，select 就可以监听到，然后执行对应分支，例如：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>firstCh </span><span>:=</span><span> </span><span>make</span><span>(</span><span>chan</span><span> </span><span>string</span><span>)</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>secondCh </span><span>:=</span><span> </span><span>make</span><span>(</span><span>chan</span><span> </span><span>string</span><span>)</span></div></div><div><div><div>4</div></div><div><span><span>  </span></span><span>thirdCh </span><span>:=</span><span> </span><span>make</span><span>(</span><span>chan</span><span> </span><span>string</span><span>)</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>  </span><span>go</span><span> </span><span>func</span><span>() {</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>firstCh </span><span>&lt;-</span><span> </span><span>downloadFile</span><span>(</span><span>"firstCh"</span><span>)</span></div></div><div><div><div>8</div></div><div><span><span>  </span></span><span>}()</span></div></div><div><div><div>9</div></div><div><span>  </span><span>go</span><span> </span><span>func</span><span>() {</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>secondCh </span><span>&lt;-</span><span> </span><span>downloadFile</span><span>(</span><span>"secondCh"</span><span>)</span></div></div><div><div><div>11</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>12</div></div><div><span>  </span><span>go</span><span> </span><span>func</span><span>() {</span></div></div><div><div><div>13</div></div><div><span><span>    </span></span><span>thirdCh </span><span>&lt;-</span><span> </span><span>downloadFile</span><span>(</span><span>"thirdCh"</span><span>)</span></div></div><div><div><div>14</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>15</div></div><div>
</div></div><div><div><div>16</div></div><div><span>  </span><span>select</span><span> {</span></div></div><div><div><div>17</div></div><div><span>  </span><span>case</span><span> filePath </span><span>:=</span><span> </span><span>&lt;-</span><span>firstCh:</span></div></div><div><div><div>18</div></div><div><span><span>    </span></span><span>fmt.</span><span>Println</span><span>(filePath)</span></div></div><div><div><div>19</div></div><div><span>  </span><span>case</span><span> filePath </span><span>:=</span><span> </span><span>&lt;-</span><span>secondCh:</span></div></div><div><div><div>20</div></div><div><span><span>    </span></span><span>fmt.</span><span>Println</span><span>(filePath)</span></div></div><div><div><div>21</div></div><div><span>  </span><span>case</span><span> filePath </span><span>:=</span><span> </span><span>&lt;-</span><span>thirdCh:</span></div></div><div><div><div>22</div></div><div><span><span>    </span></span><span>fmt.</span><span>Println</span><span>(filePath)</span></div></div><div><div><div>23</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>24</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>提倡通过通信来共享内存，而不是通过共享内存来通信</p></section></section></section>
<section><h1>08 同步原语<a href="#08-同步原语"><span>#</span></a></h1><p>在某些场景下，我们仍然需要通过共享内存的方式来完成并发任务，例如多个 Goroutine 需要同时读写一个共享的变量</p><p>如果不加任何控制，就会产生“竞态条件”（Race Condition），导致程序结果不可预测</p><p>同步原语就是用来解决这类问题的，它们位于 <code>sync</code> 包中，可以帮助我们协调 Goroutine，安全地访问共享资源</p><section><h2>Mutex 互斥锁<a href="#mutex-互斥锁"><span>#</span></a></h2><p>Mutex 是 “Mutual Exclusion”（互斥）的缩写。当一个 Goroutine 获取了锁之后，其他尝试获取该锁的 Goroutine 就会被阻塞，直到锁被释放。这确保了同一时刻只有一个 Goroutine 可以访问被保护的共享资源。</p><p><code>sync.Mutex</code> 提供了两个方法：</p><ul>
<li><code>Lock()</code>：获取锁</li>
<li><code>Unlock()</code>：释放锁</li>
</ul><p>我们来看一个未使用锁导致问题的例子：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>var</span><span> count </span><span>int</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>func</span><span> </span><span>add</span><span>() {</span></div></div><div><div><div>4</div></div><div><span><span>  </span></span><span>count</span><span>++</span></div></div><div><div><div>5</div></div><div><span>}</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>8</div></div><div><span>  </span><span>// 启动 1000 个 goroutine 来增加 count</span></div></div><div><div><div>9</div></div><div><span>  </span><span>for</span><span> i </span><span>:=</span><span> </span><span>0</span><span>; i </span><span>&lt;</span><span> </span><span>1000</span><span>; i</span><span>++</span><span> {</span></div></div><div><div><div>10</div></div><div><span>    </span><span>go</span><span> </span><span>add</span><span>()</span></div></div><div><div><div>11</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>12</div></div><div><span><span>  </span></span><span>time.</span><span>Sleep</span><span>(</span><span>2</span><span> </span><span>*</span><span> time.Second)</span></div></div><div><div><div>13</div></div><div><span><span>  </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"count 的值为:"</span><span>, count)</span></div></div><div><div><div>14</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>输出：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>count</span><span> </span><span>的值为:</span><span> </span><span>941</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>多次运行，你会发现 <code>count</code> 的值基本都不是 1000。这是因为 <code>count++</code> 不是一个原子操作，导致了数据竞争</p><p>现在我们使用 <code>Mutex</code> 来修复它：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>var</span><span> count </span><span>int</span></div></div><div><div><div>2</div></div><div><span>var</span><span> lock </span><span>sync</span><span>.</span><span>Mutex</span><span> </span><span>// 声明一个互斥锁</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>func</span><span> </span><span>addWithLock</span><span>() {</span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>lock.</span><span>Lock</span><span>() </span><span>// 操作前加锁</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>count</span><span>++</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>lock.</span><span>Unlock</span><span>() </span><span>// 操作后解锁</span></div></div><div><div><div>8</div></div><div><span>}</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>11</div></div><div><span>  </span><span>for</span><span> i </span><span>:=</span><span> </span><span>0</span><span>; i </span><span>&lt;</span><span> </span><span>1000</span><span>; i</span><span>++</span><span> {</span></div></div><div><div><div>12</div></div><div><span>    </span><span>go</span><span> </span><span>addWithLock</span><span>()</span></div></div><div><div><div>13</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>14</div></div><div><span><span>  </span></span><span>time.</span><span>Sleep</span><span>(</span><span>2</span><span> </span><span>*</span><span> time.Second)</span></div></div><div><div><div>15</div></div><div><span><span>  </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"加锁后 count 的值为:"</span><span>, count)</span></div></div><div><div><div>16</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>输出：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>加锁后</span><span> </span><span>count</span><span> </span><span>的值为:</span><span> </span><span>1000</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>通过加锁，我们保证了 <code>count++</code> 操作的原子性，得到了正确的结果</p><blockquote><p><strong>注意</strong>：必须在 <code>Lock()</code> 后使用 <code>defer lock.Unlock()</code> 来确保锁一定会被释放，即使函数发生 <code>panic</code></p></blockquote></section><section><h2>RWMutex 读写锁<a href="#rwmutex-读写锁"><span>#</span></a></h2><p><code>sync.RWMutex</code>（读写锁）是对 <code>Mutex</code> 的一种优化。它将访问分为“读操作”和“写操作”</p><ul>
<li>多个 Goroutine 可以同时获取读锁</li>
<li>只有一个 Goroutine 可以获取写锁</li>
<li>当写锁被持有时，所有其他读锁和写锁的获取都会被阻塞</li>
</ul><p>读写锁适用于“读多写少”的场景，可以大大提高并发性能</p><p>它提供了以下方法：</p><ul>
<li><code>RLock()</code> / <code>RUnlock()</code>：获取/释放读锁</li>
<li><code>Lock()</code> / <code>Unlock()</code>：获取/释放写锁</li>
</ul><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>var</span><span> (</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>count   </span><span>int</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>rwLock  </span><span>sync</span><span>.</span><span>RWMutex</span></div></div><div><div><div>4</div></div><div><span>)</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>// 写操作</span></div></div><div><div><div>7</div></div><div><span>func</span><span> </span><span>write</span><span>() {</span></div></div><div><div><div>8</div></div><div><span><span>  </span></span><span>rwLock.</span><span>Lock</span><span>() </span><span>// 加写锁</span></div></div><div><div><div>9</div></div><div><span>  </span><span>defer</span><span> rwLock.</span><span>Unlock</span><span>()</span></div></div><div><div><div>10</div></div><div><span><span>  </span></span><span>count</span><span>++</span></div></div><div><div><div>11</div></div><div><span><span>  </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"正在执行写操作..."</span><span>)</span></div></div><div><div><div>12</div></div><div><span><span>  </span></span><span>time.</span><span>Sleep</span><span>(time.Millisecond </span><span>*</span><span> </span><span>10</span><span>)</span></div></div><div><div><div>13</div></div><div><span>}</span></div></div><div><div><div>14</div></div><div>
</div></div><div><div><div>15</div></div><div><span>// 读操作</span></div></div><div><div><div>16</div></div><div><span>func</span><span> </span><span>read</span><span>(</span><span>i</span><span> </span><span>int</span><span>) {</span></div></div><div><div><div>17</div></div><div><span><span>  </span></span><span>rwLock.</span><span>RLock</span><span>() </span><span>// 加读锁</span></div></div><div><div><div>18</div></div><div><span>  </span><span>defer</span><span> rwLock.</span><span>RUnlock</span><span>()</span></div></div><div><div><div>19</div></div><div><span><span>  </span></span><span>fmt.</span><span>Printf</span><span>(</span><span>"Goroutine </span><span>%d</span><span> 正在读取数据: count = </span><span>%d\n</span><span>"</span><span>, i, count)</span></div></div><div><div><div>20</div></div><div><span><span>  </span></span><span>time.</span><span>Sleep</span><span>(time.Millisecond)</span></div></div><div><div><div>21</div></div><div><span>}</span></div></div><div><div><div>22</div></div><div>
</div></div><div><div><div>23</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>24</div></div><div><span>  </span><span>// 启动一个写 goroutine</span></div></div><div><div><div>25</div></div><div><span>  </span><span>go</span><span> </span><span>write</span><span>()</span></div></div><div><div><div>26</div></div><div>
</div></div><div><div><div>27</div></div><div><span>  </span><span>// 启动多个读 goroutine</span></div></div><div><div><div>28</div></div><div><span>  </span><span>for</span><span> i </span><span>:=</span><span> </span><span>0</span><span>; i </span><span>&lt;</span><span> </span><span>5</span><span>; i</span><span>++</span><span> {</span></div></div><div><div><div>29</div></div><div><span>    </span><span>go</span><span> </span><span>read</span><span>(i)</span></div></div><div><div><div>30</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>31</div></div><div>
</div></div><div><div><div>32</div></div><div><span><span>  </span></span><span>time.</span><span>Sleep</span><span>(time.Second </span><span>*</span><span> </span><span>2</span><span>)</span></div></div><div><div><div>33</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>在上面的例子中，多个 <code>read</code> Goroutine 可以并发执行，而 <code>write</code> Goroutine 会等待所有读操作完成后再执行，执行期间会阻塞其他所有读写操作</p></section><section><h2>WaitGroup 等待组<a href="#waitgroup-等待组"><span>#</span></a></h2><p><code>sync.WaitGroup</code> 用于等待一组 Goroutine 全部完成。主 Goroutine 可以调用 <code>Wait()</code> 方法来阻塞自己，直到所有其他 Goroutine 都完成了任务</p><p>它有三个主要方法：</p><ul>
<li><code>Add(n int)</code>：等待组的计数器加 <code>n</code>，表示有 <code>n</code> 个任务需要等待</li>
<li><code>Done()</code>：计数器减 1，通常在 Goroutine 完成任务时通过 <code>defer</code> 调用。它等价于 <code>Add(-1)</code></li>
<li><code>Wait()</code>：阻塞当前 Goroutine，直到计数器归零</li>
</ul><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> </span><span>worker</span><span>(</span><span>id</span><span> </span><span>int</span><span>, </span><span>wg</span><span> </span><span>*</span><span>sync</span><span>.</span><span>WaitGroup</span><span>) {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>defer</span><span> wg.</span><span>Done</span><span>() </span><span>// 完成后通知 WaitGroup</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span><span>  </span></span><span>fmt.</span><span>Printf</span><span>(</span><span>"工人 </span><span>%d</span><span> 开始工作</span><span>\n</span><span>"</span><span>, id)</span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>time.</span><span>Sleep</span><span>(time.Second)</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>fmt.</span><span>Printf</span><span>(</span><span>"工人 </span><span>%d</span><span> 完成工作</span><span>\n</span><span>"</span><span>, id)</span></div></div><div><div><div>7</div></div><div><span>}</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>10</div></div><div><span>  </span><span>var</span><span> wg </span><span>sync</span><span>.</span><span>WaitGroup</span></div></div><div><div><div>11</div></div><div>
</div></div><div><div><div>12</div></div><div><span>  </span><span>for</span><span> i </span><span>:=</span><span> </span><span>1</span><span>; i </span><span>&lt;=</span><span> </span><span>3</span><span>; i</span><span>++</span><span> {</span></div></div><div><div><div>13</div></div><div><span><span>    </span></span><span>wg.</span><span>Add</span><span>(</span><span>1</span><span>) </span><span>// 每启动一个 goroutine，计数器加 1</span></div></div><div><div><div>14</div></div><div><span>    </span><span>go</span><span> </span><span>worker</span><span>(i, </span><span>&amp;</span><span>wg)</span></div></div><div><div><div>15</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>16</div></div><div>
</div></div><div><div><div>17</div></div><div><span><span>  </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"Main Goroutine 等待所有工人完成工作..."</span><span>)</span></div></div><div><div><div>18</div></div><div><span><span>  </span></span><span>wg.</span><span>Wait</span><span>() </span><span>// 等待所有 goroutine 完成</span></div></div><div><div><div>19</div></div><div><span><span>  </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"所有工人都已完成工作，Main Goroutine 退出"</span><span>)</span></div></div><div><div><div>20</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>输出（不唯一）：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>工人</span><span> </span><span>1</span><span> </span><span>开始工作</span></div></div><div><div><div>2</div></div><div><span>工人</span><span> </span><span>2</span><span> </span><span>开始工作</span></div></div><div><div><div>3</div></div><div><span>Main</span><span> </span><span>Goroutine</span><span> </span><span>等待所有工人完成工作...</span></div></div><div><div><div>4</div></div><div><span>工人</span><span> </span><span>3</span><span> </span><span>开始工作</span></div></div><div><div><div>5</div></div><div><span>工人</span><span> </span><span>1</span><span> </span><span>完成工作</span></div></div><div><div><div>6</div></div><div><span>工人</span><span> </span><span>3</span><span> </span><span>完成工作</span></div></div><div><div><div>7</div></div><div><span>工人</span><span> </span><span>2</span><span> </span><span>完成工作</span></div></div><div><div><div>8</div></div><div><span>所有工人都已完成工作，Main</span><span> </span><span>Goroutine</span><span> </span><span>退出</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>Once<a href="#once"><span>#</span></a></h2><p><code>sync.Once</code> 可以保证某个函数在程序的整个生命周期中只被执行一次，即使在多个 Goroutine 中被并发调用。这对于单例对象的初始化或者只需要执行一次的设置非常有用</p><p>它只有一个方法：</p><ul>
<li><code>Do(f func())</code>：如果 <code>f</code> 从未被调用过，则执行 <code>f</code>。</li>
</ul><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>var</span><span> once </span><span>sync</span><span>.</span><span>Once</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>func</span><span> </span><span>initialize</span><span>() {</span></div></div><div><div><div>4</div></div><div><span><span>  </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"初始化操作只执行一次。"</span><span>)</span></div></div><div><div><div>5</div></div><div><span>}</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>8</div></div><div><span>  </span><span>var</span><span> wg </span><span>sync</span><span>.</span><span>WaitGroup</span></div></div><div><div><div>9</div></div><div><span>  </span><span>for</span><span> i </span><span>:=</span><span> </span><span>0</span><span>; i </span><span>&lt;</span><span> </span><span>10</span><span>; i</span><span>++</span><span> {</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>wg.</span><span>Add</span><span>(</span><span>1</span><span>)</span></div></div><div><div><div>11</div></div><div><span>    </span><span>go</span><span> </span><span>func</span><span>() {</span></div></div><div><div><div>12</div></div><div><span>      </span><span>defer</span><span> wg.</span><span>Done</span><span>()</span></div></div><div><div><div>13</div></div><div><span><span>      </span></span><span>once.</span><span>Do</span><span>(initialize)</span></div></div><div><div><div>14</div></div><div><span><span>    </span></span><span>}()</span></div></div><div><div><div>15</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>16</div></div><div><span><span>  </span></span><span>wg.</span><span>Wait</span><span>()</span></div></div><div><div><div>17</div></div><div><span><span>    </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"所有 Goroutine 执行完毕。"</span><span>)</span></div></div><div><div><div>18</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>输出：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>初始化操作只执行一次。</span></div></div><div><div><div>2</div></div><div><span>所有</span><span> </span><span>Goroutine</span><span> </span><span>执行完毕。</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>无论多少个 Goroutine 调用 <code>once.Do(initialize)</code>，<code>initialize</code> 函数都只会被执行一次</p></section><section><h2>Cond 条件变量<a href="#cond-条件变量"><span>#</span></a></h2><p><code>sync.Cond</code> 用于在 Goroutine 之间同步和通信。它使得一个 Goroutine 可以等待某个条件成立，而另一个 Goroutine 在条件成立时通知等待者</p><p><code>Cond</code> 必须和一个 <code>Locker</code>（通常是 <code>*sync.Mutex</code>）关联使用</p><p>主要方法：</p><ul>
<li><code>Wait()</code>：自动释放持有的锁并阻塞等待。当被唤醒时，它会重新获取锁</li>
<li><code>Signal()</code>：唤醒一个正在等待的 Goroutine</li>
<li><code>Broadcast()</code>：唤醒所有正在等待的 Goroutine</li>
</ul><p>下面是一个简单的生产者-消费者模型示例：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>var</span><span> lock </span><span>sync</span><span>.</span><span>Mutex</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>cond </span><span>:=</span><span> sync.</span><span>NewCond</span><span>(</span><span>&amp;</span><span>lock)</span></div></div><div><div><div>4</div></div><div><span><span>  </span></span><span>queue </span><span>:=</span><span> </span><span>make</span><span>([]</span><span>int</span><span>, </span><span>0</span><span>, </span><span>10</span><span>)</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>  </span><span>// 生产者</span></div></div><div><div><div>7</div></div><div><span>  </span><span>for</span><span> i </span><span>:=</span><span> </span><span>0</span><span>; i </span><span>&lt;</span><span> </span><span>10</span><span>; i</span><span>++</span><span> {</span></div></div><div><div><div>8</div></div><div><span>    </span><span>go</span><span> </span><span>func</span><span>(</span><span>i</span><span> </span><span>int</span><span>) {</span></div></div><div><div><div>9</div></div><div><span><span>      </span></span><span>cond.L.</span><span>Lock</span><span>()</span></div></div><div><div><div>10</div></div><div><span>      </span><span>defer</span><span> cond.L.</span><span>Unlock</span><span>()</span></div></div><div><div><div>11</div></div><div><span><span>      </span></span><span>queue </span><span>=</span><span> </span><span>append</span><span>(queue, i)</span></div></div><div><div><div>12</div></div><div><span><span>      </span></span><span>fmt.</span><span>Printf</span><span>(</span><span>"生产者放入: </span><span>%d\n</span><span>"</span><span>, i)</span></div></div><div><div><div>13</div></div><div><span><span>      </span></span><span>cond.</span><span>Signal</span><span>() </span><span>// 通知一个消费者</span></div></div><div><div><div>14</div></div><div><span><span>    </span></span><span>}(i)</span></div></div><div><div><div>15</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>16</div></div><div>
</div></div><div><div><div>17</div></div><div><span>  </span><span>// 消费者</span></div></div><div><div><div>18</div></div><div><span>  </span><span>for</span><span> i </span><span>:=</span><span> </span><span>0</span><span>; i </span><span>&lt;</span><span> </span><span>10</span><span>; i</span><span>++</span><span> {</span></div></div><div><div><div>19</div></div><div><span>    </span><span>go</span><span> </span><span>func</span><span>(</span><span>i</span><span> </span><span>int</span><span>) {</span></div></div><div><div><div>20</div></div><div><span><span>      </span></span><span>cond.L.</span><span>Lock</span><span>()</span></div></div><div><div><div>21</div></div><div><span>      </span><span>defer</span><span> cond.L.</span><span>Unlock</span><span>()</span></div></div><div><div><div>22</div></div><div><span>      </span><span>// 如果队列为空，则等待</span></div></div><div><div><div>23</div></div><div><span>      </span><span>for</span><span> </span><span>len</span><span>(queue) </span><span>==</span><span> </span><span>0</span><span> {</span></div></div><div><div><div>24</div></div><div><span><span>        </span></span><span>fmt.</span><span>Printf</span><span>(</span><span>"消费者 </span><span>%d</span><span> 等待中...</span><span>\n</span><span>"</span><span>, i)</span></div></div><div><div><div>25</div></div><div><span><span>        </span></span><span>cond.</span><span>Wait</span><span>()</span></div></div><div><div><div>26</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>27</div></div><div><span><span>      </span></span><span>item </span><span>:=</span><span> queue[</span><span>0</span><span>]</span></div></div><div><div><div>28</div></div><div><span><span>      </span></span><span>queue </span><span>=</span><span> queue[</span><span>1</span><span>:]</span></div></div><div><div><div>29</div></div><div><span><span>      </span></span><span>fmt.</span><span>Printf</span><span>(</span><span>"消费者 </span><span>%d</span><span> 取出: </span><span>%d\n</span><span>"</span><span>, i, item)</span></div></div><div><div><div>30</div></div><div><span><span>    </span></span><span>}(i)</span></div></div><div><div><div>31</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>32</div></div><div>
</div></div><div><div><div>33</div></div><div><span><span>  </span></span><span>time.</span><span>Sleep</span><span>(</span><span>2</span><span> </span><span>*</span><span> time.Second)</span></div></div><div><div><div>34</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>在这个例子中，消费者在队列为空时调用 <code>cond.Wait()</code> 等待，生产者在放入数据后调用 <code>cond.Signal()</code> 唤醒一个等待的消费者。<code>for len(queue) == 0</code> 循环是必要的，因为 <code>Wait()</code> 被唤醒时不一定代表条件就满足了（可能被其他 Goroutine 抢先）</p></section></section>
<section><h1>09 Context 上下文<a href="#09-context-上下文"><span>#</span></a></h1><p><code>Context</code> 是 Go 语言中用于在 API 边界和 Goroutine 之间传递请求范围值、取消信号和超时/截止日期的一种标准机制。它对于构建健壮、可控的网络服务和并发程序至关重要</p><p><code>Context</code> 本质上是不可变的，每次派生（如添加超时或值）都会创建一个新的 <code>Context</code> 实例，形成一个树状结构</p><section><h2>Context 接口<a href="#context-接口"><span>#</span></a></h2><p><code>Context</code> 是一个接口类型，只有四个方法：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>type</span><span> </span><span>Context</span><span> </span><span>interface</span><span> {</span></div></div><div><div><div>2</div></div><div><span>    </span><span>// Done 返回一个只读的 channel，当 context 被取消或超时时，该 channel 会被关闭</span></div></div><div><div><div>3</div></div><div><span>    </span><span>Done</span><span>() </span><span>&lt;-chan</span><span> </span><span>struct</span><span>{}</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>    </span><span>// Err 在 Done() channel 关闭后，返回 context 被取消的原因</span></div></div><div><div><div>6</div></div><div><span>    </span><span>// 如果是因取消操作，返回 Canceled；如果是因超时，返回 DeadlineExceeded</span></div></div><div><div><div>7</div></div><div><span>    </span><span>Err</span><span>() </span><span>error</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>    </span><span>// Deadline 返回 context 的截止时间。如果没有设置截止时间，ok 会是 false</span></div></div><div><div><div>10</div></div><div><span>    </span><span>Deadline</span><span>() (</span><span>deadline</span><span> </span><span>time</span><span>.</span><span>Time</span><span>, </span><span>ok</span><span> </span><span>bool</span><span>)</span></div></div><div><div><div>11</div></div><div>
</div></div><div><div><div>12</div></div><div><span>    </span><span>// Value 返回与此 context 关联的键的值，或在没有关联值时返回 nil</span></div></div><div><div><div>13</div></div><div><span>    </span><span>Value</span><span>(</span><span>key</span><span> </span><span>interface</span><span>{}) </span><span>interface</span><span>{}</span></div></div><div><div><div>14</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>创建根 Context　<a href="#创建根-context"><span>#</span></a></h2><p></p><figure><img src="https://www.lansganbs.cn/images/article/%E7%BC%96%E7%A8%8B%E8%AF%AD%E8%A8%80/GoLang%20%E5%9F%BA%E7%A1%80/context%E6%A0%91.png" alt="alt text" /><figcaption>alt text</figcaption></figure><p></p><p>所有的 <code>Context</code> 都派生自一个根 <code>Context</code>。Go 提供了两个函数来创建根 <code>Context</code>：</p><ul>
<li><code>context.Background()</code>：通常用于 <code>main</code> 函数、初始化和测试中，作为最顶层的 <code>Context</code>，它永远不会被取消</li>
<li><code>context.TODO()</code>：当你不确定要使用哪个 <code>Context</code> 或者函数将来可能需要接收一个 <code>Context</code> 时，可以使用它。它本质上是一个占位符</li>
</ul><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>// 示例：创建根 Context</span></div></div><div><div><div>2</div></div><div><span>bgCtx </span><span>:=</span><span> context.</span><span>Background</span><span>()</span></div></div><div><div><div>3</div></div><div><span>todoCtx </span><span>:=</span><span> context.</span><span>TODO</span><span>()</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>fmt.</span><span>Printf</span><span>(</span><span>"background context: </span><span>%v\n</span><span>"</span><span>, bgCtx)</span></div></div><div><div><div>6</div></div><div><span>fmt.</span><span>Printf</span><span>(</span><span>"todo context: </span><span>%v\n</span><span>"</span><span>, todoCtx)</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>WithCancel：可取消的 Context<a href="#withcancel可取消的-context"><span>#</span></a></h2><p><code>context.WithCancel</code> 函数创建一个可被显式取消的 <code>Context</code></p><ul>
<li><strong>函数签名</strong>：<code>func WithCancel(parent Context) (ctx Context, cancel CancelFunc)</code></li>
<li>它返回一个新的 <code>Context</code> 和一个 <code>CancelFunc</code> 函数。调用这个 <code>cancel</code> 函数会向所有派生自此 <code>Context</code> 的 Goroutine 发送取消信号</li>
<li><strong>重要</strong>：必须调用 <code>cancel</code> 函数以释放资源，通常使用 <code>defer</code></li>
</ul><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>package</span><span> </span><span>main</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>import</span><span> (</span></div></div><div><div><div>4</div></div><div><span>    </span><span>"</span><span>context</span><span>"</span></div></div><div><div><div>5</div></div><div><span>    </span><span>"</span><span>fmt</span><span>"</span></div></div><div><div><div>6</div></div><div><span>    </span><span>"</span><span>time</span><span>"</span></div></div><div><div><div>7</div></div><div><span>)</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>func</span><span> </span><span>worker</span><span>(</span><span>ctx</span><span> </span><span>context</span><span>.</span><span>Context</span><span>, </span><span>name</span><span> </span><span>string</span><span>) {</span></div></div><div><div><div>10</div></div><div><span>    </span><span>for</span><span> {</span></div></div><div><div><div>11</div></div><div><span>        </span><span>select</span><span> {</span></div></div><div><div><div>12</div></div><div><span>        </span><span>case</span><span> </span><span>&lt;-</span><span>ctx.</span><span>Done</span><span>(): </span><span>// 监听取消信号</span></div></div><div><div><div>13</div></div><div><span><span>            </span></span><span>fmt.</span><span>Printf</span><span>(</span><span>"</span><span>%s</span><span> 收到取消信号，停止工作。</span><span>\n</span><span>"</span><span>, name)</span></div></div><div><div><div>14</div></div><div><span>            </span><span>return</span></div></div><div><div><div>15</div></div><div><span>        </span><span>default</span><span>:</span></div></div><div><div><div>16</div></div><div><span><span>            </span></span><span>fmt.</span><span>Printf</span><span>(</span><span>"</span><span>%s</span><span> 正在工作中...</span><span>\n</span><span>"</span><span>, name)</span></div></div><div><div><div>17</div></div><div><span><span>            </span></span><span>time.</span><span>Sleep</span><span>(</span><span>500</span><span> </span><span>*</span><span> time.Millisecond)</span></div></div><div><div><div>18</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>19</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>20</div></div><div><span>}</span></div></div><div><div><div>21</div></div><div>
</div></div><div><div><div>22</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>23</div></div><div><span>    </span><span>// 创建一个可取消的 context</span></div></div><div><div><div>24</div></div><div><span><span>    </span></span><span>ctx, cancel </span><span>:=</span><span> context.</span><span>WithCancel</span><span>(context.</span><span>Background</span><span>())</span></div></div><div><div><div>25</div></div><div>
</div></div><div><div><div>26</div></div><div><span>    </span><span>// 启动一个 worker goroutine</span></div></div><div><div><div>27</div></div><div><span>    </span><span>go</span><span> </span><span>worker</span><span>(ctx, </span><span>"Worker-1"</span><span>)</span></div></div><div><div><div>28</div></div><div>
</div></div><div><div><div>29</div></div><div><span>    </span><span>// 让 worker 运行 2 秒</span></div></div><div><div><div>30</div></div><div><span><span>    </span></span><span>time.</span><span>Sleep</span><span>(</span><span>2</span><span> </span><span>*</span><span> time.Second)</span></div></div><div><div><div>31</div></div><div>
</div></div><div><div><div>32</div></div><div><span>    </span><span>// 发送取消信号</span></div></div><div><div><div>33</div></div><div><span><span>    </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"Main Goroutine: 发送取消信号！"</span><span>)</span></div></div><div><div><div>34</div></div><div><span>    </span><span>cancel</span><span>()</span></div></div><div><div><div>35</div></div><div>
</div></div><div><div><div>36</div></div><div><span>    </span><span>// 等待一小段时间，确保 worker 收到信号并退出</span></div></div><div><div><div>37</div></div><div><span><span>    </span></span><span>time.</span><span>Sleep</span><span>(</span><span>1</span><span> </span><span>*</span><span> time.Second)</span></div></div><div><div><div>38</div></div><div><span><span>    </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"Main Goroutine: 退出。"</span><span>)</span></div></div><div><div><div>39</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p><strong>输出：</strong></p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>Worker-1</span><span> </span><span>正在工作中...</span></div></div><div><div><div>2</div></div><div><span>Worker-1</span><span> </span><span>正在工作中...</span></div></div><div><div><div>3</div></div><div><span>Worker-1</span><span> </span><span>正在工作中...</span></div></div><div><div><div>4</div></div><div><span>Worker-1</span><span> </span><span>正在工作中...</span></div></div><div><div><div>5</div></div><div><span>Main</span><span> </span><span>Goroutine:</span><span> </span><span>发送取消信号！</span></div></div><div><div><div>6</div></div><div><span>Worker-1</span><span> </span><span>收到取消信号，停止工作。</span></div></div><div><div><div>7</div></div><div><span>Main</span><span> </span><span>Goroutine:</span><span> </span><span>退出。</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>WithTimeout：定时取消的 Context / WithDeadline：带超时的 Context<a href="#withtimeout定时取消的-context--withdeadline带超时的-context"><span>#</span></a></h2><p><code>WithTimeout</code> 和 <code>WithDeadline</code> 用于创建一个在指定时间后会自动取消的 <code>Context</code></p><ul>
<li><code>WithTimeout(parent Context, timeout time.Duration)</code>：在 <code>timeout</code> 之后自动取消</li>
<li><code>WithDeadline(parent Context, d time.Time)</code>：在绝对时间 <code>d</code> 到达时自动取消</li>
</ul><p>这对于控制数据库查询、API 调用等有时间限制的操作非常有用</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>package</span><span> </span><span>main</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>import</span><span> (</span></div></div><div><div><div>4</div></div><div><span>    </span><span>"</span><span>context</span><span>"</span></div></div><div><div><div>5</div></div><div><span>    </span><span>"</span><span>fmt</span><span>"</span></div></div><div><div><div>6</div></div><div><span>    </span><span>"</span><span>time</span><span>"</span></div></div><div><div><div>7</div></div><div><span>)</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>func</span><span> </span><span>longRunningTask</span><span>(</span><span>ctx</span><span> </span><span>context</span><span>.</span><span>Context</span><span>) {</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"任务开始..."</span><span>)</span></div></div><div><div><div>11</div></div><div><span>    </span><span>select</span><span> {</span></div></div><div><div><div>12</div></div><div><span>    </span><span>case</span><span> </span><span>&lt;-</span><span>time.</span><span>After</span><span>(</span><span>3</span><span> </span><span>*</span><span> time.Second): </span><span>// 模拟一个需要 3 秒才能完成的任务</span></div></div><div><div><div>13</div></div><div><span><span>        </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"任务正常完成。"</span><span>)</span></div></div><div><div><div>14</div></div><div><span>    </span><span>case</span><span> </span><span>&lt;-</span><span>ctx.</span><span>Done</span><span>(): </span><span>// 监听超时/取消信号</span></div></div><div><div><div>15</div></div><div><span><span>        </span></span><span>fmt.</span><span>Printf</span><span>(</span><span>"任务被中断: </span><span>%v\n</span><span>"</span><span>, ctx.</span><span>Err</span><span>())</span></div></div><div><div><div>16</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>17</div></div><div><span>}</span></div></div><div><div><div>18</div></div><div>
</div></div><div><div><div>19</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>20</div></div><div><span>    </span><span>// 创建一个 2 秒后超时的 context</span></div></div><div><div><div>21</div></div><div><span><span>    </span></span><span>ctx, cancel </span><span>:=</span><span> context.</span><span>WithTimeout</span><span>(context.</span><span>Background</span><span>(), </span><span>2</span><span>*</span><span>time.Second)</span></div></div><div><div><div>22</div></div><div><span>    </span><span>defer</span><span> </span><span>cancel</span><span>() </span><span>// 及时释放资源</span></div></div><div><div><div>23</div></div><div>
</div></div><div><div><div>24</div></div><div><span>    </span><span>longRunningTask</span><span>(ctx)</span></div></div><div><div><div>25</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p><strong>输出：</strong></p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>任务开始...</span></div></div><div><div><div>2</div></div><div><span>任务被中断:</span><span> </span><span>context</span><span> </span><span>deadline</span><span> </span><span>exceeded</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>WithValue：传递请求范围的值<a href="#withvalue传递请求范围的值"><span>#</span></a></h2><p><code>WithValue</code> 用于在 <code>Context</code> 中附加键值对数据，这些数据可以在整个调用链中被传递和访问</p><ul>
<li><strong>适用场景</strong>：传递请求 ID、用户身份信息、追踪 ID 等</li>
<li><strong>注意</strong>：<code>WithValue</code> 应谨慎使用。不要用它来传递函数的可选参数，这会使代码可读性变差。其 <code>key</code> 应该使用自定义的、不可导出的类型，以避免键冲突</li>
</ul><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>package</span><span> </span><span>main</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>import</span><span> (</span></div></div><div><div><div>4</div></div><div><span>    </span><span>"</span><span>context</span><span>"</span></div></div><div><div><div>5</div></div><div><span>    </span><span>"</span><span>fmt</span><span>"</span></div></div><div><div><div>6</div></div><div><span>)</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>// 使用自定义类型作为 key，防止命名冲突</span></div></div><div><div><div>9</div></div><div><span>type</span><span> </span><span>contextKey</span><span> </span><span>string</span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span>const</span><span> </span><span>userIDKey</span><span> </span><span>=</span><span> </span><span>contextKey</span><span>(</span><span>"userID"</span><span>)</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span>func</span><span> </span><span>processRequest</span><span>(</span><span>ctx</span><span> </span><span>context</span><span>.</span><span>Context</span><span>) {</span></div></div><div><div><div>14</div></div><div><span>    </span><span>// 从 context 中获取值</span></div></div><div><div><div>15</div></div><div><span><span>    </span></span><span>userID </span><span>:=</span><span> ctx.</span><span>Value</span><span>(userIDKey)</span></div></div><div><div><div>16</div></div><div><span>    </span><span>if</span><span> userID </span><span>!=</span><span> </span><span>nil</span><span> {</span></div></div><div><div><div>17</div></div><div><span><span>        </span></span><span>fmt.</span><span>Printf</span><span>(</span><span>"处理请求，用户 ID 是: </span><span>%d\n</span><span>"</span><span>, userID)</span></div></div><div><div><div>18</div></div><div><span><span>    </span></span><span>} </span><span>else</span><span> {</span></div></div><div><div><div>19</div></div><div><span><span>        </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"处理请求，但未找到用户 ID。"</span><span>)</span></div></div><div><div><div>20</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>21</div></div><div><span>}</span></div></div><div><div><div>22</div></div><div>
</div></div><div><div><div>23</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>24</div></div><div><span>    </span><span>// 创建一个包含 userID 的 context</span></div></div><div><div><div>25</div></div><div><span><span>    </span></span><span>ctx </span><span>:=</span><span> context.</span><span>WithValue</span><span>(context.</span><span>Background</span><span>(), userIDKey, </span><span>12345</span><span>)</span></div></div><div><div><div>26</div></div><div>
</div></div><div><div><div>27</div></div><div><span>    </span><span>processRequest</span><span>(ctx)</span></div></div><div><div><div>28</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p><strong>输出：</strong></p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>处理请求，用户</span><span> </span><span>ID</span><span> </span><span>是:</span><span> </span><span>12345</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section>
<section><h1>10 并发模式<a href="#10-并发模式"><span>#</span></a></h1><p>Go 的并发以 Goroutine 与 Channel 为核心，结合 <code>context</code>、<code>sync</code>、<code>time</code>、<code>errgroup</code> 等标准库，可以拼装出稳定、可控、可观测的并发方案</p><section><h2>for select<a href="#for-select"><span>#</span></a></h2><p><strong>场景</strong>：持续从多个输入通道读取数据，或同时监听数据与退出信号</p><p><strong>要点</strong></p><ul>
<li>用 <code>for { select { ... } }</code> 持续处理</li>
<li>用 <code>default</code> 分支可避免阻塞，但可能忙等；多数情况下不需要 <code>default</code></li>
<li>当任一输入通道被关闭时，按需退出或忽略该分支</li>
</ul><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>package</span><span> </span><span>main</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>import</span><span> (</span></div></div><div><div><div>4</div></div><div><span>  </span><span>"</span><span>context</span><span>"</span></div></div><div><div><div>5</div></div><div><span>  </span><span>"</span><span>fmt</span><span>"</span></div></div><div><div><div>6</div></div><div><span>  </span><span>"</span><span>time</span><span>"</span></div></div><div><div><div>7</div></div><div><span>)</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>10</div></div><div><span><span>  </span></span><span>data </span><span>:=</span><span> </span><span>make</span><span>(</span><span>chan</span><span> </span><span>int</span><span>)</span></div></div><div><div><div>11</div></div><div><span><span>  </span></span><span>quit </span><span>:=</span><span> </span><span>make</span><span>(</span><span>chan</span><span> </span><span>struct</span><span>{})</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span>  </span><span>// 生产者</span></div></div><div><div><div>14</div></div><div><span>  </span><span>go</span><span> </span><span>func</span><span>() {</span></div></div><div><div><div>15</div></div><div><span>    </span><span>for</span><span> i </span><span>:=</span><span> </span><span>0</span><span>; i </span><span>&lt;</span><span> </span><span>5</span><span>; i</span><span>++</span><span> {</span></div></div><div><div><div>16</div></div><div><span><span>      </span></span><span>time.</span><span>Sleep</span><span>(</span><span>120</span><span> </span><span>*</span><span> time.Millisecond)</span></div></div><div><div><div>17</div></div><div><span><span>      </span></span><span>data </span><span>&lt;-</span><span> i</span></div></div><div><div><div>18</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>19</div></div><div><span>    </span><span>close</span><span>(data)</span></div></div><div><div><div>20</div></div><div><span><span>  </span></span><span>}()</span></div></div><div><div><div>21</div></div><div>
</div></div><div><div><div>22</div></div><div><span>  </span><span>// 取消者</span></div></div><div><div><div>23</div></div><div><span>  </span><span>go</span><span> </span><span>func</span><span>() {</span></div></div><div><div><div>24</div></div><div><span><span>    </span></span><span>time.</span><span>Sleep</span><span>(</span><span>700</span><span> </span><span>*</span><span> time.Millisecond)</span></div></div><div><div><div>25</div></div><div><span>    </span><span>close</span><span>(quit)</span></div></div><div><div><div>26</div></div><div><span><span>  </span></span><span>}()</span></div></div><div><div><div>27</div></div><div>
</div></div><div><div><div>28</div></div><div><span>  </span><span>for</span><span> {</span></div></div><div><div><div>29</div></div><div><span>    </span><span>select</span><span> {</span></div></div><div><div><div>30</div></div><div><span>    </span><span>case</span><span> v, ok </span><span>:=</span><span> </span><span>&lt;-</span><span>data:</span></div></div><div><div><div>31</div></div><div><span>      </span><span>if</span><span> </span><span>!</span><span>ok {</span></div></div><div><div><div>32</div></div><div><span><span>        </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"data closed"</span><span>)</span></div></div><div><div><div>33</div></div><div><span>        </span><span>return</span></div></div><div><div><div>34</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>35</div></div><div><span><span>      </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"recv:"</span><span>, v)</span></div></div><div><div><div>36</div></div><div><span>    </span><span>case</span><span> </span><span>&lt;-</span><span>quit:</span></div></div><div><div><div>37</div></div><div><span><span>      </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"quit"</span><span>)</span></div></div><div><div><div>38</div></div><div><span>      </span><span>return</span></div></div><div><div><div>39</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>40</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>41</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>输出：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>recv:</span><span> </span><span>0</span></div></div><div><div><div>2</div></div><div><span>recv:</span><span> </span><span>1</span></div></div><div><div><div>3</div></div><div><span>recv:</span><span> </span><span>2</span></div></div><div><div><div>4</div></div><div><span>recv:</span><span> </span><span>3</span></div></div><div><div><div>5</div></div><div><span>quit</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>select timeout 模式<a href="#select-timeout-模式"><span>#</span></a></h2><blockquote><p>如果可以使用 select timeout 模式进行超时取消，应该优先使用</p></blockquote><p><strong>场景</strong>：等待某个通道事件，但又不想无限期阻塞；超时则采取降级或返回错误</p><p><strong>要点</strong></p><ul>
<li><code>case &lt;-time.After(d)</code> 是最轻量的超时手段</li>
<li>频繁调用时建议预创建 <code>time.Timer</code> 循环复用，避免大量临时对象</li>
<li>业务上层仍建议用 <code>context</code> 做总时限控制</li>
</ul><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>package</span><span> </span><span>main</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>import</span><span> (</span></div></div><div><div><div>4</div></div><div><span>  </span><span>"</span><span>errors</span><span>"</span></div></div><div><div><div>5</div></div><div><span>  </span><span>"</span><span>fmt</span><span>"</span></div></div><div><div><div>6</div></div><div><span>  </span><span>"</span><span>time</span><span>"</span></div></div><div><div><div>7</div></div><div><span>)</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>func</span><span> </span><span>recvWithTimeout</span><span>(</span><span>in</span><span> </span><span>&lt;-chan</span><span> </span><span>int</span><span>, </span><span>d</span><span> </span><span>time</span><span>.</span><span>Duration</span><span>) (</span><span>int</span><span>, </span><span>error</span><span>) {</span></div></div><div><div><div>10</div></div><div><span>  </span><span>select</span><span> {</span></div></div><div><div><div>11</div></div><div><span>  </span><span>case</span><span> v </span><span>:=</span><span> </span><span>&lt;-</span><span>in:</span></div></div><div><div><div>12</div></div><div><span>    </span><span>return</span><span> v, </span><span>nil</span></div></div><div><div><div>13</div></div><div><span>  </span><span>case</span><span> </span><span>&lt;-</span><span>time.</span><span>After</span><span>(d):</span></div></div><div><div><div>14</div></div><div><span>    </span><span>return</span><span> </span><span>0</span><span>, errors.</span><span>New</span><span>(</span><span>"timeout"</span><span>)</span></div></div><div><div><div>15</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>16</div></div><div><span>}</span></div></div><div><div><div>17</div></div><div>
</div></div><div><div><div>18</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>19</div></div><div><span><span>  </span></span><span>ch </span><span>:=</span><span> </span><span>make</span><span>(</span><span>chan</span><span> </span><span>int</span><span>)</span></div></div><div><div><div>20</div></div><div>
</div></div><div><div><div>21</div></div><div><span>  </span><span>// 异步晚点送达</span></div></div><div><div><div>22</div></div><div><span>  </span><span>go</span><span> </span><span>func</span><span>() {</span></div></div><div><div><div>23</div></div><div><span><span>    </span></span><span>time.</span><span>Sleep</span><span>(</span><span>300</span><span> </span><span>*</span><span> time.Millisecond)</span></div></div><div><div><div>24</div></div><div><span><span>    </span></span><span>ch </span><span>&lt;-</span><span> </span><span>42</span></div></div><div><div><div>25</div></div><div><span><span>  </span></span><span>}()</span></div></div><div><div><div>26</div></div><div>
</div></div><div><div><div>27</div></div><div><span>  </span><span>if</span><span> v, err </span><span>:=</span><span> </span><span>recvWithTimeout</span><span>(ch, </span><span>100</span><span>*</span><span>time.Millisecond); err </span><span>!=</span><span> </span><span>nil</span><span> {</span></div></div><div><div><div>28</div></div><div><span><span>    </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"first:"</span><span>, err)</span></div></div><div><div><div>29</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>30</div></div><div><span>  </span><span>if</span><span> v, err </span><span>:=</span><span> </span><span>recvWithTimeout</span><span>(ch, </span><span>500</span><span>*</span><span>time.Millisecond); err </span><span>==</span><span> </span><span>nil</span><span> {</span></div></div><div><div><div>31</div></div><div><span><span>    </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"second:"</span><span>, v)</span></div></div><div><div><div>32</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>33</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>输出：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>first:</span><span> </span><span>timeout</span></div></div><div><div><div>2</div></div><div><span>second:</span><span> </span><span>42</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>Pipeline 模式<a href="#pipeline-模式"><span>#</span></a></h2><p>Pipeline 模式模拟现实流水线：每道工序用一个函数封装，通过 channel 连接，最终由组织者把工序串起来</p><p>以打包手机为例，假设分为三步：采购-&gt;组装-&gt;打包，例如：</p><ul>
<li>流水线由一道道工序构成，每道工序通过 channel 把数据传递到下一个工序每道工序一般会对应一个函数，函数里有协程和 channel</li>
<li>协程一般用于处理数据并把它放入 channel 中整个函数会返回这个 channel 以供下一道工序使用</li>
<li>最终要有一个组织者（示例中的main函数）把这些工序串起来</li>
</ul><p><strong>要点</strong></p><ul>
<li>每个阶段函数<strong>只关闭自己创建的输出通道</strong></li>
<li>阶段内部用 <code>for-range</code> 消费上游通道</li>
<li>任何阶段出错/取消应快速退出并向下游传播收敛</li>
</ul><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>package</span><span> </span><span>main</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>import</span><span> (</span></div></div><div><div><div>4</div></div><div><span>  </span><span>"</span><span>context</span><span>"</span></div></div><div><div><div>5</div></div><div><span>  </span><span>"</span><span>fmt</span><span>"</span></div></div><div><div><div>6</div></div><div><span>  </span><span>"</span><span>strings</span><span>"</span></div></div><div><div><div>7</div></div><div><span>  </span><span>"</span><span>time</span><span>"</span></div></div><div><div><div>8</div></div><div><span>)</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span>func</span><span> </span><span>purchase</span><span>(</span><span>ctx</span><span> </span><span>context</span><span>.</span><span>Context</span><span>, </span><span>parts</span><span> []</span><span>string</span><span>) </span><span>&lt;-chan</span><span> </span><span>string</span><span> {</span></div></div><div><div><div>11</div></div><div><span><span>  </span></span><span>out </span><span>:=</span><span> </span><span>make</span><span>(</span><span>chan</span><span> </span><span>string</span><span>)</span></div></div><div><div><div>12</div></div><div><span>  </span><span>go</span><span> </span><span>func</span><span>() {</span></div></div><div><div><div>13</div></div><div><span>    </span><span>defer</span><span> </span><span>close</span><span>(out)</span></div></div><div><div><div>14</div></div><div><span>    </span><span>for</span><span> _, p </span><span>:=</span><span> </span><span>range</span><span> parts {</span></div></div><div><div><div>15</div></div><div><span>      </span><span>select</span><span> {</span></div></div><div><div><div>16</div></div><div><span>      </span><span>case</span><span> </span><span>&lt;-</span><span>ctx.</span><span>Done</span><span>():</span></div></div><div><div><div>17</div></div><div><span>        </span><span>return</span></div></div><div><div><div>18</div></div><div><span>      </span><span>case</span><span> out </span><span>&lt;-</span><span> </span><span>"raw:"</span><span> </span><span>+</span><span> p:</span></div></div><div><div><div>19</div></div><div><span><span>        </span></span><span>time.</span><span>Sleep</span><span>(</span><span>50</span><span> </span><span>*</span><span> time.Millisecond) </span><span>// 采购耗时</span></div></div><div><div><div>20</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>21</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>22</div></div><div><span><span>  </span></span><span>}()</span></div></div><div><div><div>23</div></div><div><span>  </span><span>return</span><span> out</span></div></div><div><div><div>24</div></div><div><span>}</span></div></div><div><div><div>25</div></div><div>
</div></div><div><div><div>26</div></div><div><span>func</span><span> </span><span>assemble</span><span>(</span><span>ctx</span><span> </span><span>context</span><span>.</span><span>Context</span><span>, </span><span>in</span><span> </span><span>&lt;-chan</span><span> </span><span>string</span><span>) </span><span>&lt;-chan</span><span> </span><span>string</span><span> {</span></div></div><div><div><div>27</div></div><div><span><span>  </span></span><span>out </span><span>:=</span><span> </span><span>make</span><span>(</span><span>chan</span><span> </span><span>string</span><span>)</span></div></div><div><div><div>28</div></div><div><span>  </span><span>go</span><span> </span><span>func</span><span>() {</span></div></div><div><div><div>29</div></div><div><span>    </span><span>defer</span><span> </span><span>close</span><span>(out)</span></div></div><div><div><div>30</div></div><div><span>    </span><span>for</span><span> v </span><span>:=</span><span> </span><span>range</span><span> in {</span></div></div><div><div><div>31</div></div><div><span>      </span><span>select</span><span> {</span></div></div><div><div><div>32</div></div><div><span>      </span><span>case</span><span> </span><span>&lt;-</span><span>ctx.</span><span>Done</span><span>():</span></div></div><div><div><div>33</div></div><div><span>        </span><span>return</span></div></div><div><div><div>34</div></div><div><span>      </span><span>case</span><span> out </span><span>&lt;-</span><span> strings.</span><span>ReplaceAll</span><span>(v, </span><span>"raw:"</span><span>, </span><span>"assembled:"</span><span>):</span></div></div><div><div><div>35</div></div><div><span><span>        </span></span><span>time.</span><span>Sleep</span><span>(</span><span>80</span><span> </span><span>*</span><span> time.Millisecond) </span><span>// 组装耗时</span></div></div><div><div><div>36</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>37</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>38</div></div><div><span><span>  </span></span><span>}()</span></div></div><div><div><div>39</div></div><div><span>  </span><span>return</span><span> out</span></div></div><div><div><div>40</div></div><div><span>}</span></div></div><div><div><div>41</div></div><div>
</div></div><div><div><div>42</div></div><div><span>func</span><span> </span><span>pack</span><span>(</span><span>ctx</span><span> </span><span>context</span><span>.</span><span>Context</span><span>, </span><span>in</span><span> </span><span>&lt;-chan</span><span> </span><span>string</span><span>) </span><span>&lt;-chan</span><span> </span><span>string</span><span> {</span></div></div><div><div><div>43</div></div><div><span><span>  </span></span><span>out </span><span>:=</span><span> </span><span>make</span><span>(</span><span>chan</span><span> </span><span>string</span><span>)</span></div></div><div><div><div>44</div></div><div><span>  </span><span>go</span><span> </span><span>func</span><span>() {</span></div></div><div><div><div>45</div></div><div><span>    </span><span>defer</span><span> </span><span>close</span><span>(out)</span></div></div><div><div><div>46</div></div><div><span>    </span><span>for</span><span> v </span><span>:=</span><span> </span><span>range</span><span> in {</span></div></div><div><div><div>47</div></div><div><span>      </span><span>select</span><span> {</span></div></div><div><div><div>48</div></div><div><span>      </span><span>case</span><span> </span><span>&lt;-</span><span>ctx.</span><span>Done</span><span>():</span></div></div><div><div><div>49</div></div><div><span>        </span><span>return</span></div></div><div><div><div>50</div></div><div><span>      </span><span>case</span><span> out </span><span>&lt;-</span><span> strings.</span><span>ReplaceAll</span><span>(v, </span><span>"assembled:"</span><span>, </span><span>"packed:"</span><span>):</span></div></div><div><div><div>51</div></div><div><span><span>        </span></span><span>time.</span><span>Sleep</span><span>(</span><span>40</span><span> </span><span>*</span><span> time.Millisecond) </span><span>// 打包耗时</span></div></div><div><div><div>52</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>53</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>54</div></div><div><span><span>  </span></span><span>}()</span></div></div><div><div><div>55</div></div><div><span>  </span><span>return</span><span> out</span></div></div><div><div><div>56</div></div><div><span>}</span></div></div><div><div><div>57</div></div><div>
</div></div><div><div><div>58</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>59</div></div><div><span><span>  </span></span><span>ctx </span><span>:=</span><span> context.</span><span>Background</span><span>()</span></div></div><div><div><div>60</div></div><div><span><span>  </span></span><span>parts </span><span>:=</span><span> []</span><span>string</span><span>{</span><span>"phone-A"</span><span>, </span><span>"phone-B"</span><span>, </span><span>"phone-C"</span><span>}</span></div></div><div><div><div>61</div></div><div>
</div></div><div><div><div>62</div></div><div><span><span>  </span></span><span>stage1 </span><span>:=</span><span> </span><span>purchase</span><span>(ctx, parts)</span></div></div><div><div><div>63</div></div><div><span><span>  </span></span><span>stage2 </span><span>:=</span><span> </span><span>assemble</span><span>(ctx, stage1)</span></div></div><div><div><div>64</div></div><div><span><span>  </span></span><span>stage3 </span><span>:=</span><span> </span><span>pack</span><span>(ctx, stage2)</span></div></div><div><div><div>65</div></div><div>
</div></div><div><div><div>66</div></div><div><span>  </span><span>for</span><span> v </span><span>:=</span><span> </span><span>range</span><span> stage3 {</span></div></div><div><div><div>67</div></div><div><span><span>    </span></span><span>fmt.</span><span>Println</span><span>(v)</span></div></div><div><div><div>68</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>69</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>输出：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>packed:phone-A</span></div></div><div><div><div>2</div></div><div><span>packed:phone-B</span></div></div><div><div><div>3</div></div><div><span>packed:phone-C</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>扇出和扇入模式<a href="#扇出和扇入模式"><span>#</span></a></h2><p>当某个阶段处理较慢，组织者可以“扇出”多个并行工人处理同类任务，并在末端“扇入”汇聚结果以提升吞吐</p><p>例如刚才的组装手机的例子中：经过一段时间的运转，组织者发现产能提不上去，工序2过慢，导致上游工序1配件采购速度不得不降下来，下游工序3没太多事做，不得不闲下来。于是可以采用扇出和扇入模式，图示如下：</p><p></p><figure><img src="https://www.lansganbs.cn/images/article/%E7%BC%96%E7%A8%8B%E8%AF%AD%E8%A8%80/GoLang%20%E5%9F%BA%E7%A1%80/%E6%89%87%E5%85%A5%E5%92%8C%E6%89%87%E5%87%BA.png" alt="alt text" /><figcaption>alt text</figcaption></figure><p></p><p><strong>要点</strong></p><ul>
<li>扇出：为同一输入流开多个 worker，从同一个输入通道并发读取</li>
<li>扇入：用 <code>sync.WaitGroup</code> 等待所有 worker 完成后<strong>统一关闭</strong>聚合通道</li>
<li>注意每个任务只能被一个 worker 消费，如需广播要复制流</li>
</ul><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>package</span><span> </span><span>main</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>import</span><span> (</span></div></div><div><div><div>4</div></div><div><span>  </span><span>"</span><span>context</span><span>"</span></div></div><div><div><div>5</div></div><div><span>  </span><span>"</span><span>fmt</span><span>"</span></div></div><div><div><div>6</div></div><div><span>  </span><span>"</span><span>sync</span><span>"</span></div></div><div><div><div>7</div></div><div><span>  </span><span>"</span><span>time</span><span>"</span></div></div><div><div><div>8</div></div><div><span>)</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span>func</span><span> </span><span>gen</span><span>(</span><span>nums</span><span> </span><span>...int</span><span>) </span><span>&lt;-chan</span><span> </span><span>int</span><span> {</span></div></div><div><div><div>11</div></div><div><span><span>  </span></span><span>out </span><span>:=</span><span> </span><span>make</span><span>(</span><span>chan</span><span> </span><span>int</span><span>)</span></div></div><div><div><div>12</div></div><div><span>  </span><span>go</span><span> </span><span>func</span><span>() {</span></div></div><div><div><div>13</div></div><div><span>    </span><span>for</span><span> _, n </span><span>:=</span><span> </span><span>range</span><span> nums {</span></div></div><div><div><div>14</div></div><div><span><span>      </span></span><span>out </span><span>&lt;-</span><span> n</span></div></div><div><div><div>15</div></div><div><span><span>      </span></span><span>time.</span><span>Sleep</span><span>(</span><span>20</span><span> </span><span>*</span><span> time.Millisecond)</span></div></div><div><div><div>16</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>17</div></div><div><span>    </span><span>close</span><span>(out)</span></div></div><div><div><div>18</div></div><div><span><span>  </span></span><span>}()</span></div></div><div><div><div>19</div></div><div><span>  </span><span>return</span><span> out</span></div></div><div><div><div>20</div></div><div><span>}</span></div></div><div><div><div>21</div></div><div>
</div></div><div><div><div>22</div></div><div><span>func</span><span> </span><span>worker</span><span>(</span><span>ctx</span><span> </span><span>context</span><span>.</span><span>Context</span><span>, </span><span>id</span><span> </span><span>int</span><span>, </span><span>in</span><span> </span><span>&lt;-chan</span><span> </span><span>int</span><span>, </span><span>out</span><span> </span><span>chan&lt;-</span><span> </span><span>string</span><span>, </span><span>wg</span><span> </span><span>*</span><span>sync</span><span>.</span><span>WaitGroup</span><span>) {</span></div></div><div><div><div>23</div></div><div><span>  </span><span>defer</span><span> wg.</span><span>Done</span><span>()</span></div></div><div><div><div>24</div></div><div><span>  </span><span>for</span><span> n </span><span>:=</span><span> </span><span>range</span><span> in {</span></div></div><div><div><div>25</div></div><div><span>    </span><span>select</span><span> {</span></div></div><div><div><div>26</div></div><div><span>    </span><span>case</span><span> </span><span>&lt;-</span><span>ctx.</span><span>Done</span><span>():</span></div></div><div><div><div>27</div></div><div><span>      </span><span>return</span></div></div><div><div><div>28</div></div><div><span>    </span><span>default</span><span>:</span></div></div><div><div><div>29</div></div><div><span><span>      </span></span><span>time.</span><span>Sleep</span><span>(</span><span>60</span><span> </span><span>*</span><span> time.Millisecond) </span><span>// 模拟慢阶段</span></div></div><div><div><div>30</div></div><div><span><span>      </span></span><span>out </span><span>&lt;-</span><span> fmt.</span><span>Sprintf</span><span>(</span><span>"w</span><span>%d</span><span>:</span><span>%d</span><span>*2=</span><span>%d</span><span>"</span><span>, id, n, n</span><span>*</span><span>2</span><span>)</span></div></div><div><div><div>31</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>32</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>33</div></div><div><span>}</span></div></div><div><div><div>34</div></div><div>
</div></div><div><div><div>35</div></div><div><span>func</span><span> </span><span>fanIn</span><span>(</span><span>ctx</span><span> </span><span>context</span><span>.</span><span>Context</span><span>, </span><span>in</span><span> </span><span>&lt;-chan</span><span> </span><span>int</span><span>, </span><span>workers</span><span> </span><span>int</span><span>) </span><span>&lt;-chan</span><span> </span><span>string</span><span> {</span></div></div><div><div><div>36</div></div><div><span><span>  </span></span><span>out </span><span>:=</span><span> </span><span>make</span><span>(</span><span>chan</span><span> </span><span>string</span><span>, </span><span>16</span><span>)</span></div></div><div><div><div>37</div></div><div><span>  </span><span>var</span><span> wg </span><span>sync</span><span>.</span><span>WaitGroup</span></div></div><div><div><div>38</div></div><div><span><span>  </span></span><span>wg.</span><span>Add</span><span>(workers)</span></div></div><div><div><div>39</div></div><div><span>  </span><span>for</span><span> i </span><span>:=</span><span> </span><span>0</span><span>; i </span><span>&lt;</span><span> workers; i</span><span>++</span><span> {</span></div></div><div><div><div>40</div></div><div><span>    </span><span>go</span><span> </span><span>worker</span><span>(ctx, i, in, out, </span><span>&amp;</span><span>wg)</span></div></div><div><div><div>41</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>42</div></div><div><span>  </span><span>go</span><span> </span><span>func</span><span>() {</span></div></div><div><div><div>43</div></div><div><span><span>    </span></span><span>wg.</span><span>Wait</span><span>()</span></div></div><div><div><div>44</div></div><div><span>    </span><span>close</span><span>(out)</span></div></div><div><div><div>45</div></div><div><span><span>  </span></span><span>}()</span></div></div><div><div><div>46</div></div><div><span>  </span><span>return</span><span> out</span></div></div><div><div><div>47</div></div><div><span>}</span></div></div><div><div><div>48</div></div><div>
</div></div><div><div><div>49</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>50</div></div><div><span><span>  </span></span><span>ctx </span><span>:=</span><span> context.</span><span>Background</span><span>()</span></div></div><div><div><div>51</div></div><div><span><span>  </span></span><span>input </span><span>:=</span><span> </span><span>gen</span><span>(</span><span>1</span><span>, </span><span>2</span><span>, </span><span>3</span><span>, </span><span>4</span><span>, </span><span>5</span><span>, </span><span>6</span><span>)</span></div></div><div><div><div>52</div></div><div><span><span>  </span></span><span>results </span><span>:=</span><span> </span><span>fanIn</span><span>(ctx, input, </span><span>3</span><span>) </span><span>// 扇出 3 个 worker，并最终扇入</span></div></div><div><div><div>53</div></div><div><span>  </span><span>for</span><span> r </span><span>:=</span><span> </span><span>range</span><span> results {</span></div></div><div><div><div>54</div></div><div><span><span>    </span></span><span>fmt.</span><span>Println</span><span>(r)</span></div></div><div><div><div>55</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>56</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>输出：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>w0:1*2</span><span>=2</span></div></div><div><div><div>2</div></div><div><span>w1:2*2</span><span>=4</span></div></div><div><div><div>3</div></div><div><span>w2:3*2</span><span>=6</span></div></div><div><div><div>4</div></div><div><span>w0:4*2</span><span>=8</span></div></div><div><div><div>5</div></div><div><span>w1:5*2</span><span>=10</span></div></div><div><div><div>6</div></div><div><span>w2:6*2</span><span>=12</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>Futures 模式<a href="#futures-模式"><span>#</span></a></h2><p>在实际需求中，很多任务相互独立，可并发执行；主协程无需同步等待每个返回，而是在<strong>需要时</strong>再取结果。可用“结果通道 + 占位句柄”实现</p><p>比如打算自已做顿火锅吃，那么就需要洗菜、烧水。洗菜、烧水这两个步骤相互之间没有依赖关系，是独立的，那么就可以同时做，但是最后做火锅这个步骤就需要洗好菜、烧好水之后才能进行。这个做火锅的场景就适用 Futures 模式</p><p>主写成不用等待子协程返回的结果，可以执行其他语句，等到某个时间点需要的时候再来取；如果此时没有返回结果，则需要等待</p><p><strong>要点</strong></p><ul>
<li><code>Future</code> 本质是“只读结果通道 + 取消控制”</li>
<li>主协程先发起所有任务，做其他工作；当用到结果时从对应通道读取</li>
<li>读取时可加 select 超时或 <code>context</code> 控制</li>
</ul><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>package</span><span> </span><span>main</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>import</span><span> (</span></div></div><div><div><div>4</div></div><div><span>  </span><span>"</span><span>context</span><span>"</span></div></div><div><div><div>5</div></div><div><span>  </span><span>"</span><span>fmt</span><span>"</span></div></div><div><div><div>6</div></div><div><span>  </span><span>"</span><span>time</span><span>"</span></div></div><div><div><div>7</div></div><div><span>)</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>// 一个简单的 Future 句柄</span></div></div><div><div><div>10</div></div><div><span>type</span><span> </span><span>Future</span><span>[</span><span>T</span><span> </span><span>any</span><span>] </span><span>struct</span><span> {</span></div></div><div><div><div>11</div></div><div><span><span>  </span></span><span>C   </span><span>&lt;-chan</span><span> </span><span>T</span></div></div><div><div><div>12</div></div><div><span><span>  </span></span><span>Ctx </span><span>context</span><span>.</span><span>Context</span></div></div><div><div><div>13</div></div><div><span>}</span></div></div><div><div><div>14</div></div><div>
</div></div><div><div><div>15</div></div><div><span>func</span><span> </span><span>Async</span><span>[</span><span>T</span><span> </span><span>any</span><span>](</span><span>ctx</span><span> </span><span>context</span><span>.</span><span>Context</span><span>, </span><span>f</span><span> </span><span>func</span><span>(</span><span>context</span><span>.</span><span>Context</span><span>) </span><span>T</span><span>) </span><span>Future</span><span>[</span><span>T</span><span>] {</span></div></div><div><div><div>16</div></div><div><span><span>  </span></span><span>out </span><span>:=</span><span> </span><span>make</span><span>(</span><span>chan</span><span> </span><span>T</span><span>, </span><span>1</span><span>)</span></div></div><div><div><div>17</div></div><div><span>  </span><span>go</span><span> </span><span>func</span><span>() {</span></div></div><div><div><div>18</div></div><div><span>    </span><span>defer</span><span> </span><span>close</span><span>(out)</span></div></div><div><div><div>19</div></div><div><span><span>    </span></span><span>out </span><span>&lt;-</span><span> </span><span>f</span><span>(ctx)</span></div></div><div><div><div>20</div></div><div><span><span>  </span></span><span>}()</span></div></div><div><div><div>21</div></div><div><span>  </span><span>return</span><span> </span><span>Future</span><span>[</span><span>T</span><span>]{C: out, Ctx: ctx}</span></div></div><div><div><div>22</div></div><div><span>}</span></div></div><div><div><div>23</div></div><div>
</div></div><div><div><div>24</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>25</div></div><div><span><span>  </span></span><span>ctx </span><span>:=</span><span> context.</span><span>Background</span><span>()</span></div></div><div><div><div>26</div></div><div>
</div></div><div><div><div>27</div></div><div><span>  </span><span>// 并发：洗菜、烧水（互不依赖）</span></div></div><div><div><div>28</div></div><div><span><span>  </span></span><span>wash </span><span>:=</span><span> </span><span>Async</span><span>(ctx, </span><span>func</span><span>(</span><span>context</span><span>.</span><span>Context</span><span>) </span><span>string</span><span> {</span></div></div><div><div><div>29</div></div><div><span><span>    </span></span><span>time.</span><span>Sleep</span><span>(</span><span>300</span><span> </span><span>*</span><span> time.Millisecond)</span></div></div><div><div><div>30</div></div><div><span>    </span><span>return</span><span> </span><span>"washed veggies"</span></div></div><div><div><div>31</div></div><div><span><span>  </span></span><span>})</span></div></div><div><div><div>32</div></div><div><span><span>  </span></span><span>boil </span><span>:=</span><span> </span><span>Async</span><span>(ctx, </span><span>func</span><span>(</span><span>context</span><span>.</span><span>Context</span><span>) </span><span>string</span><span> {</span></div></div><div><div><div>33</div></div><div><span><span>    </span></span><span>time.</span><span>Sleep</span><span>(</span><span>500</span><span> </span><span>*</span><span> time.Millisecond)</span></div></div><div><div><div>34</div></div><div><span>    </span><span>return</span><span> </span><span>"boiled water"</span></div></div><div><div><div>35</div></div><div><span><span>  </span></span><span>})</span></div></div><div><div><div>36</div></div><div>
</div></div><div><div><div>37</div></div><div><span>  </span><span>// 主流程可做别的事</span></div></div><div><div><div>38</div></div><div><span><span>  </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"prepare sauce..."</span><span>)</span></div></div><div><div><div>39</div></div><div>
</div></div><div><div><div>40</div></div><div><span>  </span><span>// 需要结果时再取；可套超时/取消</span></div></div><div><div><div>41</div></div><div><span>  </span><span>select</span><span> {</span></div></div><div><div><div>42</div></div><div><span>  </span><span>case</span><span> v </span><span>:=</span><span> </span><span>&lt;-</span><span>wash.C:</span></div></div><div><div><div>43</div></div><div><span><span>    </span></span><span>fmt.</span><span>Println</span><span>(v)</span></div></div><div><div><div>44</div></div><div><span>  </span><span>case</span><span> </span><span>&lt;-</span><span>time.</span><span>After</span><span>(</span><span>400</span><span> </span><span>*</span><span> time.Millisecond):</span></div></div><div><div><div>45</div></div><div><span><span>    </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"wash timeout"</span><span>)</span></div></div><div><div><div>46</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>47</div></div><div>
</div></div><div><div><div>48</div></div><div><span>  </span><span>select</span><span> {</span></div></div><div><div><div>49</div></div><div><span>  </span><span>case</span><span> v </span><span>:=</span><span> </span><span>&lt;-</span><span>boil.C:</span></div></div><div><div><div>50</div></div><div><span><span>    </span></span><span>fmt.</span><span>Println</span><span>(v)</span></div></div><div><div><div>51</div></div><div><span>  </span><span>case</span><span> </span><span>&lt;-</span><span>time.</span><span>After</span><span>(</span><span>400</span><span> </span><span>*</span><span> time.Millisecond):</span></div></div><div><div><div>52</div></div><div><span><span>    </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"boil timeout"</span><span>)</span></div></div><div><div><div>53</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>54</div></div><div>
</div></div><div><div><div>55</div></div><div><span>  </span><span>// 如果任意一步超时，可以整体取消或降级</span></div></div><div><div><div>56</div></div><div><span><span>  </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"make hotpot"</span><span>)</span></div></div><div><div><div>57</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>输出：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>prepare</span><span> </span><span>sauce...</span></div></div><div><div><div>2</div></div><div><span>washed</span><span> </span><span>veggies</span></div></div><div><div><div>3</div></div><div><span>boil</span><span> </span><span>timeout</span></div></div><div><div><div>4</div></div><div><span>make</span><span> </span><span>hotpot</span></div></div></code></pre><div><div></div><div></div></div></figure></div><blockquote><p>由于超时设置为 400ms，而烧水耗时 500ms，因此出现超时示例。把第二个 <code>time.After(400ms)</code> 调大即可正常拿到 <code>boiled water</code></p></blockquote></section></section>
<section><h1>11 指针详解<a href="#11-指针详解"><span>#</span></a></h1><section><h2>什么是指针<a href="#什么是指针"><span>#</span></a></h2><p>程序运行时的数据是存放在内存中的，那么每一个存储在内存中的数据都会有一个编号，这个编号就是内存地址，而内存地址可以被赋值给一个指针</p><p>在编程语言中，指针是一种数据类型，用来存储一个内存地址，该地址指向存储在该内存中的对象</p><p>以一个现实生活中的例子举例：每本书中都有目录，目录上会有相应章节的页码，你可以把页码理解为一系列的内存地址，通过页码你可以快速地定位到具体的章节</p></section><section><h2>指针的声明和定义<a href="#指针的声明和定义"><span>#</span></a></h2><p>通过 <code>&amp;</code> 获取变量的地址；通过 <code>*</code> 来获取指针指向的元素的值，即解引用指针，例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>name </span><span>:=</span><span> </span><span>"Zowely"</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>nameP </span><span>:=</span><span> </span><span>&amp;</span><span>name</span></div></div><div><div><div>4</div></div><div><span>    </span><span>println</span><span>(</span><span>"name 的值是:"</span><span>, name)</span></div></div><div><div><div>5</div></div><div><span>    </span><span>println</span><span>(</span><span>"name 的地址是:"</span><span>, nameP)</span></div></div><div><div><div>6</div></div><div><span>    </span><span>println</span><span>(</span><span>"name 的地址的值是:"</span><span>, </span><span>*</span><span>nameP)</span></div></div><div><div><div>7</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>输出：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>Value</span><span> </span><span>of</span><span> </span><span>name:</span><span> </span><span>Zowely</span></div></div><div><div><div>2</div></div><div><span>Address</span><span> </span><span>of</span><span> </span><span>name:</span><span> </span><span>0xc000067f28</span></div></div><div><div><div>3</div></div><div><span>Value</span><span> </span><span>at</span><span> </span><span>address</span><span> </span><span>nameP:</span><span> </span><span>Zowely</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>但是对于一个数据类型，需要使用  <code>*</code> 来表示这个数据类型的指针，例如：<code>*int </code> <code>*string</code></p><blockquote><p>不同的指针类型无法相互赋值</p></blockquote><p>比如不能将 string 类型的指针取地址之后赋值给 *int 指针类型</p><p>除了使用简短声明，指针仍然可以使用 <code>var</code> 关键字来进行声明，例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>var</span><span> intP </span><span>*int</span></div></div><div><div><div>2</div></div><div><span>int</span><span> P </span><span>=</span><span> </span><span>&amp;</span><span>name </span><span>// 指针类型不同无法赋值</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>和普通类型不同的是，指针类型还可以通过内置的 <code>new</code> 函数来进行声明，例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>intP1 </span><span>:=</span><span> </span><span>new</span><span>(</span><span>int</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>指针的操作<a href="#指针的操作"><span>#</span></a></h2><p>指针的操作无非是两种：</p><ul>
<li>获取指针的值</li>
<li>修改指针的值</li>
</ul><p>通过 <code>&amp;</code> 获取变量的地址；通过 <code>*</code> 来获取指针指向的元素的值，即解引用指针，例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>name </span><span>:=</span><span> </span><span>"Zowely"</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>nameP </span><span>:=</span><span> </span><span>&amp;</span><span>name</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>nameV </span><span>:=</span><span> </span><span>*</span><span>nameP</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"nameP 指针的值:"</span><span>, nameP)</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"nameV 变量的值:"</span><span>, nameV)</span></div></div><div><div><div>7</div></div><div><span>    </span><span>*</span><span>nameP </span><span>=</span><span> </span><span>"Zowely Go"</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"nameP 指针指向的值:"</span><span>, </span><span>*</span><span>nameP)</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"name 变量的值:"</span><span>, name)</span></div></div><div><div><div>10</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>输出</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>nameP</span><span> </span><span>指针的值:</span><span> </span><span>0xc00008e060</span></div></div><div><div><div>2</div></div><div><span>nameV</span><span> </span><span>变量的值:</span><span> </span><span>Zowely</span></div></div><div><div><div>3</div></div><div><span>nameP</span><span> </span><span>指针指向的值:</span><span> </span><span>Zowely</span><span> </span><span>Go</span></div></div><div><div><div>4</div></div><div><span>name</span><span> </span><span>变量的值:</span><span> </span><span>Zowely</span><span> </span><span>Go</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>对于下面的代码会报错：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>var</span><span> intP </span><span>*int</span></div></div><div><div><div>2</div></div><div><span>*</span><span>intP </span><span>=</span><span> </span><span>10</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>panic:</span><span> </span><span>runtime</span><span> </span><span>error:</span><span> </span><span>invalid</span><span> </span><span>memory</span><span> </span><span>address</span><span> </span><span>or</span><span> </span><span>nil</span><span> </span><span>pointer</span><span> </span><span>dereference</span></div></div><div><div><div>2</div></div><div><span>[signal </span><span>0xc0000005</span><span> code</span><span>=</span><span>0x1</span><span> addr</span><span>=</span><span>0x0</span><span> pc</span><span>=</span><span>0x6a8270]</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>goroutine</span><span> </span><span>1</span><span> [running]:</span></div></div><div><div><div>5</div></div><div><span>main.main</span><span>()</span></div></div><div><div><div>6</div></div><div><span>  </span><span>D:/gotour/ch04/main.go:7</span><span> </span><span>+0x10</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这是因为最开始声明的 intP 为 NULL，这是可以通过内置的 new 函数提前分配好内存即可，例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>var</span><span> intP </span><span>*int</span><span> </span><span>=</span><span> </span><span>new</span><span>(</span><span>int</span><span>)</span></div></div><div><div><div>2</div></div><div><span>// intP := new(int) 效果相同</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>指针参数<a href="#指针参数"><span>#</span></a></h2><p>对于下面代码：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> </span><span>modifyAge</span><span>(</span><span>age</span><span> </span><span>int</span><span>) {</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>age </span><span>=</span><span> </span><span>30</span></div></div><div><div><div>3</div></div><div><span>}</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>age </span><span>:=</span><span> </span><span>20</span></div></div><div><div><div>7</div></div><div><span>  </span><span>modifyAge</span><span>(age)</span></div></div><div><div><div>8</div></div><div><span><span>  </span></span><span>fmt.</span><span>Println</span><span>(age)</span></div></div><div><div><div>9</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>运行后发现输出：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>20</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这是因为，modifyAge 的实参 age 参数只是一份拷贝，所以修改并不会真正改变 age 的值，如果想要真正的修改，那就需要使用指针，例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> </span><span>modifyAge</span><span>(</span><span>age</span><span> </span><span>*int</span><span>) {</span></div></div><div><div><div>2</div></div><div><span>    </span><span>*</span><span>age </span><span>=</span><span> </span><span>30</span></div></div><div><div><div>3</div></div><div><span>}</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>age </span><span>:=</span><span> </span><span>20</span></div></div><div><div><div>7</div></div><div><span>    </span><span>modifyAge</span><span>(</span><span>&amp;</span><span>age)</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>fmt.</span><span>Println</span><span>(age)</span></div></div><div><div><div>9</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>指针接收者<a href="#指针接收者"><span>#</span></a></h2><p>对于是否使用指针类型作为接受者，有以下几点参考：</p><ol>
<li>如果接受者是 map、slice、channel 这类引用类型，不使用指针</li>
<li>如果需要修改接收者，那么需要使用指针</li>
<li>如果接收者是比较大的类型，可以使用指针</li>
</ol></section><section><h2>指针的两大好处<a href="#指针的两大好处"><span>#</span></a></h2><ol>
<li>可以修改指向数据的值</li>
<li>在变量赋值，参数传值的时候可以节省内存</li>
</ol></section><section><h2>什么情况下使用指针<a href="#什么情况下使用指针"><span>#</span></a></h2><ul>
<li><strong>不要</strong>对 map、slice、channel 这类引用类型使用指针</li>
<li>如果需要修改方法接收者内部的数据或者状态时，需要使用指针</li>
<li>如果需要修改参数的值或者内部数据时，也需要使用指针类型的参数</li>
<li>如果是比较大的结构体这时候可以<strong>考虑</strong>使用指针</li>
<li>像 int、bool 这样的小数据类型<strong>没必要</strong>使用指针</li>
<li>如果需要并发安全，则<strong>尽可能地不要使用</strong>指针，使用指针<strong>一定要保证</strong>并发安全</li>
<li>指针<strong>最好不要嵌套</strong>，也就是不要使用一个指向指针的指针</li>
</ul></section></section>
<section><h1>12 参数传递<a href="#12-参数传递"><span>#</span></a></h1><p>在 Go 语言中，<strong>所有参数传递都是值传递</strong>。
也就是说，当函数被调用时，实参的值会被<strong>复制</strong>一份传入函数。
如果拷贝的是<strong>值类型</strong>，函数中修改不会影响原始数据；<br />
如果拷贝的是<strong>引用类型或指针</strong>，则可以通过引用或指针间接修改原始数据</p><section><h2>修改参数<a href="#修改参数"><span>#</span></a></h2><p>当你把变量传入函数时，函数拿到的是它的<strong>拷贝</strong>，如果函数需要修改外部变量，就必须通过<strong>指针或引用类型</strong>实现</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>package</span><span> </span><span>main</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>import</span><span> </span><span>"</span><span>fmt</span><span>"</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>func</span><span> </span><span>changeValue</span><span>(</span><span>x</span><span> </span><span>int</span><span>) {</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>x </span><span>=</span><span> </span><span>100</span></div></div><div><div><div>7</div></div><div><span>}</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>func</span><span> </span><span>changePointer</span><span>(</span><span>x</span><span> </span><span>*int</span><span>) {</span></div></div><div><div><div>10</div></div><div><span>  </span><span>*</span><span>x </span><span>=</span><span> </span><span>100</span></div></div><div><div><div>11</div></div><div><span>}</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>14</div></div><div><span><span>  </span></span><span>a </span><span>:=</span><span> </span><span>10</span></div></div><div><div><div>15</div></div><div><span>  </span><span>changeValue</span><span>(a)</span></div></div><div><div><div>16</div></div><div><span><span>  </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"after changeValue:"</span><span>, a) </span><span>// 输出 10（未修改）</span></div></div><div><div><div>17</div></div><div>
</div></div><div><div><div>18</div></div><div><span>  </span><span>changePointer</span><span>(</span><span>&amp;</span><span>a)</span></div></div><div><div><div>19</div></div><div><span><span>  </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"after changePointer:"</span><span>, a) </span><span>// 输出 100（已修改）</span></div></div><div><div><div>20</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p><strong>输出：</strong></p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>after</span><span> </span><span>changeValue:</span><span> </span><span>10</span></div></div><div><div><div>2</div></div><div><span>after</span><span> </span><span>changePointer:</span><span> </span><span>100</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section><section><h2>值类型<a href="#值类型"><span>#</span></a></h2><p>值类型在传参时，会复制整个变量的值，在函数中修改值类型参数，不会影响原始变量</p><p>Go 中的常见值类型包括：</p><ul>
<li>基本类型：<code>int</code>、<code>float64</code>、<code>bool</code>、<code>string</code></li>
<li>复合类型：<code>array</code>、<code>struct</code></li>
</ul><p>例如对于下面代码：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>package</span><span> </span><span>main</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>import</span><span> </span><span>"</span><span>fmt</span><span>"</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>type</span><span> </span><span>User</span><span> </span><span>struct</span><span> {</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>name </span><span>string</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>age  </span><span>int</span></div></div><div><div><div>8</div></div><div><span>}</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span>func</span><span> </span><span>modify</span><span>(</span><span>u</span><span> </span><span>User</span><span>) {</span></div></div><div><div><div>11</div></div><div><span><span>  </span></span><span>u.name </span><span>=</span><span> </span><span>"Alice"</span></div></div><div><div><div>12</div></div><div><span><span>  </span></span><span>u.age </span><span>=</span><span> </span><span>30</span></div></div><div><div><div>13</div></div><div><span>}</span></div></div><div><div><div>14</div></div><div>
</div></div><div><div><div>15</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>16</div></div><div><span><span>  </span></span><span>u </span><span>:=</span><span> </span><span>User</span><span>{</span><span>"Bob"</span><span>, </span><span>20</span><span>}</span></div></div><div><div><div>17</div></div><div><span>  </span><span>modify</span><span>(u)</span></div></div><div><div><div>18</div></div><div><span><span>  </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"after modify:"</span><span>, u)</span></div></div><div><div><div>19</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p><strong>输出：</strong></p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>after</span><span> </span><span>modify:</span><span> </span><span>{Bob</span><span> </span><span>20</span><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><blockquote><p>说明：<code>User</code> 是值类型，<code>modify</code> 拿到的是一份拷贝，修改不影响原始数据</p></blockquote></section><section><h2>指针类型<a href="#指针类型"><span>#</span></a></h2><p>指针保存的是变量的地址，当传递指针时，复制的是这个<strong>地址值</strong>，所以可以通过指针间接修改原始数据</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>package</span><span> </span><span>main</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>import</span><span> </span><span>"</span><span>fmt</span><span>"</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>type</span><span> </span><span>User</span><span> </span><span>struct</span><span> {</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>name </span><span>string</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>age  </span><span>int</span></div></div><div><div><div>8</div></div><div><span>}</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span>func</span><span> </span><span>modifyPtr</span><span>(</span><span>u</span><span> </span><span>*</span><span>User</span><span>) {</span></div></div><div><div><div>11</div></div><div><span><span>  </span></span><span>u.name </span><span>=</span><span> </span><span>"Alice"</span></div></div><div><div><div>12</div></div><div><span><span>  </span></span><span>u.age </span><span>=</span><span> </span><span>30</span></div></div><div><div><div>13</div></div><div><span>}</span></div></div><div><div><div>14</div></div><div>
</div></div><div><div><div>15</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>16</div></div><div><span><span>  </span></span><span>u </span><span>:=</span><span> </span><span>&amp;</span><span>User</span><span>{</span><span>"Bob"</span><span>, </span><span>20</span><span>}</span></div></div><div><div><div>17</div></div><div><span>  </span><span>modifyPtr</span><span>(u)</span></div></div><div><div><div>18</div></div><div><span><span>  </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"after modifyPtr:"</span><span>, </span><span>*</span><span>u)</span></div></div><div><div><div>19</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p><strong>输出：</strong></p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>after</span><span> </span><span>modifyPtr:</span><span> </span><span>{Alice</span><span> </span><span>30</span><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><blockquote><p>说明：这里传入的是指针，函数内部修改了指针指向的对象内容</p></blockquote></section><section><h2>引用类型<a href="#引用类型"><span>#</span></a></h2><p>Go 中有三种内置的引用类型：<code>slice</code>、<code>map</code>、<code>channel</code></p><p>虽然传参依然是“值传递”，但<strong>拷贝的值中包含对底层数据结构的引用</strong>，  因此函数内部可以通过这个引用修改原始数据</p><section><h3>map<a href="#map"><span>#</span></a></h3><p><code>map</code> 是引用类型，传参时复制的是对底层哈希表的引用，因此在函数中修改 map 会直接影响原始 map</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>package</span><span> </span><span>main</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>import</span><span> </span><span>"</span><span>fmt</span><span>"</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>func</span><span> </span><span>modifyMap</span><span>(</span><span>m</span><span> </span><span>map</span><span>[</span><span>string</span><span>]</span><span>int</span><span>) {</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>m[</span><span>"age"</span><span>] </span><span>=</span><span> </span><span>30</span></div></div><div><div><div>7</div></div><div><span>}</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>10</div></div><div><span><span>  </span></span><span>user </span><span>:=</span><span> </span><span>map</span><span>[</span><span>string</span><span>]</span><span>int</span><span>{</span><span>"age"</span><span>: </span><span>20</span><span>}</span></div></div><div><div><div>11</div></div><div><span>  </span><span>modifyMap</span><span>(user)</span></div></div><div><div><div>12</div></div><div><span><span>  </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"after modifyMap:"</span><span>, user)</span></div></div><div><div><div>13</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>输出：</strong></p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>after</span><span> </span><span>modifyMap:</span><span> </span><span>map[age:30]</span></div></div></code></pre><div><div></div><div></div></div></figure></div><blockquote><p><code>map</code> 的底层结构包含一个指向哈希表的指针，复制 map 变量只是复制了这个指针</p></blockquote></section><section><h3>channel<a href="#channel"><span>#</span></a></h3><p><code>channel</code> 也是引用类型，传参时会复制底层通道的引用， 因此在函数中发送或接收数据会直接作用在原通道上</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>package</span><span> </span><span>main</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>import</span><span> </span><span>"</span><span>fmt</span><span>"</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>func</span><span> </span><span>sendData</span><span>(</span><span>ch</span><span> </span><span>chan</span><span> </span><span>int</span><span>) {</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>ch </span><span>&lt;-</span><span> </span><span>10</span></div></div><div><div><div>7</div></div><div><span>}</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>10</div></div><div><span><span>  </span></span><span>ch </span><span>:=</span><span> </span><span>make</span><span>(</span><span>chan</span><span> </span><span>int</span><span>, </span><span>1</span><span>)</span></div></div><div><div><div>11</div></div><div><span>  </span><span>sendData</span><span>(ch)</span></div></div><div><div><div>12</div></div><div><span><span>  </span></span><span>fmt.</span><span>Println</span><span>(</span><span>&lt;-</span><span>ch)</span></div></div><div><div><div>13</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>输出：</strong></p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>10</span></div></div></code></pre><div><div></div><div></div></div></figure></div><blockquote><p>函数中 <code>ch &lt;- 10</code> 实际操作的是原通道，因此主函数能接收到数据</p></blockquote></section></section><section><h2>类型的零值<a href="#类型的零值"><span>#</span></a></h2><p>在 Go 中，每种类型都有默认的<strong>零值</strong>（Zero Value），当变量声明但未初始化时，会自动赋予零值</p>







































<table><thead><tr><th>类型类别</th><th>示例类型</th><th>零值</th></tr></thead><tbody><tr><td>数值类型</td><td><code>int</code>, <code>float64</code></td><td><code>0</code></td></tr><tr><td>布尔类型</td><td><code>bool</code></td><td><code>false</code></td></tr><tr><td>字符串</td><td><code>string</code></td><td><code>""</code>（空字符串）</td></tr><tr><td>指针、接口、函数</td><td><code>*T</code>, <code>interface{}</code></td><td><code>nil</code></td></tr><tr><td>引用类型</td><td><code>map</code>, <code>slice</code>, <code>chan</code></td><td><code>nil</code></td></tr><tr><td>结构体</td><td><code>struct</code></td><td>每个字段都是对应类型的零值</td></tr></tbody></table><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>package</span><span> </span><span>main</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>import</span><span> </span><span>"</span><span>fmt</span><span>"</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>6</div></div><div><span>  </span><span>var</span><span> (</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>a </span><span>int</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>b </span><span>bool</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>c </span><span>string</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>d []</span><span>int</span></div></div><div><div><div>11</div></div><div><span><span>    </span></span><span>e </span><span>map</span><span>[</span><span>string</span><span>]</span><span>int</span></div></div><div><div><div>12</div></div><div><span><span>    </span></span><span>f </span><span>chan</span><span> </span><span>int</span></div></div><div><div><div>13</div></div><div><span><span>  </span></span><span>)</span></div></div><div><div><div>14</div></div><div><span><span>  </span></span><span>fmt.</span><span>Println</span><span>(a, b, c, d, e, f)</span></div></div><div><div><div>15</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p><strong>输出：</strong></p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>0</span><span> </span><span>false</span><span>  [] map[] &lt;nil&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>总结：</strong></p>
























<table><thead><tr><th>类型类别</th><th>参数传递时是否复制底层数据</th><th>函数内修改是否影响原值</th></tr></thead><tbody><tr><td>值类型 (<code>int</code>, <code>array</code>, <code>struct</code>)</td><td>是</td><td>否</td></tr><tr><td>指针类型 (<code>*T</code>)</td><td>否（复制指针地址）</td><td>是</td></tr><tr><td>引用类型 (<code>map</code>, <code>slice</code>, <code>chan</code>)</td><td>否（复制引用）</td><td>是</td></tr></tbody></table><blockquote><p>Go 的所有参数传递都是<strong>值传递</strong>。
能否修改原值，取决于<strong>被复制的内容</strong>是纯值、指针还是引用</p></blockquote><p>遵命，我将按照您提供的全新标题和要求，以“同步原语”部分的风格重写这些内容，并确保包含更多的小标题和丰富的示例</p><hr /></section></section>
<section><h1>13 内存分配<a href="#13-内存分配"><span>#</span></a></h1><p>Go 语言在内存分配时提供了两个内置函数：<code>new</code> 和 <code>make</code>。它们都用于在堆上分配内存，但用途截然不同，是初学者常见的困惑点</p><section><h2><code>new(T)</code>：只分配内存<a href="#newt只分配内存"><span>#</span></a></h2><p><code>new</code> 是一个内置函数，它接受一个类型 <code>T</code> 作为参数，<strong>只负责为该类型的零值分配内存</strong>，并返回一个指向这块内存的指针（<code>*T</code>）</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>package</span><span> </span><span>main</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>import</span><span> </span><span>"</span><span>fmt</span><span>"</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>type</span><span> </span><span>Person</span><span> </span><span>struct</span><span> {</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>Name </span><span>string</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>Age  </span><span>int</span></div></div><div><div><div>8</div></div><div><span>}</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>11</div></div><div><span>  </span><span>// 使用 new 分配一个 Person 结构体</span></div></div><div><div><div>12</div></div><div><span>  </span><span>// p 是一个指针 *Person，指向一个 Person 结构体的零值</span></div></div><div><div><div>13</div></div><div><span><span>  </span></span><span>p </span><span>:=</span><span> </span><span>new</span><span>(</span><span>Person</span><span>)</span></div></div><div><div><div>14</div></div><div><span><span>  </span></span><span>fmt.</span><span>Printf</span><span>(</span><span>"p 的类型: </span><span>%T\n</span><span>"</span><span>, p)</span></div></div><div><div><div>15</div></div><div><span><span>  </span></span><span>fmt.</span><span>Printf</span><span>(</span><span>"p 的值: </span><span>%+v\n</span><span>"</span><span>, </span><span>*</span><span>p) </span><span>// 零值: {Name:"" Age:0}</span></div></div><div><div><div>16</div></div><div>
</div></div><div><div><div>17</div></div><div><span>  </span><span>// new(int)</span></div></div><div><div><div>18</div></div><div><span>  </span><span>// i 是一个指针 *int，指向 int 的零值 (0)</span></div></div><div><div><div>19</div></div><div><span><span>  </span></span><span>i </span><span>:=</span><span> </span><span>new</span><span>(</span><span>int</span><span>)</span></div></div><div><div><div>20</div></div><div><span><span>  </span></span><span>fmt.</span><span>Printf</span><span>(</span><span>"i 的类型: </span><span>%T\n</span><span>"</span><span>, i)</span></div></div><div><div><div>21</div></div><div><span><span>  </span></span><span>fmt.</span><span>Printf</span><span>(</span><span>"i 的值: </span><span>%d\n</span><span>"</span><span>, </span><span>*</span><span>i)</span></div></div><div><div><div>22</div></div><div>
</div></div><div><div><div>23</div></div><div><span>  </span><span>*</span><span>i </span><span>=</span><span> </span><span>100</span></div></div><div><div><div>24</div></div><div><span><span>  </span></span><span>fmt.</span><span>Printf</span><span>(</span><span>"i 修改后的值: </span><span>%d\n</span><span>"</span><span>, </span><span>*</span><span>i)</span></div></div><div><div><div>25</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>输出：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>p</span><span> </span><span>的类型:</span><span> </span><span>*</span><span>main.Person</span></div></div><div><div><div>2</div></div><div><span>p</span><span> </span><span>的值:</span><span> </span><span>{Name:</span><span> </span><span>Age:0}</span></div></div><div><div><div>3</div></div><div><span>i</span><span> </span><span>的类型:</span><span> </span><span>*</span><span>int</span></div></div><div><div><div>4</div></div><div><span>i</span><span> </span><span>的值:</span><span> </span><span>0</span></div></div><div><div><div>5</div></div><div><span>i</span><span> </span><span>修改后的值:</span><span> </span><span>100</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><code>new</code> 几乎等价于 <code>var v T; return &amp;v</code></p></section><section><h2><code>make(T, ...)</code>：初始化内置类型<a href="#maket-初始化内置类型"><span>#</span></a></h2><p><code>make</code> 仅用于 Go 的三种内置引用类型：<code>slice</code>（切片）、<code>map</code>（映射）和 <code>channel</code>（通道）</p><p><code>make</code> 不仅会分配内存，还会<strong>初始化</strong>这些数据结构内部的描述符（例如 <code>slice</code> 的 <code>len</code> 和 <code>cap</code>），并返回这个类型的实例，而不是指针</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>package</span><span> </span><span>main</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>import</span><span> </span><span>"</span><span>fmt</span><span>"</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>6</div></div><div><span>  </span><span>// 制作一个 slice，长度为 0，容量为 10</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>s </span><span>:=</span><span> </span><span>make</span><span>([]</span><span>int</span><span>, </span><span>0</span><span>, </span><span>10</span><span>)</span></div></div><div><div><div>8</div></div><div><span><span>  </span></span><span>fmt.</span><span>Printf</span><span>(</span><span>"s: len=</span><span>%d</span><span>, cap=</span><span>%d</span><span>, value=</span><span>%v\n</span><span>"</span><span>, </span><span>len</span><span>(s), </span><span>cap</span><span>(s), s)</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span>  </span><span>// 制作一个 map</span></div></div><div><div><div>11</div></div><div><span><span>  </span></span><span>m </span><span>:=</span><span> </span><span>make</span><span>(</span><span>map</span><span>[</span><span>string</span><span>]</span><span>int</span><span>)</span></div></div><div><div><div>12</div></div><div><span><span>  </span></span><span>m[</span><span>"apple"</span><span>] </span><span>=</span><span> </span><span>1</span></div></div><div><div><div>13</div></div><div><span><span>  </span></span><span>fmt.</span><span>Printf</span><span>(</span><span>"m: </span><span>%v\n</span><span>"</span><span>, m)</span></div></div><div><div><div>14</div></div><div>
</div></div><div><div><div>15</div></div><div><span>  </span><span>// 制作一个 channel</span></div></div><div><div><div>16</div></div><div><span><span>  </span></span><span>ch </span><span>:=</span><span> </span><span>make</span><span>(</span><span>chan</span><span> </span><span>string</span><span>)</span></div></div><div><div><div>17</div></div><div><span><span>  </span></span><span>fmt.</span><span>Printf</span><span>(</span><span>"ch 的类型: </span><span>%T\n</span><span>"</span><span>, ch)</span></div></div><div><div><div>18</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>输出：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>s:</span><span> </span><span>len=0,</span><span> </span><span>cap=10,</span><span> </span><span>value=[]</span></div></div><div><div><div>2</div></div><div><span>m:</span><span> </span><span>map[apple:1]</span></div></div><div><div><div>3</div></div><div><span>ch</span><span> </span><span>的类型:</span><span> </span><span>chan</span><span> </span><span>string</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>为什么不能混用？<a href="#为什么不能混用"><span>#</span></a></h2><p>如果你尝试用 <code>new</code> 来创建 <code>slice</code>，你会得到一个指向 <code>nil</code> 切片的指针，这通常不是你想要的</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>s </span><span>:=</span><span> </span><span>new</span><span>([]</span><span>int</span><span>) </span><span>// s 是 *[]int 类型</span></div></div><div><div><div>2</div></div><div><span>fmt.</span><span>Printf</span><span>(</span><span>"s: </span><span>%v</span><span>, len=</span><span>%d\n</span><span>"</span><span>, s, </span><span>len</span><span>(</span><span>*</span><span>s))</span></div></div><div><div><div>3</div></div><div><span>// *s = append(*s, 1) // 运行时 panic: nil slice</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>栈 (Stack) 与堆 (Heap)<a href="#栈-stack-与堆-heap"><span>#</span></a></h2><p>Go 语言的内存分为栈（Stack）和堆（Heap）</p><ul>
<li><strong>栈</strong>：用于存储函数局部变量和函数调用信息。栈上分配和回收速度极快，函数返回时自动清理</li>
<li><strong>堆</strong>：用于存储生命周期更长的变量（例如被多个 Goroutine 共享或在函数返回后仍需存在的变量）。堆内存由垃圾回收器（GC）管理</li>
</ul></section><section><h2>逃逸分析 (Escape Analysis)<a href="#逃逸分析-escape-analysis"><span>#</span></a></h2><p><code>new</code> 和 <code>make</code> 分配的内存在栈上还是堆上，并不是固定的，而是由 Go 编译器在编译时通过<strong>逃逸分析</strong>（Escape Analysis）决定的</p><p>如果一个变量的生命周期只在当前函数内，它通常会被分配在栈上；如果它被函数返回（作为指针或被闭包引用），它就会“逃逸”到堆上</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> </span><span>createOnStack</span><span>() </span><span>int</span><span> {</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>x </span><span>:=</span><span> </span><span>10</span><span> </span><span>// x 在栈上</span></div></div><div><div><div>3</div></div><div><span>  </span><span>return</span><span> x</span></div></div><div><div><div>4</div></div><div><span>}</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>func</span><span> </span><span>createOnHeap</span><span>() </span><span>*int</span><span> {</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>y </span><span>:=</span><span> </span><span>20</span><span> </span><span>// y "逃逸"到堆上</span></div></div><div><div><div>8</div></div><div><span>  </span><span>return</span><span> </span><span>&amp;</span><span>y</span></div></div><div><div><div>9</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section></section>
<section><h1>14 运行时反射<a href="#14-运行时反射"><span>#</span></a></h1><p>反射（Reflection）允许程序在运行时检查自身的结构、类型和值。<code>reflect</code> 包是 Go 反射机制的核心，它在序列化（如 JSON）、ORM 和依赖注入框架中被广泛使用</p><section><h2>什么是反射<a href="#什么是反射"><span>#</span></a></h2><p>Go 语言的反射建立在“类型”和“值”这两个概念之上。<code>reflect</code> 包提供了两个核心类型：</p><ul>
<li><code>reflect.Type</code>：表示一个 Go 变量的类型信息</li>
<li><code>reflect.Value</code>：表示一个 Go 变量的实际值</li>
</ul><p>我们可以使用 <code>reflect.TypeOf()</code> 和 <code>reflect.ValueOf()</code> 来获取它们</p></section><section><h2>通过反射获取结构体信息<a href="#通过反射获取结构体信息"><span>#</span></a></h2><p>反射可以让我们在运行时遍历一个结构体的所有字段和方法</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>package</span><span> </span><span>main</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>import</span><span> (</span></div></div><div><div><div>4</div></div><div><span>  </span><span>"</span><span>fmt</span><span>"</span></div></div><div><div><div>5</div></div><div><span>  </span><span>"</span><span>reflect</span><span>"</span></div></div><div><div><div>6</div></div><div><span>)</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>type</span><span> </span><span>User</span><span> </span><span>struct</span><span> {</span></div></div><div><div><div>9</div></div><div><span><span>  </span></span><span>Name </span><span>string</span><span> </span><span>`json:"name" info:"用户名"`</span></div></div><div><div><div>10</div></div><div><span><span>  </span></span><span>Age  </span><span>int</span><span>    </span><span>`json:"age"`</span></div></div><div><div><div>11</div></div><div><span>}</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>14</div></div><div><span><span>  </span></span><span>u </span><span>:=</span><span> </span><span>User</span><span>{Name: </span><span>"Alice"</span><span>, Age: </span><span>30</span><span>}</span></div></div><div><div><div>15</div></div><div><span><span>  </span></span><span>t </span><span>:=</span><span> reflect.</span><span>TypeOf</span><span>(u)</span></div></div><div><div><div>16</div></div><div>
</div></div><div><div><div>17</div></div><div><span><span>  </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"类型:"</span><span>, t.</span><span>Name</span><span>())</span></div></div><div><div><div>18</div></div><div><span><span>  </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"字段数量:"</span><span>, t.</span><span>NumField</span><span>())</span></div></div><div><div><div>19</div></div><div>
</div></div><div><div><div>20</div></div><div><span>  </span><span>for</span><span> i </span><span>:=</span><span> </span><span>0</span><span>; i </span><span>&lt;</span><span> t.</span><span>NumField</span><span>(); i</span><span>++</span><span> {</span></div></div><div><div><div>21</div></div><div><span><span>    </span></span><span>field </span><span>:=</span><span> t.</span><span>Field</span><span>(i)</span></div></div><div><div><div>22</div></div><div><span><span>    </span></span><span>fmt.</span><span>Printf</span><span>(</span><span>"  字段名: </span><span>%s</span><span>, 类型: </span><span>%v\n</span><span>"</span><span>, field.Name, field.Type)</span></div></div><div><div><div>23</div></div><div><span>    </span><span>// 获取 Tag</span></div></div><div><div><div>24</div></div><div><span><span>    </span></span><span>fmt.</span><span>Printf</span><span>(</span><span>"    JSON Tag: </span><span>%s\n</span><span>"</span><span>, field.Tag.</span><span>Get</span><span>(</span><span>"json"</span><span>))</span></div></div><div><div><div>25</div></div><div><span><span>    </span></span><span>fmt.</span><span>Printf</span><span>(</span><span>"    Info Tag: </span><span>%s\n</span><span>"</span><span>, field.Tag.</span><span>Get</span><span>(</span><span>"info"</span><span>))</span></div></div><div><div><div>26</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>27</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>输出：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>类型:</span><span> </span><span>User</span></div></div><div><div><div>2</div></div><div><span>字段数量:</span><span> </span><span>2</span></div></div><div><div><div>3</div></div><div><span>  </span><span>字段名:</span><span> </span><span>Name,</span><span> </span><span>类型:</span><span> </span><span>string</span></div></div><div><div><div>4</div></div><div><span>    </span><span>JSON</span><span> </span><span>Tag:</span><span> </span><span>name</span></div></div><div><div><div>5</div></div><div><span>    </span><span>Info</span><span> </span><span>Tag:</span><span> </span><span>用户名</span></div></div><div><div><div>6</div></div><div><span>  </span><span>字段名:</span><span> </span><span>Age,</span><span> </span><span>类型:</span><span> </span><span>int</span></div></div><div><div><div>7</div></div><div><span>    </span><span>JSON</span><span> </span><span>Tag:</span><span> </span><span>age</span></div></div><div><div><div>8</div></div><div><span>    </span><span>Info</span><span> </span><span>Tag:</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>通过字段名 (字符串) 访问<a href="#通过字段名-字符串-访问"><span>#</span></a></h2><p>这是反射最强大的功能之一：使用字符串动态地访问或修改结构体字段</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>package</span><span> </span><span>main</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>import</span><span> (</span></div></div><div><div><div>4</div></div><div><span>  </span><span>"</span><span>fmt</span><span>"</span></div></div><div><div><div>5</div></div><div><span>  </span><span>"</span><span>reflect</span><span>"</span></div></div><div><div><div>6</div></div><div><span>)</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>type</span><span> </span><span>Config</span><span> </span><span>struct</span><span> {</span></div></div><div><div><div>9</div></div><div><span><span>  </span></span><span>Host </span><span>string</span></div></div><div><div><div>10</div></div><div><span><span>  </span></span><span>Port </span><span>int</span></div></div><div><div><div>11</div></div><div><span><span>  </span></span><span>Tags []</span><span>string</span></div></div><div><div><div>12</div></div><div><span>}</span></div></div><div><div><div>13</div></div><div>
</div></div><div><div><div>14</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>15</div></div><div><span><span>  </span></span><span>c </span><span>:=</span><span> </span><span>Config</span><span>{Host: </span><span>"localhost"</span><span>, Port: </span><span>8080</span><span>}</span></div></div><div><div><div>16</div></div><div>
</div></div><div><div><div>17</div></div><div><span>  </span><span>// 必须传递指针才能修改</span></div></div><div><div><div>18</div></div><div><span><span>  </span></span><span>v </span><span>:=</span><span> reflect.</span><span>ValueOf</span><span>(</span><span>&amp;</span><span>c).</span><span>Elem</span><span>() </span><span>// Elem() 获取指针指向的值</span></div></div><div><div><div>19</div></div><div>
</div></div><div><div><div>20</div></div><div><span>  </span><span>// 1. 通过字符串获取字段</span></div></div><div><div><div>21</div></div><div><span><span>  </span></span><span>hostField </span><span>:=</span><span> v.</span><span>FieldByName</span><span>(</span><span>"Host"</span><span>)</span></div></div><div><div><div>22</div></div><div><span><span>  </span></span><span>fmt.</span><span>Printf</span><span>(</span><span>"Host 字段值: </span><span>%s\n</span><span>"</span><span>, hostField.</span><span>String</span><span>())</span></div></div><div><div><div>23</div></div><div>
</div></div><div><div><div>24</div></div><div><span>  </span><span>// 2. 通过字符串修改字段</span></div></div><div><div><div>25</div></div><div><span><span>  </span></span><span>portField </span><span>:=</span><span> v.</span><span>FieldByName</span><span>(</span><span>"Port"</span><span>)</span></div></div><div><div><div>26</div></div><div><span>  </span><span>if</span><span> portField.</span><span>CanSet</span><span>() { </span><span>// 检查是否可设置</span></div></div><div><div><div>27</div></div><div><span><span>    </span></span><span>portField.</span><span>SetInt</span><span>(</span><span>9090</span><span>)</span></div></div><div><div><div>28</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>29</div></div><div>
</div></div><div><div><div>30</div></div><div><span><span>  </span></span><span>fmt.</span><span>Printf</span><span>(</span><span>"修改后的 Config: </span><span>%+v\n</span><span>"</span><span>, c)</span></div></div><div><div><div>31</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>输出：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>Host</span><span> </span><span>字段值:</span><span> </span><span>localhost</span></div></div><div><div><div>2</div></div><div><span>修改后的</span><span> </span><span>Config:</span><span> </span><span>{Host:localhost</span><span> </span><span>Port:9090</span><span> </span><span>Tags:[]}</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>反射的代价<a href="#反射的代价"><span>#</span></a></h2><p>反射非常强大，但也带来了显著的性能开销，因为它绕过了编译器的静态类型检查，转而在运行时进行动态查找。应在必要时（如框架开发）才使用，避免在性能敏感的核心业务逻辑中滥用</p><hr /></section></section>
<section><h1>15 非类型安全<a href="#15-非类型安全"><span>#</span></a></h1><p>Go 是一门强类型、内存安全的语言。但它提供了一个特殊的包 <code>unsafe</code>，它允许开发者绕过类型系统和内存安全检查，直接操作内存。这就像一把锋利的“手术刀”，功能强大但极其危险</p><section><h2><code>unsafe</code> 包的魔力<a href="#unsafe-包的魔力"><span>#</span></a></h2><p><code>unsafe</code> 包只提供了几个核心功能，最常用的是：</p><ul>
<li><code>unsafe.Pointer</code>：一种特殊的指针类型，它可以指向任意类型的数据，是连接 Go 指针和 <code>uintptr</code> 的桥梁</li>
<li><code>uintptr</code>：一个整数类型，其大小足以容纳一个指针的位模式。它可以用于指针的算术运算</li>
<li><code>unsafe.Sizeof</code>：返回一个类型实例所占用的字节数</li>
</ul></section><section><h2>危险的操作：类型转换<a href="#危险的操作类型转换"><span>#</span></a></h2><p><code>unsafe</code> 允许我们在不兼容的类型之间强行转换，例如将 <code>float64</code> 的内存位模式解释为 <code>uint64</code></p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>package</span><span> </span><span>main</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>import</span><span> (</span></div></div><div><div><div>4</div></div><div><span>  </span><span>"</span><span>fmt</span><span>"</span></div></div><div><div><div>5</div></div><div><span>  </span><span>"</span><span>unsafe</span><span>"</span></div></div><div><div><div>6</div></div><div><span>)</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>9</div></div><div><span><span>  </span></span><span>f </span><span>:=</span><span> </span><span>1.234</span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span>  </span><span>// &amp;f (*float64) -&gt; unsafe.Pointer -&gt; *uint64</span></div></div><div><div><div>12</div></div><div><span>  </span><span>// 最后解引用 *(*uint64)</span></div></div><div><div><div>13</div></div><div><span><span>  </span></span><span>bits </span><span>:=</span><span> </span><span>*</span><span>(</span><span>*uint64</span><span>)(unsafe.</span><span>Pointer</span><span>(</span><span>&amp;</span><span>f))</span></div></div><div><div><div>14</div></div><div>
</div></div><div><div><div>15</div></div><div><span><span>  </span></span><span>fmt.</span><span>Printf</span><span>(</span><span>"Float: </span><span>%f\n</span><span>"</span><span>, f)</span></div></div><div><div><div>16</div></div><div><span>  </span><span>// %x 用于以十六进制格式化整数</span></div></div><div><div><div>17</div></div><div><span><span>  </span></span><span>fmt.</span><span>Printf</span><span>(</span><span>"Memory bits (hex): </span><span>%x\n</span><span>"</span><span>, bits)</span></div></div><div><div><div>18</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>输出：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>Float:</span><span> </span><span>1.234000</span></div></div><div><div><div>2</div></div><div><span>Memory</span><span> </span><span>bits</span><span> (hex): 3ff3be76c8b43958</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>危险的操作：指针运算<a href="#危险的操作指针运算"><span>#</span></a></h2><p>Go 语言不允许直接对指针进行 <code>+</code> 或 <code>-</code> 运算，但 <code>unsafe</code> 和 <code>uintptr</code> 结合可以做到这一点</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>package</span><span> </span><span>main</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>import</span><span> (</span></div></div><div><div><div>4</div></div><div><span>  </span><span>"</span><span>fmt</span><span>"</span></div></div><div><div><div>5</div></div><div><span>  </span><span>"</span><span>unsafe</span><span>"</span></div></div><div><div><div>6</div></div><div><span>)</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>9</div></div><div><span>  </span><span>// 假设一个数组</span></div></div><div><div><div>10</div></div><div><span><span>  </span></span><span>arr </span><span>:=</span><span> [</span><span>3</span><span>]</span><span>int</span><span>{</span><span>10</span><span>, </span><span>20</span><span>, </span><span>30</span><span>}</span></div></div><div><div><div>11</div></div><div>
</div></div><div><div><div>12</div></div><div><span>  </span><span>// 1. 获取第一个元素的指针</span></div></div><div><div><div>13</div></div><div><span><span>  </span></span><span>p0 </span><span>:=</span><span> unsafe.</span><span>Pointer</span><span>(</span><span>&amp;</span><span>arr[</span><span>0</span><span>])</span></div></div><div><div><div>14</div></div><div>
</div></div><div><div><div>15</div></div><div><span>  </span><span>// 2. 获取 int 类型的大小</span></div></div><div><div><div>16</div></div><div><span><span>  </span></span><span>intSize </span><span>:=</span><span> unsafe.</span><span>Sizeof</span><span>(arr[</span><span>0</span><span>]) </span><span>// 通常是 8 字节 (64位系统)</span></div></div><div><div><div>17</div></div><div>
</div></div><div><div><div>18</div></div><div><span>  </span><span>// 3. 计算第二个元素的地址</span></div></div><div><div><div>19</div></div><div><span>  </span><span>// unsafe.Pointer -&gt; uintptr (进行数学运算) -&gt; unsafe.Pointer</span></div></div><div><div><div>20</div></div><div><span><span>  </span></span><span>p1 </span><span>:=</span><span> unsafe.</span><span>Pointer</span><span>(</span><span>uintptr</span><span>(p0) </span><span>+</span><span> intSize)</span></div></div><div><div><div>21</div></div><div>
</div></div><div><div><div>22</div></div><div><span>  </span><span>// 4. 将 p1 转为 *int 并修改</span></div></div><div><div><div>23</div></div><div><span>  </span><span>*</span><span>(</span><span>*int</span><span>)(p1) </span><span>=</span><span> </span><span>200</span></div></div><div><div><div>24</div></div><div>
</div></div><div><div><div>25</div></div><div><span><span>  </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"修改后的数组:"</span><span>, arr)</span></div></div><div><div><div>26</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>输出：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>修改后的数组:</span><span> [10 </span><span>200</span><span> </span><span>30]</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>使用 <code>unsafe</code> 的时机<a href="#使用-unsafe-的时机"><span>#</span></a></h2><p>使用 <code>unsafe</code> 意味着你向编译器保证你的操作是安全的，但编译器无法验证。这可能导致程序崩溃、内存泄露或不可预测的行为。它的主要用途是：</p><ol>
<li>极端的性能优化（如 <code>string</code> 和 <code>[]byte</code> 的零拷贝转换）</li>
<li>与 C 语言交互（CGo）</li>
<li>探究 Go 语言的底层数据结构（如 <code>SliceHeader</code>）</li>
</ol><hr /></section></section>
<section><h1>16 SliceHeader<a href="#16-sliceheader"><span>#</span></a></h1><p><code>slice</code>（切片）是 Go 中最常用的数据结构之一。它之所以高效，关键在于它的内部结构：<code>slice</code> 只是一个指向底层数组的轻量级描述符</p><section><h2><code>slice</code> 的内部结构<a href="#slice-的内部结构"><span>#</span></a></h2><p><code>slice</code> 变量本身并不存储数据，它只包含三个字段。这个结构在 <code>reflect</code> 包中被定义为 <code>SliceHeader</code>：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>type</span><span> </span><span>SliceHeader</span><span> </span><span>struct</span><span> {</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>Data </span><span>uintptr</span><span> </span><span>// 指向底层数组的指针</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>Len  </span><span>int</span><span>     </span><span>// 切片的长度 (当前元素个数)</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>Cap  </span><span>int</span><span>     </span><span>// 切片的容量 (底层数组的总大小)</span></div></div><div><div><div>5</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2><code>slice</code> 如何实现高效切片<a href="#slice-如何实现高效切片"><span>#</span></a></h2><p>当你对一个 <code>slice</code> 进行“切片操作”时（例如 <code>s[2:5]</code>），Go 并不复制数据。它只是创建了一个新的 <code>SliceHeader</code>，将其 <code>Data</code> 指针指向原始 <code>slice</code> 的相应位置，并设置新的 <code>Len</code> 和 <code>Cap</code></p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>package</span><span> </span><span>main</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>import</span><span> (</span></div></div><div><div><div>4</div></div><div><span>  </span><span>"</span><span>fmt</span><span>"</span></div></div><div><div><div>5</div></div><div><span>  </span><span>"</span><span>reflect</span><span>"</span></div></div><div><div><div>6</div></div><div><span>  </span><span>"</span><span>unsafe</span><span>"</span></div></div><div><div><div>7</div></div><div><span>)</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>10</div></div><div><span><span>  </span></span><span>s1 </span><span>:=</span><span> []</span><span>int</span><span>{</span><span>10</span><span>, </span><span>20</span><span>, </span><span>30</span><span>, </span><span>40</span><span>, </span><span>50</span><span>}</span></div></div><div><div><div>11</div></div><div>
</div></div><div><div><div>12</div></div><div><span>  </span><span>// 对 s1 进行切片</span></div></div><div><div><div>13</div></div><div><span><span>  </span></span><span>s2 </span><span>:=</span><span> s1[</span><span>2</span><span>:</span><span>4</span><span>] </span><span>// s2 包含 {30, 40}</span></div></div><div><div><div>14</div></div><div>
</div></div><div><div><div>15</div></div><div><span>  </span><span>// 使用 unsafe 查看它们的头部</span></div></div><div><div><div>16</div></div><div><span><span>  </span></span><span>hdr1 </span><span>:=</span><span> (</span><span>*</span><span>reflect</span><span>.</span><span>SliceHeader</span><span>)(unsafe.</span><span>Pointer</span><span>(</span><span>&amp;</span><span>s1))</span></div></div><div><div><div>17</div></div><div><span><span>  </span></span><span>hdr2 </span><span>:=</span><span> (</span><span>*</span><span>reflect</span><span>.</span><span>SliceHeader</span><span>)(unsafe.</span><span>Pointer</span><span>(</span><span>&amp;</span><span>s2))</span></div></div><div><div><div>18</div></div><div>
</div></div><div><div><div>19</div></div><div><span><span>  </span></span><span>fmt.</span><span>Printf</span><span>(</span><span>"s1: Data=</span><span>%v</span><span>, Len=</span><span>%d</span><span>, Cap=</span><span>%d\n</span><span>"</span><span>, hdr1.Data, hdr1.Len, hdr1.Cap)</span></div></div><div><div><div>20</div></div><div><span><span>  </span></span><span>fmt.</span><span>Printf</span><span>(</span><span>"s2: Data=</span><span>%v</span><span>, Len=</span><span>%d</span><span>, Cap=</span><span>%d\n</span><span>"</span><span>, hdr2.Data, hdr2.Len, hdr2.Cap)</span></div></div><div><div><div>21</div></div><div>
</div></div><div><div><div>22</div></div><div><span>  </span><span>// s2 的 Data 指针 = s1 的 Data 指针 + 2 * (int的大小)</span></div></div><div><div><div>23</div></div><div>
</div></div><div><div><div>24</div></div><div><span>  </span><span>// 修改 s2 会影响 s1</span></div></div><div><div><div>25</div></div><div><span><span>  </span></span><span>s2[</span><span>0</span><span>] </span><span>=</span><span> </span><span>300</span></div></div><div><div><div>26</div></div><div><span><span>  </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"修改 s2 后, s1 变为:"</span><span>, s1)</span></div></div><div><div><div>27</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>输出（Data 地址会变化）：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>s1:</span><span> </span><span>Data=824633884688,</span><span> </span><span>Len=5,</span><span> </span><span>Cap=</span><span>5</span></div></div><div><div><div>2</div></div><div><span>s2:</span><span> </span><span>Data=824633884704,</span><span> </span><span>Len=2,</span><span> </span><span>Cap=</span><span>3</span></div></div><div><div><div>3</div></div><div><span>修改</span><span> </span><span>s2</span><span> </span><span>后,</span><span> </span><span>s1</span><span> </span><span>变为:</span><span> [10 </span><span>20</span><span> </span><span>300</span><span> </span><span>40</span><span> </span><span>50]</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2><code>slice</code> 的扩容机制<a href="#slice-的扩容机制"><span>#</span></a></h2><p>当你使用 <code>append</code> 向 <code>slice</code> 添加元素时，如果 <code>Len</code> 超过了 <code>Cap</code>，Go 会自动触发扩容：</p><ol>
<li>分配一个<strong>新的、更大的</strong>底层数组</li>
<li>将旧数组的数据<strong>复制</strong>到新数组</li>
<li>更新 <code>SliceHeader</code> 指向新数组</li>
</ol><p>这就是为什么 <code>append</code> 可能会很昂贵，也是为什么 <code>append</code> 必须返回一个新的 <code>slice</code>（因为 <code>SliceHeader</code> 可能已改变）</p></section><section><h2><code>string</code> 和 <code>[]byte</code> 的零拷贝转换<a href="#string-和-byte-的零拷贝转换"><span>#</span></a></h2><p><code>string</code> 的内部结构 <code>StringHeader</code> 类似于 <code>SliceHeader</code>，但只有 <code>Data</code> 和 <code>Len</code>（因为 <code>string</code> 是不可变的）</p><p>在性能敏感的场景下，可以使用 <code>unsafe</code> 在它们之间进行<strong>零拷贝</strong>转换，避免内存分配和复制。但这非常危险：如果将 <code>string</code> 转为 <code>[]byte</code> 并修改了字节，将破坏 <code>string</code> 的不可变性</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> </span><span>stringToBytes</span><span>(</span><span>s</span><span> </span><span>string</span><span>) []</span><span>byte</span><span> {</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>strHdr </span><span>:=</span><span> (</span><span>*</span><span>reflect</span><span>.</span><span>StringHeader</span><span>)(unsafe.</span><span>Pointer</span><span>(</span><span>&amp;</span><span>s))</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>  </span><span>// 构造一个 SliceHeader，共用 Data 指针</span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>sliceHdr </span><span>:=</span><span> </span><span>reflect</span><span>.</span><span>SliceHeader</span><span>{</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>Data: strHdr.Data,</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>Len:  strHdr.Len,</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>Cap:  strHdr.Len,</span></div></div><div><div><div>9</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span>  </span><span>// 将 SliceHeader 转为 []byte</span></div></div><div><div><div>12</div></div><div><span>  </span><span>return</span><span> </span><span>*</span><span>(</span><span>*</span><span>[]</span><span>byte</span><span>)(unsafe.</span><span>Pointer</span><span>(</span><span>&amp;</span><span>sliceHdr))</span></div></div><div><div><div>13</div></div><div><span>}</span></div></div><div><div><div>14</div></div><div>
</div></div><div><div><div>15</div></div><div><span>// 注意：这是一个危险的只读转换</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><hr /></section></section>
<section><h1>17 质量保证<a href="#17-质量保证"><span>#</span></a></h1><p>Go 语言在设计时就深度集成了质量保证工具。<code>go test</code> 命令是 Go 开发者最常用的工具之一，它支持多种测试类型，确保代码的健壮性和正确性</p><section><h2>单元测试 (Unit Testing)<a href="#单元测试-unit-testing"><span>#</span></a></h2><p>单元测试是最基础的测试，用于验证特定函数或模块的功能</p><ul>
<li>文件必须以 <code>_test.go</code> 结尾</li>
<li>函数必须以 <code>Test</code> 开头，并接受 <code>*testing.T</code> 参数</li>
</ul><p><code>math.go</code> (被测试代码):</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>package</span><span> </span><span>mathops</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>func</span><span> </span><span>Add</span><span>(</span><span>a</span><span>, </span><span>b</span><span> </span><span>int</span><span>) </span><span>int</span><span> {</span></div></div><div><div><div>4</div></div><div><span>  </span><span>return</span><span> a </span><span>+</span><span> b</span></div></div><div><div><div>5</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><code>math_test.go</code> (测试代码):</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>package</span><span> </span><span>mathops</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>import</span><span> </span><span>"</span><span>testing</span><span>"</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>func</span><span> </span><span>TestAdd</span><span>(</span><span>t</span><span> </span><span>*</span><span>testing</span><span>.</span><span>T</span><span>) {</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>result </span><span>:=</span><span> </span><span>Add</span><span>(</span><span>2</span><span>, </span><span>3</span><span>)</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>expected </span><span>:=</span><span> </span><span>5</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>  </span><span>if</span><span> result </span><span>!=</span><span> expected {</span></div></div><div><div><div>10</div></div><div><span>    </span><span>// t.Errorf 会记录错误并继续执行</span></div></div><div><div><div>11</div></div><div><span><span>    </span></span><span>t.</span><span>Errorf</span><span>(</span><span>"Add(2, 3) = </span><span>%d</span><span>; want </span><span>%d</span><span>"</span><span>, result, expected)</span></div></div><div><div><div>12</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>13</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>运行测试：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>$</span><span> </span><span>go</span><span> </span><span>test</span><span> </span><span>./...</span></div></div><div><div><div>2</div></div><div><span>PASS</span></div></div><div><div><div>3</div></div><div><span>ok</span><span>    </span><span>example.com/mathops</span><span>  </span><span>0.002s</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>基准测试 (Benchmark Testing)<a href="#基准测试-benchmark-testing"><span>#</span></a></h2><p>基准测试用于衡量代码的性能</p><ul>
<li>函数必须以 <code>Benchmark</code> 开头，并接受 <code>*testing.B</code> 参数</li>
<li><code>b.N</code> 是测试框架动态调整的循环次数</li>
</ul><p><code>math_test.go</code>:</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> </span><span>BenchmarkAdd</span><span>(</span><span>b</span><span> </span><span>*</span><span>testing</span><span>.</span><span>B</span><span>) {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>for</span><span> i </span><span>:=</span><span> </span><span>0</span><span>; i </span><span>&lt;</span><span> b.N; i</span><span>++</span><span> {</span></div></div><div><div><div>3</div></div><div><span>    </span><span>Add</span><span>(</span><span>100</span><span>, </span><span>200</span><span>)</span></div></div><div><div><div>4</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>5</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>运行基准测试 (使用 <code>-bench=.</code>):</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>$</span><span> </span><span>go</span><span> </span><span>test</span><span> </span><span>-bench=.</span></div></div><div><div><div>2</div></div><div><span>goos:</span><span> </span><span>darwin</span></div></div><div><div><div>3</div></div><div><span>goarch:</span><span> </span><span>arm64</span></div></div><div><div><div>4</div></div><div><span>pkg:</span><span> </span><span>example.com/mathops</span></div></div><div><div><div>5</div></div><div><span>BenchmarkAdd-10</span><span>      </span><span>1000000000</span><span>           </span><span>0.2825</span><span> </span><span>ns/op</span></div></div><div><div><div>6</div></div><div><span>PASS</span></div></div><div><div><div>7</div></div><div><span>ok</span><span>    </span><span>example.com/mathops</span><span>  </span><span>0.315s</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>示例测试 (Example Tests)<a href="#示例测试-example-tests"><span>#</span></a></h2><p>示例测试既是文档（会显示在 Godoc 中），也是可执行的测试</p><ul>
<li>函数以 <code>Example</code> 开头</li>
<li>使用 <code>// Output:</code> 注释来指定预期的标准输出</li>
</ul><p><code>example_test.go</code>:</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>package</span><span> </span><span>mathops</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>import</span><span> </span><span>"</span><span>fmt</span><span>"</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>func</span><span> </span><span>ExampleAdd</span><span>() {</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>sum </span><span>:=</span><span> </span><span>Add</span><span>(</span><span>5</span><span>, </span><span>10</span><span>)</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>fmt.</span><span>Println</span><span>(sum)</span></div></div><div><div><div>8</div></div><div><span>  </span><span>// Output: 15</span></div></div><div><div><div>9</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>代码覆盖率 (Code Coverage)<a href="#代码覆盖率-code-coverage"><span>#</span></a></h2><p>Go 提供了测量测试覆盖率的工具，帮助我们识别哪些代码没有被测试到</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>$</span><span> </span><span>go</span><span> </span><span>test</span><span> </span><span>-cover</span></div></div><div><div><div>2</div></div><div><span>PASS</span></div></div><div><div><div>3</div></div><div><span>coverage:</span><span> </span><span>100.0%</span><span> </span><span>of</span><span> </span><span>statements</span></div></div><div><div><div>4</div></div><div><span>ok</span><span>    </span><span>example.com/mathops</span><span>  </span><span>0.014s</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>竞争检测 (Race Detector)<a href="#竞争检测-race-detector"><span>#</span></a></h2><p>Go 的并发特性很容易引入数据竞争。<code>Race Detector</code> 是一个在运行时检测数据竞争的工具</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span># 假设我们有一个并发不安全的测试</span></div></div><div><div><div>2</div></div><div><span>$</span><span> </span><span>go</span><span> </span><span>test</span><span> </span><span>-race</span></div></div><div><div><div>3</div></div><div><span>==================</span></div></div><div><div><div>4</div></div><div><span>WARNING:</span><span> </span><span>DATA</span><span> </span><span>RACE</span></div></div><div><div><div>5</div></div><div><span>Read</span><span> </span><span>at</span><span> </span><span>0x000109f3e1b0</span><span> </span><span>by</span><span> </span><span>goroutine</span><span> </span><span>8</span></div></div><div><div><div>6</div></div><div><span>...</span></div></div><div><div><div>7</div></div><div><span>Previous</span><span> </span><span>write</span><span> </span><span>at</span><span> </span><span>0x000109f3e1b0</span><span> </span><span>by</span><span> </span><span>goroutine</span><span> </span><span>7</span></div></div><div><div><div>8</div></div><div><span>==================</span></div></div><div><div><div>9</div></div><div><span>---</span><span> </span><span>FAIL:</span><span> </span><span>TestUnsafeCounter</span><span> (1.01s)</span></div></div><div><div><div>10</div></div><div><span>FAIL</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>模糊测试 (Fuzz Testing)<a href="#模糊测试-fuzz-testing"><span>#</span></a></h2><p>Go 1.18 引入了模糊测试（Fuzz Testing），它会自动生成随机输入来测试代码，以发现边缘情况和安全漏洞</p><ul>
<li>函数必须以 <code>Fuzz</code> 开头，并接受 <code>*testing.F</code> 参数</li>
</ul><hr /></section></section>
<section><h1>18 性能优化<a href="#18-性能优化"><span>#</span></a></h1><p>Go 语言的性能优化不是靠猜，而是靠测量。Go 提供了强大的静态检查和运行时分析工具（pprof），帮助开发者定位瓶颈</p><section><h2>静态检查：<code>go vet</code><a href="#静态检查go-vet"><span>#</span></a></h2><p><code>go vet</code> 是 Go 内置的静态分析工具，它在编译前检查代码，寻找可疑的构造或常见的错误，这些错误可能会导致 bug 或性能问题</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>// 示例：一个常见的错误</span></div></div><div><div><div>2</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>3</div></div><div><span>  </span><span>// ...</span></div></div><div><div><div>4</div></div><div><span>  </span><span>// 错误：在循环中使用了 defer</span></div></div><div><div><div>5</div></div><div><span>  </span><span>for</span><span> i </span><span>:=</span><span> </span><span>0</span><span>; i </span><span>&lt;</span><span> </span><span>1000</span><span>; i</span><span>++</span><span> {</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>mu.</span><span>Lock</span><span>()</span></div></div><div><div><div>7</div></div><div><span>    </span><span>defer</span><span> mu.</span><span>Unlock</span><span>() </span><span>// vet 会警告这个</span></div></div><div><div><div>8</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>9</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>运行 <code>go vet</code>：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>$</span><span> </span><span>go</span><span> </span><span>vet</span><span> </span><span>.</span></div></div><div><div><div>2</div></div><div><span># command-line-arguments</span></div></div><div><div><div>3</div></div><div><span>./main.go:10:10:</span><span> </span><span>defer</span><span> </span><span>in</span><span> </span><span>loop</span><span> </span><span>flows</span><span> </span><span>out</span><span> </span><span>of</span><span> </span><span>loop</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>Linter 聚合器：<code>golangci-lint</code><a href="#linter-聚合器golangci-lint"><span>#</span></a></h2><p><code>golangci-lint</code> 是一个流行的第三方 Linter 聚合器，它集成了数十种检查工具（包括 <code>vet</code>），提供了更全面的代码质量和潜在性能问题的分析</p></section><section><h2>性能分析：<code>pprof</code><a href="#性能分析pprof"><span>#</span></a></h2><p><code>pprof</code> 是 Go 语言的性能分析（Profiling）工具集，是性能优化的基石。它可以在运行时收集数据，分析程序的 CPU、内存、Goroutine 等使用情况</p><p><strong>集成 pprof</strong></p><p>对于 Web 服务器，只需匿名导入 <code>net/http/pprof</code> 包</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>import</span><span> (</span></div></div><div><div><div>2</div></div><div><span>  </span><span>"</span><span>net/http</span><span>"</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>_ </span><span>"</span><span>net/http/pprof</span><span>"</span><span> </span><span>// 匿名导入，自动注册 pprof 的 handler</span></div></div><div><div><div>4</div></div><div><span>)</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>7</div></div><div><span>  </span><span>// ... 你的业务 handler</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>  </span><span>// pprof 会在 /debug/pprof/ 路径下提供服务</span></div></div><div><div><div>10</div></div><div><span><span>  </span></span><span>http.</span><span>ListenAndServe</span><span>(</span><span>":8080"</span><span>, </span><span>nil</span><span>)</span></div></div><div><div><div>11</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>CPU 分析 (CPU Profiling)<a href="#cpu-分析-cpu-profiling"><span>#</span></a></h2><p>我们可以使用 <code>go tool pprof</code> 来分析 CPU 瓶颈：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span># 采样 30 秒的 CPU 数据</span></div></div><div><div><div>2</div></div><div><span>$</span><span> </span><span>go</span><span> </span><span>tool</span><span> </span><span>pprof</span><span> </span><span>http://localhost:8080/debug/pprof/profile?seconds=</span><span>30</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>(</span><span>pprof</span><span>) </span><span>top</span></div></div><div><div><div>5</div></div><div><span># (pprof) 终端中输入 top，显示占用 CPU 最多的函数</span></div></div><div><div><div>6</div></div><div><span>Showing</span><span> </span><span>top</span><span> </span><span>10</span><span> </span><span>nodes</span><span> </span><span>out</span><span> </span><span>of</span><span> </span><span>120</span></div></div><div><div><div>7</div></div><div><span>      </span><span>flat</span><span>  </span><span>flat%</span><span>   </span><span>sum%</span><span>        </span><span>cum</span><span>   </span><span>cum%</span></div></div><div><div><div>8</div></div><div><span>     </span><span>5.10s</span><span> </span><span>20.00%</span><span> </span><span>20.00%</span><span>      </span><span>8.20s</span><span> </span><span>32.16%</span><span>  </span><span>main.busyLoop</span></div></div><div><div><div>9</div></div><div><span>     </span><span>...</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>内存分析 (Memory Profiling)<a href="#内存分析-memory-profiling"><span>#</span></a></h2><p><code>pprof</code> 也可以分析堆内存分配：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span># 分析当前堆内存使用</span></div></div><div><div><div>2</div></div><div><span>$</span><span> </span><span>go</span><span> </span><span>tool</span><span> </span><span>pprof</span><span> </span><span>http://localhost:8080/debug/pprof/heap</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>(</span><span>pprof</span><span>) </span><span>top</span></div></div><div><div><div>5</div></div><div><span>Showing</span><span> </span><span>top</span><span> </span><span>10</span><span> </span><span>nodes</span><span> </span><span>out</span><span> </span><span>of</span><span> </span><span>80</span></div></div><div><div><div>6</div></div><div><span>      </span><span>flat</span><span>  </span><span>flat%</span><span>   </span><span>sum%</span><span>        </span><span>cum</span><span>   </span><span>cum%</span></div></div><div><div><div>7</div></div><div><span>   </span><span>1024MB</span><span> </span><span>33.33%</span><span> </span><span>33.33%</span><span>     </span><span>1024MB</span><span> </span><span>33.33%</span><span>  </span><span>main.bigAllocation</span></div></div><div><div><div>8</div></div><div><span>   </span><span>...</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>火焰图 (Flame Graphs)<a href="#火焰图-flame-graphs"><span>#</span></a></h2><p><code>pprof</code> 还能生成可视化的火焰图，帮助我们直观地看到函数调用链和性能热点</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>(</span><span>pprof</span><span>) </span><span>web</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2><code>sync.Pool</code>：复用对象<a href="#syncpool复用对象"><span>#</span></a></h2><p>减少内存分配是 Go 性能优化的关键手段之一。对于那些频繁创建和销毁的临时对象，使用 <code>sync.Pool</code> 可以复用它们，大大减轻 GC 压力</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>var</span><span> bigBufferPool </span><span>=</span><span> </span><span>sync</span><span>.</span><span>Pool</span><span>{</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>New: </span><span>func</span><span>() </span><span>interface</span><span>{} {</span></div></div><div><div><div>3</div></div><div><span>    </span><span>// 池中没有时，创建一个新的</span></div></div><div><div><div>4</div></div><div><span>    </span><span>return</span><span> </span><span>make</span><span>([]</span><span>byte</span><span>, </span><span>1024</span><span>*</span><span>64</span><span>) </span><span>// 64KB</span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>},</span></div></div><div><div><div>6</div></div><div><span>}</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>func</span><span> </span><span>ProcessRequest</span><span>(</span><span>w</span><span> </span><span>http</span><span>.</span><span>ResponseWriter</span><span>, </span><span>r</span><span> </span><span>*</span><span>http</span><span>.</span><span>Request</span><span>) {</span></div></div><div><div><div>9</div></div><div><span>  </span><span>// 从池中获取</span></div></div><div><div><div>10</div></div><div><span><span>  </span></span><span>buf </span><span>:=</span><span> bigBufferPool.</span><span>Get</span><span>().([]</span><span>byte</span><span>)</span></div></div><div><div><div>11</div></div><div>
</div></div><div><div><div>12</div></div><div><span>  </span><span>// 使用 buf ...</span></div></div><div><div><div>13</div></div><div>
</div></div><div><div><div>14</div></div><div><span>  </span><span>// 归还到池中</span></div></div><div><div><div>15</div></div><div><span>  </span><span>// 注意：需要重置 buf 的状态（如果需要）</span></div></div><div><div><div>16</div></div><div><span><span>  </span></span><span>bigBufferPool.</span><span>Put</span><span>(buf)</span></div></div><div><div><div>17</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><hr /></section></section>
<section><h1>19 协作开发<a href="#19-协作开发"><span>#</span></a></h1><p>在 Go 1.11 之前，Go 语言使用 <code>GOPATH</code> 来管理依赖，导致了“依赖地狱”问题。Go Modules（模块化管理）的出现彻底解决了这个问题，极大地提升了协作开发的效率和项目的可维护性</p><section><h2><code>GOPATH</code> 的困境<a href="#gopath-的困境"><span>#</span></a></h2><p><code>GOPATH</code> 要求所有项目和依赖都存放在同一个工作空间。这导致：</p><ol>
<li><strong>版本冲突</strong>：项目 A 依赖 <code>lib v1.0</code>，项目 B 依赖 <code>lib v2.0</code>，<code>GOPATH</code> 下只能存一个版本</li>
<li><strong>无法复现</strong>：新开发者拉取代码，<code>go get</code> 下载的是最新依赖，可能导致构建失败</li>
</ol></section><section><h2>Go Modules 简介<a href="#go-modules-简介"><span>#</span></a></h2><p>Go Modules 允许项目独立于 <code>GOPATH</code> 存在，并实现了可复现的依赖管理</p><p><strong><code>go.mod</code> 文件</strong></p><p><code>go.mod</code> 是 Go Modules 的核心，它定义了模块的路径、Go 版本以及所有依赖项及其<strong>精确版本</strong></p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>module example.com</span><span>/</span><span>myproject</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>go</span><span> </span><span>1.21</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>require (</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>github.com</span><span>/</span><span>gin</span><span>-</span><span>gonic</span><span>/</span><span>gin v1.</span><span>9.1</span><span> </span><span>// 直接依赖</span></div></div><div><div><div>7</div></div><div><span>)</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>require (</span></div></div><div><div><div>10</div></div><div><span><span>  </span></span><span>github.com</span><span>/</span><span>modern</span><span>-go/</span><span>concurrent v0.</span><span>0.0</span><span>-</span><span>20180306012644</span><span>-</span><span>bacd9c7ef1dd </span><span>// indirect</span></div></div><div><div><div>11</div></div><div><span>  </span><span>// ... 其他间接依赖</span></div></div><div><div><div>12</div></div><div><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong><code>go.sum</code> 文件</strong></p><p><code>go.sum</code> 文件记录了每个依赖包的哈希校验和，确保你下载的依赖没有被篡改，保证了构建的安全性</p></section><section><h2>模块化管理如何提升协作<a href="#模块化管理如何提升协作"><span>#</span></a></h2><ol>
<li><strong>可复现构建</strong>：<code>go.mod</code> 和 <code>go.sum</code> 锁定了所有依赖（包括间接依赖）的精确版本。任何开发者、任何 CI/CD 平台，只要克隆了代码并执行 <code>go build</code>，都会下载完全相同的依赖，保证了构建的一致性</li>
<li><strong>语义化版本</strong>：Go Modules 遵循语义化版本（SemVer），允许开发者明确地升级（<code>go get lib@v1.2.0</code>）或降级依赖</li>
<li><strong>摆脱 <code>GOPATH</code></strong>：项目可以放在磁盘的任何位置，不再受单一工作空间的限制</li>
</ol></section><section><h2>统一的代码风格：<code>gofmt</code><a href="#统一的代码风格gofmt"><span>#</span></a></h2><p>Go 通过 <code>gofmt</code> 工具强制推行统一的代码风格。它会自动格式化代码，消除了团队中关于“花括号放哪里”的无休止争论</p><p><code>goimports</code> 是 <code>gofmt</code> 的超集，它在格式化代码的同时，还会自动管理（添加/删除）<code>import</code> 声明</p><p>协作开发流程中，通常会要求所有提交的代码必须通过 <code>gofmt</code> 检查</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span># 格式化当前目录所有文件</span></div></div><div><div><div>2</div></div><div><span>$</span><span> </span><span>gofmt</span><span> </span><span>-w</span><span> </span><span>.</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span># (推荐) 使用 goimports</span></div></div><div><div><div>5</div></div><div><span>$</span><span> </span><span>goimports</span><span> </span><span>-w</span><span> </span><span>.</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section></section>
<section><h1>20 网络编程<a href="#20-网络编程"><span>#</span></a></h1><p>Go 语言在网络编程方面拥有“天生”的优势，其 <code>net/http</code> 包足以构建高性能的 RESTful API，同时其内置的 <code>net/rpc</code> 包和流行的 <code>gRPC</code> 框架也使其成为 RPC 开发的利器</p><section><h2><code>net/http</code> 构建 RESTful API<a href="#nethttp-构建-restful-api"><span>#</span></a></h2><p>Go 的标准库 <code>net/http</code> 非常强大，无需任何框架即可构建生产级的 Web 服务</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>package</span><span> </span><span>main</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>import</span><span> (</span></div></div><div><div><div>4</div></div><div><span>  </span><span>"</span><span>encoding/json</span><span>"</span></div></div><div><div><div>5</div></div><div><span>  </span><span>"</span><span>fmt</span><span>"</span></div></div><div><div><div>6</div></div><div><span>  </span><span>"</span><span>net/http</span><span>"</span></div></div><div><div><div>7</div></div><div><span>)</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>// 定义数据结构</span></div></div><div><div><div>10</div></div><div><span>type</span><span> </span><span>Article</span><span> </span><span>struct</span><span> {</span></div></div><div><div><div>11</div></div><div><span><span>  </span></span><span>ID    </span><span>int</span><span>    </span><span>`json:"id"`</span></div></div><div><div><div>12</div></div><div><span><span>  </span></span><span>Title </span><span>string</span><span> </span><span>`json:"title"`</span></div></div><div><div><div>13</div></div><div><span>}</span></div></div><div><div><div>14</div></div><div>
</div></div><div><div><div>15</div></div><div><span>var</span><span> articles </span><span>=</span><span> []</span><span>Article</span><span>{</span></div></div><div><div><div>16</div></div><div><span><span>  </span></span><span>{ID: </span><span>1</span><span>, Title: </span><span>"Hello Go"</span><span>},</span></div></div><div><div><div>17</div></div><div><span><span>  </span></span><span>{ID: </span><span>2</span><span>, Title: </span><span>"RESTful API"</span><span>},</span></div></div><div><div><div>18</div></div><div><span>}</span></div></div><div><div><div>19</div></div><div>
</div></div><div><div><div>20</div></div><div><span>// 路由与 Handler</span></div></div><div><div><div>21</div></div><div><span>func</span><span> </span><span>articlesHandler</span><span>(</span><span>w</span><span> </span><span>http</span><span>.</span><span>ResponseWriter</span><span>, </span><span>r</span><span> </span><span>*</span><span>http</span><span>.</span><span>Request</span><span>) {</span></div></div><div><div><div>22</div></div><div><span>  </span><span>switch</span><span> r.Method {</span></div></div><div><div><div>23</div></div><div><span>  </span><span>case</span><span> http.MethodGet:</span></div></div><div><div><div>24</div></div><div><span>    </span><span>// 处理 JSON 数据</span></div></div><div><div><div>25</div></div><div><span><span>    </span></span><span>w.</span><span>Header</span><span>().</span><span>Set</span><span>(</span><span>"Content-Type"</span><span>, </span><span>"application/json"</span><span>)</span></div></div><div><div><div>26</div></div><div><span><span>    </span></span><span>json.</span><span>NewEncoder</span><span>(w).</span><span>Encode</span><span>(articles)</span></div></div><div><div><div>27</div></div><div>
</div></div><div><div><div>28</div></div><div><span>  </span><span>case</span><span> http.MethodPost:</span></div></div><div><div><div>29</div></div><div><span>    </span><span>// (此处应添加 Post 逻辑)</span></div></div><div><div><div>30</div></div><div><span><span>    </span></span><span>w.</span><span>WriteHeader</span><span>(http.StatusCreated)</span></div></div><div><div><div>31</div></div><div>
</div></div><div><div><div>32</div></div><div><span>  </span><span>default</span><span>:</span></div></div><div><div><div>33</div></div><div><span><span>    </span></span><span>w.</span><span>WriteHeader</span><span>(http.StatusMethodNotAllowed)</span></div></div><div><div><div>34</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>35</div></div><div><span>}</span></div></div><div><div><div>36</div></div><div>
</div></div><div><div><div>37</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>38</div></div><div><span><span>  </span></span><span>http.</span><span>HandleFunc</span><span>(</span><span>"/api/articles"</span><span>, articlesHandler)</span></div></div><div><div><div>39</div></div><div>
</div></div><div><div><div>40</div></div><div><span><span>  </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"Starting RESTful API server on :8080"</span><span>)</span></div></div><div><div><div>41</div></div><div><span><span>  </span></span><span>http.</span><span>ListenAndServe</span><span>(</span><span>":8080"</span><span>, </span><span>nil</span><span>)</span></div></div><div><div><div>42</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section><section><h2>什么是 RPC<a href="#什么是-rpc"><span>#</span></a></h2><p>RPC (Remote Procedure Call，远程过程调用) 允许一个程序调用另一个地址空间（通常是另一台机器）的函数或方法，就像调用本地函数一样。它隐藏了底层的网络通信细节</p></section><section><h2>Go 的 <code>net/rpc</code><a href="#go-的-netrpc"><span>#</span></a></h2><p>Go 标准库提供了 <code>net/rpc</code> 包，用于实现简单、高效的 RPC</p><p><strong><code>net/rpc</code> 服务端</strong></p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>package</span><span> </span><span>main</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>import</span><span> (</span></div></div><div><div><div>4</div></div><div><span>  </span><span>"</span><span>fmt</span><span>"</span></div></div><div><div><div>5</div></div><div><span>  </span><span>"</span><span>net</span><span>"</span></div></div><div><div><div>6</div></div><div><span>  </span><span>"</span><span>net/rpc</span><span>"</span></div></div><div><div><div>7</div></div><div><span>)</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>// RPC 服务结构体</span></div></div><div><div><div>10</div></div><div><span>type</span><span> </span><span>Arith</span><span> </span><span>struct</span><span>{}</span></div></div><div><div><div>11</div></div><div>
</div></div><div><div><div>12</div></div><div><span>// RPC 方法（必须导出，参数和返回值符合规范）</span></div></div><div><div><div>13</div></div><div><span>func</span><span> (</span><span>t </span><span>*</span><span>Arith</span><span>) </span><span>Multiply</span><span>(</span><span>args</span><span> </span><span>*</span><span>Args</span><span>, </span><span>reply</span><span> </span><span>*int</span><span>) </span><span>error</span><span> {</span></div></div><div><div><div>14</div></div><div><span>  </span><span>*</span><span>reply </span><span>=</span><span> args.A </span><span>*</span><span> args.B</span></div></div><div><div><div>15</div></div><div><span>  </span><span>return</span><span> </span><span>nil</span></div></div><div><div><div>16</div></div><div><span>}</span></div></div><div><div><div>17</div></div><div>
</div></div><div><div><div>18</div></div><div><span>type</span><span> </span><span>Args</span><span> </span><span>struct</span><span> {</span></div></div><div><div><div>19</div></div><div><span><span>  </span></span><span>A, B </span><span>int</span></div></div><div><div><div>20</div></div><div><span>}</span></div></div><div><div><div>21</div></div><div>
</div></div><div><div><div>22</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>23</div></div><div><span><span>  </span></span><span>arith </span><span>:=</span><span> </span><span>new</span><span>(</span><span>Arith</span><span>)</span></div></div><div><div><div>24</div></div><div><span><span>  </span></span><span>rpc.</span><span>Register</span><span>(arith) </span><span>// 注册服务</span></div></div><div><div><div>25</div></div><div>
</div></div><div><div><div>26</div></div><div><span><span>  </span></span><span>listener, _ </span><span>:=</span><span> net.</span><span>Listen</span><span>(</span><span>"tcp"</span><span>, </span><span>":1234"</span><span>)</span></div></div><div><div><div>27</div></div><div><span><span>  </span></span><span>fmt.</span><span>Println</span><span>(</span><span>"RPC server listening on :1234"</span><span>)</span></div></div><div><div><div>28</div></div><div>
</div></div><div><div><div>29</div></div><div><span>  </span><span>for</span><span> {</span></div></div><div><div><div>30</div></div><div><span><span>    </span></span><span>conn, _ </span><span>:=</span><span> listener.</span><span>Accept</span><span>()</span></div></div><div><div><div>31</div></div><div><span>    </span><span>go</span><span> rpc.</span><span>ServeConn</span><span>(conn) </span><span>// 为每个连接提供服务</span></div></div><div><div><div>32</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>33</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p><strong><code>net/rpc</code> 客户端</strong></p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>package</span><span> </span><span>main</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>import</span><span> (</span></div></div><div><div><div>4</div></div><div><span>  </span><span>"</span><span>fmt</span><span>"</span></div></div><div><div><div>5</div></div><div><span>  </span><span>"</span><span>net/rpc</span><span>"</span></div></div><div><div><div>6</div></div><div><span>)</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>type</span><span> </span><span>Args</span><span> </span><span>struct</span><span> {</span></div></div><div><div><div>9</div></div><div><span><span>  </span></span><span>A, B </span><span>int</span></div></div><div><div><div>10</div></div><div><span>}</span></div></div><div><div><div>11</div></div><div>
</div></div><div><div><div>12</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>13</div></div><div><span><span>  </span></span><span>client, err </span><span>:=</span><span> rpc.</span><span>Dial</span><span>(</span><span>"tcp"</span><span>, </span><span>"localhost:1234"</span><span>)</span></div></div><div><div><div>14</div></div><div><span>  </span><span>if</span><span> err </span><span>!=</span><span> </span><span>nil</span><span> {</span></div></div><div><div><div>15</div></div><div><span>    </span><span>panic</span><span>(err)</span></div></div><div><div><div>16</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>17</div></div><div>
</div></div><div><div><div>18</div></div><div><span><span>  </span></span><span>args </span><span>:=</span><span> </span><span>&amp;</span><span>Args</span><span>{A: </span><span>7</span><span>, B: </span><span>8</span><span>}</span></div></div><div><div><div>19</div></div><div><span>  </span><span>var</span><span> reply </span><span>int</span></div></div><div><div><div>20</div></div><div>
</div></div><div><div><div>21</div></div><div><span>  </span><span>// 像调用本地函数一样调用远程方法</span></div></div><div><div><div>22</div></div><div><span>  </span><span>// "Arith.Multiply" 是 "服务名.方法名"</span></div></div><div><div><div>23</div></div><div><span><span>  </span></span><span>err </span><span>=</span><span> client.</span><span>Call</span><span>(</span><span>"Arith.Multiply"</span><span>, args, </span><span>&amp;</span><span>reply)</span></div></div><div><div><div>24</div></div><div><span>  </span><span>if</span><span> err </span><span>!=</span><span> </span><span>nil</span><span> {</span></div></div><div><div><div>25</div></div><div><span>    </span><span>panic</span><span>(err)</span></div></div><div><div><div>26</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>27</div></div><div>
</div></div><div><div><div>28</div></div><div><span><span>  </span></span><span>fmt.</span><span>Printf</span><span>(</span><span>"Arith: </span><span>%d</span><span> * </span><span>%d</span><span> = </span><span>%d\n</span><span>"</span><span>, args.A, args.B, reply)</span></div></div><div><div><div>29</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>客户端输出：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>Arith:</span><span> </span><span>7</span><span> </span><span>*</span><span> </span><span>8</span><span> </span><span>=</span><span> </span><span>56</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>gRPC：现代 RPC 框架<a href="#grpc现代-rpc-框架"><span>#</span></a></h2><p>虽然 <code>net/rpc</code> 简单易用，但它只支持 Go 语言。在现代微服务架构中，<code>gRPC</code>（Google 开源的 RPC 框架）更为流行</p><p><code>gRPC</code> 使用 <code>Protocol Buffers</code> 作为接口定义语言（IDL），可以自动生成多语言（Java, Python, C++, Go…）的客户端和服务端代码，并提供了流式传输、认证、负载均衡等高级功能</p></section></section>]]></content>
    <author><name>Zowely</name></author>
    <category term="编程语言" />
    <category term="Golang" />
    <category term="编程语言" />
  </entry>
  <entry>
    <title>个人训练赛20241211</title>
    <link href="https://www.lansganbs.cn/posts/%E7%AE%97%E6%B3%95%E7%AB%9E%E8%B5%9B/%E4%B8%AA%E4%BA%BA%E8%AE%AD%E7%BB%83%E8%B5%9B20241211/" rel="alternate" type="text/html" />
    <id>https://www.lansganbs.cn/posts/%E7%AE%97%E6%B3%95%E7%AB%9E%E8%B5%9B/%E4%B8%AA%E4%BA%BA%E8%AE%AD%E7%BB%83%E8%B5%9B20241211/</id>
    <published>2024-12-31T00:00:00.000Z</published>
    <updated>2024-12-31T17:01:42.000Z</updated>
    <summary>2024秋哈尔滨理工大学高年级个人训练赛题解</summary>
    <content type="html"><![CDATA[<section><h1>个人训练赛20241211<a href="#个人训练赛20241211"><span>#</span></a></h1><ul>
<li><a href="#c-diamond-miner">C. Diamond Miner</a></li>
<li><a href="#c-mp3">C. MP3</a></li>
<li><a href="#d-nezzar-and-board">D. Nezzar and Board</a></li>
<li><a href="#e-hiking">E. Hiking</a></li>
</ul><hr /><section><h3><a href="https://codeforces.com/contest/489/problem/E" target="_blank">E. Hiking</a><a href="#e-hiking"><span>#</span></a></h3><p>Date：Aug.31, 2019 | Rating：2522 | Number of accepted&lt;66&gt;(1.53%) | Number of attempts&lt;185&gt;(4.30%) | Total&lt;4303&gt;</p><p>Tag：二分 动态规划 数论和线性代数</p><p>可以涉及的算法：二分 DP 01分数规划</p><p>读完题意就可以转化为尽量减少<span><span>∑∣l−r∣∑b\frac{\sum \sqrt{\left| l - r\right |}}{\sum b}</span><span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span><span>∑</span><span></span><span>b</span></span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>∑</span><span></span><span><span><span><span><span><span></span><span><span><span><span>∣</span></span><span>l</span><span>−</span><span>r</span><span><span>∣</span></span></span></span></span><span><span></span><span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span></span></span></span></p><p>下面介绍一下01分数规划问题：给定两个都包含<span><span>nn</span><span><span><span></span><span>n</span></span></span></span>个正整数的正数数列{<span><span>a1,a2,a3,...,ana_1,a_2,a_3,...,a_n</span><span><span><span></span><span><span>a</span><span><span><span><span><span><span></span><span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>,</span><span></span><span><span>a</span><span><span><span><span><span><span></span><span><span>2</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>,</span><span></span><span><span>a</span><span><span><span><span><span><span></span><span><span>3</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>,</span><span></span><span>...</span><span>,</span><span></span><span><span>a</span><span><span><span><span><span><span></span><span><span>n</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span>}和{<span><span>b1,b2,b3,...,bnb_1,b_2,b_3,...,b_n</span><span><span><span></span><span><span>b</span><span><span><span><span><span><span></span><span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>,</span><span></span><span><span>b</span><span><span><span><span><span><span></span><span><span>2</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>,</span><span></span><span><span>b</span><span><span><span><span><span><span></span><span><span>3</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>,</span><span></span><span>...</span><span>,</span><span></span><span><span>b</span><span><span><span><span><span><span></span><span><span>n</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span>}，同时选出k个a和b，求max<span><span>∑i=1naisi∑i=1nbisi\frac{\sum_{i=1}^{n}a_i s_i }{\sum_{i=1}^{n}b_i s_i }</span><span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span><span><span>∑</span><span><span><span><span><span><span></span><span><span><span>i</span><span>=</span><span>1</span></span></span></span><span><span></span><span><span><span>n</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span><span>b</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span><span>s</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span><span><span></span><span></span></span><span><span></span><span><span><span><span>∑</span><span><span><span><span><span><span></span><span><span><span>i</span><span>=</span><span>1</span></span></span></span><span><span></span><span><span><span>n</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span><span>a</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span><span>s</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span></span></span></span>，其中<span><span>si=1s_i=1</span><span><span><span></span><span><span>s</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>=</span><span></span></span><span><span></span><span>1</span></span></span></span>或<span><span>si=0s_i=0</span><span><span><span></span><span><span>s</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>=</span><span></span></span><span><span></span><span>0</span></span></span></span>表示选或不选第<span><span>ii</span><span><span><span></span><span>i</span></span></span></span>个数，且<span><span>∑i=1nsi=k\sum_{i=1}^{n} s_i=k</span><span><span><span></span><span><span>∑</span><span><span><span><span><span><span></span><span><span><span>i</span><span>=</span><span>1</span></span></span></span><span><span></span><span><span><span>n</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span><span>s</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>=</span><span></span></span><span><span></span><span>k</span></span></span></span>。</p><p>为了加快速度，我们可以用“猜”的方法，猜一个数<span><span>xx</span><span><span><span></span><span>x</span></span></span></span>，使</p><ul>
<li><span><span>∑i=1naisi∑i=1nbisi≥x\frac{\sum_{i=1}^{n}a_i s_i }{\sum_{i=1}^{n}b_i s_i } \ge x</span><span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span><span><span>∑</span><span><span><span><span><span><span></span><span><span><span>i</span><span>=</span><span>1</span></span></span></span><span><span></span><span><span><span>n</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span><span>b</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span><span>s</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span><span><span></span><span></span></span><span><span></span><span><span><span><span>∑</span><span><span><span><span><span><span></span><span><span><span>i</span><span>=</span><span>1</span></span></span></span><span><span></span><span><span><span>n</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span><span>a</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span><span>s</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span></span><span>≥</span><span></span></span><span><span></span><span>x</span></span></span></span></li>
</ul><p>移项得<span><span>f=∑i=1n(ai−xbi)≥kf = \sum_{i=1}^{n}(a_i - x b_i)\ge k</span><span><span><span></span><span>f</span><span></span><span>=</span><span></span></span><span><span></span><span><span>∑</span><span><span><span><span><span><span></span><span><span><span>i</span><span>=</span><span>1</span></span></span></span><span><span></span><span><span><span>n</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>(</span><span><span>a</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>−</span><span></span></span><span><span></span><span>x</span><span><span>b</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>)</span><span></span><span>≥</span><span></span></span><span><span></span><span>k</span></span></span></span></p><p>01分数规划有两种解决办法：二分法，Dinkelbach算法。</p><p>下面利用二分来找到零点：</p><p>我们需要最小化一个分式。经典的方法是将分式的最小化问题转化为参数<span><span>λ\lambda</span><span><span><span></span><span>λ</span></span></span></span>的函数，然后通过二分法寻找最优的<span><span>λ\lambda</span><span><span><span></span><span>λ</span></span></span></span>。</p><p>将原问题转化：</p><p>具体来说我们的目标是最小化：</p><p><span><span>总挫败感所用休息点的风景值总和=∑∣l−rj∣∑bj\frac{\text{总挫败感}}{\text{所用休息点的风景值总和}} = \frac{\sum \sqrt{|l - r_j|}}{\sum b_j}</span><span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span><span><span>所用休息点的风景值总和</span></span></span></span></span><span><span></span><span></span></span><span><span></span><span><span><span><span>总挫败感</span></span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span></span><span>=</span><span></span></span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span><span>∑</span><span></span><span><span>b</span><span><span><span><span><span><span></span><span><span>j</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>∑</span><span></span><span><span><span><span><span><span></span><span><span>∣</span><span>l</span><span>−</span><span><span>r</span><span><span><span><span><span><span></span><span><span>j</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>∣</span></span></span><span><span></span><span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span></span></span></span></p><ol>
<li>设目标函数为：</li>
</ol><p><span><span>λ=∑∣l−rj∣∑bj\lambda = \frac{\sum \sqrt{|l - r_j|}}{\sum b_j}</span><span><span><span></span><span>λ</span><span></span><span>=</span><span></span></span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span><span>∑</span><span></span><span><span>b</span><span><span><span><span><span><span></span><span><span>j</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>∑</span><span></span><span><span><span><span><span><span></span><span><span>∣</span><span>l</span><span>−</span><span><span>r</span><span><span><span><span><span><span></span><span><span>j</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>∣</span></span></span><span><span></span><span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span></span></span></span></p><ol>
<li>为了最小化这个分式，我们可以令：</li>
</ol><p><span><span>∑∣l−rj∣−λ∑bj=0\sum \sqrt{|l - r_j|} - \lambda \sum b_j = 0</span><span><span><span></span><span>∑</span><span></span><span><span><span><span><span><span></span><span><span>∣</span><span>l</span><span></span><span>−</span><span></span><span><span>r</span><span><span><span><span><span><span></span><span><span>j</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>∣</span></span></span><span><span></span><span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span><span>−</span><span></span></span><span><span></span><span>λ</span><span></span><span>∑</span><span></span><span><span>b</span><span><span><span><span><span><span></span><span><span>j</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>=</span><span></span></span><span><span></span><span>0</span></span></span></span></p><ol>
<li>我们可以看成是对于给定的<span><span>λ\lambda</span><span><span><span></span><span>λ</span></span></span></span>，判断是否存在一条路径，使得：</li>
</ol><p><span><span>∑∣l−rj∣≤λ∑bj\sum \sqrt{|l - r_j|} \leq \lambda \sum b_j</span><span><span><span></span><span>∑</span><span></span><span><span><span><span><span><span></span><span><span>∣</span><span>l</span><span></span><span>−</span><span></span><span><span>r</span><span><span><span><span><span><span></span><span><span>j</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>∣</span></span></span><span><span></span><span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span><span>≤</span><span></span></span><span><span></span><span>λ</span><span></span><span>∑</span><span></span><span><span>b</span><span><span><span><span><span><span></span><span><span>j</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span></p><ol>
<li>如果存在这样的路径，我们就尝试更小的<span><span>λ\lambda</span><span><span><span></span><span>λ</span></span></span></span>；如果不存在，则尝试更大的<span><span>λ\lambda</span><span><span><span></span><span>λ</span></span></span></span>。</li>
</ol><p>使用二分法寻找最优的<span><span>λ\lambda</span><span><span><span></span><span>λ</span></span></span></span>：</p><ol>
<li>由于<span><span>λ\lambda</span><span><span><span></span><span>λ</span></span></span></span>是连续的，我们可以在一个范围内对<span><span>λ\lambda</span><span><span><span></span><span>λ</span></span></span></span>进行二分搜索，直到找到最小的<span><span>λ\lambda</span><span><span><span></span><span>λ</span></span></span></span>。</li>
</ol><p>我们可以将休息点看成图中的节点，两个休息点之间的边权为：</p><p><span><span>边权=∣l−(xi−xj)∣−λbi\text{边权} = \sqrt{|l - (x_i - x_j)|} - \lambda b_i</span><span><span><span></span><span><span>边权</span></span><span></span><span>=</span><span></span></span><span><span></span><span><span><span><span><span><span></span><span><span>∣</span><span>l</span><span></span><span>−</span><span></span><span>(</span><span><span>x</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>−</span><span></span><span><span>x</span><span><span><span><span><span><span></span><span><span>j</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>)</span><span>∣</span></span></span><span><span></span><span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span><span>−</span><span></span></span><span><span></span><span>λ</span><span><span>b</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span></p><p>其中，<span><span>xix_i</span><span><span><span></span><span><span>x</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span> 是休息点的坐标，<span><span>bib_i</span><span><span><span></span><span><span>b</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span>是休息点的风景值。</p><p>我们需要找到从起点到终点的最小代价路径，使得总代价<span><span>≤0\leq 0</span><span><span><span></span><span>≤</span><span></span></span><span><span></span><span>0</span></span></span></span>。如果存在这样的路径，那么对于当前的 <span><span>λ\lambda</span><span><span><span></span><span>λ</span></span></span></span>，答案就是可行的。</p><p>一些注意点：</p><p>实际上思路正确的话，除了二分应该不太会有导致WA的点</p><ul>
<li>二分的判断调节不是hi == lo，因为hi和lo是double，所以要设定一个极小值来进行比较，不然直接比较是有误差的
不妨可以试一试下面的代码输出0还是1</li>
</ul><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>#include</span><span> </span><span>&lt;bits/stdc++.h&gt;</span></div></div><div><div><div>2</div></div><div><span>using</span><span> </span><span>namespace</span><span> </span><span>std</span><span>;</span></div></div><div><div><div>3</div></div><div><span>int</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>4</div></div><div><span>  </span><span>double</span><span> a </span><span>=</span><span> </span><span>0.1</span><span>, b </span><span>=</span><span> </span><span>0.2</span><span>, c </span><span>=</span><span> </span><span>0.3</span><span>;</span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>cout </span><span>&lt;&lt;</span><span> (a </span><span>+</span><span> b </span><span>==</span><span> c) </span><span>&lt;&lt;</span><span> endl;</span></div></div><div><div><div>6</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>实际上保留20位小数，a,b,c分别为0.10000000000000000555，0.20000000000000001110，0.29999999999999998890</p><p>最后这道题的时间复杂度为<span><span>O(n2log(∑∣l−rj∣))O(n^{2}log(\sum\sqrt{|l - r_j|}))</span><span><span><span></span><span>O</span><span>(</span><span><span>n</span><span><span><span><span><span><span></span><span><span><span>2</span></span></span></span></span></span></span></span></span><span>l</span><span>o</span><span>g</span><span>(</span><span>∑</span><span></span><span><span><span><span><span><span></span><span><span>∣</span><span>l</span><span></span><span>−</span><span></span><span><span>r</span><span><span><span><span><span><span></span><span><span>j</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>∣</span></span></span><span><span></span><span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span>))</span></span></span></span></p><p>std：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>#include</span><span> </span><span>&lt;bits/stdc++.h&gt;</span></div></div><div><div><div>2</div></div><div><span>using</span><span> </span><span>namespace</span><span> </span><span>std</span><span>;</span></div></div><div><div><div>3</div></div><div><span>#define</span><span> </span><span>int</span><span> </span><span>long</span><span> </span><span>long</span></div></div><div><div><div>4</div></div><div><span>using</span><span> </span><span>i64</span><span> </span><span>=</span><span> </span><span>long</span><span> </span><span>long</span><span>;</span></div></div><div><div><div>5</div></div><div><span>const</span><span> </span><span>int</span><span> N </span><span>=</span><span> </span><span>2.01</span><span>e</span><span>6</span><span>;</span></div></div><div><div><div>6</div></div><div><span>const</span><span> </span><span>int</span><span> M </span><span>=</span><span> </span><span>1.01</span><span>e</span><span>3</span><span>;</span></div></div><div><div><div>7</div></div><div><span>const</span><span> </span><span>int</span><span> mod </span><span>=</span><span> </span><span>998244353</span><span>;</span></div></div><div><div><div>8</div></div><div><span>const</span><span> </span><span>double</span><span> eps </span><span>=</span><span> </span><span>1</span><span>e-</span><span>7</span><span>;</span></div></div><div><div><div>9</div></div><div><span>#define</span><span> </span><span>endl</span><span> </span><span>'</span><span>\n</span><span>'</span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span>#ifdef</span><span> </span><span>LOCAL</span></div></div><div><div><div>12</div></div><div><span>#include</span><span> </span><span>"C:/Users/70510/Desktop/Others/algo/debug.h"</span></div></div><div><div><div>13</div></div><div><span>#else</span></div></div><div><div><div>14</div></div><div><span>#define</span><span> </span><span>debug</span><span>(...) </span><span>42</span></div></div><div><div><div>15</div></div><div><span>#endif</span></div></div><div><div><div>16</div></div><div>
</div></div><div><div><div>17</div></div><div><span>int</span><span> n;</span></div></div><div><div><div>18</div></div><div><span>double</span><span> l;</span></div></div><div><div><div>19</div></div><div><span>double</span><span> x[N], b[N];</span></div></div><div><div><div>20</div></div><div><span>double</span><span> d[N];</span></div></div><div><div><div>21</div></div><div><span>int</span><span> pre[N];</span></div></div><div><div><div>22</div></div><div>
</div></div><div><div><div>23</div></div><div><span>bool</span><span> </span><span>check</span><span>(</span><span>double</span><span> </span><span>mid</span><span>) {</span></div></div><div><div><div>24</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>0</span><span>; i </span><span>&lt;=</span><span> n; i</span><span>++</span><span>) {</span></div></div><div><div><div>25</div></div><div><span><span>    </span></span><span>d[i] </span><span>=</span><span> </span><span>1</span><span>e</span><span>30</span><span>;</span></div></div><div><div><div>26</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>27</div></div><div><span><span>  </span></span><span>d[</span><span>0</span><span>] </span><span>=</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>28</div></div><div><span><span>  </span></span><span>pre[</span><span>0</span><span>] </span><span>=</span><span> </span><span>-</span><span>1</span><span>;</span></div></div><div><div><div>29</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>1</span><span>; i </span><span>&lt;=</span><span> n; i</span><span>++</span><span>) {</span></div></div><div><div><div>30</div></div><div><span>    </span><span>for</span><span> (</span><span>int</span><span> j </span><span>=</span><span> </span><span>0</span><span>; j </span><span>&lt;</span><span> i; j</span><span>++</span><span>) {</span></div></div><div><div><div>31</div></div><div><span>      </span><span>double</span><span> frustration </span><span>=</span><span> </span><span>sqrt</span><span>(</span><span>abs</span><span>(l </span><span>-</span><span> (x[i] </span><span>-</span><span> x[j])));</span></div></div><div><div><div>32</div></div><div><span>      </span><span>double</span><span> cost </span><span>=</span><span> d[j] </span><span>+</span><span> frustration </span><span>-</span><span> mid </span><span>*</span><span> b[i];</span></div></div><div><div><div>33</div></div><div><span>      </span><span>if</span><span> (d[i] </span><span>&gt;</span><span> cost) {</span></div></div><div><div><div>34</div></div><div><span><span>        </span></span><span>d[i] </span><span>=</span><span> cost;</span></div></div><div><div><div>35</div></div><div><span><span>        </span></span><span>pre[i] </span><span>=</span><span> j;</span></div></div><div><div><div>36</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>37</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>38</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>39</div></div><div><span>  </span><span>return</span><span> d[n] </span><span>&lt;=</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>40</div></div><div><span>}</span></div></div><div><div><div>41</div></div><div>
</div></div><div><div><div>42</div></div><div><span>void</span><span> </span><span>solve</span><span>() {</span></div></div><div><div><div>43</div></div><div><span><span>  </span></span><span>cin </span><span>&gt;&gt;</span><span> n </span><span>&gt;&gt;</span><span> l;</span></div></div><div><div><div>44</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>1</span><span>; i </span><span>&lt;=</span><span> n; i</span><span>++</span><span>) {</span></div></div><div><div><div>45</div></div><div><span><span>    </span></span><span>cin </span><span>&gt;&gt;</span><span> x[i] </span><span>&gt;&gt;</span><span> b[i];</span></div></div><div><div><div>46</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>47</div></div><div><span><span>  </span></span><span>x[</span><span>0</span><span>] </span><span>=</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>48</div></div><div><span><span>  </span></span><span>b[</span><span>0</span><span>] </span><span>=</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>49</div></div><div><span>  </span><span>double</span><span> lo </span><span>=</span><span> </span><span>0</span><span>, hi </span><span>=</span><span> </span><span>1</span><span>e</span><span>6</span><span>;</span></div></div><div><div><div>50</div></div><div><span>  </span><span>while</span><span> (hi </span><span>-</span><span> lo </span><span>&gt;</span><span> eps) {</span></div></div><div><div><div>51</div></div><div><span>    </span><span>double</span><span> mid </span><span>=</span><span> (lo </span><span>+</span><span> hi) </span><span>/</span><span> </span><span>2</span><span>;</span></div></div><div><div><div>52</div></div><div><span>    </span><span>if</span><span> (</span><span>check</span><span>(mid)) {</span></div></div><div><div><div>53</div></div><div><span><span>      </span></span><span>hi </span><span>=</span><span> mid;</span></div></div><div><div><div>54</div></div><div><span><span>    </span></span><span>} </span><span>else</span><span> {</span></div></div><div><div><div>55</div></div><div><span><span>      </span></span><span>lo </span><span>=</span><span> mid;</span></div></div><div><div><div>56</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>57</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>58</div></div><div><span>  </span><span>check</span><span>(hi);</span></div></div><div><div><div>59</div></div><div><span>  </span><span>vector</span><span>&lt;</span><span>int</span><span>&gt; ans;</span></div></div><div><div><div>60</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> n; i </span><span>!=</span><span> </span><span>-</span><span>1</span><span>; i </span><span>=</span><span> pre[i]) {</span></div></div><div><div><div>61</div></div><div><span>    </span><span>if</span><span> (i </span><span>!=</span><span> </span><span>0</span><span>) {</span></div></div><div><div><div>62</div></div><div><span><span>      </span></span><span>ans.</span><span>push_back</span><span>(i);</span></div></div><div><div><div>63</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>64</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>65</div></div><div><span>  </span><span>reverse</span><span>(ans.</span><span>begin</span><span>(), ans.</span><span>end</span><span>());</span></div></div><div><div><div>66</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>0</span><span>; i </span><span>&lt;</span><span> (</span><span>int</span><span>)ans.</span><span>size</span><span>(); i</span><span>++</span><span>) {</span></div></div><div><div><div>67</div></div><div><span><span>    </span></span><span>cout </span><span>&lt;&lt;</span><span> ans[i] </span><span>&lt;&lt;</span><span> </span><span>" </span><span>\n</span><span>"</span><span>[i </span><span>==</span><span> (</span><span>int</span><span>)ans.</span><span>size</span><span>() </span><span>-</span><span> </span><span>1</span><span>];</span></div></div><div><div><div>68</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>69</div></div><div><span>}</span></div></div><div><div><div>70</div></div><div>
</div></div><div><div><div>71</div></div><div><span>signed</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>72</div></div><div><span>  </span><span>ios</span><span>::</span><span>sync_with_stdio</span><span>(</span><span>false</span><span>);</span></div></div><div><div><div>73</div></div><div><span><span>  </span></span><span>cin.</span><span>tie</span><span>(</span><span>0</span><span>), cout.</span><span>tie</span><span>(</span><span>0</span><span>);</span></div></div><div><div><div>74</div></div><div><span>  </span><span>int</span><span> T </span><span>=</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>75</div></div><div><span><span>  </span></span><span>// cin &gt;&gt; T;</span></div></div><div><div><div>76</div></div><div><span>  </span><span>while</span><span> (T</span><span>--</span><span>) </span><span>solve</span><span>();</span></div></div><div><div><div>77</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><blockquote><p>01分数规划相关题目：<a href="http://poj.org/problem?id=2976" target="_blank">Dropping tests</a></p></blockquote><hr /></section><section><h3><a href="https://codeforces.com/contest/1478/problem/D" target="_blank">D. Nezzar and Board</a><a href="#d-nezzar-and-board"><span>#</span></a></h3><p>Date：Jan.28, 2021 | Rating：1972 | Number of accepted&lt;1244&gt;(7.68%) | Number of attempts&lt;1672&gt;(10.33%) | Total&lt;16192&gt;</p><p>Tag：构造 数学 数论</p><p>可以涉及的算法：裴蜀定理 二元线性丢番图方程</p><p>问题描述</p><p>给定一个整数序列<span><span>x1,x2,…,xnx_1, x_2, \dots, x_n</span><span><span><span></span><span><span>x</span><span><span><span><span><span><span></span><span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>,</span><span></span><span><span>x</span><span><span><span><span><span><span></span><span><span>2</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>,</span><span></span><span>…</span><span></span><span>,</span><span></span><span><span>x</span><span><span><span><span><span><span></span><span><span>n</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span>，可以进行以下操作任意次：</p><ul>
<li>选择任意两个整数 <span><span>xx</span><span><span><span></span><span>x</span></span></span></span> 和 <span><span>yy</span><span><span><span></span><span>y</span></span></span></span>，计算<span><span>2x−y2x - y</span><span><span><span></span><span>2</span><span>x</span><span></span><span>−</span><span></span></span><span><span></span><span>y</span></span></span></span>，并将结果视为新的整数。</li>
</ul><p>现在，给定一个目标值<span><span>kk</span><span><span><span></span><span>k</span></span></span></span>，问是否可以通过上述操作，从序列中的某个元素出发，经过若干次操作，得到<span><span>kk</span><span><span><span></span><span>k</span></span></span></span>。</p><p>为了方便分析，我们先对操作进行等价变换，并理解其本质。</p><p>操作等价变换
原操作：<span><span>2x−y2x - y</span><span><span><span></span><span>2</span><span>x</span><span></span><span>−</span><span></span></span><span><span></span><span>y</span></span></span></span></p><p>可以看作：</p><ol>
<li>
<p><span><span>2x−y=x+x−y 2x - y = x + x - y</span><span><span><span></span><span>2</span><span>x</span><span></span><span>−</span><span></span></span><span><span></span><span>y</span><span></span><span>=</span><span></span></span><span><span></span><span>x</span><span></span><span>+</span><span></span></span><span><span></span><span>x</span><span></span><span>−</span><span></span></span><span><span></span><span>y</span></span></span></span>
这样，我们可以将操作视为：从一个元素<span><span>xx</span><span><span><span></span><span>x</span></span></span></span>开始，加上任意两个数的差<span><span>x−yx - y</span><span><span><span></span><span>x</span><span></span><span>−</span><span></span></span><span><span></span><span>y</span></span></span></span>。</p>
</li>
<li>
<p>由于<span><span>x−yx - y</span><span><span><span></span><span>x</span><span></span><span>−</span><span></span></span><span><span></span><span>y</span></span></span></span>可以为正数或负数，因此，操作的本质是：从某个元素开始，加上任意数对的差。</p>
</li>
</ol><p>考虑序列元素之间的差，我们定义序列：</p><ul>
<li>
<p><span><span>ai=xi−xi−1a_i = x_i - x_{i-1}</span><span><span><span></span><span><span>a</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>=</span><span></span></span><span><span></span><span><span>x</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>−</span><span></span></span><span><span></span><span><span>x</span><span><span><span><span><span><span></span><span><span><span>i</span><span>−</span><span>1</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span>，对于<span><span>i=2,3,…,ni = 2,3,\dots,n</span><span><span><span></span><span>i</span><span></span><span>=</span><span></span></span><span><span></span><span>2</span><span>,</span><span></span><span>3</span><span>,</span><span></span><span>…</span><span></span><span>,</span><span></span><span>n</span></span></span></span>
这样，我们可以表示任意两个元素之间的差：</p>
</li>
<li>
<p><span><span>xi−xj=∑k=j+1iakx_i - x_j = \sum_{k = j+1}^{i} a_k</span><span><span><span></span><span><span>x</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>−</span><span></span></span><span><span></span><span><span>x</span><span><span><span><span><span><span></span><span><span>j</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>=</span><span></span></span><span><span></span><span><span>∑</span><span><span><span><span><span><span></span><span><span><span>k</span><span>=</span><span>j</span><span>+</span><span>1</span></span></span></span><span><span></span><span><span><span>i</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span><span>a</span><span><span><span><span><span><span></span><span><span>k</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span>，当 <span><span>i&gt;ji &gt; j</span><span><span><span></span><span>i</span><span></span><span>&gt;</span><span></span></span><span><span></span><span>j</span></span></span></span>
因此，任意的<span><span>xix_i</span><span><span><span></span><span><span>x</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span> 都可以表示为：</p>
</li>
<li>
<p><span><span>xi=x1+∑k=2iakx_i = x_1 + \sum_{k = 2}^{i} a_k</span><span><span><span></span><span><span>x</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>=</span><span></span></span><span><span></span><span><span>x</span><span><span><span><span><span><span></span><span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>+</span><span></span></span><span><span></span><span><span>∑</span><span><span><span><span><span><span></span><span><span><span>k</span><span>=</span><span>2</span></span></span></span><span><span></span><span><span><span>i</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span><span>a</span><span><span><span><span><span><span></span><span><span>k</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span></p>
</li>
</ul><p>于是问题转化为：从某个<span><span>xix_i </span><span><span><span></span><span><span>x</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span>出发，通过加上若干个差<span><span>xp−xqx_p - x_q</span><span><span><span></span><span><span>x</span><span><span><span><span><span><span></span><span><span>p</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>−</span><span></span></span><span><span></span><span><span>x</span><span><span><span><span><span><span></span><span><span>q</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span>（即<span><span>a a</span><span><span><span></span><span>a</span></span></span></span> 的线性组合），得到目标值<span><span>kk</span><span><span><span></span><span>k</span></span></span></span>。</p><p>由于差 <span><span>xp−xqx_p - x_q</span><span><span><span></span><span><span>x</span><span><span><span><span><span><span></span><span><span>p</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>−</span><span></span></span><span><span></span><span><span>x</span><span><span><span><span><span><span></span><span><span>q</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span>可以表示为 <span><span>aa </span><span><span><span></span><span>a</span></span></span></span> 的线性组合，且系数为整数（可能为负数），因此，我们可以表示目标值与<span><span>xix_i </span><span><span><span></span><span><span>x</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span>之间的差为：
<span><span>k−xi=z1a1+z2a2+⋯+zn−1an−1k - x_i = z_1 a_1 + z_2 a_2 + \dots + z_{n-1} a_{n-1}</span><span><span><span></span><span>k</span><span></span><span>−</span><span></span></span><span><span></span><span><span>x</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>=</span><span></span></span><span><span></span><span><span>z</span><span><span><span><span><span><span></span><span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span><span>a</span><span><span><span><span><span><span></span><span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>+</span><span></span></span><span><span></span><span><span>z</span><span><span><span><span><span><span></span><span><span>2</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span><span>a</span><span><span><span><span><span><span></span><span><span>2</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>+</span><span></span></span><span><span></span><span>⋯</span><span></span><span>+</span><span></span></span><span><span></span><span><span>z</span><span><span><span><span><span><span></span><span><span><span>n</span><span>−</span><span>1</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span><span>a</span><span><span><span><span><span><span></span><span><span><span>n</span><span>−</span><span>1</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span>其中，<span><span>zjz_j</span><span><span><span></span><span><span>z</span><span><span><span><span><span><span></span><span><span>j</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span>为整数。</p><p>那么就可以利用裴蜀定理：对于整数序列<span><span>a1,a2,…,an−1a_1, a_2, \dots, a_{n-1}</span><span><span><span></span><span><span>a</span><span><span><span><span><span><span></span><span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>,</span><span></span><span><span>a</span><span><span><span><span><span><span></span><span><span>2</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>,</span><span></span><span>…</span><span></span><span>,</span><span></span><span><span>a</span><span><span><span><span><span><span></span><span><span><span>n</span><span>−</span><span>1</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span>，存在整数解<span><span>z1,z2,…,zn−1z_1, z_2, \dots, z_{n-1}</span><span><span><span></span><span><span>z</span><span><span><span><span><span><span></span><span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>,</span><span></span><span><span>z</span><span><span><span><span><span><span></span><span><span>2</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>,</span><span></span><span>…</span><span></span><span>,</span><span></span><span><span>z</span><span><span><span><span><span><span></span><span><span><span>n</span><span>−</span><span>1</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span>，使得线性组合等于某个值的充要条件是，该值是这些数的最大公约数<span><span>gg</span><span><span><span></span><span>g</span></span></span></span>的倍数。</p><p>因此，我们的问题转化为： 是否存在序列中的某个元素<span><span>xix_i</span><span><span><span></span><span><span>x</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span>，使得<span><span>k−xik - x_i</span><span><span><span></span><span>k</span><span></span><span>−</span><span></span></span><span><span></span><span><span>x</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span> 是<span><span>g g</span><span><span><span></span><span>g</span></span></span></span>的倍数。</p><p>注意到，所有的<span><span>xix_i</span><span><span><span></span><span><span>x</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span>都满足：<span><span>xi≡x1(modg)x_i \equiv x_1 \pmod{g}</span><span><span><span></span><span><span>x</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>≡</span><span></span></span><span><span></span><span><span>x</span><span><span><span><span><span><span></span><span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span></span></span><span><span></span><span>(</span><span><span><span>mod</span></span></span><span></span><span>g</span><span>)</span></span></span></span></p><p>因为<span><span>xi−x1=∑k=2iakx_i - x_1 = \sum_{k=2}^{i} a_k</span><span><span><span></span><span><span>x</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>−</span><span></span></span><span><span></span><span><span>x</span><span><span><span><span><span><span></span><span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>=</span><span></span></span><span><span></span><span><span>∑</span><span><span><span><span><span><span></span><span><span><span>k</span><span>=</span><span>2</span></span></span></span><span><span></span><span><span><span>i</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span><span>a</span><span><span><span><span><span><span></span><span><span>k</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span>，所以<span><span>xi−x1x_i - x_1</span><span><span><span></span><span><span>x</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>−</span><span></span></span><span><span></span><span><span>x</span><span><span><span><span><span><span></span><span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span> 是<span><span>gg</span><span><span><span></span><span>g</span></span></span></span>的倍数。</p><p>因此，只需检查<span><span>kk</span><span><span><span></span><span>k</span></span></span></span>是否与<span><span>x1x_1</span><span><span><span></span><span><span>x</span><span><span><span><span><span><span></span><span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span>同余于模<span><span>gg</span><span><span><span></span><span>g</span></span></span></span>意义下，即： 如果<span><span>(k−x1)mod  g=0(k - x_1) \mod g = 0</span><span><span><span></span><span>(</span><span>k</span><span></span><span>−</span><span></span></span><span><span></span><span><span>x</span><span><span><span><span><span><span></span><span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>)</span><span></span><span></span></span><span><span></span><span><span><span>mod</span></span></span><span></span><span></span><span>g</span><span></span><span>=</span><span></span></span><span><span></span><span>0</span></span></span></span>，总体时间复杂度<span><span>O(nlog(max(x[i])))O(nlog(max(x[i])))</span><span><span><span></span><span>O</span><span>(</span><span>n</span><span>l</span><span>o</span><span>g</span><span>(</span><span>ma</span><span>x</span><span>(</span><span>x</span><span>[</span><span>i</span><span>])))</span></span></span></span>。</p><p>另外通过丢番图方程入手也可以解决这道题目，手算前几项的递推式可以发现是线性丢番图方程，殊途同归。</p><p>科普一个没啥用的小知识：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>std::__gcd和std::gcd实现并不同，std::__gcd是euclid算法，std::gcd是stein算法，一般来说，随机数据后者效率高，但实际上没人卡这个就是了。</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>std:</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>#include</span><span> </span><span>&lt;bits/stdc++.h&gt;</span></div></div><div><div><div>2</div></div><div><span>using</span><span> </span><span>namespace</span><span> </span><span>std</span><span>;</span></div></div><div><div><div>3</div></div><div><span>#define</span><span> </span><span>int</span><span> </span><span>long</span><span> </span><span>long</span></div></div><div><div><div>4</div></div><div><span>using</span><span> </span><span>i64</span><span> </span><span>=</span><span> </span><span>long</span><span> </span><span>long</span><span>;</span></div></div><div><div><div>5</div></div><div><span>const</span><span> </span><span>int</span><span> N </span><span>=</span><span> </span><span>2.01</span><span>e</span><span>6</span><span>;</span></div></div><div><div><div>6</div></div><div><span>const</span><span> </span><span>int</span><span> M </span><span>=</span><span> </span><span>1.01</span><span>e</span><span>3</span><span>;</span></div></div><div><div><div>7</div></div><div><span>const</span><span> </span><span>int</span><span> mod </span><span>=</span><span> </span><span>998244353</span><span>;</span></div></div><div><div><div>8</div></div><div><span>#define</span><span> </span><span>endl</span><span> </span><span>'</span><span>\n</span><span>'</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span>#ifdef</span><span> </span><span>LOCAL</span></div></div><div><div><div>11</div></div><div><span>#include</span><span> </span><span>"C:/Users/70510/Desktop/Others/algo/debug.h"</span></div></div><div><div><div>12</div></div><div><span>#else</span></div></div><div><div><div>13</div></div><div><span>#define</span><span> </span><span>debug</span><span>(...) </span><span>42</span></div></div><div><div><div>14</div></div><div><span>#endif</span></div></div><div><div><div>15</div></div><div>
</div></div><div><div><div>16</div></div><div><span>void</span><span> </span><span>solve</span><span>() {</span></div></div><div><div><div>17</div></div><div><span>  </span><span>int</span><span> n, k;</span></div></div><div><div><div>18</div></div><div><span><span>  </span></span><span>cin </span><span>&gt;&gt;</span><span> n </span><span>&gt;&gt;</span><span> k;</span></div></div><div><div><div>19</div></div><div><span>  </span><span>vector</span><span>&lt;</span><span>int</span><span>&gt; </span><span>x</span><span>(n);</span></div></div><div><div><div>20</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>0</span><span>; i </span><span>&lt;</span><span> n; i</span><span>++</span><span>) {</span></div></div><div><div><div>21</div></div><div><span><span>    </span></span><span>cin </span><span>&gt;&gt;</span><span> x[i];</span></div></div><div><div><div>22</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>23</div></div><div><span>  </span><span>int</span><span> g </span><span>=</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>24</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>1</span><span>; i </span><span>&lt;</span><span> n; i</span><span>++</span><span>) {</span></div></div><div><div><div>25</div></div><div><span><span>    </span></span><span>g </span><span>=</span><span> </span><span>__gcd</span><span>(g, </span><span>abs</span><span>(x[i] </span><span>-</span><span> x[i </span><span>-</span><span> </span><span>1</span><span>]));</span></div></div><div><div><div>26</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>27</div></div><div><span>  </span><span>int</span><span> delta </span><span>=</span><span> </span><span>abs</span><span>(k </span><span>-</span><span> x[</span><span>0</span><span>]);</span></div></div><div><div><div>28</div></div><div><span>  </span><span>if</span><span> (delta </span><span>%</span><span> g </span><span>==</span><span> </span><span>0</span><span>) {</span></div></div><div><div><div>29</div></div><div><span><span>    </span></span><span>cout </span><span>&lt;&lt;</span><span> </span><span>"YES"</span><span> </span><span>&lt;&lt;</span><span> endl;</span></div></div><div><div><div>30</div></div><div><span><span>  </span></span><span>} </span><span>else</span><span> {</span></div></div><div><div><div>31</div></div><div><span><span>    </span></span><span>cout </span><span>&lt;&lt;</span><span> </span><span>"NO"</span><span> </span><span>&lt;&lt;</span><span> endl;</span></div></div><div><div><div>32</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>33</div></div><div><span>}</span></div></div><div><div><div>34</div></div><div>
</div></div><div><div><div>35</div></div><div><span>signed</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>36</div></div><div><span>  </span><span>ios</span><span>::</span><span>sync_with_stdio</span><span>(</span><span>false</span><span>);</span></div></div><div><div><div>37</div></div><div><span><span>  </span></span><span>cin.</span><span>tie</span><span>(</span><span>0</span><span>), cout.</span><span>tie</span><span>(</span><span>0</span><span>);</span></div></div><div><div><div>38</div></div><div><span>  </span><span>int</span><span> T </span><span>=</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>39</div></div><div><span><span>  </span></span><span>cin </span><span>&gt;&gt;</span><span> T;</span></div></div><div><div><div>40</div></div><div><span>  </span><span>while</span><span> (T</span><span>--</span><span>) </span><span>solve</span><span>();</span></div></div><div><div><div>41</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><blockquote><p>裴蜀定理：<a href="https://www.luogu.com.cn/problem/P4549" target="_blank">【模板】裴蜀定理</a></p><p>裴蜀定理：<a href="https://codeforces.com/contest/510/problem/D" target="_blank">D. Fox And Jumping</a></p><p>裴蜀定理：<a href="https://acm.hdu.edu.cn/showproblem.php?pid=5512" target="_blank">Pagodas</a></p><p>裴蜀定理：<a href="http://poj.org/problem?id=1579" target="_blank">Function Run Fun</a></p><p>拓展欧几里得算法与二元丢番图方程的解：<a href="https://www.luogu.com.cn/problem/P1516" target="_blank">P1516 青蛙的约会</a></p></blockquote><hr /></section><section><h3><a href="https://codeforces.com/contest/1496/problem/C" target="_blank">C. Diamond Miner</a><a href="#c-diamond-miner"><span>#</span></a></h3><p>Date：March.10, 2021 | Rating：1176 | Number of accepted&lt;5878&gt;(53.86%) | Number of attempts&lt;6593&gt; (60.41%) | Total&lt;10914&gt;</p><p>Tag：计算几何 贪心 排序</p><p>题意：略</p><p>因为坐标每个象限都是等效的，所以我们可以把所有的坐标全都移到同一个想先，为了方便起见，全部都移到第一象限，保证坐标全部为正数，方便计算。</p><p>引用官方题解的一张图方便解释：</p><p></p><figure><img src="https://www.helloimg.com/i/2024/12/31/6773e86e6ae45.png" alt="codeforces" /><figcaption>codeforces</figcaption></figure><p></p><p>我们知道，在三角形中任意两边的长度大于第三遍的长度，在此假设有四个点<span><span>A,B,C,DA,B,C,D</span><span><span><span></span><span>A</span><span>,</span><span></span><span>B</span><span>,</span><span></span><span>C</span><span>,</span><span></span><span>D</span></span></span></span>存在，有两种挖矿的方法</p><ol>
<li><span><span>BB</span><span><span><span></span><span>B</span></span></span></span>挖<span><span>CC</span><span><span><span></span><span>C</span></span></span></span> <span><span>DD</span><span><span><span></span><span>D</span></span></span></span>挖<span><span>AA</span><span><span><span></span><span>A</span></span></span></span> 那么花费的能量为<span><span>∣BC∣+∣AD∣|BC|+|AD|</span><span><span><span></span><span>∣</span><span>B</span><span>C</span><span>∣</span><span></span><span>+</span><span></span></span><span><span></span><span>∣</span><span>A</span><span>D</span><span>∣</span></span></span></span></li>
<li><span><span>BB</span><span><span><span></span><span>B</span></span></span></span>挖<span><span>AA</span><span><span><span></span><span>A</span></span></span></span> <span><span>DD</span><span><span><span></span><span>D</span></span></span></span>挖<span><span>CC</span><span><span><span></span><span>C</span></span></span></span> 那么花费的能量为<span><span>∣AB∣+∣CD∣=∣AO∣+∣DO∣+∣BO∣+∣CO∣&gt;∣BC∣+∣AD∣|AB|+|CD|=|AO|+|DO|+|BO|+|CO|&gt;|BC|+|AD|</span><span><span><span></span><span>∣</span><span>A</span><span>B</span><span>∣</span><span></span><span>+</span><span></span></span><span><span></span><span>∣</span><span>C</span><span>D</span><span>∣</span><span></span><span>=</span><span></span></span><span><span></span><span>∣</span><span>A</span><span>O</span><span>∣</span><span></span><span>+</span><span></span></span><span><span></span><span>∣</span><span>D</span><span>O</span><span>∣</span><span></span><span>+</span><span></span></span><span><span></span><span>∣</span><span>B</span><span>O</span><span>∣</span><span></span><span>+</span><span></span></span><span><span></span><span>∣</span><span>C</span><span>O</span><span>∣</span><span></span><span>&gt;</span><span></span></span><span><span></span><span>∣</span><span>B</span><span>C</span><span>∣</span><span></span><span>+</span><span></span></span><span><span></span><span>∣</span><span>A</span><span>D</span><span>∣</span></span></span></span></li>
</ol><p>显然如果想要花费的能量最小，就是让任意两条线段之间除了定点都不相交</p><p>可以将所有的点在<span><span>xx</span><span><span><span></span><span>x</span></span></span></span>轴<span><span>yy</span><span><span><span></span><span>y</span></span></span></span>轴上排序，累加所有的线段就可以得到最小值，时间复杂度为<span><span>O(nlogn)O(nlogn)</span><span><span><span></span><span>O</span><span>(</span><span>n</span><span>l</span><span>o</span><span>g</span><span>n</span><span>)</span></span></span></span></p><p>std:</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>#include</span><span> </span><span>&lt;bits/stdc++.h&gt;</span></div></div><div><div><div>2</div></div><div><span>using</span><span> </span><span>namespace</span><span> </span><span>std</span><span>;</span></div></div><div><div><div>3</div></div><div><span>#define</span><span> </span><span>int</span><span> </span><span>long</span><span> </span><span>long</span></div></div><div><div><div>4</div></div><div><span>using</span><span> </span><span>i64</span><span> </span><span>=</span><span> </span><span>long</span><span> </span><span>long</span><span>;</span></div></div><div><div><div>5</div></div><div><span>const</span><span> </span><span>int</span><span> N </span><span>=</span><span> </span><span>2.01</span><span>e</span><span>6</span><span>;</span></div></div><div><div><div>6</div></div><div><span>const</span><span> </span><span>int</span><span> M </span><span>=</span><span> </span><span>1.01</span><span>e</span><span>3</span><span>;</span></div></div><div><div><div>7</div></div><div><span>const</span><span> </span><span>int</span><span> mod </span><span>=</span><span> </span><span>998244353</span><span>;</span></div></div><div><div><div>8</div></div><div><span>#define</span><span> </span><span>endl</span><span> </span><span>'</span><span>\n</span><span>'</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span>#ifdef</span><span> </span><span>LOCAL</span></div></div><div><div><div>11</div></div><div><span>#include</span><span> </span><span>"C:/Users/70510/Desktop/Others/algo/debug.h"</span></div></div><div><div><div>12</div></div><div><span>#else</span></div></div><div><div><div>13</div></div><div><span>#define</span><span> </span><span>debug</span><span>(...) </span><span>42</span></div></div><div><div><div>14</div></div><div><span>#endif</span></div></div><div><div><div>15</div></div><div>
</div></div><div><div><div>16</div></div><div><span>int</span><span> x[N], y[N];</span></div></div><div><div><div>17</div></div><div>
</div></div><div><div><div>18</div></div><div><span>void</span><span> </span><span>solve</span><span>() {</span></div></div><div><div><div>19</div></div><div><span>  </span><span>int</span><span> n;</span></div></div><div><div><div>20</div></div><div><span><span>  </span></span><span>cin </span><span>&gt;&gt;</span><span> n;</span></div></div><div><div><div>21</div></div><div><span>  </span><span>vector</span><span>&lt;</span><span>double</span><span>&gt; xx;</span></div></div><div><div><div>22</div></div><div><span>  </span><span>vector</span><span>&lt;</span><span>double</span><span>&gt; yy;</span></div></div><div><div><div>23</div></div><div><span>  </span><span>int</span><span> totalPoints </span><span>=</span><span> n </span><span>*</span><span> </span><span>2</span><span>;</span></div></div><div><div><div>24</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>0</span><span>; i </span><span>&lt;</span><span> totalPoints; </span><span>++</span><span>i) {</span></div></div><div><div><div>25</div></div><div><span>    </span><span>int</span><span> x, y;</span></div></div><div><div><div>26</div></div><div><span><span>    </span></span><span>cin </span><span>&gt;&gt;</span><span> x </span><span>&gt;&gt;</span><span> y;</span></div></div><div><div><div>27</div></div><div><span>    </span><span>if</span><span> (x </span><span>==</span><span> </span><span>0</span><span>) {</span></div></div><div><div><div>28</div></div><div><span><span>      </span></span><span>yy.</span><span>push_back</span><span>(</span><span>abs</span><span>(y));</span></div></div><div><div><div>29</div></div><div><span><span>    </span></span><span>} </span><span>else</span><span> </span><span>if</span><span> (y </span><span>==</span><span> </span><span>0</span><span>) {</span></div></div><div><div><div>30</div></div><div><span><span>      </span></span><span>xx.</span><span>push_back</span><span>(</span><span>abs</span><span>(x));</span></div></div><div><div><div>31</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>32</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>33</div></div><div><span>  </span><span>sort</span><span>(yy.</span><span>begin</span><span>(), yy.</span><span>end</span><span>());</span></div></div><div><div><div>34</div></div><div><span>  </span><span>sort</span><span>(xx.</span><span>begin</span><span>(), xx.</span><span>end</span><span>());</span></div></div><div><div><div>35</div></div><div><span>  </span><span>double</span><span> ans </span><span>=</span><span> </span><span>0.0</span><span>;</span></div></div><div><div><div>36</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>0</span><span>; i </span><span>&lt;</span><span> n; </span><span>++</span><span>i) {</span></div></div><div><div><div>37</div></div><div><span>    </span><span>double</span><span> dis </span><span>=</span><span> </span><span>sqrt</span><span>(yy[i] </span><span>*</span><span> yy[i] </span><span>+</span><span> xx[i] </span><span>*</span><span> xx[i]);</span></div></div><div><div><div>38</div></div><div><span><span>    </span></span><span>ans </span><span>+=</span><span> dis;</span></div></div><div><div><div>39</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>40</div></div><div><span><span>  </span></span><span>cout </span><span>&lt;&lt;</span><span> fixed </span><span>&lt;&lt;</span><span> </span><span>setprecision</span><span>(</span><span>15</span><span>) </span><span>&lt;&lt;</span><span> ans </span><span>&lt;&lt;</span><span> endl;</span></div></div><div><div><div>41</div></div><div><span>}</span></div></div><div><div><div>42</div></div><div>
</div></div><div><div><div>43</div></div><div><span>signed</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>44</div></div><div><span>  </span><span>ios</span><span>::</span><span>sync_with_stdio</span><span>(</span><span>false</span><span>);</span></div></div><div><div><div>45</div></div><div><span><span>  </span></span><span>cin.</span><span>tie</span><span>(</span><span>0</span><span>), cout.</span><span>tie</span><span>(</span><span>0</span><span>);</span></div></div><div><div><div>46</div></div><div><span>  </span><span>int</span><span> T </span><span>=</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>47</div></div><div><span><span>  </span></span><span>cin </span><span>&gt;&gt;</span><span> T;</span></div></div><div><div><div>48</div></div><div><span>  </span><span>while</span><span> (T</span><span>--</span><span>) </span><span>solve</span><span>();</span></div></div><div><div><div>49</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><hr /></section><section><h3><a href="https://codeforces.com/contest/1199/problem/C" target="_blank">C. MP3</a><a href="#c-mp3"><span>#</span></a></h3><p>Date：July 30, 2019 | Rating：1748 | Number of accepted&lt;1884&gt;(26.85%) | Number of attempts&lt;4402&gt;(62.74%) | Total&lt;7016&gt;</p><p>Tag：双指针</p><p>题意：略</p><p>首先，我们需要计算在给定磁盘大小下，数组中最多可以有多少个不同的值。</p><p>每个值需要的位数为 <span><span>k=⌈log⁡2K⌉k = \lceil \log_2 K \rceil</span><span><span><span></span><span>k</span><span></span><span>=</span><span></span></span><span><span></span><span>⌈</span><span><span>lo<span>g</span></span><span><span><span><span><span><span></span><span><span>2</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>K</span><span>⌉</span></span></span></span>，总共需要的位数为 <span><span>n×kn \times k</span><span><span><span></span><span>n</span><span></span><span>×</span><span></span></span><span><span></span><span>k</span></span></span></span>。磁盘大小为 <span><span>II</span><span><span><span></span><span>I</span></span></span></span> 字节，即 <span><span>8I8I</span><span><span><span></span><span>8</span><span>I</span></span></span></span> 位。</p><p>因此，需要满足：<span><span>n×k≤8In \times k \leq 8I</span><span><span><span></span><span>n</span><span></span><span>×</span><span></span></span><span><span></span><span>k</span><span></span><span>≤</span><span></span></span><span><span></span><span>8</span><span>I</span></span></span></span>，即：<span><span>k≤8Ink \leq \dfrac{8I}{n}</span><span><span><span></span><span>k</span><span></span><span>≤</span><span></span></span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span>n</span></span></span><span><span></span><span></span></span><span><span></span><span><span>8</span><span>I</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span></span></span></span>，由于 <span><span>kk</span><span><span><span></span><span>k</span></span></span></span> 为非负整数，我们取其下界。</p><p>然后，不同值的最大数量 <span><span>KK</span><span><span><span></span><span>K</span></span></span></span> 满足：<span><span>K≤2kK \leq 2^k</span><span><span><span></span><span>K</span><span></span><span>≤</span><span></span></span><span><span></span><span><span>2</span><span><span><span><span><span><span></span><span><span>k</span></span></span></span></span></span></span></span></span></span></span>，由于 <span><span>k=⌈log⁡2K⌉k = \lceil \log_2 K \rceil</span><span><span><span></span><span>k</span><span></span><span>=</span><span></span></span><span><span></span><span>⌈</span><span><span>lo<span>g</span></span><span><span><span><span><span><span></span><span><span>2</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>K</span><span>⌉</span></span></span></span>，所以：<span><span>K≤28InK \leq 2^{\frac{8I}{n}}</span><span><span><span></span><span>K</span><span></span><span>≤</span><span></span></span><span><span></span><span><span>2</span><span><span><span><span><span><span></span><span><span><span><span></span><span><span><span><span><span><span></span><span><span><span>n</span></span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>8</span><span>I</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span></span></span></span></span></span></span></span></span></span></span></span></p><p>但是，<span><span>KK</span><span><span><span></span><span>K</span></span></span></span> 最多为 <span><span>nn</span><span><span><span></span><span>n</span></span></span></span>，因为数组长度为 <span><span>nn</span><span><span><span></span><span>n</span></span></span></span>。</p><p>为了避免 <span><span>KK</span><span><span><span></span><span>K</span></span></span></span> 过大（当 <span><span>nn</span><span><span><span></span><span>n</span></span></span></span> 很小时，<span><span>28In2^{\frac{8I}{n}}</span><span><span><span></span><span><span>2</span><span><span><span><span><span><span></span><span><span><span><span></span><span><span><span><span><span><span></span><span><span><span>n</span></span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>8</span><span>I</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span></span></span></span></span></span></span></span></span></span></span></span> 可能很大），我们可以设定一个上限，比如 <span><span>Kmax=nK_{\text{max}} = n</span><span><span><span></span><span><span>K</span><span><span><span><span><span><span></span><span><span><span><span>max</span></span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>=</span><span></span></span><span><span></span><span>n</span></span></span></span>。</p><p>我们可以对数组进行预处理：排序+统计数组中不同元素的值及其出现次数， 进而滑动窗口寻找最优区间</p><p>我们的目标是：</p><ul>
<li>选择一个包含不超过 <span><span>KK</span><span><span><span></span><span>K</span></span></span></span> 个不同值的子区间（这些值是连续的，因为经过排序）；</li>
<li>使得在这个区间内的元素数量尽可能多，从而被修改的元素数量尽可能少；</li>
</ul><p>具体来讲：</p><ul>
<li>设定一个滑动窗口，初始化左端点和右端点；</li>
<li>枚举所有可能的左右端点组合，使得窗口内的不同值数量不超过 <span><span>KK</span><span><span><span></span><span>K</span></span></span></span>；</li>
<li>计算每种情况下需要修改的元素数量，即总元素数减去窗口内的元素数量；</li>
<li>取需要修改元素数量最小的方案；</li>
</ul><p>总时间复杂度为 <span><span>O(nlog⁡n)O(n \log n)</span><span><span><span></span><span>O</span><span>(</span><span>n</span><span></span><span>lo<span>g</span></span><span></span><span>n</span><span>)</span></span></span></span>。</p><p>std:</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>#include</span><span> </span><span>&lt;bits/stdc++.h&gt;</span></div></div><div><div><div>2</div></div><div><span>using</span><span> </span><span>namespace</span><span> </span><span>std</span><span>;</span></div></div><div><div><div>3</div></div><div><span>#define</span><span> </span><span>int</span><span> </span><span>long</span><span> </span><span>long</span></div></div><div><div><div>4</div></div><div><span>using</span><span> </span><span>i64</span><span> </span><span>=</span><span> </span><span>long</span><span> </span><span>long</span><span>;</span></div></div><div><div><div>5</div></div><div><span>const</span><span> </span><span>int</span><span> N </span><span>=</span><span> </span><span>2.01</span><span>e</span><span>6</span><span>;</span></div></div><div><div><div>6</div></div><div><span>const</span><span> </span><span>int</span><span> M </span><span>=</span><span> </span><span>1.01</span><span>e</span><span>3</span><span>;</span></div></div><div><div><div>7</div></div><div><span>const</span><span> </span><span>int</span><span> mod </span><span>=</span><span> </span><span>998244353</span><span>;</span></div></div><div><div><div>8</div></div><div><span>#define</span><span> </span><span>endl</span><span> </span><span>'</span><span>\n</span><span>'</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span>#ifdef</span><span> </span><span>LOCAL</span></div></div><div><div><div>11</div></div><div><span>#include</span><span> </span><span>"C:/Users/70510/Desktop/Others/algo/debug.h"</span></div></div><div><div><div>12</div></div><div><span>#else</span></div></div><div><div><div>13</div></div><div><span>#define</span><span> </span><span>debug</span><span>(...) </span><span>42</span></div></div><div><div><div>14</div></div><div><span>#endif</span></div></div><div><div><div>15</div></div><div>
</div></div><div><div><div>16</div></div><div><span>void</span><span> </span><span>solve</span><span>() {</span></div></div><div><div><div>17</div></div><div><span>  </span><span>int</span><span> n, I;</span></div></div><div><div><div>18</div></div><div><span><span>  </span></span><span>cin </span><span>&gt;&gt;</span><span> n </span><span>&gt;&gt;</span><span> I;</span></div></div><div><div><div>19</div></div><div><span>  </span><span>vector</span><span>&lt;</span><span>int</span><span>&gt; </span><span>a</span><span>(n);</span></div></div><div><div><div>20</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>0</span><span>; i </span><span>&lt;</span><span> n; </span><span>++</span><span>i) {</span></div></div><div><div><div>21</div></div><div><span><span>    </span></span><span>cin </span><span>&gt;&gt;</span><span> a[i];</span></div></div><div><div><div>22</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>23</div></div><div><span>  </span><span>sort</span><span>(a.</span><span>begin</span><span>(), a.</span><span>end</span><span>());</span></div></div><div><div><div>24</div></div><div><span>  </span><span>vector</span><span>&lt;</span><span>int</span><span>&gt; val;</span></div></div><div><div><div>25</div></div><div><span>  </span><span>vector</span><span>&lt;</span><span>int</span><span>&gt; count;</span></div></div><div><div><div>26</div></div><div><span>  </span><span>vector</span><span>&lt;</span><span>int</span><span>&gt; </span><span>pre</span><span>(</span><span>1</span><span>, </span><span>0</span><span>);</span></div></div><div><div><div>27</div></div><div><span>  </span><span>int</span><span> cnt </span><span>=</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>28</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> </span><span>1</span><span>; i </span><span>&lt;=</span><span> n; </span><span>++</span><span>i) {</span></div></div><div><div><div>29</div></div><div><span>    </span><span>if</span><span> (i </span><span>==</span><span> n </span><span>||</span><span> a[i] </span><span>!=</span><span> a[i </span><span>-</span><span> </span><span>1</span><span>]) {</span></div></div><div><div><div>30</div></div><div><span><span>      </span></span><span>val.</span><span>push_back</span><span>(a[i </span><span>-</span><span> </span><span>1</span><span>]);</span></div></div><div><div><div>31</div></div><div><span><span>      </span></span><span>count.</span><span>push_back</span><span>(cnt);</span></div></div><div><div><div>32</div></div><div><span><span>      </span></span><span>pre.</span><span>push_back</span><span>(pre.</span><span>back</span><span>() </span><span>+</span><span> cnt);</span></div></div><div><div><div>33</div></div><div><span><span>      </span></span><span>cnt </span><span>=</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>34</div></div><div><span><span>    </span></span><span>} </span><span>else</span><span> {</span></div></div><div><div><div>35</div></div><div><span><span>      </span></span><span>cnt</span><span>++</span><span>;</span></div></div><div><div><div>36</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>37</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>38</div></div><div><span>  </span><span>int</span><span> max_bits </span><span>=</span><span> (</span><span>8</span><span> </span><span>*</span><span> I) </span><span>/</span><span> n;</span></div></div><div><div><div>39</div></div><div><span>  </span><span>int</span><span> K;</span></div></div><div><div><div>40</div></div><div><span>  </span><span>if</span><span> (max_bits </span><span>&gt;=</span><span> </span><span>20</span><span>) {</span></div></div><div><div><div>41</div></div><div><span><span>    </span></span><span>K </span><span>=</span><span> val.</span><span>size</span><span>();</span></div></div><div><div><div>42</div></div><div><span><span>  </span></span><span>} </span><span>else</span><span> {</span></div></div><div><div><div>43</div></div><div><span><span>    </span></span><span>K </span><span>=</span><span> </span><span>min</span><span>((</span><span>int</span><span>)val.</span><span>size</span><span>(), </span><span>1</span><span>LL</span><span> </span><span>&lt;&lt;</span><span> max_bits);</span></div></div><div><div><div>44</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>45</div></div><div><span>  </span><span>int</span><span> cht </span><span>=</span><span> n;</span></div></div><div><div><div>46</div></div><div><span>  </span><span>int</span><span> tot_val </span><span>=</span><span> val.</span><span>size</span><span>();</span></div></div><div><div><div>47</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> lo </span><span>=</span><span> </span><span>0</span><span>, hi </span><span>=</span><span> </span><span>0</span><span>; hi </span><span>&lt;</span><span> tot_val; hi</span><span>++</span><span>) {</span></div></div><div><div><div>48</div></div><div><span>    </span><span>while</span><span> (hi </span><span>-</span><span> lo </span><span>+</span><span> </span><span>1</span><span> </span><span>&gt;</span><span> K) {</span></div></div><div><div><div>49</div></div><div><span><span>      </span></span><span>lo</span><span>++</span><span>;</span></div></div><div><div><div>50</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>51</div></div><div><span>    </span><span>int</span><span> unchanged </span><span>=</span><span> pre[hi </span><span>+</span><span> </span><span>1</span><span>] </span><span>-</span><span> pre[lo];</span></div></div><div><div><div>52</div></div><div><span>    </span><span>int</span><span> changes </span><span>=</span><span> n </span><span>-</span><span> unchanged;</span></div></div><div><div><div>53</div></div><div><span><span>    </span></span><span>cht </span><span>=</span><span> </span><span>min</span><span>(cht, changes);</span></div></div><div><div><div>54</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>55</div></div><div><span><span>  </span></span><span>cout </span><span>&lt;&lt;</span><span> cht </span><span>&lt;&lt;</span><span> endl;</span></div></div><div><div><div>56</div></div><div><span>}</span></div></div><div><div><div>57</div></div><div>
</div></div><div><div><div>58</div></div><div><span>signed</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>59</div></div><div><span>  </span><span>ios</span><span>::</span><span>sync_with_stdio</span><span>(</span><span>false</span><span>);</span></div></div><div><div><div>60</div></div><div><span><span>  </span></span><span>cin.</span><span>tie</span><span>(</span><span>0</span><span>), cout.</span><span>tie</span><span>(</span><span>0</span><span>);</span></div></div><div><div><div>61</div></div><div><span>  </span><span>int</span><span> T </span><span>=</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>62</div></div><div><span><span>  </span></span><span>// cin &gt;&gt; T;</span></div></div><div><div><div>63</div></div><div><span>  </span><span>while</span><span> (T</span><span>--</span><span>) </span><span>solve</span><span>();</span></div></div><div><div><div>64</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section></section>]]></content>
    <author><name>Zowely</name></author>
    <category term="算法竞赛" />
    <category term="算法竞赛" />
    <category term="题解" />
    <category term="C++" />
  </entry>
  <entry>
    <title>关于算法竞赛中代码模版的规范化问题</title>
    <link href="https://www.lansganbs.cn/posts/%E7%AE%97%E6%B3%95%E7%AB%9E%E8%B5%9B/%E5%85%B3%E4%BA%8E%E7%AE%97%E6%B3%95%E7%AB%9E%E8%B5%9B%E4%B8%AD%E4%BB%A3%E7%A0%81%E6%A8%A1%E7%89%88%E7%9A%84%E8%A7%84%E8%8C%83%E5%8C%96%E9%97%AE%E9%A2%98/" rel="alternate" type="text/html" />
    <id>https://www.lansganbs.cn/posts/%E7%AE%97%E6%B3%95%E7%AB%9E%E8%B5%9B/%E5%85%B3%E4%BA%8E%E7%AE%97%E6%B3%95%E7%AB%9E%E8%B5%9B%E4%B8%AD%E4%BB%A3%E7%A0%81%E6%A8%A1%E7%89%88%E7%9A%84%E8%A7%84%E8%8C%83%E5%8C%96%E9%97%AE%E9%A2%98/</id>
    <published>2024-12-31T00:00:00.000Z</published>
    <updated>2024-12-31T21:42:38.000Z</updated>
    <summary>关于算法竞赛中代码模版的规范化问题</summary>
    <content type="html"><![CDATA[<p>在平时写算法的时候看到别人的代码，有的时候代码很短，但是一言难尽；也有的人代码很长，但是却可以看得下去，几乎不影响观感。那么这就引申出一个问题，代码怎么写才能看起来更加的“规范”，“美观”，代码模板怎么写才能够保证“泛用性”，“可读性”。</p>
<section><h3>代码的格式问题<a href="#代码的格式问题"><span>#</span></a></h3><p>代码的基本格式我个人认为需要保证好基本的K&amp;R规范或者BSD规范，之后再这两种规范下进行微调。</p><blockquote><p>K&amp;R规范的明显标志为 左大括号不换行 右大括号和该范围的起始语句列数上持平</p><p>BSD规范的明显标志为 左右大括号全部换行 都和该范围的起始语句列数上持平</p></blockquote><p>具体的K&amp;R规范和BSD规范可以参见如下文章<a href="https://blog.csdn.net/weixin_30741653/article/details/97516375" target="_blank">c语言代码风格</a></p><p>其次看下面两段代码</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>#include &lt;bits/stdc++.h&gt;</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>using i64 = long long;</span></div></div><div><div><div>4</div></div><div><span>using u64 = unsigned long long;</span></div></div><div><div><div>5</div></div><div><span>using u32 = unsigned;</span></div></div><div><div><div>6</div></div><div><span>using u128 = unsigned __int128;</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>constexpr int inf = 1E9;</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span>void solve() {</span></div></div><div><div><div>11</div></div><div><span><span>    </span></span><span>int n, m;</span></div></div><div><div><div>12</div></div><div><span><span>    </span></span><span>std::cin &gt;&gt; n &gt;&gt; m;</span></div></div><div><div><div>13</div></div><div>
</div></div><div><div><div>14</div></div><div><span><span>    </span></span><span>std::vector&lt;int&gt; a(n), b(n), s(n);</span></div></div><div><div><div>15</div></div><div><span><span>    </span></span><span>for (int i = 0; i &lt; n; i++) {</span></div></div><div><div><div>16</div></div><div><span><span>        </span></span><span>std::cin &gt;&gt; a[i] &gt;&gt; b[i] &gt;&gt; s[i];</span></div></div><div><div><div>17</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>18</div></div><div>
</div></div><div><div><div>19</div></div><div><span><span>    </span></span><span>auto ss = s;</span></div></div><div><div><div>20</div></div><div><span><span>    </span></span><span>auto vs = a;</span></div></div><div><div><div>21</div></div><div><span><span>    </span></span><span>vs.insert(vs.end(), b.begin(), b.end());</span></div></div><div><div><div>22</div></div><div><span><span>    </span></span><span>std::sort(ss.begin(), ss.end());</span></div></div><div><div><div>23</div></div><div><span><span>    </span></span><span>std::sort(vs.begin(), vs.end());</span></div></div><div><div><div>24</div></div><div>
</div></div><div><div><div>25</div></div><div><span><span>    </span></span><span>vs.erase(std::unique(vs.begin(), vs.end()), vs.end());</span></div></div><div><div><div>26</div></div><div><span><span>    </span></span><span>ss.erase(std::unique(ss.begin(), ss.end()), ss.end());</span></div></div><div><div><div>27</div></div><div>
</div></div><div><div><div>28</div></div><div><span><span>    </span></span><span>for (int i = 0; i &lt; n; i++) {</span></div></div><div><div><div>29</div></div><div><span><span>        </span></span><span>a[i] = std::lower_bound(vs.begin(), vs.end(), a[i]) - vs.begin();</span></div></div><div><div><div>30</div></div><div><span><span>        </span></span><span>b[i] = std::lower_bound(vs.begin(), vs.end(), b[i]) - vs.begin();</span></div></div><div><div><div>31</div></div><div><span><span>        </span></span><span>s[i] = std::lower_bound(ss.begin(), ss.end(), s[i]) - ss.begin();</span></div></div><div><div><div>32</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>33</div></div><div>
</div></div><div><div><div>34</div></div><div><span><span>    </span></span><span>std::vector&lt;std::vector&lt;int&gt;&gt; q(m);</span></div></div><div><div><div>35</div></div><div><span><span>    </span></span><span>for (int i = 0; i &lt; m; i++) {</span></div></div><div><div><div>36</div></div><div><span><span>        </span></span><span>int k;</span></div></div><div><div><div>37</div></div><div><span><span>        </span></span><span>std::cin &gt;&gt; k;</span></div></div><div><div><div>38</div></div><div><span><span>        </span></span><span>q[i].resize(k);</span></div></div><div><div><div>39</div></div><div><span><span>        </span></span><span>for (int j = 0; j &lt; k; j++) {</span></div></div><div><div><div>40</div></div><div><span><span>            </span></span><span>std::cin &gt;&gt; q[i][j];</span></div></div><div><div><div>41</div></div><div><span><span>            </span></span><span>q[i][j]--;</span></div></div><div><div><div>42</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>43</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>44</div></div><div>
</div></div><div><div><div>45</div></div><div><span><span>    </span></span><span>const int nv = vs.size();</span></div></div><div><div><div>46</div></div><div><span><span>    </span></span><span>const int ns = ss.size();</span></div></div><div><div><div>47</div></div><div>
</div></div><div><div><div>48</div></div><div><span><span>    </span></span><span>int maxa = -1;</span></div></div><div><div><div>49</div></div><div>
</div></div><div><div><div>50</div></div><div><span><span>    </span></span><span>std::vector&lt;int&gt; L(ns, -inf), R(ns, inf);</span></div></div><div><div><div>51</div></div><div>
</div></div><div><div><div>52</div></div><div><span><span>    </span></span><span>std::vector&lt;std::vector&lt;std::array&lt;int, 2&gt;&gt;&gt; ban(ns + 1);</span></div></div><div><div><div>53</div></div><div><span><span>    </span></span><span>auto addSeg = [&amp;](int s, int l, int r) {</span></div></div><div><div><div>54</div></div><div><span><span>        </span></span><span>ban[s].push_back({l, r});</span></div></div><div><div><div>55</div></div><div><span><span>    </span></span><span>};</span></div></div><div><div><div>56</div></div><div>
</div></div><div><div><div>57</div></div><div><span><span>    </span></span><span>for (int i = m - 1; i &gt;= 0; i--) {</span></div></div><div><div><div>58</div></div><div><span><span>        </span></span><span>for (auto j : q[i]) {</span></div></div><div><div><div>59</div></div><div><span><span>            </span></span><span>if (maxa &lt; a[j]) {</span></div></div><div><div><div>60</div></div><div><span><span>                </span></span><span>continue;</span></div></div><div><div><div>61</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>62</div></div><div><span><span>            </span></span><span>R[s[j]] = std::min(R[s[j]], b[j]);</span></div></div><div><div><div>63</div></div><div><span><span>            </span></span><span>L[s[j]] = std::max(L[s[j]], maxa + 1);</span></div></div><div><div><div>64</div></div><div><span><span>            </span></span><span>if (a[j] &lt; maxa) {</span></div></div><div><div><div>65</div></div><div><span><span>                </span></span><span>addSeg(ns, a[j] + 1, maxa);</span></div></div><div><div><div>66</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>67</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>68</div></div><div><span><span>        </span></span><span>for (auto j : q[i]) {</span></div></div><div><div><div>69</div></div><div><span><span>            </span></span><span>maxa = std::max(maxa, a[j]);</span></div></div><div><div><div>70</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>71</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>72</div></div><div>
</div></div><div><div><div>73</div></div><div><span><span>    </span></span><span>std::vector&lt;std::vector&lt;int&gt;&gt; vec(ns);</span></div></div><div><div><div>74</div></div><div><span><span>    </span></span><span>for (int i = 0; i &lt; n; i++) {</span></div></div><div><div><div>75</div></div><div><span><span>        </span></span><span>vec[s[i]].push_back(i);</span></div></div><div><div><div>76</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>77</div></div><div>
</div></div><div><div><div>78</div></div><div><span><span>    </span></span><span>for (int i = 0; i &lt; ns; i++) {</span></div></div><div><div><div>79</div></div><div><span><span>        </span></span><span>if (L[i] &gt; R[i]) {</span></div></div><div><div><div>80</div></div><div><span><span>            </span></span><span>std::cout &lt;&lt; -1 &lt;&lt; "\n";</span></div></div><div><div><div>81</div></div><div><span><span>            </span></span><span>return;</span></div></div><div><div><div>82</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>83</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>84</div></div><div>
</div></div><div><div><div>85</div></div><div><span><span>    </span></span><span>int mina = inf;</span></div></div><div><div><div>86</div></div><div><span><span>    </span></span><span>for (int i = 0; i &lt; m; i++) {</span></div></div><div><div><div>87</div></div><div><span><span>        </span></span><span>for (auto j : q[i]) {</span></div></div><div><div><div>88</div></div><div><span><span>            </span></span><span>if (mina &gt; a[j]) {</span></div></div><div><div><div>89</div></div><div><span><span>                </span></span><span>continue;</span></div></div><div><div><div>90</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>91</div></div><div><span><span>            </span></span><span>if (b[j] &gt; a[j]) {</span></div></div><div><div><div>92</div></div><div><span><span>                </span></span><span>addSeg(s[j], a[j] + 1, b[j]);</span></div></div><div><div><div>93</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>94</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>95</div></div><div><span><span>        </span></span><span>for (auto j : q[i]) {</span></div></div><div><div><div>96</div></div><div><span><span>            </span></span><span>mina = std::min(mina, a[j]);</span></div></div><div><div><div>97</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>98</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>99</div></div><div>
</div></div><div><div><div>100</div></div><div><span><span>    </span></span><span>std::vector&lt;bool&gt; can(nv);</span></div></div><div><div><div>101</div></div><div><span><span>    </span></span><span>{</span></div></div><div><div><div>102</div></div><div><span><span>        </span></span><span>std::vector&lt;int&gt; d(nv);</span></div></div><div><div><div>103</div></div><div><span><span>        </span></span><span>for (auto [l, r] : ban[ns]) {</span></div></div><div><div><div>104</div></div><div><span><span>            </span></span><span>d[l]++;</span></div></div><div><div><div>105</div></div><div><span><span>            </span></span><span>if (r + 1 &lt; nv) {</span></div></div><div><div><div>106</div></div><div><span><span>                </span></span><span>d[r + 1]--;</span></div></div><div><div><div>107</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>108</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>109</div></div><div><span><span>        </span></span><span>for (int i = 1; i &lt; nv; i++) {</span></div></div><div><div><div>110</div></div><div><span><span>            </span></span><span>d[i] += d[i - 1];</span></div></div><div><div><div>111</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>112</div></div><div><span><span>        </span></span><span>for (int i = 0; i &lt; nv; i++) {</span></div></div><div><div><div>113</div></div><div><span><span>            </span></span><span>can[i] = (d[i] == 0);</span></div></div><div><div><div>114</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>115</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>116</div></div><div>
</div></div><div><div><div>117</div></div><div><span><span>    </span></span><span>std::vector&lt;int&gt; left(nv, -1);</span></div></div><div><div><div>118</div></div><div><span><span>    </span></span><span>for (int i = 0; i &lt; nv; i++) {</span></div></div><div><div><div>119</div></div><div><span><span>        </span></span><span>if (i &gt; 0) {</span></div></div><div><div><div>120</div></div><div><span><span>            </span></span><span>left[i] = left[i - 1];</span></div></div><div><div><div>121</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>122</div></div><div><span><span>        </span></span><span>if (can[i]) {</span></div></div><div><div><div>123</div></div><div><span><span>            </span></span><span>left[i] = i;</span></div></div><div><div><div>124</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>125</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>126</div></div><div>
</div></div><div><div><div>127</div></div><div><span><span>    </span></span><span>std::vector&lt;std::array&lt;int, 2&gt;&gt; ans;</span></div></div><div><div><div>128</div></div><div><span><span>    </span></span><span>for (int i = 0; i &lt; ns; i++) {</span></div></div><div><div><div>129</div></div><div><span><span>        </span></span><span>if (L[i] &lt; 0) {</span></div></div><div><div><div>130</div></div><div><span><span>            </span></span><span>continue;</span></div></div><div><div><div>131</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>132</div></div><div><span><span>        </span></span><span>int lst = -1;</span></div></div><div><div><div>133</div></div><div><span><span>        </span></span><span>std::vector&lt;std::array&lt;int, 2&gt;&gt; e;</span></div></div><div><div><div>134</div></div><div><span><span>        </span></span><span>for (auto [l, r] : ban[i]) {</span></div></div><div><div><div>135</div></div><div><span><span>            </span></span><span>e.push_back({l - 1, -1});</span></div></div><div><div><div>136</div></div><div><span><span>            </span></span><span>e.push_back({r, 1});</span></div></div><div><div><div>137</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>138</div></div><div><span><span>        </span></span><span>std::sort(e.begin(), e.end());</span></div></div><div><div><div>139</div></div><div><span><span>        </span></span><span>int sum = 0;</span></div></div><div><div><div>140</div></div><div><span><span>        </span></span><span>int val = -1;</span></div></div><div><div><div>141</div></div><div><span><span>        </span></span><span>for (auto [x, t] : e) {</span></div></div><div><div><div>142</div></div><div><span><span>            </span></span><span>if (sum == 0 &amp;&amp; x &gt;= 0) {</span></div></div><div><div><div>143</div></div><div><span><span>                </span></span><span>int u = left[std::min(x, R[i])];</span></div></div><div><div><div>144</div></div><div><span><span>                </span></span><span>if (u &gt; lst &amp;&amp; u &gt;= L[i]) {</span></div></div><div><div><div>145</div></div><div><span><span>                    </span></span><span>val = u;</span></div></div><div><div><div>146</div></div><div><span><span>                </span></span><span>}</span></div></div><div><div><div>147</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>148</div></div><div><span><span>            </span></span><span>sum += t;</span></div></div><div><div><div>149</div></div><div><span><span>            </span></span><span>lst = x;</span></div></div><div><div><div>150</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>151</div></div><div><span><span>        </span></span><span>if (int u = left[R[i]]; u &gt; lst &amp;&amp; u &gt;= L[i]) {</span></div></div><div><div><div>152</div></div><div><span><span>            </span></span><span>val = u;</span></div></div><div><div><div>153</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>154</div></div><div><span><span>        </span></span><span>if (val == -1) {</span></div></div><div><div><div>155</div></div><div><span><span>            </span></span><span>std::cout &lt;&lt; -1 &lt;&lt; "\n";</span></div></div><div><div><div>156</div></div><div><span><span>            </span></span><span>return;</span></div></div><div><div><div>157</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>158</div></div><div><span><span>        </span></span><span>ans.push_back({vs[val], ss[i]});</span></div></div><div><div><div>159</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>160</div></div><div>
</div></div><div><div><div>161</div></div><div><span><span>    </span></span><span>std::vector&lt;int&gt; score(n);</span></div></div><div><div><div>162</div></div><div><span><span>    </span></span><span>std::sort(ans.begin(), ans.end());</span></div></div><div><div><div>163</div></div><div><span><span>    </span></span><span>std::map&lt;int, int&gt; diff;</span></div></div><div><div><div>164</div></div><div><span><span>    </span></span><span>for (auto [d, p] : ans) {</span></div></div><div><div><div>165</div></div><div><span><span>        </span></span><span>diff[p] = d;</span></div></div><div><div><div>166</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>167</div></div><div><span><span>    </span></span><span>for (int i = 0; i &lt; n; i++) {</span></div></div><div><div><div>168</div></div><div><span><span>        </span></span><span>score[i] += std::lower_bound(ans.begin(), ans.end(), std::array {vs[a[i]] + 1, 0}) - ans.begin();</span></div></div><div><div><div>169</div></div><div><span><span>        </span></span><span>if (diff.contains(ss[s[i]])) {</span></div></div><div><div><div>170</div></div><div><span><span>            </span></span><span>int x = diff[ss[s[i]]];</span></div></div><div><div><div>171</div></div><div><span><span>            </span></span><span>if (vs[a[i]] &lt; x &amp;&amp; x &lt;= vs[b[i]]) {</span></div></div><div><div><div>172</div></div><div><span><span>                </span></span><span>score[i]++;</span></div></div><div><div><div>173</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>174</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>175</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>176</div></div><div>
</div></div><div><div><div>177</div></div><div><span><span>    </span></span><span>int cur = 0;</span></div></div><div><div><div>178</div></div><div><span><span>    </span></span><span>std::vector&lt;std::set&lt;int&gt;&gt; set(n + 1);</span></div></div><div><div><div>179</div></div><div><span><span>    </span></span><span>for (int i = m - 1; i &gt;= 0; i--) {</span></div></div><div><div><div>180</div></div><div><span><span>        </span></span><span>for (auto j : q[i]) {</span></div></div><div><div><div>181</div></div><div><span><span>            </span></span><span>int val = -1;</span></div></div><div><div><div>182</div></div><div><span><span>            </span></span><span>for (int k = score[j]; k &lt;= score[j] + 1 &amp;&amp; k &lt;= n; k++) {</span></div></div><div><div><div>183</div></div><div><span><span>                </span></span><span>auto it = set[k].lower_bound(a[j]);</span></div></div><div><div><div>184</div></div><div><span><span>                </span></span><span>if (it != set[k].begin()) {</span></div></div><div><div><div>185</div></div><div><span><span>                    </span></span><span>val = std::max(val, *std::prev(it));</span></div></div><div><div><div>186</div></div><div><span><span>                </span></span><span>}</span></div></div><div><div><div>187</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>188</div></div><div><span><span>            </span></span><span>if (val != -1) {</span></div></div><div><div><div>189</div></div><div><span><span>                </span></span><span>int u = left[a[j]];</span></div></div><div><div><div>190</div></div><div><span><span>                </span></span><span>if (u &lt;= val) {</span></div></div><div><div><div>191</div></div><div><span><span>                    </span></span><span>std::cout &lt;&lt; -1 &lt;&lt; "\n";</span></div></div><div><div><div>192</div></div><div><span><span>                    </span></span><span>return;</span></div></div><div><div><div>193</div></div><div><span><span>                </span></span><span>}</span></div></div><div><div><div>194</div></div><div><span><span>                </span></span><span>for (int t = 0; t &lt; 2; t++) {</span></div></div><div><div><div>195</div></div><div><span><span>                    </span></span><span>while (std::binary_search(ss.begin(), ss.end(), cur)) {</span></div></div><div><div><div>196</div></div><div><span><span>                        </span></span><span>cur++;</span></div></div><div><div><div>197</div></div><div><span><span>                    </span></span><span>}</span></div></div><div><div><div>198</div></div><div><span><span>                    </span></span><span>ans.push_back({vs[u], cur});</span></div></div><div><div><div>199</div></div><div><span><span>                    </span></span><span>cur++;</span></div></div><div><div><div>200</div></div><div><span><span>                </span></span><span>}</span></div></div><div><div><div>201</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>202</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>203</div></div><div><span><span>        </span></span><span>for (auto j : q[i]) {</span></div></div><div><div><div>204</div></div><div><span><span>            </span></span><span>set[score[j]].insert(a[j]);</span></div></div><div><div><div>205</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>206</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>207</div></div><div>
</div></div><div><div><div>208</div></div><div><span><span>    </span></span><span>std::cout &lt;&lt; ans.size() &lt;&lt; "\n";</span></div></div><div><div><div>209</div></div><div><span><span>    </span></span><span>for (auto [d, p] : ans) {</span></div></div><div><div><div>210</div></div><div><span><span>        </span></span><span>std::cout &lt;&lt; d &lt;&lt; " " &lt;&lt; p &lt;&lt; "\n";</span></div></div><div><div><div>211</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>212</div></div><div>
</div></div><div><div><div>213</div></div><div><span><span>    </span></span><span>// {</span></div></div><div><div><div>214</div></div><div><span><span>    </span></span><span>//     std::vector&lt;int&gt; score(n);</span></div></div><div><div><div>215</div></div><div><span><span>    </span></span><span>//     for (int i = 0; i &lt; n; i++) {</span></div></div><div><div><div>216</div></div><div><span><span>    </span></span><span>//         for (auto [d, p] : ans) {</span></div></div><div><div><div>217</div></div><div><span><span>    </span></span><span>//             if (vs[a[i]] &gt;= d || (ss[s[i]] == p &amp;&amp; vs[b[i]] &gt;= d)) {</span></div></div><div><div><div>218</div></div><div><span><span>    </span></span><span>//                 score[i]++;</span></div></div><div><div><div>219</div></div><div><span><span>    </span></span><span>//             }</span></div></div><div><div><div>220</div></div><div><span><span>    </span></span><span>//         }</span></div></div><div><div><div>221</div></div><div><span><span>    </span></span><span>//     }</span></div></div><div><div><div>222</div></div><div><span><span>    </span></span><span>//     for (int i = 0; i &lt; m; i++) {</span></div></div><div><div><div>223</div></div><div><span><span>    </span></span><span>//         for (int j = i + 1; j &lt; m; j++) {</span></div></div><div><div><div>224</div></div><div><span><span>    </span></span><span>//             for (auto x : q[i]) {</span></div></div><div><div><div>225</div></div><div><span><span>    </span></span><span>//                 for (auto y : q[j]) {</span></div></div><div><div><div>226</div></div><div><span><span>    </span></span><span>//                     assert(score[x] &gt; score[y]);</span></div></div><div><div><div>227</div></div><div><span><span>    </span></span><span>//                 }</span></div></div><div><div><div>228</div></div><div><span><span>    </span></span><span>//             }</span></div></div><div><div><div>229</div></div><div><span><span>    </span></span><span>//         }</span></div></div><div><div><div>230</div></div><div><span><span>    </span></span><span>//     }</span></div></div><div><div><div>231</div></div><div><span><span>    </span></span><span>//     assert(!ans.empty());</span></div></div><div><div><div>232</div></div><div><span><span>    </span></span><span>//     for (int i = 0; i &lt; ans.size(); i++) {</span></div></div><div><div><div>233</div></div><div><span><span>    </span></span><span>//         for (int j = 0; j &lt; i; j++) {</span></div></div><div><div><div>234</div></div><div><span><span>    </span></span><span>//             assert(ans[i][1] != ans[j][1]);</span></div></div><div><div><div>235</div></div><div><span><span>    </span></span><span>//         }</span></div></div><div><div><div>236</div></div><div><span><span>    </span></span><span>//     }</span></div></div><div><div><div>237</div></div><div><span><span>    </span></span><span>// }</span></div></div><div><div><div>238</div></div><div><span>}</span></div></div><div><div><div>239</div></div><div>
</div></div><div><div><div>240</div></div><div><span>int main() {</span></div></div><div><div><div>241</div></div><div><span><span>    </span></span><span>std::ios::sync_with_stdio(false);</span></div></div><div><div><div>242</div></div><div><span><span>    </span></span><span>std::cin.tie(nullptr);</span></div></div><div><div><div>243</div></div><div>
</div></div><div><div><div>244</div></div><div><span><span>    </span></span><span>int t;</span></div></div><div><div><div>245</div></div><div><span><span>    </span></span><span>std::cin &gt;&gt; t;</span></div></div><div><div><div>246</div></div><div>
</div></div><div><div><div>247</div></div><div><span><span>    </span></span><span>while (t--) {</span></div></div><div><div><div>248</div></div><div><span><span>        </span></span><span>solve();</span></div></div><div><div><div>249</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>250</div></div><div>
</div></div><div><div><div>251</div></div><div><span><span>    </span></span><span>return 0;</span></div></div><div><div><div>252</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span> </span></span><span>/*</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span><span>                                                                   </span></span><span>|| ॐ नमः पार्वती पतये हर हर महादेव ||</span></div></div><div><div><div>5</div></div><div><span><span>                                      </span></span><span>⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀</span></div></div><div><div><div>6</div></div><div><span><span>                                    </span></span><span>⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣰⣷⣗⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀</span></div></div><div><div><div>7</div></div><div><span><span>                                    </span></span><span>⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡀⠀⠀⠀⠀⠀⠀⣠⣾⡿⠁⠈⢓⣟⡢⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀</span></div></div><div><div><div>8</div></div><div><span><span>                                    </span></span><span>⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣧⠀⠀⠀⠀⠀⣰⣿⠃⠀⠀⠀⠀⢐⣿⣼⠆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀</span></div></div><div><div><div>9</div></div><div><span><span>                                    </span></span><span>⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢿⣖⡀⠀⠀⠀⠈⠻⢗⣄⠀⢀⣴⣿⠟⠁⠀⠀⠀⠀⠀⢀⣴⡦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀</span></div></div><div><div><div>10</div></div><div><span><span>                                    </span></span><span>⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⣿⡾⡢⣀⠀⠀⠀⠀⠛⠗⣿⠟⠁⠀⠀⠀⠀⢀⣠⣴⠿⠋⠻⣽⢆⠀⠀⠀⠀⠀⠀⠀⠀</span></div></div><div><div><div>11</div></div><div><span><span>                                    </span></span><span>⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⣿⣝⢷⢦⣤⣠⣀⡀⠀⠁⠀⣀⣀⠀⣄⡶⠟⠋⠀⠀⡀⠀⠈⢻⣷⣄⠀⠀⠀⠀⠀⠀</span></div></div><div><div><div>12</div></div><div><span><span>                                    </span></span><span>⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡀⠀⠐⠊⠉⠉⠉⠉⠛⠛⠷⠷⣴⡄⣀⠀⠈⠿⡧⡄⠉⠉⠁⠛⠛⠋⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⣾⡿⠋⠀⠀⠀⠀⠀⠀</span></div></div><div><div><div>13</div></div><div><span><span>                                    </span></span><span>⠀⠀⠀⠀⠀⠀⠀⠀⢀⣤⣼⠿⠛⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠻⣿⢤⡀⠀⠛⢟⣄⠀⠀⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣤⢿⡿⠃⠀⠀⠀⠀⠀⠀⠀⠀</span></div></div><div><div><div>14</div></div><div><span><span>                                    </span></span><span>⠀⠀⠀⠀⠀⠀⣤⣾⠟⠉⠀⠀⠀⠀⠠⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⢳⡻⡄⠀⠀⠐⠠⠥⣥⣀⣀⣄⣀⣀⣀⣠⣤⣴⠶⣚⠯⠚⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀</span></div></div><div><div><div>15</div></div><div><span><span>                                    </span></span><span>⠀⠀⠀⠀⠀⠀⠈⢿⣧⠀⠀⠀⠀⠀⠀⠀⣀⣠⣤⣤⣦⣤⣄⡁⠠⠀⠀⠀⢳⣻⠀⠀⠀⠀⠀⠀⠈⠉⠉⠛⠛⠋⠉⠁⢀⣀⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀</span></div></div><div><div><div>16</div></div><div><span><span>                                    </span></span><span>⠀⠀⠀⠀⠀⠀⠀⠀⢿⣷⠀⢀⣠⡴⣞⠿⠍⠁⠀⠀⠀⠀⠈⠙⠸⣷⣄⠀⠈⣽⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣴⡶⠈⠀⠉⠙⠛⠻⣷⣴⡀⡀⠀⠀⠀⠀⠀⠀⠀</span></div></div><div><div><div>17</div></div><div><span><span>                                    </span></span><span>⠀⠀⠀⠀⠀⠀⠀⠀⠀⢻⣧⡿⠓⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⣾⡆⢨⣽⠃⠀⠀⠀⠀⠀⠀⠀⣠⣾⠟⠁⠀⠀⠂⠀⠀⢀⣀⣄⣉⠻⢾⣷⣄⠀⠀⠀⠀⠀</span></div></div><div><div><div>18</div></div><div><span><span>                                    </span></span><span>⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣨⡇⣾⡿⠀⠀⠀⠀⠀⠀⢀⣼⡿⠁⠀⠀⠀⠀⢠⣴⣿⠯⠋⠉⠙⠳⠰⣮⣻⣷⡀⠀⠀⠀</span></div></div><div><div><div>19</div></div><div><span><span>                                    </span></span><span>⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣾⠟⣼⣿⠁⠀⠀⠀⠀⠀⢠⣾⠟⠀⠀⠀⠀⠀⣴⣿⠟⠀⠀⠀⠀⠀⠀⠀⠈⢿⣿⡷⡀⠀⠀</span></div></div><div><div><div>20</div></div><div><span><span>                                    </span></span><span>⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣦⡦⣤⣤⣤⣤⠤⠴⠰⠾⠛⢁⣾⡿⠁⠀⠀⠀⠀⠀⢠⣿⠋⠀⠀⠀⠀⠀⣼⡿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⣧⡷⣳⠀⠀</span></div></div><div><div><div>21</div></div><div><span><span>                                    </span></span><span>⠀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠠⠱⣷⡆⠀⠀⠀⠀⠀⢀⣠⢶⡿⠋⠀⠀⠀⠀⠀⠀⣔⡽⠃⠀⠀⠀⠀⠀⣼⡿⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣵⡍⣇⠀</span></div></div><div><div><div>22</div></div><div><span><span>                                    </span></span><span>⢠⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢝⣷⡀⠀⠀⠀⠀⠸⣿⣫⣀⠀⠀⠀⠀⠀⢀⣼⠟⠀⠀⠀⠀⠀⢠⣞⡟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⣿⢹⢾⠀</span></div></div><div><div><div>23</div></div><div><span><span>                                    </span></span><span>⢸⠇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⣾⣇⠀⠀⠀⠀⠀⠀⠉⠙⠻⠷⠲⠖⠾⠛⠁⠀⠀⠀⣀⣠⣞⡷⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠸⣿⢸⣾⡃</span></div></div><div><div><div>24</div></div><div><span><span>                                    </span></span><span>⢸⣠⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠰⣯⠞⠛⠛⠛⠽⠶⣄⢀⡄⠀⠀⣴⢿⠻⠿⠿⠿⠛⠏⠃⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢂⣾⢸⣿⡅</span></div></div><div><div><div>25</div></div><div><span><span>                                    </span></span><span>⢀⣏⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⠺⣷⣀⠙⣾⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣿⠃⢸⣻⠆</span></div></div><div><div><div>26</div></div><div><span><span>                                    </span></span><span>⠈⣿⢾⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠹⣿⡄⠹⡸⡄⠀⠀⠀⢸⠇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣿⠏⠀⢾⣿⠀</span></div></div><div><div><div>27</div></div><div><span><span>                                    </span></span><span>⠀⢻⡿⣱⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢹⡇⠀⣧⣷⠀⠀⠀⢸⣟⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⡿⠋⠀⠀⣇⡏⠀</span></div></div><div><div><div>28</div></div><div><span><span>                                    </span></span><span>⠀⠈⣿⡼⡵⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⠇⠀⢸⢿⠀⠀⠀⣸⣯⢿⣵⣀⣀⠀⠀⠀⠀⠀⢀⢀⣤⣾⠟⠋⠀⠀⠀⣼⣻⠁⠀</span></div></div><div><div><div>29</div></div><div><span><span>                                    </span></span><span>⠀⠀⢸⣯⡝⣮⢣⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣾⣿⠀⠀⢸⣿⠀⠀⠀⢸⣿⠂⠈⠙⠛⠛⠛⠛⠉⠉⠐⠒⠁⠀⠀⠀⠀⠀⢠⣷⡏⠀⠀</span></div></div><div><div><div>30</div></div><div><span><span>                                    </span></span><span>⠀⠀⠀⢻⣷⡙⢷⢿⢤⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣾⡟⠃⠀⠀⣼⣿⠀⠀⠀⠀⢿⣷⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⢿⡞⠁⠀⠀</span></div></div><div><div><div>31</div></div><div><span><span>                                    </span></span><span>⠀⠀⠀⠀⠻⣿⡄⠈⠛⠶⣵⣀⢀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣤⠾⠛⠁⠀⠂⠀⢀⣷⡏⠀⠀⠀⠀⠈⢻⣷⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡰⣫⠟⠀⠀⠀⠀</span></div></div><div><div><div>32</div></div><div><span><span>                                    </span></span><span>⠀⠀⠀⠀⠀⠙⢿⣦⠀⠀⠈⠘⠙⠻⠟⠿⠷⠒⠚⠙⠁⠀⠈⠁⠀⠀⠀⠀⠀⢀⣾⡿⠀⠀⠀⠀⠀⠀⠀⠙⢿⡶⠠⢀⠀⠀⠀⠀⢀⣀⣄⠀⣠⠚⠁⠀⠀⠀⠀⠀</span></div></div><div><div><div>33</div></div><div><span><span>                                    </span></span><span>⠀⠀⠀⠀⠀⠀⠈⠻⣿⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣿⡟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠙⠒⠻⠿⠿⠛⠛⠉⠐⠁⠀⠀⠀⠀⠀⠀⠀⠀</span></div></div><div><div><div>34</div></div><div><span><span>                                    </span></span><span>⠀⠀⠀⠀⠀⠀⠀⠀⠈⠙⢙⠀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡀⣠⢞⡵⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀</span></div></div><div><div><div>35</div></div><div><span><span>                                    </span></span><span>⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠃⠖⠶⣶⣤⣤⡤⢤⣤⡤⠶⠞⠫⠒⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀</span></div></div><div><div><div>36</div></div><div><span><span>                                    </span></span><span>⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀</span></div></div><div><div><div>37</div></div><div>
</div></div><div><div><div>38</div></div><div>
</div></div><div><div><div>39</div></div><div><span><span>                                                          </span></span><span>।। ऊँ कृष्णाय वासुदेवाय हरये परमात्मने ।।</span></div></div><div><div><div>40</div></div><div><span><span>                                                          </span></span><span>।। प्रणतः क्लेशनाशाय गोविंदाय नमो नमः ।।</span></div></div><div><div><div>41</div></div><div>
</div></div><div><div><div>42</div></div><div>
</div></div><div><div><div>43</div></div><div>
</div></div><div><div><div>44</div></div><div><span><span>                                            </span></span><span>---------------&gt; tiwari ji presents &lt;---------------</span></div></div><div><div><div>45</div></div><div>
</div></div><div><div><div>46</div></div><div><span><span>  </span></span><span>“ Mnn boot karega k chor yrr apne se nahi hoga  Just ask 1 question “ Why I started ? “</span></div></div><div><div><div>47</div></div><div>
</div></div><div><div><div>48</div></div><div><span><span>    </span></span><span>"Countless reasons not to do it, but only one reason to go for it — and that's enough."*/</span></div></div><div><div><div>49</div></div><div>
</div></div><div><div><div>50</div></div><div>
</div></div><div><div><div>51</div></div><div>
</div></div><div><div><div>52</div></div><div><span>#include &lt;bits/stdc++.h&gt;</span></div></div><div><div><div>53</div></div><div><span>using namespace std;</span></div></div><div><div><div>54</div></div><div><span>#define ll long long</span></div></div><div><div><div>55</div></div><div><span>#define pb push_back</span></div></div><div><div><div>56</div></div><div><span>#define pp pop_back()</span></div></div><div><div><div>57</div></div><div><span>#define vi vector&lt;ll&gt;</span></div></div><div><div><div>58</div></div><div><span>#define tiwari ll t ; cin&gt;&gt;t; while(t--)</span></div></div><div><div><div>59</div></div><div><span>#define  loop  for(int i=0;i&lt;n;i++){cin&gt;&gt;a[i];}</span></div></div><div><div><div>60</div></div><div><span>#define  mn ll m,n; cin&gt;&gt;m&gt;&gt;n;</span></div></div><div><div><div>61</div></div><div><span>#define  str string s ; cin&gt;&gt;s;</span></div></div><div><div><div>62</div></div><div><span>#define str2 string s; cin&gt;&gt;s; string t; cin&gt;&gt;t;</span></div></div><div><div><div>63</div></div><div><span>#define  set unordered_set&lt;int&gt;seen;</span></div></div><div><div><div>64</div></div><div><span>#define  mii unordered_map&lt;int,int&gt;mp;</span></div></div><div><div><div>65</div></div><div><span>#define  mic unordered_map&lt;int ,char&gt;mpp;</span></div></div><div><div><div>66</div></div><div><span>#define  mcc unordered_map&lt;char,char&gt;mpc;</span></div></div><div><div><div>67</div></div><div><span>#define srt sort(a.begin(),a.end());</span></div></div><div><div><div>68</div></div><div><span>#define couta for(auto it:a){cout&lt;&lt;it&lt;&lt;" ";}</span></div></div><div><div><div>69</div></div><div><span>#define bp1 __builtin_popcount</span></div></div><div><div><div>70</div></div><div><span>#define bpll __builtin_popcountll</span></div></div><div><div><div>71</div></div><div><span>#define countdigits int cg=(int)(log10(n)+1)</span></div></div><div><div><div>72</div></div><div><span>#define  surukezero __builtin_clz(a)</span></div></div><div><div><div>73</div></div><div><span>#define  lastkezero __builtin_ctz(a)</span></div></div><div><div><div>74</div></div><div><span>#define  decimalnumberdega auto number=0bn;</span></div></div><div><div><div>75</div></div><div><span>#define  ithbit1haiyanahi if((n&gt;&gt;i)&amp;1){}</span></div></div><div><div><div>76</div></div><div><span>#define last1removekaro n&amp;(n-1)</span></div></div><div><div><div>77</div></div><div><span>#define binaryme bitset&lt;32&gt;(xr).to_string();</span></div></div><div><div><div>78</div></div><div>
</div></div><div><div><div>79</div></div><div><span>int main() {</span></div></div><div><div><div>80</div></div><div>
</div></div><div><div><div>81</div></div><div><span><span>     </span></span><span>tiwari{</span></div></div><div><div><div>82</div></div><div>
</div></div><div><div><div>83</div></div><div><span><span>         </span></span><span>int n;</span></div></div><div><div><div>84</div></div><div><span><span>         </span></span><span>cin&gt;&gt;n;</span></div></div><div><div><div>85</div></div><div>
</div></div><div><div><div>86</div></div><div><span><span>         </span></span><span>vi a(n);</span></div></div><div><div><div>87</div></div><div><span><span>         </span></span><span>loop;</span></div></div><div><div><div>88</div></div><div><span><span>         </span></span><span>srt;</span></div></div><div><div><div>89</div></div><div>
</div></div><div><div><div>90</div></div><div><span><span>      </span></span><span>int ans=0;</span></div></div><div><div><div>91</div></div><div><span><span>         </span></span><span>for(int i=0;i&lt;n-1;){</span></div></div><div><div><div>92</div></div><div><span><span>             </span></span><span>if(a[i]==a[i+1]){</span></div></div><div><div><div>93</div></div><div><span><span>                </span></span><span>ans=ans+1;</span></div></div><div><div><div>94</div></div><div><span><span>                </span></span><span>i=i+2;</span></div></div><div><div><div>95</div></div><div><span><span>             </span></span><span>}</span></div></div><div><div><div>96</div></div><div><span><span>             </span></span><span>else{</span></div></div><div><div><div>97</div></div><div><span><span>                </span></span><span>i++;</span></div></div><div><div><div>98</div></div><div><span><span>             </span></span><span>}</span></div></div><div><div><div>99</div></div><div><span><span>         </span></span><span>}</span></div></div><div><div><div>100</div></div><div><span><span>         </span></span><span>cout&lt;&lt;ans&lt;&lt;endl;</span></div></div><div><div><div>101</div></div><div><span><span>     </span></span><span>}</span></div></div><div><div><div>102</div></div><div>
</div></div><div><div><div>103</div></div><div>
</div></div><div><div><div>104</div></div><div>
</div></div><div><div><div>105</div></div><div>
</div></div><div><div><div>106</div></div><div>
</div></div><div><div><div>107</div></div><div>
</div></div><div><div><div>108</div></div><div>
</div></div><div><div><div>109</div></div><div>
</div></div><div><div><div>110</div></div><div>
</div></div><div><div><div>111</div></div><div><span><span>    </span></span><span>return 0;</span></div></div><div><div><div>112</div></div><div><span>}</span></div></div><div><div><div>113</div></div><div>
</div></div><div><div><div>114</div></div><div><span>/* JAB TAK KHUD KUCH NA KAR LO TABTAK DUSRO KO GYAN MAT DO */</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>上述两份代码皆选自Codeforces上的正确提交，谁的代码风格更好不言而喻。</p><p>第一份代码出自Jiangly。</p><p>第二份代码充斥了大量的宏定义，字符画，多余的空格，看似意义不明的函数，拥挤的代码…这份代码规范差，起手模板的宏定义可读性差，基本循环语句甚至包含了宏定义，码风极差。</p></section>
<section><h3>关于代码的起手<a href="#关于代码的起手"><span>#</span></a></h3><p>常见的基础模版大体分为两种</p><ol>
<li>基本代码模版</li>
</ol><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>#include &lt;bits/stdc++.h&gt;</span></div></div><div><div><div>2</div></div><div><span>using namespace std;</span></div></div><div><div><div>3</div></div><div><span>void solve() {</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>}</span></div></div><div><div><div>6</div></div><div><span>int main() {</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>ios::sync_with_stdio(false);</span></div></div><div><div><div>8</div></div><div><span><span>  </span></span><span>cin.tie(0), cout.tie(0);</span></div></div><div><div><div>9</div></div><div><span><span>  </span></span><span>int tt = 1;</span></div></div><div><div><div>10</div></div><div><span><span>  </span></span><span>// cin &gt;&gt; tt;</span></div></div><div><div><div>11</div></div><div><span><span>  </span></span><span>while (tt--) {</span></div></div><div><div><div>12</div></div><div><span><span>      </span></span><span>solve();</span></div></div><div><div><div>13</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>14</div></div><div><span><span>  </span></span><span>return 0;</span></div></div><div><div><div>15</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><ol>
<li>长篇大论的包含宏定义+函数+重载等的模板，如</li>
</ol><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>#include &lt;bits/stdc++.h&gt;</span></div></div><div><div><div>2</div></div><div><span>// #include &lt;bits/extc++.h&gt;</span></div></div><div><div><div>3</div></div><div><span>using namespace std;</span></div></div><div><div><div>4</div></div><div><span>// using namespace __gnu_pbds;</span></div></div><div><div><div>5</div></div><div><span>#define endl '\n'</span></div></div><div><div><div>6</div></div><div><span>#define ture true</span></div></div><div><div><div>7</div></div><div><span>#define flase false</span></div></div><div><div><div>8</div></div><div><span>#define pow power</span></div></div><div><div><div>9</div></div><div><span>#define all(x) begin(x), end(x)</span></div></div><div><div><div>10</div></div><div><span>#define mem(a, x) memset(a, x, sizeof(a))</span></div></div><div><div><div>11</div></div><div><span>#define gcd(a, b) gcdint(a, b)</span></div></div><div><div><div>12</div></div><div><span>#define lcm(a, b) (a / gcd(a, b) * b)</span></div></div><div><div><div>13</div></div><div><span>#define sz(x) (int)x.size()</span></div></div><div><div><div>14</div></div><div><span>#define lowbit(x) (x &amp; -x)</span></div></div><div><div><div>15</div></div><div><span>#define pb push_back</span></div></div><div><div><div>16</div></div><div><span>#define EPS 1e-7</span></div></div><div><div><div>17</div></div><div><span>#define int ll</span></div></div><div><div><div>18</div></div><div><span>#define ll long long</span></div></div><div><div><div>19</div></div><div><span>#define i64 long long</span></div></div><div><div><div>20</div></div><div><span>#define i128 __int128</span></div></div><div><div><div>21</div></div><div><span>#define fr first</span></div></div><div><div><div>22</div></div><div><span>#define sc second</span></div></div><div><div><div>23</div></div><div><span>#define tcT template &lt;class T</span></div></div><div><div><div>24</div></div><div><span>#define tcTU tcT, class U</span></div></div><div><div><div>25</div></div><div>
</div></div><div><div><div>26</div></div><div><span>void unsyncIO() { cin.tie(0)-&gt;sync_with_stdio(0); }</span></div></div><div><div><div>27</div></div><div><span>void setPrec() { cout &lt;&lt; fixed &lt;&lt; setprecision(15); }</span></div></div><div><div><div>28</div></div><div><span>void setIO() { unsyncIO(), setPrec(); }</span></div></div><div><div><div>29</div></div><div>
</div></div><div><div><div>30</div></div><div><span>inline int gcdint(int a, int b) { return b ? gcdint(b, a % b) : a; }</span></div></div><div><div><div>31</div></div><div><span>inline i128 gcd128(i128 a, i128 b) { return b ? gcd128(b, a % b) : a; }</span></div></div><div><div><div>32</div></div><div><span>inline int cdiv(int a, int b) { return a / b + ((a ^ b) &gt; 0 &amp;&amp; a % b); }</span></div></div><div><div><div>33</div></div><div><span>inline int fdiv(int a, int b) { return a / b - ((a ^ b) &lt; 0 &amp;&amp; a % b); }</span></div></div><div><div><div>34</div></div><div>
</div></div><div><div><div>35</div></div><div><span>tcT &gt; using V = vector&lt;T&gt;;</span></div></div><div><div><div>36</div></div><div><span>tcTU &gt; using PR = pair&lt;T, U&gt;;</span></div></div><div><div><div>37</div></div><div><span>tcTU &gt; using MP = map&lt;T, U&gt;;</span></div></div><div><div><div>38</div></div><div><span>tcTU &gt; using VP = vector&lt;pair&lt;T, U&gt;&gt;;</span></div></div><div><div><div>39</div></div><div><span>tcT &gt; using pqg = priority_queue&lt;T, vector&lt;T&gt;, greater&lt;T&gt;&gt;;</span></div></div><div><div><div>40</div></div><div><span>tcT &gt; using pql = priority_queue&lt;T, vector&lt;T&gt;, less&lt;T&gt;&gt;;</span></div></div><div><div><div>41</div></div><div>
</div></div><div><div><div>42</div></div><div><span>tcTU &gt; istream &amp;operator&gt;&gt;(istream &amp;in, pair&lt;T, U&gt; &amp;a) {</span></div></div><div><div><div>43</div></div><div><span><span>  </span></span><span>return in &gt;&gt; a.first &gt;&gt; a.second;</span></div></div><div><div><div>44</div></div><div><span>}</span></div></div><div><div><div>45</div></div><div>
</div></div><div><div><div>46</div></div><div><span>tcT &gt; istream &amp;operator&gt;&gt;(istream &amp;in, vector&lt;T&gt; &amp;a) {</span></div></div><div><div><div>47</div></div><div><span><span>  </span></span><span>for (auto &amp;x : a) {</span></div></div><div><div><div>48</div></div><div><span><span>    </span></span><span>in &gt;&gt; x;</span></div></div><div><div><div>49</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>50</div></div><div><span><span>  </span></span><span>return in;</span></div></div><div><div><div>51</div></div><div><span>}</span></div></div><div><div><div>52</div></div><div>
</div></div><div><div><div>53</div></div><div><span>tcTU &gt; ostream &amp;operator&lt;&lt;(ostream &amp;out, const pair&lt;T, U&gt; &amp;a) {</span></div></div><div><div><div>54</div></div><div><span><span>  </span></span><span>return out &lt;&lt; a.first &lt;&lt; ' ' &lt;&lt; a.second;</span></div></div><div><div><div>55</div></div><div><span>}</span></div></div><div><div><div>56</div></div><div>
</div></div><div><div><div>57</div></div><div><span>tcTU &gt; ostream &amp;operator&lt;&lt;(ostream &amp;out, const vector&lt;pair&lt;T, U&gt;&gt; &amp;a) {</span></div></div><div><div><div>58</div></div><div><span><span>  </span></span><span>for (auto &amp;x : a) {</span></div></div><div><div><div>59</div></div><div><span><span>    </span></span><span>out &lt;&lt; x &lt;&lt; endl;</span></div></div><div><div><div>60</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>61</div></div><div><span><span>  </span></span><span>return out;</span></div></div><div><div><div>62</div></div><div><span>}</span></div></div><div><div><div>63</div></div><div>
</div></div><div><div><div>64</div></div><div><span>tcT &gt; ostream &amp;operator&lt;&lt;(ostream &amp;out, const vector&lt;T&gt; &amp;a) {</span></div></div><div><div><div>65</div></div><div><span><span>  </span></span><span>int n = a.size();</span></div></div><div><div><div>66</div></div><div><span><span>  </span></span><span>if (!n) {</span></div></div><div><div><div>67</div></div><div><span><span>    </span></span><span>return out;</span></div></div><div><div><div>68</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>69</div></div><div><span><span>  </span></span><span>out &lt;&lt; a[0];</span></div></div><div><div><div>70</div></div><div><span><span>  </span></span><span>for (int i = 1; i &lt; n; i++) {</span></div></div><div><div><div>71</div></div><div><span><span>    </span></span><span>out &lt;&lt; ' ' &lt;&lt; a[i];</span></div></div><div><div><div>72</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>73</div></div><div><span><span>  </span></span><span>return out;</span></div></div><div><div><div>74</div></div><div><span>}</span></div></div><div><div><div>75</div></div><div>
</div></div><div><div><div>76</div></div><div><span>std::ostream &amp;operator&lt;&lt;(std::ostream &amp;os, i128 n) {</span></div></div><div><div><div>77</div></div><div><span><span>  </span></span><span>std::string s;</span></div></div><div><div><div>78</div></div><div><span><span>  </span></span><span>while (n) {</span></div></div><div><div><div>79</div></div><div><span><span>    </span></span><span>s += '0' + n % 10;</span></div></div><div><div><div>80</div></div><div><span><span>    </span></span><span>n /= 10;</span></div></div><div><div><div>81</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>82</div></div><div><span><span>  </span></span><span>std::reverse(s.begin(), s.end());</span></div></div><div><div><div>83</div></div><div><span><span>  </span></span><span>return os &lt;&lt; s;</span></div></div><div><div><div>84</div></div><div><span>}</span></div></div><div><div><div>85</div></div><div>
</div></div><div><div><div>86</div></div><div><span>inline int power(int a, i64 b, int p = 1e9 + 7) {</span></div></div><div><div><div>87</div></div><div><span><span>  </span></span><span>int res = 1;</span></div></div><div><div><div>88</div></div><div><span><span>  </span></span><span>for (; b; b /= 2, a = 1LL * a * a % p) {</span></div></div><div><div><div>89</div></div><div><span><span>    </span></span><span>if (b % 2) {</span></div></div><div><div><div>90</div></div><div><span><span>      </span></span><span>res = 1LL * res * a % p;</span></div></div><div><div><div>91</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>92</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>93</div></div><div><span><span>  </span></span><span>return res;</span></div></div><div><div><div>94</div></div><div><span>}</span></div></div><div><div><div>95</div></div><div>
</div></div><div><div><div>96</div></div><div><span>tcT &gt; bool ckmin(T &amp;a, const T &amp;b) { return b &lt; a ? a = b, 1 : 0; }</span></div></div><div><div><div>97</div></div><div><span>tcT &gt; bool ckmax(T &amp;a, const T &amp;b) { return a &lt; b ? a = b, 1 : 0; }</span></div></div><div><div><div>98</div></div><div>
</div></div><div><div><div>99</div></div><div><span>tcT &gt; void remDup(vector&lt;T&gt; &amp;v) {</span></div></div><div><div><div>100</div></div><div><span><span>  </span></span><span>sort(all(v));</span></div></div><div><div><div>101</div></div><div><span><span>  </span></span><span>v.erase(unique(all(v)), end(v));</span></div></div><div><div><div>102</div></div><div><span>}</span></div></div><div><div><div>103</div></div><div>
</div></div><div><div><div>104</div></div><div><span>tcTU &gt; void erase(T &amp;t, const U &amp;u) {</span></div></div><div><div><div>105</div></div><div><span><span>  </span></span><span>auto it = t.find(u);</span></div></div><div><div><div>106</div></div><div><span><span>  </span></span><span>assert(it != end(t));</span></div></div><div><div><div>107</div></div><div><span><span>  </span></span><span>t.erase(it);</span></div></div><div><div><div>108</div></div><div><span>}</span></div></div><div><div><div>109</div></div><div>
</div></div><div><div><div>110</div></div><div><span>tcTU &gt; T fstTrue(T lo, T hi, U f) {</span></div></div><div><div><div>111</div></div><div><span><span>  </span></span><span>hi++;</span></div></div><div><div><div>112</div></div><div><span><span>  </span></span><span>assert(lo &lt;= hi);</span></div></div><div><div><div>113</div></div><div><span><span>  </span></span><span>while (lo &lt; hi) {</span></div></div><div><div><div>114</div></div><div><span><span>    </span></span><span>T mid = lo + (hi - lo) / 2;</span></div></div><div><div><div>115</div></div><div><span><span>    </span></span><span>f(mid) ? hi = mid : lo = mid + 1;</span></div></div><div><div><div>116</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>117</div></div><div><span><span>  </span></span><span>return lo;</span></div></div><div><div><div>118</div></div><div><span>}</span></div></div><div><div><div>119</div></div><div>
</div></div><div><div><div>120</div></div><div><span>tcTU &gt; T lstTrue(T lo, T hi, U f) {</span></div></div><div><div><div>121</div></div><div><span><span>  </span></span><span>lo--;</span></div></div><div><div><div>122</div></div><div><span><span>  </span></span><span>assert(lo &lt;= hi);</span></div></div><div><div><div>123</div></div><div><span><span>  </span></span><span>while (lo &lt; hi) {</span></div></div><div><div><div>124</div></div><div><span><span>    </span></span><span>T mid = lo + (hi - lo + 1) / 2;</span></div></div><div><div><div>125</div></div><div><span><span>    </span></span><span>f(mid) ? lo = mid : hi = mid - 1;</span></div></div><div><div><div>126</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>127</div></div><div><span><span>  </span></span><span>return lo;</span></div></div><div><div><div>128</div></div><div><span>}</span></div></div><div><div><div>129</div></div><div>
</div></div><div><div><div>130</div></div><div><span>constexpr int mod = 1e9 + 7;</span></div></div><div><div><div>131</div></div><div><span>constexpr int inf = 0x7fffffff;</span></div></div><div><div><div>132</div></div><div><span>constexpr int N = 1.01e6;</span></div></div><div><div><div>133</div></div><div><span>constexpr int M = 2.01e3;</span></div></div><div><div><div>134</div></div><div>
</div></div><div><div><div>135</div></div><div><span>#ifdef LOCAL</span></div></div><div><div><div>136</div></div><div><span>#include &lt;C:/Users/70510/Desktop/Others/algo/debug.h&gt;</span></div></div><div><div><div>137</div></div><div><span>#else</span></div></div><div><div><div>138</div></div><div><span>#define debug(...) 42</span></div></div><div><div><div>139</div></div><div><span>#endif</span></div></div><div><div><div>140</div></div><div>
</div></div><div><div><div>141</div></div><div><span>void solve() {}</span></div></div><div><div><div>142</div></div><div>
</div></div><div><div><div>143</div></div><div><span>signed main() {</span></div></div><div><div><div>144</div></div><div><span><span>  </span></span><span>setIO();</span></div></div><div><div><div>145</div></div><div><span><span>  </span></span><span>int tt = 1;</span></div></div><div><div><div>146</div></div><div><span><span>  </span></span><span>// cin &gt;&gt; tt;</span></div></div><div><div><div>147</div></div><div><span><span>  </span></span><span>while (tt--) {</span></div></div><div><div><div>148</div></div><div><span><span>    </span></span><span>solve();</span></div></div><div><div><div>149</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>150</div></div><div><span><span>  </span></span><span>return 0;</span></div></div><div><div><div>151</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>上面就是我的代码模板，我在此要强调的是，模板长≠模板冗长。</p><p>第一份代码简洁，我也推荐大家写这种代码，但是实际写下来肯定是不如第二份舒服。</p><p>第二份代码包含了大量的宏定义等行为，但是每一段定义做了区域性的划分</p><ul>
<li>最初的#define保证了基本的可移植性 一直到自己的代码中如果有相关宏定义的话可能根本不报错</li>
<li>之后的函数是关于关闭流同步，基本短代码函数重载</li>
<li>之后的using使用template&lt;class <em>T</em>&gt;来定义部分STL容器，摒弃大量的vi，vii，pi，vpii等过长定义</li>
<li>重载部分STL的输入输出流以及int128定义，这里将重载运算符相关的代码放在了一起</li>
<li>接下来重载power函数，对基础pow进行直接加速</li>
<li>然后用template&lt;class <em>T</em>&gt;定义另一部分基本函数，取最大最小值赋值/二分/去重等函数</li>
<li>然后constexpr最常使用的四个int常量</li>
<li>引入本地debug.h</li>
<li>最后的主体代码</li>
</ul><p>整体看下来，代码虽然很长，但基本逻辑是非常清晰的，对每一部分都进行了划分。现在的大多数橙名，红名以下选手的代码模板基本都是一通乱写，不知所云，这也是灰名和红名之间的差距，代码的可读性都是大大不同。</p><p>这里我的模板主要是从以下代码吸收而来，并借鉴了其良好的代码分区原则</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>#include &lt;bits/stdc++.h&gt;</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>using namespace std;</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>/////////////////////// MACROS ////////////////////////////////////////////</span></div></div><div><div><div>6</div></div><div><span>using ll = long long;</span></div></div><div><div><div>7</div></div><div><span>using ld = long double;</span></div></div><div><div><div>8</div></div><div><span>using db = double;</span></div></div><div><div><div>9</div></div><div><span>using str = string;</span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span>using pi = pair&lt;int, int&gt;;</span></div></div><div><div><div>12</div></div><div><span>using pl = pair&lt;ll, ll&gt;;</span></div></div><div><div><div>13</div></div><div>
</div></div><div><div><div>14</div></div><div><span>using vi = vector&lt;int&gt;;</span></div></div><div><div><div>15</div></div><div><span>using vl = vector&lt;ll&gt;;</span></div></div><div><div><div>16</div></div><div><span>using vs = vector&lt;str&gt;;</span></div></div><div><div><div>17</div></div><div><span>using vc = vector&lt;char&gt;;</span></div></div><div><div><div>18</div></div><div><span>using vpi = vector&lt;pi&gt;;</span></div></div><div><div><div>19</div></div><div><span>using vpl = vector&lt;pl&gt;;</span></div></div><div><div><div>20</div></div><div>
</div></div><div><div><div>21</div></div><div><span>#define tcT template &lt;class T</span></div></div><div><div><div>22</div></div><div><span>#define tcTU tcT, class U</span></div></div><div><div><div>23</div></div><div><span>tcT &gt; using V = vector&lt;T&gt;;</span></div></div><div><div><div>24</div></div><div><span>tcT, size_t SZ &gt; using AR = array&lt;T, SZ&gt;;</span></div></div><div><div><div>25</div></div><div><span>tcTU &gt; using PR = pair&lt;T, U&gt;;</span></div></div><div><div><div>26</div></div><div><span>tcTU &gt; using umap = unordered_map&lt;T, U&gt;;</span></div></div><div><div><div>27</div></div><div><span>tcT &gt; using uset = unordered_set&lt;T&gt;;</span></div></div><div><div><div>28</div></div><div><span>tcT &gt; using mset = multiset&lt;T&gt;;</span></div></div><div><div><div>29</div></div><div>
</div></div><div><div><div>30</div></div><div><span>#define mp make_pair</span></div></div><div><div><div>31</div></div><div><span>#define f first</span></div></div><div><div><div>32</div></div><div><span>#define s second</span></div></div><div><div><div>33</div></div><div>
</div></div><div><div><div>34</div></div><div><span>#define sz(x) int((x).size())</span></div></div><div><div><div>35</div></div><div><span>#define all(x) x.begin(), x.end()</span></div></div><div><div><div>36</div></div><div><span>#define rall(x) x.rbegin(), x.rend()</span></div></div><div><div><div>37</div></div><div><span>#define rsz resize</span></div></div><div><div><div>38</div></div><div><span>#define ins insert</span></div></div><div><div><div>39</div></div><div><span>#define ft front()</span></div></div><div><div><div>40</div></div><div><span>#define bk back()</span></div></div><div><div><div>41</div></div><div><span>#define ppb pop_back()</span></div></div><div><div><div>42</div></div><div><span>#define ppf pop_front()</span></div></div><div><div><div>43</div></div><div><span>#define pb push_back</span></div></div><div><div><div>44</div></div><div><span>#define eb emplace_back</span></div></div><div><div><div>45</div></div><div><span>#define pf push_front</span></div></div><div><div><div>46</div></div><div>
</div></div><div><div><div>47</div></div><div><span>#define lb lower_bound</span></div></div><div><div><div>48</div></div><div><span>#define ub upper_bound</span></div></div><div><div><div>49</div></div><div>
</div></div><div><div><div>50</div></div><div><span>// LOOPS</span></div></div><div><div><div>51</div></div><div><span>#define FOR(i, a, b) for (int i = (a); i &lt; (b); ++i)</span></div></div><div><div><div>52</div></div><div><span>#define F0R(i, a) FOR(i, 0, a)</span></div></div><div><div><div>53</div></div><div><span>#define ROF(i, a, b) for (int i = (b) - 1; i &gt;= (a); --i)</span></div></div><div><div><div>54</div></div><div><span>#define R0F(i, a) ROF(i, 0, a)</span></div></div><div><div><div>55</div></div><div><span>#define rep(a) F0R(_, a)</span></div></div><div><div><div>56</div></div><div><span>#define each(a, x) for (auto&amp; a : x)</span></div></div><div><div><div>57</div></div><div>
</div></div><div><div><div>58</div></div><div><span>/////////////////////// IMPORANT VARS /////////////////////////////////////</span></div></div><div><div><div>59</div></div><div>
</div></div><div><div><div>60</div></div><div><span>const int MOD = 1e9 + 7;  // 998244353;</span></div></div><div><div><div>61</div></div><div><span>const int MX = 2e5 + 5;</span></div></div><div><div><div>62</div></div><div><span>const ll INFL = ll(3e18) + 10;</span></div></div><div><div><div>63</div></div><div><span>const int INF = int(1e9) + 10;</span></div></div><div><div><div>64</div></div><div><span>const ld PI = acos((ld)-1);</span></div></div><div><div><div>65</div></div><div><span>const int dx[4] = {1, 0, -1, 0}, dy[4] = {0, 1, 0, -1};</span></div></div><div><div><div>66</div></div><div><span>tcT &gt; using pqg = priority_queue&lt;T, vector&lt;T&gt;, greater&lt;T&gt;&gt;;</span></div></div><div><div><div>67</div></div><div><span>tcT &gt; using pql = priority_queue&lt;T, vector&lt;T&gt;, less&lt;T&gt;&gt;;</span></div></div><div><div><div>68</div></div><div><span>mt19937 rng(chrono::steady_clock::now().time_since_epoch().count());</span></div></div><div><div><div>69</div></div><div><span>#define nl '\n'</span></div></div><div><div><div>70</div></div><div>
</div></div><div><div><div>71</div></div><div><span>constexpr int pct(int x) { return __builtin_popcount(x); }  // # of bits set</span></div></div><div><div><div>72</div></div><div><span>constexpr int bits(int x) { return x == 0 ? 0 : 31 - __builtin_clz(x); }</span></div></div><div><div><div>73</div></div><div><span>constexpr int p2(int x) { return 1 &lt;&lt; x; }</span></div></div><div><div><div>74</div></div><div><span>constexpr int msk2(int x) { return p2(x) - 1; }</span></div></div><div><div><div>75</div></div><div>
</div></div><div><div><div>76</div></div><div><span>ll cdiv(ll a, ll b) {</span></div></div><div><div><div>77</div></div><div><span><span>  </span></span><span>return a / b + ((a ^ b) &gt; 0 &amp;&amp; a % b);</span></div></div><div><div><div>78</div></div><div><span>}  // divide a by b rounded up</span></div></div><div><div><div>79</div></div><div><span>ll fdiv(ll a, ll b) {</span></div></div><div><div><div>80</div></div><div><span><span>  </span></span><span>return a / b - ((a ^ b) &lt; 0 &amp;&amp; a % b);</span></div></div><div><div><div>81</div></div><div><span>}  // divide a by b rounded down</span></div></div><div><div><div>82</div></div><div>
</div></div><div><div><div>83</div></div><div><span>tcT &gt; bool ckmin(T&amp; a, const T&amp; b) {</span></div></div><div><div><div>84</div></div><div><span><span>  </span></span><span>return b &lt; a ? a = b, 1 : 0;</span></div></div><div><div><div>85</div></div><div><span>}  // set a = min(a,b)</span></div></div><div><div><div>86</div></div><div><span>tcT &gt; bool ckmax(T&amp; a, const T&amp; b) { return a &lt; b ? a = b, 1 : 0; }</span></div></div><div><div><div>87</div></div><div>
</div></div><div><div><div>88</div></div><div><span>tcTU &gt; T fstTrue(T lo, T hi, U f) {</span></div></div><div><div><div>89</div></div><div><span><span>  </span></span><span>hi++;</span></div></div><div><div><div>90</div></div><div><span><span>  </span></span><span>assert(lo &lt;= hi);  // assuming f is increasing</span></div></div><div><div><div>91</div></div><div><span><span>  </span></span><span>while (lo &lt; hi) {  // find first index such that f is true</span></div></div><div><div><div>92</div></div><div><span><span>    </span></span><span>T mid = lo + (hi - lo) / 2;</span></div></div><div><div><div>93</div></div><div><span><span>    </span></span><span>f(mid) ? hi = mid : lo = mid + 1;</span></div></div><div><div><div>94</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>95</div></div><div><span><span>  </span></span><span>return lo;</span></div></div><div><div><div>96</div></div><div><span>}</span></div></div><div><div><div>97</div></div><div><span>tcTU &gt; T lstTrue(T lo, T hi, U f) {</span></div></div><div><div><div>98</div></div><div><span><span>  </span></span><span>lo--;</span></div></div><div><div><div>99</div></div><div><span><span>  </span></span><span>assert(lo &lt;= hi);  // assuming f is decreasing</span></div></div><div><div><div>100</div></div><div><span><span>  </span></span><span>while (lo &lt; hi) {  // find last index such that f is true</span></div></div><div><div><div>101</div></div><div><span><span>    </span></span><span>T mid = lo + (hi - lo + 1) / 2;</span></div></div><div><div><div>102</div></div><div><span><span>    </span></span><span>f(mid) ? lo = mid : hi = mid - 1;</span></div></div><div><div><div>103</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>104</div></div><div><span><span>  </span></span><span>return lo;</span></div></div><div><div><div>105</div></div><div><span>}</span></div></div><div><div><div>106</div></div><div><span>tcT &gt; void remDup(vector&lt;T&gt;&amp; v) {  // sort and remove duplicates</span></div></div><div><div><div>107</div></div><div><span><span>  </span></span><span>sort(all(v));</span></div></div><div><div><div>108</div></div><div><span><span>  </span></span><span>v.erase(unique(all(v)), end(v));</span></div></div><div><div><div>109</div></div><div><span>}</span></div></div><div><div><div>110</div></div><div><span>tcTU &gt; void erase(T&amp; t, const U&amp; u) {  // don't erase</span></div></div><div><div><div>111</div></div><div><span><span>  </span></span><span>auto it = t.find(u);</span></div></div><div><div><div>112</div></div><div><span><span>  </span></span><span>assert(it != end(t));</span></div></div><div><div><div>113</div></div><div><span><span>  </span></span><span>t.erase(it);</span></div></div><div><div><div>114</div></div><div><span>}  // element that doesn't exist from (multi)set</span></div></div><div><div><div>115</div></div><div>
</div></div><div><div><div>116</div></div><div><span>// #include &lt;ext/pb_ds/assoc_container.hpp&gt;</span></div></div><div><div><div>117</div></div><div><span>// using namespace __gnu_pbds;</span></div></div><div><div><div>118</div></div><div>
</div></div><div><div><div>119</div></div><div><span>// tcT&gt; using iset = tree&lt;T, null_type, less&lt;T&gt;, rb_tree_tag,</span></div></div><div><div><div>120</div></div><div><span>// tree_order_statistics_node_update&gt;; #define ook order_of_key #define fbo</span></div></div><div><div><div>121</div></div><div><span>// find_by_order</span></div></div><div><div><div>122</div></div><div>
</div></div><div><div><div>123</div></div><div><span>// struct chash {</span></div></div><div><div><div>124</div></div><div><span>//   const uint64_t C = ll(2e18*PI)+71;</span></div></div><div><div><div>125</div></div><div><span>//   const int RANDOM = rng();</span></div></div><div><div><div>126</div></div><div><span>//   ll operator()(ll x) const {</span></div></div><div><div><div>127</div></div><div><span>//     return __builtin_bswap64((x^RANDOM)*C); }</span></div></div><div><div><div>128</div></div><div><span>// };</span></div></div><div><div><div>129</div></div><div>
</div></div><div><div><div>130</div></div><div><span>// struct splitmix64_hash {</span></div></div><div><div><div>131</div></div><div><span>//   static uint64_t splitmix64(uint64_t x) {</span></div></div><div><div><div>132</div></div><div><span>//     // http://xorshift.di.unimi.it/splitmix64.c</span></div></div><div><div><div>133</div></div><div><span>//     x += 0x9e3779b97f4a7c15;</span></div></div><div><div><div>134</div></div><div><span>//     x = (x ^ (x &gt;&gt; 30)) * 0xbf58476d1ce4e5b9;</span></div></div><div><div><div>135</div></div><div><span>//     x = (x ^ (x &gt;&gt; 27)) * 0x94d049bb133111eb;</span></div></div><div><div><div>136</div></div><div><span>//     return x ^ (x &gt;&gt; 31);</span></div></div><div><div><div>137</div></div><div><span>//   }</span></div></div><div><div><div>138</div></div><div>
</div></div><div><div><div>139</div></div><div><span>//   size_t operator()(uint64_t x) const {</span></div></div><div><div><div>140</div></div><div><span>//     static const uint64_t FIXED_RANDOM =</span></div></div><div><div><div>141</div></div><div><span>// std::chrono::steady_clock::now().time_since_epoch().count();</span></div></div><div><div><div>142</div></div><div><span>// return splitmix64(x + FIXED_RANDOM);</span></div></div><div><div><div>143</div></div><div><span>//   }</span></div></div><div><div><div>144</div></div><div><span>// };</span></div></div><div><div><div>145</div></div><div>
</div></div><div><div><div>146</div></div><div><span>// template&lt;class K,class V&gt; using um = unordered_map&lt;K,V,chash&gt;;</span></div></div><div><div><div>147</div></div><div><span>// template&lt;class K,class V&gt; using ht = gp_hash_table&lt;K,V,chash&gt;;</span></div></div><div><div><div>148</div></div><div><span>// template&lt;class K,class V&gt; V get(ht&lt;K,V&gt;&amp; u, K x) {</span></div></div><div><div><div>149</div></div><div><span>//   auto it = u.find(x); return it == end(u) ? 0 : it-&gt;s; }</span></div></div><div><div><div>150</div></div><div>
</div></div><div><div><div>151</div></div><div><span>/////////////////////// OUPUT /////////////////////////////////////////////</span></div></div><div><div><div>152</div></div><div><span>#define ts to_string</span></div></div><div><div><div>153</div></div><div><span>str ts(char c) { return str(1, c); }</span></div></div><div><div><div>154</div></div><div><span>str ts(const char* s) { return (str)s; }</span></div></div><div><div><div>155</div></div><div><span>str ts(str s) { return s; }</span></div></div><div><div><div>156</div></div><div><span>str ts(bool b) {</span></div></div><div><div><div>157</div></div><div><span>#ifdef LOCAL</span></div></div><div><div><div>158</div></div><div><span><span>  </span></span><span>return b ? "true" : "false";</span></div></div><div><div><div>159</div></div><div><span>#else</span></div></div><div><div><div>160</div></div><div><span><span>  </span></span><span>return ts((int)b);</span></div></div><div><div><div>161</div></div><div><span>#endif</span></div></div><div><div><div>162</div></div><div><span>}</span></div></div><div><div><div>163</div></div><div><span>tcTU &gt; str ts(pair&lt;T, U&gt; p) {</span></div></div><div><div><div>164</div></div><div><span>#ifdef LOCAL</span></div></div><div><div><div>165</div></div><div><span><span>  </span></span><span>return "(" + ts(p.f) + ", " + ts(p.s) + ")";</span></div></div><div><div><div>166</div></div><div><span>#else</span></div></div><div><div><div>167</div></div><div><span><span>  </span></span><span>return ts(p.f) + " " + ts(p.s);</span></div></div><div><div><div>168</div></div><div><span>#endif</span></div></div><div><div><div>169</div></div><div><span>}</span></div></div><div><div><div>170</div></div><div>
</div></div><div><div><div>171</div></div><div><span>tcTU &gt; str ts(V&lt;pair&lt;T, U&gt;&gt; v) {</span></div></div><div><div><div>172</div></div><div><span>#ifdef LOCAL</span></div></div><div><div><div>173</div></div><div><span><span>  </span></span><span>bool fst = 1;</span></div></div><div><div><div>174</div></div><div><span><span>  </span></span><span>str res = "{";</span></div></div><div><div><div>175</div></div><div><span><span>  </span></span><span>for (const auto&amp; x : v) {</span></div></div><div><div><div>176</div></div><div><span><span>    </span></span><span>if (!fst) res += ", ";</span></div></div><div><div><div>177</div></div><div><span><span>    </span></span><span>fst = 0;</span></div></div><div><div><div>178</div></div><div><span><span>    </span></span><span>res += ts(x);</span></div></div><div><div><div>179</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>180</div></div><div><span><span>  </span></span><span>res += "}";</span></div></div><div><div><div>181</div></div><div><span><span>  </span></span><span>return res;</span></div></div><div><div><div>182</div></div><div><span>#else</span></div></div><div><div><div>183</div></div><div><span><span>  </span></span><span>bool fst = 1;</span></div></div><div><div><div>184</div></div><div><span><span>  </span></span><span>str res = "";</span></div></div><div><div><div>185</div></div><div><span><span>  </span></span><span>for (const auto&amp; x : v) {</span></div></div><div><div><div>186</div></div><div><span><span>    </span></span><span>if (!fst) res += " ";</span></div></div><div><div><div>187</div></div><div><span><span>    </span></span><span>fst = 0;</span></div></div><div><div><div>188</div></div><div><span><span>    </span></span><span>res += ts(x);</span></div></div><div><div><div>189</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>190</div></div><div><span><span>  </span></span><span>return res;</span></div></div><div><div><div>191</div></div><div><span>#endif</span></div></div><div><div><div>192</div></div><div><span>}</span></div></div><div><div><div>193</div></div><div>
</div></div><div><div><div>194</div></div><div><span>tcT &gt; str ts(T v) {</span></div></div><div><div><div>195</div></div><div><span>#ifdef LOCAL</span></div></div><div><div><div>196</div></div><div><span><span>  </span></span><span>bool fst = 1;</span></div></div><div><div><div>197</div></div><div><span><span>  </span></span><span>str res = "{";</span></div></div><div><div><div>198</div></div><div><span><span>  </span></span><span>for (const auto&amp; x : v) {</span></div></div><div><div><div>199</div></div><div><span><span>    </span></span><span>if (!fst) res += ", ";</span></div></div><div><div><div>200</div></div><div><span><span>    </span></span><span>fst = 0;</span></div></div><div><div><div>201</div></div><div><span><span>    </span></span><span>res += ts(x);</span></div></div><div><div><div>202</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>203</div></div><div><span><span>  </span></span><span>res += "}";</span></div></div><div><div><div>204</div></div><div><span><span>  </span></span><span>return res;</span></div></div><div><div><div>205</div></div><div><span>#else</span></div></div><div><div><div>206</div></div><div><span><span>  </span></span><span>bool fst = 1;</span></div></div><div><div><div>207</div></div><div><span><span>  </span></span><span>str res = "";</span></div></div><div><div><div>208</div></div><div><span><span>  </span></span><span>for (const auto&amp; x : v) {</span></div></div><div><div><div>209</div></div><div><span><span>    </span></span><span>if (!fst) res += " ";</span></div></div><div><div><div>210</div></div><div><span><span>    </span></span><span>fst = 0;</span></div></div><div><div><div>211</div></div><div><span><span>    </span></span><span>res += ts(x);</span></div></div><div><div><div>212</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>213</div></div><div><span><span>  </span></span><span>return res;</span></div></div><div><div><div>214</div></div><div>
</div></div><div><div><div>215</div></div><div><span>#endif</span></div></div><div><div><div>216</div></div><div><span>}</span></div></div><div><div><div>217</div></div><div>
</div></div><div><div><div>218</div></div><div><span>///////////////////////// DEBUG ///////////////////////////////////////////</span></div></div><div><div><div>219</div></div><div><span>#define tcTUU tcT, class... U</span></div></div><div><div><div>220</div></div><div><span>void DBG() { cerr &lt;&lt; "]" &lt;&lt; "\e[0m" &lt;&lt; endl; }</span></div></div><div><div><div>221</div></div><div><span>tcTUU &gt; void DBG(const T&amp; t, const U&amp;... u) {</span></div></div><div><div><div>222</div></div><div><span><span>  </span></span><span>cerr &lt;&lt; ts(t);</span></div></div><div><div><div>223</div></div><div><span><span>  </span></span><span>if (sizeof...(u)) cerr &lt;&lt; ", ";</span></div></div><div><div><div>224</div></div><div><span><span>  </span></span><span>DBG(u...);</span></div></div><div><div><div>225</div></div><div><span>}</span></div></div><div><div><div>226</div></div><div><span>#ifdef LOCAL</span></div></div><div><div><div>227</div></div><div><span>#define dbg(...)                                                     \</span></div></div><div><div><div>228</div></div><div><span><span>  </span></span><span>cerr &lt;&lt; "\e[1m" &lt;&lt; "Line(" &lt;&lt; __LINE__ &lt;&lt; ") -&gt; [" &lt;&lt; #__VA_ARGS__ \</span></div></div><div><div><div>229</div></div><div><span><span>       </span></span><span>&lt;&lt; "]: [",                                                    \</span></div></div><div><div><div>230</div></div><div><span><span>      </span></span><span>DBG(__VA_ARGS__);</span></div></div><div><div><div>231</div></div><div><span>#define asrt(...)                                                   \</span></div></div><div><div><div>232</div></div><div><span><span>  </span></span><span>if (!(__VA_ARGS__))                                               \</span></div></div><div><div><div>233</div></div><div><span><span>    </span></span><span>cerr &lt;&lt; "Line(" &lt;&lt; __LINE__ &lt;&lt; ") -&gt; function(" &lt;&lt; __FUNCTION__ \</span></div></div><div><div><div>234</div></div><div><span><span>         </span></span><span>&lt;&lt; ") -&gt; CHK FAILED: (" &lt;&lt; #__VA_ARGS__ &lt;&lt; ")" &lt;&lt; "\n",    \</span></div></div><div><div><div>235</div></div><div><span><span>        </span></span><span>exit(0);</span></div></div><div><div><div>236</div></div><div><span>#else</span></div></div><div><div><div>237</div></div><div><span>#define dbg(...) 0</span></div></div><div><div><div>238</div></div><div><span>#define asrt(...) 0</span></div></div><div><div><div>239</div></div><div><span>#endif</span></div></div><div><div><div>240</div></div><div>
</div></div><div><div><div>241</div></div><div><span>///////////////////////// FILE I/O ////////////////////////////////////////</span></div></div><div><div><div>242</div></div><div><span>void unsyncIO() { cin.tie(0)-&gt;sync_with_stdio(0); }</span></div></div><div><div><div>243</div></div><div><span>void setPrec() { cout &lt;&lt; fixed &lt;&lt; setprecision(15); }</span></div></div><div><div><div>244</div></div><div><span>void setIn(str s) { freopen(s.c_str(), "r", stdin); }</span></div></div><div><div><div>245</div></div><div><span>void setOut(str s) { freopen(s.c_str(), "w", stdout); }</span></div></div><div><div><div>246</div></div><div><span>void setIO(str s = "") {</span></div></div><div><div><div>247</div></div><div><span><span>  </span></span><span>unsyncIO();</span></div></div><div><div><div>248</div></div><div><span><span>  </span></span><span>setPrec();</span></div></div><div><div><div>249</div></div><div><span>#ifndef LOCAL</span></div></div><div><div><div>250</div></div><div><span><span>  </span></span><span>if (sz(s)) setIn(s + ".in"), setOut(s + ".out");  // for USACO</span></div></div><div><div><div>251</div></div><div><span>#endif</span></div></div><div><div><div>252</div></div><div><span>}</span></div></div><div><div><div>253</div></div><div>
</div></div><div><div><div>254</div></div><div><span>///////////////////////// TEMPLATE ABOVE //////////////////////////////////</span></div></div><div><div><div>255</div></div><div>
</div></div><div><div><div>256</div></div><div><span>// REMEMBER</span></div></div><div><div><div>257</div></div><div><span>// - Don't Focus On Only One Approach</span></div></div><div><div><div>258</div></div><div><span>// - Read And Understand Problem Fully</span></div></div><div><div><div>259</div></div><div><span>// - Think Of Edges Cases</span></div></div><div><div><div>260</div></div><div><span>// - Implement Carefully</span></div></div><div><div><div>261</div></div><div><span>// - Always Check For Overflows</span></div></div><div><div><div>262</div></div><div><span>// - Reset Global Variables</span></div></div><div><div><div>263</div></div><div><span>// - Look At The Bigger Picture</span></div></div><div><div><div>264</div></div><div><span>// - Don't Get Discouraged, You Can Pull It Back</span></div></div><div><div><div>265</div></div><div>
</div></div><div><div><div>266</div></div><div><span>void solve() {}</span></div></div><div><div><div>267</div></div><div>
</div></div><div><div><div>268</div></div><div><span>int main() {</span></div></div><div><div><div>269</div></div><div><span><span>  </span></span><span>setIO();</span></div></div><div><div><div>270</div></div><div>
</div></div><div><div><div>271</div></div><div><span><span>  </span></span><span>int TT = 1;</span></div></div><div><div><div>272</div></div><div><span><span>  </span></span><span>// cin &gt;&gt; TT;</span></div></div><div><div><div>273</div></div><div>
</div></div><div><div><div>274</div></div><div><span><span>  </span></span><span>rep(TT) solve();</span></div></div><div><div><div>275</div></div><div>
</div></div><div><div><div>276</div></div><div><span><span>  </span></span><span>exit(0 - 0);</span></div></div><div><div><div>277</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>这份代码由橙名所写，我个人认为，如果可读性是比较良好，并且非常值得借鉴的代码。</p><p>这里随机粘贴一位灰名选手的代码</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>//JAI SHREE RAM</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>#include&lt;bits/stdc++.h&gt;</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>#include&lt;ext/pb_ds/assoc_container.hpp&gt;</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>#include&lt;ext/pb_ds/tree_policy.hpp&gt;</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>using namespace std;</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>using namespace std::chrono;</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>#define ll long long</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>using namespace __gnu_pbds;</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>typedef tree&lt;ll int,null_type,less_equal&lt;ll int&gt;,rb_tree_tag,tree_order_statistics_node_update&gt; pbds;// find_by_order, order_of_key</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>//for different comparator use greater instead of less</span></div></div><div><div><div>11</div></div><div><span><span>    </span></span><span>void myerase(pbds &amp;t, ll int v){</span></div></div><div><div><div>12</div></div><div><span><span>        </span></span><span>ll int rank = t.order_of_key(v);//Number of elements that are less than v in t</span></div></div><div><div><div>13</div></div><div><span><span>        </span></span><span>pbds::iterator it = t.find_by_order(rank); //Iterator that points to the (rank+1)th element in t</span></div></div><div><div><div>14</div></div><div><span><span>        </span></span><span>t.erase(it);</span></div></div><div><div><div>15</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>16</div></div><div><span><span>    </span></span><span>#define print_vector(v); for(int i=0;i&lt;v.size();i++){cout&lt;&lt;v[i]&lt;&lt;" ";}cout&lt;&lt;endl;</span></div></div><div><div><div>17</div></div><div><span><span>    </span></span><span>ll int MOD1=1000000007;</span></div></div><div><div><div>18</div></div><div><span><span>    </span></span><span>ll int power_x_y(ll int x, ll int y)//logy time complexity//binary exponentiation</span></div></div><div><div><div>19</div></div><div><span><span>    </span></span><span>{</span></div></div><div><div><div>20</div></div><div><span><span>        </span></span><span>ll int temp;</span></div></div><div><div><div>21</div></div><div><span><span>        </span></span><span>if (y == 0)</span></div></div><div><div><div>22</div></div><div><span><span>            </span></span><span>return 1;</span></div></div><div><div><div>23</div></div><div><span><span>        </span></span><span>temp = power_x_y(x, y / 2);</span></div></div><div><div><div>24</div></div><div><span><span>        </span></span><span>if (y % 2 == 0)</span></div></div><div><div><div>25</div></div><div><span><span>            </span></span><span>return (((temp)) * ((temp)));</span></div></div><div><div><div>26</div></div><div><span><span>        </span></span><span>else</span></div></div><div><div><div>27</div></div><div><span><span>            </span></span><span>return (((x)) *((temp)) * ((temp)));</span></div></div><div><div><div>28</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>29</div></div><div><span><span>    </span></span><span>ll int power_x_y_p(ll int x, ll int y,ll int p)//logy time complexity//binary exponentiation</span></div></div><div><div><div>30</div></div><div><span><span>    </span></span><span>{</span></div></div><div><div><div>31</div></div><div><span><span>        </span></span><span>ll int temp;</span></div></div><div><div><div>32</div></div><div><span><span>        </span></span><span>if (y == 0)</span></div></div><div><div><div>33</div></div><div><span><span>            </span></span><span>return 1;</span></div></div><div><div><div>34</div></div><div><span><span>        </span></span><span>temp = power_x_y_p(x, y / 2,p)%p;</span></div></div><div><div><div>35</div></div><div><span><span>        </span></span><span>if (y % 2 == 0)</span></div></div><div><div><div>36</div></div><div><span><span>            </span></span><span>return (((temp%p)) * ((temp%p)))%p;</span></div></div><div><div><div>37</div></div><div><span><span>        </span></span><span>else</span></div></div><div><div><div>38</div></div><div><span><span>            </span></span><span>return ((((x%p) *(temp%p))%p) * (temp%p))%p;</span></div></div><div><div><div>39</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>40</div></div><div><span><span>    </span></span><span>//for 8 it will return 1000</span></div></div><div><div><div>41</div></div><div><span><span>    </span></span><span>vector&lt;ll int&gt; binary_representation(ll int n){</span></div></div><div><div><div>42</div></div><div><span><span>        </span></span><span>vector&lt;ll int &gt;v;</span></div></div><div><div><div>43</div></div><div><span><span>        </span></span><span>while(n&gt;0){</span></div></div><div><div><div>44</div></div><div><span><span>            </span></span><span>v.push_back(n%2);</span></div></div><div><div><div>45</div></div><div><span><span>            </span></span><span>n/=2;</span></div></div><div><div><div>46</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>47</div></div><div><span><span>        </span></span><span>reverse(v.begin(),v.end());</span></div></div><div><div><div>48</div></div><div><span><span>        </span></span><span>return v;</span></div></div><div><div><div>49</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>50</div></div><div><span><span>    </span></span><span>void divisors(ll int n,vector&lt;ll int&gt;&amp;v){</span></div></div><div><div><div>51</div></div><div><span><span>        </span></span><span>for(ll int i=1;i&lt;=sqrt(n);i++){</span></div></div><div><div><div>52</div></div><div><span><span>            </span></span><span>if(n%i==0){</span></div></div><div><div><div>53</div></div><div><span><span>                </span></span><span>v.push_back(i);</span></div></div><div><div><div>54</div></div><div><span><span>                </span></span><span>if(n/i!=n and n/i!=i){</span></div></div><div><div><div>55</div></div><div><span><span>                    </span></span><span>v.push_back(n/i);</span></div></div><div><div><div>56</div></div><div><span><span>                </span></span><span>}</span></div></div><div><div><div>57</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>58</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>59</div></div><div><span><span>        </span></span><span>if(n!=1)v.push_back(n);</span></div></div><div><div><div>60</div></div><div><span><span>        </span></span><span>sort(v.begin(),v.end());</span></div></div><div><div><div>61</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>62</div></div><div><span><span>    </span></span><span>void generate_all_k_length_subsequences_of_s(ll int index,string s,vector&lt;string&gt;&amp;storage,ll int k,string curr){</span></div></div><div><div><div>63</div></div><div><span><span>        </span></span><span>if(curr.size()==k){storage.push_back(curr);return;}</span></div></div><div><div><div>64</div></div><div><span><span>        </span></span><span>if(index==s.size()) return ;</span></div></div><div><div><div>65</div></div><div><span><span>        </span></span><span>//not pick</span></div></div><div><div><div>66</div></div><div><span><span>        </span></span><span>generate_all_k_length_subsequences_of_s(index+1,s,storage,k,curr);</span></div></div><div><div><div>67</div></div><div><span><span>        </span></span><span>//pick</span></div></div><div><div><div>68</div></div><div><span><span>        </span></span><span>curr.push_back(s[index]);</span></div></div><div><div><div>69</div></div><div><span><span>        </span></span><span>generate_all_k_length_subsequences_of_s(index+1,s,storage,k,curr);</span></div></div><div><div><div>70</div></div><div><span><span>        </span></span><span>curr.pop_back();</span></div></div><div><div><div>71</div></div><div><span><span>        </span></span><span>return;</span></div></div><div><div><div>72</div></div><div>
</div></div><div><div><div>73</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>74</div></div><div><span><span>    </span></span><span>void generate_all_subsets_of_s(ll int index,string s,vector&lt;string&gt;&amp;storage,string curr){</span></div></div><div><div><div>75</div></div><div><span><span>        </span></span><span>if(index==s.size()){storage.push_back(curr);return;}</span></div></div><div><div><div>76</div></div><div><span><span>        </span></span><span>//not pick</span></div></div><div><div><div>77</div></div><div><span><span>        </span></span><span>generate_all_subsets_of_s(index+1,s,storage,curr);</span></div></div><div><div><div>78</div></div><div><span><span>        </span></span><span>//pick</span></div></div><div><div><div>79</div></div><div><span><span>        </span></span><span>curr.push_back(s[index]);</span></div></div><div><div><div>80</div></div><div><span><span>        </span></span><span>generate_all_subsets_of_s(index+1,s,storage,curr);</span></div></div><div><div><div>81</div></div><div><span><span>        </span></span><span>curr.pop_back();</span></div></div><div><div><div>82</div></div><div><span><span>        </span></span><span>return;</span></div></div><div><div><div>83</div></div><div>
</div></div><div><div><div>84</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>85</div></div><div>
</div></div><div><div><div>86</div></div><div>
</div></div><div><div><div>87</div></div><div><span><span>    </span></span><span>// Returns n^(-1) mod p</span></div></div><div><div><div>88</div></div><div><span><span>    </span></span><span>unsigned long long modInverse(unsigned long long n,  ll int p)</span></div></div><div><div><div>89</div></div><div><span><span>    </span></span><span>{</span></div></div><div><div><div>90</div></div><div><span><span>        </span></span><span>return power_x_y_p(n, p - 2, p);</span></div></div><div><div><div>91</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>92</div></div><div><span><span>    </span></span><span>unsigned long long nCrModPFermat( long long n,ll int r, ll int p)   //time taken by all the functions involved=O(n + logp)</span></div></div><div><div><div>93</div></div><div><span><span>    </span></span><span>{</span></div></div><div><div><div>94</div></div><div><span><span>        </span></span><span>if (n &lt; r)</span></div></div><div><div><div>95</div></div><div><span><span>            </span></span><span>return 0;</span></div></div><div><div><div>96</div></div><div><span><span>        </span></span><span>if (r == 0)</span></div></div><div><div><div>97</div></div><div><span><span>            </span></span><span>return 1;</span></div></div><div><div><div>98</div></div><div><span><span>        </span></span><span>//fest calculation of the three fact values in one go</span></div></div><div><div><div>99</div></div><div><span><span>        </span></span><span>unsigned long long fac[n + 1];</span></div></div><div><div><div>100</div></div><div><span><span>        </span></span><span>fac[0] = 1;</span></div></div><div><div><div>101</div></div><div><span><span>        </span></span><span>for (int i = 1; i &lt;= n; i++)</span></div></div><div><div><div>102</div></div><div><span><span>            </span></span><span>fac[i] = (fac[i - 1] * i) % p;</span></div></div><div><div><div>103</div></div><div><span><span>        </span></span><span>return (((fac[n] * modInverse(fac[r], p)) % p)* (modInverse(fac[n - r], p) % p))% p;</span></div></div><div><div><div>104</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>105</div></div><div>
</div></div><div><div><div>106</div></div><div><span>ll int getHighestSetBit(ll int n) {</span></div></div><div><div><div>107</div></div><div><span><span>    </span></span><span>if (n == 0) return -1;</span></div></div><div><div><div>108</div></div><div><span><span>    </span></span><span>int position = 0;</span></div></div><div><div><div>109</div></div><div>
</div></div><div><div><div>110</div></div><div><span><span>    </span></span><span>while (n &gt; 1) { // Continue until n reduces to 1</span></div></div><div><div><div>111</div></div><div><span><span>        </span></span><span>n &gt;&gt;= 1;    // Right shift the number by 1</span></div></div><div><div><div>112</div></div><div><span><span>        </span></span><span>position++; // Increment the position counter</span></div></div><div><div><div>113</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>114</div></div><div><span><span>    </span></span><span>return position; // Return the position of the highest set bit</span></div></div><div><div><div>115</div></div><div><span>}</span></div></div><div><div><div>116</div></div><div><span><span>    </span></span><span>bool isPrime(ll int n)</span></div></div><div><div><div>117</div></div><div><span><span>    </span></span><span>{</span></div></div><div><div><div>118</div></div><div><span><span>        </span></span><span>// Corner cases</span></div></div><div><div><div>119</div></div><div><span><span>        </span></span><span>if (n &lt;= 1)</span></div></div><div><div><div>120</div></div><div><span><span>            </span></span><span>return false;</span></div></div><div><div><div>121</div></div><div><span><span>        </span></span><span>if (n &lt;= 3)</span></div></div><div><div><div>122</div></div><div><span><span>            </span></span><span>return true;</span></div></div><div><div><div>123</div></div><div>
</div></div><div><div><div>124</div></div><div><span><span>        </span></span><span>// This is checked so that we can skip</span></div></div><div><div><div>125</div></div><div><span><span>        </span></span><span>// middle five numbers in below loop</span></div></div><div><div><div>126</div></div><div><span><span>        </span></span><span>if (n % 2 == 0 || n % 3 == 0)</span></div></div><div><div><div>127</div></div><div><span><span>            </span></span><span>return false;</span></div></div><div><div><div>128</div></div><div>
</div></div><div><div><div>129</div></div><div><span><span>        </span></span><span>for (int i = 5; i * i &lt;= n; i = i + 6)</span></div></div><div><div><div>130</div></div><div><span><span>            </span></span><span>if (n % i == 0 || n % (i + 2) == 0)</span></div></div><div><div><div>131</div></div><div><span><span>                </span></span><span>return false;</span></div></div><div><div><div>132</div></div><div>
</div></div><div><div><div>133</div></div><div><span><span>        </span></span><span>return true;</span></div></div><div><div><div>134</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>135</div></div><div><span><span>    </span></span><span>vector&lt;ll int&gt; Sieve_to_store_primes(ll int n)</span></div></div><div><div><div>136</div></div><div><span><span>    </span></span><span>{</span></div></div><div><div><div>137</div></div><div><span><span>        </span></span><span>bool prime[n + 1];memset(prime,1, sizeof(prime));</span></div></div><div><div><div>138</div></div><div><span><span>        </span></span><span>for (ll int p = 2; p&lt;= n; p++) {</span></div></div><div><div><div>139</div></div><div><span><span>            </span></span><span>if (prime[p] ==1) {</span></div></div><div><div><div>140</div></div><div><span><span>                </span></span><span>for (ll int i=p*2; i &lt;= n; i += p)</span></div></div><div><div><div>141</div></div><div><span><span>                    </span></span><span>prime[i] =0;</span></div></div><div><div><div>142</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>143</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>144</div></div><div><span><span>        </span></span><span>vector&lt;ll int&gt;v;</span></div></div><div><div><div>145</div></div><div><span><span>        </span></span><span>for (int p = 2; p &lt;= n; p++){</span></div></div><div><div><div>146</div></div><div><span><span>            </span></span><span>if (prime[p]){</span></div></div><div><div><div>147</div></div><div><span><span>                </span></span><span>v.push_back(p);</span></div></div><div><div><div>148</div></div><div><span><span>              </span></span><span>}</span></div></div><div><div><div>149</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>150</div></div><div><span><span>        </span></span><span>return v;</span></div></div><div><div><div>151</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>152</div></div><div><span><span>    </span></span><span>vector&lt;ll int&gt; Sieve_highest_lowest_primes(ll int n)</span></div></div><div><div><div>153</div></div><div><span><span>    </span></span><span>{</span></div></div><div><div><div>154</div></div><div><span><span>        </span></span><span>bool prime[n + 1];memset(prime,1, sizeof(prime));</span></div></div><div><div><div>155</div></div><div><span><span>        </span></span><span>vector&lt;ll int&gt;hp(n+1);</span></div></div><div><div><div>156</div></div><div><span><span>        </span></span><span>vector&lt;ll int&gt;lp(n+1,0);</span></div></div><div><div><div>157</div></div><div><span><span>        </span></span><span>for (ll int p = 2; p &lt;= n; p++) {</span></div></div><div><div><div>158</div></div><div><span><span>            </span></span><span>if (prime[p] ==1) {</span></div></div><div><div><div>159</div></div><div><span><span>                </span></span><span>lp[p]=hp[p]=p;//if the number is a prime number then the lowest and the highest prime both are p</span></div></div><div><div><div>160</div></div><div><span><span>                </span></span><span>for (ll int i = p * 2; i &lt;= n; i += p){</span></div></div><div><div><div>161</div></div><div><span><span>                    </span></span><span>prime[i] = false;</span></div></div><div><div><div>162</div></div><div><span><span>                    </span></span><span>hp[i]=p;</span></div></div><div><div><div>163</div></div><div><span><span>                    </span></span><span>if(lp[i]==0){</span></div></div><div><div><div>164</div></div><div><span><span>                        </span></span><span>lp[i]=p;</span></div></div><div><div><div>165</div></div><div><span><span>                    </span></span><span>}</span></div></div><div><div><div>166</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>167</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>168</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>169</div></div><div><span><span>      </span></span><span>return lp;//return hp;</span></div></div><div><div><div>170</div></div><div><span><span>      </span></span><span>//NOTE THAT THE HIGHEST PRIME AND THE LOWEST PRIME OF 0 AND 1 ARE MARKED AS 0</span></div></div><div><div><div>171</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>172</div></div><div><span><span>    </span></span><span>vector&lt;vector&lt;ll int&gt;&gt; Sieve_all_divisors(ll int n){</span></div></div><div><div><div>173</div></div><div><span><span>        </span></span><span>vector&lt;vector&lt;ll int&gt;&gt;v(n+1);</span></div></div><div><div><div>174</div></div><div><span><span>        </span></span><span>for(ll int i=1;i&lt;=n;i++){</span></div></div><div><div><div>175</div></div><div><span><span>            </span></span><span>for(ll int j=i;j&lt;=n;j+=i){</span></div></div><div><div><div>176</div></div><div><span><span>                </span></span><span>v[j].push_back(i);</span></div></div><div><div><div>177</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>178</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>179</div></div><div><span><span>        </span></span><span>return v;</span></div></div><div><div><div>180</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>181</div></div><div><span><span>    </span></span><span>vector&lt;vector&lt;ll int&gt;&gt; Sieve_all_prime_factors(ll int n){</span></div></div><div><div><div>182</div></div><div><span><span>        </span></span><span>vector&lt;ll int&gt;lp=Sieve_highest_lowest_primes(n);</span></div></div><div><div><div>183</div></div><div><span><span>        </span></span><span>vector&lt;vector&lt;ll int&gt;&gt;ans(n+1);//note that the prime factors of 0 and 1 are none</span></div></div><div><div><div>184</div></div><div><span><span>        </span></span><span>for(ll int i=2;i&lt;=n;i++){</span></div></div><div><div><div>185</div></div><div><span><span>            </span></span><span>ll int temp=i;</span></div></div><div><div><div>186</div></div><div><span><span>            </span></span><span>while(temp&gt;1){</span></div></div><div><div><div>187</div></div><div><span><span>                </span></span><span>ll int div=lp[temp];</span></div></div><div><div><div>188</div></div><div><span><span>                </span></span><span>ans[i].push_back(div);</span></div></div><div><div><div>189</div></div><div><span><span>                </span></span><span>while(temp%div==0){</span></div></div><div><div><div>190</div></div><div><span><span>                    </span></span><span>temp/=div;</span></div></div><div><div><div>191</div></div><div><span><span>                </span></span><span>}</span></div></div><div><div><div>192</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>193</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>194</div></div><div><span><span>        </span></span><span>return ans;</span></div></div><div><div><div>195</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>196</div></div><div>
</div></div><div><div><div>197</div></div><div><span><span>    </span></span><span>ll int query_interactive(ll int x,ll int y){</span></div></div><div><div><div>198</div></div><div><span><span>        </span></span><span>cout&lt;&lt;"?"&lt;&lt;" "&lt;&lt;x&lt;&lt;" "&lt;&lt;y&lt;&lt;endl;</span></div></div><div><div><div>199</div></div><div><span><span>        </span></span><span>ll int ans;</span></div></div><div><div><div>200</div></div><div><span><span>        </span></span><span>cin&gt;&gt;ans;</span></div></div><div><div><div>201</div></div><div><span><span>        </span></span><span>return ans;</span></div></div><div><div><div>202</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>203</div></div><div><span><span>    </span></span><span>void print_interactive(ll int x,ll int y){</span></div></div><div><div><div>204</div></div><div><span><span>        </span></span><span>cout&lt;&lt;"!"&lt;&lt;" "&lt;&lt;x&lt;&lt;" "&lt;&lt;y&lt;&lt;endl;</span></div></div><div><div><div>205</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>206</div></div><div><span><span>    </span></span><span>//__builtin_popcount(n) to count the number of set bits in a number//we can check if a number is a power of 2 using __builtin_popcount()</span></div></div><div><div><div>207</div></div><div><span><span>    </span></span><span>//__builtin_clz will count the number of leading zeroes</span></div></div><div><div><div>208</div></div><div><span><span>    </span></span><span>//reverse(v.begin(),v.end())//modifies the vector inplace</span></div></div><div><div><div>209</div></div><div><span><span>    </span></span><span>//sort(v.rbegin(), v.rend());//to sort a vector in reverse order</span></div></div><div><div><div>210</div></div><div><span><span>    </span></span><span>//sort(arr, arr + n, greater&lt;ll int&gt;());//sort an array in reverse order</span></div></div><div><div><div>211</div></div><div><span><span>    </span></span><span>//fib(n)*fib(n+1)=sum of square of all fibs before n</span></div></div><div><div><div>212</div></div><div><span><span>    </span></span><span>//there are approx n^1/3 divisors of a number</span></div></div><div><div><div>213</div></div><div><span><span>    </span></span><span>//there are approx loglogn number of distinct prime factors of a number</span></div></div><div><div><div>214</div></div><div><span><span>    </span></span><span>//there are approx n/logn different primes before a number n</span></div></div><div><div><div>215</div></div><div><span><span>    </span></span><span>//gcd knowledge:::</span></div></div><div><div><div>216</div></div><div><span><span>    </span></span><span>//gcd(a1,a2,a3,a4,a5..an)=gcd(a1,|a1-a2|,|a1-a3|,|a1-a4|,|a1-a5|....,|a1-an|)</span></div></div><div><div><div>217</div></div><div><span><span>    </span></span><span>//gcd(a,b)=gcd(a,b%a)</span></div></div><div><div><div>218</div></div><div>
</div></div><div><div><div>219</div></div><div><span><span>    </span></span><span>//REMEMBER</span></div></div><div><div><div>220</div></div><div><span><span>    </span></span><span>//all primes till a number A : A*loglog(A)</span></div></div><div><div><div>221</div></div><div><span><span>    </span></span><span>//all highest primes till a number A: A*loglog(A)//all lowest primes till a number A: A*loglog(A)</span></div></div><div><div><div>222</div></div><div><span><span>    </span></span><span>//all the divisors of all numbers till a max number A: A*log(A)</span></div></div><div><div><div>223</div></div><div><span><span>    </span></span><span>//all the prime factors of N queries with max number A: A*loglog(A) + N*log(A)</span></div></div><div><div><div>224</div></div><div>
</div></div><div><div><div>225</div></div><div>
</div></div><div><div><div>226</div></div><div><span><span>    </span></span><span>int main(){</span></div></div><div><div><div>227</div></div><div><span><span>        </span></span><span>ios_base::sync_with_stdio(false);</span></div></div><div><div><div>228</div></div><div><span><span>        </span></span><span>cin.tie(NULL);ll int t;t=1;</span></div></div><div><div><div>229</div></div><div><span><span>        </span></span><span>ll int p=998244353;</span></div></div><div><div><div>230</div></div><div><span><span>        </span></span><span>for(int o=0;o&lt;t;o++){</span></div></div><div><div><div>231</div></div><div><span><span>            </span></span><span>ll int n;cin&gt;&gt;n;</span></div></div><div><div><div>232</div></div><div><span><span>            </span></span><span>ll int a[n+1]={0};</span></div></div><div><div><div>233</div></div><div><span><span>            </span></span><span>for(int i=1;i&lt;=n;i++){</span></div></div><div><div><div>234</div></div><div><span><span>                </span></span><span>cin&gt;&gt;a[i];</span></div></div><div><div><div>235</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>236</div></div><div><span><span>            </span></span><span>vector&lt;vector&lt;ll int&gt;&gt;dist=Sieve_all_prime_factors(*max_element(a+1,a+n+1));</span></div></div><div><div><div>237</div></div><div><span><span>            </span></span><span>for(int i=1;i&lt;=n;i++){</span></div></div><div><div><div>238</div></div><div><span><span>                </span></span><span>vector&lt;ll int&gt;v=dist[a[i]];</span></div></div><div><div><div>239</div></div><div><span><span>                </span></span><span>ll int mul=1;</span></div></div><div><div><div>240</div></div><div><span><span>                </span></span><span>for(auto e:v){</span></div></div><div><div><div>241</div></div><div><span><span>                    </span></span><span>mul*=e;</span></div></div><div><div><div>242</div></div><div><span><span>                </span></span><span>}</span></div></div><div><div><div>243</div></div><div><span><span>                </span></span><span>a[i]=mul;</span></div></div><div><div><div>244</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>245</div></div><div><span><span>            </span></span><span>vector&lt;vector&lt;ll int&gt;&gt;divisors=Sieve_all_divisors(*max_element(a+1,a+n+1));</span></div></div><div><div><div>246</div></div><div><span>/*            //testing</span></div></div><div><div><div>247</div></div><div><span><span>            </span></span><span>for(int i=1;i&lt;=n;i++){</span></div></div><div><div><div>248</div></div><div><span><span>                </span></span><span>cout&lt;&lt;a[i]&lt;&lt;" ";</span></div></div><div><div><div>249</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>250</div></div><div><span><span>            </span></span><span>cout&lt;&lt;endl;</span></div></div><div><div><div>251</div></div><div><span><span>            </span></span><span>for (auto e:dist){</span></div></div><div><div><div>252</div></div><div><span><span>                </span></span><span>for(auto e2:e){</span></div></div><div><div><div>253</div></div><div><span><span>                    </span></span><span>cout&lt;&lt;e2&lt;&lt;" ";</span></div></div><div><div><div>254</div></div><div><span><span>                </span></span><span>}</span></div></div><div><div><div>255</div></div><div><span><span>                </span></span><span>cout&lt;&lt;endl;</span></div></div><div><div><div>256</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>257</div></div><div><span><span>            </span></span><span>cout&lt;&lt;endl;cout&lt;&lt;endl;</span></div></div><div><div><div>258</div></div><div><span><span>            </span></span><span>for (auto e:divisors){</span></div></div><div><div><div>259</div></div><div><span><span>                </span></span><span>for(auto e2:e){</span></div></div><div><div><div>260</div></div><div><span><span>                    </span></span><span>cout&lt;&lt;e2&lt;&lt;" ";</span></div></div><div><div><div>261</div></div><div><span><span>                </span></span><span>}</span></div></div><div><div><div>262</div></div><div><span><span>                </span></span><span>cout&lt;&lt;endl;</span></div></div><div><div><div>263</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>264</div></div><div><span><span>            </span></span><span>cout&lt;&lt;"TESTING 1 OVER"&lt;&lt;endl;</span></div></div><div><div><div>265</div></div><div><span><span>            </span></span><span>//testing over*/</span></div></div><div><div><div>266</div></div><div><span><span>            </span></span><span>ll int temp[(*max_element(a+1,a+n+1))+1];memset(temp,0,sizeof(temp));</span></div></div><div><div><div>267</div></div><div><span><span>            </span></span><span>ll int dp[n+1]={0};</span></div></div><div><div><div>268</div></div><div><span><span>            </span></span><span>for(int i=1;i&lt;=n;i++){</span></div></div><div><div><div>269</div></div><div><span><span>                </span></span><span>//cout&lt;&lt;"i:"&lt;&lt;i&lt;&lt;endl;</span></div></div><div><div><div>270</div></div><div><span><span>                </span></span><span>ll int cnt=dist[a[i]].size();</span></div></div><div><div><div>271</div></div><div><span><span>                </span></span><span>//cout&lt;&lt;"cnt:"&lt;&lt;cnt&lt;&lt;endl;</span></div></div><div><div><div>272</div></div><div><span><span>                </span></span><span>ll int sum=0;</span></div></div><div><div><div>273</div></div><div><span><span>                </span></span><span>ll int mx=((1&lt;&lt;cnt)-1);</span></div></div><div><div><div>274</div></div><div><span><span>                </span></span><span>//cout&lt;&lt;"mx:"&lt;&lt;mx&lt;&lt;endl;</span></div></div><div><div><div>275</div></div><div><span><span>                </span></span><span>for(ll int j=1;j&lt;=mx;j++){</span></div></div><div><div><div>276</div></div><div><span><span>                    </span></span><span>ll int t=j;</span></div></div><div><div><div>277</div></div><div><span><span>                    </span></span><span>ll int pos=0;</span></div></div><div><div><div>278</div></div><div><span><span>                    </span></span><span>ll int x=1;ll int set=0;</span></div></div><div><div><div>279</div></div><div><span><span>                    </span></span><span>while(t&gt;0){</span></div></div><div><div><div>280</div></div><div><span><span>                        </span></span><span>if(t%2==1){</span></div></div><div><div><div>281</div></div><div><span><span>                            </span></span><span>x*=dist[a[i]][pos];set++;</span></div></div><div><div><div>282</div></div><div><span><span>                        </span></span><span>}</span></div></div><div><div><div>283</div></div><div><span><span>                        </span></span><span>t/=2;</span></div></div><div><div><div>284</div></div><div><span><span>                        </span></span><span>pos++;</span></div></div><div><div><div>285</div></div><div><span>/*                        cout&lt;&lt;"pos:"&lt;&lt;pos&lt;&lt;endl;</span></div></div><div><div><div>286</div></div><div><span><span>                        </span></span><span>cout&lt;&lt;"t:"&lt;&lt;t&lt;&lt;endl;</span></div></div><div><div><div>287</div></div><div><span><span>                        </span></span><span>cout&lt;&lt;"x:"&lt;&lt;x&lt;&lt;endl;*/</span></div></div><div><div><div>288</div></div><div><span><span>                    </span></span><span>}</span></div></div><div><div><div>289</div></div><div><span><span>                    </span></span><span>ll int sign=1;if(set%2==0)sign=(ll int)(-1);</span></div></div><div><div><div>290</div></div><div><span><span>                    </span></span><span>sum+=temp[x]*sign;sum%=p;</span></div></div><div><div><div>291</div></div><div><span><span>                </span></span><span>}</span></div></div><div><div><div>292</div></div><div><span><span>                </span></span><span>//cout&lt;&lt;"sum:"&lt;&lt;sum&lt;&lt;endl;</span></div></div><div><div><div>293</div></div><div><span><span>                </span></span><span>if(i==1)sum=1;</span></div></div><div><div><div>294</div></div><div><span><span>                </span></span><span>dp[i]=sum%p;</span></div></div><div><div><div>295</div></div><div><span><span>                </span></span><span>for(int j=0;j&lt;divisors[a[i]].size();j++){</span></div></div><div><div><div>296</div></div><div><span><span>                    </span></span><span>temp[divisors[a[i]][j]]+=sum;temp[divisors[a[i]][j]]%=p;</span></div></div><div><div><div>297</div></div><div><span><span>                </span></span><span>}</span></div></div><div><div><div>298</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>299</div></div><div><span><span>            </span></span><span>cout&lt;&lt;((dp[n]%p)+p)%p&lt;&lt;endl;</span></div></div><div><div><div>300</div></div><div>
</div></div><div><div><div>301</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>302</div></div><div><span><span>        </span></span><span>return 0;</span></div></div><div><div><div>303</div></div><div><span><span>    </span></span><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>这份代码模板完全不想看究竟定义了什么，重载了什么，而且代码之间没有空格，显得十分紧凑，阅读观感也极差。因此，一份良好的代码起手模板只有两种：</p><ul>
<li>最基本的能保证运行的短代码模板</li>
<li>能够有着良好分区的长代码模板</li>
</ul></section>
<section><h3>算法模版<a href="#算法模版"><span>#</span></a></h3><p>良好的算法模板必须由lambda/namespace/struct/template/代码片段封装，以此保证代码的可移植性，可读性。算法模板一定不能是一段针对于该算法的模板题目可以运行的代码程序。</p><ol>
<li>lambda</li>
</ol><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>djikstra(); djikstra(s); 不写默认s=1</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>vector&lt;int&gt; dis(n + 1, 1E18);</span></div></div><div><div><div>4</div></div><div><span>auto djikstra = [&amp;](int s = 1) -&gt; void {</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>using PII = pair&lt;int, int&gt;;</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>priority_queue&lt;PII, vector&lt;PII&gt;, greater&lt;PII&gt;&gt; q;</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>q.emplace(0, s);</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>dis[s] = 0;</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>vector&lt;int&gt; vis(n + 1);</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>while (!q.empty()) {</span></div></div><div><div><div>11</div></div><div><span><span>        </span></span><span>int x = q.top().second;</span></div></div><div><div><div>12</div></div><div><span><span>        </span></span><span>q.pop();</span></div></div><div><div><div>13</div></div><div><span><span>        </span></span><span>if (vis[x]) continue;</span></div></div><div><div><div>14</div></div><div><span><span>        </span></span><span>vis[x] = 1;</span></div></div><div><div><div>15</div></div><div><span><span>        </span></span><span>for (auto [y, w] : ver[x]) {</span></div></div><div><div><div>16</div></div><div><span><span>            </span></span><span>if (dis[y] &gt; dis[x] + w) {</span></div></div><div><div><div>17</div></div><div><span><span>                </span></span><span>dis[y] = dis[x] + w;</span></div></div><div><div><div>18</div></div><div><span><span>                </span></span><span>q.emplace(dis[y], y);</span></div></div><div><div><div>19</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>20</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>21</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>22</div></div><div><span>};</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><ol>
<li>namespace</li>
</ol><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>eg:</span></div></div><div><div><div>2</div></div><div><span>Geometry::Point&lt;int&gt; a, b;</span></div></div><div><div><div>3</div></div><div><span>cin &gt;&gt; a &gt;&gt; b;</span></div></div><div><div><div>4</div></div><div><span>cout &lt;&lt; a + b &lt;&lt; endl;</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>namespace Geometry { // 平面几何基础</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>using ld = long double;</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>const ld PI = acos(-1);</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>const ld EPS = 1e-7;</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>const ld INF = numeric_limits&lt;ld&gt;::max();</span></div></div><div><div><div>11</div></div><div><span><span>    </span></span><span>#define cc(x) cout &lt;&lt; fixed &lt;&lt; setprecision(x);</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span><span>    </span></span><span>ld fgcd(ld x, ld y) { // 实数域gcd</span></div></div><div><div><div>14</div></div><div><span><span>        </span></span><span>return abs(y) &lt; EPS ? abs(x) : fgcd(y, fmod(x, y));</span></div></div><div><div><div>15</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>16</div></div><div><span><span>    </span></span><span>template&lt;class T, class S&gt; bool equal(T x, S y) {</span></div></div><div><div><div>17</div></div><div><span><span>        </span></span><span>return -EPS &lt; x - y &amp;&amp; x - y &lt; EPS;</span></div></div><div><div><div>18</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>19</div></div><div><span><span>    </span></span><span>template&lt;class T&gt; int sign(T x) {</span></div></div><div><div><div>20</div></div><div><span><span>        </span></span><span>if (-EPS &lt; x &amp;&amp; x &lt; EPS) return 0;</span></div></div><div><div><div>21</div></div><div><span><span>        </span></span><span>return x &lt; 0 ? -1 : 1;</span></div></div><div><div><div>22</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>23</div></div><div>
</div></div><div><div><div>24</div></div><div><span><span>    </span></span><span>template&lt;class T&gt; struct Point { // 在C++17下使用 emplace_back 绑定可能会导致CE！</span></div></div><div><div><div>25</div></div><div><span><span>        </span></span><span>T x, y;</span></div></div><div><div><div>26</div></div><div><span><span>        </span></span><span>Point(T x_ = 0, T y_ = 0) : x(x_), y(y_) {} // 初始化</span></div></div><div><div><div>27</div></div><div><span><span>        </span></span><span>template&lt;class U&gt; operator Point&lt;U&gt;() { // 自动类型匹配</span></div></div><div><div><div>28</div></div><div><span><span>            </span></span><span>return Point&lt;U&gt;(U(x), U(y));</span></div></div><div><div><div>29</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>30</div></div><div><span><span>        </span></span><span>Point &amp;operator+=(Point p) &amp; { return x += p.x, y += p.y, *this; }</span></div></div><div><div><div>31</div></div><div><span><span>        </span></span><span>Point &amp;operator+=(T t) &amp; { return x += t, y += t, *this; }</span></div></div><div><div><div>32</div></div><div><span><span>        </span></span><span>Point &amp;operator-=(Point p) &amp; { return x -= p.x, y -= p.y, *this; }</span></div></div><div><div><div>33</div></div><div><span><span>        </span></span><span>Point &amp;operator-=(T t) &amp; { return x -= t, y -= t, *this; }</span></div></div><div><div><div>34</div></div><div><span><span>        </span></span><span>Point &amp;operator*=(T t) &amp; { return x *= t, y *= t, *this; }</span></div></div><div><div><div>35</div></div><div><span><span>        </span></span><span>Point &amp;operator/=(T t) &amp; { return x /= t, y /= t, *this; }</span></div></div><div><div><div>36</div></div><div><span><span>        </span></span><span>Point operator-() const { return Point(-x, -y); }</span></div></div><div><div><div>37</div></div><div><span><span>        </span></span><span>friend Point operator+(Point a, Point b) { return a += b; }</span></div></div><div><div><div>38</div></div><div><span><span>        </span></span><span>friend Point operator+(Point a, T b) { return a += b; }</span></div></div><div><div><div>39</div></div><div><span><span>        </span></span><span>friend Point operator-(Point a, Point b) { return a -= b; }</span></div></div><div><div><div>40</div></div><div><span><span>        </span></span><span>friend Point operator-(Point a, T b) { return a -= b; }</span></div></div><div><div><div>41</div></div><div><span><span>        </span></span><span>friend Point operator*(Point a, T b) { return a *= b; }</span></div></div><div><div><div>42</div></div><div><span><span>        </span></span><span>friend Point operator*(T a, Point b) { return b *= a; }</span></div></div><div><div><div>43</div></div><div><span><span>        </span></span><span>friend Point operator/(Point a, T b) { return a /= b; }</span></div></div><div><div><div>44</div></div><div><span><span>        </span></span><span>friend bool operator&lt;(Point a, Point b) {</span></div></div><div><div><div>45</div></div><div><span><span>            </span></span><span>return equal(a.x, b.x) ? a.y &lt; b.y - EPS : a.x &lt; b.x - EPS;</span></div></div><div><div><div>46</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>47</div></div><div><span><span>        </span></span><span>friend bool operator&gt;(Point a, Point b) { return b &lt; a; }</span></div></div><div><div><div>48</div></div><div><span><span>        </span></span><span>friend bool operator==(Point a, Point b) { return !(a &lt; b) &amp;&amp; !(b &lt; a); }</span></div></div><div><div><div>49</div></div><div><span><span>        </span></span><span>friend bool operator!=(Point a, Point b) { return a &lt; b || b &lt; a; }</span></div></div><div><div><div>50</div></div><div><span><span>        </span></span><span>friend auto &amp;operator&gt;&gt;(istream &amp;is, Point &amp;p) {</span></div></div><div><div><div>51</div></div><div><span><span>            </span></span><span>return is &gt;&gt; p.x &gt;&gt; p.y;</span></div></div><div><div><div>52</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>53</div></div><div><span><span>        </span></span><span>friend auto &amp;operator&lt;&lt;(ostream &amp;os, Point p) {</span></div></div><div><div><div>54</div></div><div><span><span>            </span></span><span>return os &lt;&lt; "(" &lt;&lt; p.x &lt;&lt; ", " &lt;&lt; p.y &lt;&lt; ")";</span></div></div><div><div><div>55</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>56</div></div><div><span><span>    </span></span><span>};</span></div></div><div><div><div>57</div></div><div><span><span>    </span></span><span>template&lt;class T&gt; struct Line {</span></div></div><div><div><div>58</div></div><div><span><span>        </span></span><span>Point&lt;T&gt; a, b;</span></div></div><div><div><div>59</div></div><div><span><span>        </span></span><span>Line(Point&lt;T&gt; a_ = Point&lt;T&gt;(), Point&lt;T&gt; b_ = Point&lt;T&gt;()) : a(a_), b(b_) {}</span></div></div><div><div><div>60</div></div><div><span><span>        </span></span><span>template&lt;class U&gt; operator Line&lt;U&gt;() { // 自动类型匹配</span></div></div><div><div><div>61</div></div><div><span><span>            </span></span><span>return Line&lt;U&gt;(Point&lt;U&gt;(a), Point&lt;U&gt;(b));</span></div></div><div><div><div>62</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>63</div></div><div><span><span>        </span></span><span>friend auto &amp;operator&lt;&lt;(ostream &amp;os, Line l) {</span></div></div><div><div><div>64</div></div><div><span><span>            </span></span><span>return os &lt;&lt; "&lt;" &lt;&lt; l.a &lt;&lt; ", " &lt;&lt; l.b &lt;&lt; "&gt;";</span></div></div><div><div><div>65</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>66</div></div><div><span><span>    </span></span><span>};</span></div></div><div><div><div>67</div></div><div>
</div></div><div><div><div>68</div></div><div><span><span>    </span></span><span>template&lt;class T&gt; T cross(Point&lt;T&gt; a, Point&lt;T&gt; b) { // 叉乘</span></div></div><div><div><div>69</div></div><div><span><span>        </span></span><span>return a.x * b.y - a.y * b.x;</span></div></div><div><div><div>70</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>71</div></div><div><span><span>    </span></span><span>template&lt;class T&gt;</span></div></div><div><div><div>72</div></div><div><span><span>    </span></span><span>T cross(Point&lt;T&gt; p1, Point&lt;T&gt; p2, Point&lt;T&gt; p0) { // 叉乘 (p1 - p0) x (p2 - p0);</span></div></div><div><div><div>73</div></div><div><span><span>        </span></span><span>return cross(p1 - p0, p2 - p0);</span></div></div><div><div><div>74</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>75</div></div><div>
</div></div><div><div><div>76</div></div><div><span><span>    </span></span><span>template&lt;class T&gt; T dot(Point&lt;T&gt; a, Point&lt;T&gt; b) { // 点乘</span></div></div><div><div><div>77</div></div><div><span><span>        </span></span><span>return a.x * b.x + a.y * b.y;</span></div></div><div><div><div>78</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>79</div></div><div><span><span>    </span></span><span>template&lt;class T&gt; T dot(Point&lt;T&gt; p1, Point&lt;T&gt; p2, Point&lt;T&gt; p0) { // 点乘 (p1 - p0) * (p2 - p0);</span></div></div><div><div><div>80</div></div><div><span><span>        </span></span><span>return dot(p1 - p0, p2 - p0);</span></div></div><div><div><div>81</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>82</div></div><div>
</div></div><div><div><div>83</div></div><div><span><span>    </span></span><span>template&lt;class T&gt; ld dis(T x1, T y1, T x2, T y2) {</span></div></div><div><div><div>84</div></div><div><span><span>        </span></span><span>ld val = (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2);</span></div></div><div><div><div>85</div></div><div><span><span>        </span></span><span>return sqrt(val);</span></div></div><div><div><div>86</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>87</div></div><div><span><span>    </span></span><span>template&lt;class T&gt; ld dis(Point&lt;T&gt; a, Point&lt;T&gt; b) {</span></div></div><div><div><div>88</div></div><div><span><span>        </span></span><span>return dis(a.x, a.y, b.x, b.y);</span></div></div><div><div><div>89</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>90</div></div><div>
</div></div><div><div><div>91</div></div><div><span><span>    </span></span><span>template&lt;class T&gt; T dis1(Point&lt;T&gt; p1, Point&lt;T&gt; p2) { // 曼哈顿距离公式</span></div></div><div><div><div>92</div></div><div><span><span>        </span></span><span>return abs(p1.x - p2.x) + abs(p1.y - p2.y);</span></div></div><div><div><div>93</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>94</div></div><div>
</div></div><div><div><div>95</div></div><div><span><span>    </span></span><span>Point&lt;ld&gt; standardize(Point&lt;ld&gt; vec) { // 转换为单位向量</span></div></div><div><div><div>96</div></div><div><span><span>        </span></span><span>return vec / sqrt(vec.x * vec.x + vec.y * vec.y);</span></div></div><div><div><div>97</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>98</div></div><div>
</div></div><div><div><div>99</div></div><div><span><span>    </span></span><span>template&lt;class T&gt; Point&lt;T&gt; rotate(Point&lt;T&gt; p1, Point&lt;T&gt; p2) { // 旋转</span></div></div><div><div><div>100</div></div><div><span><span>        </span></span><span>Point&lt;T&gt; vec = p1 - p2;</span></div></div><div><div><div>101</div></div><div><span><span>        </span></span><span>return {-vec.y, vec.x};</span></div></div><div><div><div>102</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>103</div></div><div><span>} // namespace Geometry</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><ol>
<li>struct</li>
</ol><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>const int base = 1000000000;</span></div></div><div><div><div>2</div></div><div><span>const int base_digits = 9; // 分解为九个数位一个数字</span></div></div><div><div><div>3</div></div><div><span>struct bigint {</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>vector&lt;int&gt; a;</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>int sign;</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>bigint() : sign(1) {}</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>bigint operator-() const {</span></div></div><div><div><div>9</div></div><div><span><span>        </span></span><span>bigint res = *this;</span></div></div><div><div><div>10</div></div><div><span><span>        </span></span><span>res.sign = -sign;</span></div></div><div><div><div>11</div></div><div><span><span>        </span></span><span>return res;</span></div></div><div><div><div>12</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>13</div></div><div><span><span>    </span></span><span>bigint(long long v) {</span></div></div><div><div><div>14</div></div><div><span><span>        </span></span><span>*this = v;</span></div></div><div><div><div>15</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>16</div></div><div><span><span>    </span></span><span>bigint(const string &amp;s) {</span></div></div><div><div><div>17</div></div><div><span><span>        </span></span><span>read(s);</span></div></div><div><div><div>18</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>19</div></div><div><span><span>    </span></span><span>void operator=(const bigint &amp;v) {</span></div></div><div><div><div>20</div></div><div><span><span>        </span></span><span>sign = v.sign;</span></div></div><div><div><div>21</div></div><div><span><span>        </span></span><span>a = v.a;</span></div></div><div><div><div>22</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>23</div></div><div><span><span>    </span></span><span>void operator=(long long v) {</span></div></div><div><div><div>24</div></div><div><span><span>        </span></span><span>a.clear();</span></div></div><div><div><div>25</div></div><div><span><span>        </span></span><span>sign = 1;</span></div></div><div><div><div>26</div></div><div><span><span>        </span></span><span>if (v &lt; 0) sign = -1, v = -v;</span></div></div><div><div><div>27</div></div><div><span><span>        </span></span><span>for (; v &gt; 0; v = v / base) {</span></div></div><div><div><div>28</div></div><div><span><span>            </span></span><span>a.push_back(v % base);</span></div></div><div><div><div>29</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>30</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>31</div></div><div>
</div></div><div><div><div>32</div></div><div><span><span>    </span></span><span>// 基础加减乘除</span></div></div><div><div><div>33</div></div><div><span><span>    </span></span><span>bigint operator+(const bigint &amp;v) const {</span></div></div><div><div><div>34</div></div><div><span><span>        </span></span><span>if (sign == v.sign) {</span></div></div><div><div><div>35</div></div><div><span><span>            </span></span><span>bigint res = v;</span></div></div><div><div><div>36</div></div><div><span><span>            </span></span><span>for (int i = 0, carry = 0; i &lt; (int)max(a.size(), v.a.size()) || carry; ++i) {</span></div></div><div><div><div>37</div></div><div><span><span>                </span></span><span>if (i == (int)res.a.size()) {</span></div></div><div><div><div>38</div></div><div><span><span>                    </span></span><span>res.a.push_back(0);</span></div></div><div><div><div>39</div></div><div><span><span>                </span></span><span>}</span></div></div><div><div><div>40</div></div><div><span><span>                </span></span><span>res.a[i] += carry + (i &lt; (int)a.size() ? a[i] : 0);</span></div></div><div><div><div>41</div></div><div><span><span>                </span></span><span>carry = res.a[i] &gt;= base;</span></div></div><div><div><div>42</div></div><div><span><span>                </span></span><span>if (carry) {</span></div></div><div><div><div>43</div></div><div><span><span>                    </span></span><span>res.a[i] -= base;</span></div></div><div><div><div>44</div></div><div><span><span>                </span></span><span>}</span></div></div><div><div><div>45</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>46</div></div><div><span><span>            </span></span><span>return res;</span></div></div><div><div><div>47</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>48</div></div><div><span><span>        </span></span><span>return *this - (-v);</span></div></div><div><div><div>49</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>50</div></div><div><span><span>    </span></span><span>bigint operator-(const bigint &amp;v) const {</span></div></div><div><div><div>51</div></div><div><span><span>        </span></span><span>if (sign == v.sign) {</span></div></div><div><div><div>52</div></div><div><span><span>            </span></span><span>if (abs() &gt;= v.abs()) {</span></div></div><div><div><div>53</div></div><div><span><span>                </span></span><span>bigint res = *this;</span></div></div><div><div><div>54</div></div><div><span><span>                </span></span><span>for (int i = 0, carry = 0; i &lt; (int)v.a.size() || carry; ++i) {</span></div></div><div><div><div>55</div></div><div><span><span>                    </span></span><span>res.a[i] -= carry + (i &lt; (int)v.a.size() ? v.a[i] : 0);</span></div></div><div><div><div>56</div></div><div><span><span>                    </span></span><span>carry = res.a[i] &lt; 0;</span></div></div><div><div><div>57</div></div><div><span><span>                    </span></span><span>if (carry) {</span></div></div><div><div><div>58</div></div><div><span><span>                        </span></span><span>res.a[i] += base;</span></div></div><div><div><div>59</div></div><div><span><span>                    </span></span><span>}</span></div></div><div><div><div>60</div></div><div><span><span>                </span></span><span>}</span></div></div><div><div><div>61</div></div><div><span><span>                </span></span><span>res.trim();</span></div></div><div><div><div>62</div></div><div><span><span>                </span></span><span>return res;</span></div></div><div><div><div>63</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>64</div></div><div><span><span>            </span></span><span>return -(v - *this);</span></div></div><div><div><div>65</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>66</div></div><div><span><span>        </span></span><span>return *this + (-v);</span></div></div><div><div><div>67</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>68</div></div><div><span><span>    </span></span><span>void operator*=(int v) {</span></div></div><div><div><div>69</div></div><div><span><span>        </span></span><span>check(v);</span></div></div><div><div><div>70</div></div><div><span><span>        </span></span><span>for (int i = 0, carry = 0; i &lt; (int)a.size() || carry; ++i) {</span></div></div><div><div><div>71</div></div><div><span><span>            </span></span><span>if (i == (int)a.size()) {</span></div></div><div><div><div>72</div></div><div><span><span>                </span></span><span>a.push_back(0);</span></div></div><div><div><div>73</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>74</div></div><div><span><span>            </span></span><span>long long cur = a[i] * (long long)v + carry;</span></div></div><div><div><div>75</div></div><div><span><span>            </span></span><span>carry = (int)(cur / base);</span></div></div><div><div><div>76</div></div><div><span><span>            </span></span><span>a[i] = (int)(cur % base);</span></div></div><div><div><div>77</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>78</div></div><div><span><span>        </span></span><span>trim();</span></div></div><div><div><div>79</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>80</div></div><div><span><span>    </span></span><span>void operator/=(int v) {</span></div></div><div><div><div>81</div></div><div><span><span>        </span></span><span>check(v);</span></div></div><div><div><div>82</div></div><div><span><span>        </span></span><span>for (int i = (int)a.size() - 1, rem = 0; i &gt;= 0; --i) {</span></div></div><div><div><div>83</div></div><div><span><span>            </span></span><span>long long cur = a[i] + rem * (long long)base;</span></div></div><div><div><div>84</div></div><div><span><span>            </span></span><span>a[i] = (int)(cur / v);</span></div></div><div><div><div>85</div></div><div><span><span>            </span></span><span>rem = (int)(cur % v);</span></div></div><div><div><div>86</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>87</div></div><div><span><span>        </span></span><span>trim();</span></div></div><div><div><div>88</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>89</div></div><div><span><span>    </span></span><span>int operator%(int v) const {</span></div></div><div><div><div>90</div></div><div><span><span>        </span></span><span>if (v &lt; 0) {</span></div></div><div><div><div>91</div></div><div><span><span>            </span></span><span>v = -v;</span></div></div><div><div><div>92</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>93</div></div><div><span><span>        </span></span><span>int m = 0;</span></div></div><div><div><div>94</div></div><div><span><span>        </span></span><span>for (int i = a.size() - 1; i &gt;= 0; --i) {</span></div></div><div><div><div>95</div></div><div><span><span>            </span></span><span>m = (a[i] + m * (long long)base) % v;</span></div></div><div><div><div>96</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>97</div></div><div><span><span>        </span></span><span>return m * sign;</span></div></div><div><div><div>98</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>99</div></div><div>
</div></div><div><div><div>100</div></div><div><span><span>    </span></span><span>void operator+=(const bigint &amp;v) {</span></div></div><div><div><div>101</div></div><div><span><span>        </span></span><span>*this = *this + v;</span></div></div><div><div><div>102</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>103</div></div><div><span><span>    </span></span><span>void operator-=(const bigint &amp;v) {</span></div></div><div><div><div>104</div></div><div><span><span>        </span></span><span>*this = *this - v;</span></div></div><div><div><div>105</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>106</div></div><div><span><span>    </span></span><span>bigint operator*(int v) const {</span></div></div><div><div><div>107</div></div><div><span><span>        </span></span><span>bigint res = *this;</span></div></div><div><div><div>108</div></div><div><span><span>        </span></span><span>res *= v;</span></div></div><div><div><div>109</div></div><div><span><span>        </span></span><span>return res;</span></div></div><div><div><div>110</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>111</div></div><div><span><span>    </span></span><span>bigint operator/(int v) const {</span></div></div><div><div><div>112</div></div><div><span><span>        </span></span><span>bigint res = *this;</span></div></div><div><div><div>113</div></div><div><span><span>        </span></span><span>res /= v;</span></div></div><div><div><div>114</div></div><div><span><span>        </span></span><span>return res;</span></div></div><div><div><div>115</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>116</div></div><div><span><span>    </span></span><span>void operator%=(const int &amp;v) {</span></div></div><div><div><div>117</div></div><div><span><span>        </span></span><span>*this = *this % v;</span></div></div><div><div><div>118</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>119</div></div><div>
</div></div><div><div><div>120</div></div><div><span><span>    </span></span><span>bool operator&lt;(const bigint &amp;v) const {</span></div></div><div><div><div>121</div></div><div><span><span>        </span></span><span>if (sign != v.sign) return sign &lt; v.sign;</span></div></div><div><div><div>122</div></div><div><span><span>        </span></span><span>if (a.size() != v.a.size()) return a.size() * sign &lt; v.a.size() * v.sign;</span></div></div><div><div><div>123</div></div><div><span><span>        </span></span><span>for (int i = a.size() - 1; i &gt;= 0; i--)</span></div></div><div><div><div>124</div></div><div><span><span>            </span></span><span>if (a[i] != v.a[i]) return a[i] * sign &lt; v.a[i] * sign;</span></div></div><div><div><div>125</div></div><div><span><span>        </span></span><span>return false;</span></div></div><div><div><div>126</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>127</div></div><div><span><span>    </span></span><span>bool operator&gt;(const bigint &amp;v) const {</span></div></div><div><div><div>128</div></div><div><span><span>        </span></span><span>return v &lt; *this;</span></div></div><div><div><div>129</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>130</div></div><div><span><span>    </span></span><span>bool operator&lt;=(const bigint &amp;v) const {</span></div></div><div><div><div>131</div></div><div><span><span>        </span></span><span>return !(v &lt; *this);</span></div></div><div><div><div>132</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>133</div></div><div><span><span>    </span></span><span>bool operator&gt;=(const bigint &amp;v) const {</span></div></div><div><div><div>134</div></div><div><span><span>        </span></span><span>return !(*this &lt; v);</span></div></div><div><div><div>135</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>136</div></div><div><span><span>    </span></span><span>bool operator==(const bigint &amp;v) const {</span></div></div><div><div><div>137</div></div><div><span><span>        </span></span><span>return !(*this &lt; v) &amp;&amp; !(v &lt; *this);</span></div></div><div><div><div>138</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>139</div></div><div><span><span>    </span></span><span>bool operator!=(const bigint &amp;v) const {</span></div></div><div><div><div>140</div></div><div><span><span>        </span></span><span>return *this &lt; v || v &lt; *this;</span></div></div><div><div><div>141</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>142</div></div><div>
</div></div><div><div><div>143</div></div><div><span><span>    </span></span><span>bigint abs() const {</span></div></div><div><div><div>144</div></div><div><span><span>        </span></span><span>bigint res = *this;</span></div></div><div><div><div>145</div></div><div><span><span>        </span></span><span>res.sign *= res.sign;</span></div></div><div><div><div>146</div></div><div><span><span>        </span></span><span>return res;</span></div></div><div><div><div>147</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>148</div></div><div><span><span>    </span></span><span>void check(int v) { // 检查输入的是否为负数</span></div></div><div><div><div>149</div></div><div><span><span>        </span></span><span>if (v &lt; 0) {</span></div></div><div><div><div>150</div></div><div><span><span>            </span></span><span>sign = -sign;</span></div></div><div><div><div>151</div></div><div><span><span>            </span></span><span>v = -v;</span></div></div><div><div><div>152</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>153</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>154</div></div><div><span><span>    </span></span><span>void trim() { // 去除前导零</span></div></div><div><div><div>155</div></div><div><span><span>        </span></span><span>while (!a.empty() &amp;&amp; !a.back()) a.pop_back();</span></div></div><div><div><div>156</div></div><div><span><span>        </span></span><span>if (a.empty()) sign = 1;</span></div></div><div><div><div>157</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>158</div></div><div><span><span>    </span></span><span>bool isZero() const { // 判断是否等于零</span></div></div><div><div><div>159</div></div><div><span><span>        </span></span><span>return a.empty() || (a.size() == 1 &amp;&amp; !a[0]);</span></div></div><div><div><div>160</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>161</div></div><div><span><span>    </span></span><span>friend bigint gcd(const bigint &amp;a, const bigint &amp;b) {</span></div></div><div><div><div>162</div></div><div><span><span>        </span></span><span>return b.isZero() ? a : gcd(b, a % b);</span></div></div><div><div><div>163</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>164</div></div><div><span><span>    </span></span><span>friend bigint lcm(const bigint &amp;a, const bigint &amp;b) {</span></div></div><div><div><div>165</div></div><div><span><span>        </span></span><span>return a / gcd(a, b) * b;</span></div></div><div><div><div>166</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>167</div></div><div><span><span>    </span></span><span>void read(const string &amp;s) {</span></div></div><div><div><div>168</div></div><div><span><span>        </span></span><span>sign = 1;</span></div></div><div><div><div>169</div></div><div><span><span>        </span></span><span>a.clear();</span></div></div><div><div><div>170</div></div><div><span><span>        </span></span><span>int pos = 0;</span></div></div><div><div><div>171</div></div><div><span><span>        </span></span><span>while (pos &lt; (int)s.size() &amp;&amp; (s[pos] == '-' || s[pos] == '+')) {</span></div></div><div><div><div>172</div></div><div><span><span>            </span></span><span>if (s[pos] == '-') sign = -sign;</span></div></div><div><div><div>173</div></div><div><span><span>            </span></span><span>++pos;</span></div></div><div><div><div>174</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>175</div></div><div><span><span>        </span></span><span>for (int i = s.size() - 1; i &gt;= pos; i -= base_digits) {</span></div></div><div><div><div>176</div></div><div><span><span>            </span></span><span>int x = 0;</span></div></div><div><div><div>177</div></div><div><span><span>            </span></span><span>for (int j = max(pos, i - base_digits + 1); j &lt;= i; j++) x = x * 10 + s[j] - '0';</span></div></div><div><div><div>178</div></div><div><span><span>            </span></span><span>a.push_back(x);</span></div></div><div><div><div>179</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>180</div></div><div><span><span>        </span></span><span>trim();</span></div></div><div><div><div>181</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>182</div></div><div><span><span>    </span></span><span>friend istream &amp;operator&gt;&gt;(istream &amp;stream, bigint &amp;v) {</span></div></div><div><div><div>183</div></div><div><span><span>        </span></span><span>string s;</span></div></div><div><div><div>184</div></div><div><span><span>        </span></span><span>stream &gt;&gt; s;</span></div></div><div><div><div>185</div></div><div><span><span>        </span></span><span>v.read(s);</span></div></div><div><div><div>186</div></div><div><span><span>        </span></span><span>return stream;</span></div></div><div><div><div>187</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>188</div></div><div><span><span>    </span></span><span>friend ostream &amp;operator&lt;&lt;(ostream &amp;stream, const bigint &amp;v) {</span></div></div><div><div><div>189</div></div><div><span><span>        </span></span><span>if (v.sign == -1) stream &lt;&lt; '-';</span></div></div><div><div><div>190</div></div><div><span><span>        </span></span><span>stream &lt;&lt; (v.a.empty() ? 0 : v.a.back());</span></div></div><div><div><div>191</div></div><div><span><span>        </span></span><span>for (int i = (int)v.a.size() - 2; i &gt;= 0; --i)</span></div></div><div><div><div>192</div></div><div><span><span>            </span></span><span>stream &lt;&lt; setw(base_digits) &lt;&lt; setfill('0') &lt;&lt; v.a[i];</span></div></div><div><div><div>193</div></div><div><span><span>        </span></span><span>return stream;</span></div></div><div><div><div>194</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>195</div></div><div>
</div></div><div><div><div>196</div></div><div><span><span>    </span></span><span>/* 大整数乘除大整数部分 */</span></div></div><div><div><div>197</div></div><div><span><span>    </span></span><span>typedef vector&lt;long long&gt; vll;</span></div></div><div><div><div>198</div></div><div><span><span>    </span></span><span>bigint operator*(const bigint &amp;v) const { // 大整数乘大整数</span></div></div><div><div><div>199</div></div><div><span><span>        </span></span><span>vector&lt;int&gt; a6 = convert_base(this-&gt;a, base_digits, 6);</span></div></div><div><div><div>200</div></div><div><span><span>        </span></span><span>vector&lt;int&gt; b6 = convert_base(v.a, base_digits, 6);</span></div></div><div><div><div>201</div></div><div><span><span>        </span></span><span>vll a(a6.begin(), a6.end());</span></div></div><div><div><div>202</div></div><div><span><span>        </span></span><span>vll b(b6.begin(), b6.end());</span></div></div><div><div><div>203</div></div><div><span><span>        </span></span><span>while (a.size() &lt; b.size()) a.push_back(0);</span></div></div><div><div><div>204</div></div><div><span><span>        </span></span><span>while (b.size() &lt; a.size()) b.push_back(0);</span></div></div><div><div><div>205</div></div><div><span><span>        </span></span><span>while (a.size() &amp; (a.size() - 1)) a.push_back(0), b.push_back(0);</span></div></div><div><div><div>206</div></div><div><span><span>        </span></span><span>vll c = karatsubaMultiply(a, b);</span></div></div><div><div><div>207</div></div><div><span><span>        </span></span><span>bigint res;</span></div></div><div><div><div>208</div></div><div><span><span>        </span></span><span>res.sign = sign * v.sign;</span></div></div><div><div><div>209</div></div><div><span><span>        </span></span><span>for (int i = 0, carry = 0; i &lt; (int)c.size(); i++) {</span></div></div><div><div><div>210</div></div><div><span><span>            </span></span><span>long long cur = c[i] + carry;</span></div></div><div><div><div>211</div></div><div><span><span>            </span></span><span>res.a.push_back((int)(cur % 1000000));</span></div></div><div><div><div>212</div></div><div><span><span>            </span></span><span>carry = (int)(cur / 1000000);</span></div></div><div><div><div>213</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>214</div></div><div><span><span>        </span></span><span>res.a = convert_base(res.a, 6, base_digits);</span></div></div><div><div><div>215</div></div><div><span><span>        </span></span><span>res.trim();</span></div></div><div><div><div>216</div></div><div><span><span>        </span></span><span>return res;</span></div></div><div><div><div>217</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>218</div></div><div><span><span>    </span></span><span>friend pair&lt;bigint, bigint&gt; divmod(const bigint &amp;a1,</span></div></div><div><div><div>219</div></div><div><span><span>                                       </span></span><span>const bigint &amp;b1) { // 大整数除大整数，同时返回答案与余数</span></div></div><div><div><div>220</div></div><div><span><span>        </span></span><span>int norm = base / (b1.a.back() + 1);</span></div></div><div><div><div>221</div></div><div><span><span>        </span></span><span>bigint a = a1.abs() * norm;</span></div></div><div><div><div>222</div></div><div><span><span>        </span></span><span>bigint b = b1.abs() * norm;</span></div></div><div><div><div>223</div></div><div><span><span>        </span></span><span>bigint q, r;</span></div></div><div><div><div>224</div></div><div><span><span>        </span></span><span>q.a.resize(a.a.size());</span></div></div><div><div><div>225</div></div><div><span><span>        </span></span><span>for (int i = a.a.size() - 1; i &gt;= 0; i--) {</span></div></div><div><div><div>226</div></div><div><span><span>            </span></span><span>r *= base;</span></div></div><div><div><div>227</div></div><div><span><span>            </span></span><span>r += a.a[i];</span></div></div><div><div><div>228</div></div><div><span><span>            </span></span><span>int s1 = r.a.size() &lt;= b.a.size() ? 0 : r.a[b.a.size()];</span></div></div><div><div><div>229</div></div><div><span><span>            </span></span><span>int s2 = r.a.size() &lt;= b.a.size() - 1 ? 0 : r.a[b.a.size() - 1];</span></div></div><div><div><div>230</div></div><div><span><span>            </span></span><span>int d = ((long long)base * s1 + s2) / b.a.back();</span></div></div><div><div><div>231</div></div><div><span><span>            </span></span><span>r -= b * d;</span></div></div><div><div><div>232</div></div><div><span><span>            </span></span><span>while (r &lt; 0) r += b, --d;</span></div></div><div><div><div>233</div></div><div><span><span>            </span></span><span>q.a[i] = d;</span></div></div><div><div><div>234</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>235</div></div><div><span><span>        </span></span><span>q.sign = a1.sign * b1.sign;</span></div></div><div><div><div>236</div></div><div><span><span>        </span></span><span>r.sign = a1.sign;</span></div></div><div><div><div>237</div></div><div><span><span>        </span></span><span>q.trim();</span></div></div><div><div><div>238</div></div><div><span><span>        </span></span><span>r.trim();</span></div></div><div><div><div>239</div></div><div><span><span>        </span></span><span>return make_pair(q, r / norm);</span></div></div><div><div><div>240</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>241</div></div><div><span><span>    </span></span><span>static vector&lt;int&gt; convert_base(const vector&lt;int&gt; &amp;a, int old_digits, int new_digits) {</span></div></div><div><div><div>242</div></div><div><span><span>        </span></span><span>vector&lt;long long&gt; p(max(old_digits, new_digits) + 1);</span></div></div><div><div><div>243</div></div><div><span><span>        </span></span><span>p[0] = 1;</span></div></div><div><div><div>244</div></div><div><span><span>        </span></span><span>for (int i = 1; i &lt; (int)p.size(); i++) p[i] = p[i - 1] * 10;</span></div></div><div><div><div>245</div></div><div><span><span>        </span></span><span>vector&lt;int&gt; res;</span></div></div><div><div><div>246</div></div><div><span><span>        </span></span><span>long long cur = 0;</span></div></div><div><div><div>247</div></div><div><span><span>        </span></span><span>int cur_digits = 0;</span></div></div><div><div><div>248</div></div><div><span><span>        </span></span><span>for (int i = 0; i &lt; (int)a.size(); i++) {</span></div></div><div><div><div>249</div></div><div><span><span>            </span></span><span>cur += a[i] * p[cur_digits];</span></div></div><div><div><div>250</div></div><div><span><span>            </span></span><span>cur_digits += old_digits;</span></div></div><div><div><div>251</div></div><div><span><span>            </span></span><span>while (cur_digits &gt;= new_digits) {</span></div></div><div><div><div>252</div></div><div><span><span>                </span></span><span>res.push_back((int)(cur % p[new_digits]));</span></div></div><div><div><div>253</div></div><div><span><span>                </span></span><span>cur /= p[new_digits];</span></div></div><div><div><div>254</div></div><div><span><span>                </span></span><span>cur_digits -= new_digits;</span></div></div><div><div><div>255</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>256</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>257</div></div><div><span><span>        </span></span><span>res.push_back((int)cur);</span></div></div><div><div><div>258</div></div><div><span><span>        </span></span><span>while (!res.empty() &amp;&amp; !res.back()) res.pop_back();</span></div></div><div><div><div>259</div></div><div><span><span>        </span></span><span>return res;</span></div></div><div><div><div>260</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>261</div></div><div><span><span>    </span></span><span>static vll karatsubaMultiply(const vll &amp;a, const vll &amp;b) {</span></div></div><div><div><div>262</div></div><div><span><span>        </span></span><span>int n = a.size();</span></div></div><div><div><div>263</div></div><div><span><span>        </span></span><span>vll res(n + n);</span></div></div><div><div><div>264</div></div><div><span><span>        </span></span><span>if (n &lt;= 32) {</span></div></div><div><div><div>265</div></div><div><span><span>            </span></span><span>for (int i = 0; i &lt; n; i++) {</span></div></div><div><div><div>266</div></div><div><span><span>                </span></span><span>for (int j = 0; j &lt; n; j++) {</span></div></div><div><div><div>267</div></div><div><span><span>                    </span></span><span>res[i + j] += a[i] * b[j];</span></div></div><div><div><div>268</div></div><div><span><span>                </span></span><span>}</span></div></div><div><div><div>269</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>270</div></div><div><span><span>            </span></span><span>return res;</span></div></div><div><div><div>271</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>272</div></div><div>
</div></div><div><div><div>273</div></div><div><span><span>        </span></span><span>int k = n &gt;&gt; 1;</span></div></div><div><div><div>274</div></div><div><span><span>        </span></span><span>vll a1(a.begin(), a.begin() + k);</span></div></div><div><div><div>275</div></div><div><span><span>        </span></span><span>vll a2(a.begin() + k, a.end());</span></div></div><div><div><div>276</div></div><div><span><span>        </span></span><span>vll b1(b.begin(), b.begin() + k);</span></div></div><div><div><div>277</div></div><div><span><span>        </span></span><span>vll b2(b.begin() + k, b.end());</span></div></div><div><div><div>278</div></div><div>
</div></div><div><div><div>279</div></div><div><span><span>        </span></span><span>vll a1b1 = karatsubaMultiply(a1, b1);</span></div></div><div><div><div>280</div></div><div><span><span>        </span></span><span>vll a2b2 = karatsubaMultiply(a2, b2);</span></div></div><div><div><div>281</div></div><div>
</div></div><div><div><div>282</div></div><div><span><span>        </span></span><span>for (int i = 0; i &lt; k; i++) a2[i] += a1[i];</span></div></div><div><div><div>283</div></div><div><span><span>        </span></span><span>for (int i = 0; i &lt; k; i++) b2[i] += b1[i];</span></div></div><div><div><div>284</div></div><div>
</div></div><div><div><div>285</div></div><div><span><span>        </span></span><span>vll r = karatsubaMultiply(a2, b2);</span></div></div><div><div><div>286</div></div><div><span><span>        </span></span><span>for (int i = 0; i &lt; (int)a1b1.size(); i++) r[i] -= a1b1[i];</span></div></div><div><div><div>287</div></div><div><span><span>        </span></span><span>for (int i = 0; i &lt; (int)a2b2.size(); i++) r[i] -= a2b2[i];</span></div></div><div><div><div>288</div></div><div>
</div></div><div><div><div>289</div></div><div><span><span>        </span></span><span>for (int i = 0; i &lt; (int)r.size(); i++) res[i + k] += r[i];</span></div></div><div><div><div>290</div></div><div><span><span>        </span></span><span>for (int i = 0; i &lt; (int)a1b1.size(); i++) res[i] += a1b1[i];</span></div></div><div><div><div>291</div></div><div><span><span>        </span></span><span>for (int i = 0; i &lt; (int)a2b2.size(); i++) res[i + n] += a2b2[i];</span></div></div><div><div><div>292</div></div><div><span><span>        </span></span><span>return res;</span></div></div><div><div><div>293</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>294</div></div><div>
</div></div><div><div><div>295</div></div><div><span><span>    </span></span><span>void operator*=(const bigint &amp;v) {</span></div></div><div><div><div>296</div></div><div><span><span>        </span></span><span>*this = *this * v;</span></div></div><div><div><div>297</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>298</div></div><div><span><span>    </span></span><span>bigint operator/(const bigint &amp;v) const {</span></div></div><div><div><div>299</div></div><div><span><span>        </span></span><span>return divmod(*this, v).first;</span></div></div><div><div><div>300</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>301</div></div><div><span><span>    </span></span><span>void operator/=(const bigint &amp;v) {</span></div></div><div><div><div>302</div></div><div><span><span>        </span></span><span>*this = *this / v;</span></div></div><div><div><div>303</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>304</div></div><div><span><span>    </span></span><span>bigint operator%(const bigint &amp;v) const {</span></div></div><div><div><div>305</div></div><div><span><span>        </span></span><span>return divmod(*this, v).second;</span></div></div><div><div><div>306</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>307</div></div><div><span><span>    </span></span><span>void operator%=(const bigint &amp;v) {</span></div></div><div><div><div>308</div></div><div><span><span>        </span></span><span>*this = *this % v;</span></div></div><div><div><div>309</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>310</div></div><div><span>};</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><ol>
<li>template</li>
</ol><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>template&lt;class T&gt; struct Frac {</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>T x, y;</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>Frac() : Frac(0, 1) {}</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>Frac(T x_) : Frac(x_, 1) {}</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>Frac(T x_, T y_) : x(x_), y(y_) {</span></div></div><div><div><div>6</div></div><div><span><span>        </span></span><span>if (y &lt; 0) {</span></div></div><div><div><div>7</div></div><div><span><span>            </span></span><span>y = -y;</span></div></div><div><div><div>8</div></div><div><span><span>            </span></span><span>x = -x;</span></div></div><div><div><div>9</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>11</div></div><div>
</div></div><div><div><div>12</div></div><div><span><span>    </span></span><span>constexpr double val() const {</span></div></div><div><div><div>13</div></div><div><span><span>        </span></span><span>return 1. * x / y;</span></div></div><div><div><div>14</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>15</div></div><div><span><span>    </span></span><span>constexpr Frac norm() const { // 调整符号、转化为最简形式</span></div></div><div><div><div>16</div></div><div><span><span>        </span></span><span>T p = gcd(x, y);</span></div></div><div><div><div>17</div></div><div><span><span>        </span></span><span>return {x / p, y / p};</span></div></div><div><div><div>18</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>19</div></div><div><span><span>    </span></span><span>friend constexpr auto &amp;operator&lt;&lt;(ostream &amp;o, const Frac &amp;j) {</span></div></div><div><div><div>20</div></div><div><span><span>        </span></span><span>T p = gcd(j.x, j.y);</span></div></div><div><div><div>21</div></div><div><span><span>        </span></span><span>if (j.y == p) {</span></div></div><div><div><div>22</div></div><div><span><span>            </span></span><span>return o &lt;&lt; j.x / p;</span></div></div><div><div><div>23</div></div><div><span><span>        </span></span><span>} else {</span></div></div><div><div><div>24</div></div><div><span><span>            </span></span><span>return o &lt;&lt; j.x / p &lt;&lt; "/" &lt;&lt; j.y / p;</span></div></div><div><div><div>25</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>26</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>27</div></div><div><span><span>    </span></span><span>constexpr Frac &amp;operator/=(const Frac &amp;i) {</span></div></div><div><div><div>28</div></div><div><span><span>        </span></span><span>x *= i.y;</span></div></div><div><div><div>29</div></div><div><span><span>        </span></span><span>y *= i.x;</span></div></div><div><div><div>30</div></div><div><span><span>        </span></span><span>if (y &lt; 0) {</span></div></div><div><div><div>31</div></div><div><span><span>            </span></span><span>x = -x;</span></div></div><div><div><div>32</div></div><div><span><span>            </span></span><span>y = -y;</span></div></div><div><div><div>33</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>34</div></div><div><span><span>        </span></span><span>return *this;</span></div></div><div><div><div>35</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>36</div></div><div><span><span>    </span></span><span>constexpr Frac &amp;operator+=(const Frac &amp;i) {</span></div></div><div><div><div>37</div></div><div><span><span>        </span></span><span>x = x * i.y + y * i.x;</span></div></div><div><div><div>38</div></div><div><span><span>        </span></span><span>y *= i.y;</span></div></div><div><div><div>39</div></div><div><span><span>        </span></span><span>return *this;</span></div></div><div><div><div>40</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>41</div></div><div><span><span>    </span></span><span>constexpr Frac &amp;operator-=(const Frac &amp;i) {</span></div></div><div><div><div>42</div></div><div><span><span>        </span></span><span>x = x * i.y - y * i.x;</span></div></div><div><div><div>43</div></div><div><span><span>        </span></span><span>y *= i.y;</span></div></div><div><div><div>44</div></div><div><span><span>        </span></span><span>return *this;</span></div></div><div><div><div>45</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>46</div></div><div><span><span>    </span></span><span>constexpr Frac &amp;operator*=(const Frac &amp;i) {</span></div></div><div><div><div>47</div></div><div><span><span>        </span></span><span>x *= i.x;</span></div></div><div><div><div>48</div></div><div><span><span>        </span></span><span>y *= i.y;</span></div></div><div><div><div>49</div></div><div><span><span>        </span></span><span>return *this;</span></div></div><div><div><div>50</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>51</div></div><div><span><span>    </span></span><span>friend constexpr Frac operator+(const Frac i, const Frac j) { return i += j; }</span></div></div><div><div><div>52</div></div><div><span><span>    </span></span><span>friend constexpr Frac operator-(const Frac i, const Frac j) { return i -= j; }</span></div></div><div><div><div>53</div></div><div><span><span>    </span></span><span>friend constexpr Frac operator*(const Frac i, const Frac j) { return i *= j; }</span></div></div><div><div><div>54</div></div><div><span><span>    </span></span><span>friend constexpr Frac operator/(const Frac i, const Frac j) { return i /= j; }</span></div></div><div><div><div>55</div></div><div><span><span>    </span></span><span>friend constexpr Frac operator-(const Frac i) { return Frac(-i.x, i.y); }</span></div></div><div><div><div>56</div></div><div><span><span>    </span></span><span>friend constexpr bool operator&lt;(const Frac i, const Frac j) { return i.x * j.y &lt; i.y * j.x; }</span></div></div><div><div><div>57</div></div><div><span><span>    </span></span><span>friend constexpr bool operator&gt;(const Frac i, const Frac j) { return i.x * j.y &gt; i.y * j.x; }</span></div></div><div><div><div>58</div></div><div><span><span>    </span></span><span>friend constexpr bool operator==(const Frac i, const Frac j) { return i.x * j.y == i.y * j.x; }</span></div></div><div><div><div>59</div></div><div><span><span>    </span></span><span>friend constexpr bool operator!=(const Frac i, const Frac j) { return i.x * j.y != i.y * j.x; }</span></div></div><div><div><div>60</div></div><div><span>};</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><ol>
<li>代码片段</li>
</ol><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>int mul(int a, int b, int m) {</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>int r = a * b - m * (int)(1.L / m * a * b);</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>return r - m * (r &gt;= m) + m * (r &lt; 0);</span></div></div><div><div><div>4</div></div><div><span>}</span></div></div><div><div><div>5</div></div><div><span>int mypow(int a, int b, int m) {</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>int res = 1 % m;</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>for (; b; b &gt;&gt;= 1, a = mul(a, a, m)) {</span></div></div><div><div><div>8</div></div><div><span><span>        </span></span><span>if (b &amp; 1) {</span></div></div><div><div><div>9</div></div><div><span><span>            </span></span><span>res = mul(res, a, m);</span></div></div><div><div><div>10</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>11</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>12</div></div><div><span><span>    </span></span><span>return res;</span></div></div><div><div><div>13</div></div><div><span>}</span></div></div><div><div><div>14</div></div><div>
</div></div><div><div><div>15</div></div><div><span>int B[] = {2, 3, 5, 7, 11, 13, 17, 19, 23};</span></div></div><div><div><div>16</div></div><div><span>bool MR(int n) {</span></div></div><div><div><div>17</div></div><div><span><span>    </span></span><span>if (n &lt;= 1) return 0;</span></div></div><div><div><div>18</div></div><div><span><span>    </span></span><span>for (int p : B) {</span></div></div><div><div><div>19</div></div><div><span><span>        </span></span><span>if (n == p) return 1;</span></div></div><div><div><div>20</div></div><div><span><span>        </span></span><span>if (n % p == 0) return 0;</span></div></div><div><div><div>21</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>22</div></div><div><span><span>    </span></span><span>int m = (n - 1) &gt;&gt; __builtin_ctz(n - 1);</span></div></div><div><div><div>23</div></div><div><span><span>    </span></span><span>for (int p : B) {</span></div></div><div><div><div>24</div></div><div><span><span>        </span></span><span>int t = m, a = mypow(p, m, n);</span></div></div><div><div><div>25</div></div><div><span><span>        </span></span><span>while (t != n - 1 &amp;&amp; a != 1 &amp;&amp; a != n - 1) {</span></div></div><div><div><div>26</div></div><div><span><span>            </span></span><span>a = mul(a, a, n);</span></div></div><div><div><div>27</div></div><div><span><span>            </span></span><span>t *= 2;</span></div></div><div><div><div>28</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>29</div></div><div><span><span>        </span></span><span>if (a != n - 1 &amp;&amp; t % 2 == 0) return 0;</span></div></div><div><div><div>30</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>31</div></div><div><span><span>    </span></span><span>return 1;</span></div></div><div><div><div>32</div></div><div><span>}</span></div></div><div><div><div>33</div></div><div><span>int PR(int n) {</span></div></div><div><div><div>34</div></div><div><span><span>    </span></span><span>for (int p : B) {</span></div></div><div><div><div>35</div></div><div><span><span>        </span></span><span>if (n % p == 0) return p;</span></div></div><div><div><div>36</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>37</div></div><div><span><span>    </span></span><span>auto f = [&amp;](int x) -&gt; int {</span></div></div><div><div><div>38</div></div><div><span><span>        </span></span><span>x = mul(x, x, n) + 1;</span></div></div><div><div><div>39</div></div><div><span><span>        </span></span><span>return x &gt;= n ? x - n : x;</span></div></div><div><div><div>40</div></div><div><span><span>    </span></span><span>};</span></div></div><div><div><div>41</div></div><div><span><span>    </span></span><span>int x = 0, y = 0, tot = 0, p = 1, q, g;</span></div></div><div><div><div>42</div></div><div><span><span>    </span></span><span>for (int i = 0; (i &amp; 255) || (g = gcd(p, n)) == 1; i++, x = f(x), y = f(f(y))) {</span></div></div><div><div><div>43</div></div><div><span><span>        </span></span><span>if (x == y) {</span></div></div><div><div><div>44</div></div><div><span><span>            </span></span><span>x = tot++;</span></div></div><div><div><div>45</div></div><div><span><span>            </span></span><span>y = f(x);</span></div></div><div><div><div>46</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>47</div></div><div><span><span>        </span></span><span>q = mul(p, abs(x - y), n);</span></div></div><div><div><div>48</div></div><div><span><span>        </span></span><span>if (q) p = q;</span></div></div><div><div><div>49</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>50</div></div><div><span><span>    </span></span><span>return g;</span></div></div><div><div><div>51</div></div><div><span>}</span></div></div><div><div><div>52</div></div><div><span>vector&lt;int&gt; fac(int n) {</span></div></div><div><div><div>53</div></div><div><span><span>    </span></span><span>#define pb emplace_back</span></div></div><div><div><div>54</div></div><div><span><span>    </span></span><span>if (n == 1) return {};</span></div></div><div><div><div>55</div></div><div><span><span>    </span></span><span>if (MR(n)) return {n};</span></div></div><div><div><div>56</div></div><div><span><span>    </span></span><span>int d = PR(n);</span></div></div><div><div><div>57</div></div><div><span><span>    </span></span><span>auto v1 = fac(d), v2 = fac(n / d);</span></div></div><div><div><div>58</div></div><div><span><span>    </span></span><span>auto i1 = v1.begin(), i2 = v2.begin();</span></div></div><div><div><div>59</div></div><div><span><span>    </span></span><span>vector&lt;int&gt; ans;</span></div></div><div><div><div>60</div></div><div><span><span>    </span></span><span>while (i1 != v1.end() || i2 != v2.end()) {</span></div></div><div><div><div>61</div></div><div><span><span>        </span></span><span>if (i1 == v1.end()) {</span></div></div><div><div><div>62</div></div><div><span><span>            </span></span><span>ans.pb(*i2++);</span></div></div><div><div><div>63</div></div><div><span><span>        </span></span><span>} else if (i2 == v2.end()) {</span></div></div><div><div><div>64</div></div><div><span><span>            </span></span><span>ans.pb(*i1++);</span></div></div><div><div><div>65</div></div><div><span><span>        </span></span><span>} else {</span></div></div><div><div><div>66</div></div><div><span><span>            </span></span><span>if (*i1 &lt; *i2) {</span></div></div><div><div><div>67</div></div><div><span><span>                </span></span><span>ans.pb(*i1++);</span></div></div><div><div><div>68</div></div><div><span><span>            </span></span><span>} else {</span></div></div><div><div><div>69</div></div><div><span><span>                </span></span><span>ans.pb(*i2++);</span></div></div><div><div><div>70</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>71</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>72</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>73</div></div><div><span><span>    </span></span><span>return ans;</span></div></div><div><div><div>74</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>上述五份代码，如果有良好的C++代码阅读基础，很容易看得懂，可移植性也非常高，分别为迪杰斯特拉，平面几何，高精度运算，分数四则运算，素数检测。</p><p>每一份代码都保证了该种算法的大体最优时间复杂度，而不是未经优化的基础算法模版。</p><p>综上，一份良好的算法模版应具有如下几点要求：</p><ul>
<li>用lambda/namespace/struct/template/代码片段封装，大体为struct&gt;template&gt;namespace&gt;lambda&gt;代码片段封装，实际也需要根据算法的类型，长度，难易程度等来相应调整使用的方法。</li>
<li>具有优化后的大体最优时间复杂度</li>
<li>绝大多数情况是一段不可运行的代码，是算法模板，而不是可运行代码</li>
<li>保证可移植性，可读性等，例如Segt的模板不能只解决某一个特定区间问题，应该同时解决区间修改，区间询问等多种相关问题</li>
</ul></section>]]></content>
    <author><name>Zowely</name></author>
    <category term="算法竞赛" />
    <category term="算法竞赛" />
    <category term="代码规范" />
    <category term="C++" />
  </entry>
</feed>