<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>OpticHong&#39;s Blog</title>
  
  
  <link href="https://gme-hong.github.io/atom.xml" rel="self"/>
  
  <link href="https://gme-hong.github.io/"/>
  <updated>2025-09-27T13:13:39.715Z</updated>
  <id>https://gme-hong.github.io/</id>
  
  <author>
    <name>OpticHong</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>Graduate Notes</title>
    <link href="https://gme-hong.github.io/2025/07/22/Postgraduate-Notes/"/>
    <id>https://gme-hong.github.io/2025/07/22/Postgraduate-Notes/</id>
    <published>2025-07-22T09:59:41.000Z</published>
    <updated>2025-09-27T13:13:39.715Z</updated>
    
    <content type="html"><![CDATA[<p>All the notes are based on Notion and cannot be accessed without magic.</p><p><a class="link" href="https://gme-hong.notion.site/Notes-for-Papers-245046db1ae2803ea96fd86657f8be27">Notes for Papers | Share some excellent papers that I read during my graduate studies <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a></p><table><thead><tr><th>No.</th><th>Content</th><th>Date</th></tr></thead><tbody><tr><td>1</td><td><a class="link" href="https://gme-hong.notion.site/238046db1ae280dea64be8e16ac245d4?pvs=74">分布式训练 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a></td><td>07.25.2025</td></tr><tr><td>2</td><td><a class="link" href="https://gme-hong.notion.site/nanoVLM-27a046db1ae28024a453c2f29b145efa">nanoVLM <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a></td><td>09.27.2025</td></tr><tr><td></td><td></td><td></td></tr></tbody></table>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;All the notes are based on Notion and cannot be accessed without magic.&lt;/p&gt;
&lt;p&gt;&lt;a class=&quot;link&quot; href=&quot;https://gme-hong.notion.site/Notes-f</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>机式指南</title>
    <link href="https://gme-hong.github.io/2025/03/01/%E6%9C%BA%E5%BC%8F%E6%8C%87%E5%8D%97/"/>
    <id>https://gme-hong.github.io/2025/03/01/%E6%9C%BA%E5%BC%8F%E6%8C%87%E5%8D%97/</id>
    <published>2025-03-01T05:27:51.000Z</published>
    <updated>2025-03-01T05:53:38.095Z</updated>
    
    <content type="html"><![CDATA[<p>机式重点永远是<strong>树结构</strong>、<strong>图结构</strong>以及<strong>动态规划</strong>！！！</p><h2 id="夏令营经典题型"><a href="#夏令营经典题型" class="headerlink" title="夏令营经典题型"></a>夏令营经典题型</h2><h3 id="结构体排序问题"><a href="#结构体排序问题" class="headerlink" title="结构体排序问题"></a>结构体排序问题</h3><p>这类问题往往都是一个模板，即待排序的数据都是结构体，排序规则涉及到结构体中的多个元素。</p><p>其中，<strong>结构体的构造部分尽可能使用struct而不是用class</strong>（这是因为，算法题中一般都只是涉及到“组织”的问题，因此完全不需要使用对“封装、继承”等相对完善的class）。</p><p>此外，排序规则需要构建一个返回值为bool，传递两个结构体元素的比较函数<code>bool cmp(Elem e1,Elem e2)</code>，具体函数内的返回规则需要根据题目所给的判断逻辑进行编写。</p><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;bits/stdc++.h&gt;</span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line">cosnt <span class="type">int</span> N =<span class="number">1e5</span>+<span class="number">10</span>;</span><br><span class="line"><span class="keyword">struct</span> <span class="title class_">Elem</span>&#123;</span><br><span class="line">string str;<span class="comment">// 如果不支持c++，则需要改成字符数组 char str[30];此外进行字符串比较时，需要利用strcmp(s1,s2)进行比较。</span></span><br><span class="line">  <span class="type">int</span> age;</span><br><span class="line">  <span class="type">double</span> socre;</span><br><span class="line">&#125;Stu[N];</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">bool</span> <span class="title">cmp</span><span class="params">(Elem e1, Elem e2)</span></span>&#123;</span><br><span class="line">  <span class="keyword">if</span>(e1.socre != e2.socre) <span class="keyword">return</span> e1.socre &lt; e2.socre;<span class="comment">//这是升序排列</span></span><br><span class="line">  <span class="keyword">else</span> <span class="keyword">if</span>(e1.str != e2.str) <span class="keyword">return</span> e1.str &lt; e2.str;</span><br><span class="line">  <span class="keyword">else</span> <span class="keyword">return</span> e1.age &lt; e2.age;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span>&#123;</span><br><span class="line">  <span class="comment">//首先是输入逻辑部分，对结构体进行填充。</span></span><br><span class="line">  <span class="type">int</span> n;</span><br><span class="line">  cin&gt;&gt;n;</span><br><span class="line">  <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">0</span>;i&lt;n;i++) cin&gt;&gt;Stu[i].str&gt;&gt;Stu[i].age&gt;&gt;Stu[i].socre;</span><br><span class="line">  <span class="comment">//对结构体中的数据，利用内置函数sort进行排序。</span></span><br><span class="line">  <span class="built_in">sort</span>(Stu,Stu+n,cmp);</span><br><span class="line">  <span class="comment">//最后根据题目所给要求进行输出。</span></span><br><span class="line">  cout&lt;&lt;Stu[<span class="number">0</span>].str&lt;&lt;Stu[<span class="number">0</span>].age&lt;&lt;Stu[<span class="number">0</span>].score;</span><br><span class="line">  <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><div class="highlight-container" data-rel="Python"><figure class="iseeu highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 定义结构体类</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Elem</span>:</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, name, age, score</span>):</span><br><span class="line">        self.name = name</span><br><span class="line">        self.age = age</span><br><span class="line">        self.score = score</span><br><span class="line"></span><br><span class="line"><span class="comment"># 自定义比较函数</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">cmp</span>(<span class="params">elem</span>):</span><br><span class="line">    <span class="comment"># 按照分数升序排列，如果分数相同，则按照名字升序排列，再按照年龄升序排列</span></span><br><span class="line">    <span class="keyword">return</span> (elem.score, elem.name, elem.age)</span><br><span class="line"></span><br><span class="line"><span class="comment">###</span></span><br><span class="line">在 Python 中，sort 和 <span class="built_in">sorted</span> 函数支持通过 key 参数指定排序规则，而 key 参数需要传入一个函数，该函数会对每个元素进行处理，返回一个可以比较的值（如数字或元组）。根据这个返回值，Python 会对所有元素进行排序。</span><br><span class="line"><span class="comment">###</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">main</span>():</span><br><span class="line">    <span class="comment"># 输入数据</span></span><br><span class="line">    n = <span class="built_in">int</span>(<span class="built_in">input</span>())</span><br><span class="line">    students = []</span><br><span class="line">    <span class="keyword">for</span> _ <span class="keyword">in</span> <span class="built_in">range</span>(n):</span><br><span class="line">        name, age, score = <span class="built_in">input</span>().split()  </span><br><span class="line">        age = <span class="built_in">int</span>(age)</span><br><span class="line">        score = <span class="built_in">float</span>(score)</span><br><span class="line">        students.append(Elem(name, age, score))</span><br><span class="line">    </span><br><span class="line">    <span class="comment"># 对数据进行排序</span></span><br><span class="line">    students.sort(key=cmp)  <span class="comment">#默认是整体升序排列，如果需要整体降序，参数reverse可以置为True。如果排序中的某一非数值属性需要降序，则将字符串进行反转[::-1]，数值属性需要降序直接加负号即可。如果需要按照字符串第一个字符进行排序可以使用ord()函数。</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment"># 输出第一个学生的信息</span></span><br><span class="line">    <span class="built_in">print</span>(students[<span class="number">0</span>].name, students[<span class="number">0</span>].age, students[<span class="number">0</span>].score)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&quot;__main__&quot;</span>:</span><br><span class="line">    main()</span><br></pre></td></tr></table></figure></div><h4 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h4><p>这种题目的变体，多集中在模板中最关键的两步，即<strong>是否待排序的元素需要构建结构体</strong>，以及<strong>需要构建多个排序规则</strong>。</p><ol><li><p><a class="link" href="https://www.nowcoder.com/questionTerminal/dfeed0e0e4624814b122265e859783b2">是否需要构建结构体？ <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a></p><p>当涉及到待排序元素有多个属性时，这个时候毫无疑问构建结构体是一种不错的结果方案；</p><p>但是，待排序元素就只用单一属性时，这个时候也可能需要构建结构体。此时，我们需要明白构建结构体的目的是为了更方便的排序，是因为但从其自身的属性上已经不好进行快速比较时，才将比较的逻辑单独抽离出来。<code>比如说，字符串的排序，但排序的逻辑不再是字符串的字典序，而是其字符串的长度。</code></p></li><li><p><a class="link" href="https://www.nowcoder.com/questionTerminal/bf3ec474bb7d410dbb9d5bbcd07a93e5">构建多个排序规则。 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a></p><p>多个和单个排序规则本质上没有太大区别。注意到题目中如果有多种情况需要利用到不同的排序规则时，在代码中需要使用<code>case</code>进行实现。</p></li></ol><h3 id="日期类问题"><a href="#日期类问题" class="headerlink" title="日期类问题"></a>日期类问题</h3><p>这类问题往往属于模拟的大类，其最明显的特点就是没有难以理解的算法，更多的是进行模拟。</p><p>其中涉及到的知识点有：<strong>闰年判断</strong>、1年1月1日为星期一</p><p><strong>额外需要注意的就是这几个周期和月份单词的拼写</strong>。</p><h4 id="间隔时间"><a href="#间隔时间" class="headerlink" title="间隔时间"></a>间隔时间</h4><div class="highlight-container" data-rel="Cpp"><figure class="iseeu highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;bits/stdc++.h&gt;</span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"><span class="type">int</span> date[<span class="number">2</span>][<span class="number">13</span>]=&#123;&#123;<span class="number">0</span>,<span class="number">31</span>,<span class="number">28</span>,<span class="number">31</span>,<span class="number">30</span>,<span class="number">31</span>,<span class="number">30</span>,<span class="number">31</span>,<span class="number">31</span>,<span class="number">30</span>,<span class="number">31</span>,<span class="number">30</span>,<span class="number">31</span>&#125;,&#123;<span class="number">0</span>,<span class="number">31</span>,<span class="number">29</span>,<span class="number">31</span>,<span class="number">30</span>,<span class="number">31</span>,<span class="number">30</span>,<span class="number">31</span>,<span class="number">31</span>,<span class="number">30</span>,<span class="number">31</span>,<span class="number">30</span>,<span class="number">31</span>&#125;&#125;; <span class="comment">//首先定义每月天数，将闰月年区分开来，方便后面判断完闰年后的月份天数确定。</span></span><br><span class="line"><span class="function"><span class="type">bool</span> <span class="title">isLeap</span><span class="params">(<span class="type">int</span> year)</span></span>&#123; <span class="comment">//判断闰年的函数，被400整除或者被4整除但不被100整除</span></span><br><span class="line">  <span class="keyword">return</span> ((year%<span class="number">4</span>==<span class="number">0</span>)&amp;&amp;(year%<span class="number">100</span>!=<span class="number">0</span>)) || (year%<span class="number">400</span>==<span class="number">0</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span>&#123;</span><br><span class="line">  <span class="type">int</span> time1,time2,y1,y2,m1,m2,d1,d2;</span><br><span class="line">  <span class="keyword">while</span>(cin&gt;&gt;time1&gt;&gt;time2)&#123;</span><br><span class="line">    <span class="keyword">if</span>(time1&gt;time2)&#123;<span class="type">int</span> tmp=time2;time2=time1;time1=time2;&#125;</span><br><span class="line">    <span class="comment">//需要前面的数字就用除，需要后面的数字就用模</span></span><br><span class="line">    y1=time1/<span class="number">10000</span>,m1=time1%<span class="number">10000</span>/<span class="number">100</span>,d1=time1%<span class="number">100</span>;</span><br><span class="line">    y2=time2/<span class="number">10000</span>,m2=time2%<span class="number">10000</span>/<span class="number">100</span>,d2=time2%<span class="number">100</span>;</span><br><span class="line">    <span class="type">int</span> sum=<span class="number">1</span>;</span><br><span class="line">    <span class="keyword">while</span>(y1&lt;y2 || m1&lt;m2 || d1&lt;d2)&#123; <span class="comment">//使用天数累加的方式进行模拟</span></span><br><span class="line">      d1++;</span><br><span class="line">      <span class="keyword">if</span>(d1==date[<span class="built_in">isLeap</span>(y1)][m1]+<span class="number">1</span>)&#123;m1++;d1=<span class="number">1</span>;&#125;</span><br><span class="line">      <span class="keyword">if</span>(m1==<span class="number">13</span>)&#123;y1++;m1=<span class="number">1</span>;&#125;</span><br><span class="line">      sum++;</span><br><span class="line">    &#125;</span><br><span class="line">    cout&lt;&lt;sum&lt;&lt;endl;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><div class="highlight-container" data-rel="Py"><figure class="iseeu highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line">date=[</span><br><span class="line">    [<span class="number">0</span>,<span class="number">31</span>,<span class="number">28</span>,<span class="number">31</span>,<span class="number">30</span>,<span class="number">31</span>,<span class="number">30</span>,<span class="number">31</span>,<span class="number">31</span>,<span class="number">30</span>,<span class="number">31</span>,<span class="number">30</span>,<span class="number">31</span>],</span><br><span class="line">    [<span class="number">0</span>,<span class="number">31</span>,<span class="number">29</span>,<span class="number">31</span>,<span class="number">30</span>,<span class="number">31</span>,<span class="number">30</span>,<span class="number">31</span>,<span class="number">31</span>,<span class="number">30</span>,<span class="number">31</span>,<span class="number">30</span>,<span class="number">31</span>]</span><br><span class="line">]</span><br><span class="line"><span class="keyword">def</span> <span class="title function_">is_leap</span>(<span class="params">year</span>):</span><br><span class="line">    <span class="keyword">return</span> (year%<span class="number">4</span>==<span class="number">0</span> <span class="keyword">and</span> year%<span class="number">100</span>!=<span class="number">0</span>) <span class="keyword">or</span> year%<span class="number">400</span>==<span class="number">0</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">main</span>():</span><br><span class="line">    sum_date=<span class="number">1</span></span><br><span class="line">    <span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line">        <span class="keyword">try</span>:</span><br><span class="line">            time1,time2=<span class="built_in">map</span>(<span class="built_in">int</span>,<span class="built_in">input</span>().split())</span><br><span class="line">            <span class="keyword">if</span> time1&gt;time2:</span><br><span class="line">                time1,time2=time2,time1</span><br><span class="line">            year1,month1,day1=time1//<span class="number">10000</span>,time1%<span class="number">10000</span>//<span class="number">100</span>,time1%<span class="number">100</span></span><br><span class="line">            year2,month2,day2=time2//<span class="number">10000</span>,time2%<span class="number">10000</span>//<span class="number">100</span>,time2%<span class="number">100</span></span><br><span class="line">            <span class="keyword">while</span> year1&lt;year2 <span class="keyword">or</span> month1&lt;month2 <span class="keyword">or</span> day1&lt;day2:</span><br><span class="line">                day1+=<span class="number">1</span></span><br><span class="line">                <span class="keyword">if</span> day1==date[is_leap(year1)][month1]+<span class="number">1</span>:</span><br><span class="line">                    day1=<span class="number">1</span></span><br><span class="line">                    month1+=<span class="number">1</span></span><br><span class="line">                <span class="keyword">if</span> month1==<span class="number">13</span>:</span><br><span class="line">                    month1=<span class="number">1</span></span><br><span class="line">                    year1+=<span class="number">1</span></span><br><span class="line">                sum_date+=<span class="number">1</span></span><br><span class="line">            <span class="built_in">print</span>(sum_date)</span><br><span class="line">        <span class="keyword">except</span>:</span><br><span class="line">            <span class="keyword">break</span></span><br><span class="line">                </span><br><span class="line">main() </span><br></pre></td></tr></table></figure></div><h4 id="判断星期"><a href="#判断星期" class="headerlink" title="判断星期"></a>判断星期</h4><div class="highlight-container" data-rel="Cpp"><figure class="iseeu highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;bits/stdc++.h&gt;</span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"><span class="function"><span class="type">bool</span> <span class="title">isleap</span><span class="params">(<span class="type">int</span> y)</span> </span>&#123; <span class="keyword">return</span> (y % <span class="number">4</span> == <span class="number">0</span> &amp;&amp; y % <span class="number">100</span> != <span class="number">0</span>) || y % <span class="number">400</span> == <span class="number">0</span>; &#125;</span><br><span class="line"><span class="type">int</span> m[<span class="number">2</span>][<span class="number">13</span>] = &#123;&#123;<span class="number">0</span>, <span class="number">31</span>, <span class="number">28</span>, <span class="number">31</span>, <span class="number">30</span>, <span class="number">31</span>, <span class="number">30</span>, <span class="number">31</span>, <span class="number">31</span>, <span class="number">30</span>, <span class="number">31</span>, <span class="number">30</span>, <span class="number">31</span>&#125;,</span><br><span class="line">                &#123;<span class="number">0</span>, <span class="number">31</span>, <span class="number">29</span>, <span class="number">31</span>, <span class="number">30</span>, <span class="number">31</span>, <span class="number">30</span>, <span class="number">31</span>, <span class="number">31</span>, <span class="number">30</span>, <span class="number">31</span>, <span class="number">30</span>, <span class="number">31</span>&#125;&#125;;</span><br><span class="line">string week[<span class="number">7</span>] = &#123;<span class="string">&quot;Sunday&quot;</span>,<span class="string">&quot;Monday&quot;</span>,<span class="string">&quot;Tuesday&quot;</span>,<span class="string">&quot;Wednesday&quot;</span>,<span class="string">&quot;Thursday&quot;</span>,<span class="string">&quot;Friday&quot;</span>,<span class="string">&quot;Saturday&quot;</span>&#125;;</span><br><span class="line">string mouth[<span class="number">13</span>] = &#123;<span class="string">&quot;&quot;</span>,<span class="string">&quot;January&quot;</span>,<span class="string">&quot;February&quot;</span>,<span class="string">&quot;March&quot;</span>,<span class="string">&quot;April&quot;</span>,<span class="string">&quot;May&quot;</span>,<span class="string">&quot;June&quot;</span>,<span class="string">&quot;July&quot;</span>,<span class="string">&quot;August&quot;</span>,<span class="string">&quot;September&quot;</span>,<span class="string">&quot;October&quot;</span>,<span class="string">&quot;November&quot;</span>,<span class="string">&quot;December&quot;</span>&#125;;</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  <span class="type">int</span> d2, m2, y2, d1 = <span class="number">1</span>, m1 = <span class="number">1</span>, y1 = <span class="number">1</span>;</span><br><span class="line">  string mm;</span><br><span class="line">  cin &gt;&gt; d2 &gt;&gt; mm &gt;&gt; y2;</span><br><span class="line">  <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i &lt;= <span class="number">12</span>; i++)</span><br><span class="line">    <span class="keyword">if</span> (mm == mouth[i]) &#123;m2 = i;<span class="keyword">break</span>;&#125;</span><br><span class="line">  <span class="type">int</span> sum = <span class="number">1</span>;</span><br><span class="line">  <span class="keyword">while</span> (d1 &lt; d2 || m1 &lt; m2 || y1 &lt; y2) &#123;</span><br><span class="line">    d1++;</span><br><span class="line">    <span class="keyword">if</span> (d1 == m[<span class="built_in">isleap</span>(y1)][m1] + <span class="number">1</span>) &#123;d1 = <span class="number">1</span>;m1++;&#125;</span><br><span class="line">    <span class="keyword">if</span> (m1 == <span class="number">13</span>) &#123;m1 = <span class="number">1</span>;y1++;&#125;</span><br><span class="line">    sum++;</span><br><span class="line">  &#125;</span><br><span class="line">  cout &lt;&lt; week[sum % <span class="number">7</span>] &lt;&lt; endl;</span><br><span class="line">  <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><h2 id="C-C-快速入门"><a href="#C-C-快速入门" class="headerlink" title="C&#x2F;C++快速入门"></a>C&#x2F;C++快速入门</h2><ol><li><p>绝对值在$10^9$范围以内的整数都可以定义为int型。其他情况，使用<code>typedef long long ll;</code>类型，以及unsigned long long (ull)类型。</p></li><li><p><code>const int INF=0x3fffffff;</code></p></li><li><p><code>cout&lt;&lt;fixed&lt;&lt;setw(7)&lt;&lt;setprecision(2)&lt;&lt;x&lt;&lt;endl;</code>设置输出x的宽度为7，其中小数部分占2位。</p></li><li><p>浮点数比较大小：</p><p><code>const double eps=1e-8;</code></p><p>等于：<code>#define equ(a,b) fabs(a-b)&lt;eps</code></p><p>大于：<code>#define more(a,b) (a-b)&gt;eps</code></p><p>小于：<code>#define less(a,b) (a-b)&lt;-eps</code></p><p>大于等于：<code>#define more_eq(a,b) (a-b)&gt;-eps</code></p><p>小于等于：<code>#define less_eq(a,b) (a-b)&lt;eps</code></p></li><li><p>π值：<code>const double pi=acos(-1.0); </code></p></li><li><p>如果两个操作数都是整数类型（例如 <code>int</code>、<code>long</code>、<code>long long</code> 等），则进行的是整数除法，结果将是一个整数，并且会<strong>向下取整</strong>（截断小数部分）。</p></li><li><p>在C语言中，<code>scanf(&quot;%s&quot;, str1)</code>用于从标准输入中读取一个字符串，并将其存储到字符数组<code>str1</code>中。然而，<code>scanf</code>函数<font color="red">在读取字符串后会在缓冲区中留下一个换行符（’\n’）</font>，这可能会导致后续的输入函数（如<code>fgets</code>或另一个<code>scanf</code>）出现问题。为了解决这个问题，可以在<code>scanf</code>之后添加一个<code>getchar()</code>函数调用，<strong>以读取并消耗掉缓冲区中的换行符</strong>。这样可以确保下一个输入函数从清空了缓冲区的状态开始读取。</p></li><li><p>大规模输入输出时，不要用cin和cout，否则时间真的会超限！</p></li><li><p>long long 和int不要混用！</p></li><li><p>mex(arr) 表示数组arr中末出现过的最小非负整数。例如[0,1,2,4]的mex为3。</p><div class="highlight-container" data-rel="Cpp"><figure class="iseeu highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">int</span> <span class="title">mex</span><span class="params">(<span class="type">const</span> vector&lt;<span class="type">int</span>&gt;&amp; arr)</span> </span>&#123;</span><br><span class="line">    <span class="function">unordered_set&lt;<span class="type">int</span>&gt; <span class="title">s</span><span class="params">(arr.begin(), arr.end())</span></span>;</span><br><span class="line">    <span class="type">int</span> mex = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">while</span> (s.<span class="built_in">count</span>(mex)) mex++;</span><br><span class="line">    <span class="keyword">return</span> mex;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div></li><li></li></ol><h2 id="Python快速入门"><a href="#Python快速入门" class="headerlink" title="Python快速入门"></a>Python快速入门</h2><ol><li><p>Python构建二维数组的方法：<code>[[0] * (n + 1) for _ in range(m + 1)]</code>，这样构建的二维数组，在修改任意元素时不会造成其他位置的元素被修改。</p></li><li><p>Python的排序函数：<code>sorted()</code>，可用于对任何可迭代对象（如列表、元组、字符串等）进行排序。<code>sorted()</code>返回排序后的新列表，不会改变原列表。</p> <div class="highlight-container" data-rel="Py"><figure class="iseeu highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">numbers = [<span class="number">5</span>, <span class="number">2</span>, <span class="number">9</span>, <span class="number">1</span>, <span class="number">5</span>, <span class="number">6</span>]</span><br><span class="line">sorted_numbers = <span class="built_in">sorted</span>(numbers)</span><br><span class="line"><span class="built_in">print</span>(sorted_numbers)  <span class="comment"># 输出：[1, 2, 5, 5, 6, 9]</span></span><br><span class="line"><span class="built_in">print</span>(numbers)         <span class="comment"># 输出：[5, 2, 9, 1, 5, 6]（原列表未改变）</span></span><br></pre></td></tr></table></figure></div><p> 此外，<code>list.sort()</code>可以实现就地排序。</p> <div class="highlight-container" data-rel="Py"><figure class="iseeu highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">numbers = [<span class="number">5</span>, <span class="number">2</span>, <span class="number">9</span>, <span class="number">1</span>, <span class="number">5</span>, <span class="number">6</span>]</span><br><span class="line">numbers.sort()</span><br><span class="line"><span class="built_in">print</span>(numbers)  <span class="comment"># 输出：[1, 2, 5, 5, 6, 9]</span></span><br></pre></td></tr></table></figure></div></li><li><p>Python获取子字符串只需要通过切片就行。字符串的拼接和复制也只需通过简单的数学运算符<code>+</code>和<code>*</code>实现。</p></li></ol><hr><h2 id="入门模拟"><a href="#入门模拟" class="headerlink" title="入门模拟"></a>入门模拟</h2><h3 id="日期计算"><a href="#日期计算" class="headerlink" title="日期计算"></a>日期计算</h3><p>有两个日期，求两个日期之间的天数，<strong>如果两个日期是连续的，则规定它们之间的天数为两天</strong>。</p><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"><span class="type">int</span> date[<span class="number">2</span>][<span class="number">13</span>]=&#123;&#123;<span class="number">0</span>,<span class="number">31</span>,<span class="number">28</span>,<span class="number">31</span>,<span class="number">30</span>,<span class="number">31</span>,<span class="number">30</span>,<span class="number">31</span>,<span class="number">31</span>,<span class="number">30</span>,<span class="number">31</span>,<span class="number">30</span>,<span class="number">31</span>&#125;,&#123;<span class="number">0</span>,<span class="number">31</span>,<span class="number">29</span>,<span class="number">31</span>,<span class="number">30</span>,<span class="number">31</span>,<span class="number">30</span>,<span class="number">31</span>,<span class="number">31</span>,<span class="number">30</span>,<span class="number">31</span>,<span class="number">30</span>,<span class="number">31</span>&#125;&#125;;</span><br><span class="line"><span class="function"><span class="type">bool</span> <span class="title">isLeap</span><span class="params">(<span class="type">int</span> year)</span></span>&#123;</span><br><span class="line">  <span class="keyword">return</span> ((year%<span class="number">4</span>==<span class="number">0</span>)&amp;&amp;(year%<span class="number">100</span>!=<span class="number">0</span>)) || (year%<span class="number">400</span>==<span class="number">0</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span>&#123;</span><br><span class="line">  <span class="type">int</span> time1,time2,y1,y2,m1,m2,d1,d2;</span><br><span class="line">  <span class="keyword">while</span>(cin&gt;&gt;time1&gt;&gt;time2)&#123;</span><br><span class="line">    <span class="keyword">if</span>(time1&gt;time2)&#123;<span class="type">int</span> tmp=time2;time2=time1;time1=time2;&#125;</span><br><span class="line">    <span class="comment">//需要前面的数字就用除，需要后面的数字就用模</span></span><br><span class="line">    y1=time1/<span class="number">10000</span>,m1=time1%<span class="number">10000</span>/<span class="number">100</span>,d1=time1%<span class="number">100</span>;</span><br><span class="line">    y2=time2/<span class="number">10000</span>,m2=time2%<span class="number">10000</span>/<span class="number">100</span>,d2=time2%<span class="number">100</span>;</span><br><span class="line">    <span class="type">int</span> sum=<span class="number">1</span>;</span><br><span class="line">    <span class="keyword">while</span>(y1&lt;y2 || m1&lt;m2 || d1&lt;d2)&#123;</span><br><span class="line">      d1++;</span><br><span class="line">      <span class="keyword">if</span>(d1==date[<span class="built_in">isLeap</span>(y1)][m1]+<span class="number">1</span>)&#123;m1++;d1=<span class="number">1</span>;&#125;</span><br><span class="line">      <span class="keyword">if</span>(m1==<span class="number">13</span>)&#123;y1++;m1=<span class="number">1</span>;&#125;</span><br><span class="line">      sum++;</span><br><span class="line">    &#125;</span><br><span class="line">    cout&lt;&lt;sum&lt;&lt;endl;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><p>由日期换算成星期时，也是需要用两日期的差值进行计算的。记1年1月1日为星期一，创建周期数组（注意第一个值为Sunday）：</p><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">string week[<span class="number">7</span>]=&#123;<span class="string">&quot;Sunday&quot;</span>,<span class="string">&quot;Monday&quot;</span>,<span class="string">&quot;Tuesday&quot;</span>,<span class="string">&quot;Wednesday&quot;</span>,<span class="string">&quot;Thursday&quot;</span>,<span class="string">&quot;Friday&quot;</span>,<span class="string">&quot;Saturday&quot;</span>&#125;;</span><br></pre></td></tr></table></figure></div><h3 id="进制转换"><a href="#进制转换" class="headerlink" title="进制转换"></a>进制转换</h3><p>两种非十进制需要借助十进制进行转换。如果涉及到小数，只能用字符串存储，然后根据整数部分<font color="orange">除基取余</font>和根据小数部分<font color="orange">乘基取整</font>完成运算。</p><p><img lazyload src="/images/loading.svg" data-src="https://ec28649.webp.li/20250301134944009.png" alt="image-20240511174603435"></p><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;bits/stdc++.h&gt;</span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"><span class="keyword">typedef</span> <span class="type">long</span> <span class="type">long</span> ll;</span><br><span class="line"><span class="type">char</span> alpha[<span class="number">6</span>]=&#123;<span class="string">&#x27;A&#x27;</span>,<span class="string">&#x27;B&#x27;</span>,<span class="string">&#x27;C&#x27;</span>,<span class="string">&#x27;D&#x27;</span>,<span class="string">&#x27;E&#x27;</span>,<span class="string">&#x27;F&#x27;</span>&#125;;</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="type">int</span> a,b;<span class="type">char</span> n[<span class="number">80</span>];</span><br><span class="line">    <span class="keyword">while</span>(<span class="built_in">scanf</span>(<span class="string">&quot;%d%s%d&quot;</span>,&amp;a,n,&amp;b)!=EOF)&#123;</span><br><span class="line">        ll res_10=<span class="number">0</span>; <span class="type">int</span> len=<span class="built_in">strlen</span>(n);</span><br><span class="line">        <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">0</span>;i&lt;len;i++) res_10 = res_10*a + (n[i]&lt;=<span class="string">&#x27;9&#x27;</span>? n[i]-<span class="string">&#x27;0&#x27;</span>: n[i]&lt;=<span class="string">&#x27;F&#x27;</span>? n[i]-<span class="string">&#x27;A&#x27;</span>+<span class="number">10</span>: n[i]-<span class="string">&#x27;a&#x27;</span> + <span class="number">10</span>);</span><br><span class="line">        <span class="keyword">if</span>(b==<span class="number">10</span>) cout&lt;&lt;res_10&lt;&lt;endl;</span><br><span class="line">        <span class="keyword">else</span>&#123;</span><br><span class="line">            <span class="type">char</span> res_b[<span class="number">80</span>]; <span class="type">int</span> cnt=<span class="number">0</span>;</span><br><span class="line">            <span class="built_in">memset</span>(res_b,<span class="number">0</span>,<span class="built_in">sizeof</span>(res_b));</span><br><span class="line">            <span class="keyword">do</span>&#123;</span><br><span class="line">                res_b[cnt++]= res_10%b &lt;= <span class="number">9</span> ?res_10%b + <span class="string">&#x27;0&#x27;</span>: alpha[res_10%b<span class="number">-10</span>];</span><br><span class="line">                res_10/=b;</span><br><span class="line">            &#125;<span class="keyword">while</span>(res_10);</span><br><span class="line">            <span class="keyword">for</span>(<span class="type">int</span> i=cnt<span class="number">-1</span>;i&gt;=<span class="number">0</span>;i--) cout&lt;&lt;res_b[i];</span><br><span class="line">            cout&lt;&lt;endl;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="built_in">memset</span>(n,<span class="number">0</span>,<span class="built_in">sizeof</span>(n));</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><p><font color="red">当需要转换的是“大数”时，就应该仔细地去分析进制转换的具体实现细节！</font></p><p><img lazyload src="/images/loading.svg" data-src="https://ec28649.webp.li/20250301134959747.png" alt="image-20240512220414356"></p><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;bits/stdc++.h&gt;</span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"><span class="type">char</span> str1[<span class="number">37</span>],str2[<span class="number">37</span>],str3[<span class="number">100010</span>];</span><br><span class="line"><span class="comment">//其中str1用来存储被除数，str2用来存储除数，str3用来存储二进制结果。</span></span><br><span class="line"><span class="function"><span class="type">bool</span> <span class="title">isEmpty</span><span class="params">(<span class="type">char</span> str[])</span></span>&#123;</span><br><span class="line">    <span class="type">int</span> len=<span class="built_in">strlen</span>(str);</span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">0</span>;i&lt;len;i++) <span class="keyword">if</span>(str[i]!=<span class="string">&#x27;0&#x27;</span>) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="keyword">while</span>(<span class="built_in">scanf</span>(<span class="string">&quot;%s&quot;</span>,str1)!=EOF)&#123;</span><br><span class="line">        <span class="type">int</span> idx1=<span class="number">0</span>,idx2=<span class="number">0</span>,len=<span class="built_in">strlen</span>(str1);</span><br><span class="line">        <span class="keyword">do</span>&#123;</span><br><span class="line">            <span class="type">int</span> num=<span class="number">0</span>;</span><br><span class="line">            <span class="keyword">while</span>(idx1&lt;len)&#123;</span><br><span class="line">                num=num*<span class="number">10</span>+str1[idx1]-<span class="string">&#x27;0&#x27;</span>;</span><br><span class="line">                str2[idx1++]=num/<span class="number">2</span> + <span class="string">&#x27;0&#x27;</span>;</span><br><span class="line">                num%=<span class="number">2</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            str3[idx2++]=num + <span class="string">&#x27;0&#x27;</span>;</span><br><span class="line">            <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">0</span>;i&lt;=len;i++) <span class="keyword">if</span>(str2[i])&#123;idx1=i;<span class="keyword">break</span>;&#125;</span><br><span class="line">            <span class="built_in">memset</span>(str1,<span class="number">0</span>,<span class="built_in">sizeof</span>(str1));</span><br><span class="line">            <span class="built_in">strcpy</span>(str1, str2);  <span class="comment">//交换被除数和除数</span></span><br><span class="line">            <span class="built_in">memset</span>(str2,<span class="number">0</span>,<span class="built_in">sizeof</span>(str2));</span><br><span class="line">        &#125;<span class="keyword">while</span>(!<span class="built_in">isEmpty</span>(str1));</span><br><span class="line">        <span class="type">int</span> len1=<span class="built_in">strlen</span>(str3);</span><br><span class="line">        <span class="keyword">for</span>(<span class="type">int</span> i=len1<span class="number">-1</span>;i&gt;=<span class="number">0</span>;i--) cout&lt;&lt;str3[i];</span><br><span class="line">        cout&lt;&lt;endl;</span><br><span class="line">        <span class="built_in">memset</span>(str3,<span class="number">0</span>,<span class="built_in">sizeof</span>(str3));</span><br><span class="line">    &#125;</span><br><span class="line">  <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><blockquote><p>[!caution]</p><p>这段代码对10进制以下的进制都是通用的，只需要把最里层while循环中的两个‘2’换成所需要转换的进制数即可。</p><p>当然对于11-16进制，只需要再添加一个<code>char alpha[6]=&#123;&#39;A&#39;,&#39;B&#39;,&#39;C&#39;,&#39;D&#39;,&#39;E&#39;,&#39;F&#39;&#125;;</code>这样的<code>aplha</code>转换表即可。</p></blockquote><h3 id="字符串转数值"><a href="#字符串转数值" class="headerlink" title="字符串转数值"></a>字符串转数值</h3><p>这是一类经典的题目，不要再单独写一个函数用来<code>str_to_int</code>了</p><p>“123456789”转成对应的数值其实相当方便：</p><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> sum=<span class="number">0</span>,len=<span class="built_in">strlen</span>(str);</span><br><span class="line"><span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">0</span>;i&lt;len;i++)</span><br><span class="line">  sum=sum*<span class="number">10</span>+str[i]-<span class="string">&#x27;0&#x27;</span>;</span><br></pre></td></tr></table></figure></div><p>而数值转字符串就更加方便了：</p><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> num=<span class="number">123456789</span>;</span><br><span class="line"><span class="type">char</span> str[<span class="number">20</span>];</span><br><span class="line"><span class="built_in">sprintf</span>(str,<span class="string">&quot;%d&quot;</span>,num);</span><br></pre></td></tr></table></figure></div><h2 id="数学知识"><a href="#数学知识" class="headerlink" title="数学知识"></a>数学知识</h2><h3 id="质数"><a href="#质数" class="headerlink" title="质数"></a>质数</h3><h4 id="质数的判定"><a href="#质数的判定" class="headerlink" title="质数的判定"></a>质数的判定</h4><p>时间复杂度为$O(\sqrt n)$</p><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">bool</span> <span class="title">is_prime</span><span class="params">(<span class="type">int</span> n)</span></span>&#123;</span><br><span class="line">  <span class="keyword">if</span>(n&lt;<span class="number">2</span>) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">2</span>;i&lt;=n/i;i++)&#123;</span><br><span class="line">            <span class="keyword">if</span>(n%i==<span class="number">0</span>) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">  <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function">def <span class="title">is_prime</span><span class="params">(n)</span>:</span></span><br><span class="line"><span class="function">    if n&lt;<span class="number">2</span>: return False</span></span><br><span class="line"><span class="function">    i=</span><span class="number">2</span></span><br><span class="line">    <span class="keyword">while</span> i*i &lt;=n:</span><br><span class="line">        <span class="keyword">if</span> n%i==<span class="number">0</span>: <span class="keyword">return</span> False</span><br><span class="line">        i+=<span class="number">1</span></span><br><span class="line">    <span class="keyword">return</span> True</span><br></pre></td></tr></table></figure></div><h4 id="分解质因子"><a href="#分解质因子" class="headerlink" title="分解质因子"></a>分解质因子</h4><p>时间复杂度为$O(logn\sim \sqrt n)$</p><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">divide</span><span class="params">(<span class="type">int</span> n)</span></span>&#123;</span><br><span class="line">  <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">2</span>;i&lt;=n/i;i++)&#123;</span><br><span class="line">      <span class="keyword">if</span>(n%i==<span class="number">0</span>) &#123;</span><br><span class="line">          <span class="type">int</span> s=<span class="number">0</span>;</span><br><span class="line">          <span class="keyword">while</span>(n%i==<span class="number">0</span>)&#123;</span><br><span class="line">              n/=i; s++;</span><br><span class="line">            &#125;</span><br><span class="line">          <span class="built_in">printf</span>(<span class="string">&quot;%d %d\n&quot;</span>,i,s);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  <span class="keyword">if</span>(n&gt;<span class="number">1</span>) <span class="built_in">printf</span>(<span class="string">&quot;%d %d\n&quot;</span>,n,<span class="number">1</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function">def <span class="title">divide</span><span class="params">(n)</span>:</span></span><br><span class="line"><span class="function">    i=</span><span class="number">2</span></span><br><span class="line">    <span class="keyword">while</span> i*i &lt;=n:</span><br><span class="line">        <span class="keyword">if</span> n%i==<span class="number">0</span>:</span><br><span class="line">            s=<span class="number">0</span></span><br><span class="line">            <span class="keyword">while</span> n%i==<span class="number">0</span>:</span><br><span class="line">                n<span class="comment">//=i</span></span><br><span class="line">                s+=<span class="number">1</span></span><br><span class="line">            <span class="built_in">print</span>(i,s)</span><br><span class="line">        i+=<span class="number">1</span></span><br><span class="line">    <span class="keyword">if</span> n&gt;<span class="number">1</span>: <span class="built_in">print</span>(n,<span class="number">1</span>)</span><br></pre></td></tr></table></figure></div><h4 id="埃氏筛"><a href="#埃氏筛" class="headerlink" title="埃氏筛"></a>埃氏筛</h4><p>时间复杂度$O(nloglogn)$</p><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">const</span> <span class="type">int</span> N=<span class="number">1e5</span>+<span class="number">10</span>;</span><br><span class="line"><span class="type">int</span> primes[N],cnt;</span><br><span class="line"><span class="type">bool</span> st[N];</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">get_primes</span><span class="params">(<span class="type">int</span> n)</span></span>&#123;</span><br><span class="line">  <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">2</span>;i&lt;=n;i++)&#123;</span><br><span class="line">      <span class="keyword">if</span>(!st[i])&#123;</span><br><span class="line">          primes[cnt++]=n;</span><br><span class="line">          <span class="keyword">for</span>(<span class="type">int</span> j=<span class="number">2</span>*i;j&lt;=n;j+=i) st[j]=<span class="literal">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><h4 id="欧拉筛"><a href="#欧拉筛" class="headerlink" title="欧拉筛"></a>欧拉筛</h4><p>时间复杂度$O(n)$，每个数只会被其最小质因子筛掉。</p><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">const</span> <span class="type">int</span> N=<span class="number">1e5</span>+<span class="number">10</span>;</span><br><span class="line"><span class="type">int</span> primes[N],cnt;</span><br><span class="line"><span class="type">bool</span> st[N];</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">get_primes</span><span class="params">(<span class="type">int</span> n)</span></span>&#123;</span><br><span class="line">  <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">2</span>;i&lt;=n;i++)&#123;</span><br><span class="line">      <span class="keyword">if</span>(!st[i]) primes[cnt++]=i;</span><br><span class="line">      <span class="keyword">for</span>(<span class="type">int</span> j=<span class="number">0</span>;primes[j]&lt;=n/i;j++)&#123;</span><br><span class="line">          st[primes[j]*i]=<span class="literal">true</span>;</span><br><span class="line">          <span class="keyword">if</span>(i%primes[j]==<span class="number">0</span>) <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><h3 id="约数"><a href="#约数" class="headerlink" title="约数"></a>约数</h3><h4 id="分解约数"><a href="#分解约数" class="headerlink" title="分解约数"></a>分解约数</h4><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">get_divisors</span><span class="params">(<span class="type">int</span> n)</span></span>&#123;</span><br><span class="line">  vector&lt;<span class="type">int</span>&gt; res;</span><br><span class="line">  <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">1</span>;i&lt;=n/i;i++)&#123;</span><br><span class="line">      <span class="keyword">if</span>(n%i==<span class="number">0</span>)&#123;</span><br><span class="line">          res.<span class="built_in">push_back</span>(i);</span><br><span class="line">          <span class="keyword">if</span>(i!=n/i) res.<span class="built_in">push_back</span>(n/i);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  <span class="built_in">sort</span>(res.<span class="built_in">begin</span>(),res.<span class="built_in">end</span>());</span><br><span class="line">  <span class="keyword">return</span> res;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><h4 id="约数个数"><a href="#约数个数" class="headerlink" title="约数个数"></a>约数个数</h4><h4 id="约数之和"><a href="#约数之和" class="headerlink" title="约数之和"></a>约数之和</h4><h4 id="最大公约数"><a href="#最大公约数" class="headerlink" title="最大公约数"></a>最大公约数</h4><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">int</span> <span class="title">gcd</span><span class="params">(<span class="type">int</span> a,<span class="type">int</span> b)</span></span>&#123;</span><br><span class="line">  <span class="keyword">return</span> b ? <span class="built_in">gcd</span>(b,a%b) : a;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><h2 id="算法初步"><a href="#算法初步" class="headerlink" title="算法初步"></a>算法初步</h2><h3 id="排序"><a href="#排序" class="headerlink" title="排序"></a>排序</h3><h4 id="冒泡排序"><a href="#冒泡排序" class="headerlink" title="冒泡排序"></a>冒泡排序</h4><p>冒泡排序通过多次<font color="red">比较和交换相邻元素</font>的方式将待排序的数据按照升序或降序进行排序。时间复杂度：$O(n^2)$。</p><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">BubbleSort</span><span class="params">(<span class="type">int</span> arr[],<span class="type">int</span> len)</span></span>&#123;</span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">1</span>;i&lt;len;i++)</span><br><span class="line">        <span class="keyword">for</span>(<span class="type">int</span> j=<span class="number">0</span>;j&lt;i;j++)</span><br><span class="line">            <span class="keyword">if</span>(arr[j]&gt;arr[j+<span class="number">1</span>])&#123;<span class="type">int</span> tmp=arr[j];arr[j]=arr[j+<span class="number">1</span>];arr[j+<span class="number">1</span>]=tmp;&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><h4 id="选择排序"><a href="#选择排序" class="headerlink" title="选择排序"></a>选择排序</h4><p>选择排序将待排序的数据序列分为已排序部分和未排序部分，每次<font color="red">从未排序部分选择最小（或最大）的元素，将其放置在已排序部分的末尾</font>，直到整个序列排序完成。时间复杂度：$O(n^2)$。</p><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">SelectionSort</span><span class="params">(<span class="type">int</span> arr[],<span class="type">int</span> len)</span></span>&#123;</span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">0</span>;i&lt;len;i++)&#123;</span><br><span class="line">        <span class="type">int</span> idx=i;</span><br><span class="line">        <span class="keyword">for</span>(<span class="type">int</span> j=idx+<span class="number">1</span>;j&lt;len;j++) <span class="keyword">if</span>(arr[j]&lt;arr[idx]) idx=j;</span><br><span class="line">        <span class="type">int</span> tmp=arr[idx]; arr[idx]=arr[i]; arr[i]=tmp;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><h4 id="直接插入排序"><a href="#直接插入排序" class="headerlink" title="直接插入排序"></a>直接插入排序</h4><p>插入排序将待排序的数据序列分为已排序部分和未排序部分，每次从<font color="red">未排序部分选择一个元素插入到已排序部分的合适位置</font>，直到整个序列排序完成。时间复杂度：$O(n^2)$。</p><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">InsertionSort</span><span class="params">(<span class="type">int</span> arr[],<span class="type">int</span> len)</span></span>&#123;</span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">1</span>;i&lt;len;i++)&#123;</span><br><span class="line">        <span class="type">int</span> idx=<span class="number">0</span>;</span><br><span class="line">        <span class="keyword">while</span>(idx&lt;= i)&#123;<span class="keyword">if</span>(arr[i]&lt;arr[idx])&#123;<span class="type">int</span> tmp=arr[i];arr[i]=arr[idx];arr[idx]=tmp;&#125;idx++;&#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><h4 id="希尔排序"><a href="#希尔排序" class="headerlink" title="希尔排序"></a>希尔排序</h4><p>希尔排序（Shell Sort）是一种基于插入排序的排序算法，旨在提高插入排序在大规模数据集上的效率。它通过比较距离较远的元素来进行排序，从而减少了数据移动的次数。</p><ol><li><strong>分组排序</strong>：<ul><li>将整个待排序的数组按照一定的增量（gap）分成若干个子序列，对每个子序列分别进行插入排序。</li><li>初始的增量较大，随着算法的进行逐渐减小，直到增量为1，此时整个数组被当作一个子序列进行插入排序。</li></ul></li><li><strong>增量序列</strong>：<ul><li>增量序列的选择对希尔排序的性能有很大影响。常用的增量序列有希尔增量（gap &#x3D; n&#x2F;2, n&#x2F;4, …, 1）、Hibbard增量（1, 3, 7, 15, …）等。</li><li>理想的增量序列可以减少比较次数和移动次数，从而提高排序效率。</li></ul></li><li><strong>排序过程</strong>：<ul><li>初始时选择一个较大的增量，将数组元素分组。</li><li>对每个分组内的元素进行插入排序。</li><li>减小增量，重复上述步骤，直到增量为1。</li></ul></li></ol><h4 id="快速排序"><a href="#快速排序" class="headerlink" title="快速排序"></a>快速排序</h4><p>快速排序是通过<strong>分治的策略</strong>将一个大问题分解为多个子问题，然后逐步解决子问题，最终达到整体问题的解决。</p><p>快速排序的基本思想可以概括为以下几个步骤：</p><ol><li>选择一个基准元素：从待排序的数组中<strong>选择一个元素作为基准</strong>，通常选择第一个元素、最后一个元素或者随机选取。</li><li>分区：将数组中的其他元素按照与基准元素的大小关系分为两个子数组，<strong>一个子数组中的元素小于基准元素，另一个子数组中的元素大于基准元素</strong>。同时，<strong>基准元素所在的位置也确定了</strong>。</li><li>递归排序：对分区后的两个子数组<strong>递归</strong>地应用上述步骤，直到子数组的大小为1或0（即已经有序）。</li><li>合并结果：将所有子数组的结果合并起来，即得到最终的有序数组。</li></ol><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">QuickSort</span><span class="params">(<span class="type">int</span> arr[],<span class="type">int</span> l,<span class="type">int</span> r)</span></span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(l&gt;=r) <span class="keyword">return</span>;  <span class="comment">//递归出口</span></span><br><span class="line">    <span class="type">int</span> num=arr[l],front=l<span class="number">-1</span>,rear=r+<span class="number">1</span>; <span class="comment">//注意前后两指针的初始位置</span></span><br><span class="line">    <span class="keyword">while</span>(front&lt;rear)&#123;</span><br><span class="line">        <span class="keyword">while</span>(arr[++front]&lt;num);</span><br><span class="line">        <span class="keyword">while</span>(arr[--rear]&gt;num);</span><br><span class="line">        <span class="keyword">if</span>(front&lt;rear) <span class="built_in">swap</span>(arr[front],arr[rear]);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="built_in">QuickSort</span>(arr,l,rear);</span><br><span class="line">    <span class="built_in">QuickSort</span>(arr,rear+<span class="number">1</span>,r);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><blockquote><p>[!IMPORTANT]</p><p>从快排中抽离出来的一个双指针的思想</p><div class="highlight-container" data-rel="Cpp"><figure class="iseeu highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">while</span>(front&lt;rear)&#123;</span><br><span class="line"><span class="keyword">while</span>(Condition1) Action1; </span><br><span class="line"><span class="keyword">while</span>(Condition2) Action2;</span><br><span class="line">Action3;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><ul><li><code>Condition1</code>用来确定<strong>左指针</strong>在一轮循环中什么时候停止，<code>Action1</code>是遍历<strong>左端序列</strong>时需要做的行为；</li><li><code>Condition2</code>用来确定<strong>右指针</strong>在一轮循环中什么时候停止，<code>Action2</code>是遍历<strong>右端序列</strong>时需要做的行为；</li><li><code>Action3</code>是完成一轮循环后需要执行的动作。</li></ul></blockquote><h4 id="归并排序"><a href="#归并排序" class="headerlink" title="归并排序"></a>归并排序</h4><p>归并排序是一种经典的、<strong>稳定</strong>的排序算法，其基本思想是将一个大问题分解为多个小问题，通过递归地将小问题排序并合并，最终得到整体问题的解决。</p><p>归并排序的基本思想可以概括为以下几个步骤：</p><ol><li>分解：将待排序的数组递归地分解为较小的子数组，直到每个子数组只包含一个元素（即已经有序）或为空。</li><li>合并：将分解得到的子数组逐个合并，得到更大的有序子数组。<strong>合并过程是通过比较每个子数组的元素，选取最小（或最大）的元素放入新的数组中，并移动相应的指针</strong>。</li><li>递归排序和合并：重复执行步骤1和步骤2，直到所有的子数组合并为一个完整的有序数组。</li></ol><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">MergeSort</span><span class="params">(<span class="type">int</span> arr[],<span class="type">int</span> l,<span class="type">int</span> r)</span></span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(l&gt;=r) <span class="keyword">return</span>;</span><br><span class="line">    <span class="type">int</span> mid=l+r&gt;&gt;<span class="number">1</span>;</span><br><span class="line">    <span class="built_in">MergeSort</span>(arr,l,mid);</span><br><span class="line">    <span class="built_in">MergeSort</span>(arr,mid+<span class="number">1</span>,r);</span><br><span class="line">    <span class="type">int</span> idx=<span class="number">0</span>, idx1=l,idx2=mid+<span class="number">1</span>;</span><br><span class="line">    <span class="type">int</span> tmp[<span class="number">100010</span>];</span><br><span class="line">    <span class="keyword">while</span>(idx1&lt;=mid &amp;&amp; idx2&lt;=r)</span><br><span class="line">        <span class="keyword">if</span>(arr[idx1]&lt;arr[idx2]) tmp[idx++]=arr[idx1++];</span><br><span class="line">        <span class="keyword">else</span> tmp[idx++]=arr[idx2++];</span><br><span class="line">    <span class="keyword">while</span>(idx1&lt;=mid) tmp[idx++]=arr[idx1++];</span><br><span class="line">    <span class="keyword">while</span>(idx2&lt;=r) tmp[idx++]=arr[idx2++];</span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> i=l,j=<span class="number">0</span>;i&lt;=r;i++,j++) arr[i]=tmp[j];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><blockquote><p>[!IMPORTANT]</p><p>从归并排序中抽离出来的一个双指针的思想，也就是说双路归并使用双指针即可。</p><p>假如是多路归并呢？想想数据结构这门课中所学的赢者树和败者树，其本质都是堆。<strong>因此多路归并完全可以用堆（优先队列）进行优化。</strong></p><div class="highlight-container" data-rel="Cpp"><figure class="iseeu highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">while</span>(idx1&lt;len1 &amp;&amp; idx2&lt;len2)&#123;</span><br><span class="line">   <span class="keyword">if</span>(Condition1) Action1;</span><br><span class="line">   <span class="keyword">else</span> Action2;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">while</span>(Condition2) Action3;</span><br><span class="line"><span class="keyword">while</span>(Condition3) Action4;</span><br></pre></td></tr></table></figure></div><ul><li><code>Condition1</code>是两个数组进行比较的条件，<code>Action1</code>是对应条件为真执行的动作，<code>Action2</code>是对应条件为假执行的动作。</li><li><code>Condition2</code>是对<strong>数组1</strong>进行的扫尾条件判断，<code>Action3</code>是对应的扫尾动作；</li><li><code>Condition3</code>是对<strong>数组2</strong>进行的扫尾条件判断，<code>Action4</code>是对应的扫尾动作；</li></ul><p>OJ例题：<a class="link" href="https://leetcode.cn/problems/median-of-two-sorted-arrays/description/">寻找两个正序数组的中位数 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a></p></blockquote><h4 id="sort函数"><a href="#sort函数" class="headerlink" title="sort函数"></a>sort函数</h4><p>在OJ的题目中，一般不会手动写排序函数，可以直接调用C++的sort函数进行排序！其中，最关键的点就是排序规则的定义。默认情况下是升序排序，可以通过在第三个参数填入<code>greater&lt;int&gt;()</code>来实现降序排序，而且是针对一般的整数数组。要自定义排序规则，则需要实现一个自定义的比较函数<code>bool cmp()</code>。</p><blockquote><p> [!Note]</p><p>如学生类比较函数的规则：先按分数升序排序，相同按名字字母序排序。</p></blockquote><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">bool</span> <span class="title">cmp</span><span class="params">(Student s1, Student s2)</span></span>&#123;</span><br><span class="line">  <span class="keyword">if</span>(s1.score != s2.score) <span class="keyword">return</span> s1.score &gt; s2.score;</span><br><span class="line">  <span class="keyword">else</span> <span class="keyword">return</span> <span class="built_in">strcmp</span>(s1.name,s2.name) &lt; <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><h3 id="二分"><a href="#二分" class="headerlink" title="二分"></a><a class="link" href="https://leetcode.cn/tag/binary-search/problemset/">二分 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a></h3><p><strong>二分通过不断将搜索范围减半来快速定位目标元素</strong>。</p><blockquote><p>[!CAUTION]</p><p><strong>二分的主要思想不是单调性</strong>（当然序列满足单调性一定能二分做），而是<strong>能否根据某一性质划分为左右两个区间</strong>！</p><p><strong>每进行一次二分就是把答案所在的区间给缩小，因此需要准确地判断出答案应该在哪个区间</strong>！当区间的长度为1时，那么该区间中的元素就是答案！</p></blockquote><h4 id="整数二分"><a href="#整数二分" class="headerlink" title="整数二分"></a>整数二分</h4><p>无论是<code>l+r&gt;&gt;1</code>还是<code>l+r+1&gt;&gt;1</code>都是为了数组元素个数为奇时，处理好边界问题。</p><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">int</span> <span class="title">bsearch_l</span><span class="params">(<span class="type">int</span> l,<span class="type">int</span> r)</span></span>&#123;  <span class="comment">//找左边边界</span></span><br><span class="line"><span class="keyword">while</span>(l&lt;r)&#123;</span><br><span class="line">      <span class="type">int</span> mid=l+r&gt;&gt;<span class="number">1</span>;</span><br><span class="line">      <span class="keyword">if</span>(<span class="built_in">check</span>(mid)) r=mid; <span class="comment">//这个check函数需要严格考虑边界情况，以及方向是否正确</span></span><br><span class="line">      <span class="keyword">else</span> l=mid+<span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">  <span class="keyword">return</span> l; <span class="comment">//这里无论返回是l还是r都是一样的</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">bsearch_r</span><span class="params">(<span class="type">int</span> l,<span class="type">int</span> r)</span></span>&#123;  <span class="comment">//找右边边界</span></span><br><span class="line"><span class="keyword">while</span>(l&lt;r)&#123;</span><br><span class="line">      <span class="type">int</span> mid=l+r+<span class="number">1</span>&gt;&gt;<span class="number">1</span>;</span><br><span class="line">      <span class="keyword">if</span>(<span class="built_in">check</span>(mid)) l=mid;</span><br><span class="line">      <span class="keyword">else</span> r=mid<span class="number">-1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">  <span class="keyword">return</span> l;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><blockquote><p>[!Tip]</p><p>主要通过check函数判断应该递归哪个区间来判断使用哪个模板。<strong>二分找不到元素的情况是下标l对应的元素不等于需要查找的元素！</strong></p></blockquote><h5 id="单调性二分"><a href="#单调性二分" class="headerlink" title="单调性二分"></a><a class="link" href="https://leetcode.cn/problems/binary-search/description/">单调性二分 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a></h5><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">int</span> <span class="title">search</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; nums, <span class="type">int</span> target)</span> </span>&#123;</span><br><span class="line">    <span class="type">int</span> l=<span class="number">0</span>,r=nums.<span class="built_in">size</span>()<span class="number">-1</span>;</span><br><span class="line">    <span class="keyword">while</span>(l&lt;r)&#123;</span><br><span class="line">        <span class="type">int</span> mid=(l+r)&gt;&gt;<span class="number">1</span>;</span><br><span class="line">        <span class="keyword">if</span>(nums[mid]&gt;=target) r=mid; <span class="comment">//需要严格注意边界和区间</span></span><br><span class="line">        <span class="keyword">else</span> l=mid+<span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span>(nums[l]!=target) <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">return</span> l;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><h5 id="特性二分"><a href="#特性二分" class="headerlink" title="特性二分"></a><a class="link" href="https://leetcode.cn/problems/search-in-rotated-sorted-array/">特性二分 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a></h5><blockquote><p>这个题目典型就是根据旋转之后，<strong>根据哪一边是有序的这一特性进行二分的</strong>。</p></blockquote><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">int</span> <span class="title">search</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; nums, <span class="type">int</span> target)</span> </span>&#123;</span><br><span class="line">    <span class="type">int</span> left = <span class="number">0</span>;</span><br><span class="line">    <span class="type">int</span> right = nums.<span class="built_in">size</span>() - <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">while</span> (left &lt;= right) &#123;</span><br><span class="line">        <span class="type">int</span> mid = left + right &gt;&gt; <span class="number">1</span>;</span><br><span class="line">        <span class="keyword">if</span> (nums[mid] == target) <span class="keyword">return</span> mid;</span><br><span class="line">        <span class="comment">// 判断哪一半是有序的</span></span><br><span class="line">        <span class="keyword">if</span> (nums[left] &lt;= nums[mid])<span class="comment">// 左半部分是有序的</span></span><br><span class="line">            <span class="keyword">if</span> (nums[left] &lt;= target &amp;&amp; target &lt; nums[mid]) </span><br><span class="line">                right = mid - <span class="number">1</span>; <span class="comment">// 目标值在左半部分</span></span><br><span class="line">            <span class="keyword">else</span> </span><br><span class="line">                left = mid + <span class="number">1</span>; <span class="comment">// 目标值在右半部分</span></span><br><span class="line">        <span class="keyword">else</span> <span class="comment">// 右半部分是有序的</span></span><br><span class="line">            <span class="keyword">if</span> (nums[mid] &lt; target &amp;&amp; target &lt;= nums[right]) </span><br><span class="line">                left = mid + <span class="number">1</span>; <span class="comment">// 目标值在右半部分</span></span><br><span class="line">            <span class="keyword">else</span></span><br><span class="line">                right = mid - <span class="number">1</span>; <span class="comment">// 目标值在左半部分</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">-1</span>; </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><h5 id="非指针二分"><a href="#非指针二分" class="headerlink" title="非指针二分"></a><a class="link" href="https://leetcode.cn/problems/minimum-size-subarray-sum/description/">非指针二分 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a></h5><blockquote><p>[!Note]</p><p>这里的非指针二分其实也是特性二分中的一种，只是这种二分更加抽象，以至于连<code>left</code>和<code>right</code>都不再只是表示区间两端的指针。</p><p>如这个题目中的，假设把<code>left</code>定义成子区间长度的下限，而<code>right</code>定义为子区间长度的上限，那么问题就变成，求解一个最小的区间长度，使得<code>check</code>满足条件。</p></blockquote><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//判断长度为mid的子数组和是否能够大于等于target</span></span><br><span class="line"><span class="function"><span class="type">bool</span> <span class="title">isOver</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; sum, <span class="type">int</span> mid, <span class="type">int</span> target)</span></span>&#123;</span><br><span class="line">    <span class="type">int</span> n = sum.<span class="built_in">size</span>();</span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> i=mid; i&lt;n; i++)</span><br><span class="line">        <span class="keyword">if</span>((sum[i]-sum[i-mid])&gt;=target)</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">minSubArrayLen</span><span class="params">(<span class="type">int</span> target, vector&lt;<span class="type">int</span>&gt;&amp; nums)</span> </span>&#123;</span><br><span class="line">    <span class="type">int</span> n = nums.<span class="built_in">size</span>();</span><br><span class="line">    <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">sum</span><span class="params">(n+<span class="number">1</span>, <span class="number">0</span>)</span></span>;</span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">1</span>; i&lt;=n; i++)</span><br><span class="line">        sum[i] = sum[i<span class="number">-1</span>]+nums[i<span class="number">-1</span>];  <span class="comment">//前缀和</span></span><br><span class="line">    <span class="type">int</span> left = <span class="number">1</span>;</span><br><span class="line">    <span class="type">int</span> right = n;  <span class="comment">//子数组的长度范围为闭区间[1, n]</span></span><br><span class="line">    <span class="keyword">while</span>(left&lt;=right)&#123;  <span class="comment">//二分查找</span></span><br><span class="line">        <span class="type">int</span> mid = (left+right)&gt;&gt;<span class="number">1</span>;</span><br><span class="line">        <span class="keyword">if</span>(!<span class="built_in">isOver</span>(sum, mid, target)) left = mid+<span class="number">1</span>;</span><br><span class="line">        <span class="keyword">else</span> right = mid<span class="number">-1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> left==n+<span class="number">1</span> ? <span class="number">0</span> : left;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><h5 id="左右边界不一致"><a href="#左右边界不一致" class="headerlink" title="左右边界不一致"></a><a class="link" href="https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/description/">左右边界不一致 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a></h5><blockquote><p>[!NOTE]</p><p>在前面的举例中，无一例外都是左右边界一直的情况，所以无论是<code>bsearch_l</code>还是<code>bsearch_r</code>都能完成任务。但是一旦当左右边界不一致时，<code>bsearch_l</code>所求的是左边界，而<code>bsearch_r</code>所求的是右边界。</p></blockquote><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">searchRange</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; nums, <span class="type">int</span> target)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(nums.<span class="built_in">size</span>()==<span class="number">0</span>) <span class="keyword">return</span> vector&lt;<span class="type">int</span>&gt;&#123;<span class="number">-1</span>,<span class="number">-1</span>&#125;;</span><br><span class="line">    <span class="type">int</span> l=<span class="number">0</span>,r=nums.<span class="built_in">size</span>()<span class="number">-1</span>;</span><br><span class="line">    <span class="keyword">while</span>(l&lt;r)&#123;</span><br><span class="line">        <span class="type">int</span> mid=(l+r+<span class="number">1</span>)&gt;&gt;<span class="number">1</span>;</span><br><span class="line">        <span class="keyword">if</span>(nums[mid]&lt;=target) l=mid;</span><br><span class="line">        <span class="keyword">else</span> r=mid<span class="number">-1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="type">int</span> right=l;</span><br><span class="line">    l=<span class="number">0</span>;r=nums.<span class="built_in">size</span>()<span class="number">-1</span>;</span><br><span class="line">    <span class="keyword">while</span>(l&lt;r)&#123;</span><br><span class="line">        <span class="type">int</span> mid=(l+r)&gt;&gt;<span class="number">1</span>;</span><br><span class="line">        <span class="keyword">if</span>(nums[mid]&gt;=target) r=mid;</span><br><span class="line">        <span class="keyword">else</span> l=mid+<span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">  <span class="type">int</span> left=l;</span><br><span class="line">    <span class="keyword">if</span>(target==nums[left]) <span class="keyword">return</span> vector&lt;<span class="type">int</span>&gt;&#123;left,right&#125;;</span><br><span class="line">    <span class="keyword">return</span> vector&lt;<span class="type">int</span>&gt;&#123;<span class="number">-1</span>,<span class="number">-1</span>&#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><h4 id="浮点二分"><a href="#浮点二分" class="headerlink" title="浮点二分"></a>浮点二分</h4><p>浮点二分常见于一些求实根的题目中。eg. xmu2024年人工智能实验室夏令营机式的第一题就是利用浮点二分求实根！需要额外注意的就是<code>eps</code>的精度问题，一般需要比保留的位数多两位。</p><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">double</span> <span class="title">bsearch_1</span><span class="params">(<span class="type">double</span> l,<span class="type">double</span> r)</span></span>&#123;</span><br><span class="line"><span class="keyword">while</span>(r-l&gt;eps)&#123;</span><br><span class="line">      <span class="type">double</span> mid=(l+r)/<span class="number">2</span>;</span><br><span class="line">      <span class="keyword">if</span>(<span class="built_in">check</span>(mid)) r=mid;  </span><br><span class="line">      <span class="keyword">else</span> l=mid;</span><br><span class="line">    &#125;</span><br><span class="line">  <span class="keyword">return</span> l;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><p>当然，如果<a class="link" href="https://leetcode.cn/problems/sqrtx/submissions/557190365/">一些题目 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a>只需要保留整数部分，那么也是可以用整数二分去求解的。</p><div class="highlight-container" data-rel="Cpp"><figure class="iseeu highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">int</span> <span class="title">mySqrt</span><span class="params">(<span class="type">int</span> x)</span> </span>&#123;</span><br><span class="line">    <span class="type">int</span> left = <span class="number">0</span>, right = x;</span><br><span class="line">    <span class="keyword">while</span>(left &lt;= right)&#123;</span><br><span class="line">        <span class="type">int</span> mid = left + right &gt;&gt; <span class="number">1</span>;</span><br><span class="line">        <span class="type">double</span> p = <span class="number">1.0</span> * mid * mid;</span><br><span class="line">        <span class="keyword">if</span>(p == x) <span class="keyword">return</span> mid;</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span>(p &lt; x) left = mid + <span class="number">1</span>;</span><br><span class="line">        <span class="keyword">else</span> right = mid - <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> right;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><h3 id="大数运算"><a href="#大数运算" class="headerlink" title="大数运算"></a>大数运算</h3><p><strong>注意大数在数组中的存储顺序：从低位存储到高位。</strong></p><h4 id="大数加"><a href="#大数加" class="headerlink" title="大数加"></a>大数加</h4><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;vector&gt;</span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"><span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">add</span><span class="params">(vector&lt;<span class="type">int</span>&gt; &amp;a, vector&lt;<span class="type">int</span>&gt; &amp;b)</span></span>&#123;</span><br><span class="line">    vector&lt;<span class="type">int</span>&gt; res;<span class="type">int</span> sum=<span class="number">0</span>;</span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">0</span>;i&lt;a.<span class="built_in">size</span>()||i&lt;b.<span class="built_in">size</span>();i++)&#123;</span><br><span class="line">        <span class="keyword">if</span>(i&lt;a.<span class="built_in">size</span>()) sum+=a[i];</span><br><span class="line">        <span class="keyword">if</span>(i&lt;b.<span class="built_in">size</span>()) sum+=b[i];</span><br><span class="line">        res.<span class="built_in">push_back</span>(sum%<span class="number">10</span>);</span><br><span class="line">        sum/=<span class="number">10</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span>(sum) res.<span class="built_in">push_back</span>(<span class="number">1</span>);</span><br><span class="line">    <span class="keyword">return</span> res;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span>&#123;</span><br><span class="line">    string a,b; vector&lt;<span class="type">int</span>&gt; A,B;</span><br><span class="line">    cin&gt;&gt;a&gt;&gt;b;</span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> i=a.<span class="built_in">size</span>()<span class="number">-1</span>;i&gt;=<span class="number">0</span>;i--) A.<span class="built_in">push_back</span>(a[i]-<span class="string">&#x27;0&#x27;</span>);</span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> i=b.<span class="built_in">size</span>()<span class="number">-1</span>;i&gt;=<span class="number">0</span>;i--) B.<span class="built_in">push_back</span>(b[i]-<span class="string">&#x27;0&#x27;</span>);</span><br><span class="line">    <span class="keyword">auto</span> res= <span class="built_in">add</span>(A,B);</span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> i=res.<span class="built_in">size</span>()<span class="number">-1</span>;i&gt;=<span class="number">0</span>;i--) cout&lt;&lt;res[i];</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><h4 id="大数减"><a href="#大数减" class="headerlink" title="大数减"></a>大数减</h4><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;vector&gt;</span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"><span class="function"><span class="type">bool</span> <span class="title">cmp</span><span class="params">(<span class="type">const</span> vector&lt;<span class="type">int</span>&gt; &amp;a,<span class="type">const</span> vector&lt;<span class="type">int</span>&gt;&amp;b)</span></span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(a.<span class="built_in">size</span>()!=b.<span class="built_in">size</span>()) <span class="keyword">return</span> a.<span class="built_in">size</span>()&gt;b.<span class="built_in">size</span>();</span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">for</span>(<span class="type">int</span> i=a.<span class="built_in">size</span>()<span class="number">-1</span>;i&gt;=<span class="number">0</span>;i--) <span class="keyword">if</span>(a[i]!=b[i]) <span class="keyword">return</span> a[i]&gt;b[i];</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">sub</span><span class="params">(vector&lt;<span class="type">int</span>&gt; &amp;a, vector&lt;<span class="type">int</span>&gt;&amp;b)</span></span>&#123;</span><br><span class="line">    vector&lt;<span class="type">int</span>&gt; res;<span class="type">int</span> sum=<span class="number">0</span>;</span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">0</span>;i&lt;a.<span class="built_in">size</span>();i++)&#123;</span><br><span class="line">        sum+=a[i];</span><br><span class="line">        <span class="keyword">if</span>(i&lt;b.<span class="built_in">size</span>()) sum-=b[i];</span><br><span class="line">        res.<span class="built_in">push_back</span>((sum+<span class="number">10</span>)%<span class="number">10</span>);</span><br><span class="line">        <span class="keyword">if</span>(sum&lt;<span class="number">0</span>) sum=<span class="number">-1</span>;  <span class="comment">//借位与否</span></span><br><span class="line">        <span class="keyword">else</span> sum=<span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">while</span>(res.<span class="built_in">size</span>()&gt;<span class="number">1</span> &amp;&amp; res.<span class="built_in">back</span>()==<span class="number">0</span>) res.<span class="built_in">pop_back</span>();  <span class="comment">//去掉前导0</span></span><br><span class="line">    <span class="keyword">return</span> res;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span>&#123;</span><br><span class="line">    string a,b;vector&lt;<span class="type">int</span>&gt; A,B;</span><br><span class="line">    cin&gt;&gt;a&gt;&gt;b;</span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> i=a.<span class="built_in">size</span>()<span class="number">-1</span>;i&gt;=<span class="number">0</span>;i--) A.<span class="built_in">push_back</span>(a[i]-<span class="string">&#x27;0&#x27;</span>);</span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> i=b.<span class="built_in">size</span>()<span class="number">-1</span>;i&gt;=<span class="number">0</span>;i--) B.<span class="built_in">push_back</span>(b[i]-<span class="string">&#x27;0&#x27;</span>);</span><br><span class="line">    vector&lt;<span class="type">int</span>&gt; res;</span><br><span class="line">    <span class="keyword">if</span>(<span class="built_in">cmp</span>(A,B)) &#123;res=<span class="built_in">sub</span>(A,B); <span class="keyword">for</span>(<span class="type">int</span> i=res.<span class="built_in">size</span>()<span class="number">-1</span>;i&gt;=<span class="number">0</span>;i--) cout&lt;&lt;res[i];&#125;</span><br><span class="line">    <span class="keyword">else</span> &#123;res=<span class="built_in">sub</span>(B,A); cout&lt;&lt;<span class="string">&quot;-&quot;</span>; <span class="keyword">for</span>(<span class="type">int</span> i=res.<span class="built_in">size</span>()<span class="number">-1</span>;i&gt;=<span class="number">0</span>;i--) cout&lt;&lt;res[i];&#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><h4 id="大数乘"><a href="#大数乘" class="headerlink" title="大数乘"></a>大数乘</h4><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">mul</span><span class="params">(vector&lt;<span class="type">int</span>&gt; a,<span class="type">int</span> b)</span></span>&#123;</span><br><span class="line">    vector&lt;<span class="type">int</span>&gt; res;<span class="type">int</span> sum=<span class="number">0</span>;</span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">0</span>;i&lt;a.<span class="built_in">size</span>() || sum;i++)&#123;</span><br><span class="line">        sum+=a[i]*b;</span><br><span class="line">        res.<span class="built_in">push_back</span>(sum%<span class="number">10</span>);</span><br><span class="line">        sum/=<span class="number">10</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> res;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><h4 id="大数除"><a href="#大数除" class="headerlink" title="大数除"></a>大数除</h4><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">div</span><span class="params">(vector&lt;<span class="type">int</span>&gt; a,<span class="type">int</span> b，<span class="type">int</span> &amp;c)</span></span>&#123;  <span class="comment">//c是余数</span></span><br><span class="line">    vector&lt;<span class="type">int</span>&gt; res; <span class="type">int</span> sum=<span class="number">0</span>;</span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> i=a.<span class="built_in">size</span>()<span class="number">-1</span>;i&gt;=<span class="number">0</span>;i--)&#123;</span><br><span class="line">        sum=sum*<span class="number">10</span>+a[i];</span><br><span class="line">        res.<span class="built_in">push_back</span>(sum/b);</span><br><span class="line">        sum%=b;</span><br><span class="line">    &#125;</span><br><span class="line">  c=sum; <span class="built_in">reverse</span>(res.<span class="built_in">begin</span>(),res.<span class="built_in">end</span>());</span><br><span class="line">    <span class="keyword">while</span>(res.<span class="built_in">size</span>()&gt;<span class="number">1</span> &amp;&amp; res.<span class="built_in">back</span>()== <span class="number">0</span>) res.<span class="built_in">pop_back</span>();</span><br><span class="line">    <span class="keyword">return</span> res;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><h3 id="哈希"><a href="#哈希" class="headerlink" title="哈希"></a>哈希</h3><h4 id="整数哈希"><a href="#整数哈希" class="headerlink" title="整数哈希"></a>整数哈希</h4><p>整数哈希的作用是将<strong>数据范围较大的值域转换到范围较小的值域空间</strong>。其中避免哈希冲突的方案有拉链法和开放地址法。</p><p><strong>原数据大且稀疏，转到到小且稠密的空间上</strong>。</p><p><strong>除了数据空间的映射之外，哈希还可能应用到一些在集合以$O(1)$时间复杂度去判断元素是否存在的场景。</strong></p><h5 id="拉链法"><a href="#拉链法" class="headerlink" title="拉链法"></a>拉链法</h5><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">const</span> <span class="type">int</span> N=<span class="number">1e5</span>+<span class="number">3</span>;  <span class="comment">// N一定要是素数且大于数据范围</span></span><br><span class="line"><span class="type">int</span> h[N],e[N],ne[N],idx; <span class="comment">// 其中h是指针数组，e存储节点，ne存储指针</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">init</span><span class="params">()</span></span>&#123;</span><br><span class="line">  <span class="built_in">memset</span>(h,<span class="number">-1</span>,<span class="keyword">sizeof</span> h);</span><br><span class="line">  <span class="built_in">memset</span>(ne,<span class="number">-1</span>,<span class="keyword">sizeof</span> ne);</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">insert</span><span class="params">(<span class="type">int</span> x)</span></span>&#123;</span><br><span class="line">  <span class="type">int</span> k=(x%N+N)%N;</span><br><span class="line">  e[idx]=x;ne[idx]=h[k];h[k]=idx++; <span class="comment">// 这三句是头插法实现，如果用尾插法不要直接循环找末尾元素，可以增加一个尾指针。</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">bool</span> <span class="title">find</span><span class="params">(<span class="type">int</span> x)</span></span>&#123;</span><br><span class="line">  <span class="type">int</span> k=(x%N+N)%N;</span><br><span class="line">  <span class="keyword">for</span>(<span class="type">int</span> i=h[k];i!=<span class="number">-1</span>;i=ne[i])</span><br><span class="line">      <span class="keyword">if</span>(e[i]==x) <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">  <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><div class="highlight-container" data-rel="Cpp"><figure class="iseeu highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line">N = <span class="number">100003</span>  # N 是素数且大于数据范围</span><br><span class="line">h, e, ne, idx = [<span class="number">-1</span>]*N, [<span class="number">0</span>]*N, [<span class="number">-1</span>]*N, <span class="number">0</span>     </span><br><span class="line"></span><br><span class="line">def <span class="built_in">init</span>():</span><br><span class="line">    global h, ne, idx</span><br><span class="line">    h = [<span class="number">-1</span>] * N</span><br><span class="line">    ne = [<span class="number">-1</span>] * N</span><br><span class="line">    idx = <span class="number">0</span></span><br><span class="line"></span><br><span class="line">def <span class="built_in">insert</span>(x):</span><br><span class="line">    global idx</span><br><span class="line">    k = (x % N + N) % N  # 处理负数取模</span><br><span class="line">    e[idx] = x</span><br><span class="line">    ne[idx] = h[k]</span><br><span class="line">    h[k] = idx</span><br><span class="line">    idx += <span class="number">1</span></span><br><span class="line"></span><br><span class="line">def <span class="built_in">find</span>(x):</span><br><span class="line">    k = (x % N + N) % N  # 处理负数取模</span><br><span class="line">    i = h[k]</span><br><span class="line">    <span class="keyword">while</span> i != <span class="number">-1</span>:</span><br><span class="line">        <span class="keyword">if</span> e[i] == x:</span><br><span class="line">            <span class="keyword">return</span> True</span><br><span class="line">        i = ne[i]</span><br><span class="line">    <span class="keyword">return</span> False</span><br></pre></td></tr></table></figure></div><p>经典例题：<a class="link" href="https://acm.swust.edu.cn/#/problem/1012/-1">1012.哈希表（链地址法处理冲突） - Problems | SWUST OJ <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a></p><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;bits/stdc++.h&gt;</span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"><span class="keyword">typedef</span> pair&lt;<span class="type">int</span>,<span class="type">int</span>&gt; PII;</span><br><span class="line"><span class="type">int</span> m,n,idx;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">insert</span><span class="params">(<span class="type">int</span> h[],<span class="type">int</span> e[],<span class="type">int</span> ne[],<span class="type">int</span> c)</span></span>&#123;</span><br><span class="line">    <span class="type">int</span> k=(c%m+m)%m;</span><br><span class="line">    <span class="type">int</span> i=h[k];</span><br><span class="line">    <span class="keyword">while</span>(i!=<span class="number">-1</span> &amp;&amp; ne[i]!=<span class="number">-1</span>) i=ne[i];</span><br><span class="line">    e[idx]=c;</span><br><span class="line">    <span class="keyword">if</span>(i==<span class="number">-1</span>) &#123;h[k]=idx++;&#125;</span><br><span class="line">    <span class="keyword">else</span> &#123;ne[i]=idx++;&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function">PII <span class="title">find</span><span class="params">(<span class="type">int</span> h[],<span class="type">int</span> e[],<span class="type">int</span> ne[],<span class="type">int</span> c)</span></span>&#123;</span><br><span class="line">    <span class="type">int</span> k=(c%m+m)%m;<span class="type">int</span> i=h[k];<span class="type">int</span> sum=<span class="number">0</span>;</span><br><span class="line">    <span class="keyword">while</span>(i!=<span class="number">-1</span>)&#123;</span><br><span class="line">        sum++;</span><br><span class="line">        <span class="keyword">if</span>(e[i]==c) <span class="keyword">return</span> <span class="built_in">make_pair</span>(k,sum);</span><br><span class="line">        i=ne[i];</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">make_pair</span>(k,<span class="number">-1</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span>&#123;</span><br><span class="line">    cin&gt;&gt;m&gt;&gt;n;</span><br><span class="line">    <span class="type">int</span> h[m],e[m],ne[m],num;</span><br><span class="line">    <span class="built_in">memset</span>(h,<span class="number">-1</span>,<span class="keyword">sizeof</span> h);</span><br><span class="line">    <span class="built_in">memset</span>(ne,<span class="number">-1</span>,<span class="keyword">sizeof</span> ne);</span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">0</span>;i&lt;n;i++) &#123;</span><br><span class="line">        cin&gt;&gt;num;</span><br><span class="line">        <span class="built_in">insert</span>(h,e,ne,num);</span><br><span class="line">    &#125;</span><br><span class="line">    cin&gt;&gt;num;</span><br><span class="line">    PII res=<span class="built_in">find</span>(h,e,ne,num);</span><br><span class="line">    <span class="keyword">if</span>(res.second!=<span class="number">-1</span>) cout&lt;&lt;res.first&lt;&lt;<span class="string">&quot;,&quot;</span>&lt;&lt;res.second;</span><br><span class="line">    <span class="keyword">else</span> cout&lt;&lt;<span class="string">&quot;-1&quot;</span>;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><h5 id="开放地址法"><a href="#开放地址法" class="headerlink" title="开放地址法"></a>开放地址法</h5><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">const</span> <span class="type">int</span> N=<span class="number">2e5</span>+<span class="number">3</span>;   <span class="comment">//这个N需要是大于2到3倍数据范围的一个最小质数</span></span><br><span class="line"><span class="type">const</span> <span class="type">int</span> INF=<span class="number">0x3f3f3f3f</span>;  <span class="comment">//约定改数表示位置无数据存储</span></span><br><span class="line"><span class="type">int</span> h[N];</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">init</span><span class="params">()</span></span>&#123;</span><br><span class="line">  <span class="built_in">memset</span>(h,<span class="number">0x3f</span>,<span class="keyword">sizeof</span> h);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 如果x在哈希表中，返回x的下标；如果x不在哈希表中，返回x应该插入的位置。因此find的返回值还需要再进行一次if判断。</span></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">find</span><span class="params">(<span class="type">int</span> x)</span></span>&#123;</span><br><span class="line">  <span class="type">int</span> k=(x%N+N)%N;</span><br><span class="line">  <span class="keyword">while</span>(h[k]!=INF &amp;&amp; h[k]!=x)&#123;</span><br><span class="line">      k++;</span><br><span class="line">      <span class="keyword">if</span>(k==N) k=<span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line">  <span class="keyword">return</span> k;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><h6 id="最长连续序列"><a href="#最长连续序列" class="headerlink" title="最长连续序列"></a><a class="link" href="https://leetcode.cn/problems/longest-consecutive-sequence/submissions/558717961/">最长连续序列 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a></h6><p>也就是说是可以自己实现C++ STL容器<code>unordered_set</code>。</p><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">const</span> <span class="type">static</span> <span class="type">int</span> maxn=<span class="number">2e5</span>+<span class="number">3</span>;</span><br><span class="line"><span class="type">const</span> <span class="type">int</span> INF=<span class="number">0x3f3f3f3f</span>;</span><br><span class="line"><span class="type">int</span> h[maxn];</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">init</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="built_in">memset</span>(h,<span class="number">0x3f</span>,<span class="keyword">sizeof</span> h);</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">find</span><span class="params">(<span class="type">int</span> x)</span></span>&#123;</span><br><span class="line">    <span class="type">int</span> k=(x%maxn+maxn)%maxn;</span><br><span class="line">    <span class="keyword">while</span>(h[k]!=INF &amp;&amp; h[k]!=x)&#123;</span><br><span class="line">        k++;</span><br><span class="line">        <span class="keyword">if</span>(k==maxn) k=<span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> k;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">longestConsecutive</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; nums)</span> </span>&#123;</span><br><span class="line">    <span class="built_in">init</span>();</span><br><span class="line">    <span class="keyword">for</span>(<span class="keyword">auto</span> item:nums)&#123;</span><br><span class="line">        <span class="type">int</span> idx=<span class="built_in">find</span>(item);</span><br><span class="line">        <span class="keyword">if</span>(h[idx]==INF) h[idx]=item;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="type">int</span> res=<span class="number">0</span>;</span><br><span class="line">    <span class="keyword">for</span>(<span class="keyword">auto</span> item :nums)&#123;</span><br><span class="line">        <span class="keyword">if</span>(h[<span class="built_in">find</span>(item<span class="number">-1</span>)]==INF)&#123;</span><br><span class="line">            <span class="type">int</span> curnum=item;</span><br><span class="line">            <span class="type">int</span> curlen=<span class="number">1</span>;</span><br><span class="line">            <span class="keyword">while</span>(h[<span class="built_in">find</span>(curnum+<span class="number">1</span>)]!=INF)&#123;curlen++;curnum++;&#125;</span><br><span class="line">            res=<span class="built_in">max</span>(res,curlen);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> res;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><h4 id="字符串哈希"><a href="#字符串哈希" class="headerlink" title="字符串哈希"></a>字符串哈希</h4><p>核心思想：将字符串看成P进制数，P的经验值是131或13331，取这两个值的冲突概率低<br>小技巧：<strong>取模的数用2^64，这样直接用unsigned long long存储，溢出的结果就是取模的结果</strong></p><p><strong>可以用来快速判断区间字符串是否一致。</strong></p><p><font color="red"><strong>字符串下标必须从1开始，可以更好地处理边界问题</strong></font>。</p><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="type">unsigned</span> <span class="type">long</span> <span class="type">long</span> ULL;  <span class="comment">//用unsigned long long存储就避免了取模运算</span></span><br><span class="line"><span class="type">const</span> <span class="type">int</span> N=<span class="number">1e5</span>+<span class="number">10</span>;</span><br><span class="line"><span class="type">const</span> <span class="type">int</span> P=<span class="number">131</span>; <span class="comment">// P的经验值是131或13331</span></span><br><span class="line">ULL h[N], p[N]; <span class="comment">// h[k]存储字符串前k个字母的哈希值, p[k]存储 P^k mod 2^64</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 初始化</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">init</span><span class="params">(string str)</span></span>&#123;  <span class="comment">//这个字符串需保证下标从1开始</span></span><br><span class="line">  p[<span class="number">0</span>] = <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i &lt;= n; i ++ )&#123;</span><br><span class="line">        h[i] = h[i - <span class="number">1</span>] * P + str[i];</span><br><span class="line">        p[i] = p[i - <span class="number">1</span>] * P;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 计算子串 str[l ~ r] 的哈希值</span></span><br><span class="line"><span class="function">ULL <span class="title">strHash</span><span class="params">(<span class="type">int</span> l, <span class="type">int</span> r)</span></span>&#123;</span><br><span class="line">    <span class="keyword">return</span> h[r] - h[l - <span class="number">1</span>] * p[r - l + <span class="number">1</span>];  <span class="comment">// r-l+1是这两个位数之间差的值</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><h3 id="前缀和与差分"><a href="#前缀和与差分" class="headerlink" title="前缀和与差分"></a>前缀和与差分</h3><p>前缀和的作用：能够快速求出原数组中<strong>一段的和</strong>。前缀和数组还有个最基本的性质，就是单调递增的。</p><p>差分的作用：可以用于<strong>处理区间修改和查询操作</strong>。<strong>通过对差分数组进行修改，可以在常数时间内更新原始序列的某个区间</strong>。这对于需要频繁修改某个区间的问题非常有用，如区间加法、区间减法等。</p><blockquote><p>[!TIP]</p><p><font color="red"><strong>数组下标必须从1开始，可以更好地处理边界问题</strong></font>。</p></blockquote><p><img lazyload src="/images/loading.svg" data-src="https://ec28649.webp.li/20250301134853477.png" alt="加粗样式"></p><p><img lazyload src="/images/loading.svg" data-src="https://ec28649.webp.li/20250301135049003.png" alt="在这里插入图片描述"></p><h4 id="一维前缀和"><a href="#一维前缀和" class="headerlink" title="一维前缀和"></a>一维前缀和</h4><p>一维前缀和数组：<code>sum[i] = sum[i - 1] + arr[i]</code>;</p><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">const</span> <span class="type">int</span> size=<span class="number">4</span>;</span><br><span class="line"><span class="type">int</span> sum[size];</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i &lt;= size; i++)</span><br><span class="line">        sum[i] = sum[i - <span class="number">1</span>] + arr[i];</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><h4 id="二维前缀和"><a href="#二维前缀和" class="headerlink" title="二维前缀和"></a>二维前缀和</h4><p>二维前缀和数组：<code>sum[i][j] = sum[i][j - 1] + sum[i - 1][j] - sum[i - 1][j - 1] + arr[i][j]</code>;</p><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">const</span> <span class="type">int</span> x_size = <span class="number">4</span>;</span><br><span class="line"><span class="type">const</span> <span class="type">int</span> y_size = <span class="number">4</span>;</span><br><span class="line"><span class="type">int</span> sum[y_size][x_size];</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i &lt;= y_size; i++)</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">1</span>; j &lt;= x_size; j++)</span><br><span class="line">            sum[i][j] = sum[i][j - <span class="number">1</span>] + sum[i - <span class="number">1</span>][j] - sum[i - <span class="number">1</span>][j - <span class="number">1</span>] + arr[i][j];</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><h4 id="一维差分"><a href="#一维差分" class="headerlink" title="一维差分"></a>一维差分</h4><p>一维差分数组：<code>dif[i] = arr[i] - arr[i - 1]</code></p><p>对差分数组进行前缀和运算可以获得原数组，但局限于<strong>多次操作单次(少次)还原</strong></p><p>一维差分运算：</p><p><code>arr[L,R]+value</code></p><p>等价于</p><p><code>dif[L]+value,</code> </p><p><code>dif[R+1]-value</code></p><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">const</span> <span class="type">int</span> size=<span class="number">4</span>;</span><br><span class="line"><span class="type">int</span> dif[size];</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i &lt;= size; i++)</span><br><span class="line">        dif[i] = arr[i] - arr[i - <span class="number">1</span>];</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"><span class="type">const</span> <span class="type">int</span> N = <span class="number">1e6</span>+<span class="number">10</span>;</span><br><span class="line"><span class="type">int</span> arr[N],dif[N];</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">insert</span><span class="params">(<span class="type">int</span> l,<span class="type">int</span> r,<span class="type">int</span> c)</span></span>&#123;</span><br><span class="line">    dif[l]+=c;</span><br><span class="line">    dif[r+<span class="number">1</span>]-=c;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="type">int</span> n,m;</span><br><span class="line">    cin&gt;&gt;n&gt;&gt;m;</span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">1</span>;i&lt;=n;i++) cin&gt;&gt;arr[i];</span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">1</span>;i&lt;=n;i++) <span class="built_in">insert</span>(i,i,arr[i]);  <span class="comment">//这就是在初始化差分数组</span></span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">0</span>;i&lt;m;i++)&#123;</span><br><span class="line">        <span class="type">int</span> l,r,c; cin&gt;&gt;l&gt;&gt;r&gt;&gt;c;</span><br><span class="line">        <span class="built_in">insert</span>(l,r,c);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">1</span>;i&lt;=n;i++) arr[i]=arr[i<span class="number">-1</span>]+dif[i];</span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">1</span>;i&lt;=n;i++) cout&lt;&lt;arr[i]&lt;&lt;<span class="string">&quot; &quot;</span>;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><h4 id="二维差分"><a href="#二维差分" class="headerlink" title="二维差分"></a>二维差分</h4><p>二维差分数组：<code>dif[i][j]=arr[i][j]-arr[i-1][j]-arr[i][j-1]+arr[i-1][j-1]</code></p><p>二维差分运算：</p><p><code>arr[x1,x2][y1,y2]+value</code></p><p>等价于</p><p><code>dif[x1][y1]+value, </code></p><p><code>dif[x1+1][y1]-value, </code></p><p><code>dif[x1][y1+1]-value, </code></p><p><code>dif[x1+1][y1+1]+value</code></p><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">const</span> <span class="type">int</span> x_size = <span class="number">4</span>;</span><br><span class="line"><span class="type">const</span> <span class="type">int</span> y_size = <span class="number">4</span>;</span><br><span class="line"><span class="type">int</span> dif[y_size][x_size];</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i &lt;= y_size; i++)</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">1</span>; j &lt;= x_size; j++)</span><br><span class="line">            dif[i][j] = arr[i][j] - arr[i][j - <span class="number">1</span>] - arr[i - <span class="number">1</span>][j] + arr[i - <span class="number">1</span>][j - <span class="number">1</span>];</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"><span class="type">const</span> <span class="type">int</span> N=<span class="number">1e3</span>+<span class="number">10</span>;</span><br><span class="line"><span class="type">int</span> arr[N][N],diff[N][N];</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">insert</span><span class="params">(<span class="type">int</span> x1,<span class="type">int</span> y1,<span class="type">int</span> x2,<span class="type">int</span> y2,<span class="type">int</span> c)</span></span>&#123;</span><br><span class="line">    diff[x1][y1]+=c; diff[x1][y2+<span class="number">1</span>]-=c; diff[x2+<span class="number">1</span>][y1]-=c; diff[x2+<span class="number">1</span>][y2+<span class="number">1</span>]+=c;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="type">int</span> n,m,k;cin&gt;&gt;n&gt;&gt;m&gt;&gt;k;</span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">1</span>;i&lt;=n;i++) <span class="keyword">for</span>(<span class="type">int</span> j=<span class="number">1</span>;j&lt;=m;j++) cin&gt;&gt;arr[i][j];</span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">1</span>;i&lt;=n;i++) <span class="keyword">for</span>(<span class="type">int</span> j=<span class="number">1</span>;j&lt;=m;j++) <span class="built_in">insert</span>(i,j,i,j,arr[i][j]);  <span class="comment">//初始化dif数组</span></span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> t=<span class="number">0</span>;t&lt;k;t++) &#123;</span><br><span class="line">        <span class="type">int</span> x1,y1,x2,y2,num; cin&gt;&gt;x1&gt;&gt;y1&gt;&gt;x2&gt;&gt;y2&gt;&gt;num; <span class="built_in">insert</span>(x1,y1,x2,y2,num);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">1</span>;i&lt;=n;i++) <span class="keyword">for</span>(<span class="type">int</span> j=<span class="number">1</span>;j&lt;=m;j++) arr[i][j]=arr[i<span class="number">-1</span>][j]+arr[i][j<span class="number">-1</span>]-arr[i<span class="number">-1</span>][j<span class="number">-1</span>]+diff[i][j];</span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">1</span>;i&lt;=n;i++) &#123;<span class="keyword">for</span>(<span class="type">int</span> j=<span class="number">1</span>;j&lt;=m;j++) cout&lt;&lt;arr[i][j]&lt;&lt;<span class="string">&quot; &quot;</span>; cout&lt;&lt;endl;&#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><h3 id="双指针"><a href="#双指针" class="headerlink" title="双指针"></a><a class="link" href="https://leetcode.cn/tag/two-pointers/problemset/">双指针 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a></h3><p>双指针将$O(n^2)$的算法优化到$O(n)$ 。</p><p><strong>双指针最重要的是找到某一种方案去更新两个指针</strong>。</p><p>双指针可以是从两端往中间靠（大部分情况），也可以是从中间往两端靠（eg. 最长回文子串），也可是同时从一边以不同速度走（大部分情况，eg. KMP算法、链表一趟遍历的删插操作），也可以是作用在不同的序列上（eg. 归并排序中的合并操作）。 </p><h4 id="最长回文子串"><a href="#最长回文子串" class="headerlink" title="最长回文子串"></a><a class="link" href="https://leetcode.cn/problems/longest-palindromic-substring/description/">最长回文子串 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a></h4><blockquote><p>[!TIP]</p><p>首先确定回文串，找中心然后想两边扩散看是不是对称的就可以了。</p><p><strong>一个元素可以作为中心点，两个元素也可以作为中心点</strong>。</p></blockquote><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> maxL=<span class="number">0</span>,left=<span class="number">0</span>,right=<span class="number">0</span>;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">extend</span><span class="params">(<span class="type">const</span> string&amp; s,<span class="type">int</span> i,<span class="type">int</span> j,<span class="type">int</span> n)</span></span>&#123;</span><br><span class="line">    <span class="keyword">while</span>(i&gt;=<span class="number">0</span> &amp;&amp; j&lt;n &amp;&amp;s[i]==s[j])&#123;</span><br><span class="line">        <span class="keyword">if</span>(j-i+<span class="number">1</span>&gt;maxL)&#123;left=i,right=j,maxL=j-i+<span class="number">1</span>;&#125;</span><br><span class="line">        i--;j++;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function">string <span class="title">longestPalindrome</span><span class="params">(string s)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">0</span>;i&lt;s.<span class="built_in">size</span>();i++)&#123;</span><br><span class="line">        <span class="built_in">extend</span>(s,i,i,s.<span class="built_in">size</span>());  <span class="comment">//一个元素为中心点</span></span><br><span class="line">        <span class="built_in">extend</span>(s,i,i+<span class="number">1</span>,s.<span class="built_in">size</span>());  <span class="comment">//两个元素为中心点</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> s.<span class="built_in">substr</span>(left,maxL);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><h4 id="盛最多水的容器"><a href="#盛最多水的容器" class="headerlink" title="盛最多水的容器"></a><a class="link" href="https://leetcode.cn/problems/container-with-most-water/description/">盛最多水的容器 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a></h4><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">int</span> <span class="title">maxArea</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; height)</span> </span>&#123;</span><br><span class="line">    <span class="type">int</span> len = height.<span class="built_in">size</span>();</span><br><span class="line">    <span class="type">int</span> i=<span class="number">0</span>,j=len<span class="number">-1</span>,maxA=<span class="number">0</span>;</span><br><span class="line">    <span class="keyword">while</span>(i&lt;j)&#123;</span><br><span class="line">        <span class="keyword">if</span>((j-i)*<span class="built_in">min</span>(height[i],height[j])&gt;maxA)</span><br><span class="line">            maxA=(j-i)*<span class="built_in">min</span>(height[i],height[j]);</span><br><span class="line">        <span class="keyword">if</span>(height[i]&gt;height[j]) j--; <span class="comment">//移动短板</span></span><br><span class="line">        <span class="keyword">else</span> i++;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> maxA;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><h4 id="三数之和"><a href="#三数之和" class="headerlink" title="三数之和"></a><a class="link" href="https://leetcode.cn/problems/3sum/description/">三数之和 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a></h4><div class="highlight-container" data-rel="Cpp"><figure class="iseeu highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">vector&lt;vector&lt;<span class="type">int</span>&gt;&gt; <span class="built_in">threeSum</span>(vector&lt;<span class="type">int</span>&gt;&amp; nums) &#123;</span><br><span class="line">    vector&lt;vector&lt;<span class="type">int</span>&gt;&gt; res;</span><br><span class="line">    <span class="type">int</span> len =nums.<span class="built_in">size</span>();</span><br><span class="line">    <span class="built_in">sort</span>(nums.<span class="built_in">begin</span>(),nums.<span class="built_in">end</span>()); <span class="comment">//进行排序是为了后面双指针进行双向更新提供条件</span></span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">0</span>;i&lt;len;i++)&#123;</span><br><span class="line">        <span class="keyword">if</span>(i &gt; <span class="number">0</span> &amp;&amp; nums[i] == nums[i<span class="number">-1</span>]) <span class="keyword">continue</span>; <span class="comment">//防止遍历的i出现重复</span></span><br><span class="line">        <span class="type">int</span> l=i+<span class="number">1</span>,r=len<span class="number">-1</span>;</span><br><span class="line">        <span class="keyword">while</span>(l&lt;r)&#123;</span><br><span class="line">            <span class="keyword">if</span>(nums[l]+nums[r]==-nums[i]) &#123;</span><br><span class="line">                res.<span class="built_in">push_back</span>(&#123;nums[i],nums[l++],nums[r]&#125;); </span><br><span class="line">                <span class="keyword">while</span>(nums[l<span class="number">-1</span>]==nums[l] &amp;&amp; l&lt;r) l++; <span class="comment">//防止双指针出现重复元素</span></span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">else</span> <span class="keyword">if</span> (nums[l]+nums[r]&gt;-nums[i]) r--;</span><br><span class="line">            <span class="keyword">else</span> l++;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> res;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><h3 id="位运算"><a href="#位运算" class="headerlink" title="位运算"></a>位运算</h3><p>求n的2进制的第k位数字：<code>n&gt;&gt;k &amp; 1</code></p><p>提取一个整数 <code>n</code> 的二进制表示中最低位的 1 所代表的值：<code>lowbit(n)=n &amp; -n</code></p><h3 id="离散化"><a href="#离散化" class="headerlink" title="离散化"></a>离散化</h3><p>离散化的本质是建立了一段数列到自然数之间的映射关系（value -&gt; index)，通过建立新索引，来缩小目标区间，使得可以进行一系列连续数组可以进行的操作。eg：二分，前缀和等</p><blockquote><p>[!caution]</p><p>离散化首先需要<strong>排序去重</strong>！</p></blockquote><p>排序：<code>sort(alls.begin(),alls.end())</code><br>去重：<code>alls.earse(unique(alls.begin(),alls.end()),alls.end())</code></p><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">vector&lt;<span class="type">int</span>&gt; alls; <span class="comment">// 存储所有待离散化的值</span></span><br><span class="line"><span class="built_in">sort</span>(alls.<span class="built_in">begin</span>(), alls.<span class="built_in">end</span>()); <span class="comment">// 将所有值排序</span></span><br><span class="line">alls.<span class="built_in">erase</span>(<span class="built_in">unique</span>(alls.<span class="built_in">begin</span>(), alls.<span class="built_in">end</span>()), alls.<span class="built_in">end</span>());   <span class="comment">// 去掉重复元素</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 二分求出x对应的离散化的值</span></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">find</span><span class="params">(<span class="type">int</span> x)</span></span>&#123; <span class="comment">// 找到第一个大于等于x的位置</span></span><br><span class="line">    <span class="type">int</span> l = <span class="number">0</span>, r = alls.<span class="built_in">size</span>() - <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">while</span> (l &lt; r)&#123;</span><br><span class="line">        <span class="type">int</span> mid = l + r &gt;&gt; <span class="number">1</span>;</span><br><span class="line">        <span class="keyword">if</span> (alls[mid] &gt;= x) r = mid;</span><br><span class="line">        <span class="keyword">else</span> l = mid + <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> r + <span class="number">1</span>; <span class="comment">// 映射到1, 2, ...n</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><h4 id="区间和"><a href="#区间和" class="headerlink" title="区间和"></a><a class="link" href="https://ykj.cpolar.cn/problem/6588">区间和 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a></h4><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;bits/stdc++.h&gt;</span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"><span class="type">const</span> <span class="type">int</span> N = <span class="number">3e5</span> + <span class="number">10</span>;</span><br><span class="line"><span class="keyword">typedef</span> pair&lt;<span class="type">int</span>, <span class="type">int</span>&gt; PII;</span><br><span class="line"><span class="type">int</span> n, m;</span><br><span class="line"><span class="type">int</span> arr[N], sum[N];</span><br><span class="line">vector&lt;<span class="type">int</span>&gt; odd;</span><br><span class="line">vector&lt;PII&gt; add, query;</span><br><span class="line"></span><br><span class="line"><span class="comment">//value-&gt;key的映射函数(通过二分实现)</span></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">find</span><span class="params">(<span class="type">int</span> x)</span> </span>&#123;</span><br><span class="line">    <span class="type">int</span> len = odd.<span class="built_in">size</span>();</span><br><span class="line">    <span class="type">int</span> l = <span class="number">1</span>, r = len;</span><br><span class="line">    <span class="keyword">while</span> (l &lt; r) &#123;</span><br><span class="line">        <span class="type">int</span> mid = (l + r) &gt;&gt; <span class="number">1</span>;</span><br><span class="line">        <span class="keyword">if</span> (odd[mid] &gt;= x)</span><br><span class="line">            r = mid;</span><br><span class="line">        <span class="keyword">else</span></span><br><span class="line">            l = mid + <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> l + <span class="number">1</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    cin &gt;&gt; n &gt;&gt; m;</span><br><span class="line">    <span class="comment">//读入数据，并将需要离散化的数据添加到odd中</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; n; i++) &#123;</span><br><span class="line">        <span class="type">int</span> x, c;</span><br><span class="line">        cin &gt;&gt; x &gt;&gt; c;</span><br><span class="line">        add.<span class="built_in">push_back</span>(<span class="built_in">make_pair</span>(x, c));</span><br><span class="line">        odd.<span class="built_in">push_back</span>(x);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; n; i++) &#123;</span><br><span class="line">        <span class="type">int</span> l, r;</span><br><span class="line">        cin &gt;&gt; l &gt;&gt; r;</span><br><span class="line">        query.<span class="built_in">push_back</span>(<span class="built_in">make_pair</span>(l, r));</span><br><span class="line">        odd.<span class="built_in">push_back</span>(l);</span><br><span class="line">        odd.<span class="built_in">push_back</span>(r);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//排序与去重，为了使用二分查找建立值到键的索引</span></span><br><span class="line">    <span class="built_in">sort</span>(odd.<span class="built_in">begin</span>(), odd.<span class="built_in">end</span>());</span><br><span class="line">    odd.<span class="built_in">erase</span>(<span class="built_in">unique</span>(odd.<span class="built_in">begin</span>(), odd.<span class="built_in">end</span>()), odd.<span class="built_in">end</span>());</span><br><span class="line">  </span><br><span class="line">  <span class="comment">//将对原数的操作映射到对离散后数值的操作</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">auto</span> item : add) &#123;</span><br><span class="line">        <span class="type">int</span> idx = <span class="built_in">find</span>(item.first);</span><br><span class="line">        arr[idx] += item.second;</span><br><span class="line">    &#125;</span><br><span class="line">  <span class="comment">//建立前缀和数组</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i &lt;= odd.<span class="built_in">size</span>(); i++) &#123;</span><br><span class="line">        sum[i] += sum[i - <span class="number">1</span>] + arr[i];</span><br><span class="line">    &#125;</span><br><span class="line">   <span class="comment">//利用前缀和求区间和</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; m; i++) &#123;</span><br><span class="line">        <span class="type">int</span> l = <span class="built_in">find</span>(query[i].first), r = <span class="built_in">find</span>(query[i].second);</span><br><span class="line">        cout &lt;&lt; sum[r] - sum[l - <span class="number">1</span>] &lt;&lt; endl;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><h3 id="区间合并"><a href="#区间合并" class="headerlink" title="区间合并"></a><a class="link" href="hook://file/fmu9032lv?p=UmVzb3VyY2VzL3ZpZGVvcw==&n=%E5%8C%BA%E9%97%B4%E5%90%88%E5%B9%B6%2Emov">区间合并 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a></h3><p>首先需要将所有的区间根据<strong>其左区间的值进行升序排序；然后遍历每一个区间进行合并；遍历期间需要维护一个用于合并的区间</strong>。</p><p><img lazyload src="/images/loading.svg" data-src="https://ec28649.webp.li/20250301135101578.png" alt="image-20240821213440340"></p><p>对于待合并的区间，只可能与维护的区间有三种可能性：</p><ol><li><p>待合并区间完全包括在维护的区间中</p><p><strong>无需更新维护区间</strong></p></li><li><p>待合并区间与维护的区间有部分交集（且只可能是<code>End</code>端不在维护的区间中）</p><p>**将维护区间的<code>End</code>换成待合并区间的<code>End</code>**。</p></li><li><p>待合并区间与维护的区间完全无交集</p><p><strong>当前维护的区间可以纳入答案中，将该待合并的区间作为新的维护区间</strong>。</p></li></ol><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> pair&lt;<span class="type">int</span>,<span class="type">int</span>&gt; PII;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">merge</span><span class="params">(vector&lt;PII&gt; &amp;segs)</span></span>&#123; <span class="comment">// 将所有存在交集的区间合并</span></span><br><span class="line">    vector&lt;PII&gt; res;</span><br><span class="line">    <span class="built_in">sort</span>(segs.<span class="built_in">begin</span>(), segs.<span class="built_in">end</span>());  <span class="comment">//默认以左区间进行排序</span></span><br><span class="line">    <span class="type">int</span> st = <span class="number">-2e9</span>, ed = <span class="number">-2e9</span>;  <span class="comment">// 设置区间的边界</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">auto</span> seg : segs)</span><br><span class="line">        <span class="keyword">if</span> (ed &lt; seg.first)&#123;  <span class="comment">// 新的区间与维护的区间无交集</span></span><br><span class="line">            <span class="keyword">if</span> (st != <span class="number">-2e9</span>) res.<span class="built_in">push_back</span>(&#123;st, ed&#125;);</span><br><span class="line">            st = seg.first, ed = seg.second;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">else</span> ed = <span class="built_in">max</span>(ed, seg.second); </span><br><span class="line">    <span class="keyword">if</span> (st != <span class="number">-2e9</span>) res.<span class="built_in">push_back</span>(&#123;st, ed&#125;);</span><br><span class="line">    segs = res;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><h3 id="KMP算法"><a href="#KMP算法" class="headerlink" title="KMP算法"></a><a class="link" href="hook://file/fpfh2fI1G?p=UmVzb3VyY2VzL3ZpZGVvcw==&n=KMP%2Emov">KMP算法 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a></h3><p>KMP算法用来解决子串与父串进行匹配的问题，可以将时间复杂度为$O(n^2)$的暴力匹配变成时间复杂度为$O(n+m)$。KMP算法的核心思想是<strong>利用已经匹配的信息来避免重复匹配，从而提高匹配效率</strong>。next数组记录了模式字符串中<strong>每个位置的前缀和后缀的最长公共部分的长度</strong>（最长相同前后缀or模式串匹配失败后应该重新开始匹配的位置）。</p><p><strong>特别需要注意的是：主串和模板串都从第一个位置开始存储。</strong></p><p><a class="link" href="https://www.bilibili.com/video/BV1AY4y157yL/">手算next数组 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a></p><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="type">const</span> <span class="type">int</span> N=<span class="number">1e6</span>+<span class="number">10</span>,M=<span class="number">1e6</span>+<span class="number">10</span>;</span><br><span class="line"><span class="type">char</span> str1[N],str2[M];</span><br><span class="line"><span class="type">int</span> ne[M];</span><br><span class="line"><span class="type">int</span> n,m;</span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">get_next</span><span class="params">()</span></span>&#123;  </span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">2</span>,j=<span class="number">0</span>;i&lt;=m;i++)&#123; <span class="comment">//从模式字符串的第二个字符开始遍历（因为部分匹配表的第一个值总是0）</span></span><br><span class="line">        <span class="keyword">while</span>(j &amp;&amp; str2[i]!=str2[j+<span class="number">1</span>]) j=ne[j]; <span class="comment">//如果当前字符和前缀字符不匹配，并且j不为0，则回溯到前一个部分匹配的位置</span></span><br><span class="line">        <span class="keyword">if</span>(str2[i]==str2[j+<span class="number">1</span>]) j++; <span class="comment">//如果当前字符和前缀字符匹配，则j加1</span></span><br><span class="line">        ne[i]=j;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">kmp</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">1</span>,j=<span class="number">0</span>;i&lt;=n;i++)&#123;</span><br><span class="line">        <span class="keyword">while</span>(j &amp;&amp; str1[i]!=str2[j+<span class="number">1</span>]) j=ne[j];</span><br><span class="line">        <span class="keyword">if</span>(str1[i]==str2[j+<span class="number">1</span>]) j++;</span><br><span class="line">        <span class="keyword">if</span>(j==m)&#123;</span><br><span class="line">            <span class="built_in">printf</span>(<span class="string">&quot;%d &quot;</span>,i-m);</span><br><span class="line">            j=ne[j];  <span class="comment">//子串完全匹配后，下一个字符肯定不再相等，因此j需要重新回溯</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span>&#123;</span><br><span class="line">    cin&gt;&gt;n&gt;&gt;str1+<span class="number">1</span>&gt;&gt;m&gt;&gt;str2+<span class="number">1</span>;  <span class="comment">//两个字符数组下标从1开始存储</span></span><br><span class="line">    <span class="built_in">get_next</span>();</span><br><span class="line">    <span class="built_in">kmp</span>();</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><h4 id="最短回文串"><a href="#最短回文串" class="headerlink" title="最短回文串"></a><a class="link" href="https://leetcode.cn/problems/shortest-palindrome/">最短回文串 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a></h4><p>理解KMP算法的关键一步是理解next数组的含义，即<strong>字符串中每个位置的最长相等前后缀的长度</strong>，这种特性能很好地与回文串结合。</p><p>将任意串<code>baacbca</code>翻转再拼接原串<code>acbcaabbaacbca</code>的next数组为<code>00001100112345</code>，很有意思的是<strong>next数组的最后一个值就是原串中存在的一个最长回文串<code>[arr.end()-5+1,arr.end()]</code>的长度。</strong>假若需要将原串在一个方向添加字符拼凑成新的回文串，则只需在原串的末尾拼凑上原串<code>[arr.begin(),arr.begin()+arr.size()-5]</code>的字符即可。</p><div class="highlight-container" data-rel="Cpp"><figure class="iseeu highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">string <span class="title">shortestPalindrome</span><span class="params">(string s)</span> </span>&#123;</span><br><span class="line">    <span class="type">const</span> <span class="type">static</span> <span class="type">int</span> maxn=<span class="number">1e5</span>+<span class="number">10</span>;</span><br><span class="line">    <span class="type">int</span> ne[maxn];</span><br><span class="line">    string s1=<span class="string">&quot; &quot;</span>+s+<span class="string">&quot; &quot;</span>+<span class="built_in">string</span>(s.<span class="built_in">rbegin</span>(),s.<span class="built_in">rend</span>());  <span class="comment">//第一个空格是为了next数组计算，第二个空格是防止原串全为相同字符，导致回文串长度超过原串长度</span></span><br><span class="line">    <span class="type">int</span> len=s1.<span class="built_in">size</span>();</span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> j=<span class="number">0</span>,i=<span class="number">2</span>;i&lt;len;i++)&#123;</span><br><span class="line">        <span class="keyword">while</span>(j &amp;&amp; s1[i]!=s1[j+<span class="number">1</span>]) j=ne[j];</span><br><span class="line">        <span class="keyword">if</span>(s1[i]==s1[j+<span class="number">1</span>]) j++;</span><br><span class="line">        ne[i]=j;</span><br><span class="line">    &#125;</span><br><span class="line">    string s2=s.<span class="built_in">substr</span>(<span class="number">0</span>,ne[len<span class="number">-1</span>]);</span><br><span class="line">    string s3=s.<span class="built_in">substr</span>(ne[len<span class="number">-1</span>]);</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">string</span>(s3.<span class="built_in">rbegin</span>(),s3.<span class="built_in">rend</span>())+s2+s3;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><h4 id="重复的子字符串"><a href="#重复的子字符串" class="headerlink" title="重复的子字符串"></a><a class="link" href="https://leetcode.cn/problems/repeated-substring-pattern/description/">重复的子字符串 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a></h4><p>KMP算法在应用层面最重要的就是场景识别，即在具体问题中能够抽象出使用KMP算法的场景，即模式串匹配。</p><div class="highlight-container" data-rel="Cpp"><figure class="iseeu highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">const</span> <span class="type">static</span> <span class="type">int</span> maxn=<span class="number">2e4</span>+<span class="number">10</span>;</span><br><span class="line"><span class="type">int</span> ne[maxn];</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">getnext</span><span class="params">(<span class="type">const</span> string&amp; s)</span></span>&#123;</span><br><span class="line">    <span class="type">int</span> len=s.<span class="built_in">size</span>();</span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> j=<span class="number">0</span>,i=<span class="number">2</span>;i&lt;len;i++)&#123;</span><br><span class="line">        <span class="keyword">while</span>(j &amp;&amp; s[i]!=s[j+<span class="number">1</span>]) j=ne[j];</span><br><span class="line">        <span class="keyword">if</span>(s[i]==s[j+<span class="number">1</span>]) j++;</span><br><span class="line">        ne[i]=j;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">bool</span> <span class="title">repeatedSubstringPattern</span><span class="params">(string s)</span> </span>&#123;</span><br><span class="line">    string ss=s+s;</span><br><span class="line">    <span class="type">int</span> len = ss.<span class="built_in">size</span>();</span><br><span class="line">    s=<span class="string">&quot; &quot;</span>+s;</span><br><span class="line">    <span class="built_in">getnext</span>(s);</span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> j=<span class="number">0</span>,i=<span class="number">1</span>;i&lt;len<span class="number">-1</span>;i++)&#123;</span><br><span class="line">        <span class="keyword">while</span>(j &amp;&amp; ss[i]!=s[j+<span class="number">1</span>]) j=ne[j];</span><br><span class="line">        <span class="keyword">if</span>(ss[i]==s[j+<span class="number">1</span>]) j++;</span><br><span class="line">        <span class="keyword">if</span>(j==s.<span class="built_in">size</span>()<span class="number">-1</span>) <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><h2 id="基础数据结构"><a href="#基础数据结构" class="headerlink" title="基础数据结构"></a>基础数据结构</h2><h3 id="STL"><a href="#STL" class="headerlink" title="STL"></a>STL</h3><h4 id="vector"><a href="#vector" class="headerlink" title="vector"></a>vector</h4><h4 id="deque"><a href="#deque" class="headerlink" title="deque"></a>deque</h4><h4 id="stack"><a href="#stack" class="headerlink" title="stack"></a>stack</h4><h4 id="queue"><a href="#queue" class="headerlink" title="queue"></a>queue</h4><h4 id="priority-queue"><a href="#priority-queue" class="headerlink" title="priority_queue"></a>priority_queue</h4><h4 id="list"><a href="#list" class="headerlink" title="list"></a>list</h4><h4 id="集合系列"><a href="#集合系列" class="headerlink" title="集合系列"></a>集合系列</h4><h5 id="set"><a href="#set" class="headerlink" title="set"></a>set</h5><ul><li>底层结构：set底层是由红黑树实现（红黑树是一种平衡二叉树），存储空间不连续；</li><li>访问遍历：不支持随机访问迭代器，不能通过下标直接访问，支持按顺序迭代，迭代器遍历的顺序是从小到大的顺序；</li><li>操作效率：查询&#x2F;插入&#x2F;删除的效率都为$O(logn)$；</li><li>排序方式 : 默认使用<code>less</code>仿函数 , 即<code>&lt;</code>运算符进行排序 ; 也可以自定义排序规则仿函数 ;</li><li>使用场景：<strong>需要 集合有序 且 元素不重复 的场景；</strong></li></ul><h5 id="multiset"><a href="#multiset" class="headerlink" title="multiset"></a>multiset</h5><ul><li>底层结构 : 底层由 红黑树 实现 , 红黑树 是一种<strong>平衡二叉搜索树</strong> , 存储空间不连续;</li><li>访问遍历 : 不支持 随机访问迭代器 , 不能听过下标访问 , 支持按顺序迭代，迭代器遍历的顺序是从小到大的顺序；</li><li>操作效率：查询 &#x2F; 插入 &#x2F; 删除 效率 为 O(log n) 复杂度;</li><li>排序方式 : 默认使用<code>less</code>仿函数 , 即<code>&lt;</code>运算符进行排序 ; 也可以自定义排序规则仿函数;</li><li>使用场景 : 需要 <strong>集合有序</strong> 且 <strong>元素重复</strong> 的场景;</li></ul><h5 id="unordered-set"><a href="#unordered-set" class="headerlink" title="unordered_set"></a>unordered_set</h5><ul><li>底层结构 : 底层由 <strong>哈希表</strong> 实现 , <strong>存储空间不连续</strong>;</li><li>访问遍历 : 不支持 随机访问迭代器 , 不能听过下标访问;</li><li><strong>操作效率：插入、删除和查找操作的平均时间复杂度为 O(1)，但在最坏情况下可能达到 O(n)；</strong></li><li>使用场景 : 更适合 <strong>频繁插入和查找操作</strong> 且 <strong>不关心元素顺序</strong> 的场景；</li></ul><h4 id="字典系列"><a href="#字典系列" class="headerlink" title="字典系列"></a>字典系列</h4><blockquote><p>[!Tip]</p><p>字典容器 与 集合容器 的区别是字典容器存储的是 <strong>键值对元素 , 是 pair 对象</strong>； 集合容器 存储的是 单纯的 <strong>键单个元素</strong> ;</p></blockquote><h5 id="map"><a href="#map" class="headerlink" title="map"></a>map</h5><ul><li>底层结构 : 底层由 红黑树 实现 , 红黑树 是 一种 平衡二叉搜索树 , 存储空间 不连续 ; 存储的 元素 是 键值对 元素;</li><li>访问遍历 : 不支持 随机访问迭代器 , 不能听过下标访问 , 只能通过迭代器进行访问;</li><li>操作效率：查询 &#x2F; 插入 &#x2F; 删除 效率 为 O(log n) 复杂度 ; 与 set 集合容器相同;</li><li>排序方式 : 默认使用<code>less</code>仿函数 , 即<code>&lt;</code>运算符进行排序 ; 也可以自定义排序规则仿函数 ; map 映射容器 不允许重复的键 , multimap 多重映射容器允许重复的键;</li><li>使用场景 : 需要 有序 键值对 且 <strong>元素 不重复</strong> 的场景;</li></ul><h5 id="multimap"><a href="#multimap" class="headerlink" title="multimap"></a>multimap</h5><ul><li>底层结构: 底层由 红黑树 实现，红黑树 是 一种 平衡二叉搜索树，存储空间 不连续， 存储的 元素 是 键值对 元素 ;</li><li>访问遍历: 不支持 随机访问迭代器 , 不能听过下标访问 , 只能通过迭代器进行访问 ;</li><li>操作效率：查询 &#x2F; 插入 &#x2F; 删除 效率 为 O(log n) 复杂度 ; 与 set 集合容器相同 ;</li><li>排序方式：默认使用<code>less</code>仿函数 , 即<code>&lt;</code>运算符进行排序 ; 也可以自定义排序规则仿函数 ; map 映射容器 不允许重复的键 , multimap 多重映射容器允许重复的键;</li><li>使用场景: 需要 有序 键值对 且 <strong>元素 重复</strong> 的场景 ;</li></ul><h5 id="unordered-map"><a href="#unordered-map" class="headerlink" title="unordered_map"></a>unordered_map</h5><ul><li>底层结构：set底层是由红黑树实现（红黑树是一种平衡二叉树），存储空间不连续，存储的 元素 是 键值对 元素 ;</li><li><strong>操作效率：插入、删除和查找操作的平均时间复杂度为 O(1)，但在最坏情况下可能达到 O(n)；</strong></li><li>使用场景 : 更适合 <strong>频繁插入和查找操作</strong> 且 <strong>不关心元素顺序</strong> 的场景；</li></ul><h4 id="bitset"><a href="#bitset" class="headerlink" title="bitset"></a>bitset</h4><h3 id="模拟链表"><a href="#模拟链表" class="headerlink" title="模拟链表"></a>模拟链表</h3><h4 id="单链表"><a href="#单链表" class="headerlink" title="单链表"></a>单链表</h4><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">const</span> <span class="type">int</span> N =<span class="number">1e6</span>+<span class="number">10</span>;</span><br><span class="line"><span class="type">int</span> head,idx; <span class="comment">// head是链表的头结点，idx是当前链表指针</span></span><br><span class="line"><span class="type">int</span> e[N],ne[N]; <span class="comment">// e静态链表数组，ne是链表指针数组。即e数组存储节点的值，而ne存储节点的next指针</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">init</span><span class="params">()</span></span>&#123;</span><br><span class="line">  head=<span class="number">-1</span>; idx=<span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">add2head</span><span class="params">(<span class="type">int</span> c)</span></span>&#123;</span><br><span class="line">  e[idx]=c;</span><br><span class="line">  ne[idx]=head; </span><br><span class="line">  head=idx++;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">add</span><span class="params">(<span class="type">int</span> k,<span class="type">int</span> c)</span></span>&#123;</span><br><span class="line">  e[idx]=c;</span><br><span class="line">  ne[idx]=ne[k];</span><br><span class="line">  ne[k]=idx++;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">earse</span><span class="params">(<span class="type">int</span> k)</span></span>&#123;</span><br><span class="line">  ne[k]=ne[ne[k]];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><h4 id="双链表"><a href="#双链表" class="headerlink" title="双链表"></a>双链表</h4><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">const</span> <span class="type">int</span> N=<span class="number">100010</span>;</span><br><span class="line"><span class="type">int</span> e[N],l[N],r[N],idx;</span><br><span class="line"></span><br><span class="line"><span class="comment">//初始化</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">init</span><span class="params">()</span></span>&#123;</span><br><span class="line">    l[<span class="number">1</span>]=<span class="number">0</span>;r[<span class="number">0</span>]=<span class="number">1</span>;idx=<span class="number">2</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//在节点a的右边插入一个数x</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">insert</span><span class="params">(<span class="type">int</span> a,<span class="type">int</span> x)</span></span>&#123;</span><br><span class="line">    e[idx]=x;</span><br><span class="line">    l[idx]=a,r[idx]=r[a];</span><br><span class="line">    l[r[a]]=idx,r[a]=idx++;</span><br><span class="line">&#125; </span><br><span class="line"></span><br><span class="line"><span class="comment">//删除节点a</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">remove</span><span class="params">(<span class="type">int</span> a)</span></span>&#123;</span><br><span class="line">    l[r[a]]=l[a];</span><br><span class="line"> </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><h4 id="邻接表"><a href="#邻接表" class="headerlink" title="邻接表"></a>邻接表</h4><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> h[N], e[N], ne[N], idx;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">init</span><span class="params">()</span></span>&#123;</span><br><span class="line">    idx = <span class="number">0</span>;</span><br><span class="line">  <span class="built_in">memset</span>(h, <span class="number">-1</span>, <span class="keyword">sizeof</span> h);</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 添加一条边a-&gt;b</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">add</span><span class="params">(<span class="type">int</span> a, <span class="type">int</span> b)</span></span>&#123;</span><br><span class="line">    e[idx]=b;ne[idx]=h[a];h[a]=idx++;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><h3 id="模拟栈"><a href="#模拟栈" class="headerlink" title="模拟栈"></a>模拟栈</h3><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">const</span> <span class="type">int</span> N=<span class="number">1e6</span>+<span class="number">10</span>;</span><br><span class="line"><span class="type">int</span> stk[N],tt;</span><br><span class="line"><span class="comment">//push</span></span><br><span class="line">stk[++tt]=x;</span><br><span class="line"><span class="comment">//pop</span></span><br><span class="line">tt--;</span><br><span class="line"><span class="comment">//judge empty</span></span><br><span class="line"><span class="keyword">if</span>(tt&gt;<span class="number">0</span>) <span class="keyword">not</span> empty</span><br><span class="line"><span class="keyword">else</span> empty</span><br><span class="line"><span class="comment">//top of stack</span></span><br><span class="line">stk[tt];</span><br></pre></td></tr></table></figure></div><h3 id="模拟队列"><a href="#模拟队列" class="headerlink" title="模拟队列"></a>模拟队列</h3><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">const</span> <span class="type">int</span> N=<span class="number">1e6</span>+<span class="number">10</span>;</span><br><span class="line"><span class="type">int</span> que[N],hh,tt=<span class="number">-1</span>;  <span class="comment">//hh是队头，tt是队尾</span></span><br><span class="line"><span class="comment">//push</span></span><br><span class="line">que[++tt]=x;</span><br><span class="line"><span class="comment">//pop</span></span><br><span class="line">hh++;</span><br><span class="line"><span class="comment">//judge empty</span></span><br><span class="line"><span class="keyword">if</span>(hh&lt;=tt) <span class="keyword">not</span> empty</span><br><span class="line"><span class="keyword">else</span> empty</span><br><span class="line"><span class="comment">//front of queue</span></span><br><span class="line">que[hh];</span><br></pre></td></tr></table></figure></div><p>如果要实现循环队列，只需要每次在插入值时对<code>tt</code>进行判断，如果其等于N就将其赋值为0。或者直接<code>que[++tt%N]=x;</code>，当然这样就要注意操作次数，防止整数溢出。</p><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">const</span> <span class="type">int</span> N = <span class="number">1e6</span> + <span class="number">10</span>;</span><br><span class="line"><span class="type">int</span> que[N], hh = <span class="number">0</span>, tt = <span class="number">0</span>;  <span class="comment">// hh是队头，tt是队尾</span></span><br><span class="line"><span class="function"><span class="type">bool</span> <span class="title">isEmpty</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> hh == tt;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">bool</span> <span class="title">isFull</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> (tt + <span class="number">1</span>) % N == hh;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">push</span><span class="params">(<span class="type">int</span> x)</span> </span>&#123;</span><br><span class="line">    que[tt] = x;</span><br><span class="line">    tt = (tt + <span class="number">1</span>) % N;  <span class="comment">// 更新队尾，使用取模实现循环</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">pop</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    hh = (hh + <span class="number">1</span>) % N;  <span class="comment">// 更新队头，使用取模实现循环</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">front</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> que[hh];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><h3 id="单调栈"><a href="#单调栈" class="headerlink" title="单调栈"></a>单调栈</h3><blockquote><p>[!Caution]</p><p>常见题型：<strong>找出区间每个数左边（右边）离它最近的比它大&#x2F;小的数</strong>，特别是“最近”两字！</p><p><strong>单调增栈求更大，单调减栈求更小</strong></p><p>OJ例题：<a class="link" href="https://www.luogu.com.cn/problem/P5788">P5788 【模板】单调栈 - 洛谷 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a></p></blockquote><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">const</span> <span class="type">int</span> N=<span class="number">1e6</span>+<span class="number">10</span>;</span><br><span class="line"><span class="type">int</span> stk[N],tt = <span class="number">0</span>;</span><br><span class="line"><span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i &lt;= n; i ++ )  <span class="comment">//左边</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">while</span> (tt &amp;&amp; <span class="built_in">check</span>(stk[tt], i)) tt--;  <span class="comment">// 单调栈就是始终需要维护一个单调的栈</span></span><br><span class="line">  <span class="keyword">if</span>(tt) find the value stk[tt];</span><br><span class="line">  <span class="keyword">else</span> <span class="keyword">not</span> find;</span><br><span class="line">    stk[++tt] = i;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">for</span> (<span class="type">int</span> i = n<span class="number">-1</span>; i &gt;= <span class="number">0</span>; i -- )  <span class="comment">//右边</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">while</span> (tt &amp;&amp; <span class="built_in">check</span>(stk[tt], i)) tt--;  <span class="comment">// 单调栈就是始终需要维护一个单调的栈</span></span><br><span class="line">  <span class="keyword">if</span>(tt) find the value stk[tt];</span><br><span class="line">  <span class="keyword">else</span> <span class="keyword">not</span> find;</span><br><span class="line">    stk[++tt] = i;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">pre.<span class="built_in">push_back</span>(INT_MAX);  <span class="comment">//这一步相当关键，这里push的值需要根据单调增还是减栈确定，增站用max，减栈用min</span></span><br><span class="line"><span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt;= len; i++) &#123; <span class="comment">//左右两边</span></span><br><span class="line">    <span class="keyword">while</span> (tt &amp;&amp; pre[stk[tt]] &lt;= pre[i]) &#123; <span class="comment">// 单调增栈</span></span><br><span class="line">        <span class="type">int</span> idx = stk[tt--];</span><br><span class="line">        res1[idx] = (tt == <span class="number">0</span> ? <span class="number">-1</span> : stk[tt]);  <span class="comment">//左端</span></span><br><span class="line">        res2[idx] = (i == len ? <span class="number">-1</span> : i);  <span class="comment">//右端</span></span><br><span class="line">    &#125;</span><br><span class="line">    stk[++tt] = i;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><h4 id="接雨水"><a href="#接雨水" class="headerlink" title="接雨水"></a><a class="link" href="https://leetcode.cn/problems/trapping-rain-water/description/">接雨水 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a></h4><div class="highlight-container" data-rel="Cpp"><figure class="iseeu highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">const</span> <span class="type">static</span> <span class="type">int</span> N = <span class="number">2e4</span> + <span class="number">10</span>;</span><br><span class="line"><span class="type">int</span> stk[N], tt = <span class="number">0</span>;</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">trap</span><span class="params">(vector&lt;<span class="type">int</span>&gt; &amp;height)</span> </span>&#123;</span><br><span class="line">    <span class="type">int</span> res1[N], res2[N];</span><br><span class="line">    <span class="type">int</span> len = height.<span class="built_in">size</span>();</span><br><span class="line">    height.<span class="built_in">push_back</span>(INT_MAX);</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt;= len; i++) &#123;</span><br><span class="line">        <span class="keyword">while</span> (tt &amp;&amp; height[stk[tt]] &lt; height[i]) &#123; <span class="comment">// 单调增栈</span></span><br><span class="line">            <span class="type">int</span> idx = stk[tt--];</span><br><span class="line">            res1[idx] = (tt == <span class="number">0</span> ? <span class="number">-1</span> : stk[tt]);</span><br><span class="line">            res2[idx] = (i == len ? <span class="number">-1</span> : i);</span><br><span class="line">        &#125;</span><br><span class="line">        stk[++tt] = i;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="type">int</span> sum = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; len; i++)</span><br><span class="line">        <span class="keyword">if</span> (res1[i] != <span class="number">-1</span> &amp;&amp; res2[i] != <span class="number">-1</span>)</span><br><span class="line">        sum += (<span class="built_in">min</span>(height[res1[i]], height[res2[i]]) - height[i])*(res2[i]-res1[i]<span class="number">-1</span>);</span><br><span class="line">    <span class="keyword">return</span> sum;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><blockquote><p> [!WARNING]</p><p>这个LeetCode的题“<a class="link" href="https://leetcode.cn/problems/trapping-rain-water/description/">接雨水 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a>”，看似可以利用左右两边比其大而形成蓄水部位的特性利用单调栈解题，<del>但是忽略了“最近”这个局部的概念而导致很难正确求解</del>。</p><p><font color="red"><strong>补：利用这个局部也是能正确求解的，使用分层的思想。</strong></font></p><p><img lazyload src="/images/loading.svg" data-src="https://ec28649.webp.li/20250301135119329.png" alt="image-20240822004148780"></p><p><code>height = [0,1,0,4,1,0,1,3,2,1,2,1]</code></p><p>假如把左&#x2F;右没有比起更大的值记为-1，那么每个元素其左边最近更大的下标序列为：</p><p><code>-1 -1 1 -1 3 4 3 3 7 8 7 10</code></p><p>其右边最近更大的下标序列为：</p><p><code>1 3 3 -1 7 6 7 -1 -1 10 -1 -1</code></p><p>将左右两边序列结合观察，只有在<code>2,4,5,6,9</code>这几个位置蓄水（对应位置均不为-1）。第5个位置就暴露出“最近”这个局部概念的问题，其得到的左右两边更大的，能够形成的局部蓄水高度并不是其全局蓄水高度。</p><p>因此，想要从这直接计算每个位置的最大蓄水高度显然是不行的。但也很好解决，只要确定每个可蓄水位的最大蓄水高度就能够完成最终求解。</p><p>左侧最大蓄水高度：<code>[0,1,1,4,4,4,4,4,4,4,4,4]</code></p><p>右侧最大蓄水高度：<code>[4,4,4,4,3,3,3,3,2,2,2,1]</code></p><div class="highlight-container" data-rel="Cpp"><figure class="iseeu highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">const</span> <span class="type">static</span> <span class="type">int</span> N = <span class="number">2e4</span>+<span class="number">10</span>;</span><br><span class="line"><span class="type">int</span> stk[N],tt=<span class="number">0</span>;</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">trap</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; height)</span> </span>&#123;</span><br><span class="line">    <span class="type">int</span> lmax[N],rmax[N];</span><br><span class="line">    vector&lt;<span class="type">int</span>&gt; res1,res2;</span><br><span class="line">    <span class="type">int</span> len=height.<span class="built_in">size</span>();</span><br><span class="line">    lmax[<span class="number">0</span>]=height[<span class="number">0</span>],rmax[len<span class="number">-1</span>]=height[len<span class="number">-1</span>];</span><br><span class="line">  </span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">1</span>;i&lt;len;i++) lmax[i]=<span class="built_in">max</span>(lmax[i<span class="number">-1</span>],height[i]); </span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> i=len<span class="number">-2</span>;i&gt;=<span class="number">0</span>;i--) rmax[i]=<span class="built_in">max</span>(rmax[i+<span class="number">1</span>],height[i]); </span><br><span class="line">  </span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> i=len<span class="number">-1</span>;i&gt;=<span class="number">0</span>;i--)&#123; <span class="comment">//需要找右边离他最近且比他大or相等的元素</span></span><br><span class="line">        <span class="keyword">while</span>(tt &amp;&amp; height[stk[tt]]&lt;=height[i]) tt--;</span><br><span class="line">        <span class="keyword">if</span>(tt) res1.<span class="built_in">push_back</span>(stk[tt]);</span><br><span class="line">        <span class="keyword">else</span> res1.<span class="built_in">push_back</span>(<span class="number">-1</span>);</span><br><span class="line">        stk[++tt]=i;</span><br><span class="line">    &#125;</span><br><span class="line">    tt=<span class="number">0</span>;</span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">0</span>;i&lt;len;i++)&#123;</span><br><span class="line">        <span class="keyword">while</span>(tt &amp;&amp; height[stk[tt]]&lt;=height[i]) tt--;</span><br><span class="line">        <span class="keyword">if</span>(tt) res2.<span class="built_in">push_back</span>(stk[tt]);</span><br><span class="line">        <span class="keyword">else</span> res2.<span class="built_in">push_back</span>(<span class="number">-1</span>);</span><br><span class="line">        stk[++tt]=i;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="built_in">reverse</span>(res1.<span class="built_in">begin</span>(),res1.<span class="built_in">end</span>());</span><br><span class="line">    <span class="type">int</span> sum=<span class="number">0</span>;</span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">0</span>;i&lt;len;i++)</span><br><span class="line">        <span class="keyword">if</span>(res1[i]!=<span class="number">-1</span> &amp;&amp; res2[i]!=<span class="number">-1</span>)</span><br><span class="line">            sum+=<span class="built_in">min</span>(rmax[res1[i]],lmax[res2[i]])-height[i];</span><br><span class="line">    <span class="keyword">return</span> sum;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><p>上面的方法从左到右和从右到左用了两次单调栈来寻找左边和右边的更大元素。其实这是没必要的，<strong>可以只用一趟就能够同时完成寻找作用两边更大的元素</strong>。</p><p>模板还是一样，但思路有点不同。在两次单调栈中，我们每次都是将外循环<code>for</code>遍历到的元素当成基准元素来找寻离当前基准元素左&#x2F;右边更大的元素。<strong>但是在一次单调栈中，当前遍历到的元素不在基准元素，反而是左&#x2F;右边更大的元素</strong>。</p><p>我们假设外层的<code>for</code>循环是从左到右，且栈是单调增栈。<strong>当外循环遍历到元素<code>i</code>时，此时<code>i</code>为右边界，从内循环<code>while</code>出来后的栈顶元素为基准，下一个栈顶元素为左边界。</strong>此时寻找到左边界和右边界都是比基准元素<strong>更小且最近</strong>的值。</p><p>因此，上面的代码可以进一步优化为：</p><div class="highlight-container" data-rel="Cpp"><figure class="iseeu highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">const</span> <span class="type">static</span> <span class="type">int</span> N = <span class="number">2e4</span> + <span class="number">10</span>;</span><br><span class="line"><span class="type">int</span> stk[N], tt = <span class="number">0</span>;</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">trap</span><span class="params">(vector&lt;<span class="type">int</span>&gt; &amp;height)</span> </span>&#123;</span><br><span class="line">    <span class="type">int</span> lmax[N], rmax[N];</span><br><span class="line">    <span class="type">int</span> res1[N], res2[N];</span><br><span class="line">    <span class="type">int</span> len = height.<span class="built_in">size</span>();</span><br><span class="line">    lmax[<span class="number">0</span>] = height[<span class="number">0</span>], rmax[len - <span class="number">1</span>] = height[len - <span class="number">1</span>];</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i &lt; len; i++) lmax[i] = <span class="built_in">max</span>(lmax[i - <span class="number">1</span>], height[i]);</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = len - <span class="number">2</span>; i &gt;= <span class="number">0</span>; i--) rmax[i] = <span class="built_in">max</span>(rmax[i + <span class="number">1</span>], height[i]);</span><br><span class="line">    height.<span class="built_in">push_back</span>(INT_MAX);  <span class="comment">//这一步相当关键</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt;= len; i++) &#123;</span><br><span class="line">        <span class="keyword">while</span> (tt &amp;&amp; height[stk[tt]] &lt;= height[i]) &#123; <span class="comment">// 单调增栈</span></span><br><span class="line">            <span class="type">int</span> idx = stk[tt--];</span><br><span class="line">            res1[idx] = (tt == <span class="number">0</span> ? <span class="number">-1</span> : stk[tt]);</span><br><span class="line">            res2[idx] = (i == len ? <span class="number">-1</span> : i);</span><br><span class="line">        &#125;</span><br><span class="line">        stk[++tt] = i;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="type">int</span> sum = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; len; i++)</span><br><span class="line">        <span class="keyword">if</span> (res1[i] != <span class="number">-1</span> &amp;&amp; res2[i] != <span class="number">-1</span>)</span><br><span class="line">        sum += <span class="built_in">min</span>(lmax[res1[i]], rmax[res2[i]]) - height[i];</span><br><span class="line">    <span class="keyword">return</span> sum;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><p><font color="red">当然，尽管优化成一轮遍历，但其实如果按照这个思路求解，前面的找可蓄水位其实就是没必要的操作了！</font></p><p><font color="orange">因此我们尝试不去计算每个位置左右的最大蓄水高度，只用已求解的局部需水量进行分层计算。</font></p><div class="highlight-container" data-rel="Cpp"><figure class="iseeu highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">const</span> <span class="type">static</span> <span class="type">int</span> N = <span class="number">2e4</span> + <span class="number">10</span>;</span><br><span class="line"><span class="type">int</span> stk[N], tt = <span class="number">0</span>;</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">trap</span><span class="params">(vector&lt;<span class="type">int</span>&gt; &amp;height)</span> </span>&#123;</span><br><span class="line">    <span class="type">int</span> res1[N], res2[N];</span><br><span class="line">    <span class="type">int</span> len = height.<span class="built_in">size</span>();</span><br><span class="line">    height.<span class="built_in">push_back</span>(INT_MAX);</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt;= len; i++) &#123;</span><br><span class="line">        <span class="keyword">while</span> (tt &amp;&amp; height[stk[tt]] &lt; height[i]) &#123; <span class="comment">// 单调增栈</span></span><br><span class="line">            <span class="type">int</span> idx = stk[tt--];</span><br><span class="line">            res1[idx] = (tt == <span class="number">0</span> ? <span class="number">-1</span> : stk[tt]);</span><br><span class="line">            res2[idx] = (i == len ? <span class="number">-1</span> : i);</span><br><span class="line">        &#125;</span><br><span class="line">        stk[++tt] = i;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="type">int</span> sum = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; len; i++)</span><br><span class="line">        <span class="keyword">if</span> (res1[i] != <span class="number">-1</span> &amp;&amp; res2[i] != <span class="number">-1</span>)</span><br><span class="line">        sum += (<span class="built_in">min</span>(height[res1[i]], height[res2[i]]) - height[i])*(res2[i]-res1[i]<span class="number">-1</span>);</span><br><span class="line">    <span class="keyword">return</span> sum;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><p>类似的还有LeetCode<a class="link" href="https://leetcode.cn/problems/largest-rectangle-in-histogram/">柱状图中最大的矩形 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a>，这里构建的就是一个单调递减栈。</p><div class="highlight-container" data-rel="Cpp"><figure class="iseeu highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">int</span> <span class="title">largestRectangleArea</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; heights)</span> </span>&#123;</span><br><span class="line">    <span class="type">const</span> <span class="type">int</span> N=<span class="number">1e5</span>+<span class="number">10</span>;</span><br><span class="line">    <span class="type">int</span> stk[N],tt=<span class="number">0</span>;</span><br><span class="line">    <span class="type">int</span> len = heights.<span class="built_in">size</span>();</span><br><span class="line">    <span class="type">int</span> maxSize=<span class="number">0</span>;</span><br><span class="line">    heights.<span class="built_in">push_back</span>(INT_MIN);  <span class="comment">//单调递减栈，插INT_MIN；递增栈，插INT_MAX</span></span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">0</span>;i&lt;=len;i++)&#123;</span><br><span class="line">        <span class="keyword">while</span>(tt &amp;&amp; heights[stk[tt]]&gt;=heights[i]) &#123;  <span class="comment">//最近更小，单调减栈，遍历元素需更小；最近更大，单调递增栈，遍历元素需更大</span></span><br><span class="line">            <span class="type">int</span> idx=stk[tt--];</span><br><span class="line">            <span class="type">int</span> left = (tt==<span class="number">0</span> ? <span class="number">-1</span> : stk[tt]);  <span class="comment">//一般左边界都需要特判，右边界视题而定</span></span><br><span class="line">            maxSize=<span class="built_in">max</span>(maxSize,(i-left<span class="number">-1</span>)*heights[idx]);</span><br><span class="line">        &#125;</span><br><span class="line">        stk[++tt]=i;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> maxSize;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><p>假如我们不使用单调栈，则可以想到使用双指针的算法。</p><div class="highlight-container" data-rel="Cpp"><figure class="iseeu highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">int</span> <span class="title">trap</span><span class="params">(vector&lt;<span class="type">int</span>&gt; &amp;height)</span> </span>&#123;</span><br><span class="line">    <span class="type">int</span> len = height.<span class="built_in">size</span>();</span><br><span class="line">    <span class="type">int</span> l=<span class="number">0</span>,r=len<span class="number">-1</span>,maxl=<span class="number">0</span>,maxr=<span class="number">0</span>;</span><br><span class="line">    <span class="type">int</span> res=<span class="number">0</span>;</span><br><span class="line">    <span class="keyword">while</span>(l&lt;=r)&#123;</span><br><span class="line">        <span class="type">int</span> wal=<span class="built_in">min</span>(maxl,maxr);</span><br><span class="line">        <span class="keyword">if</span>(height[l]&lt;=wal)&#123;</span><br><span class="line">            res+=wal-height[l++];</span><br><span class="line">            <span class="keyword">continue</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span>(height[r]&lt;=wal)&#123;</span><br><span class="line">            res+=wal-height[r--];</span><br><span class="line">            <span class="keyword">continue</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        maxl=<span class="built_in">max</span>(maxl,height[l]);</span><br><span class="line">        maxr=<span class="built_in">max</span>(maxr,height[r]);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> res;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div></blockquote><h3 id="单调队列"><a href="#单调队列" class="headerlink" title="单调队列"></a>单调队列</h3><p>常见题型：求滑动窗口（窗口大小固定）中的<strong>最值</strong>，<strong>求单调队列中的任意值也可用二分。</strong></p><p><strong>单调队列中存储的都是Index，而不是Value。</strong></p><p>OJ例题：<a class="link" href="https://www.luogu.com.cn/problem/P1886">滑动窗口 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a>、<a class="link" href="https://leetcode.cn/problems/sliding-window-maximum/description/">滑动窗口最大值 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a>、<a class="link" href="https://leetcode.cn/problems/shortest-subarray-with-sum-at-least-k/">和至少为 K 的最短子数组 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a></p><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">const</span> <span class="type">int</span> N=<span class="number">1e6</span>+<span class="number">10</span>;</span><br><span class="line"><span class="type">int</span> n,k;  <span class="comment">// n是数的个数，k是区间长度</span></span><br><span class="line"><span class="type">int</span> arr[N],que[N];  <span class="comment">// arr用来存储数，que是单调队列，其中存储区间元素的下标</span></span><br><span class="line"><span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">0</span>;i&lt;n;i++)&#123;</span><br><span class="line">    <span class="keyword">if</span>(hh&lt;=tt &amp;&amp; i-que[hh]+<span class="number">1</span>&gt;k) hh++;  <span class="comment">// 如果队列超过区间长度</span></span><br><span class="line">    <span class="keyword">while</span>(hh&lt;=tt &amp;&amp; arr[que[tt]]&gt;=arr[i]) tt--; <span class="comment">// 维护单调队列，如果当前元素要小于单调队列的队尾元素，则队尾元素弹出，确保队列单调递增，队头能够取到最小值。</span></span><br><span class="line">    que[++tt]=i; <span class="comment">// 将当前元素插入</span></span><br><span class="line">    <span class="keyword">if</span>(i&gt;=k<span class="number">-1</span>) <span class="built_in">printf</span>(<span class="string">&quot;%d &quot;</span>, arr[que[hh]]);  <span class="comment">//当窗口全部滑入数组上后判定</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><h4 id="和至少为-K-的最短子数组"><a href="#和至少为-K-的最短子数组" class="headerlink" title="和至少为 K 的最短子数组"></a><a class="link" href="https://leetcode.cn/problems/shortest-subarray-with-sum-at-least-k/">和至少为 K 的最短子数组 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a></h4><p>这个题目很好的将单调队列和前缀和融合在一起！</p><p>这个题目必须用前缀和的原因有两点：</p><p>1、前缀和在计算区间和时只需O(1)的代价</p><p>2、<strong>不用前缀和不能满足题目所要求的连续区间</strong>。因为在获取单调队列时，必然会导致队列中的元素不再连续（虽然相对位置关系依旧保留）。只有使用前缀和，其中的每一个值代表一个区间的和，才在以离散的状态构建单调队列的同时保持连续区间的要求（两个前缀和数组的值作差能表示一个区间的和）。</p><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="type">long</span> <span class="type">long</span> ll;</span><br><span class="line"><span class="type">const</span> <span class="type">static</span> <span class="type">int</span> N =<span class="number">1e5</span>+<span class="number">10</span>;</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">shortestSubarray</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; nums, <span class="type">int</span> k)</span> </span>&#123;</span><br><span class="line">    <span class="type">int</span> len=nums.<span class="built_in">size</span>();</span><br><span class="line">    ll sum[N];</span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">1</span>;i&lt;=len;i++) sum[i]=sum[i<span class="number">-1</span>]+nums[i<span class="number">-1</span>];</span><br><span class="line">    ll que[N],hh=<span class="number">0</span>,tt=<span class="number">-1</span>;</span><br><span class="line">    ll minL=INT_MAX;</span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">0</span>;i&lt;=len;i++)&#123; <span class="comment">//前缀和得从0开始！</span></span><br><span class="line">        <span class="keyword">while</span>(hh&lt;=tt &amp;&amp; sum[i]-sum[que[hh]]&gt;=k) minL=<span class="built_in">min</span>(minL,i-que[hh++]);</span><br><span class="line">        <span class="keyword">while</span>(hh&lt;=tt &amp;&amp; sum[que[tt]]&gt;=sum[i]) tt--;</span><br><span class="line">        que[++tt]=i;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> minL == INT_MAX ? <span class="number">-1</span> : minL;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><h4 id="环形子数组的最大和"><a href="#环形子数组的最大和" class="headerlink" title="环形子数组的最大和"></a><a class="link" href="https://leetcode.cn/problems/maximum-sum-circular-subarray/">环形子数组的最大和 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a></h4><p>环形问题拉直处理。依旧是前缀和+单调队列。</p><p>限制是区间长度不大于数组长度。</p><p>当然其实拉直处理，可以用模运算实现，而不是真实地再复制数组一遍。</p><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">const</span> <span class="type">static</span> <span class="type">int</span> N = <span class="number">6e4</span>+<span class="number">10</span>;</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">maxSubarraySumCircular</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; nums)</span> </span>&#123;</span><br><span class="line">    <span class="type">int</span> k = nums.<span class="built_in">size</span>(),len=k*<span class="number">2</span>;</span><br><span class="line">    nums.<span class="built_in">insert</span>(nums.<span class="built_in">end</span>(),nums.<span class="built_in">begin</span>(),nums.<span class="built_in">end</span>());</span><br><span class="line">    <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">sum</span><span class="params">(len+<span class="number">1</span>)</span></span>;</span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">1</span>;i&lt;=len;i++)sum[i]=sum[i<span class="number">-1</span>]+nums[i<span class="number">-1</span>];</span><br><span class="line">    <span class="type">int</span> maxV=INT_MIN;</span><br><span class="line">    <span class="type">int</span> que[N],hh=<span class="number">0</span>,tt=<span class="number">-1</span>;</span><br><span class="line">    <span class="built_in">memset</span>(que,<span class="number">0</span>,<span class="keyword">sizeof</span> que);</span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">0</span>;i&lt;=len;i++)&#123;</span><br><span class="line">        <span class="keyword">if</span>(hh&lt;=tt &amp;&amp; i-que[hh]&gt;k) hh++;</span><br><span class="line">        <span class="keyword">while</span>(hh&lt;=tt &amp;&amp; sum[que[tt]] &gt; sum[i]) tt--;</span><br><span class="line">        <span class="keyword">if</span>(i&gt;k<span class="number">-1</span>) maxV=<span class="built_in">max</span>(maxV,sum[i]-sum[que[hh]]);</span><br><span class="line">        que[++tt]=i;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> maxV;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><h3 id="堆"><a href="#堆" class="headerlink" title="堆"></a>堆</h3><p>堆是一棵完全二叉树，使用数组从1进行存储。堆可以动态的维护一个区间内的最小值&#x2F;最大值。</p><p>向堆中插入一个数：<code>heap[++size]=x;up(size);</code></p><p>求堆中最小值：<code>heap[1];</code></p><p>删除堆中的最小值：<code>swap(heap[1],heap[size]);size--;down(1);</code></p><p>删除堆中任意一个元素：<code>swap(heap[k],heap[size]);size--;down(k);up(k);</code></p><p>修改任意一个元素：<code>heap[k]=c;down(k);up(k);</code></p><p><strong>堆的数组存储是从下标1开始的。</strong></p><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">const</span> <span class="type">int</span> N=<span class="number">1e5</span>+<span class="number">10</span>;</span><br><span class="line"><span class="type">int</span> h[N],n,size;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">down</span><span class="params">(<span class="type">int</span> x)</span></span>&#123;</span><br><span class="line">  <span class="type">int</span> tmp=x;</span><br><span class="line">  <span class="keyword">if</span>(x*<span class="number">2</span>&lt;=size &amp;&amp; h[x*<span class="number">2</span>]&lt;h[tmp]) tmp=x*<span class="number">2</span>;</span><br><span class="line">  <span class="keyword">if</span>(x*<span class="number">2</span>+<span class="number">1</span>&lt;=size &amp;&amp; h[x*<span class="number">2</span>+<span class="number">1</span>]&lt;h[tmp]) tmp=x*<span class="number">2</span>+<span class="number">1</span>;</span><br><span class="line">  <span class="keyword">if</span>(tmp!=x) &#123;</span><br><span class="line">      <span class="built_in">swap</span>(h[tmp],h[x]);</span><br><span class="line">      <span class="built_in">down</span>(tmp);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">up</span><span class="params">(<span class="type">int</span> x)</span></span>&#123;</span><br><span class="line">  <span class="keyword">while</span>(x/<span class="number">2</span> &amp;&amp; h[x]&lt;h[x/<span class="number">2</span>]) &#123;<span class="built_in">swap</span>(h[x],h[x/<span class="number">2</span>]);x&gt;&gt;=<span class="number">1</span>;&#125;  <span class="comment">//小根堆</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">build</span><span class="params">()</span></span>&#123;  <span class="comment">//建堆，时间复杂度为O(n)</span></span><br><span class="line">  <span class="keyword">for</span>(<span class="type">int</span> i=n/<span class="number">2</span>;i&gt;<span class="number">0</span>;i--) <span class="built_in">down</span>(i); </span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">push</span><span class="params">(<span class="type">int</span> c)</span></span>&#123;</span><br><span class="line">  h[++size]=c;</span><br><span class="line">  <span class="built_in">up</span>(size);</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">pop</span><span class="params">()</span></span>&#123;</span><br><span class="line">  <span class="built_in">swap</span>(h[<span class="number">1</span>],h[size]);</span><br><span class="line">  size--;</span><br><span class="line">  <span class="built_in">down</span>(<span class="number">1</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">pop_k</span><span class="params">(<span class="type">int</span> k)</span></span>&#123;</span><br><span class="line">  <span class="built_in">swap</span>(h[k],h[size]);</span><br><span class="line">  size--;</span><br><span class="line">  <span class="built_in">down</span>(k);<span class="built_in">up</span>(k);</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">revise</span><span class="params">(<span class="type">int</span> k,<span class="type">int</span> c)</span></span>&#123;</span><br><span class="line">  h[k]=c;</span><br><span class="line">  <span class="built_in">down</span>(k);<span class="built_in">up</span>(k);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><h4 id="P3378-【模板】堆-洛谷"><a href="#P3378-【模板】堆-洛谷" class="headerlink" title="P3378 【模板】堆 - 洛谷"></a><a class="link" href="https://www.luogu.com.cn/problem/P3378">P3378 【模板】堆 - 洛谷 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a></h4><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;bits/stdc++.h&gt;</span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"><span class="type">const</span> <span class="type">int</span> N=<span class="number">1e6</span>+<span class="number">10</span>;</span><br><span class="line"><span class="type">int</span> h[N],n,size;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">down</span><span class="params">(<span class="type">int</span> k)</span></span>&#123;</span><br><span class="line">    <span class="type">int</span> tmp=k;</span><br><span class="line">    <span class="keyword">if</span>(k*<span class="number">2</span>&lt;=size &amp;&amp; h[tmp]&gt;h[k*<span class="number">2</span>]) tmp=k*<span class="number">2</span>;</span><br><span class="line">    <span class="keyword">if</span>(k*<span class="number">2</span>+<span class="number">1</span>&lt;=size &amp;&amp; h[tmp]&gt;h[k*<span class="number">2</span>+<span class="number">1</span>]) tmp=k*<span class="number">2</span>+<span class="number">1</span>;</span><br><span class="line">    <span class="keyword">if</span>(tmp != k) &#123;<span class="built_in">swap</span>(h[tmp],h[k]);<span class="built_in">down</span>(tmp);&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">up</span><span class="params">(<span class="type">int</span> k)</span></span>&#123;</span><br><span class="line">    <span class="keyword">while</span>(k/<span class="number">2</span> &amp;&amp; h[k/<span class="number">2</span>]&gt;h[k]) &#123;<span class="built_in">swap</span>(h[k],h[k/<span class="number">2</span>]);k&gt;&gt;=<span class="number">1</span>;&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">push</span><span class="params">(<span class="type">int</span> x)</span></span>&#123;</span><br><span class="line">    h[++size]=x;</span><br><span class="line">    <span class="built_in">up</span>(size);</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">pop</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="built_in">swap</span>(h[<span class="number">1</span>],h[size]);</span><br><span class="line">    size--;</span><br><span class="line">    <span class="built_in">down</span>(<span class="number">1</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="built_in">scanf</span>(<span class="string">&quot;%d&quot;</span>,&amp;n);</span><br><span class="line">    <span class="type">int</span> c,op;</span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">0</span>;i&lt;n;i++)&#123;</span><br><span class="line">        <span class="built_in">scanf</span>(<span class="string">&quot;%d&quot;</span>,&amp;c);</span><br><span class="line">        <span class="keyword">if</span>(c==<span class="number">1</span>)&#123;</span><br><span class="line">            <span class="built_in">scanf</span>(<span class="string">&quot;%d&quot;</span>,&amp;op);</span><br><span class="line">            <span class="built_in">push</span>(op);</span><br><span class="line">        &#125;<span class="keyword">else</span> <span class="keyword">if</span>(c==<span class="number">2</span>)&#123;</span><br><span class="line">            <span class="built_in">printf</span>(<span class="string">&quot;%d\n&quot;</span>,h[<span class="number">1</span>]);</span><br><span class="line">        &#125;<span class="keyword">else</span>&#123;</span><br><span class="line">            <span class="built_in">pop</span>();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><h3 id="Tire树"><a href="#Tire树" class="headerlink" title="Tire树"></a>Tire树</h3><p>Tire树是一种用来快速<strong>存储字符串集合</strong>，<strong>统计和排序大量的字符串前缀</strong>来减少查询时间，最大限度地减少无谓的字符串比较的数据结构。其特点是字符类别不多（一般就是小写字母、大写字母以及数字），即<strong>一般宽度上限为62&#x3D;26+26+10</strong>，而深度上限是<strong>字符串的长度</strong>。</p><p>Trie树是一种多叉树的结构，<strong>每个节点保存一个字符，一条路径表示一个字符串</strong>。</p><p><img lazyload src="/images/loading.svg" data-src="https://ec28649.webp.li/20250301135135464.png" alt="image-20220321154950133"></p><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;bits/stdc++.h&gt;</span></span></span><br><span class="line"><span class="type">const</span> <span class="type">int</span> N=<span class="number">1e6</span>+<span class="number">10</span>;</span><br><span class="line"><span class="type">int</span> son[N][<span class="number">26</span>],cnt[N],idx;  <span class="comment">//son是从0开始存储，而cnt是从1开始存储</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//这个insert是求完全匹配的字符串数</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">insert</span><span class="params">(<span class="type">char</span> str[])</span></span>&#123;  </span><br><span class="line">  <span class="type">int</span> p=<span class="number">0</span>;</span><br><span class="line">  <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">0</span>;str[i];i++)&#123;</span><br><span class="line">      <span class="type">int</span> u=str[i]-<span class="string">&#x27;a&#x27;</span>;</span><br><span class="line">      <span class="keyword">if</span>(!son[p][u]) son[p][u]=++idx; <span class="comment">//son数组中存储的是idx</span></span><br><span class="line">      p=son[p][u];</span><br><span class="line">    &#125;</span><br><span class="line">  cnt[p]++;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//这个insert是求前缀匹配的字符串数</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">insert</span><span class="params">(<span class="type">char</span> str[])</span></span>&#123;  </span><br><span class="line">  <span class="type">int</span> p=<span class="number">0</span>;</span><br><span class="line">  <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">0</span>;str[i];i++)&#123;</span><br><span class="line">      <span class="type">int</span> u=str[i]-<span class="string">&#x27;a&#x27;</span>;</span><br><span class="line">      <span class="keyword">if</span>(!son[p][u]) son[p][u]=++idx;</span><br><span class="line">      p=son[p][u];</span><br><span class="line">      cnt[p]++;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//这个query是求完全匹配的字符串数</span></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">query</span><span class="params">(<span class="type">char</span> str[])</span></span>&#123;</span><br><span class="line">  <span class="type">int</span> p=<span class="number">0</span>;</span><br><span class="line">  <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">0</span>;str[i];i++)&#123;</span><br><span class="line">      <span class="type">int</span> u=str[i]-<span class="string">&#x27;a&#x27;</span>;</span><br><span class="line">      <span class="keyword">if</span>(!son[p][u]) <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">      p=son[p][u];</span><br><span class="line">    &#125;</span><br><span class="line">  <span class="keyword">return</span> cnt[p];</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//这个是求最长公共前缀</span></span><br><span class="line"><span class="type">int</span> cur=idx;</span><br><span class="line"><span class="keyword">for</span>(<span class="type">int</span> i=cur;i&gt;<span class="number">0</span>;i--) <span class="keyword">if</span>(cnt[i]&gt;cnt[cur]) cur=i;</span><br><span class="line"><span class="keyword">if</span>(cnt[<span class="number">1</span>]==len) cout&lt;&lt;strs[<span class="number">0</span>].<span class="built_in">substr</span>(<span class="number">0</span>,cur);</span><br><span class="line"><span class="keyword">else</span> cout&lt;&lt; <span class="string">&quot;&quot;</span>;</span><br></pre></td></tr></table></figure></div><h4 id="P8306-【模板】字典树-洛谷"><a href="#P8306-【模板】字典树-洛谷" class="headerlink" title="P8306 【模板】字典树 - 洛谷"></a><a class="link" href="https://www.luogu.com.cn/problem/P8306">P8306 【模板】字典树 - 洛谷 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a></h4><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;bits/stdc++.h&gt;</span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"><span class="type">const</span> <span class="type">int</span> N=<span class="number">3e6</span>+<span class="number">10</span>;</span><br><span class="line"><span class="type">int</span> son[N][<span class="number">65</span>],cnt[N],idx;</span><br><span class="line"><span class="type">char</span> str[N];</span><br><span class="line"><span class="type">int</span> t,n,q;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">insert</span><span class="params">(<span class="type">char</span> str[])</span></span>&#123;</span><br><span class="line">    <span class="type">int</span> p=<span class="number">0</span>, u=<span class="number">0</span>;</span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">0</span>;str[i];i++)&#123;</span><br><span class="line">        <span class="keyword">if</span>(str[i]&lt;=<span class="string">&#x27;9&#x27;</span>) u=str[i]-<span class="string">&#x27;0&#x27;</span>;</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span>(str[i]&lt;=<span class="string">&#x27;Z&#x27;</span>) u=str[i]-<span class="string">&#x27;A&#x27;</span>+<span class="number">11</span>;</span><br><span class="line">        <span class="keyword">else</span> u=str[i]-<span class="string">&#x27;a&#x27;</span>+<span class="number">37</span>;</span><br><span class="line">        <span class="keyword">if</span>(!son[p][u]) son[p][u]=++idx;</span><br><span class="line">        cnt[son[p][u]]++;</span><br><span class="line">        p=son[p][u];</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">query</span><span class="params">(<span class="type">char</span> str[])</span></span>&#123;</span><br><span class="line">    <span class="type">int</span> p=<span class="number">0</span>,u=<span class="number">0</span>,sum=<span class="number">0</span>;</span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">0</span>;str[i];i++)&#123;</span><br><span class="line">        <span class="keyword">if</span>(str[i]&lt;=<span class="string">&#x27;9&#x27;</span>) u=str[i]-<span class="string">&#x27;0&#x27;</span>;</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span>(str[i]&lt;=<span class="string">&#x27;Z&#x27;</span>) u=str[i]-<span class="string">&#x27;A&#x27;</span>+<span class="number">11</span>;</span><br><span class="line">        <span class="keyword">else</span> u=str[i]-<span class="string">&#x27;a&#x27;</span>+<span class="number">37</span>;</span><br><span class="line">        <span class="keyword">if</span>(!son[p][u]) <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">        p=son[p][u];</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> cnt[p];</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="built_in">scanf</span>(<span class="string">&quot;%d&quot;</span>,&amp;t);</span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> j=<span class="number">0</span>;j&lt;t;j++)&#123;</span><br><span class="line">        <span class="built_in">scanf</span>(<span class="string">&quot;%d%d&quot;</span>,&amp;n,&amp;q);</span><br><span class="line">        <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">0</span>;i&lt;n;i++) &#123;<span class="built_in">scanf</span>(<span class="string">&quot;%s&quot;</span>,str);<span class="built_in">insert</span>(str);&#125;</span><br><span class="line">        <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">0</span>;i&lt;q;i++) &#123;</span><br><span class="line">            <span class="built_in">scanf</span>(<span class="string">&quot;%s&quot;</span>,str);</span><br><span class="line">            <span class="built_in">printf</span>(<span class="string">&quot;%d\n&quot;</span>,<span class="built_in">query</span>(str));</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="built_in">memset</span>(son,<span class="number">0</span>,<span class="keyword">sizeof</span> son);</span><br><span class="line">        <span class="built_in">memset</span>(cnt,<span class="number">0</span>,<span class="keyword">sizeof</span> cnt);</span><br><span class="line">        idx=<span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><h3 id="并查集"><a href="#并查集" class="headerlink" title="并查集"></a>并查集</h3><p>并查集是一种用于处理<strong>集合合并与查询问题</strong>的数据结构。并查集通常用于解决一些与集合划分和连接相关的问题，例如：</p><ol><li>连通性问题：<strong>判断两个元素是否属于同一个连通分量</strong>。</li><li>图的最小生成树：在构建最小生成树时，可以使用<strong>并查集来判断是否形成了环路</strong>，从而避免将边加入最小生成树中。</li><li>元素的分类和分组：将元素划分为不相交的组或分类，以便进行高效的组内操作。</li></ol><p><strong>并查集的<code>p</code>数组下标从0开始还是从1开始是根据节点编号从0开始还是从1开始确定的。</strong></p><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">const</span> <span class="type">int</span> N=<span class="number">1e6</span>+<span class="number">10</span>;</span><br><span class="line"><span class="type">int</span> p[N],n;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">init</span><span class="params">()</span></span>&#123;</span><br><span class="line">  <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">1</span>;i&lt;=n;i++) p[i]=i;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">find</span><span class="params">(<span class="type">int</span> x)</span></span>&#123;</span><br><span class="line">  <span class="keyword">if</span>(p[x]!=x) p[x]=<span class="built_in">find</span>(p[x]);</span><br><span class="line">  <span class="keyword">return</span> p[x];</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">join</span><span class="params">(<span class="type">int</span> x,<span class="type">int</span> y)</span></span>&#123;</span><br><span class="line">  p[<span class="built_in">find</span>(x)]=<span class="built_in">find</span>(y);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><p><strong>如果需要维护集合大小，需要重新开辟一个以每个节点为根树大小的数组</strong></p><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">const</span> <span class="type">int</span> N=<span class="number">1e5</span>+<span class="number">10</span>;</span><br><span class="line"><span class="type">int</span> p[N],size[N],n;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">init</span><span class="params">()</span></span>&#123;</span><br><span class="line">  <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">1</span>;i&lt;=n;i++)&#123;p[i]=i;size[i]=<span class="number">1</span>;&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">find</span><span class="params">(<span class="type">int</span> x)</span></span>&#123; </span><br><span class="line">  <span class="keyword">if</span>(p[x]!=x) p[x]=<span class="built_in">find</span>(p[x]);</span><br><span class="line">  <span class="keyword">return</span> p[x];</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">join</span><span class="params">(<span class="type">int</span> x,<span class="type">int</span> y)</span></span>&#123;</span><br><span class="line">  <span class="keyword">if</span>(<span class="built_in">find</span>(x)!=<span class="built_in">find</span>(y)) size[<span class="built_in">find</span>(y)]+=size[<span class="built_in">find</span>(x)];</span><br><span class="line">  p[<span class="built_in">find</span>(x)]=<span class="built_in">find</span>(y);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><h4 id="P3367-【模板】并查集-洛谷"><a href="#P3367-【模板】并查集-洛谷" class="headerlink" title="P3367 【模板】并查集 - 洛谷"></a><a class="link" href="https://www.luogu.com.cn/problem/P3367">P3367 【模板】并查集 - 洛谷 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a></h4><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;bits/stdc++.h&gt;</span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"><span class="type">const</span> <span class="type">int</span> N=<span class="number">1e4</span>+<span class="number">10</span>;</span><br><span class="line"><span class="type">int</span> p[N],n,m;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">init</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">1</span>;i&lt;=n;i++) p[i]=i;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">find</span><span class="params">(<span class="type">int</span> x)</span></span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(p[x]!=x) p[x]=<span class="built_in">find</span>(p[x]);</span><br><span class="line">    <span class="keyword">return</span> p[x];</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">join</span><span class="params">(<span class="type">int</span> x,<span class="type">int</span> y)</span></span>&#123;</span><br><span class="line">    p[<span class="built_in">find</span>(x)]=<span class="built_in">find</span>(y);</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="built_in">scanf</span>(<span class="string">&quot;%d%d&quot;</span>,&amp;n,&amp;m);</span><br><span class="line">    <span class="built_in">init</span>();</span><br><span class="line">    <span class="type">int</span> c,x,y;</span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">0</span>;i&lt;m;i++)&#123;</span><br><span class="line">        <span class="built_in">scanf</span>(<span class="string">&quot;%d%d%d&quot;</span>,&amp;c,&amp;x,&amp;y);</span><br><span class="line">        <span class="keyword">if</span>(c==<span class="number">1</span>) <span class="built_in">join</span>(x,y);</span><br><span class="line">        <span class="keyword">else</span> <span class="built_in">find</span>(x)==<span class="built_in">find</span>(y)?<span class="built_in">printf</span>(<span class="string">&quot;%s\n&quot;</span>,<span class="string">&quot;Y&quot;</span>):<span class="built_in">printf</span>(<span class="string">&quot;%s\n&quot;</span>,<span class="string">&quot;N&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><h2 id="树-图"><a href="#树-图" class="headerlink" title="树&amp;图"></a>树&amp;图</h2><h3 id="遍历"><a href="#遍历" class="headerlink" title="遍历"></a>遍历</h3><h4 id="树的深度优先遍历"><a href="#树的深度优先遍历" class="headerlink" title="树的深度优先遍历"></a><a class="link" href="https://www.bilibili.com/video/BV1b7411N798?p=53">树的深度优先遍历 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a></h4><blockquote><p>[!Note]</p><p>树的遍历有<strong>先根遍历和后根遍历</strong>，其中<strong>二叉树还有中根遍历</strong>，森林的遍历可以基于树的遍历，有<strong>先序遍历和中序遍历</strong>。</p><p><strong>树的先根遍历序列和其对应的二叉树（左孩子右兄弟存储）的先根遍历序列一致，树的后根遍历序列和其对应的二叉树的中根遍历序列一致。</strong></p><p>森林的<strong>先序遍历</strong>序列等价于每棵树按序<strong>先根遍历</strong>的序列，此外还可以将森林使用左孩子右兄弟表示法转化成二叉树再进行<strong>先序遍历</strong>。</p><p>森林的<strong>中序遍历</strong>序列等价于每棵树按序<strong>后根遍历</strong>的序列，此外还可以将森林使用左孩子右兄弟表示法转化成二叉树再进行<strong>中序遍历</strong>。</p><p><img lazyload src="/images/loading.svg" data-src="https://ec28649.webp.li/20250301135145771.png" alt="image-20240827162116725"></p></blockquote><div class="highlight-container" data-rel="Cpp"><figure class="iseeu highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">preorder</span><span class="params">(TreeNode *root, vector&lt;<span class="type">int</span>&gt; &amp;res)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (root == <span class="literal">nullptr</span>) <span class="keyword">return</span>;</span><br><span class="line">    res.<span class="built_in">push_back</span>(root-&gt;val);</span><br><span class="line">    <span class="built_in">preorder</span>(root-&gt;left, res);</span><br><span class="line">    <span class="built_in">preorder</span>(root-&gt;right, res);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">midorder</span><span class="params">(TreeNode *root, vector&lt;<span class="type">int</span>&gt; &amp;res)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (root == <span class="literal">nullptr</span>) <span class="keyword">return</span>;</span><br><span class="line">    <span class="built_in">preorder</span>(root-&gt;left, res);</span><br><span class="line">    res.<span class="built_in">push_back</span>(root-&gt;val);</span><br><span class="line">    <span class="built_in">preorder</span>(root-&gt;right, res);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">postorder</span><span class="params">(TreeNode *root, vector&lt;<span class="type">int</span>&gt; &amp;res)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (root == <span class="literal">nullptr</span>) <span class="keyword">return</span>;</span><br><span class="line">    <span class="built_in">preorder</span>(root-&gt;left, res);</span><br><span class="line">    <span class="built_in">preorder</span>(root-&gt;right, res);</span><br><span class="line">  res.<span class="built_in">push_back</span>(root-&gt;val);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">preorderTraversal</span><span class="params">(TreeNode *root)</span> </span>&#123;</span><br><span class="line">    vector&lt;<span class="type">int</span>&gt; res;</span><br><span class="line">    <span class="built_in">preorder</span>(root, res);</span><br><span class="line">    <span class="keyword">return</span> res;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><h4 id="树的宽度优先遍历"><a href="#树的宽度优先遍历" class="headerlink" title="树的宽度优先遍历"></a>树的宽度优先遍历</h4><blockquote><p>[!Note]</p><p>树的宽度优先遍历的实现逻辑和其二叉树的宽度优先遍历实现逻辑是一致的。</p></blockquote><h4 id="深度优先搜索"><a href="#深度优先搜索" class="headerlink" title="深度优先搜索"></a>深度优先搜索</h4><blockquote><p>[!Note]</p><p><font color="green">在搞懂DFS前，需要明白DFS是基于递归的，只有很好的理解递归，才能将DFS用到炉火纯青！</font></p><p>递归最终要的就是两个点：<strong>递归出口和递归点</strong></p><p><strong>递归出口即是什么时候截止</strong>。</p><p>假如遍历一颗BST，需要找到值为5的节点。那么递归出口就有两种情况：当递归到叶节点时终止和当递归找到值为5的节点终止。</p><p><strong>递归点即需要找到当前问题如何转换成其子问题</strong>。</p><p>还是上面那个问题，需要找到值为5的节点。对于当前节点来说，找到值为5的节点有三种可能：当前节点本身值为5，值为5的节点在左子树中，值为5的节点在右子树中。因此问题就向其子空间（子问题）进行了转换，这就是递归点。</p><p>OJ例题：<a class="link" href="https://leetcode.cn/problems/symmetric-tree/description/">深度优先搜索知识点题库 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a></p><p>如上面这道OJ例题，判断二叉树是否对称。</p><p><strong>其递归出口就有四种情况：左右子树同时递归到叶子结点、左子树递归到叶子结点但右子树没有、右子树递归到叶子结点但左子树没有、左右子树都未递归到叶子结点，但当前存储的值不同。</strong></p><p><strong>递归点就是：左子树的左孩子与右子树的右孩子对称、左子树的右孩子与右子树的左孩子对称。</strong></p><div class="highlight-container" data-rel="Cpp"><figure class="iseeu highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> boolean <span class="title">isSymmetric</span><span class="params">(TreeNode root)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">isSame</span>(root.left, root.right);</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">private</span> boolean <span class="title">isSame</span><span class="params">(TreeNode p, TreeNode q)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (p == null &amp;&amp; q == null) <span class="keyword">return</span>  <span class="literal">true</span>;</span><br><span class="line">    <span class="keyword">if</span> ((p == null || q == null) || p.val != q.val) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">isSame</span>(p.left, q.right) &amp;&amp; <span class="built_in">isSame</span>(p.right, q.left);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div></blockquote><p>时间复杂度O(n+m)，n表示点数，m表示边数</p><p>深搜的关键在于<strong>本状态和下一状态的衔接</strong>以及<strong>递归出口</strong>的设置。</p><p><strong>深搜在网格题目中备受关注！</strong></p><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">int</span> <span class="title">dfs</span><span class="params">(<span class="type">int</span> u)</span></span>&#123;</span><br><span class="line">  <span class="keyword">if</span>(u==n) <span class="keyword">return</span>; <span class="comment">//递归出口</span></span><br><span class="line">    st[u] = <span class="literal">true</span>; <span class="comment">//状态记录</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = h[u]; i != <span class="number">-1</span>; i = ne[i])&#123;</span><br><span class="line">        <span class="keyword">if</span> (!st[e[i]]) <span class="built_in">dfs</span>(j); <span class="comment">//进入dfs前有条件限制，则进行了剪枝</span></span><br><span class="line">    &#125;</span><br><span class="line">  st[u] = <span class="literal">false</span>; <span class="comment">//状态恢复</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><h5 id="B3621-枚举元组-洛谷"><a href="#B3621-枚举元组-洛谷" class="headerlink" title="B3621 枚举元组 - 洛谷"></a><a class="link" href="https://www.luogu.com.cn/problem/B3621">B3621 枚举元组 - 洛谷 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a></h5><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;bits/stdc++.h&gt;</span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"><span class="type">const</span> <span class="type">int</span> N=<span class="number">6</span>;</span><br><span class="line"><span class="type">int</span> n,k,res[N];</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">dfs</span><span class="params">(<span class="type">int</span> t)</span></span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(t==n) &#123;<span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">0</span>;i&lt;n;i++) cout&lt;&lt;res[i]&lt;&lt;<span class="string">&quot; &quot;</span>;cout&lt;&lt;endl;<span class="keyword">return</span>;&#125;</span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">1</span>;i&lt;=k;i++)&#123;</span><br><span class="line">        res[t]=i;</span><br><span class="line">        <span class="built_in">dfs</span>(t+<span class="number">1</span>);  <span class="comment">//这道题就没有涉及到状态恢复</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="built_in">scanf</span>(<span class="string">&quot;%d%d&quot;</span>,&amp;n,&amp;k);</span><br><span class="line">    <span class="built_in">dfs</span>(<span class="number">0</span>);</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><h5 id="P10448-组合型枚举-洛谷"><a href="#P10448-组合型枚举-洛谷" class="headerlink" title="P10448 组合型枚举 - 洛谷"></a><a class="link" href="https://www.luogu.com.cn/problem/P10448">P10448 组合型枚举 - 洛谷 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a></h5><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;bits/stdc++.h&gt;</span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"><span class="type">const</span> <span class="type">int</span> N=<span class="number">26</span>;</span><br><span class="line"><span class="type">int</span> n,k,res[N];</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">dfs</span><span class="params">(<span class="type">int</span> t)</span></span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(t==n+<span class="number">1</span>) &#123;<span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">1</span>;i&lt;=n;i++) cout&lt;&lt;res[i]&lt;&lt;<span class="string">&quot; &quot;</span>;cout&lt;&lt;endl;<span class="keyword">return</span>;&#125;</span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">1</span>;i&lt;=k;i++)&#123;</span><br><span class="line">        <span class="keyword">if</span>(i&gt;res[t<span class="number">-1</span>])&#123; <span class="comment">// 确保递增顺序</span></span><br><span class="line">            res[t]=i;</span><br><span class="line">            <span class="built_in">dfs</span>(t+<span class="number">1</span>); </span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="built_in">scanf</span>(<span class="string">&quot;%d%d&quot;</span>,&amp;k,&amp;n);</span><br><span class="line">    <span class="built_in">dfs</span>(<span class="number">1</span>);</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><blockquote><p>[!IMPORTANT]</p><p>DFS往往有很多变种，无返回值的DFS，带返回值的DFS，无需状态记录的DFS和带状态记录的DFS等。其中，有无返回值和有无状态记录两者并不是冲突对立的。一个DFS的题目可能是无返回值需要状态记录的，也有可能是带返回值无状态记录的。</p><p><strong>无返回值DFS模板：</strong></p><div class="highlight-container" data-rel="Cpp"><figure class="iseeu highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">dfs</span><span class="params">([<span class="string">&#x27;search space&#x27;</span>,] <span class="string">&#x27;current state parameters&#x27;</span>)</span></span>&#123;</span><br><span class="line">  <span class="keyword">if</span>(<span class="string">&#x27;reach of terminal state&#x27;</span>) <span class="keyword">return</span>;</span><br><span class="line">  <span class="built_in">dfs</span>(<span class="string">&#x27;next state parameters&#x27;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><p>OJ例题：<a class="link" href="https://leetcode.cn/problems/number-of-islands/description/">岛屿数量 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a></p><p><strong>带返回值DFS模板：</strong></p><div class="highlight-container" data-rel="Cpp"><figure class="iseeu highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">int</span> <span class="title">dfs</span><span class="params">([<span class="string">&#x27;search space&#x27;</span>,] <span class="string">&#x27;current state parameters&#x27;</span>)</span></span>&#123;</span><br><span class="line">  <span class="keyword">if</span>(<span class="string">&#x27;reach of terminal state&#x27;</span>) <span class="keyword">return</span> value;</span><br><span class="line">  <span class="keyword">return</span> <span class="built_in">dfs</span>(<span class="string">&#x27;next state parameters&#x27;</span>)+<span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><p>OJ例题：<a class="link" href="https://leetcode.cn/problems/max-area-of-island/">岛屿的最大面积 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a></p></blockquote><h5 id="岛屿数量"><a href="#岛屿数量" class="headerlink" title="岛屿数量"></a><a class="link" href="https://leetcode.cn/problems/number-of-islands/description/">岛屿数量 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a></h5><div class="highlight-container" data-rel="Cpp"><figure class="iseeu highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">const</span> <span class="type">static</span> <span class="type">int</span> maxn=<span class="number">1e3</span>+<span class="number">10</span>;</span><br><span class="line"><span class="type">bool</span> sta[maxn][maxn];</span><br><span class="line"><span class="type">int</span> n,m;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">dfs</span><span class="params">(vector&lt;vector&lt;<span class="type">char</span>&gt;&gt;&amp; grid,<span class="type">int</span> x,<span class="type">int</span> y)</span></span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(x&lt;<span class="number">0</span> || x&gt;=n || y&lt;<span class="number">0</span> || y&gt;=m || sta[x][y] || grid[x][y]==<span class="string">&#x27;0&#x27;</span>) <span class="keyword">return</span>;</span><br><span class="line">    sta[x][y]=<span class="literal">true</span>;</span><br><span class="line">    <span class="built_in">dfs</span>(grid,x+<span class="number">1</span>,y);</span><br><span class="line">    <span class="built_in">dfs</span>(grid,x<span class="number">-1</span>,y);</span><br><span class="line">    <span class="built_in">dfs</span>(grid,x,y+<span class="number">1</span>);</span><br><span class="line">    <span class="built_in">dfs</span>(grid,x,y<span class="number">-1</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">numIslands</span><span class="params">(vector&lt;vector&lt;<span class="type">char</span>&gt;&gt;&amp; grid)</span> </span>&#123;</span><br><span class="line">    n=grid.<span class="built_in">size</span>(),m=grid[<span class="number">0</span>].<span class="built_in">size</span>();</span><br><span class="line">    <span class="type">int</span> res=<span class="number">0</span>;</span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">0</span>;i&lt;n;i++)</span><br><span class="line">        <span class="keyword">for</span>(<span class="type">int</span> j=<span class="number">0</span>;j&lt;m;j++)</span><br><span class="line">            <span class="keyword">if</span>(grid[i][j]==<span class="string">&#x27;1&#x27;</span>&amp;&amp; !sta[i][j]) &#123;res++;<span class="built_in">dfs</span>(grid,i,j);&#125;</span><br><span class="line">    <span class="keyword">return</span> res;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><h5 id="岛屿的最大面积"><a href="#岛屿的最大面积" class="headerlink" title="岛屿的最大面积"></a><a class="link" href="https://leetcode.cn/problems/max-area-of-island/">岛屿的最大面积 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a></h5><div class="highlight-container" data-rel="Cpp"><figure class="iseeu highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">const</span> <span class="type">static</span> <span class="type">int</span> maxn=<span class="number">60</span>;</span><br><span class="line"><span class="type">bool</span> sta[maxn][maxn];</span><br><span class="line"><span class="type">int</span> n,m;</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">dfs</span><span class="params">(vector&lt;vector&lt;<span class="type">int</span>&gt;&gt;&amp; grid,<span class="type">int</span> x,<span class="type">int</span> y)</span></span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(x&lt;<span class="number">0</span> || x&gt;=n || y&lt;<span class="number">0</span> || y&gt;=m || sta[x][y] || grid[x][y]==<span class="number">0</span>) <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">    sta[x][y]=<span class="literal">true</span>; </span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">dfs</span>(grid,x+<span class="number">1</span>,y)+<span class="built_in">dfs</span>(grid,x<span class="number">-1</span>,y)+<span class="built_in">dfs</span>(grid,x,y+<span class="number">1</span>)+<span class="built_in">dfs</span>(grid,x,y<span class="number">-1</span>)+<span class="number">1</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">maxAreaOfIsland</span><span class="params">(vector&lt;vector&lt;<span class="type">int</span>&gt;&gt;&amp; grid)</span> </span>&#123;</span><br><span class="line">    n=grid.<span class="built_in">size</span>(),m=grid[<span class="number">0</span>].<span class="built_in">size</span>();</span><br><span class="line">    <span class="type">int</span> res=<span class="number">0</span>;</span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">0</span>;i&lt;n;i++)</span><br><span class="line">        <span class="keyword">for</span>(<span class="type">int</span> j=<span class="number">0</span>;j&lt;m;j++)</span><br><span class="line">            <span class="keyword">if</span>(grid[i][j]==<span class="number">1</span> &amp;&amp; !sta[i][j])</span><br><span class="line">                res=<span class="built_in">max</span>(res,<span class="built_in">dfs</span>(grid,i,j));</span><br><span class="line">    <span class="keyword">return</span> res;   </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><blockquote><p> [!Tip]</p><p>对于DFS搜索空间是网格的情况，往往需要遍历其上下左右四个访问，因此可以创建两个偏量数组<code>dx和dy</code>，通过<code>for</code>循环进行简便表示。</p></blockquote><div class="highlight-container" data-rel="Cpp"><figure class="iseeu highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> dx=&#123;<span class="number">0</span>,<span class="number">0</span>,<span class="number">1</span>,<span class="number">-1</span>&#125;;</span><br><span class="line"><span class="type">int</span> dy=&#123;<span class="number">1</span>,<span class="number">-1</span>,<span class="number">0</span>,<span class="number">0</span>&#125;;</span><br><span class="line"><span class="comment">//void dfs</span></span><br><span class="line"><span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">0</span>;i&lt;<span class="number">4</span>;i++)&#123;</span><br><span class="line">  <span class="built_in">dfs</span>([<span class="string">&#x27;search space&#x27;</span>,] x+dx[i],y+dy[i]);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//int dfs</span></span><br><span class="line"><span class="type">int</span> ans=<span class="number">1</span>;</span><br><span class="line"><span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">0</span>;i&lt;<span class="number">4</span>;i++)&#123;</span><br><span class="line">  ans+=<span class="built_in">dfs</span>([<span class="string">&#x27;search space&#x27;</span>,] x+dx[i],y+dy[i]);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><h4 id="宽度优先搜索"><a href="#宽度优先搜索" class="headerlink" title="宽度优先搜索"></a>宽度优先搜索</h4><p>宽搜本身具备<strong>最短路的性质</strong>，<font color="orange">当边权一致时，可以用来求最短路径</font>。</p><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">const</span> <span class="type">int</span> N=<span class="number">1e5</span>+<span class="number">10</span>;</span><br><span class="line"><span class="type">int</span> st[N];</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">bfs</span><span class="params">()</span></span>&#123;</span><br><span class="line">  queue&lt;<span class="type">int</span>&gt; q;</span><br><span class="line">  st[<span class="number">1</span>]=<span class="literal">true</span>;</span><br><span class="line">  q.<span class="built_in">push</span>(<span class="number">1</span>);</span><br><span class="line">  <span class="keyword">while</span>(!q.<span class="built_in">empty</span>())&#123;</span><br><span class="line">      <span class="type">int</span> tmp=q.<span class="built_in">front</span>();</span><br><span class="line">      q.<span class="built_in">pop</span>();</span><br><span class="line">      <span class="keyword">for</span>(<span class="type">int</span> i=h[i];i!=<span class="number">-1</span>;i=ne[i])&#123;</span><br><span class="line">          <span class="type">int</span> node=e[i];</span><br><span class="line">          <span class="keyword">if</span>(!st[node])&#123;</span><br><span class="line">              st[node]=<span class="literal">true</span>;</span><br><span class="line">              q.<span class="built_in">push</span>(node);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><h5 id="除法求值"><a href="#除法求值" class="headerlink" title="除法求值"></a><a class="link" href="https://leetcode.cn/problems/evaluate-division/description/">除法求值 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a></h5><p>这个题目很好的利用了宽度优先搜索的最短路特性！<a class="link" href="https://leetcode.cn/problems/evaluate-division/solutions/340296/san-chong-jie-fa-by-baymaxhwy">多个视角 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a>（并查集 | Floyd | DFS | BFS）看这道题。</p><blockquote><p>[!Note]</p><p>顺便借着这个题目梳理一下图相关题目的做法。</p><p>首先，<strong>最重要的是能够识别出这题可以用图做</strong>。其实也很好判断，图最重要的就是<strong>结点与结点之间的关联</strong>，若题目能抽象出<strong>结点</strong>和<strong>结点之间的关联</strong>，且题目求解的答案也需要利用到结点之间的关系，那么这道题很大程度上就要用图解题。</p><p><strong>那么下一步就要思考，答案的求解需要涉及到什么知识</strong>。基于图遍历的求解？最短路问题？最小生成树问题？拓扑排序问题？还是二分图问题？这一步就是问题建模的过程，因此相当关键。这需要我们对这些问题所涉及的模板特别熟悉，且能灵活的改动。</p><p><strong>确定大体的解题思路之后，就要落足于题目实际</strong>。图需要用哪种结构进行存储？邻接矩阵、邻接表、还是结构体数组？这需要根据上一步确定的方法，以及题目数据规模共同敲定。是否需要用到额外的辅助数据结构？堆、队列、集合、哈希表？</p><p><strong>最后就是编码的细节</strong>。是否需要状态记录？DFS的递归出口，返回值是否正确？数组下标从1开始还是从0开始？</p></blockquote><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">const</span> <span class="type">static</span> <span class="type">int</span> maxn= <span class="number">30</span>;</span><br><span class="line"><span class="type">double</span> g[maxn][maxn];</span><br><span class="line"><span class="type">bool</span> st[maxn];</span><br><span class="line">unordered_map &lt;string,<span class="type">int</span>&gt; node;</span><br><span class="line"><span class="type">int</span> idx=<span class="number">0</span>;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">init</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="built_in">memset</span>(st,<span class="number">0</span>,<span class="keyword">sizeof</span> st);</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">double</span> <span class="title">dfs</span><span class="params">(<span class="type">int</span> x,<span class="type">int</span> y)</span></span>&#123;  <span class="comment">//这里用dfs是不合题意的，因为不是最短构造</span></span><br><span class="line">    <span class="keyword">if</span>(x==y) <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">    st[x]=<span class="literal">true</span>;</span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">0</span>;i&lt;idx;i++)</span><br><span class="line">        <span class="keyword">if</span>(g[x][i] &amp;&amp; !st[i]) <span class="keyword">return</span> <span class="built_in">dfs</span>(i, y) * g[x][i];</span><br><span class="line">    <span class="keyword">return</span> <span class="number">-1.0</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">double</span> <span class="title">bfs</span><span class="params">(<span class="type">int</span> x, <span class="type">int</span> y)</span> </span>&#123;</span><br><span class="line">    <span class="type">double</span> tmp[maxn];</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; idx; i++) tmp[i] = <span class="number">1</span>;</span><br><span class="line">    queue&lt;<span class="type">int</span>&gt; q;</span><br><span class="line">    q.<span class="built_in">push</span>(x);</span><br><span class="line">    st[x] = <span class="literal">true</span>;</span><br><span class="line">    <span class="keyword">while</span> (!q.<span class="built_in">empty</span>()) &#123;</span><br><span class="line">        <span class="type">int</span> top = q.<span class="built_in">front</span>();</span><br><span class="line">        q.<span class="built_in">pop</span>();</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; idx; i++)</span><br><span class="line">            <span class="keyword">if</span> (!st[i] &amp;&amp; g[top][i]) &#123;</span><br><span class="line">                tmp[i] *= tmp[top] * g[top][i];</span><br><span class="line">                st[i] = <span class="literal">true</span>;</span><br><span class="line">                q.<span class="built_in">push</span>(i);</span><br><span class="line">            &#125;</span><br><span class="line">        <span class="keyword">if</span> (top == y) <span class="keyword">return</span> tmp[top];</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">-1.0</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function">vector&lt;<span class="type">double</span>&gt; <span class="title">calcEquation</span><span class="params">(vector&lt;vector&lt;string&gt;&gt;&amp; equations, vector&lt;<span class="type">double</span>&gt;&amp; values, vector&lt;vector&lt;string&gt;&gt;&amp; queries)</span> </span>&#123;  <span class="comment">//这个题是很明显的需要用邻接矩阵去存储的</span></span><br><span class="line">    <span class="type">int</span> len=equations.<span class="built_in">size</span>();</span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">0</span>;i&lt;len;i++)&#123;</span><br><span class="line">        <span class="keyword">if</span>(!node.<span class="built_in">count</span>(equations[i][<span class="number">0</span>])) &#123;node.<span class="built_in">insert</span>(&#123;equations[i][<span class="number">0</span>],idx++&#125;);&#125;</span><br><span class="line">        <span class="keyword">if</span>(!node.<span class="built_in">count</span>(equations[i][<span class="number">1</span>])) &#123;node.<span class="built_in">insert</span>(&#123;equations[i][<span class="number">1</span>],idx++&#125;);&#125;</span><br><span class="line">        g[node[equations[i][<span class="number">0</span>]]][node[equations[i][<span class="number">1</span>]]] = values[i];</span><br><span class="line">        g[node[equations[i][<span class="number">1</span>]]][node[equations[i][<span class="number">0</span>]]]=<span class="number">1</span>/values[i];</span><br><span class="line">    &#125;</span><br><span class="line">    vector&lt;<span class="type">double</span>&gt; res;</span><br><span class="line">    <span class="keyword">for</span>(<span class="keyword">auto</span> item:queries)</span><br><span class="line">        <span class="keyword">if</span>(node.<span class="built_in">count</span>(item[<span class="number">0</span>]) &amp;&amp; node.<span class="built_in">count</span>(item[<span class="number">1</span>]))&#123;</span><br><span class="line">            <span class="built_in">init</span>();</span><br><span class="line">            res.<span class="built_in">push_back</span>(<span class="built_in">bfs</span>(node[item[<span class="number">0</span>]],node[item[<span class="number">1</span>]]));</span><br><span class="line">        &#125;<span class="keyword">else</span> res.<span class="built_in">push_back</span>(<span class="number">-1.0</span>);</span><br><span class="line">    <span class="keyword">return</span> res;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><h3 id="拓扑排序"><a href="#拓扑排序" class="headerlink" title="拓扑排序"></a>拓扑排序</h3><p><strong>有向图 | 宽度优先搜索的一种应用</strong></p><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">const</span> <span class="type">int</span> N=<span class="number">1e5</span>+<span class="number">10</span>;</span><br><span class="line"><span class="type">int</span> dim[N],q[N],tp[N],n;</span><br><span class="line"><span class="function"><span class="type">bool</span> <span class="title">topsort</span><span class="params">()</span></span>&#123;</span><br><span class="line">  <span class="type">int</span> hh=<span class="number">0</span>,tt=<span class="number">-1</span>,idx=<span class="number">0</span>; <span class="comment">// 初始空队列</span></span><br><span class="line">  <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">1</span>;i&lt;=n;i++) <span class="keyword">if</span>(!dim[i]) q[++tt]=i; <span class="comment">// 将入度为0的元素放入队列中</span></span><br><span class="line">  <span class="keyword">while</span>(hh&lt;=tt)&#123; <span class="comment">// 队不空</span></span><br><span class="line">      <span class="type">int</span> tmp=q[hh++];  <span class="comment">// 取出队头元素</span></span><br><span class="line">      tp[idx++]=tmp; <span class="comment">// 其实这里也没必要额外开一个tp存储，q中存储的就是拓扑序</span></span><br><span class="line">      <span class="keyword">for</span>(<span class="type">int</span> i=h[tmp];i!=<span class="number">-1</span>;i=ne[i])  <span class="comment">// 遍历队头元素的出边</span></span><br><span class="line">          <span class="keyword">if</span>(--d[e[i]]==<span class="number">0</span>) q[++tt]=e[i]; </span><br><span class="line">    &#125;</span><br><span class="line">  <span class="keyword">return</span> tt==n<span class="number">-1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><h3 id="最短路"><a href="#最短路" class="headerlink" title="最短路"></a>最短路</h3><p><img lazyload src="/images/loading.svg" data-src="https://ec28649.webp.li/20250301135200899.png" alt="image-20240827000750510"></p><h4 id="Dijkstra算法"><a href="#Dijkstra算法" class="headerlink" title="Dijkstra算法"></a>Dijkstra算法</h4><h5 id="朴素Dijkstra"><a href="#朴素Dijkstra" class="headerlink" title="朴素Dijkstra"></a>朴素Dijkstra</h5><p>基于邻接矩阵存储的算法。</p><p>时间复杂度是$O(n^2)$，仅支持求正权边的最短路径。算法时间复杂度与边数无关，适用于稠密图。</p><p>算法的基本思想是通过逐步扩展已确定最短路径的顶点集合，从源点开始逐步确定源点到其他顶点的最短路径。Dijkstra算法使用了贪心策略，<strong>每次选择当前距离源点最近的顶点进行扩展，直到所有顶点都被扩展</strong>。</p><ol><li>初始化：将源点的最短路径距离设置为0，其他顶点的最短路径距离设置为正无穷大（或一个足够大的值）。</li><li>选择最短路径顶点：从尚未确定最短路径的顶点中选择一个距离源点最近的顶点，将其标记为已确定最短路径。</li><li>更新最短路径距离：对于选择的顶点，遍历其所有邻接顶点，如果通过该顶点可以获得更短的路径，则更新邻接顶点的最短路径距离为源点经过选择顶点到该邻接顶点的距离。</li><li>重复进行步骤2和步骤3，直到所有顶点都被确定最短路径。</li></ol><p>最短路径获取：<strong>通过记录每个顶点的前驱节点，可以从目标顶点逆向获取最短路径</strong>。</p><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">const</span> <span class="type">int</span> N=<span class="number">1e3</span>+<span class="number">10</span>;</span><br><span class="line"><span class="type">int</span> g[N][N],dist[N];</span><br><span class="line"><span class="type">bool</span> st[N]; <span class="comment">// g存储每条边的权重，dist存储1号点到其他点的距离，st存储每个节点的最短路径是否确定。</span></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">dijkstra</span><span class="params">()</span></span>&#123;</span><br><span class="line">  <span class="built_in">memset</span>(dist,<span class="number">0x3f</span>,<span class="keyword">sizeof</span> dist);</span><br><span class="line">  dist[<span class="number">1</span>]=<span class="number">0</span>;</span><br><span class="line">  <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">0</span>;i&lt;n<span class="number">-1</span>;i++)&#123; <span class="comment">// 遍历剩余节点</span></span><br><span class="line">      <span class="type">int</span> d=<span class="number">-1</span>;</span><br><span class="line">      <span class="keyword">for</span>(<span class="type">int</span> j=<span class="number">1</span>;j&lt;=n;j++) <span class="comment">// 在还未确定最短路的节点中，寻找距离最小的点</span></span><br><span class="line">          <span class="keyword">if</span>(!st[j]&amp;&amp;(d==<span class="number">-1</span>||dist[d]&gt;dist[j])) d=j;</span><br><span class="line">      <span class="keyword">for</span>(<span class="type">int</span> j=<span class="number">1</span>;j&lt;=n;j++) <span class="comment">// 根据寻找到的节点d更新其他点的距离</span></span><br><span class="line">          dist[j]=<span class="built_in">min</span>(dist[j],dist[d]+g[d][j]);</span><br><span class="line">      st[d]=<span class="literal">true</span>; <span class="comment">// 将节点d设置为最短路径确定的点</span></span><br><span class="line">    &#125;</span><br><span class="line">  <span class="keyword">if</span>(dist[n] == <span class="number">0x3f3f3f3f</span>) <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">  <span class="keyword">else</span> <span class="keyword">return</span> dist[n];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><h5 id="堆优化Dijkstra"><a href="#堆优化Dijkstra" class="headerlink" title="堆优化Dijkstra"></a>堆优化Dijkstra</h5><p>基于邻接表存储的算法。</p><p>时间复杂度是$O(mlogn)$，仅支持求正权边的最短路径。算法时间复杂度与边数有关，适用于稀疏图。</p><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> pair&lt;<span class="type">int</span>,<span class="type">int</span>&gt; PII;</span><br><span class="line"><span class="type">int</span> h[N],e[N],ne[N],w[N],n,idx,dist[N];</span><br><span class="line"><span class="type">bool</span> st[N]; <span class="comment">// st存储每个节点的最短路径是否确定。</span></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">dijkstra</span><span class="params">()</span></span>&#123;</span><br><span class="line">  <span class="built_in">memset</span>(dist,<span class="number">0x3f</span>,<span class="keyword">sizeof</span> dist);</span><br><span class="line">  priority_queue&lt;PII,vector&lt;PII&gt;,greater&lt;PII&gt;&gt;heap;</span><br><span class="line">  heap.<span class="built_in">push</span>(&#123;<span class="number">0</span>,<span class="number">1</span>&#125;); <span class="comment">//插入距离和节点编号</span></span><br><span class="line">  <span class="keyword">while</span>(!heap.<span class="built_in">empty</span>())&#123;</span><br><span class="line">      <span class="keyword">auto</span> d=heap.<span class="built_in">top</span>();</span><br><span class="line">      heap.<span class="built_in">pop</span>();</span><br><span class="line">      <span class="type">int</span> node=d.second,dis=d.first;</span><br><span class="line">      <span class="keyword">if</span>(st[node]) <span class="keyword">continue</span>;</span><br><span class="line">      st[node]=<span class="literal">true</span>;</span><br><span class="line">      <span class="keyword">for</span>(<span class="type">int</span> i=h[node];i!=<span class="number">-1</span>;i=ne[i])&#123;</span><br><span class="line">          <span class="type">int</span> k=e[i];</span><br><span class="line">          <span class="keyword">if</span>(dist[k]&gt;dist[node]+w[i])&#123;</span><br><span class="line">              dist[k]=dist[node]+w[i];</span><br><span class="line">              heap.<span class="built_in">push</span>(&#123;dist[k],k&#125;);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span>(dist[n] == <span class="number">0x3f3f3f3f</span>) <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">  <span class="keyword">else</span> <span class="keyword">return</span> dist[n];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><h4 id="Bellman-Ford算法"><a href="#Bellman-Ford算法" class="headerlink" title="Bellman-Ford算法"></a>Bellman-Ford算法</h4><p>基于结构体存储的算法。</p><p><strong>算法的基本思想是通过不断地松弛边来逐步确定源点到其他所有顶点的最短路径</strong>。它使用了<strong>动态规划</strong>的思想，通过对所有边进行<strong>松弛操作</strong>，逐渐更新顶点的最短路径估计值，直到达到最优解。</p><ol><li>初始化：将源点的最短路径估计值设置为0，其他顶点的最短路径估计值设置为正无穷大（或一个足够大的值）。</li><li>进行松弛操作：对图中的每条边进行松弛操作，即根据边的权值更新顶点的最短路径估计值。对于边(u, v)，如果通过 u 可以获得更短的路径，**则更新顶点 v 的最短路径估计值为 dist[u] + weight(u, v)**，其中 dist[u] 表示源点到顶点 u 的最短路径估计值。</li><li>重复进行步骤2：重复进行步骤2，执行 V-1 次松弛操作，其中 V 是图中顶点的数量。这样可以确保在最坏情况下，所有的最短路径都能被找到。</li><li>检测负权回路：<strong>进行第 V 次松弛操作后，如果仍然存在可以进行松弛操作的边，则说明图中存在负权回路，无法得到有效的最短路径</strong>。</li></ol><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">const</span> <span class="type">int</span> N=<span class="number">1e3</span>+<span class="number">10</span>,M=<span class="number">1e5</span>+<span class="number">10</span>;</span><br><span class="line"><span class="type">int</span> n, m; <span class="comment">// n表示点数，m表示边数</span></span><br><span class="line"><span class="type">int</span> dist[N],backup[N]; <span class="comment">// dist[x]存储1到x的最短路距离</span></span><br><span class="line"><span class="keyword">struct</span>&#123; <span class="comment">// 边，a表示出点，b表示入点，w表示边的权重</span></span><br><span class="line">    <span class="type">int</span> a, b, w;</span><br><span class="line">&#125;edges[M];</span><br><span class="line"><span class="comment">// 求1到n的最短路距离，如果无法从1走到n，则返回-1。</span></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">bellman_ford</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="built_in">memset</span>(dist, <span class="number">0x3f</span>, <span class="keyword">sizeof</span> dist);</span><br><span class="line">    dist[<span class="number">1</span>] = <span class="number">0</span>;</span><br><span class="line">    <span class="comment">// 如果第n次迭代仍然会松弛三角不等式，就说明存在一条长度是n+1的最短路径，由抽屉原理，路径中至少存在两个相同的点，说明图中存在负权回路。</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; n; i ++ )&#123;</span><br><span class="line">      <span class="built_in">memcpy</span>(backup,dist,<span class="keyword">sizeof</span> dist);</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">0</span>; j &lt; m; j ++ )&#123;</span><br><span class="line">            <span class="type">int</span> a = edges[j].a, b = edges[j].b, w = edges[j].w;</span><br><span class="line">           dist[b] = <span class="built_in">min</span>(dist[b],backup[a]+w);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (dist[n] &gt; <span class="number">0x3f3f3f3f</span> / <span class="number">2</span>) <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">return</span> dist[n];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><h4 id="SPFA算法-Shortest-Path-Faster-Algorithm"><a href="#SPFA算法-Shortest-Path-Faster-Algorithm" class="headerlink" title="SPFA算法 (Shortest Path Faster Algorithm)"></a>SPFA算法 (Shortest Path Faster Algorithm)</h4><p>基于邻接表存储的算法。</p><p>SPFA算法通过不断<strong>地松弛边</strong>来逐步确定源点到其他所有顶点的最短路径。与Bellman-Ford算法不同的是，SPFA算法使用了一个<strong>队列来优化松弛操作</strong>的选择，<strong>减少了不必要的松弛次数</strong>，从而提高了算法的效率。</p><ol><li><p>创建一个队列，将源点加入队列，并初始化源点到其他所有顶点的距离为无穷大（或一个足够大的值），源点到自身的距离为0。</p></li><li><p>当队列不为空时，执行以下操作：</p><p>a. 从队列中取出一个顶点作为当前顶点。</p><p>b. 遍历当前顶点的所有邻接顶点：如果通过当前顶点可以获得更短的路径，则更新邻接顶点的距离，并将其加入队列（如果尚未在队列中）。</p></li><li><p>当队列为空时，算法结束。此时，源点到所有其他顶点的最短路径长度已确定。</p></li></ol><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">const</span> <span class="type">int</span> N=<span class="number">1e3</span>+<span class="number">10</span>;</span><br><span class="line"><span class="type">int</span> h[N],w[N],e[N],ne[N],dist[N],idx,n;       <span class="comment">// 邻接表存储所有边</span></span><br><span class="line"><span class="type">bool</span> st[N];     <span class="comment">// 存储每个点是否在队列中</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">spfa</span><span class="params">()</span></span>&#123; <span class="comment">// 求1号点到n号点的最短路距离，如果从1号点无法走到n号点则返回-1</span></span><br><span class="line">    <span class="built_in">memset</span>(dist, <span class="number">0x3f</span>, <span class="keyword">sizeof</span> dist);dist[<span class="number">1</span>] = <span class="number">0</span>;</span><br><span class="line">    queue&lt;<span class="type">int</span>&gt; q; q.<span class="built_in">push</span>(<span class="number">1</span>); st[<span class="number">1</span>] = <span class="literal">true</span>;</span><br><span class="line">    <span class="keyword">while</span> (!q.<span class="built_in">empty</span>())&#123;</span><br><span class="line">        <span class="keyword">auto</span> node = q.<span class="built_in">front</span>();</span><br><span class="line">        q.<span class="built_in">pop</span>();</span><br><span class="line">        st[node] = <span class="literal">false</span>; <span class="comment">//当一个点从队列中出队时，需要将对应的 st 数组中的标记设置为 false，表示该点不在队列中了。</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = h[node]; i != <span class="number">-1</span>; i = ne[i])&#123;</span><br><span class="line">            <span class="type">int</span> k = e[i];</span><br><span class="line">            <span class="keyword">if</span> (dist[k] &gt; dist[node] + w[i])&#123;</span><br><span class="line">                dist[k] = dist[node] + w[i];</span><br><span class="line">                <span class="keyword">if</span> (!st[k])&#123;  <span class="comment">// 如果队列中已存在j，则不需要将j重复插入</span></span><br><span class="line">                    q.<span class="built_in">push</span>(k);st[k] = <span class="literal">true</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (dist[n] == <span class="number">0x3f3f3f3f</span>) <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">return</span> dist[n];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><p><strong>判断负环</strong>：如果某条最短路径上有n个点（除了自己），那么加上自己之后一共有n+1个点，由抽屉原理一定有两个点相同，所以存在环。</p><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">const</span> <span class="type">int</span> N=<span class="number">1e3</span>+<span class="number">10</span>;</span><br><span class="line"><span class="type">int</span> h[N],w[N],e[N],ne[N],dist[N],cnt[N],idx,n;       <span class="comment">// 邻接表存储所有边</span></span><br><span class="line"><span class="type">bool</span> st[N];     <span class="comment">// 存储每个点是否在队列中</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">spfa</span><span class="params">()</span></span>&#123; <span class="comment">// 求1号点到n号点的最短路距离，如果从1号点无法走到n号点则返回-1</span></span><br><span class="line">    queue&lt;<span class="type">int</span>&gt; q;</span><br><span class="line">  <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">1</span>;i&lt;=n;i++) &#123;st[i]=<span class="literal">true</span>;q.<span class="built_in">push</span>(i);&#125;</span><br><span class="line">    <span class="keyword">while</span> (!q.<span class="built_in">empty</span>())&#123;</span><br><span class="line">        <span class="keyword">auto</span> node = q.<span class="built_in">front</span>();</span><br><span class="line">        q.<span class="built_in">pop</span>();</span><br><span class="line">        st[node] = <span class="literal">false</span>; <span class="comment">//当一个点从队列中出队时，需要将对应的 st 数组中的标记设置为 false，表示该点不在队列中了。</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = h[node]; i != <span class="number">-1</span>; i = ne[i])&#123;</span><br><span class="line">            <span class="type">int</span> k = e[i];</span><br><span class="line">            <span class="keyword">if</span> (dist[k] &gt; dist[node] + w[i])&#123;</span><br><span class="line">                dist[k] = dist[node] + w[i];</span><br><span class="line">              cnt[k]=cnt[node]+<span class="number">1</span>;</span><br><span class="line">              <span class="keyword">if</span>(cnt[k]&gt;=n) <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">                <span class="keyword">if</span> (!st[k])&#123;  <span class="comment">// 如果队列中已存在j，则不需要将j重复插入</span></span><br><span class="line">                    q.<span class="built_in">push</span>(k);st[k] = <span class="literal">true</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><h4 id="Floyd算法"><a href="#Floyd算法" class="headerlink" title="Floyd算法"></a>Floyd算法</h4><p>基于邻接矩阵存储的算法。</p><p>Floyd算法是一种经典的<strong>动态规划</strong>算法，用于求解所有节点对之间的最短路径。其基本思想是通过<strong>中转节点逐步更新路径</strong>，从而找到最短路径。</p><ol><li>初始化距离矩阵：将每条边的权重存储在距离矩阵 <code>dist</code> 中，如果两个节点之间没有直接的边相连，则将其距离设置为一个较大的值（通常为无穷大）。</li><li>通过中转节点逐步更新路径：对于每一个节点k，考虑从节点i到节点j的路径，判断是否存在通过节点k的更短路径。如果存在，则更新距离矩阵中节点i到节点j的距离为节点i到节点k的距离加上节点k到节点j的距离。</li><li>重复执行第2步，直到所有节点对之间的最短路径都被计算出来。</li><li>最终，距离矩阵 <code>dist</code> 中存储的就是所有节点对之间的最短路径。</li></ol><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">const</span> <span class="type">int</span> INF=<span class="number">0x3f3f3f3f</span>,N=<span class="number">1e3</span>+<span class="number">10</span>;</span><br><span class="line"><span class="type">int</span> d[N][N];</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">init</span><span class="params">()</span></span>&#123;</span><br><span class="line"><span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i &lt;= n; i ++ )</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">1</span>; j &lt;= n; j ++ )</span><br><span class="line">            <span class="keyword">if</span> (i == j) d[i][j] = <span class="number">0</span>; <span class="keyword">else</span> d[i][j] = INF;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 算法结束后，d[a][b]表示a到b的最短距离</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">floyd</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> k = <span class="number">1</span>; k &lt;= n; k ++ )</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i &lt;= n; i ++ )</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">1</span>; j &lt;= n; j ++ )</span><br><span class="line">                d[i][j] = <span class="built_in">min</span>(d[i][j], d[i][k] + d[k][j]);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><h3 id="最小生成树"><a href="#最小生成树" class="headerlink" title="最小生成树"></a>最小生成树</h3><h4 id="Prim算法"><a href="#Prim算法" class="headerlink" title="Prim算法"></a>Prim算法</h4><p>Prim算法是基于顶点的贪心算法。</p><p>给定一个连通的无向加权图$G &#x3D; (V, E)$，其中$V$是顶点集合，$E$是边集合，每条边有一个权重 $w(u, v)$。Prim算法从<strong>一个初始顶点开始，逐步扩展生成树</strong>，<font color="red">每次选择具有最小权重的边并且该边连接了一个新的顶点</font>。</p><ol><li><p><strong>初始化</strong>：</p><ul><li>选择一个初始顶点，将其加入生成树。</li><li>创建一个集合$T$，用于存储生成树中的顶点，初始时包含初始顶点。</li><li>对于每个顶点，记录从$T$到该顶点的最小边权重（使用<strong>优先队列或最小堆来有效地获取最小边</strong>）。</li></ul></li><li><p><strong>扩展生成树</strong>：</p><ul><li>从优先队列中选择权重最小的边，该边连接了一个在$T$中的顶点和一个不在$T$中的顶点。</li><li>将新顶点加入集合$T$。</li><li>更新优先队列中与新顶点相连的边的权重（如果这些边的权重比当前记录的最小权重小）。</li></ul></li><li><p><strong>重复</strong>：</p><ul><li>重复上述步骤，直到所有顶点都被包含在生成树中。</li></ul></li></ol><h5 id="朴素Prim"><a href="#朴素Prim" class="headerlink" title="朴素Prim"></a>朴素Prim</h5><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> n;      <span class="comment">// n表示点数</span></span><br><span class="line"><span class="type">int</span> g[N][N];        <span class="comment">// 邻接矩阵，存储所有边</span></span><br><span class="line"><span class="type">int</span> dist[N];        <span class="comment">// 存储其他点到当前最小生成树的距离</span></span><br><span class="line"><span class="type">bool</span> st[N];     <span class="comment">// 存储每个点是否已经在生成树中</span></span><br><span class="line"><span class="comment">// 如果图不连通，则返回INF(值是0x3f3f3f3f), 否则返回最小生成树的树边权重之和</span></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">prim</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="built_in">memset</span>(dist, <span class="number">0x3f</span>, <span class="keyword">sizeof</span> dist);</span><br><span class="line">    <span class="type">int</span> res = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; n; i ++ )&#123;</span><br><span class="line">        <span class="type">int</span> t = <span class="number">-1</span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">1</span>; j &lt;= n; j ++ )</span><br><span class="line">            <span class="keyword">if</span> (!st[j] &amp;&amp; (t == <span class="number">-1</span> || dist[t] &gt; dist[j]))</span><br><span class="line">            t = j;</span><br><span class="line">        <span class="keyword">if</span> (i &amp;&amp; dist[t] == INF) <span class="keyword">return</span> INF;</span><br><span class="line">        <span class="keyword">if</span> (i) res += dist[t];</span><br><span class="line">        st[t] = <span class="literal">true</span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">1</span>; j &lt;= n; j ++ ) dist[j] = <span class="built_in">min</span>(dist[j], g[t][j]);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> res;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><h4 id="Kruskal算法"><a href="#Kruskal算法" class="headerlink" title="Kruskal算法"></a>Kruskal算法</h4><p>kruskal基于贪心策略，通过逐步选择权重最小的边来构建生成树。</p><p>给定一个连通的无向加权图$G &#x3D; (V, E)$，其中$V$是顶点集合，$E$是边集合，每条边有一个权重 $w(u, v)$。Kruskal算法通过<strong>选择不形成环且权重最小的边</strong>来构建最小生成树。</p><ol><li><p><strong>初始化</strong>：</p><ul><li>创建一个空集合$T$用于存储最小生成树中的边。</li><li>将图中的所有边按权重从小到大排序。</li></ul></li><li><p><strong>选择边</strong>：</p><ul><li>从权重最小的边开始，逐条检查这些边。</li><li>如果当前边连接的两个顶点属于不同的连通分量（即不形成环），则将该边加入集合$T$。</li><li>使用并查集数据结构来高效地检测和合并连通分量。</li></ul></li><li><p><strong>重复</strong>：</p><ul><li>重复上述步骤，直到集合$T$中包含$V-1$条边为止，此时$T$就是图的最小生成树。</li></ul></li></ol><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> n, m;       <span class="comment">// n是点数，m是边数</span></span><br><span class="line"><span class="type">int</span> p[N];       <span class="comment">// 并查集的父节点数组</span></span><br><span class="line"><span class="keyword">struct</span> <span class="title class_">Edge</span>&#123;    <span class="comment">// 存储边</span></span><br><span class="line">    <span class="type">int</span> a, b, w;</span><br><span class="line">    <span class="type">bool</span> <span class="keyword">operator</span>&lt; (<span class="type">const</span> Edge &amp;e)<span class="type">const</span>&#123;</span><br><span class="line">    <span class="keyword">return</span> w &lt; e.w;</span><br><span class="line">&#125;</span><br><span class="line">&#125;edges[M];</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">find</span><span class="params">(<span class="type">int</span> x)</span></span>&#123; <span class="comment">// 并查集核心操作</span></span><br><span class="line">    <span class="keyword">if</span> (p[x] != x) p[x] = <span class="built_in">find</span>(p[x]);</span><br><span class="line">    <span class="keyword">return</span> p[x];</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">kruskal</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="built_in">sort</span>(edges, edges + m);</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i &lt;= n; i ++ ) p[i] = i;    <span class="comment">// 初始化并查集</span></span><br><span class="line">    <span class="type">int</span> res = <span class="number">0</span>, cnt = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; m; i ++ )&#123;</span><br><span class="line">        <span class="type">int</span> a = edges[i].a, b = edges[i].b, w = edges[i].w;</span><br><span class="line">        a = <span class="built_in">find</span>(a), b = <span class="built_in">find</span>(b);</span><br><span class="line">        <span class="keyword">if</span> (a != b)&#123;   <span class="comment">// 如果两个连通块不连通，则将这两个连通块合并</span></span><br><span class="line">            p[a] = b;</span><br><span class="line">            res += w;</span><br><span class="line">            cnt ++ ;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (cnt &lt; n - <span class="number">1</span>) <span class="keyword">return</span> INF;</span><br><span class="line">    <span class="keyword">return</span> res;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><h3 id="二分图"><a href="#二分图" class="headerlink" title="二分图"></a>二分图</h3><h4 id="染色法"><a href="#染色法" class="headerlink" title="染色法"></a>染色法</h4><p>只要图不含有奇数个环，那么一定能被染色。</p><ol><li>选择一个起始节点作为初始顶点，并将其染成一种颜色（例如红色）。</li><li>对于起始节点的每个邻居节点，将其染成与起始节点不同的颜色（例如蓝色）。</li><li>递归或迭代地对每个邻居节点进行步骤2，将其邻居节点的邻居节点染色，直到所有节点都被染色。</li><li>在染色的过程中，如果发现任意两个相邻节点被染成相同的颜色，说明该图不是二分图。</li><li>如果所有节点都被正确染色，并且没有相邻节点被染成相同的颜色，那么该图是二分图。</li></ol><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> n;      <span class="comment">// n表示点数</span></span><br><span class="line"><span class="type">int</span> h[N], e[M], ne[M], idx;     <span class="comment">// 邻接表存储图</span></span><br><span class="line"><span class="type">int</span> color[N];       <span class="comment">// 表示每个点的颜色，-1表示未染色，0表示白色，1表示黑色</span></span><br><span class="line"><span class="comment">// 参数：u表示当前节点，c表示当前点的颜色</span></span><br><span class="line"><span class="function"><span class="type">bool</span> <span class="title">dfs</span><span class="params">(<span class="type">int</span> u, <span class="type">int</span> c)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    color[u] = c;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = h[u]; i != <span class="number">-1</span>; i = ne[i])&#123;</span><br><span class="line">        <span class="type">int</span> j = e[i];</span><br><span class="line">        <span class="keyword">if</span> (color[j] == <span class="number">-1</span>)</span><br><span class="line">        <span class="keyword">if</span> (!<span class="built_in">dfs</span>(j, !c)) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (color[j] == c) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">bool</span> <span class="title">check</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="built_in">memset</span>(color, <span class="number">-1</span>, <span class="keyword">sizeof</span> color);</span><br><span class="line">    <span class="type">bool</span> flag = <span class="literal">true</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i &lt;= n; i ++ )</span><br><span class="line">        <span class="keyword">if</span> (color[i] == <span class="number">-1</span>)</span><br><span class="line">        <span class="keyword">if</span> (!<span class="built_in">dfs</span>(i, <span class="number">0</span>))&#123;</span><br><span class="line">        flag = <span class="literal">false</span>;</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> flag;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><h4 id="匈牙利算法"><a href="#匈牙利算法" class="headerlink" title="匈牙利算法"></a>匈牙利算法</h4><p>匈牙利算法是一种解决最大匹配问题的经典算法。它用于在二分图中找到一个最大的匹配，即将尽可能多的边匹配起来。匈牙利算法基于增广路径的思想，其基本思路是通过不断寻找增广路径来增加匹配的边数，直到无法找到增广路径为止。</p><ol><li>初始化匹配：将所有的匹配边置为空。</li><li>对于二分图的每个未匹配顶点，尝试找到增广路径。</li><li>寻找增广路径：从一个未匹配的顶点开始，使用深度优先搜索或广度优先搜索的方法，依次访问与当前顶点相邻的未匹配顶点。如果找到一个未匹配的顶点，则将路径中的边进行翻转，即将原本匹配的边变为非匹配边，将原本非匹配的边变为匹配边。</li><li>重复步骤3，直到无法找到增广路径为止。</li><li>最终，得到的匹配即为二分图的最大匹配。</li></ol><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> n1, n2;     <span class="comment">// n1表示第一个集合中的点数，n2表示第二个集合中的点数</span></span><br><span class="line"><span class="type">int</span> h[N], e[M], ne[M], idx;     <span class="comment">// 邻接表存储所有边，匈牙利算法中只会用到从第一个集合指向第二个集合的边，所以这里只用存一个方向的边</span></span><br><span class="line"><span class="type">int</span> match[N];       <span class="comment">// 存储第二个集合中的每个点当前匹配的第一个集合中的点是哪个</span></span><br><span class="line"><span class="type">bool</span> st[N];     <span class="comment">// 表示第二个集合中的每个点是否已经被遍历过</span></span><br><span class="line"><span class="function"><span class="type">bool</span> <span class="title">find</span><span class="params">(<span class="type">int</span> x)</span></span>&#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = h[x]; i != <span class="number">-1</span>; i = ne[i])&#123;</span><br><span class="line">        <span class="type">int</span> j = e[i];</span><br><span class="line">        <span class="keyword">if</span> (!st[j])&#123;</span><br><span class="line">            st[j] = <span class="literal">true</span>;</span><br><span class="line">            <span class="keyword">if</span> (match[j] == <span class="number">0</span> || <span class="built_in">find</span>(match[j]))&#123;</span><br><span class="line">                match[j] = x;</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 求最大匹配数，依次枚举第一个集合中的每个点能否匹配第二个集合中的点</span></span><br><span class="line"><span class="type">int</span> res = <span class="number">0</span>;</span><br><span class="line"><span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i &lt;= n1; i ++ )&#123;</span><br><span class="line">    <span class="built_in">memset</span>(st, <span class="literal">false</span>, <span class="keyword">sizeof</span> st);</span><br><span class="line">    <span class="keyword">if</span> (<span class="built_in">find</span>(i)) res ++ ;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><h2 id="动态规划"><a href="#动态规划" class="headerlink" title="动态规划"></a>动态规划</h2><h3 id="背包问题"><a href="#背包问题" class="headerlink" title="背包问题"></a>背包问题</h3><h4 id="01背包"><a href="#01背包" class="headerlink" title="01背包"></a>01背包</h4><h4 id="完全背包"><a href="#完全背包" class="headerlink" title="完全背包"></a>完全背包</h4><h4 id="多重背包"><a href="#多重背包" class="headerlink" title="多重背包"></a>多重背包</h4><h4 id="分组背包"><a href="#分组背包" class="headerlink" title="分组背包"></a>分组背包</h4><h3 id="线性DP"><a href="#线性DP" class="headerlink" title="线性DP"></a>线性DP</h3><h3 id="区间DP"><a href="#区间DP" class="headerlink" title="区间DP"></a>区间DP</h3><h3 id="计数DP"><a href="#计数DP" class="headerlink" title="计数DP"></a>计数DP</h3><h3 id="数位统计DP"><a href="#数位统计DP" class="headerlink" title="数位统计DP"></a>数位统计DP</h3><h3 id="状态压缩DP"><a href="#状态压缩DP" class="headerlink" title="状态压缩DP"></a>状态压缩DP</h3><h3 id="树型DP"><a href="#树型DP" class="headerlink" title="树型DP"></a>树型DP</h3><h3 id="记忆化搜索"><a href="#记忆化搜索" class="headerlink" title="记忆化搜索"></a>记忆化搜索</h3>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;机式重点永远是&lt;strong&gt;树结构&lt;/strong&gt;、&lt;strong&gt;图结构&lt;/strong&gt;以及&lt;strong&gt;动态规划&lt;/strong&gt;！！！&lt;/p&gt;
&lt;h2 id=&quot;夏令营经典题型&quot;&gt;&lt;a href=&quot;#夏令营经典题型&quot; class=&quot;headerlink&quot; tit</summary>
      
    
    
    
    <category term="Notes" scheme="https://gme-hong.github.io/categories/Notes/"/>
    
    
    <category term="Algorithms" scheme="https://gme-hong.github.io/tags/Algorithms/"/>
    
  </entry>
  
  <entry>
    <title>一封跨越半个世纪的来信引发的思考</title>
    <link href="https://gme-hong.github.io/2024/11/13/Reflections/"/>
    <id>https://gme-hong.github.io/2024/11/13/Reflections/</id>
    <published>2024-11-12T18:10:37.000Z</published>
    <updated>2025-02-28T14:23:42.244Z</updated>
    
    <content type="html"><![CDATA[<div class="hbe hbe-container" id="hexo-blog-encrypt" data-wpm="Oh, this is an invalid password. Check and try again, please." data-whm="OOPS, these decrypted content may changed, but you can still have a look.">  <script id="hbeData" type="hbeData" data-hmacdigest="d52563c86848ec6f71f8daec2d3c0ba06b3a65abaec87e1206dd2484f887bb6b">f40388f3de01b3a13117d1077f5eb92a2eb67d1a5e775a90f5101260360f3fd708b9f20eddc3bd1d00e28eaeb63f1750652a775a98105f39bcf4c574d108f75f97420e27736b7f67a8ebae6c685ebc6140799c134d894d37e4c1ec8018976471d23a907a258135a5bf1e41d891ec99ad0fade21d913bee00bf498e2ac36923019ce9dbf7bc4682192a72ca2110677cb3ede92caff0949ea4994702c84906600fcd624ff9799b21fca84e69fddf707123102073c63e5756001ff0b25ec33fe47c77c8607e939a00533a035f33e3ca26deb9222ea746b215e99aae1d8575f31a4f6b6ec2241cb458f01f46c5d35a168c3f90aa37c10e3de201ed32f2da0444fe318c67101203a21a12512f0c9e06dd34610bc8b3f9d70a68a3be6cb7e2d4de4d7ec8c19de1fbbb98085e38b34b1c0ab1d339890f8945a4894664b2601fe05a990abc7a50aaa88b8c50e1f009907a45b97c263615271e76bc5d721de0d221cbb28f120b901f69dd6f2c4d1a39cef4728ebb0f117880ee8c9c981b33c2115b71ea999781414d053fdf4bd48caeadbc380289846f45f04bb02aee832af7d354ec59731fe9099b06bc96f44ada447886f40b96273516379f8cb662dbac8313ec24f7a8abe3fc904045d2032e1fdf46618bc65ddb0b610bccfae103b0e693c2665d687bb7c0306a6d771935a99246f9c1a094a6b4c8c8b0d1c1f3fe0eca5a4ce47cc6a25696fc6017716942583f2422252e7579ff9df2d4c04a0dc76d0ca6ed311f72361d25d15d05ba01e83644c7d7a2e33b0da737e9f9ef2531942df08d926cf16465e879e37c7c16462e913ec0b8476864cc982d1735a7c6823f3cc825536b09991df972244ba5f7141c8cdac3efc60ce18c8f22a67f9a7bdd9d3b158008886c6ff8d0a3d2ea9c22ecb6c225537416f0c4e2a22717048fd6d640e1e8a0da34127e977c73ece8856f84dc4a0d72f57ffb7694e8f0ac3ff8d7105a1c0a1f1644b12fdb8985bed0ae0fea3d1dae39b394bd8471b48142baf71dcc71502bc51548df0431dbd267a9caf86393e5e875d7ff55392282f1693203f54b2cc80780aefd5f1c1c8f60d34631ab32ade911114ae682264724ec9e9309adb16e0721ea2044c7e8242bba340bbc6c03bfd75dacfc81b93fa6d6435c46bc35d2756629b465f1f0968ef9dc57eed1e4ac232ef7e61e594bd298021a8dbff0f55f3f8e8e2c231368864b546f2d4d50ea3d7a26f73e1748f665ebfac7d87ef509bc87eecd24116fbb0e9307c7d8b1ac3a261fddb9bd758ccf9db22989b8bd859d212e50f77af115a5e13cb45c470265bfd3b3dfe2bbaf3a9e7417c7827d40b62b49439ff0f9bc58067cc6f6b55d7f30a60101ab4dc06eb48235a6824547a6bf3f3d7fc63e9c787394038cf6b45fbefafb689075ab0eb8983643474451af0e5acc8d49664905e925fa2bc96a15a09dda9aad8980365c7112a88e7760689445d2c8193a1764eaa47030074e7d29d9e7a1771e1ec5e05a18986db02ec7f8c73a226ab4ebf6cc5747c4ed6889e907b12337f37b889c5cff934d6aaf37b9a82d8bf24efec128cb4298849da6cd7cf5e1165e4be5180706708330b22648a05a9cb450dc60d9ed583ed8b9580ebf8603b4c42122315809bf9ae07eccd79c6c6812ee26289493796ef9b8a94939cb5038f4dfe1eefeaa1087b6300b71ec9f7a1bf50e9399fbdd03bba29b9e08b2760d8a6f5f2512e9a2675d972e91071e6fac6d51d7bc78ec3e3f50b80523a943416455c3533f765b849b83e5afa76035d206f2b6209b6a73f9483e98a9f7b0af39435bd4890e61b43645ad1011915f8b4ccf4ec3aa22b67bc7434d4727529bebc72d5b6e6cd02ffdda4b9353277972d1df2969c5738835d61a435f0214b546df3971d5fe34c29a124b014ce45c964f1f5a63131b71bcf9a54edd1097eae18f99f8ed729c92bc911c1a7fcd2de52391488f58f1b35ec92e7cd18d59659eb9048afe701502f8190100e10ac82bc75d0e7f19b462b927f8c6a5087d9f33e6f8c7b9d2fb5c2433bd4f0354db89ba99cb09f0fd72f5879415ee85ab36636cc804962df3edbf0b22abadbd958c8e6c6511f75739182676b7fc45b028b2ec8f4173a990bb7d168d969769fbd474056e8c6a2edff5214f0efb49f8893a6250a01a0bbd6afdd66ba6ded6cede2d4cb14198c794d2812bfd3abd55b80847ef333a7379c6f211e65530a39d03762c24a0fd7912108039240008c899685b46049bca60fe48672a7d0ed78ea3dd8b2284fee713a84617658b4b51a3149dececad16ddb981406782cc52fda050efd661f16f85e698466d419857fe28636d70b79d41959db20423e99a0276993b947dbe4dbe0d22865e776a0f85a2fd394c94e5a58c4f6b40ab42a7fefb7ca5349d104c178eed6b4eb638f8fe7eb5fc5d85748ce9dbb12a55046da5e6c9788c16e1827a3fca69aa0ea6aecdd5694ae83c4738a22bc907e5c632b1cc42d102eed0eefbe1744498d2ba1e0427472b9628e4575f0fe3783526431aa61cdb92a976d6a72bfe5f1de584faa8943a2a1279cd1fb369de8f4201b060521eefa671b9cecc95a028945dba69290784bf1dc4f67d636d9dbac0c3f15320fae478c4248e67993a306f2555dfe4ce61ac94e8db91ebf8b2b3707dfc320dc57623b3ccbab12e1ffa68e81c89e59e40fc9c45e5a7d14f2c3c0566d7579346081bc34b7f7e5f86b4fc77e3b4b9a9a6ddd8feed802891c0e021a68420748f290ce5e3ceeb95d955c6a74b121372b003476277f997010171a2cc4dc206a796a7ffc25eae4afaa0aa35a0bb0e38bc6baaacdb4ee80699d05114fafd0eb73aee34d6b3fadcc522cd10db0df5f37e8fcad163d36125183aba846db15264969cece4d57bd14364a7bd3ec780d4a7c8e7e852b5fd36aee5f87c7268930142be6d6398c577fcfba0f5d561276a679e74b7a648331a6fe175e643d6015f3a782409c6a48b4f94e9b593d1287800ab46fc395e83dee6e24fe944aa5c02b3127fbe3c1223485616b5ac52fe1adbf8664b9731d9d7077941facdb0fc2395ed7813d3889948f41afb8a1b266e950300853f5f46b61bd434283527f51da60602b100fabee99b4524335a66900ac62d1c0f9961b898171a3119d4d83830881140e5f1cf1e5e63d33ea86b5c80756227eb6ead2b666857ced559d0a44f6fdd7ba9f01082d05fd3f30e228a8610a28df9aca7aeca226aeb17051da3547ec8d7ed8742df290f70e541ee6e88487948333891a4f7af1b7865f308a5d2f7564079ec646c16d715e84db8661df977631790de0ad9758e2916412b9ed1f016a5b5e7c4040f2b727ec7416b6e37c3a5c66224a68fda898c3ae61918de9500c85931f707046d89c509bf115688b6ac01c67eae8681e18cb01da23d8045daf8eecedc9326ede41b63dd35f3e70064b4b42f9f78bb6724762b178e44115c67fabf765f2d159d53d27477787c64141a8024b38692b07aa2e7ee8f7d2d357999c7ac175cfead0fa2452e6127a84c659256999b05e844b80f22a6842ea86cabe460a343846803730a2dc77f35bc17f6080bad05f683eabf02ad35107d765af8b23ac516651f4964f2dd1715a98e157d8c887542cc5f57fc8bd5f61da3de43c136cfc2ef18866d3c4066878ce7088e440336db5f70c06aca04bb7058c44cd1922d2876c9078997f7ea33fd5eeed96e64639a0d45c400ac59588afa54fb64c9acf220809a6077f99e77715a9cc15b5fe998a7003e8b8fe2d11f219e7e26daaa7eafd3bb282ab080485972db2b2abe40240973f9175c0e89df0ddcf55c984bd7c9ec80b1256f085a85769da8175258eb9e62f461204b71edafa8486ff1566636df0103a2effc3d87c75d4157d78d24b564bcae92806e7e6fc68a4384789d8a02732eb7ca6d5180b37b9f25844eac2a7858f7674af6a9c786fbc54890de4f6a8e4c0b856992c15eff9c2322138eb270731b726c14c0c5a9fd9f2ebb66fc5306a85ad37dafdf308c1483e3b8a42e4e2074cb9cbc8d5ac7ddc24ed66f1762d38630ecb21f00aa5089684d8c189e6f5325d9605d8377e04532a42d8d3afcf65541fffc5f5c9fc5c0005e5637bac78a304131e7101ab0011c77e47901ffe84770bdf9124d9dbb5704944e72f815d3f7c0d67623b2e7ac40b77e0885285b469c10e1e4a72ef4254ca0c4d2a235f385ecd952107d2a123674ed6f30785589964495b17759db573f80348e8884cc28bc7fdbc756bd94a64c5722633ea8e494f1c242f98f947ce16d0c4c7e362300a214b120b1b2f08861af9a8e6c38e7a8ff4a4284049c458a1d651d9bbf54f9251fb5332fc88177f26adc5a0ac9c8f9ec58effe41a88e38744127a0c5a163978cb7dcf1ac902c774ada14a494af64d750e0366c345ca7bc6d271d90dec15667c924d7f5430d50e53ae01bb547dee205215df0e88312ef6fd62a82e0980404b140af25728579883de2dbffd57c3674ffff1c35c97cb1d1e31ecc77b7616fd061e1042b6cd50bd81c7d2ff4aaf61577b3d8791e3630873d9f33565a543323794591cc98ae164a4b91dd9a8ca5744b5145a279aec17381cfe75978056a8b01fa49c421e2d46c8aa32edc1b036c1ca978b9267786340c22887d946d388a49d7278177be3425144efd47d1e2dd2557656292f0da9920337dd58381cd0f553f90e8462dd3b3348513f5e736655955bdb82504bee54b35bb091aba9382aea1fca085a7cac38acd917f7220292aea2c1aa31a121f40c7d374209ed532b253387b880c58a52a6864d5eea2c297149ba693250b5fbcea7921d1e67c4283159bd18ec0ea4a91416662a46866d9c61a2028f6cdd414912e6647745df0dfe74e00a783ea2fe7415ed652b0f5f589748229848bbbdc7ab27ef6f1d1a0f3ef73412cff3af22f4ba5c773d3713fdd110fb31a1eb62c9f44c70467d64702573278aa1b90198eddfde278fb71b513b050776649afb363c4e978b5f92ec6375d53127743c26ea97752f65e18a0d64ae5d65472be36c98a9d0bb91c9b004cd29ab35055d7d9c5e0af65619b82a0ad5377657e33e01fe6dc791c068a0e714f3279e8dba923a3b2e5655849cb26bb9043ae4d21e6</script>  <div class="hbe hbe-content">    <div class="hbe hbe-input hbe-input-default">      <input class="hbe hbe-input-field hbe-input-field-default" type="password" id="hbePass">      <label class="hbe hbe-input-label hbe-input-label-default" for="hbePass">        <span class="hbe hbe-input-label-content hbe-input-label-content-default">Hey, password is required here.</span>      </label>    </div>  </div></div><link href="/css/hbe.style.css" rel="stylesheet" type="text/css"><script data-swup-reload-script type="module" src="/js/plugins/hbe.js"></script><script data-swup-reload-script type="module">import {initHBE} from "/js/plugins/hbe.js";  console.log("hexo-blog-encrypt: loaded.");    initHBE();</script>]]></content>
    
    
    <summary type="html">Here&#39;s something encrypted, password is required to continue reading.</summary>
    
    
    
    
  </entry>
  
  <entry>
    <title>_._</title>
    <link href="https://gme-hong.github.io/2024/10/01/Undergraduate-Experience/"/>
    <id>https://gme-hong.github.io/2024/10/01/Undergraduate-Experience/</id>
    <published>2024-10-01T14:29:36.000Z</published>
    <updated>2025-02-28T14:24:33.213Z</updated>
    
    <content type="html"><![CDATA[<h3 id="Preface"><a href="#Preface" class="headerlink" title="Preface"></a>Preface</h3><p>就像<code>Cover</code>中所描述的那样，我的推免之路并不是一帆风顺，中间有太多插曲，甚至一度做好了放弃的打算，但好在沉住了气，守得云开见月明！</p><h3 id="BG"><a href="#BG" class="headerlink" title="BG"></a>BG</h3><p>本科：山西大学-计算机科学与技术专业（第五轮学科评估B+）</p><p>专业排名：1&#x2F;117（综合）、9&#x2F;117（GPA）</p><p>英语：540（CET-6）、587（CET-4）</p><p>竞赛：国奖（国二、国三）、省奖（省一、省二……）</p><p>科研：一篇中科院二区TOP一作（RA-L）</p><p>夏令营：西工大CS（学硕优营）、电子科大深思（专硕优营）、电子科大CS（学硕优营）、厦大MAC（专硕优营）、浙大软院（放弃）、华东师范（放弃）、北邮5组（放弃）</p><p>预推免：中科院VIPL、同济CS（放弃）、中科大10系（offer）、西安交大软院（wl）</p><p><strong>最终去向：中科大10系</strong></p><h3 id="我在山大的三年"><a href="#我在山大的三年" class="headerlink" title="我在山大的三年"></a>我在山大的三年</h3><p>过去三年的经历还是相当丰富的，但好像主旋律依旧是“学习”。</p><p>本人记性不是特别好，很难面面俱到地回想起前三年的点滴，因此后面主要以重要的时间阶段作为回忆的锚点，如果你对某些故事特别感兴趣亦或想了解更多，欢迎邮件。</p><p>本人并不是一个外向的人，这一路大多都是摸石头过河，很少参考往届学长学姐的经验。执笔此文也是希望，我这个样本，能给后面的SXUers提供更多的参考。希望诸位都能站在前人的肩膀上，能预见的更多，能有更多从容选择的机会。</p><h4 id="春风得意马蹄疾，一日看尽长安花。"><a href="#春风得意马蹄疾，一日看尽长安花。" class="headerlink" title="春风得意马蹄疾，一日看尽长安花。"></a>春风得意马蹄疾，一日看尽长安花。</h4><p><strong>这个时期（大一）主持了年级的迎新晚会，尝到了成果的甜头，绩点稳中向好，有过一段短暂的恋爱经历……总之算是前三年最轻松的时段。</strong></p><p>上大学之前，我可完全没有主持的经验，主打一个突破舒适区。可能是选拔有点水，反正我就顺利地成为了晚会的主持人，虽说顺利，但好像前前后后应该也是面了三次。也就是这一次经历，结识了一个特别有趣、努力的天津姑娘，古力博坤（晚会主持人之一）。</p><style>    .container {        max-width: 600px;        height: auto; /* 设置容器高度为视口高度 */        margin: auto;    }    .container img {        max-width: 100%; /* 图片自适应容器宽度 */        height: auto; /* 保持比例 */    }</style><div class="container">    <style>.tanjmgksxzok{}</style><img lazyload src="/images/loading.svg" data-src="/2024/10/01/Undergraduate-Experience/WechatIMG573.jpg" class="tanjmgksxzok" alt="WechatIMG575"></div><p>这是晚会前一天去柳巷租完礼服后路边拍的，摄影师是古力博坤。你就说，这人物，这构图，是不是满昏😳。特殊时期，也是乖乖地戴好口罩。</p><div class="container">    <style>.iqcjxnvnkrug{}</style><img lazyload src="/images/loading.svg" data-src="/2024/10/01/Undergraduate-Experience/WechatIMG574.jpg" class="iqcjxnvnkrug" alt="WechatIMG575"></div><p>这应该快到晚会闭幕式时拍的。emmm，除了略显局促，其他完美🤪！</p><hr><p>这个成果指的是一套基于兴趣聚类的宿舍分配系统。这是由我们辅导员老高牵头，我作为负责人完成的一个项目，也是我大学第一个接手的项目。虽然这个过程中出现了很多小插曲，但总算顺利地按我承诺的期限和质量完成了这个项目。这段经历真正让我以“做好产品”的角度去思考问题，也让我开始去思考一个团队如何高效地进行合作（敏捷开发模式中提倡的<strong>小而精</strong>的开发团队是很有必要的）。</p><div class="container">    <style>.pllvqpexmtnz{}</style><img lazyload src="/images/loading.svg" data-src="/2024/10/01/Undergraduate-Experience/WechatIMG575.jpg" class="pllvqpexmtnz" alt="WechatIMG575"></div><p>这个项目之后，也是自信溢满。（主要也还是导员在那吹：什么一般的毕业生也就做一个小系统，你们能独立地弄出来已经快赶上毕业生的水平了。🌝balabala……）</p><p>其实回首观望，也就那么回事。<strong>只是说以当时的眼界、格局和心境，说“春风得意”也为之不过</strong>。</p><hr><p>这个时期上课也是不敢懈怠（当然是什么课也不敢懈怠，不去就扣学分的Warning⚠️，在这个时候还是蛮具有威慑力的），所以绩点基本上也没什么大问题。</p><div class="container">    <style>.fwoxnexyazca{}</style><img lazyload src="/images/loading.svg" data-src="/2024/10/01/Undergraduate-Experience/image-20241003011758940.png" class="fwoxnexyazca" alt="WechatIMG575"></div><p>谁懂，之前没学过游泳，刚上游泳课跟不上进度，周末自费去游泳馆🐷……别问我构图为什么是斜的，emmm，可能当时候就喜欢这样拍🥲？</p><h4 id="长路漫漫无尽处，迷雾重重向何方。"><a href="#长路漫漫无尽处，迷雾重重向何方。" class="headerlink" title="长路漫漫无尽处，迷雾重重向何方。"></a>长路漫漫无尽处，迷雾重重向何方。</h4><p>这段时期应该是我整个大学生涯最迷惘的时候。</p><p>特殊时期，我们有两场期末考试是在线上进行的（大一下和大二上），也就是大二上这场线上考试彻底击碎了我的心理防线。一场考试下来竟然有人把绩点稳稳地维持在4.0往上，简直离谱（不过三年下来，专业绩点最高也就3.87，是什么原因大家都心知肚明）。因为有这样一群“大牛”的存在，自然我的排名就掉到百米开外咯，也让我一度觉得保研这件事已经跟我没有太大关系。所以当时最纠结的一件事就是，是继续坚持保研这条路，还是直接放弃，提前准备考研。</p><p>这段记忆之所以那么深刻，是因为我始终记得那个每天晚上走在操场上，迷惘，不知所措的少年。</p><p><strong>当然也就是这样一段时期，让我深刻地意识到，我的大学仿佛又活成了高中的模样</strong>。</p><h4 id="长凤破浪会有时，直挂云帆济沧海。"><a href="#长凤破浪会有时，直挂云帆济沧海。" class="headerlink" title="长凤破浪会有时，直挂云帆济沧海。"></a>长凤破浪会有时，直挂云帆济沧海。</h4><p>随着所做的项目“基于2D虚拟人语音驱动算法的研究”在第十四届服务外包创新创业大赛中接连取胜，我也逐渐从迷失的状态慢慢过度到无问东西的状态。没错，前一个阶段我还是挺过去了，很庆幸当时并没有放弃！</p><style>    .carousel {        position: relative;        width: 100%;        max-width: 600px;        max-height: 400px;        overflow: hidden;        margin: 16px auto !important;    }    .carousel-images {        display: flex;        transition: transform 0.5s ease;    }    .carousel-images img {        width: 100%;        height: auto;        object-fit: cover;    }    .carousel-buttons {        position: absolute;        top: 50%;        width: 100%;        display: flex;        justify-content: space-between;        transform: translateY(-50%);    }    .button {        background-color: rgba(255, 255, 255, 0.7);        border: none;        cursor: pointer;        padding: 10px;    }</style><script>    let currentIndex = 0;    const slideInterval = 3000; // 每3秒切换一次    function showSlide(index) {        const slides = document.querySelectorAll('.carousel-images img');        if (index >= slides.length) {            currentIndex = 0;        } else if (index < 0) {            currentIndex = slides.length - 1;        } else {            currentIndex = index;        }        const offset = -currentIndex * 100;        document.getElementById('carouselImages').style.transform = `translateX(${offset}%)`;    }    function nextSlide() {        showSlide(currentIndex + 1);    }    function prevSlide() {        showSlide(currentIndex - 1);    }    // 自动轮播    setInterval(nextSlide, slideInterval);</script><div class="carousel">    <div class="carousel-images" id="carouselImages">        <style>.syuxvgxxwcin{}</style><img lazyload src="/images/loading.svg" data-src="/2024/10/01/Undergraduate-Experience/WechatIMG579.jpg" class="syuxvgxxwcin" alt="The 14th China College Students Service Outsourcing Innovation and Entrepreneurship Competition">        <style>.mrmuulegncvh{}</style><img lazyload src="/images/loading.svg" data-src="/2024/10/01/Undergraduate-Experience/WechatIMG582.jpg" class="mrmuulegncvh" alt="The 14th China College Students Service Outsourcing Innovation and Entrepreneurship Competition">        <style>.dwbfbrrmmhij{}</style><img lazyload src="/images/loading.svg" data-src="/2024/10/01/Undergraduate-Experience/WechatIMG584.jpg" class="dwbfbrrmmhij" alt="The 14th China College Students Service Outsourcing Innovation and Entrepreneurship Competition">        <style>.hfpduvgtgxjf{}</style><img lazyload src="/images/loading.svg" data-src="/2024/10/01/Undergraduate-Experience/WechatIMG585.jpg" class="hfpduvgtgxjf" alt="The 14th China College Students Service Outsourcing Innovation and Entrepreneurship Competition">        <style>.jlypmzqvwgnn{}</style><img lazyload src="/images/loading.svg" data-src="/2024/10/01/Undergraduate-Experience/WechatIMG588.jpg" class="jlypmzqvwgnn" alt="The 14th China College Students Service Outsourcing Innovation and Entrepreneurship Competition">        <style>.rkyitfghuyov{}</style><img lazyload src="/images/loading.svg" data-src="/2024/10/01/Undergraduate-Experience/WechatIMG591.jpg" class="rkyitfghuyov" alt="The 14th China College Students Service Outsourcing Innovation and Entrepreneurship Competition">        <style>.xvgkoqtpyuep{}</style><img lazyload src="/images/loading.svg" data-src="/2024/10/01/Undergraduate-Experience/WechatIMG593.jpg" class="xvgkoqtpyuep" alt="The 14th China College Students Service Outsourcing Innovation and Entrepreneurship Competition">        <style>.urwbgfmredjo{}</style><img lazyload src="/images/loading.svg" data-src="/2024/10/01/Undergraduate-Experience/WechatIMG594.jpg" class="urwbgfmredjo" alt="The 14th China College Students Service Outsourcing Innovation and Entrepreneurship Competition">        <style>.neowyycsyejq{}</style><img lazyload src="/images/loading.svg" data-src="/2024/10/01/Undergraduate-Experience/WechatIMG576.jpg" class="neowyycsyejq" alt="The 14th China College Students Service Outsourcing Innovation and Entrepreneurship Competition">        <style>.elikzlgnttcz{}</style><img lazyload src="/images/loading.svg" data-src="/2024/10/01/Undergraduate-Experience/WechatIMG589.jpg" class="elikzlgnttcz" alt="The 14th China College Students Service Outsourcing Innovation and Entrepreneurship Competition">        <style>.vhpyvgaibksj{}</style><img lazyload src="/images/loading.svg" data-src="/2024/10/01/Undergraduate-Experience/WechatIMG592.jpg" class="vhpyvgaibksj" alt="The 14th China College Students Service Outsourcing Innovation and Entrepreneurship Competition">        <style>.ahlbxklfisne{}</style><img lazyload src="/images/loading.svg" data-src="/2024/10/01/Undergraduate-Experience/WechatIMG596.jpg" class="ahlbxklfisne" alt="The 14th China College Students Service Outsourcing Innovation and Entrepreneurship Competition">        <style>.gtsyfxjdqhpu{}</style><img lazyload src="/images/loading.svg" data-src="/2024/10/01/Undergraduate-Experience/WechatIMG599.jpg" class="gtsyfxjdqhpu" alt="The 14th China College Students Service Outsourcing Innovation and Entrepreneurship Competition">        <style>.phwtmirttmtn{}</style><img lazyload src="/images/loading.svg" data-src="/2024/10/01/Undergraduate-Experience/WechatIMG600.jpg" class="phwtmirttmtn" alt="The 14th China College Students Service Outsourcing Innovation and Entrepreneurship Competition">        <style>.hsptezkmpupn{}</style><img lazyload src="/images/loading.svg" data-src="/2024/10/01/Undergraduate-Experience/WechatIMG601.jpg" class="hsptezkmpupn" alt="The 14th China College Students Service Outsourcing Innovation and Entrepreneurship Competition">    </div>    <div class="carousel-buttons">        <button class="button" onclick="prevSlide()">&#10094;</button>        <button class="button" onclick="nextSlide()">&#10095;</button>    </div></div><p style="text-align: center; color: gray; margin-top: -10px;">No action? Refresh.</p><p><strong>在逆境中重生，这个时期可以说是最艰难的时候，是身心俱疲但仍需向上求索，是逐渐重拾信心的阶段。也很感谢，身边一直有一个小团队在支撑着我前进。</strong></p><h4 id="竹杖芒鞋轻胜马，谁怕？一蓑烟雨任平生。"><a href="#竹杖芒鞋轻胜马，谁怕？一蓑烟雨任平生。" class="headerlink" title="竹杖芒鞋轻胜马，谁怕？一蓑烟雨任平生。"></a>竹杖芒鞋轻胜马，谁怕？一蓑烟雨任平生。</h4><p>进度条来到大三，我的竞赛加分已经全部加满。也就是说，我的推免资格已经不成问题了，接下来需要做的就是被外校认可。也就是这一时期，开启了我的科研之路。<strong>我并不想把这一年描述的有多艰难，因为课题组的氛围实在太棒了，陈老师一直把我视为己出，各位师兄师姐都非常的友好，以至于很多时候都有被治愈到</strong>。</p><style>    .vertical {        display: flex; /* 使用 Flexbox */        flex-direction: column;        justify-content: center; /* 水平居中 */        align-items: center; /* 垂直居中 */        max-width: 600px;        height: auto; /* 设置容器高度为视口高度 */        margin: 16px auto !important;    }    .vertical img {        max-width: 100%; /* 图片自适应容器宽度 */        height: auto; /* 保持比例 */        margin: 8px !important;    }</style><div class="vertical">    <style>.mrmsliyjpshi{}</style><img lazyload src="/images/loading.svg" data-src="/2024/10/01/Undergraduate-Experience/WechatIMG1172.jpg" class="mrmsliyjpshi" alt="WechatIMG575">    <style>.ipbntcinxmnv{}</style><img lazyload src="/images/loading.svg" data-src="/2024/10/01/Undergraduate-Experience/DineTogether.jpg" class="ipbntcinxmnv" alt="WechatIMG575">    <style>.xnpditglytgd{}</style><img lazyload src="/images/loading.svg" data-src="/2024/10/01/Undergraduate-Experience/fig1.jpg" class="xnpditglytgd" alt="WechatIMG575">    <style>.qvgrqzrgmcgl{}</style><img lazyload src="/images/loading.svg" data-src="/2024/10/01/Undergraduate-Experience/WechatIMG1176.jpg" class="qvgrqzrgmcgl" alt="WechatIMG575"></div><p>课题组经常会聚餐（哈哈哈，也是在这个过程，我学会了喝酒~白的哦！）和团建，<strong>这一学年里与大家留下来很多珍贵的回忆，承蒙各位师兄师姐关照</strong>。</p><p>当然，这一学年最令人激动的还是在6月13号，我的处女作被录用啦~</p><div class="container">    <style>.gykemwibtxrw{}</style><img lazyload src="/images/loading.svg" data-src="/2024/10/01/Undergraduate-Experience/RA-L2024.png" class="gykemwibtxrw" alt="WechatIMG575"></div><h4 id="梦里寻他千百度，蓦然回首，那人却在，灯火阑珊处。"><a href="#梦里寻他千百度，蓦然回首，那人却在，灯火阑珊处。" class="headerlink" title="梦里寻他千百度，蓦然回首，那人却在，灯火阑珊处。"></a>梦里寻他千百度，蓦然回首，那人却在，灯火阑珊处。</h4><p><strong>BG完善的差不多，这个阶段的主线任务就是利用手头的资源博取更大的收益</strong>。资料准备-夏令营-预推免-正式推免，从今年的四月份到九月份，应该是焦虑混杂着不安，让人睡不着觉的时候，也就是这个时期我的头发掉的最多。（当然，没秃啊！）</p><p>我们可以详细聊聊资料应该准备哪些！（下图仅供展示，所有能Share的资料已全部在主页展出，请不要再私聊索取资料！）</p><div class="container">    <style>.foladkfgnxgw{}</style><img lazyload src="/images/loading.svg" data-src="/2024/10/01/Undergraduate-Experience/image-20241007180248175.png" class="foladkfgnxgw" alt="WechatIMG575"></div><p><strong>首先，最重要的是所有的资料都需要有条不紊</strong>。最好用一个方便文件管理的软件，当然系统的文件夹也是可行的，重要的不是工具，是意识。</p><p>其次，你应该着重准备的材料是：<strong>个人简历，相应的证明材料，Personal Presentation</strong>，这三项是最重要的；其次还有：推荐信，个人陈述（未来规划），各院校的官网、历年推免信息（建议有能力的同学，可以利用RSS进行管理）……</p><p>最后，<strong>你应该思考如何完成一场较为精彩的复试考核</strong>，eg.面试、机式、笔试。不是所有院校都会组织机式和笔试，但是面试是肯定有的。因此你必须做的是梳理前面三年的经历，可选择的是专业课复习（当然，其实面试时也很有可能涉及到专业课考核）、OJ复习。</p><h5 id="西安"><a href="#西安" class="headerlink" title="西安"></a>西安</h5><p>首次西安行是西工大，刘老师人很好。还没过去之前，跟刘老师说了我高铁时间的问题，老师也是直接帮忙解决了！</p><p>虽然，西工大（长安校区）确实是偏了些，但在同等条件下，第五轮CS评估A+对潜心科研的同学未免是一个坏事。</p><div class="container">    <style>.alzftfzrxsau{}</style><img lazyload src="/images/loading.svg" data-src="/2024/10/01/Undergraduate-Experience/image-20241007191940171.png" class="alzftfzrxsau" alt="WechatIMG575"></div><p>西工大还有一个正式的开营仪式，本来拍了几张照片，但是删掉了……下面几张还是借的同学的😬。</p><style>    .thumbnails {        display: flex;        white-space: nowrap;        overflow-x: auto;        margin: 8px auto 8px auto;        justify-content: space-between;    }    .thumbnails img {        width: 100px; /* 缩略图宽度 */        height: 100px; /* 缩略图高度 */        cursor: pointer;        margin: 0 5px;        border: 2px solid transparent;    }</style><div class="thumbnails">    <style>.agcknodcktbn{}</style><img lazyload src="/images/loading.svg" data-src="/2024/10/01/Undergraduate-Experience/WechatIMG1181.jpg" class="agcknodcktbn" alt="缩略图1">    <style>.fyztjmhyegdx{}</style><img lazyload src="/images/loading.svg" data-src="/2024/10/01/Undergraduate-Experience/WechatIMG1180.jpg" class="fyztjmhyegdx" alt="缩略图2">    <style>.maukfckleunw{}</style><img lazyload src="/images/loading.svg" data-src="/2024/10/01/Undergraduate-Experience/WechatIMG1179.jpg" class="maukfckleunw" alt="缩略图3">    <style>.mqucevvgvxkh{}</style><img lazyload src="/images/loading.svg" data-src="/2024/10/01/Undergraduate-Experience/WechatIMG1178.jpg" class="mqucevvgvxkh" alt="缩略图3">    <style>.irbpweyzdmph{}</style><img lazyload src="/images/loading.svg" data-src="/2024/10/01/Undergraduate-Experience/WechatIMG1177.jpg" class="irbpweyzdmph" alt="缩略图3">    <style>.bejouskxjvvc{}</style><img lazyload src="/images/loading.svg" data-src="/2024/10/01/Undergraduate-Experience/WechatIMG484.jpg" class="bejouskxjvvc" alt="缩略图3">    <style>.grjbjmffraxh{}</style><img lazyload src="/images/loading.svg" data-src="/2024/10/01/Undergraduate-Experience/WechatIMG485.jpg" class="grjbjmffraxh" alt="缩略图3">    <style>.ihbmolfxuqux{}</style><img lazyload src="/images/loading.svg" data-src="/2024/10/01/Undergraduate-Experience/WechatIMG486.jpg" class="ihbmolfxuqux" alt="缩略图3"></div><p>西工大的考核也只有面试，组成是“Presentation+英语+政治+专业（BG）”。考核的时候，刘老师就坐在旁边，但是我还是挺紧张的。老师中途还提醒我声音大点，但是我没听清，忽略掉了😅。当然，最后还是拿到了优营（93.8&#x2F;100）。</p><h5 id="成都"><a href="#成都" class="headerlink" title="成都"></a>成都</h5><p>成都一行太过仓促，只在成都待了一天，面完电子科大就撤了。当然撤之前去了趟太古里，去IFS看了那个熊猫屁股🤔。</p><div class="vertical">    <style>.tpqcwgojtaco{}</style><img lazyload src="/images/loading.svg" data-src="/2024/10/01/Undergraduate-Experience/22041720004955_.pic.png" class="tpqcwgojtaco" alt="WechatIMG575">    <style>.tgbyngwlrred{}</style><img lazyload src="/images/loading.svg" data-src="/2024/10/01/Undergraduate-Experience/22171720005226_.pic.png" class="tgbyngwlrred" alt="WechatIMG575"></div><p>成电的夏令营考核也只有面试，组成是“自我介绍+专业课+英语+BG”。</p><p>虽然过程挺仓促的，但是依旧成功拿下成电的CS学硕，我觉得是夏令营最大的成功。以至于后面我拿到中科大专硕的offer时，依旧犹豫如何做选择。</p><p>联系的王老师人也特别好。王老师是我夏令营的主面试官，后面拿到优营，也是第一时间给老师发去了邮件，老师看到后也是第一时间打电话过来确认，至此我有一个特别满意的保底院校。可是最后还是没能过去，挺对不起王老师的😭。</p><h5 id="厦门"><a href="#厦门" class="headerlink" title="厦门"></a>厦门</h5><p>厦大是我夏令营的最后一程，后面陆陆续续还有其他学校的入营，但都拒绝了。厦大的夏令营考核包括机式和面试（当然，在此之前Mac实验室还有一轮的Paper Reading和线上汇报），机式就三道题，面试主要是BG。</p><p>其实我很清楚就算拿到了厦大Mac的offer可能也不会过去，之所以仍然附身前往，是因为之前和大师兄有个约定——沿沙滩而坐，海风习过，畅饮而谈。</p><style>    .box9 {        display: grid;        grid-template-columns: repeat(3, 1fr); /* 三列 */        gap: 10px; /* 项目之间的间距 */        max-width: 600px; /* 容器宽度 */        margin: 16px auto;    }    .box9 div {        width: 100%; /* 每个项宽度100% */        aspect-ratio: 1; /* 使每个项保持正方形 */        overflow: hidden; /* 隐藏溢出部分 */        border-radius: 8px; /* 圆角效果 */    }    .box9 img {        width: 100%; /* 图片宽度100% */        height: 100%; /* 图片高度100% */        object-fit: cover; /* 保持比例并填充 */        margin: 0px !important;    }</style><div class="box9">    <div><style>.ipfirtclewlu{}</style><img lazyload src="/images/loading.svg" data-src="/2024/10/01/Undergraduate-Experience/WechatIMG1190.jpg" class="ipfirtclewlu" alt="图片1"></div>    <div><style>.cvfodjtrggvr{}</style><img lazyload src="/images/loading.svg" data-src="/2024/10/01/Undergraduate-Experience/WechatIMG1183.jpg" class="cvfodjtrggvr" alt="图片2"></div>    <div><style>.lbivdoukxngx{}</style><img lazyload src="/images/loading.svg" data-src="/2024/10/01/Undergraduate-Experience/WechatIMG1182.jpg" class="lbivdoukxngx" alt="图片3"></div>    <div><style>.femmevquhskw{}</style><img lazyload src="/images/loading.svg" data-src="/2024/10/01/Undergraduate-Experience/WechatIMG1185.jpg" class="femmevquhskw" alt="图片4"></div>    <div><style>.fydkcaredtyg{}</style><img lazyload src="/images/loading.svg" data-src="/2024/10/01/Undergraduate-Experience/WechatIMG1184.jpg" class="fydkcaredtyg" alt="图片5"></div>    <div><style>.huzcsnqgocho{}</style><img lazyload src="/images/loading.svg" data-src="/2024/10/01/Undergraduate-Experience/WechatIMG1187.jpg" class="huzcsnqgocho" alt="图片6"></div>    <div><style>.detjmdhchpxl{}</style><img lazyload src="/images/loading.svg" data-src="/2024/10/01/Undergraduate-Experience/WechatIMG1188.jpg" class="detjmdhchpxl" alt="图片7"></div>    <div><style>.ealkymjkvbux{}</style><img lazyload src="/images/loading.svg" data-src="/2024/10/01/Undergraduate-Experience/WechatIMG1189.jpg" class="ealkymjkvbux" alt="图片8"></div>    <div><style>.pxtsmuflfslc{}</style><img lazyload src="/images/loading.svg" data-src="/2024/10/01/Undergraduate-Experience/WechatIMG1186.jpg" class="pxtsmuflfslc" alt="图片9"></div></div><p>好不容易来了趟海滨城市，怎么能不去游玩呢？第二天，<a href="https://gme-hong.github.io/masonry/">鼓浪屿</a>！</p><h5 id="北京"><a href="#北京" class="headerlink" title="北京"></a>北京</h5><p>北京之旅跟厦门之行很类似。中科院计算所的VIPL组也是发了预推免邀请，但是拿到了offer大概率也不会去的那种。选择前往有两方面原因：1、已经gap了近2个月之久，各种笔面机试的能力都要重新捡起来。2、去北师大看蒋xinxin。</p><p><strong>需要注意的是，如果你特别想去中科院计算所，那么你最好参加他们的夏令营而不是预推免</strong>。VIPL的考核还是比较全面的，包括笔试、机式和面试。其中笔试也可以说是面面俱到了。</p><div class="container">    <style>.lojqgceefspv{}</style><img lazyload src="/images/loading.svg" data-src="/2024/10/01/Undergraduate-Experience/image-20241012002926775.png" class="lojqgceefspv"></div><p>只有前一天的笔试和机式通过了，才能参加第二天的面试。</p><style>    .container {        display: flex; /* 使用 Flexbox */        justify-content: center; /* 水平居中 */        align-items: center; /* 垂直居中 */        max-width: 600px;        height: auto; /* 设置容器高度为视口高度 */        margin: 16px auto !important;    }    .container img {        max-width: 100%; /* 图片自适应容器宽度 */        height: auto; /* 保持比例 */        margin: 8px auto 0 !important;    }</style><div class="container">    <style>.ueqajpdcxupu{}</style><img lazyload src="/images/loading.svg" data-src="/2024/10/01/Undergraduate-Experience/image-20241012003415356.png" class="ueqajpdcxupu"></div><p>除了复试本身，最令人难忘的还是北京的物价！你敢相信这不到10平的房间一晚的价格是250<strong>人民币</strong>😂。</p><style>    .container2 {        display: flex; /* 使用 Flexbox */        justify-content: center; /* 水平居中 */        align-items: center; /* 垂直居中 */        max-width: 600px; /* 最大宽度为600px */        width: 100%; /* 容器宽度为100% */        margin: 16px auto; /* 上下外边距为16px，左右自动 */        box-sizing: border-box; /* 包含内边距和边框 */    }    .container2 img {        max-width: 50%; /* 每张图片占容器宽度的50% */        height: auto; /* 保持比例 */        margin: 8px; /* 添加外边距 */    }</style><div class="container2">    <style>.xxejkdtdxxqb{}</style><img lazyload src="/images/loading.svg" data-src="/2024/10/01/Undergraduate-Experience/IMG_20240912_193434.jpg" class="xxejkdtdxxqb" alt="WechatIMG575">    <style>.yyoktrkkwlod{}</style><img lazyload src="/images/loading.svg" data-src="/2024/10/01/Undergraduate-Experience/IMG_20240912_193439.jpg" class="yyoktrkkwlod" alt="WechatIMG575"></div><p>由于学校催交材料，本来计划打算在京多待一天的，结果改了下午的高铁，匆匆地走了。但是！走之前还是去找了趟蒋xinxin（左一），去她学校蹭了顿饭。临走的时候还碰到了另一个高中同学DR（右一）。</p><div class="container">    <style>.graaejifqpch{}</style><img lazyload src="/images/loading.svg" data-src="/2024/10/01/Undergraduate-Experience/IMG_20240914_152106.jpg" class="graaejifqpch"></div><p>此外，不得不说中关村的互联网氛围还是挺浓厚的。落地北京，前往酒店的路上就碰到几个人在聊技术问题，码农无疑了。</p><div class="container2">    <style>.mxnfdgdzxbcq{}</style><img lazyload src="/images/loading.svg" data-src="/2024/10/01/Undergraduate-Experience/IMG_20240913_212338.jpg" class="mxnfdgdzxbcq" alt="WechatIMG575">    <style>.lgjwipphvyfk{}</style><img lazyload src="/images/loading.svg" data-src="/2024/10/01/Undergraduate-Experience/IMG_20240913_213333.jpg" class="lgjwipphvyfk" alt="WechatIMG575"></div><p>最后还值得一提的是：计算所之旅还认识国际关系学院一个正能量满满的姑娘！相谈甚欢，挺欣赏她这种大大咧咧，个性独立的女生。可以跟大家分享一下她镜头下的贵州（人家保研成功后孤身前往~）。</p><div class="thumbnails">    <style>.iqrwriksuqoq{}</style><img lazyload src="/images/loading.svg" data-src="/2024/10/01/Undergraduate-Experience/5891727627972_.pic_hd.jpg" class="iqrwriksuqoq" alt="缩略图1">    <style>.hvrugfggdxqc{}</style><img lazyload src="/images/loading.svg" data-src="/2024/10/01/Undergraduate-Experience/5901727627973_.pic_hd.jpg" class="hvrugfggdxqc" alt="缩略图2">    <style>.lnzflmqsqwvr{}</style><img lazyload src="/images/loading.svg" data-src="/2024/10/01/Undergraduate-Experience/6021727628135_.pic_hd.jpg" class="lnzflmqsqwvr" alt="缩略图3">    <style>.imcstynfvuuo{}</style><img lazyload src="/images/loading.svg" data-src="/2024/10/01/Undergraduate-Experience/6031727628136_.pic_hd.jpg" class="imcstynfvuuo" alt="缩略图3">    <style>.kevywqqnpdjc{}</style><img lazyload src="/images/loading.svg" data-src="/2024/10/01/Undergraduate-Experience/6041727628137_.pic_hd.jpg" class="kevywqqnpdjc" alt="缩略图3">    <style>.gfsfhjdyawto{}</style><img lazyload src="/images/loading.svg" data-src="/2024/10/01/Undergraduate-Experience/6051727628138_.pic_hd.jpg" class="gfsfhjdyawto" alt="缩略图3">    <style>.nhpraqoompus{}</style><img lazyload src="/images/loading.svg" data-src="/2024/10/01/Undergraduate-Experience/6061727628140_.pic_hd.jpg" class="nhpraqoompus" alt="缩略图3"></div><h5 id="合肥"><a href="#合肥" class="headerlink" title="合肥"></a>合肥</h5><p>科大高新校区的环境一绝！特别是图书馆！以后机会很多，再更新！</p><style>    .box4 {        display: grid;        grid-template-columns: repeat(2, 1fr); /* 两列，形成4宫格 */        gap: 10px; /* 项目之间的间距 */        max-width: 600px; /* 容器宽度 */        margin: 16px auto;    }    .box4 div {        width: 100%; /* 每个项宽度100% */        aspect-ratio: 1; /* 使每个项保持正方形 */        overflow: hidden; /* 隐藏溢出部分 */        border-radius: 8px; /* 圆角效果 */    }    .box4 img {        width: 100%; /* 图片宽度100% */        height: 100%; /* 图片高度100% */        object-fit: cover; /* 保持比例并填充 */        margin: 0px !important;    }</style><div class="box4">    <div><style>.ffvutusyvpls{}</style><img lazyload src="/images/loading.svg" data-src="/2024/10/01/Undergraduate-Experience/IMG_20240922_121248.jpg" class="ffvutusyvpls" alt="图片1"></div>    <div><style>.igdimjfbgiyc{}</style><img lazyload src="/images/loading.svg" data-src="/2024/10/01/Undergraduate-Experience/IMG_20240922_153303.jpg" class="igdimjfbgiyc" alt="图片2"></div>    <div><style>.vltgmhwhdnux{}</style><img lazyload src="/images/loading.svg" data-src="/2024/10/01/Undergraduate-Experience/IMG_20240922_153630.jpg" class="vltgmhwhdnux" alt="图片3"></div>    <div><style>.tycsffgptsrj{}</style><img lazyload src="/images/loading.svg" data-src="/2024/10/01/Undergraduate-Experience/IMG_20240922_153804.jpg" class="tycsffgptsrj" alt="图片4"></div></div><h5 id="又见西安"><a href="#又见西安" class="headerlink" title="又见西安"></a>又见西安</h5><p>幸运的是，西交的兴庆校区不像西工大的长安校区一样偏！西交软院的复试就只有面试，而且是纯纯的BG面。10分钟，快节奏。<strong>但是录取并不是按分数择优录取，而是结合本科院校背景，分档择优录取，因此想要报考西交软院的需要特别注意！</strong></p><p><strong>之所以吐槽不了西交软院，是因为庞老师！庞老师和我院的梁老师是一个课题组的，两位老师都温文尔雅，都非常愿意提携后辈！从过初筛、正式复试到正式推免，庞老师帮我费尽心思，衷心感谢！同样也感谢梁老师的帮助！</strong></p><div class="container">    <style>.hggiwnfmovtg{}</style><img lazyload src="/images/loading.svg" data-src="/2024/10/01/Undergraduate-Experience/image-20241012015605260.png" class="hggiwnfmovtg"></div><p>最后，此趟西安行最印象深刻的是我住的酒店！<a class="link" href="http://dpurl.cn/BSrRXWAz">艺龙海雅酒店(西安大雁塔雁翔路北口地铁站店) <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a>  ，百元价位中，住过最满意的酒店，强烈推荐！</p><hr><p>真正去参加考核的院校就这些，但是表示愿意接收我的老师远远不至这些，很遗憾的是很多院校的初筛卡本科院校背景。没通过初筛意味着前期的努力全部变为一张废纸，但既然结局已定，也要输的体面！</p><div class="container">    <style>.njjjebntiooy{}</style><img lazyload src="/images/loading.svg" data-src="/2024/10/01/Undergraduate-Experience/image-20241012145414888.png" class="njjjebntiooy"></div><p>eg.武大遥感国重的绝大部分老师表示愿意接收，其中李老师甚至在收到邮件后连夜打电话给我，涂老师也帮助我修改简历，董老师携课题组的认可等等；华科CS的陈老师也给予我极大的信心；中南CS的梁老师、奎老师；南开CS；南大智科；浙大工程师等等。</p><div class="vertical">    <style>.nksoqsobuosu{}</style><img lazyload src="/images/loading.svg" data-src="/2024/10/01/Undergraduate-Experience/image-20241012143430918.png" class="nksoqsobuosu" alt="WechatIMG576">    <style>.qiboufaqcdkb{}</style><img lazyload src="/images/loading.svg" data-src="/2024/10/01/Undergraduate-Experience/image-20241012143504789.png" class="qiboufaqcdkb" alt="WechatIMG575"></div><p>最遗憾的是同济CS，时间和科大冲突了，无赖放了刘老师的🕊。</p><hr><p><strong>个体的力量永远是有限的，积极地、诚恳地向外寻求合作是终极秘诀！</strong><font color="orange"><strong>再次感谢这一路来本校老师的帮助、外校老师的认可！</strong></font></p><p>我的三年好像就这么告一段落了，但依旧还有很多值得复盘！我也在尝试将过去三年所经历的事一点点的总结，形成自己的方法论，与诸君共勉！</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h3 id=&quot;Preface&quot;&gt;&lt;a href=&quot;#Preface&quot; class=&quot;headerlink&quot; title=&quot;Preface&quot;&gt;&lt;/a&gt;Preface&lt;/h3&gt;&lt;p&gt;就像&lt;code&gt;Cover&lt;/code&gt;中所描述的那样，我的推免之路并不是一帆风顺，中间有太多插曲，</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>成功上岸 | 中科大 | 风雨同舟，我们阔步向前</title>
    <link href="https://gme-hong.github.io/2024/09/29/Stage-Victory/"/>
    <id>https://gme-hong.github.io/2024/09/29/Stage-Victory/</id>
    <published>2024-09-29T03:42:49.000Z</published>
    <updated>2025-02-28T14:24:00.849Z</updated>
    
    <content type="html"><![CDATA[<p>成功上岸，离不开各位家人、老师、朋友的帮助。这一路故事太多，后面我们茶话细谈。</p><p><img lazyload src="/images/loading.svg" data-src="https://ec28649.webp.li/20250228222357185.png" alt="WechatIMG1169"></p><p><img lazyload src="/images/loading.svg" data-src="https://ec28649.webp.li/20250228222357186.png" alt="image-20240929114716311"></p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;成功上岸，离不开各位家人、老师、朋友的帮助。这一路故事太多，后面我们茶话细谈。&lt;/p&gt;
&lt;p&gt;&lt;img lazyload src=&quot;/images/loading.svg&quot; data-src=&quot;https://ec28649.webp.li/2025022822235718</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>Accepted | RA-L | Maiden work</title>
    <link href="https://gme-hong.github.io/2024/06/13/Debut/"/>
    <id>https://gme-hong.github.io/2024/06/13/Debut/</id>
    <published>2024-06-13T11:00:24.000Z</published>
    <updated>2025-02-28T14:23:30.536Z</updated>
    
    <content type="html"><![CDATA[<p><strong>I’m so excited that my first paper has been accepted!</strong></p><p><img lazyload src="/images/loading.svg" data-src="https://ec28649.webp.li/20250228222326367.png"></p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;&lt;strong&gt;I’m so excited that my first paper has been accepted!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img lazyload src=&quot;/images/loading.svg&quot; data-src=&quot;https://e</summary>
      
    
    
    
    <category term="Papers" scheme="https://gme-hong.github.io/categories/Papers/"/>
    
    
    <category term="Grasp Detection" scheme="https://gme-hong.github.io/tags/Grasp-Detection/"/>
    
  </entry>
  
  <entry>
    <title>Film Record</title>
    <link href="https://gme-hong.github.io/2024/05/31/Movie/"/>
    <id>https://gme-hong.github.io/2024/05/31/Movie/</id>
    <published>2024-05-31T09:31:32.000Z</published>
    <updated>2025-09-21T13:24:23.347Z</updated>
    
    <content type="html"><![CDATA[  <div class="note p-4 mb-4 rounded-small red icon-padding">    <i class="note-icon fa-solid fa-bolt"></i><p>Valuable works are always waiting to be discovered.</p>  </div><ul><li><p><input checked disabled type="checkbox"> 《燃烧》：<a class="link" href="https://movie.douban.com/review/9430930/">最全解读，你真的看懂《燃烧》了吗？（燃烧）影评 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a></p></li><li><p><input checked disabled type="checkbox"> 《深海》：<a class="link" href="https://movie.douban.com/review/14934951/">读懂《深海》的故事与讲故事的方式（深海）影评 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a></p></li><li><p><input checked disabled type="checkbox"> 《默杀》：<a class="link" href="https://movie.douban.com/review/16040391/">《默杀》深度解析：没懂的来看这篇解读，真相都藏在细节和彩蛋里（默杀）影评 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a> <code>Note：优缺点都很明显，6.9分很公允。</code></p></li><li><p><input checked disabled type="checkbox"> 《涉过愤怒的海》：<a class="link" href="https://movie.douban.com/review/15622234/">《涉过愤怒的海》深度解析：没懂的来看这篇解读，开篇就隐藏伏笔（涉过愤怒的海）影评 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a>、<a class="link" href="https://www.bilibili.com/video/BV1Ku4y1c7B4/">深度解析《涉过愤怒的海》：癫狂之下全是痛【对话曹保平】 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a></p></li><li><p><input checked disabled type="checkbox"> 《阳光普照》：<a class="link" href="https://www.zhihu.com/question/367803235?sort=created">如何评价电影《阳光普照》？ <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a></p></li><li><p><input checked disabled type="checkbox"> 《狗十三》：<a class="link" href="https://movie.douban.com/review/9839596/">一条专访《狗十三》主创：我们终于活成了听话的狗（狗十三）影评 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a></p></li><li><p><input checked disabled type="checkbox"> 《走走停停》：<a class="link" href="https://movie.douban.com/review/16072744/">《走走停停》影评：也许，只有中年人才能看懂吧（走走停停）影评 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a></p></li><li><p><input checked disabled type="checkbox"> 《路边野餐》&amp;《地球最后的夜晚》：<a class="link" href="https://movie.douban.com/review/7991214/">《路边野餐》：无法把握的人生（路边野餐）影评 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a>、<a class="link" href="https://movie.douban.com/review/9871804/">《地球最后的夜晚》全解析，长长长评来了！（地球最后的夜晚）影评 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a></p><blockquote><p>很有意思的是，我是19年年初在影院看的《地球最后的夜晚》，然后于24年线上看的《路边野餐》。路野没看多久就意识到，平静却深刻的叙事方式、现实与梦幻的交织，跟记忆中的《地球最后的夜晚》风格很像，一查果然！原来都出自毕导之手。通过迭代的作品去观察一个人的阅历轨迹是一件很有意思的事！</p></blockquote></li><li><p><input checked disabled type="checkbox"> 《二次呼吸》：<a class="link" href="https://movie.douban.com/review/15822883/">关于虚无，自由意志和爱（二次呼吸）影评 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a></p></li><li><p><input checked disabled type="checkbox"> 《消失的爱人》、《搜索》（2012，陈凯歌）、<a class="link" href="https://chinadigitaltimes.net/chinese/712836.html">旧闻评论｜事实核查在姜萍事件中如何遇阻？ <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a>、<a class="link" href="https://www.bilibili.com/video/BV1cED4YVEyt/">“努力想要活下去”的她，最终留下一封遗书走了 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a></p></li><li><p><input checked disabled type="checkbox"> 《婚姻故事》：<a class="link" href="https://movie.douban.com/review/13062434/">“当我们谈论婚姻时，我们在谈论什么？”（婚姻故事）影评 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a></p></li><li><p><input checked disabled type="checkbox"> 《魅杀》&amp;《魅杀2》</p></li><li><p><input checked disabled type="checkbox"> 《瞬息全宇宙》</p></li><li><p><input checked disabled type="checkbox"> 《Duna》&amp;《Duna 2》</p></li><li><p><input checked disabled type="checkbox"> 《某种物质》：<a class="link" href="https://movie.douban.com/review/16276564/">《某种物质》深度解析：这部电影，是我今年看过尺度最大的片子（某种物质）影评 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a></p></li><li><p><input checked disabled type="checkbox"> 《姥姥的外孙》：<a class="link" href="https://movie.douban.com/review/16200057/">《姥姥的外孙》深度解析：没懂的来看这篇解读，真相都藏在细节里（姥姥的外孙）影评 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a></p></li><li><p><input checked disabled type="checkbox"> 《因果报应》：<a class="link" href="https://www.bilibili.com/video/BV1Cy411v7Kh/">评分飙升，口碑爆棚，今年最好的悬疑片！印度悬疑片《因果报应》 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a></p></li><li><p><input checked disabled type="checkbox"> 《浪浪山小妖怪》</p><blockquote><p>在院线看的，一般，顶多7分吧。教训：<strong>甄别影片在院线上映期间的流媒体宣传</strong>。</p></blockquote></li><li><p><input checked disabled type="checkbox"> 《银翼杀手2049》：<a class="link" href="https://www.bilibili.com/video/BV1SP411N7Ea/?spm_id_from=333.337.search-card.all.click&vd_source=87f8440a6f50ec79e285ae9b481f34e5">反叛复制人取代人类？赛博朋克最强续作！万字拆解《银翼杀手2049》 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a></p></li><li><p><input checked disabled type="checkbox"> 《爱乐之城》：<a class="link" href="https://www.bilibili.com/video/BV1G741137jn/?spm_id_from=333.337.search-card.all.click&vd_source=87f8440a6f50ec79e285ae9b481f34e5">《爱乐之城》这个情人节，你是怎么过的呢？看看这部近年来最好的爱情歌舞片吧 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a></p></li><li><p><input checked disabled type="checkbox"> 《超体》：<a class="link" href="https://www.bilibili.com/video/BV1gF3yzHE2z/?spm_id_from=333.337.search-card.all.click&vd_source=87f8440a6f50ec79e285ae9b481f34e5">【万字细读】《超体》版“修仙”指南：吸毒成神，科学还是玄学？黑寡妇的科幻巅峰 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a></p></li></ul>]]></content>
    
    
      
      
    <summary type="html">
  &lt;div class=&quot;note p-4 mb-4 rounded-small red icon-padding&quot;&gt;
    &lt;i class=&quot;note-icon fa-solid fa-bolt&quot;&gt;&lt;/i&gt;&lt;p&gt;Valuable works are always wai</summary>
      
    
    
    
    
    <category term="Film" scheme="https://gme-hong.github.io/tags/Film/"/>
    
  </entry>
  
  <entry>
    <title>组内团建</title>
    <link href="https://gme-hong.github.io/2024/04/23/%E5%9B%A2%E5%BB%BA/"/>
    <id>https://gme-hong.github.io/2024/04/23/%E5%9B%A2%E5%BB%BA/</id>
    <published>2024-04-23T11:57:53.000Z</published>
    <updated>2025-02-28T14:29:34.994Z</updated>
    
    <content type="html"><![CDATA[<p>很幸运遇到这样一群有意思的人！</p><p><img lazyload src="/images/loading.svg" data-src="https://ec28649.webp.li/20250228222910657.jpg" alt="fig1"></p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;很幸运遇到这样一群有意思的人！&lt;/p&gt;
&lt;p&gt;&lt;img lazyload src=&quot;/images/loading.svg&quot; data-src=&quot;https://ec28649.webp.li/20250228222910657.jpg&quot; alt=&quot;fig1&quot;&gt;&lt;/p&gt;
</summary>
      
    
    
    
    
    <category term="Team Building" scheme="https://gme-hong.github.io/tags/Team-Building/"/>
    
  </entry>
  
  <entry>
    <title>A thought-provoking sentence</title>
    <link href="https://gme-hong.github.io/2024/04/07/Thinking-Sentence/"/>
    <id>https://gme-hong.github.io/2024/04/07/Thinking-Sentence/</id>
    <published>2024-04-07T10:52:01.000Z</published>
    <updated>2025-02-28T14:24:13.378Z</updated>
    
    <content type="html"><![CDATA[<p>I was elated today because my instructor praised my dissertation proposal at the group meeting. Of course, I was not present because of the class. I learnt about it from senior sister apprentice Huai Yao. I instinctively told her, “Don’t make a fuss, you’ll make me shy”. Then Huai Yao said something very profound to me. </p><p><img lazyload src="/images/loading.svg" data-src="https://ec28649.webp.li/20250228222411205.png" alt="image-20240407185256571"></p><p>I was thinking, it seems like we’ve always been taught to learn to be humble in our dealings with others. But this mellow wisdom in dealing with people is supposed to stay only in the world outside of ourselves. If our hearts are constantly infested with this kind of thinking, it’s easy to fail to recognise where we are.</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;I was elated today because my instructor praised my dissertation proposal at the group meeting. Of course, I was not present because of t</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>XV6 File System</title>
    <link href="https://gme-hong.github.io/2024/01/13/XV6-FS/"/>
    <id>https://gme-hong.github.io/2024/01/13/XV6-FS/</id>
    <published>2024-01-13T14:00:51.000Z</published>
    <updated>2025-02-28T14:25:18.794Z</updated>
    
    <content type="html"><![CDATA[<h2 id="Lab5-文件系统"><a href="#Lab5-文件系统" class="headerlink" title="Lab5 文件系统"></a>Lab5 文件系统</h2><h3 id="1-文件系统"><a href="#1-文件系统" class="headerlink" title="1.文件系统"></a>1.文件系统</h3><h4 id="1-1-一些经典的文件系统"><a href="#1-1-一些经典的文件系统" class="headerlink" title="1.1.一些经典的文件系统"></a>1.1.一些经典的文件系统</h4><ol><li><strong>FAT文件系统（File Allocation Table）：</strong> FAT是一种简单而广泛使用的文件系统，最初用于MS-DOS和Windows操作系统。它具有相对简单的结构，容易实现和维护，但在处理大容量存储和提供高级功能方面存在一些限制。</li><li>NTFS（New Technology File System）： NTFS是由Microsoft开发的高性能文件系统，用于Windows NT及其后续版本。它支持更大的文件和分区大小，具有更先进的权限管理、日志记录和元数据特性。</li><li><strong>ext文件系统：</strong><ul><li><strong>ext2：</strong> 是Linux中早期版本使用的文件系统，具有相对简单的结构，不支持日志。</li><li><strong>ext3：</strong> 在ext2的基础上添加了日志功能，提供了更好的稳定性和可靠性。</li><li>ext4：是ext3的后继者，引入了一些性能改进和新特性，支持更大的文件和分区。</li></ul></li><li><strong>HFS和HFS+（Hierarchical File System）：</strong> HFS是由苹果公司用于Macintosh计算机的文件系统。HFS+是其后续版本，引入了更大的文件和卷支持，以及更先进的特性。</li><li><strong>APFS（Apple File System）：</strong>是由苹果公司设计和推出的现代文件系统，用于替代HFS+（Hierarchical File System Plus），并首次引入于macOS High Sierra（10.13）操作系统。</li></ol><h4 id="1-2-xv6fs-文件系统"><a href="#1-2-xv6fs-文件系统" class="headerlink" title="1.2.xv6fs 文件系统"></a>1.2.xv6fs 文件系统</h4><p>今天我们的主角是xv6fs，是一个教学用途的<strong>类 Unix 操作系统</strong>，设计简单，方便学生学习和理解操作系统的基本原理。</p><p>在 xv6 中，文件系统负责管理文件和存储设备上的数据。xv6 使用的文件系统是基于简化的 Unix 文件系统的，包括基本的文件和目录操作、inode 结构等。</p><p>xv6磁盘文件系统的分区图如下：</p><p><img lazyload src="/images/loading.svg" data-src="https://ec28649.webp.li/20250228222442070.png" alt="image-20240113154758144"></p><h5 id="1-2-1-boot（引导块）"><a href="#1-2-1-boot（引导块）" class="headerlink" title="1.2.1.boot（引导块）"></a>1.2.1.boot（引导块）</h5><p>Lab2 中学过，这里就不再论述了。</p><h5 id="1-2-2-superblock（超级块）"><a href="#1-2-2-superblock（超级块）" class="headerlink" title="1.2.2.superblock（超级块）"></a>1.2.2.superblock（超级块）</h5><p>存有文件系统的<strong>元信息</strong></p><div class="highlight-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//fs.h</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">superblock</span> &#123;</span></span><br><span class="line">  uint size;         <span class="comment">// 文件系统大小，也就是一共多少块</span></span><br><span class="line">  uint nblocks;      <span class="comment">// 数据块数量</span></span><br><span class="line">  uint ninodes;      <span class="comment">// i结点数量</span></span><br><span class="line">  uint nlog;         <span class="comment">// 日志块数量  </span></span><br><span class="line">  uint logstart;     <span class="comment">// 第一个日志块块号 </span></span><br><span class="line">  uint inodestart;   <span class="comment">// 第一个i结点所在块号</span></span><br><span class="line">  uint bmapstart;    <span class="comment">// 第一个位图块块号</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure></div><div class="highlight-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//mkfs.c</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> NINODES 200</span></span><br><span class="line"><span class="comment">// Disk layout:</span></span><br><span class="line"><span class="comment">// [ boot block | sb block | log | inode blocks | free bit map | data blocks ]</span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> nbitmap = FSSIZE/(BSIZE*<span class="number">8</span>) + <span class="number">1</span>;</span><br><span class="line"><span class="type">int</span> ninodeblocks = NINODES / IPB + <span class="number">1</span>;</span><br><span class="line"><span class="type">int</span> nlog = LOGSIZE;</span><br><span class="line"><span class="type">int</span> nmeta;    <span class="comment">// Number of meta blocks (boot, sb, nlog, inode, bitmap)</span></span><br><span class="line"><span class="type">int</span> nblocks;  <span class="comment">// Number of data blocks</span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> fsfd;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">superblock</span> <span class="title">sb</span>;</span></span><br><span class="line"><span class="type">char</span> zeroes[BSIZE];</span><br><span class="line">uint freeinode = <span class="number">1</span>;</span><br><span class="line">uint freeblock;</span><br></pre></td></tr></table></figure></div><p>在我们进入xv6系统之后，会输出一段关于超级块<code>sb</code>中存储的信息：</p><p><img lazyload src="/images/loading.svg" data-src="https://ec28649.webp.li/20250228222442071.png" alt="image-20240113165900467"></p><p>我们可以看到，<u>整个磁盘一共有1000块</u>，<u>其中有941块是用来存储数据的</u>，<u>有200个索引节点，索引项从第32个块磁盘块开始存储</u>，<u>30条日志记录，日志记录从第2个磁盘块开始存储</u>，而<u>位图区从第58个磁盘块开始存储</u>。</p><h5 id="1-2-3-logblock（日志区）"><a href="#1-2-3-logblock（日志区）" class="headerlink" title="1.2.3.logblock（日志区）"></a>1.2.3.logblock（日志区）</h5><p>在 xv6 文件系统中，日志区是指用于事务日志（transaction log）的一部分存储区域。xv6 使用日志来确保文件系统的一致性，尤其是在面临系统崩溃或中断的情况下。<strong>日志区的主要目的是在进行文件系统更新时，首先记录要执行的所有操作，然后将这些操作一次性写入磁盘</strong>。这样，即使在执行过程中系统崩溃，可以通过日志来恢复到一致的状态。</p><h5 id="1-2-4-inode（索引区）"><a href="#1-2-4-inode（索引区）" class="headerlink" title="1.2.4.inode（索引区）"></a>1.2.4.inode（索引区）</h5><p><img lazyload src="/images/loading.svg" data-src="https://ec28649.webp.li/20250228222442072.png" alt="image-20240113161746995"></p><p><img lazyload src="/images/loading.svg" data-src="https://ec28649.webp.li/20250228222442073.png" alt="image-20240113161804816"></p><blockquote><p>我们现在想想，这个索引可能会存在的位置？</p></blockquote><ul><li>没错，肯定会在磁盘中出现，因为要在磁盘中组织这些文件，因此磁盘中一定会包含文件索引信息</li><li>那么还有可能就是在内存中了，因为进程对文件进行操作都是在内存中进行的，因此内存中必然包含文件的索引信息。</li></ul><p>那么，我们现在讨论的索引区，准确来说是在磁盘中的索引。为了和内存中的索引相区分，我们将磁盘中的索引称为dinode（disk inode）。</p><p>在<code>fs.h</code>中包含对dinode的Definition：</p><div class="highlight-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//fs.h</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> NDIRECT 12</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> NINDIRECT (BSIZE / sizeof(uint))</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> MAXFILE (NDIRECT + NINDIRECT)</span></span><br><span class="line"><span class="comment">// On-disk inode structure</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">dinode</span> &#123;</span></span><br><span class="line">  <span class="type">short</span> type;           <span class="comment">// File type</span></span><br><span class="line">  <span class="type">short</span> major;          <span class="comment">// Major device number (T_DEV only)</span></span><br><span class="line">  <span class="type">short</span> minor;          <span class="comment">// Minor device number (T_DEV only)</span></span><br><span class="line">  <span class="type">short</span> nlink;          <span class="comment">// Number of links to inode in file system</span></span><br><span class="line">  uint size;            <span class="comment">// Size of file (bytes)</span></span><br><span class="line">  uint addrs[NDIRECT+<span class="number">1</span>];   <span class="comment">// Data block addresses</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure></div><ol><li><strong><code>type</code>：</strong> 短整型（<code>short</code>），表示文件的类型。可能的取值包括：<ul><li><code>T_DIR</code>：目录</li><li><code>T_FILE</code>：普通文件</li><li><code>T_DEV</code>：设备文件</li></ul></li><li><strong><code>major</code>：</strong> 短整型（<code>short</code>），仅在文件类型为设备文件（<code>T_DEV</code>）时有意义，表示主设备号。</li><li><strong><code>minor</code>：</strong> 短整型（<code>short</code>），仅在文件类型为设备文件（<code>T_DEV</code>）时有意义，表示次设备号。</li><li><strong><code>nlink</code>：</strong> 短整型（<code>short</code>），表示指向该 inode 的<strong>硬链接数</strong>。</li><li><strong><code>size</code>：</strong> 无符号整型（<code>uint</code>），表示<u>文件的大小</u>（以字节为单位）。</li><li><strong><code>addrs[NDIRECT+1]</code>：</strong> 无符号整型数组，用于存储<strong>文件数据块的地址</strong>。<code>NDIRECT</code> 是一个常量，表示<strong>直接数据块的个数</strong>。这个数组包含了<strong>直接数据块和一级间接数据块的地址</strong>。如果文件很小，数据块地址可以直接存储在 <code>addrs</code> 数组中；<strong>如果文件较大，会使用一级间接块来存储更多的数据块地址</strong>。</li></ol><p><img lazyload src="/images/loading.svg" data-src="https://ec28649.webp.li/20250228222442074.png" alt="image-20240113162842066"></p><p>了解了dinode，我们趁热打铁，继续了解内存中的inode。</p><p>内存中的inode被一个叫<code>icache</code>的结构所组织，在<code>fs.c</code>中进行定义：</p><div class="highlight-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//fs.c</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> &#123;</span></span><br><span class="line">  <span class="class"><span class="keyword">struct</span> <span class="title">spinlock</span> <span class="title">lock</span>;</span></span><br><span class="line">  <span class="class"><span class="keyword">struct</span> <span class="title">inode</span> <span class="title">inode</span>[<span class="title">NINODE</span>];</span></span><br><span class="line">&#125; icache;</span><br></pre></td></tr></table></figure></div><ol><li><strong><code>struct spinlock lock</code>：</strong> 自旋锁，用于对整个 <code>icache</code> 结构进行加锁。由于 inode 缓存是一个共享的数据结构，多个线程同时访问时需要使用锁来保护共享资源的一致性。</li><li><strong><code>struct inode inode[NINODE]</code>：</strong><font color="orange"><font color="orange">inode数组</font></font> ，包含了 NINODE 个 <code>struct inode</code> 结构体。<code>NINODE</code> 是一个常量，表示 inode 缓存中可以缓存的 inode 的数量。每个 <code>struct inode</code> 表示一个文件或目录的元数据信息，包括文件类型、大小、指向数据块的地址等。</li></ol><p>在<code>file.h</code>中对inode进行定义：</p><div class="highlight-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//file.h</span></span><br><span class="line"><span class="comment">// in-memory copy of an inode</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">inode</span> &#123;</span></span><br><span class="line">  uint dev;           <span class="comment">// Device number</span></span><br><span class="line">  uint inum;          <span class="comment">// Inode number</span></span><br><span class="line">  <span class="type">int</span> ref;            <span class="comment">// Reference count</span></span><br><span class="line">  <span class="class"><span class="keyword">struct</span> <span class="title">sleeplock</span> <span class="title">lock</span>;</span> <span class="comment">// protects everything below here</span></span><br><span class="line">  <span class="type">int</span> valid;          <span class="comment">// inode has been read from disk?</span></span><br><span class="line"></span><br><span class="line">  <span class="type">short</span> type;         <span class="comment">// copy of disk inode</span></span><br><span class="line">  <span class="type">short</span> major;</span><br><span class="line">  <span class="type">short</span> minor;</span><br><span class="line">  <span class="type">short</span> nlink;</span><br><span class="line">  uint size;</span><br><span class="line">  uint addrs[NDIRECT+<span class="number">1</span>];</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure></div><p>我们可以看到，除了有跟dinode一致的数据项之外，还有一些额外数据项：</p><ol><li><strong><code>uint dev</code>：</strong> 无符号整型（<code>uint</code>），表示该 inode 所在的设备的设备号。</li><li><strong><code>uint inum</code>：</strong> 无符号整型（<code>uint</code>），<strong>表示该 inode 的编号，即该 inode 在设备上的唯一标识符</strong>。</li><li><strong><code>int ref</code>：</strong> 整型（<code>int</code>），表示<strong>对该 inode 的引用计数</strong>。引用计数用于跟踪有多少个指针（例如，打开文件描述符）引用了这个 inode。<font color="cornflowerblue">当引用计数为零时，inode 可以被释放</font>。</li><li><strong><code>struct sleeplock lock</code>：</strong> 一个睡眠锁（sleeping lock），用于保护 <code>struct inode</code> 中除了 <code>lock</code> 自身之外的其他字段。睡眠锁是一种同步机制，它允许线程在访问被锁定资源时进入睡眠状态。</li><li><strong><code>int valid</code>：</strong> 整型（<code>int</code>），表示该 inode 是否已经从磁盘读取并被标记为有效。当 <code>valid</code> 为 1 时，表示该 inode 包含的信息已经被读取到内存中。</li></ol><hr><h6 id="Ex5-1-请解释为-icache-添加的锁-与-为-inode-添加的锁不同的原因？"><a href="#Ex5-1-请解释为-icache-添加的锁-与-为-inode-添加的锁不同的原因？" class="headerlink" title="Ex5-1 请解释为 icache 添加的锁 与 为 inode 添加的锁不同的原因？"></a>Ex5-1 请解释为 icache 添加的锁 与 为 inode 添加的锁不同的原因？</h6><hr><h5 id="1-2-5-bitmap（位图区）"><a href="#1-2-5-bitmap（位图区）" class="headerlink" title="1.2.5.bitmap（位图区）"></a>1.2.5.bitmap（位图区）</h5><p>在理论课中，我们学过，位图是一种磁盘空间管理的方案。</p><p><img lazyload src="/images/loading.svg" data-src="https://ec28649.webp.li/20250228222442075.png" alt="image-20240113165302182"></p><p>这里补充一个大家容易误解的知识点：数据块的分配和释放由位图来管理，但位图管理的区域不止数据区，而是<strong>整个文件系统</strong>。</p><p>位图块中每一位都代表着<strong>一块</strong>，该位置 <strong>1 表示相应的块正在使用</strong>，该位置 <strong>0 表示相应的块空闲</strong>。</p><h5 id="1-2-6-data（数据区）"><a href="#1-2-6-data（数据区）" class="headerlink" title="1.2.6.data（数据区）"></a>1.2.6.data（数据区）</h5><p>数据区没啥好说的，只要记住一个点：以块为单位进行存储，可能会产生块内碎片；数据区的存储由位图区进行管理。</p><p>到这里，我们就能对单个文件的检索过程有一个清晰的认知：</p><p><img lazyload src="/images/loading.svg" data-src="https://ec28649.webp.li/20250228222442076.png" alt="image-20240113175030759"></p><blockquote><p>除了上述一些在磁盘分区中直接体现的结构之外，文件系统还有一些重要的结构需要我们去了解。</p></blockquote><hr><blockquote><p>我们先试想一下，索引极大的方便了数据块的查找，但其依然是对于文件系统内部而言的。我们平常去检索一个文件从来没有说，通过索引项去找到该文件的吧。相反，我们总是通过文件的文件名去检索文件。因此，还必须有一个结构去实现这种 ”按名存取“ 的功能。这就是目录！</p></blockquote><h5 id="1-2-7-directory（目录）"><a href="#1-2-7-directory（目录）" class="headerlink" title="1.2.7.directory（目录）"></a>1.2.7.directory（目录）</h5><p>在xv6中，跟目录有关的结构体被定义在<code>fs.h</code>中：</p><div class="highlight-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//fs.h</span></span><br><span class="line"><span class="comment">// Directory is a file containing a sequence of dirent structures.</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> DIRSIZ 14</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">dirent</span> &#123;</span></span><br><span class="line">  ushort inum;</span><br><span class="line">  <span class="type">char</span> name[DIRSIZ];</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure></div><ol><li><code>ushort inum</code>：无符号短整型，<strong>表示该目录项对应的 inode 号</strong>。</li><li><code>char name[DIRSIZ]</code>：字符数组，表示目录项的名字。<code>DIRSIZ</code> 是一个常量，表示目录项名字的最大长度。</li></ol><p>其中，每一个dirent结构体被称为目录项。在 xv6 操作系统中，目录项存储在磁盘的<strong>数据块</strong>中。每个目录都有一个对应的 inode，而目录项信息实际上被存储在该目录的数据块中。这个数据块包含了一系列的目录项，每个目录项都表示一个文件或子目录。</p><p>我们在xv6的终端中输入<code>ls</code>命令，既可以看到根目录的目录结构：</p><p><img lazyload src="/images/loading.svg" data-src="https://ec28649.webp.li/20250228222442077.png" alt="image-20240113172648618"></p><p>我们使用xv6提供的终端命令<code>mkdir</code>自己创建一个目录<code>mydir</code>，再在改目录下通过<code>echo</code>命令创建两个文件</p><p><img lazyload src="/images/loading.svg" data-src="https://ec28649.webp.li/20250228222442078.png" alt="image-20240113173057913"></p><p>之后我们在使用<code>ls</code>命令查看根目录的结构</p><p><img lazyload src="/images/loading.svg" data-src="https://ec28649.webp.li/20250228222442079.png" alt="image-20240113173116276"></p><p>我们来解释一下<code>ls</code>输出的内容：</p><ul><li>第一列表示文件名，其中“.”表示本目录，”..“表示父目录（根目录的父目录就是其自身）</li></ul><img lazyload src="/images/loading.svg" data-src="https://ec28649.webp.li/20250228222442080.png" alt="image-20240113173229906" style="zoom:50%;"><ul><li>第二列表示文件类别，我们知道，在Unix操作系统中，”一切皆文件“的思想，在<code>stat.h</code>中，给出了三种文件类型：</li></ul><div class="highlight-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">define</span> T_DIR  1   <span class="comment">// Directory</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> T_FILE 2   <span class="comment">// File</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> T_DEV  3   <span class="comment">// Device</span></span></span><br></pre></td></tr></table></figure></div><p>其中1表示目录文件，2表示数据文件，3表示设备文件。</p><p>我们可以看到“.”和”..“以及我们创建的“mydir”都是目录文件，而console是设备文件</p><ul><li>第三列表示inode编号，这是直接在目录项中存在的，其数值必然是唯一的。</li><li>第四列表示文件的大小，这是在目录项中没有的，因此我们可以推断，<code>ls</code>命令应该既访问了目录项，又访问了inode结点。</li></ul><p>很显然，这种目录的组织结构就是我们熟知的树型结构</p><p><img lazyload src="/images/loading.svg" data-src="https://ec28649.webp.li/20250228222442081.png" alt="image-20240113172607369"></p><p>那么，假如我们要检索创建的“myfile1”文件，就需要按照树型结构的路径一层层往下找。当然，不同的起始出发点就会出现两种寻找策略：直接从根节点出发寻找和从当前目录项出发寻找。但是不管是哪一种策略，其都是一个递归的过程。</p><hr><blockquote><p>到这里，我们都还是介绍一些”共性“的结构，或者说概念。但是每一个文件肯定是不同，我们还需要一种能体现”特性“的结构体来表示每一个文件。</p></blockquote><h5 id="1-2-8-file（文件结构体）"><a href="#1-2-8-file（文件结构体）" class="headerlink" title="1.2.8.file（文件结构体）"></a>1.2.8.file（文件结构体）</h5><div class="highlight-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//file.h</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">file</span> &#123;</span></span><br><span class="line">  <span class="class"><span class="keyword">enum</span> &#123;</span> FD_NONE, FD_PIPE, FD_INODE &#125; type;</span><br><span class="line">  <span class="type">int</span> ref; <span class="comment">// 引用计数</span></span><br><span class="line">  <span class="type">char</span> readable; <span class="comment">// 文件是否可读</span></span><br><span class="line">  <span class="type">char</span> writable; <span class="comment">// 文件是否可写</span></span><br><span class="line">  <span class="class"><span class="keyword">struct</span> <span class="title">pipe</span> *<span class="title">pipe</span>;</span> <span class="comment">// 管道文件</span></span><br><span class="line">  <span class="class"><span class="keyword">struct</span> <span class="title">inode</span> *<span class="title">ip</span>;</span> <span class="comment">// 关联的 inode</span></span><br><span class="line">  uint off; <span class="comment">// 读写位置</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure></div><ol><li><strong><code>num &#123; FD_NONE, FD_PIPE, FD_INODE &#125; type</code>：</strong> 表示文件类型的枚举。文件可以是普通文件（<code>FD_INODE</code>）、管道文件（<code>FD_PIPE</code>）或者无效的文件（<code>FD_NONE</code>）。</li><li><strong><code>int ref</code>：</strong> 引用计数，用于追踪有多少个文件描述符引用了这个文件。引用计数用于文件的释放，当引用计数为零时，文件可以被释放。</li><li><strong><code>char readable</code> 和 <code>char writable</code>：</strong> 标志文件是否可读和可写。这两个字段表示了文件的访问权限。</li><li><strong><code>struct pipe \*pipe</code>：</strong> 如果文件是管道文件，这个字段指向管道结构体。</li><li><strong><code>struct inode \*ip</code>：</strong> 指向文件关联的 inode 结构体，用于获取文件的元数据信息。</li><li><strong><code>uint off</code>：</strong> 当前文件的读写位置，表示下一次读写操作将在文件中的哪个位置发生。</li></ol><hr><blockquote><p>接下来我们还得再了解一个概念：文件描述符，这是在<strong>进程</strong>中直接使用的一个结构。</p></blockquote><h5 id="1-2-9-文件描述符"><a href="#1-2-9-文件描述符" class="headerlink" title="1.2.9.文件描述符"></a>1.2.9.文件描述符</h5><p>进程使用文件并不使用file结构体或inode结构体，而是提供文件的路径名来打开文件并获得文件描述符，后续将使用文件描述符来指代这个打开的文件。xv6中，每个进程都有一个文件描述符表<code>proc-&gt;ofile[]</code>，最多可以使用NOFILE&#x3D;16个文件描述符（也就是说一个进程最多同时打开16个文件），每个文件描述符都直接指向一个file结构体（系统管理的已打开的文件）。</p><div class="highlight-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//proc.c</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Per-process state</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">proc</span> &#123;</span></span><br><span class="line">...</span><br><span class="line">  <span class="class"><span class="keyword">struct</span> <span class="title">file</span> *<span class="title">ofile</span>[<span class="title">NOFILE</span>];</span>  <span class="comment">// Open files</span></span><br><span class="line">  <span class="class"><span class="keyword">struct</span> <span class="title">inode</span> *<span class="title">cwd</span>;</span>           <span class="comment">// Current directory</span></span><br><span class="line">  <span class="type">char</span> name[<span class="number">16</span>];               <span class="comment">// Process name (debugging)</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure></div><p>现在，我们可以站在整个文件系统的层次对一个进程访问文件的整个过程有一个宏观的认知：</p><p><img lazyload src="/images/loading.svg" data-src="https://ec28649.webp.li/20250228222442083.png" alt="image-20240113181318963"></p><h3 id="2-文件系统操作"><a href="#2-文件系统操作" class="headerlink" title="2.文件系统操作"></a>2.文件系统操作</h3><h4 id="2-1-盘块操作"><a href="#2-1-盘块操作" class="headerlink" title="2.1.盘块操作"></a>2.1.盘块操作</h4><p><strong>文件系统的所有操作中，对盘块的操作是最底层的，是直接和硬件（设备驱动程序）打交道的。</strong></p><p>在介绍盘块操作之前，我必须先指出大家可能模棱两可的问题：</p><p><font color="red">对盘块的操作并不是直接在磁盘中对盘块进行操作，而是对映射到内存中的块缓存进行操作</font>，至于原因，我觉得大家都明白。<font color="orange">对一个盘块的多次读写操作都是在内存中完成的，直到换出到磁盘上才真正地执行写盘操作</font>。</p><p>xv6fs只能通过块缓存来访问磁盘，而不允许直接访问。</p><h5 id="2-1-1-盘块缓冲区"><a href="#2-1-1-盘块缓冲区" class="headerlink" title="2.1.1.盘块缓冲区"></a>2.1.1.盘块缓冲区</h5><p>在xv6的<code>buf.c</code>中有对缓冲区的定义：</p><div class="highlight-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//buf.c</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> &#123;</span></span><br><span class="line">  <span class="class"><span class="keyword">struct</span> <span class="title">spinlock</span> <span class="title">lock</span>;</span></span><br><span class="line">  <span class="class"><span class="keyword">struct</span> <span class="title">buf</span> <span class="title">buf</span>[<span class="title">NBUF</span>];</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// Linked list of all buffers, through prev/next.</span></span><br><span class="line">  <span class="comment">// head.next is most recently used.</span></span><br><span class="line">  <span class="class"><span class="keyword">struct</span> <span class="title">buf</span> <span class="title">head</span>;</span></span><br><span class="line">&#125; bcache;</span><br></pre></td></tr></table></figure></div><ol><li><strong><code>struct spinlock lock</code>：</strong> 互斥锁，用于对整个缓冲区缓存进行加锁。由于缓冲区缓存是一个共享的数据结构，多个线程同时访问时需要使用锁来保护共享资源的一致性。</li><li><strong><code>struct buf buf[NBUF]</code>：</strong> 缓冲区数组，包含 NBUF 个 <code>struct buf</code> 结构体。每个元素表示一个缓冲区，用于缓存磁盘上的数据块。</li><li><strong><code>struct buf head</code>：</strong> <code>struct buf</code> 结构体，用作链表的头部。通过 <code>head.next</code> 和 <code>head.prev</code> 可以构建一个双向链表，用于管理所有缓冲区。<code>head.next</code> 指向最近使用的缓冲区，<code>head.prev</code> 指向最久未使用的缓冲区。</li></ol><p>对每一个缓冲单元在<code>buf.h</code>中进行定义：</p><div class="highlight-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//buf.h</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">buf</span> &#123;</span></span><br><span class="line">  <span class="type">int</span> flags;</span><br><span class="line">  uint dev;</span><br><span class="line">  uint blockno;</span><br><span class="line">  <span class="class"><span class="keyword">struct</span> <span class="title">sleeplock</span> <span class="title">lock</span>;</span></span><br><span class="line">  uint refcnt;</span><br><span class="line">  <span class="class"><span class="keyword">struct</span> <span class="title">buf</span> *<span class="title">prev</span>;</span> <span class="comment">// LRU cache list</span></span><br><span class="line">  <span class="class"><span class="keyword">struct</span> <span class="title">buf</span> *<span class="title">next</span>;</span></span><br><span class="line">  <span class="class"><span class="keyword">struct</span> <span class="title">buf</span> *<span class="title">qnext</span>;</span> <span class="comment">// disk queue</span></span><br><span class="line">  uchar data[BSIZE];</span><br><span class="line">&#125;;</span><br><span class="line"><span class="meta">#<span class="keyword">define</span> B_VALID 0x2  <span class="comment">// buffer has been read from disk</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> B_DIRTY 0x4  <span class="comment">// buffer needs to be written to disk</span></span></span><br></pre></td></tr></table></figure></div><ol><li><strong><code>int flags</code>：</strong> 整型，用于表示缓冲区的状态标志。这些标志可能包括：<ul><li><code>B_VALID</code>：缓冲区包含有效的数据。</li><li><code>B_DIRTY</code>：缓冲区中的数据已经被修改，需要写回磁盘。</li><li>其他标志用于表示缓冲区的状态。</li></ul></li><li><strong><code>uint dev</code>：</strong> 无符号整型，表示数据块所在的设备的设备号。</li><li><strong><code>uint blockno</code>：</strong> 无符号整型，表示数据块的块号。这是指在设备上的位置。</li><li><strong><code>struct sleeplock lock</code>：</strong> 一个睡眠锁（sleeping lock），用于对缓冲区进行加锁。睡眠锁是一种同步机制，它允许线程在访问被锁定资源时进入睡眠状态。</li><li><strong><code>uint refcnt</code>：</strong> 无符号整型，表示缓冲区的引用计数。<strong>引用计数用于跟踪有多少个指针引用了这个缓冲区。当引用计数为零时，缓冲区可以被释放。</strong></li><li><strong><code>struct buf *prev</code> 和 <code>struct buf *next</code>：</strong> 指向双向链表中前一个和后一个缓冲区的指针。这些指针用于在缓冲区之间构建 <strong>LRU（Least Recently Used）缓存列表</strong>，<strong>以实现缓冲区的管理和替换</strong>。</li><li><strong><code>struct buf *qnext</code>：</strong> 指向缓冲区的下一个缓冲区，用于构建磁盘 I&#x2F;O 队列。这个指针用于将缓冲区连接到待写回磁盘的队列中。</li><li><strong><code>uchar data[BSIZE]</code>：</strong> 字节数组，用于存储实际的数据块。<code>BSIZE</code> 是一个常量，表示数据块的大小。</li></ol><h5 id="2-1-2-初始化"><a href="#2-1-2-初始化" class="headerlink" title="2.1.2.初始化"></a>2.1.2.初始化</h5><div class="highlight-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//main.c</span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span></span><br><span class="line"><span class="title function_">main</span><span class="params">(<span class="type">void</span>)</span></span><br><span class="line">&#123;</span><br><span class="line">...</span><br><span class="line">  binit();         <span class="comment">// buffer cache</span></span><br><span class="line">  ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><div class="highlight-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//bio.c</span></span><br><span class="line"></span><br><span class="line"><span class="type">void</span></span><br><span class="line"><span class="title function_">binit</span><span class="params">(<span class="type">void</span>)</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="class"><span class="keyword">struct</span> <span class="title">buf</span> *<span class="title">b</span>;</span></span><br><span class="line">  initlock(&amp;bcache.lock, <span class="string">&quot;bcache&quot;</span>);</span><br><span class="line"><span class="comment">//PAGEBREAK!</span></span><br><span class="line">  <span class="comment">// Create linked list of buffers</span></span><br><span class="line">  bcache.head.prev = &amp;bcache.head;</span><br><span class="line">  bcache.head.next = &amp;bcache.hfead;</span><br><span class="line">  <span class="keyword">for</span>(b = bcache.buf; b &lt; bcache.buf+NBUF; b++)&#123;</span><br><span class="line">    b-&gt;next = bcache.head.next;</span><br><span class="line">    b-&gt;prev = &amp;bcache.head;</span><br><span class="line">    initsleeplock(&amp;b-&gt;lock, <span class="string">&quot;buffer&quot;</span>);</span><br><span class="line">    bcache.head.next-&gt;prev = b;</span><br><span class="line">    bcache.head.next = b;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><p>初始化就干两件事：</p><ol><li>创建bcache自旋锁</li><li>将缓存块buf构成一个LRU双向链表</li></ol><p>希望同学们不要把大部分精力陷入到代码的具体理解中，这没意义！</p><h5 id="2-1-3-查找"><a href="#2-1-3-查找" class="headerlink" title="2.1.3.查找"></a>2.1.3.查找</h5><div class="highlight-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//bio.c</span></span><br><span class="line"></span><br><span class="line"><span class="type">static</span> <span class="keyword">struct</span> buf*</span><br><span class="line"><span class="title function_">bget</span><span class="params">(uint dev, uint blockno)</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="class"><span class="keyword">struct</span> <span class="title">buf</span> *<span class="title">b</span>;</span></span><br><span class="line"></span><br><span class="line">  acquire(&amp;bcache.lock);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// Is the block already cached?</span></span><br><span class="line">  <span class="keyword">for</span>(b = bcache.head.next; b != &amp;bcache.head; b = b-&gt;next)&#123;</span><br><span class="line">    <span class="keyword">if</span>(b-&gt;dev == dev &amp;&amp; b-&gt;blockno == blockno)&#123;</span><br><span class="line">      b-&gt;refcnt++;</span><br><span class="line">      release(&amp;bcache.lock);</span><br><span class="line">      acquiresleep(&amp;b-&gt;lock);</span><br><span class="line">      <span class="keyword">return</span> b;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// Not cached; recycle an unused buffer.</span></span><br><span class="line">  <span class="comment">// Even if refcnt==0, B_DIRTY indicates a buffer is in use</span></span><br><span class="line">  <span class="comment">// because log.c has modified it but not yet committed it.</span></span><br><span class="line">  <span class="keyword">for</span>(b = bcache.head.prev; b != &amp;bcache.head; b = b-&gt;prev)&#123;</span><br><span class="line">    <span class="keyword">if</span>(b-&gt;refcnt == <span class="number">0</span> &amp;&amp; (b-&gt;flags &amp; B_DIRTY) == <span class="number">0</span>) &#123;</span><br><span class="line">      b-&gt;dev = dev;</span><br><span class="line">      b-&gt;blockno = blockno;</span><br><span class="line">      b-&gt;flags = <span class="number">0</span>;</span><br><span class="line">      b-&gt;refcnt = <span class="number">1</span>;</span><br><span class="line">      release(&amp;bcache.lock);</span><br><span class="line">      acquiresleep(&amp;b-&gt;lock);</span><br><span class="line">      <span class="keyword">return</span> b;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  panic(<span class="string">&quot;bget: no buffers&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><p>根据设备号和盘块号查找块缓存。需要注意的是，盘块缓冲区是对磁盘空间进行了抽象，抽象成了一个连续的空间。而对磁盘缓冲块的查找也相当的粗暴，直接for循环遍历！</p><h5 id="2-1-4-释放"><a href="#2-1-4-释放" class="headerlink" title="2.1.4.释放"></a>2.1.4.释放</h5><div class="highlight-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//bio.c</span></span><br><span class="line"></span><br><span class="line"><span class="type">void</span></span><br><span class="line"><span class="title function_">brelse</span><span class="params">(<span class="keyword">struct</span> buf *b)</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="keyword">if</span>(!holdingsleep(&amp;b-&gt;lock))</span><br><span class="line">    panic(<span class="string">&quot;brelse&quot;</span>);</span><br><span class="line"></span><br><span class="line">  releasesleep(&amp;b-&gt;lock);</span><br><span class="line"></span><br><span class="line">  acquire(&amp;bcache.lock);</span><br><span class="line">  b-&gt;refcnt--;</span><br><span class="line">  <span class="keyword">if</span> (b-&gt;refcnt == <span class="number">0</span>) &#123;</span><br><span class="line">    <span class="comment">// no one is waiting for it.</span></span><br><span class="line">    b-&gt;next-&gt;prev = b-&gt;prev;</span><br><span class="line">    b-&gt;prev-&gt;next = b-&gt;next;</span><br><span class="line">    b-&gt;next = bcache.head.next;</span><br><span class="line">    b-&gt;prev = &amp;bcache.head;</span><br><span class="line">    bcache.head.next-&gt;prev = b;</span><br><span class="line">    bcache.head.next = b;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  release(&amp;bcache.lock);</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//PAGEBREAK!</span></span><br><span class="line"><span class="comment">// Blank page.</span></span><br></pre></td></tr></table></figure></div><p>释放的代码很简单，需要注意的就是进行<code>b-&gt;refcnt--;</code>操作。</p><hr><p><font color="red">后续还有很多函数，考虑到文章的篇幅，这里我们不再贴代码，会指明代码所在的文件，同学们自行查找即可！</font></p><hr><h5 id="2-1-5-盘块的读写"><a href="#2-1-5-盘块的读写" class="headerlink" title="2.1.5.盘块的读写"></a>2.1.5.盘块的读写</h5><p>请注意，<font color="orange">盘块的读写指的都是从缓存块和磁盘块直接交互的过程！</font></p><p><img lazyload src="/images/loading.svg" data-src="https://ec28649.webp.li/20250228222442084.png" alt="image-20240113185835790"></p><p>读写函数都定义在<code>bio.c</code>中，<code>bread()</code>先调用<code>bget()</code>查找对应的缓存块，然后调用<code>iderw()</code>将数据块从磁盘中写入缓存区中；而bwrite()就是调用<code>iderw()</code>将数据块写回磁盘。</p><div class="highlight-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">struct</span> buf*</span><br><span class="line"><span class="title function_">bread</span><span class="params">(uint dev, uint blockno)</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="class"><span class="keyword">struct</span> <span class="title">buf</span> *<span class="title">b</span>;</span></span><br><span class="line"></span><br><span class="line">  b = bget(dev, blockno);</span><br><span class="line">  <span class="keyword">if</span>((b-&gt;flags &amp; B_VALID) == <span class="number">0</span>) &#123;</span><br><span class="line">    iderw(b);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> b;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><blockquote><p>这个读函数还是给大家贴出来，因为后面做实验要用到。</p></blockquote><h5 id="2-1-6-读入超级块"><a href="#2-1-6-读入超级块" class="headerlink" title="2.1.6.读入超级块"></a>2.1.6.读入超级块</h5><p>超级块的读入有点特殊（很正常，人家名字就已经很特殊了），有专门的在<code>fs.c</code>中的<code>readsb()</code>。</p><blockquote><p>嘿嘿，要不同学猜猜，为什么要抽离出来，定义一个专门的函数来读这个盘块？</p></blockquote><hr><h6 id="Ex5-2-请解释为什么要单独定义一个超级块的读入函数？"><a href="#Ex5-2-请解释为什么要单独定义一个超级块的读入函数？" class="headerlink" title="Ex5-2 请解释为什么要单独定义一个超级块的读入函数？"></a>Ex5-2 请解释为什么要单独定义一个超级块的读入函数？</h6><hr><blockquote><p>骚微提示一下，同学们想想这个盘块是在什么时候开始读的，哈哈哈，只能提示这么多了。</p></blockquote><h5 id="2-1-7-其他函数"><a href="#2-1-7-其他函数" class="headerlink" title="2.1.7.其他函数"></a>2.1.7.其他函数</h5><p>还有几个比较重要的函数是<code>bzero</code>、<code>balloc</code>、<code>bfree</code>，具体干嘛的，同学们自行查阅相关资料。</p><h4 id="2-2-索引节点操作"><a href="#2-2-索引节点操作" class="headerlink" title="2.2.索引节点操作"></a>2.2.索引节点操作</h4><p>对索引节点的操作是抽象在对磁盘操作的基础之上的，具体有对索引节点管理的文件的读写，以及对节点自身的分配，删除和修改。</p><h5 id="2-1-1-对索引节点自身的操作"><a href="#2-1-1-对索引节点自身的操作" class="headerlink" title="2.1.1.对索引节点自身的操作"></a>2.1.1.对索引节点自身的操作</h5><p>主要的函数有<code>iget</code>、<code>iupdate</code>、<code>idup</code>、<code>itrunc</code>、<code>stati</code>、<code>ilock</code>、<code>iunlock</code>、<code>iput</code>，都在<code>fs.c</code>中。</p><p><code>iget()</code>根据设备号dev和索引节点inum在索引节点缓存中查找，返回所匹配的索引节点缓存，或者分配一个空闲的索引节点缓存。</p><p><code>iupdate()</code>将inode缓存的内容更新到磁盘的dinode上，最后写回磁盘中。</p><p><code>idup()</code>增加索引节点缓存的引用计数，将其成员变量ref++即可。</p><p><code>itrunc()</code>将索引节点管理的文件数据（直接块和间接块）都释放掉，每个盘块通过<code>bfree()</code>释放</p><p><code>stati()</code>将索引节点缓存中的基本信息复制到stat结构体中并返回。</p><p><code>iput()</code>减少索引节点缓存的引用计数，将其成员变量ref–，若小于0则<code>itrunc</code>。</p><h5 id="2-1-2-对文件的操作"><a href="#2-1-2-对文件的操作" class="headerlink" title="2.1.2.对文件的操作"></a>2.1.2.对文件的操作</h5><p>主要的函数有<code>readi</code>、<code>bmap</code>、<code>writei</code>，都在<code>fs.c</code>中。</p><p><code>readi()</code></p><p>用于从 inode 对应的磁盘文件的偏移 off 处，读入n个字节到 dst 指向的数据缓冲区。如果是设备文件(T_DEV)，则使用设备的读操作函数 devsw[ip-&gt;major]。read()完成读入操作,否则将执行磁盘文件的读入操作,该操作略微有些复杂。</p><p><strong>磁盘文件需要逐个盘块读入数据,但首先要知道文件偏移量对应的物理盘块号是哪个,这是通过 bmap()完成的。</strong></p><p>确定盘块号之后,将会调用前面讨论过的 bread()完成磁盘盘块的读人。由于 bread()将数据读入到块缓存中,因此还需要用 memmove()将数据复制到用户空间缓冲区。</p><div class="highlight-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span></span><br><span class="line"><span class="title function_">readi</span><span class="params">(<span class="keyword">struct</span> inode *ip, <span class="type">char</span> *dst, uint off, uint n)</span></span><br><span class="line">&#123;</span><br><span class="line">  uint tot, m;</span><br><span class="line">  <span class="class"><span class="keyword">struct</span> <span class="title">buf</span> *<span class="title">bp</span>;</span></span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span>(ip-&gt;type == T_DEV)&#123;</span><br><span class="line">    <span class="keyword">if</span>(ip-&gt;major &lt; <span class="number">0</span> || ip-&gt;major &gt;= NDEV || !devsw[ip-&gt;major].read)</span><br><span class="line">      <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">    <span class="keyword">return</span> devsw[ip-&gt;major].read(ip, dst, n);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span>(off &gt; ip-&gt;size || off + n &lt; off)</span><br><span class="line">    <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">  <span class="keyword">if</span>(off + n &gt; ip-&gt;size)</span><br><span class="line">    n = ip-&gt;size - off;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">for</span>(tot=<span class="number">0</span>; tot&lt;n; tot+=m, off+=m, dst+=m)&#123;</span><br><span class="line">    bp = bread(ip-&gt;dev, bmap(ip, off/BSIZE));</span><br><span class="line">    m = min(n - tot, BSIZE - off%BSIZE);</span><br><span class="line">    memmove(dst, bp-&gt;data + off%BSIZE, m);</span><br><span class="line">    brelse(bp);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> n;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><blockquote><p>这个读函数还是给大家贴出来，因为后面做实验要用到。</p></blockquote><p><code>bmap()</code><br>由于进程发出的文件读写操作使用的是字节偏移(转换成文件内部的逻辑盘块号bn)，而磁盘读写 bread()和 bwrite()使用的是物理盘块号，因此需要 bmap()将文件字节偏移对应的逻辑盘块号 bn 转换成物理盘块号。其转换过程需要借助索引节点的 dinode.addrs[]或 inode.addrs[]，并且需要考虑直接盘块和间接盘块。</p><p>如果对应的数据盘块不存在,则 bmap() 会调用 balloc() 分配一个空闲盘块,然后再修改索引，使得 ip-&gt;addrs[bn] 指向新分配的盘块；如果该偏移落人间接索引区,则可能还需要分配间接索引盘块,然后才能分配盘块号bn所对应的数据盘块并建立索引关系。</p><p><code>writei()</code><br>writei()需要逐个盘块写出数据,因为有块缓存的存在,其会先调用 bread()将磁盘盘块读入到块缓存，然后才是将数据复制到块缓存中，最后由 log_write()向日志系统写出。writei()也是借用 bmap()，通过查找 dinode.addrs[]完成文件偏移量到磁盘盘块号的转换。</p><p>如果是设备(T_DEV),则需要通过它自身的读函数 devsw[ip-&gt;major].read 完成。</p><h4 id="2-3-目录操作"><a href="#2-3-目录操作" class="headerlink" title="2.3.目录操作"></a>2.3.目录操作</h4><p>这一部分我们直接略过了，其主要函数都在<code>fs.c</code>中，如用于目录查找的函数：<code>dirlookup</code>、<code>skipelem</code>；用于创建和删除的函数：<code>dirlink</code>；用于文件定位的函数：<code>namex</code>、<code>namei</code>、<code>nameiparent</code>，其均在<code>fs.c</code>中实现。</p><h4 id="2-4-文件操作"><a href="#2-4-文件操作" class="headerlink" title="2.4.文件操作"></a>2.4.文件操作</h4><h5 id="2-4-1-文件打开表初始化"><a href="#2-4-1-文件打开表初始化" class="headerlink" title="2.4.1.文件打开表初始化"></a>2.4.1.文件打开表初始化</h5><p>在<code>file.c</code>中有<code>fileinit</code>的定义：</p><div class="highlight-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//file.c</span></span><br><span class="line"></span><br><span class="line"><span class="type">void</span></span><br><span class="line"><span class="title function_">fileinit</span><span class="params">(<span class="type">void</span>)</span></span><br><span class="line">&#123;</span><br><span class="line">  initlock(&amp;ftable.lock, <span class="string">&quot;ftable&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><div class="highlight-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span></span><br><span class="line"><span class="title function_">main</span><span class="params">(<span class="type">void</span>)</span></span><br><span class="line">&#123;</span><br><span class="line">...</span><br><span class="line">  fileinit();      <span class="comment">// file table</span></span><br><span class="line">  ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><p>用于对系统中已打开文件列表ftable[]进行初始化，即初始化ftable.lock自旋锁</p><h5 id="2-4-2-分配、关闭和复制"><a href="#2-4-2-分配、关闭和复制" class="headerlink" title="2.4.2.分配、关闭和复制"></a>2.4.2.分配、关闭和复制</h5><p><code>filealloc</code>分配一个空闲的file对象</p><p><code>filedup</code>当用户进程对某个文件描述符进行复制时，将引起对应的file对象引用次数加一</p><p><code>fileclose</code>类似<code>iput</code></p><p><code>filestat</code>读取文件的元数据</p><h5 id="2-4-3-文件读写操作"><a href="#2-4-3-文件读写操作" class="headerlink" title="2.4.3.文件读写操作"></a>2.4.3.文件读写操作</h5><p><code>fileread()</code>是通用的文件读操作函数，当文件的type为FD_PIPE将调用piperead()进行读，而为FD_INODE时，调用readi()进行读。</p><div class="highlight-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//file.c</span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span></span><br><span class="line"><span class="title function_">fileread</span><span class="params">(<span class="keyword">struct</span> file *f, <span class="type">char</span> *addr, <span class="type">int</span> n)</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="type">int</span> r;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span>(f-&gt;readable == <span class="number">0</span>)</span><br><span class="line">    <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">  <span class="keyword">if</span>(f-&gt;type == FD_PIPE)</span><br><span class="line">    <span class="keyword">return</span> piperead(f-&gt;pipe, addr, n);</span><br><span class="line">  <span class="keyword">if</span>(f-&gt;type == FD_INODE)&#123;</span><br><span class="line">    ilock(f-&gt;ip);</span><br><span class="line">    <span class="keyword">if</span>((r = readi(f-&gt;ip, addr, f-&gt;off, n)) &gt; <span class="number">0</span>)</span><br><span class="line">      f-&gt;off += r;</span><br><span class="line">    iunlock(f-&gt;ip);</span><br><span class="line">    <span class="keyword">return</span> r;</span><br><span class="line">  &#125;</span><br><span class="line">  panic(<span class="string">&quot;fileread&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><blockquote><p>这个读函数还是给大家贴出来，因为后面做实验要用到。</p></blockquote><p><code>filewrite()</code>是通用的文件写操作函数，与读操作类似，也通过type分别调用具体的写函数。</p><h4 id="2-5-系统调用"><a href="#2-5-系统调用" class="headerlink" title="2.5.系统调用"></a>2.5.系统调用</h4><p>系统调用在Lab4中已经讲过，为此我们不再做过多赘述。本节课的系统调用的重点就是关注那些与文件操作相关的系统调用，如<code>sys_open</code>、<code>sys_close</code>、<code>sys_read</code>、<code>sys_write</code>、<code>sys_mkdir</code>、<code>sys_mknod</code>等</p><div class="highlight-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span></span><br><span class="line"><span class="title function_">sys_read</span><span class="params">(<span class="type">void</span>)</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="class"><span class="keyword">struct</span> <span class="title">file</span> *<span class="title">f</span>;</span></span><br><span class="line">  <span class="type">int</span> n;</span><br><span class="line">  <span class="type">char</span> *p;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span>(argfd(<span class="number">0</span>, <span class="number">0</span>, &amp;f) &lt; <span class="number">0</span> || argint(<span class="number">2</span>, &amp;n) &lt; <span class="number">0</span> || argptr(<span class="number">1</span>, &amp;p, n) &lt; <span class="number">0</span>)</span><br><span class="line">    <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">  <span class="keyword">return</span> fileread(f, p, n);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><p>由于我们后面的实验会用到sys_read()，因此各位重点了解一下这个函数。</p><h3 id="3-实验"><a href="#3-实验" class="headerlink" title="3.实验"></a>3.实验</h3><p>文件系统的内容相当庞大，涉及的概念多吗，因此容易弄混是很常见的事。笔者在上理论课的时候也就在这块理解的不是特别丝滑，因为诸位应该秉着从全局到末端的策略，反复理解。</p><p>那么，我们现在结合一个简单的实验，帮助同学们从进程、文件系统（内存到外存）到设备驱动，这整个文件读的操作理解清楚。</p><p><img lazyload src="/images/loading.svg" data-src="https://ec28649.webp.li/20250228222442085.png" alt="image-20240113205721509"></p><h4 id="3-1-gdbinit配置文件断点设置"><a href="#3-1-gdbinit配置文件断点设置" class="headerlink" title="3.1.gdbinit配置文件断点设置"></a>3.1.gdbinit配置文件断点设置</h4><div class="highlight-container" data-rel="Makefile"><figure class="iseeu highlight makefile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">layout src</span><br><span class="line"></span><br><span class="line">break sysfile.c:sys_read</span><br><span class="line">break file.c:fileread</span><br><span class="line">break fs.c:readi</span><br><span class="line">break bio.c:bread</span><br><span class="line">break ide.c:iderw</span><br></pre></td></tr></table></figure></div><h4 id="3-2-编译xv6-启动gdb"><a href="#3-2-编译xv6-启动gdb" class="headerlink" title="3.2.编译xv6&amp;启动gdb"></a>3.2.编译xv6&amp;启动gdb</h4><div class="highlight-container" data-rel="Shell"><figure class="iseeu highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">make qume-nox-gdb</span><br><span class="line">gdb</span><br></pre></td></tr></table></figure></div><h4 id="3-3-具体过程"><a href="#3-3-具体过程" class="headerlink" title="3.3.具体过程"></a>3.3.具体过程</h4><p>Step1：在gdb中：通过info breakpoints检查断点</p><p><img lazyload src="/images/loading.svg" data-src="https://ec28649.webp.li/20250228222442086.png" alt="image-20240113210843772"></p><p>Step2：取消断点2,3,4,5</p><div class="highlight-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">disable <span class="number">2</span> <span class="number">3</span> <span class="number">4</span> <span class="number">5</span></span><br></pre></td></tr></table></figure></div><p>Step3：continue</p><p>在gdb中输入一个c之后，程序执行到第一个断点位置，此时xv6终端呈现如下，但依旧不能输入。</p><p><img lazyload src="/images/loading.svg" data-src="https://ec28649.webp.li/20250228222442087.png" alt="image-20240113211155390"></p><p>在输入第二个c后，程序进入死循环，此时终端可以使用，即开中断已打开。</p><p>Step4：在xv6中输入<code>cat README</code>，即读README文件，并将内容输出到终端中。</p><p>此时，产生中断，截停在sys_read函数中</p><p>Step5：此时我们在gdb中恢复断点2,3,4,5</p><div class="highlight-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">enable <span class="number">2</span> <span class="number">3</span> <span class="number">4</span> <span class="number">5</span></span><br></pre></td></tr></table></figure></div><p><img lazyload src="/images/loading.svg" data-src="https://ec28649.webp.li/20250228222442088.png" alt="image-20240113211724588"></p><p>然后，我们逐行调试，在第78行进入函数fileread中。</p><p><img lazyload src="/images/loading.svg" data-src="https://ec28649.webp.li/20250228222442089.png" alt="image-20240113211913594"></p><p>再接着逐行调试，会在107行进入readi函数中。</p><p><img lazyload src="/images/loading.svg" data-src="https://ec28649.webp.li/20250228222442090.png" alt="image-20240113212046641"></p><p>再继续，此时可能会读写多个缓存块。在470行进入bread函数中。</p><p><img lazyload src="/images/loading.svg" data-src="https://ec28649.webp.li/20250228222442091.png" alt="image-20240113212320321"></p><p>最后，通过设备驱动程序读取文件，在103行进入iderw函数中。</p><p>Step7：当你继续c后，直到xv6终端中出现这么一大段字符，就说明已经从磁盘中读取了一个块的内容。</p><div class="highlight-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">NOTE: we have stopped maintaining the x86 version of xv6, and switched</span><br><span class="line">our efforts to the RISC-V version</span><br><span class="line">(https://github.com/mit-pdos/xv6-riscv.git)</span><br><span class="line"></span><br><span class="line">xv6 is a re-implementation of Dennis Ritchie&#x27;s and Ken Thompson&#x27;s Unix</span><br><span class="line">Version 6 (v6).  xv6 loosely follows the structure and style of v6,</span><br><span class="line">but is implemented for a modern x86-based multiprocessor using ANSI C.</span><br><span class="line"></span><br><span class="line">ACKNOWLEDGMENTS</span><br><span class="line"></span><br><span class="line">xv6 is inspired by John Lions&#x27;s Commentary on UNIX 6th Edition (Peer</span><br><span class="line">to Peer Communications; ISBN: 1-57398-013-7; 1st edition (June 14</span><br></pre></td></tr></table></figure></div><p>读者可以数一下，这一共多少个字符。如果是512个，那么恭喜，你数对了。字符数就是一个块的大小（512B）。</p><p>当然，读者再继续c几下还会出现更多的内容。我们直到README文件的大小为2286B，那么算下来，读者应该会执行5次c命令才算把这个文件读完。</p><p>Step8：做到这里，基本上这个小实验就算完成了。做完这些，读者应该大致对这个文件读的过程有了一个宏观的了解。至于这个后面，文件名是如何传递的，索引号是如何检索的，读者如果感兴趣，可以自行探索。</p><p>最后，我们再留一个小作业。我们刚带诸位了解了文件读的过程，那么文件写的执行过程是怎样的，请同学们自行实验探索。</p><hr><h5 id="Ex5-3-请分析echo-“0”-mydir-myfile3这条命令的执行过程，要求对执行过程经过的几个关键的写函数进行分析和截图，并绘制如下，关于写过程的流程图。"><a href="#Ex5-3-请分析echo-“0”-mydir-myfile3这条命令的执行过程，要求对执行过程经过的几个关键的写函数进行分析和截图，并绘制如下，关于写过程的流程图。" class="headerlink" title="Ex5-3 请分析echo “0” &gt; /mydir/myfile3这条命令的执行过程，要求对执行过程经过的几个关键的写函数进行分析和截图，并绘制如下，关于写过程的流程图。"></a>Ex5-3 请分析<code>echo “0” &gt; /mydir/myfile3</code>这条命令的执行过程，要求对执行过程经过的几个关键的写函数进行分析和截图，并绘制如下，关于写过程的流程图。</h5><hr><p><img lazyload src="/images/loading.svg" data-src="https://ec28649.webp.li/20250228222442092.png" alt="image-20240113213612673"></p>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;Lab5-文件系统&quot;&gt;&lt;a href=&quot;#Lab5-文件系统&quot; class=&quot;headerlink&quot; title=&quot;Lab5 文件系统&quot;&gt;&lt;/a&gt;Lab5 文件系统&lt;/h2&gt;&lt;h3 id=&quot;1-文件系统&quot;&gt;&lt;a href=&quot;#1-文件系统&quot; class=&quot;head</summary>
      
    
    
    
    <category term="Notes" scheme="https://gme-hong.github.io/categories/Notes/"/>
    
    
    <category term="XV6 OS" scheme="https://gme-hong.github.io/tags/XV6-OS/"/>
    
  </entry>
  
  <entry>
    <title>ChatGPT 打开潘多拉魔盒，行业壁垒逐步瓦解</title>
    <link href="https://gme-hong.github.io/2023/10/06/ChatGPT/"/>
    <id>https://gme-hong.github.io/2023/10/06/ChatGPT/</id>
    <published>2023-10-06T14:43:39.000Z</published>
    <updated>2025-02-28T14:23:05.332Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>作为一名非专精于NLP算法的同学，去详细论述ChatGPT的底层原理是很困难的。但好在最近为了搭建一个轻量化的RGM(Robotic Grasp Model)，接触到了一部分的图像注意力机制。想着CV、NLP本是一家亲，更何况ChatGPT的前世还可以追溯到《Attention is all you need》，为此斗胆写了一篇《ChatGPT 打开潘多拉魔盒，行业壁垒逐步瓦解》的爽文（对！爽文，让你轻松拿捏这个庞然大物），仅供同学们交流学习！</p><p>在本文中你将会了解到一些人工智能的前置知识、ChatGPT的概念、Transformer的基本原理、还有我关于ChatGPT的见解~</p><h2 id="人工智能的前置知识"><a href="#人工智能的前置知识" class="headerlink" title="人工智能的前置知识"></a>人工智能的前置知识</h2><p>ChatGPT作为一个人工智能LLM (Large Language Model) 大语言模型，细盘之前，我们先了解一下一些基本的人工智能知识。</p><h3 id="机器学习"><a href="#机器学习" class="headerlink" title="机器学习"></a>机器学习</h3><p>机器学习，作为计算机专业研一同学必修的一门课程，足以体现其在人工智能领域举足轻重的地位。虽然现在学术前沿领域都在深度学习这条赛道上一发不可收拾，但是搞深度学习前还是得了解一些机器学习哦。<code>smooth_l1_loss</code>、<code>sigmoid</code>都会吧，同学？这些在用<code>pytorch</code>搞深度学习时常用的损失函数、激活函数可都是机器学习的知识哦。撇开HMM(Hidden Markov Model)、CRF(Conditional Random Field)等一些集成方法不谈，无论是决策树、K-means聚类、支持向量机、梯度提升树还是朴素贝叶斯，这些经典的机器学习模型也都是一些工业应用领域的常青树哦！（别问我为什么，去问度娘~）。</p><p>讲了一大堆机器学习的重要性，都还没说这到底是个啥。来，上概念。</p><p>机器学习(Machine Learning，ML)是指从有限的观测数据中学习(或“猜测”)出具有一般性的规律，并将这些规律应用到未观测数据样本上的方法。主要研究内容是学习算法。基本流程是基于数据产生模型，利用模型预测输出。目标是让模型有较好泛化能力。</p><p>什么？太抽象了，不好理解？</p><p>举一个经典的例子，我们挑西瓜的时候是如何判断一个西瓜是否成熟的呢？每个人一开始都是不会挑选的，但是随着我们耳濡目染，看了很多挑西瓜能手是怎么做的，发现可以通过西瓜的颜色，大小，产地，纹路，敲击声等等因素来判断，那么这个就是一个学习的过程。</p><p>那么人工智能、机器学习以及深度学习的关系是什么呢？或者说这个家族的族谱应该怎么画呢？</p><p>别急，一张图搞明白他们的关系。</p><style>.fpxhvqfvzevl{zoom:50%;}</style><img lazyload src="/images/loading.svg" data-src="/2023/10/06/ChatGPT/10e55600dccd279069d0b58207d487c0.jpeg" class="fpxhvqfvzevl" alt="10e55600dccd279069d0b58207d487c0.jpeg"><p>不知道你有没发现，这些方法的进化过程就是人类专精于摸鱼的过程。从以前纯人工作业到现在半人工、甚至全智能作业，从需要繁琐的数据预处理和客制化的特征提取手段的机器学习过渡到让机器自己去学特征的深度学习，从有监督任务过渡到自监督任务、多阶段深度学习算法过渡到端到端的算法，从手动搭建合适的深度网络模型到NAS(Neural Architecture Search) ……</p><p>哇，果然人类摸鱼的潜能是无限的~</p><h3 id="参数-权重："><a href="#参数-权重：" class="headerlink" title="参数 / 权重："></a>参数 / 权重：</h3><p>前文我们说过，ChatGPT是一个LLM。对于任何模型而言，无非就两种表示形式，即<code>y=f(x)</code>型和<code>P(y|x)</code>型。前者是确定性模型(也称非概率模型)，后者是概率模型。无非是哪一种，其中不可或缺的就是参数。</p><p>我们举几个例子说明参数的重要性。</p><p>如<code>y=f(x)</code>模型中最简单的y=wx+b。先不引入任何机器学习的概念，这就是我们小学学的二元一次方程，只不过我们当时w和b往往是给定的，我们最关注的就是如何解得x和y。但在机器学习中，往往x是给定的，需要我们求解出(拟合出)w和b的值。按照我们小学课本里教的，w就是斜率，b就是截距。说到这里不知道你意识到没，这就是知识啊！我们知道了其特定的含义就能在一个二维平面中可视化这条直线，机器知道了就能做一个简单的yes和no的回答。又如<code>P(y|x)</code>模型中的<br><mjx-container class="MathJax" jax="SVG" display="true"><svg style="vertical-align: -0.566ex;" xmlns="http://www.w3.org/2000/svg" width="20.88ex" height="2.262ex" role="img" focusable="false" viewbox="0 -750 9228.9 1000"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><path data-c="1D43B" d="M228 637Q194 637 192 641Q191 643 191 649Q191 673 202 682Q204 683 219 683Q260 681 355 681Q389 681 418 681T463 682T483 682Q499 682 499 672Q499 670 497 658Q492 641 487 638H485Q483 638 480 638T473 638T464 637T455 637Q416 636 405 634T387 623Q384 619 355 500Q348 474 340 442T328 395L324 380Q324 378 469 378H614L615 381Q615 384 646 504Q674 619 674 627T617 637Q594 637 587 639T580 648Q580 650 582 660Q586 677 588 679T604 682Q609 682 646 681T740 680Q802 680 835 681T871 682Q888 682 888 672Q888 645 876 638H874Q872 638 869 638T862 638T853 637T844 637Q805 636 794 634T776 623Q773 618 704 340T634 58Q634 51 638 51Q646 48 692 46H723Q729 38 729 37T726 19Q722 6 716 0H701Q664 2 567 2Q533 2 504 2T458 2T437 1Q420 1 420 10Q420 15 423 24Q428 43 433 45Q437 46 448 46H454Q481 46 514 49Q520 50 522 50T528 55T534 64T540 82T547 110T558 153Q565 181 569 198Q602 330 602 331T457 332H312L279 197Q245 63 245 58Q245 51 253 49T303 46H334Q340 38 340 37T337 19Q333 6 327 0H312Q275 2 178 2Q144 2 115 2T69 2T48 1Q31 1 31 10Q31 12 34 24Q39 43 44 45Q48 46 59 46H65Q92 46 125 49Q139 52 144 61Q147 65 216 339T285 628Q285 635 228 637Z"/></g><g data-mml-node="mi" transform="translate(888,0)"><path data-c="1D440" d="M289 629Q289 635 232 637Q208 637 201 638T194 648Q194 649 196 659Q197 662 198 666T199 671T201 676T203 679T207 681T212 683T220 683T232 684Q238 684 262 684T307 683Q386 683 398 683T414 678Q415 674 451 396L487 117L510 154Q534 190 574 254T662 394Q837 673 839 675Q840 676 842 678T846 681L852 683H948Q965 683 988 683T1017 684Q1051 684 1051 673Q1051 668 1048 656T1045 643Q1041 637 1008 637Q968 636 957 634T939 623Q936 618 867 340T797 59Q797 55 798 54T805 50T822 48T855 46H886Q892 37 892 35Q892 19 885 5Q880 0 869 0Q864 0 828 1T736 2Q675 2 644 2T609 1Q592 1 592 11Q592 13 594 25Q598 41 602 43T625 46Q652 46 685 49Q699 52 704 61Q706 65 742 207T813 490T848 631L654 322Q458 10 453 5Q451 4 449 3Q444 0 433 0Q418 0 415 7Q413 11 374 317L335 624L267 354Q200 88 200 79Q206 46 272 46H282Q288 41 289 37T286 19Q282 3 278 1Q274 0 267 0Q265 0 255 0T221 1T157 2Q127 2 95 1T58 0Q43 0 39 2T35 11Q35 13 38 25T43 40Q45 46 65 46Q135 46 154 86Q158 92 223 354T289 629Z"/></g><g data-mml-node="mi" transform="translate(1939,0)"><path data-c="1D440" d="M289 629Q289 635 232 637Q208 637 201 638T194 648Q194 649 196 659Q197 662 198 666T199 671T201 676T203 679T207 681T212 683T220 683T232 684Q238 684 262 684T307 683Q386 683 398 683T414 678Q415 674 451 396L487 117L510 154Q534 190 574 254T662 394Q837 673 839 675Q840 676 842 678T846 681L852 683H948Q965 683 988 683T1017 684Q1051 684 1051 673Q1051 668 1048 656T1045 643Q1041 637 1008 637Q968 636 957 634T939 623Q936 618 867 340T797 59Q797 55 798 54T805 50T822 48T855 46H886Q892 37 892 35Q892 19 885 5Q880 0 869 0Q864 0 828 1T736 2Q675 2 644 2T609 1Q592 1 592 11Q592 13 594 25Q598 41 602 43T625 46Q652 46 685 49Q699 52 704 61Q706 65 742 207T813 490T848 631L654 322Q458 10 453 5Q451 4 449 3Q444 0 433 0Q418 0 415 7Q413 11 374 317L335 624L267 354Q200 88 200 79Q206 46 272 46H282Q288 41 289 37T286 19Q282 3 278 1Q274 0 267 0Q265 0 255 0T221 1T157 2Q127 2 95 1T58 0Q43 0 39 2T35 11Q35 13 38 25T43 40Q45 46 65 46Q135 46 154 86Q158 92 223 354T289 629Z"/></g><g data-mml-node="mo" transform="translate(2990,0)"><path data-c="28" d="M94 250Q94 319 104 381T127 488T164 576T202 643T244 695T277 729T302 750H315H319Q333 750 333 741Q333 738 316 720T275 667T226 581T184 443T167 250T184 58T225 -81T274 -167T316 -220T333 -241Q333 -250 318 -250H315H302L274 -226Q180 -141 137 -14T94 250Z"/></g><g data-mml-node="mi" transform="translate(3379,0)"><path data-c="1D707" d="M58 -216Q44 -216 34 -208T23 -186Q23 -176 96 116T173 414Q186 442 219 442Q231 441 239 435T249 423T251 413Q251 401 220 279T187 142Q185 131 185 107V99Q185 26 252 26Q261 26 270 27T287 31T302 38T315 45T327 55T338 65T348 77T356 88T365 100L372 110L408 253Q444 395 448 404Q461 431 491 431Q504 431 512 424T523 412T525 402L449 84Q448 79 448 68Q448 43 455 35T476 26Q485 27 496 35Q517 55 537 131Q543 151 547 152Q549 153 557 153H561Q580 153 580 144Q580 138 575 117T555 63T523 13Q510 0 491 -8Q483 -10 467 -10Q446 -10 429 -4T402 11T385 29T376 44T374 51L368 45Q362 39 350 30T324 12T288 -4T246 -11Q199 -11 153 12L129 -85Q108 -167 104 -180T92 -202Q76 -216 58 -216Z"/></g><g data-mml-node="mo" transform="translate(4259.8,0)"><path data-c="3D" d="M56 347Q56 360 70 367H707Q722 359 722 347Q722 336 708 328L390 327H72Q56 332 56 347ZM56 153Q56 168 72 173H708Q722 163 722 153Q722 140 707 133H70Q56 140 56 153Z"/></g><g data-mml-node="mo" transform="translate(5315.6,0)"><path data-c="5B" d="M118 -250V750H255V710H158V-210H255V-250H118Z"/></g><g data-mml-node="mi" transform="translate(5593.6,0)"><path data-c="1D70B" d="M132 -11Q98 -11 98 22V33L111 61Q186 219 220 334L228 358H196Q158 358 142 355T103 336Q92 329 81 318T62 297T53 285Q51 284 38 284Q19 284 19 294Q19 300 38 329T93 391T164 429Q171 431 389 431Q549 431 553 430Q573 423 573 402Q573 371 541 360Q535 358 472 358H408L405 341Q393 269 393 222Q393 170 402 129T421 65T431 37Q431 20 417 5T381 -10Q370 -10 363 -7T347 17T331 77Q330 86 330 121Q330 170 339 226T357 318T367 358H269L268 354Q268 351 249 275T206 114T175 17Q164 -11 132 -11Z"/></g><g data-mml-node="mo" transform="translate(6163.6,0)"><path data-c="2C" d="M78 35T78 60T94 103T137 121Q165 121 187 96T210 8Q210 -27 201 -60T180 -117T154 -158T130 -185T117 -194Q113 -194 104 -185T95 -172Q95 -168 106 -156T131 -126T157 -76T173 -3V9L172 8Q170 7 167 6T161 3T152 1T140 0Q113 0 96 17Z"/></g><g data-mml-node="mi" transform="translate(6608.2,0)"><path data-c="1D434" d="M208 74Q208 50 254 46Q272 46 272 35Q272 34 270 22Q267 8 264 4T251 0Q249 0 239 0T205 1T141 2Q70 2 50 0H42Q35 7 35 11Q37 38 48 46H62Q132 49 164 96Q170 102 345 401T523 704Q530 716 547 716H555H572Q578 707 578 706L606 383Q634 60 636 57Q641 46 701 46Q726 46 726 36Q726 34 723 22Q720 7 718 4T704 0Q701 0 690 0T651 1T578 2Q484 2 455 0H443Q437 6 437 9T439 27Q443 40 445 43L449 46H469Q523 49 533 63L521 213H283L249 155Q208 86 208 74ZM516 260Q516 271 504 416T490 562L463 519Q447 492 400 412L310 260L413 259Q516 259 516 260Z"/></g><g data-mml-node="mo" transform="translate(7358.2,0)"><path data-c="2C" d="M78 35T78 60T94 103T137 121Q165 121 187 96T210 8Q210 -27 201 -60T180 -117T154 -158T130 -185T117 -194Q113 -194 104 -185T95 -172Q95 -168 106 -156T131 -126T157 -76T173 -3V9L172 8Q170 7 167 6T161 3T152 1T140 0Q113 0 96 17Z"/></g><g data-mml-node="mi" transform="translate(7802.9,0)"><path data-c="1D435" d="M231 637Q204 637 199 638T194 649Q194 676 205 682Q206 683 335 683Q594 683 608 681Q671 671 713 636T756 544Q756 480 698 429T565 360L555 357Q619 348 660 311T702 219Q702 146 630 78T453 1Q446 0 242 0Q42 0 39 2Q35 5 35 10Q35 17 37 24Q42 43 47 45Q51 46 62 46H68Q95 46 128 49Q142 52 147 61Q150 65 219 339T288 628Q288 635 231 637ZM649 544Q649 574 634 600T585 634Q578 636 493 637Q473 637 451 637T416 636H403Q388 635 384 626Q382 622 352 506Q352 503 351 500L320 374H401Q482 374 494 376Q554 386 601 434T649 544ZM595 229Q595 273 572 302T512 336Q506 337 429 337Q311 337 310 336Q310 334 293 263T258 122L240 52Q240 48 252 48T333 46Q422 46 429 47Q491 54 543 105T595 229Z"/></g><g data-mml-node="mo" transform="translate(8561.9,0)"><path data-c="5D" d="M22 710V750H159V-250H22V-210H119V710H22Z"/></g><g data-mml-node="mo" transform="translate(8839.9,0)"><path data-c="29" d="M60 749L64 750Q69 750 74 750H86L114 726Q208 641 251 514T294 250Q294 182 284 119T261 12T224 -76T186 -143T145 -194T113 -227T90 -246Q87 -249 86 -250H74Q66 -250 63 -250T58 -247T55 -238Q56 -237 66 -225Q221 -64 221 250T66 725Q56 737 55 738Q55 746 60 749Z"/></g></g></g></svg></mjx-container><br>无论是初始概率分布，状态转移矩阵还是观测矩阵都是HMM主要的参数来源啊，而这三个部分也正是HMM的核心。</p><p>而我们所津津乐道的ChatGPT就是一个除了参数还是参数的大怪物，其拥有这可怕的1750亿的参数规模(本来还想说一下其所需的浮点运算次数的，emmm，找了半天没找到)。要训练一个千亿参数量级的模型，所需要支持的算力那也应该不是我们能设想的。再加上<code>human in the loop</code>的机制，哇，烧钱的嘞。</p><h3 id="过拟合、欠拟合与模型退化"><a href="#过拟合、欠拟合与模型退化" class="headerlink" title="过拟合、欠拟合与模型退化"></a>过拟合、欠拟合与模型退化</h3><p>如果你已经把所有的菜都备齐了，炒菜的设备也上齐了，那么接下来就应该是生火开炒了——这里具体如何炒，先加盐还是先放醋针对不同的菜应该有不同的策略，正如机器学习针对不同的任务需要设计不同的特征提取策略一样，为此这里我们还是用深度学习的黑箱模型来说事(其实主要是我不会炒菜……)。最理想的状态肯定就是炒完后菜的口感最能满足你挑剔的嘴，但是事情往往并不会向我们所期待的那样，炒焦了和没炒熟都是很常见的情况。模型的训练也正如这个做菜的过程，稍不留神就过拟、欠拟。但这还是好的，最崩溃的情况是你拿一只大锅发现怎么样也炒不出小锅的味道，而且每次用大锅颠勺都还让你累的够呛。这也就是模型的退化现象，好像这就是自然界的规律，过犹而不及。正如Google的PaLM(5400亿参数)，微软和英伟达合作的MT-NLG(5300亿参数)跟ChatGPT比起来都差点意思。</p><h3 id="人类反馈强化学习（RLHF）"><a href="#人类反馈强化学习（RLHF）" class="headerlink" title="人类反馈强化学习（RLHF）"></a>人类反馈强化学习（RLHF）</h3><p>人类反馈强化学习，一听到这个名词是不是就感觉特别高大尚。害其实也没什么，就是智能还不够智能，还需要人类从中插一脚。RLHF最本质的特点就是<code>human in the loop</code>，也就是人在回路。当然认真看过流浪地球2的同学们应该还是不太陌生的。</p><p>RLHF 主要包括三个步骤：</p><ol><li>使用监督学习训练语言模型；</li><li>根据人类偏好收集比较数据并训练奖励模型；</li><li><strong>使用强化学习针对奖励模型优化语言模型。</strong></li></ol><p>它使模型能够通过从人类获取反馈，从而不断改进自身学习技能，从而有效地适应实际环境。都说到这个份上了，养过猫猫狗狗的同学应该就能get到了，你驯化它的过程就是RLHF。</p><h3 id="神经网络"><a href="#神经网络" class="headerlink" title="神经网络"></a>神经网络</h3><p>神经网络，都以“神经”这两个字命名了，那肯定就和人的神经元系统相当类似咯。无论是你眼镜接收到的视觉信息、皮肤感知到的触觉信息还是耳朵获取的听觉信息都需要从感受器通过电信号(和化学信号)的形式传导到神经中枢，而这就跟输入向量在神经网络的传导过程十分类似。对于冲动传输链路上的每一个神经元扮演了一个节流阀的作用(激活还是抑制)，类似于神经网络的前向传播过程，每一层的每个节点都能决定通过其这条沿线的权重大小。</p><style>.dhxcosvrctlm{}</style><img lazyload src="/images/loading.svg" data-src="/2023/10/06/ChatGPT/dcffceea100ade33ea53501be945d8ce.jpeg" class="dhxcosvrctlm"><style>.sczzsamhqdpy{}</style><img lazyload src="/images/loading.svg" data-src="/2023/10/06/ChatGPT/0c2fb894842b39f67ecc33c9288dbd52.jpeg" class="sczzsamhqdpy"><p>神经网络的结构正如图五所示，最基本形式的人工神经网络有三层神经元。信息从一层神经元流向另一层，就像在人脑中一样：</p><ol><li>输入层：数据进入系统的入口点</li><li>隐藏层：处理信息的地方</li><li>输出层：系统根据数据决定如何继续操作的位置</li></ol><h2 id="ChatGPT-的概念"><a href="#ChatGPT-的概念" class="headerlink" title="ChatGPT 的概念"></a>ChatGPT 的概念</h2><p>哇，终于写到正题了，eight hours later……</p><p>GPT 对应的是三个单词：Generative，Pre-Training，Transformer。</p><p><strong>Generative</strong>：生成式，比较好理解，通过学习历史数据，来生成<strong>全新</strong>的数据。请注意，这里是全新的哦！不过全新也要看你怎么理解，如果想说它能所回答你的每一个字是不是全新的，那肯定是不现实的。这种新应该是一种全新的、带有人类语言逻辑的语句组合。更直观地去理解Generative，那肯定就得提及DALL-E(emmm，这家伙也是OpenAI的)，这个东西能够根据一段场景描述性的文字来生成一张与之对应的、全新的图片(类似于上面对全新概念的理解，你总不能要0-255的像素值是全新的吧)。从这个角度去理解ChatGPT，那它就一个词生成器啊。例如ChatGPT已经生成了“今天晚上我们去打”，那么下一个生成的字/词就有可能是“台球”、“篮球”、“羽毛球”、“乒乓球”、“游戏”，它的生成逻辑就是从这些候选的单元中选出一个概率最大值。如果你使用过ChatGPT，并且详细观察过它回答你的方式，你可能会对「逐字」这个概念有更深的感触。</p><p><strong>Pre-Training</strong>：预训练，顾名思义就是预先训练的意思，说白了就是一个特征提取的过程。“预训练“方法的诞生是出于这样的现实：标注资源稀缺而无标注资源丰富(某种特殊的任务只存在非常少量的相关训练数据，以至于模型不能从中学习总结到有用的规律)。</p><p>举个简单的例子，现在有一个对英语一窍不通的同学和一个英语基础尚佳的同学同时去完成翻译并总结一篇英语技术文章的任务。对前者来说就需要先学会英文 26 个字母，进而学会单词语法等，再去了解这篇文章相关的技术，最后才能去完成我们指派的任务。但是对后者来做这个任务就相对简单的多，他只需要去大致了解一下这篇文章所涉及到的技术，便能很好的总结出来。</p><p>这就是预训练，<strong>先把一些通用能力提前训练出来</strong>。人工智能本身就是一个不断训练参数的过程，如果我们可以提前把通用能力相关的参数提前训练好，那么在一些特殊的场景，发现通用能力不能完全适配时，只做简单的参数微调即可，这样做大幅减少了每个独立训练预测任务的计算成本。反映在具体模型上就是：</p><ol><li>模型参数不再是随机初始化，而是通过一些任务(如语言模型)进行预训练</li><li>将训练任务拆解成共性学习和特性学习两个步骤</li></ol><p><strong>Transformer</strong>：这是 ChatGPT 的灵魂，它是一个神经网络架构。后文再进行详细的说明。</p><p>以上就是 ChatGPT 的基本概念，结合起来就是一个采用了预训练和强化学习策略的生成式神经网络模型，它能够对人类的对话进行模拟。</p><h2 id="Transformer基本知识"><a href="#Transformer基本知识" class="headerlink" title="Transformer基本知识"></a>Transformer基本知识</h2><p>因为介绍Transformer的文章很多啊，在此我就直接搬运王正学长在《<a class="link" href="https://juejin.cn/post/7277802797473841186">让非算法同学也能了解 ChatGPT 等相关大模型 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a>》文章中论述的了。别看Transformer(变形金刚，很漫威)这个名字很玄乎，其实大致也就三个主要部分：Embedding、Self-Attention以及Softmax。</p><p>Embedding很好理解，就是要把输入的自然语言转换成机器能理解的向量表示。当然为了充分利用语言的序列特性，因此还需要加入额外的位置编码。</p><p>Self-Attention也就是我们常说的自注意力机制，emmm有点复杂，好像一句话不怎么讲的清楚，其实也就是<code>"What do I care most about myself"</code>。</p><p>Softmax这个好理解，直接上公式：<br><mjx-container class="MathJax" jax="SVG" display="true"><svg style="vertical-align: -2.27ex;" xmlns="http://www.w3.org/2000/svg" width="27.204ex" height="5.573ex" role="img" focusable="false" viewbox="0 -1460 12024.1 2463.1"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><path data-c="1D446" d="M308 24Q367 24 416 76T466 197Q466 260 414 284Q308 311 278 321T236 341Q176 383 176 462Q176 523 208 573T273 648Q302 673 343 688T407 704H418H425Q521 704 564 640Q565 640 577 653T603 682T623 704Q624 704 627 704T632 705Q645 705 645 698T617 577T585 459T569 456Q549 456 549 465Q549 471 550 475Q550 478 551 494T553 520Q553 554 544 579T526 616T501 641Q465 662 419 662Q362 662 313 616T263 510Q263 480 278 458T319 427Q323 425 389 408T456 390Q490 379 522 342T554 242Q554 216 546 186Q541 164 528 137T492 78T426 18T332 -20Q320 -22 298 -22Q199 -22 144 33L134 44L106 13Q83 -14 78 -18T65 -22Q52 -22 52 -14Q52 -11 110 221Q112 227 130 227H143Q149 221 149 216Q149 214 148 207T144 186T142 153Q144 114 160 87T203 47T255 29T308 24Z"/></g><g data-mml-node="mi" transform="translate(645,0)"><path data-c="1D45C" d="M201 -11Q126 -11 80 38T34 156Q34 221 64 279T146 380Q222 441 301 441Q333 441 341 440Q354 437 367 433T402 417T438 387T464 338T476 268Q476 161 390 75T201 -11ZM121 120Q121 70 147 48T206 26Q250 26 289 58T351 142Q360 163 374 216T388 308Q388 352 370 375Q346 405 306 405Q243 405 195 347Q158 303 140 230T121 120Z"/></g><g data-mml-node="mi" transform="translate(1130,0)"><path data-c="1D453" d="M118 -162Q120 -162 124 -164T135 -167T147 -168Q160 -168 171 -155T187 -126Q197 -99 221 27T267 267T289 382V385H242Q195 385 192 387Q188 390 188 397L195 425Q197 430 203 430T250 431Q298 431 298 432Q298 434 307 482T319 540Q356 705 465 705Q502 703 526 683T550 630Q550 594 529 578T487 561Q443 561 443 603Q443 622 454 636T478 657L487 662Q471 668 457 668Q445 668 434 658T419 630Q412 601 403 552T387 469T380 433Q380 431 435 431Q480 431 487 430T498 424Q499 420 496 407T491 391Q489 386 482 386T428 385H372L349 263Q301 15 282 -47Q255 -132 212 -173Q175 -205 139 -205Q107 -205 81 -186T55 -132Q55 -95 76 -78T118 -61Q162 -61 162 -103Q162 -122 151 -136T127 -157L118 -162Z"/></g><g data-mml-node="mi" transform="translate(1680,0)"><path data-c="1D461" d="M26 385Q19 392 19 395Q19 399 22 411T27 425Q29 430 36 430T87 431H140L159 511Q162 522 166 540T173 566T179 586T187 603T197 615T211 624T229 626Q247 625 254 615T261 596Q261 589 252 549T232 470L222 433Q222 431 272 431H323Q330 424 330 420Q330 398 317 385H210L174 240Q135 80 135 68Q135 26 162 26Q197 26 230 60T283 144Q285 150 288 151T303 153H307Q322 153 322 145Q322 142 319 133Q314 117 301 95T267 48T216 6T155 -11Q125 -11 98 4T59 56Q57 64 57 83V101L92 241Q127 382 128 383Q128 385 77 385H26Z"/></g><g data-mml-node="mi" transform="translate(2041,0)"><path data-c="1D45A" d="M21 287Q22 293 24 303T36 341T56 388T88 425T132 442T175 435T205 417T221 395T229 376L231 369Q231 367 232 367L243 378Q303 442 384 442Q401 442 415 440T441 433T460 423T475 411T485 398T493 385T497 373T500 364T502 357L510 367Q573 442 659 442Q713 442 746 415T780 336Q780 285 742 178T704 50Q705 36 709 31T724 26Q752 26 776 56T815 138Q818 149 821 151T837 153Q857 153 857 145Q857 144 853 130Q845 101 831 73T785 17T716 -10Q669 -10 648 17T627 73Q627 92 663 193T700 345Q700 404 656 404H651Q565 404 506 303L499 291L466 157Q433 26 428 16Q415 -11 385 -11Q372 -11 364 -4T353 8T350 18Q350 29 384 161L420 307Q423 322 423 345Q423 404 379 404H374Q288 404 229 303L222 291L189 157Q156 26 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 112 181Q151 335 151 342Q154 357 154 369Q154 405 129 405Q107 405 92 377T69 316T57 280Q55 278 41 278H27Q21 284 21 287Z"/></g><g data-mml-node="mi" transform="translate(2919,0)"><path data-c="1D44E" d="M33 157Q33 258 109 349T280 441Q331 441 370 392Q386 422 416 422Q429 422 439 414T449 394Q449 381 412 234T374 68Q374 43 381 35T402 26Q411 27 422 35Q443 55 463 131Q469 151 473 152Q475 153 483 153H487Q506 153 506 144Q506 138 501 117T481 63T449 13Q436 0 417 -8Q409 -10 393 -10Q359 -10 336 5T306 36L300 51Q299 52 296 50Q294 48 292 46Q233 -10 172 -10Q117 -10 75 30T33 157ZM351 328Q351 334 346 350T323 385T277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q217 26 254 59T298 110Q300 114 325 217T351 328Z"/></g><g data-mml-node="mi" transform="translate(3448,0)"><path data-c="1D465" d="M52 289Q59 331 106 386T222 442Q257 442 286 424T329 379Q371 442 430 442Q467 442 494 420T522 361Q522 332 508 314T481 292T458 288Q439 288 427 299T415 328Q415 374 465 391Q454 404 425 404Q412 404 406 402Q368 386 350 336Q290 115 290 78Q290 50 306 38T341 26Q378 26 414 59T463 140Q466 150 469 151T485 153H489Q504 153 504 145Q504 144 502 134Q486 77 440 33T333 -11Q263 -11 227 52Q186 -10 133 -10H127Q78 -10 57 16T35 71Q35 103 54 123T99 143Q142 143 142 101Q142 81 130 66T107 46T94 41L91 40Q91 39 97 36T113 29T132 26Q168 26 194 71Q203 87 217 139T245 247T261 313Q266 340 266 352Q266 380 251 392T217 404Q177 404 142 372T93 290Q91 281 88 280T72 278H58Q52 284 52 289Z"/></g><g data-mml-node="mo" transform="translate(4020,0)"><path data-c="28" d="M94 250Q94 319 104 381T127 488T164 576T202 643T244 695T277 729T302 750H315H319Q333 750 333 741Q333 738 316 720T275 667T226 581T184 443T167 250T184 58T225 -81T274 -167T316 -220T333 -241Q333 -250 318 -250H315H302L274 -226Q180 -141 137 -14T94 250Z"/></g><g data-mml-node="msub" transform="translate(4409,0)"><g data-mml-node="mi"><path data-c="1D467" d="M347 338Q337 338 294 349T231 360Q211 360 197 356T174 346T162 335T155 324L153 320Q150 317 138 317Q117 317 117 325Q117 330 120 339Q133 378 163 406T229 440Q241 442 246 442Q271 442 291 425T329 392T367 375Q389 375 411 408T434 441Q435 442 449 442H462Q468 436 468 434Q468 430 463 420T449 399T432 377T418 358L411 349Q368 298 275 214T160 106L148 94L163 93Q185 93 227 82T290 71Q328 71 360 90T402 140Q406 149 409 151T424 153Q443 153 443 143Q443 138 442 134Q425 72 376 31T278 -11Q252 -11 232 6T193 40T155 57Q111 57 76 -3Q70 -11 59 -11H54H41Q35 -5 35 -2Q35 13 93 84Q132 129 225 214T340 322Q352 338 347 338Z"/></g><g data-mml-node="mi" transform="translate(498,-150) scale(0.707)"><path data-c="1D456" d="M184 600Q184 624 203 642T247 661Q265 661 277 649T290 619Q290 596 270 577T226 557Q211 557 198 567T184 600ZM21 287Q21 295 30 318T54 369T98 420T158 442Q197 442 223 419T250 357Q250 340 236 301T196 196T154 83Q149 61 149 51Q149 26 166 26Q175 26 185 29T208 43T235 78T260 137Q263 149 265 151T282 153Q302 153 302 143Q302 135 293 112T268 61T223 11T161 -11Q129 -11 102 10T74 74Q74 91 79 106T122 220Q160 321 166 341T173 380Q173 404 156 404H154Q124 404 99 371T61 287Q60 286 59 284T58 281T56 279T53 278T49 278T41 278H27Q21 284 21 287Z"/></g></g><g data-mml-node="mo" transform="translate(5478.7,0)"><path data-c="3D" d="M56 347Q56 360 70 367H707Q722 359 722 347Q722 336 708 328L390 327H72Q56 332 56 347ZM56 153Q56 168 72 173H708Q722 163 722 153Q722 140 707 133H70Q56 140 56 153Z"/></g><g data-mml-node="mfrac" transform="translate(6534.5,0)"><g data-mml-node="mrow" transform="translate(994.8,710)"><g data-mml-node="mi"><path data-c="1D452" d="M39 168Q39 225 58 272T107 350T174 402T244 433T307 442H310Q355 442 388 420T421 355Q421 265 310 237Q261 224 176 223Q139 223 138 221Q138 219 132 186T125 128Q125 81 146 54T209 26T302 45T394 111Q403 121 406 121Q410 121 419 112T429 98T420 82T390 55T344 24T281 -1T205 -11Q126 -11 83 42T39 168ZM373 353Q367 405 305 405Q272 405 244 391T199 357T170 316T154 280T149 261Q149 260 169 260Q282 260 327 284T373 353Z"/></g><g data-mml-node="mi" transform="translate(466,0)"><path data-c="1D465" d="M52 289Q59 331 106 386T222 442Q257 442 286 424T329 379Q371 442 430 442Q467 442 494 420T522 361Q522 332 508 314T481 292T458 288Q439 288 427 299T415 328Q415 374 465 391Q454 404 425 404Q412 404 406 402Q368 386 350 336Q290 115 290 78Q290 50 306 38T341 26Q378 26 414 59T463 140Q466 150 469 151T485 153H489Q504 153 504 145Q504 144 502 134Q486 77 440 33T333 -11Q263 -11 227 52Q186 -10 133 -10H127Q78 -10 57 16T35 71Q35 103 54 123T99 143Q142 143 142 101Q142 81 130 66T107 46T94 41L91 40Q91 39 97 36T113 29T132 26Q168 26 194 71Q203 87 217 139T245 247T261 313Q266 340 266 352Q266 380 251 392T217 404Q177 404 142 372T93 290Q91 281 88 280T72 278H58Q52 284 52 289Z"/></g><g data-mml-node="mi" transform="translate(1038,0)"><path data-c="1D45D" d="M23 287Q24 290 25 295T30 317T40 348T55 381T75 411T101 433T134 442Q209 442 230 378L240 387Q302 442 358 442Q423 442 460 395T497 281Q497 173 421 82T249 -10Q227 -10 210 -4Q199 1 187 11T168 28L161 36Q160 35 139 -51T118 -138Q118 -144 126 -145T163 -148H188Q194 -155 194 -157T191 -175Q188 -187 185 -190T172 -194Q170 -194 161 -194T127 -193T65 -192Q-5 -192 -24 -194H-32Q-39 -187 -39 -183Q-37 -156 -26 -148H-6Q28 -147 33 -136Q36 -130 94 103T155 350Q156 355 156 364Q156 405 131 405Q109 405 94 377T71 316T59 280Q57 278 43 278H29Q23 284 23 287ZM178 102Q200 26 252 26Q282 26 310 49T356 107Q374 141 392 215T411 325V331Q411 405 350 405Q339 405 328 402T306 393T286 380T269 365T254 350T243 336T235 326L232 322Q232 321 229 308T218 264T204 212Q178 106 178 102Z"/></g><g data-mml-node="mo" transform="translate(1541,0)"><path data-c="28" d="M94 250Q94 319 104 381T127 488T164 576T202 643T244 695T277 729T302 750H315H319Q333 750 333 741Q333 738 316 720T275 667T226 581T184 443T167 250T184 58T225 -81T274 -167T316 -220T333 -241Q333 -250 318 -250H315H302L274 -226Q180 -141 137 -14T94 250Z"/></g><g data-mml-node="msub" transform="translate(1930,0)"><g data-mml-node="mi"><path data-c="1D467" d="M347 338Q337 338 294 349T231 360Q211 360 197 356T174 346T162 335T155 324L153 320Q150 317 138 317Q117 317 117 325Q117 330 120 339Q133 378 163 406T229 440Q241 442 246 442Q271 442 291 425T329 392T367 375Q389 375 411 408T434 441Q435 442 449 442H462Q468 436 468 434Q468 430 463 420T449 399T432 377T418 358L411 349Q368 298 275 214T160 106L148 94L163 93Q185 93 227 82T290 71Q328 71 360 90T402 140Q406 149 409 151T424 153Q443 153 443 143Q443 138 442 134Q425 72 376 31T278 -11Q252 -11 232 6T193 40T155 57Q111 57 76 -3Q70 -11 59 -11H54H41Q35 -5 35 -2Q35 13 93 84Q132 129 225 214T340 322Q352 338 347 338Z"/></g><g data-mml-node="mi" transform="translate(498,-150) scale(0.707)"><path data-c="1D456" d="M184 600Q184 624 203 642T247 661Q265 661 277 649T290 619Q290 596 270 577T226 557Q211 557 198 567T184 600ZM21 287Q21 295 30 318T54 369T98 420T158 442Q197 442 223 419T250 357Q250 340 236 301T196 196T154 83Q149 61 149 51Q149 26 166 26Q175 26 185 29T208 43T235 78T260 137Q263 149 265 151T282 153Q302 153 302 143Q302 135 293 112T268 61T223 11T161 -11Q129 -11 102 10T74 74Q74 91 79 106T122 220Q160 321 166 341T173 380Q173 404 156 404H154Q124 404 99 371T61 287Q60 286 59 284T58 281T56 279T53 278T49 278T41 278H27Q21 284 21 287Z"/></g></g><g data-mml-node="mo" transform="translate(2722,0)"><path data-c="29" d="M60 749L64 750Q69 750 74 750H86L114 726Q208 641 251 514T294 250Q294 182 284 119T261 12T224 -76T186 -143T145 -194T113 -227T90 -246Q87 -249 86 -250H74Q66 -250 63 -250T58 -247T55 -238Q56 -237 66 -225Q221 -64 221 250T66 725Q56 737 55 738Q55 746 60 749Z"/></g></g><g data-mml-node="mrow" transform="translate(220,-710)"><g data-mml-node="munder"><g data-mml-node="mo"><path data-c="2211" d="M61 748Q64 750 489 750H913L954 640Q965 609 976 579T993 533T999 516H979L959 517Q936 579 886 621T777 682Q724 700 655 705T436 710H319Q183 710 183 709Q186 706 348 484T511 259Q517 250 513 244L490 216Q466 188 420 134T330 27L149 -187Q149 -188 362 -188Q388 -188 436 -188T506 -189Q679 -189 778 -162T936 -43Q946 -27 959 6H999L913 -249L489 -250Q65 -250 62 -248Q56 -246 56 -239Q56 -234 118 -161Q186 -81 245 -11L428 206Q428 207 242 462L57 717L56 728Q56 744 61 748Z"/></g><g data-mml-node="mi" transform="translate(1089,-285.4) scale(0.707)"><path data-c="1D456" d="M184 600Q184 624 203 642T247 661Q265 661 277 649T290 619Q290 596 270 577T226 557Q211 557 198 567T184 600ZM21 287Q21 295 30 318T54 369T98 420T158 442Q197 442 223 419T250 357Q250 340 236 301T196 196T154 83Q149 61 149 51Q149 26 166 26Q175 26 185 29T208 43T235 78T260 137Q263 149 265 151T282 153Q302 153 302 143Q302 135 293 112T268 61T223 11T161 -11Q129 -11 102 10T74 74Q74 91 79 106T122 220Q160 321 166 341T173 380Q173 404 156 404H154Q124 404 99 371T61 287Q60 286 59 284T58 281T56 279T53 278T49 278T41 278H27Q21 284 21 287Z"/></g></g><g data-mml-node="mi" transform="translate(1549.6,0)"><path data-c="1D452" d="M39 168Q39 225 58 272T107 350T174 402T244 433T307 442H310Q355 442 388 420T421 355Q421 265 310 237Q261 224 176 223Q139 223 138 221Q138 219 132 186T125 128Q125 81 146 54T209 26T302 45T394 111Q403 121 406 121Q410 121 419 112T429 98T420 82T390 55T344 24T281 -1T205 -11Q126 -11 83 42T39 168ZM373 353Q367 405 305 405Q272 405 244 391T199 357T170 316T154 280T149 261Q149 260 169 260Q282 260 327 284T373 353Z"/></g><g data-mml-node="mi" transform="translate(2015.6,0)"><path data-c="1D465" d="M52 289Q59 331 106 386T222 442Q257 442 286 424T329 379Q371 442 430 442Q467 442 494 420T522 361Q522 332 508 314T481 292T458 288Q439 288 427 299T415 328Q415 374 465 391Q454 404 425 404Q412 404 406 402Q368 386 350 336Q290 115 290 78Q290 50 306 38T341 26Q378 26 414 59T463 140Q466 150 469 151T485 153H489Q504 153 504 145Q504 144 502 134Q486 77 440 33T333 -11Q263 -11 227 52Q186 -10 133 -10H127Q78 -10 57 16T35 71Q35 103 54 123T99 143Q142 143 142 101Q142 81 130 66T107 46T94 41L91 40Q91 39 97 36T113 29T132 26Q168 26 194 71Q203 87 217 139T245 247T261 313Q266 340 266 352Q266 380 251 392T217 404Q177 404 142 372T93 290Q91 281 88 280T72 278H58Q52 284 52 289Z"/></g><g data-mml-node="mi" transform="translate(2587.6,0)"><path data-c="1D45D" d="M23 287Q24 290 25 295T30 317T40 348T55 381T75 411T101 433T134 442Q209 442 230 378L240 387Q302 442 358 442Q423 442 460 395T497 281Q497 173 421 82T249 -10Q227 -10 210 -4Q199 1 187 11T168 28L161 36Q160 35 139 -51T118 -138Q118 -144 126 -145T163 -148H188Q194 -155 194 -157T191 -175Q188 -187 185 -190T172 -194Q170 -194 161 -194T127 -193T65 -192Q-5 -192 -24 -194H-32Q-39 -187 -39 -183Q-37 -156 -26 -148H-6Q28 -147 33 -136Q36 -130 94 103T155 350Q156 355 156 364Q156 405 131 405Q109 405 94 377T71 316T59 280Q57 278 43 278H29Q23 284 23 287ZM178 102Q200 26 252 26Q282 26 310 49T356 107Q374 141 392 215T411 325V331Q411 405 350 405Q339 405 328 402T306 393T286 380T269 365T254 350T243 336T235 326L232 322Q232 321 229 308T218 264T204 212Q178 106 178 102Z"/></g><g data-mml-node="mo" transform="translate(3090.6,0)"><path data-c="28" d="M94 250Q94 319 104 381T127 488T164 576T202 643T244 695T277 729T302 750H315H319Q333 750 333 741Q333 738 316 720T275 667T226 581T184 443T167 250T184 58T225 -81T274 -167T316 -220T333 -241Q333 -250 318 -250H315H302L274 -226Q180 -141 137 -14T94 250Z"/></g><g data-mml-node="msub" transform="translate(3479.6,0)"><g data-mml-node="mi"><path data-c="1D467" d="M347 338Q337 338 294 349T231 360Q211 360 197 356T174 346T162 335T155 324L153 320Q150 317 138 317Q117 317 117 325Q117 330 120 339Q133 378 163 406T229 440Q241 442 246 442Q271 442 291 425T329 392T367 375Q389 375 411 408T434 441Q435 442 449 442H462Q468 436 468 434Q468 430 463 420T449 399T432 377T418 358L411 349Q368 298 275 214T160 106L148 94L163 93Q185 93 227 82T290 71Q328 71 360 90T402 140Q406 149 409 151T424 153Q443 153 443 143Q443 138 442 134Q425 72 376 31T278 -11Q252 -11 232 6T193 40T155 57Q111 57 76 -3Q70 -11 59 -11H54H41Q35 -5 35 -2Q35 13 93 84Q132 129 225 214T340 322Q352 338 347 338Z"/></g><g data-mml-node="mi" transform="translate(498,-150) scale(0.707)"><path data-c="1D456" d="M184 600Q184 624 203 642T247 661Q265 661 277 649T290 619Q290 596 270 577T226 557Q211 557 198 567T184 600ZM21 287Q21 295 30 318T54 369T98 420T158 442Q197 442 223 419T250 357Q250 340 236 301T196 196T154 83Q149 61 149 51Q149 26 166 26Q175 26 185 29T208 43T235 78T260 137Q263 149 265 151T282 153Q302 153 302 143Q302 135 293 112T268 61T223 11T161 -11Q129 -11 102 10T74 74Q74 91 79 106T122 220Q160 321 166 341T173 380Q173 404 156 404H154Q124 404 99 371T61 287Q60 286 59 284T58 281T56 279T53 278T49 278T41 278H27Q21 284 21 287Z"/></g></g><g data-mml-node="mo" transform="translate(4271.6,0)"><path data-c="29" d="M60 749L64 750Q69 750 74 750H86L114 726Q208 641 251 514T294 250Q294 182 284 119T261 12T224 -76T186 -143T145 -194T113 -227T90 -246Q87 -249 86 -250H74Q66 -250 63 -250T58 -247T55 -238Q56 -237 66 -225Q221 -64 221 250T66 725Q56 737 55 738Q55 746 60 749Z"/></g></g><rect width="4860.6" height="60" x="120" y="220"/></g><g data-mml-node="mo" transform="translate(11635.1,0)"><path data-c="29" d="M60 749L64 750Q69 750 74 750H86L114 726Q208 641 251 514T294 250Q294 182 284 119T261 12T224 -76T186 -143T145 -194T113 -227T90 -246Q87 -249 86 -250H74Q66 -250 63 -250T58 -247T55 -238Q56 -237 66 -225Q221 -64 221 250T66 725Q56 737 55 738Q55 746 60 749Z"/></g></g></g></svg></mjx-container><br>这东西的本质就像我上面举的“今天晚上我们去打”的例子，就是选出一个最大概率呗。</p><hr><h3 id="第一步：embedding"><a href="#第一步：embedding" class="headerlink" title="第一步：embedding"></a>第一步：embedding</h3><p>embedding 的过程可以简单的理解为向量化。因为输入是一个个的词(token)，那需要把它映射成一个向量，embedding就是给定任何一个词，用一个向量来表示它。</p><p>在 embedding 的过程中，每个 token 都用一个单层神经网络转化为长度为 768(对于GPT-2)或 12288(对于ChatGPT的GPT-3)的 embedding 向量。</p><p>同时，模块中还有一个“辅助通路”(secondary pathway)，用于将 token 的整数位置转化为 embedding 向量。最后，将 token 值和 token 位置的 embedding 向量加在一起，生成最终的 embedding 向量序列。</p><p>那么为什么要将 token 值和 token 位置的 embedding 向量相加呢？只是尝试了各种不同的方法后发现这种方法似乎可行，而且神经网络的本身也认为，只要初始设置“大致正确”，通过足够的训练，通常算法可以自动调整细节。</p><p>以字符串“湖人”为例，在 gpt-2 中它可以将其转化为一系列长度为 768 的 embedding 向量，其中包括从每个 token 的值和位置中提取的信息。</p><style>.hanwsgrrylud{zoom:50%;}</style><img lazyload src="/images/loading.svg" data-src="/2023/10/06/ChatGPT/40c8529d16291e27f2a83f703b82c16b.png" class="hanwsgrrylud" alt="40c8529d16291e27f2a83f703b82c16b.png"><p>第一张图中就是 token embedding，纵向一列代表一个向量，可以看到最先排列的是“湖”所代表的向量，然后是“人”所代表的向量。第二张图是位置的 embedding，代表着这两个字的位置信息。将这两两个 embedding 相加得到了最终的 embedding 序列。</p><h3 id="第二步：Attention"><a href="#第二步：Attention" class="headerlink" title="第二步：Attention"></a>第二步：Attention</h3><p>Attention 是整个 transformer 的主要部分，其内部结构是非常复杂的，我作为一名非专业人士，无法面面俱到的将其中的细节完全解释清楚，因此只能把它的核心能力简单叙述一二。</p><p>在进行 embedding 之后，需要对一系列的“注意力块”进行数据操作(gpt3 中有 96 个注意力块)，而每个“注意力块”中都有一组 attention heads，每个 attention head 都独立地作用于 embedding 向量中不同值的块。</p><p>attention head 的作用就是对历史的 token 序列进行回顾，这里的历史 token 序列就是已经生成的文本，之后将这些信息进行打包，以便找到下一个 token。稍微具体的来说，attention head 的作用是重新组合与不同 token 相关的 embedding 向量的块，并赋予一定的权重。</p><p><strong>举个例子</strong>：</p><p>用 ChatGPT 翻译句子“蚂蚁集团”到“ant group”举例，首先进行上一步 embedding 操作，将句子向量化并吸收句子位置信息，得到一个句子的初始向量组。</p><img lazyload src="/images/loading.svg" data-src="/2023/10/06/ChatGPT/f6781e42fff838887dac39c51faa93c4.png" class title="f6781e42fff838887dac39c51faa93c4.png"><p>由于样本每个句子长短不同，所以每个句子都会是一个 512 x 512 的矩阵，如果长度不够就用 0 来代替。这样在训练时，无论多长的句子，都可以用一个同样规模的矩阵来表示。当然 512 是超参，可以在训练前调整大小。</p><p>接着，用每个字的初始向量分别乘以三个随机初始的矩阵W^Q,W^K,W^V分别得到三个量Qx，Kx，Vx，这样就得到了三个量：Qx，Kx，Vx，比如用“蚂”这个字举例：</p><style>.onzmmgqtmtcn{zoom:50%;}</style><img lazyload src="/images/loading.svg" data-src="/2023/10/06/ChatGPT/af78a744ecc9dcf2b84d5a3e2ab46078.png" class="onzmmgqtmtcn" alt="af78a744ecc9dcf2b84d5a3e2ab46078.png"><p>然后，计算每个单词的 Attention 数值，比如“蚂”字的 Attention 值就是用“蚂”字的 Q蚂Q蚂 分别乘以句子中其他单词的 K 值，两个矩阵相乘的数学含义就是衡量两个矩阵的相似度。</p><h3 id="第三步：将向量转为概率"><a href="#第三步：将向量转为概率" class="headerlink" title="第三步：将向量转为概率"></a>第三步：将向量转为概率</h3><p>继续用上面翻译的例子：用计算出的每个单词的 Attention 值，通过一个 SoftMax 转换(这里不必关注是怎么转换的)，计算出它跟每个单词的权重，这个权重比例所有加在一起要等于 1。再用每个权重乘以相对应的 V 值。所有乘积相加得到这个 Attention 值。</p><style>.wvbalnphputy{zoom:50%;}</style><img lazyload src="/images/loading.svg" data-src="/2023/10/06/ChatGPT/55e5bf98008def23eef58ba85ae0a828.png" class="wvbalnphputy" alt="55e5bf98008def23eef58ba85ae0a828.png"><p>这个 Attention 数值就是除了“蚂”字自有信息和位置信息以外，成功的得到了这个句子中每个单词的相关度信息。</p><p>在计算 Attention 之后，每个单词根据语义关系被打入了新的高维空间，这就是 Self-Attention(自注意力机制)。</p><p>但在 transformer 里，并不是代入了一个空间，而是代入了多个高维空间，叫做 Multi-Head Attention (多头注意力机制)。将高维空间引入模型训练的主要原因是它在训练时表现出很好的效果，这是人工智能科研论文的一个常见特点，研究人员凭借着极高的科研素养和敏感性，发现一些方向，并通过测试证明其有效性，但不一定有完美的理论支持。这为后续研究者提供了进一步完善的余地。</p><p>事实证明，如何提升Attention(Q，K，V)效率是 transformer 领域迭代最快的部分。</p><p>这就是 transformer 的大致原理，有一些细节我个人也没有深入研究，有兴趣的人可以自行去搜索。</p><hr><p><strong>！！！开始夹带私货！！！</strong></p><p>正如王正学长文章中所提到的——“如何提升Attention(Q，K，V)效率是 transformer 领域迭代最快的部分”，我是真的深有同感！之前还没那么多，但真的就是自己开始去跑代码的时候就知道，Attention是有多慢！！！特别是，我做的轻量化网络，就是那种除Self-Attention之外的部分占三分之一，Self-Attention占三分之二，参数量如此，GFLOPs更是如此……</p><p>既然都说到这里了，那我们也来倒腾一下CV领域的Attention呗。</p><style>.napanhrjmlmo{zoom:50%;}</style><img lazyload src="/images/loading.svg" data-src="/2023/10/06/ChatGPT/cc640438c277e692b60b5e6996600585.jpeg" class="napanhrjmlmo" alt="cc640438c277e692b60b5e6996600585.jpeg"><p>将Self-Attention机制引入CV领域相当重要的一篇文献就是《Non-local Neural Networks》。与对文本进行的Transformer类似，只不过由对文本的Embedding操作不再需要了，转而需要对完整的图像进行切块处理和对每一个小块进行位置编码(传统的CNN本来含有位置信息，因此没必要添加额外的位置信息)，接下来的操作和NLP中的Self-Attention大差不差了。</p><style>.tnmlnrnniobr{zoom:50%;}</style><img lazyload src="/images/loading.svg" data-src="/2023/10/06/ChatGPT/618ca0f94d2d55e0bec876e5c4f2604f.png" class="tnmlnrnniobr" alt="618ca0f94d2d55e0bec876e5c4f2604f.png"><p>如图所示的Self-Attention，这可怕的参数量……</p><p>后面还有一篇《CCNet: Criss-Cross Attention for Semantic Segmentation》是对NLNet的优化。强烈推荐看一下<a class="link" href="https://zhuanlan.zhihu.com/p/51393573">CCNet–于”阡陌交通”处超越恺明Non-local <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a>这篇博客，写的也是相当好(主要是标题挺吸引^_^)。</p><style>.fmogurfmtjoy{zoom:50%;}</style><img lazyload src="/images/loading.svg" data-src="/2023/10/06/ChatGPT/bd5342b6599340434ac528d0be5f58d1.png" class="fmogurfmtjoy" alt="bd5342b6599340434ac528d0be5f58d1.png"><h2 id="为什么说ChatGPT打开了潘多拉魔盒"><a href="#为什么说ChatGPT打开了潘多拉魔盒" class="headerlink" title="为什么说ChatGPT打开了潘多拉魔盒"></a>为什么说ChatGPT打开了潘多拉魔盒</h2><p>前置内容说得太多了，终于开始说和标题有关的内容了，再拖拖都成标题党了。</p><p>不过在谈这个话题之前我还是想多说两句。要不是得完成课题作业，我也不至于议论这样的话题，确实以一个本科生的角度去观望整个行业确实有点管中窥豹。不过气氛都烘托到这了，放弃不再执笔也很难受啊！！权当给未来的我剖析当下精神状态的样本吧……</p><p>先引用几位大佬的话来镇镇场。</p><p>微软创始人比尔盖茨曾表示：“ChatGPT的诞生丝毫不亚于个人电脑的诞生。”英伟达创始人黄仁勋也表示：“ChatGPT只是起点，我们正处于AI的iPhone时刻。”谷歌前AI团队成员、deeplearning.ai创始人吴恩达曾在推特上发表言论，认为ChatGPT等大型语言模型是“人工智能领域最为激动人心的进展之一。”</p><p>之所以说ChatGPT打开了潘多拉魔盒是因为其影响足以推动各行各业的变革。</p><p>首先找我来说说作为一个学生，ChatGPT到底给我带来哪些影响。</p><p>ChatGPT代写课程作业(本来这个课程作业就想用它快速解决的)啥的就不多赘述了，不符合核心价值观，更多的想谈一谈ChatGPT对我知识检索习惯的改变。相信大学数学生跟我一样，在没有ChatGPT之前，我们都基于一个问题打开度娘/Google/Bing，然后输出问题的描述性语句，在通过关键词检索的词条中去检索自己需要的答案。正如我描述的一样，这一套流程下来是相当繁琐的，这意味着知识的检索成本相当之高。从这里就会给绝大部人形成一种习惯，或者是一种刻板印象：检索知识的过程是比较繁琐的，不能做到对陌生知识一遇一检。然后我们细化对知识的分类，很多陌生知识的复杂程度是不一样的，比如说你在冲浪时遇到一个陌生概念“NAS”，如果你只是想知道这个名词是个什么东西，那这个陌生知识就很轻量，因此你期待所消耗在这个陌生知识上的时间成本就应该很小，但是在ChatGPT出现之前，你还是得经历上面那个过程。但其实这么轻量的搜索，很多检索系统也能直接在第一个词条给到你想要的答案，但真正迈不出一遇一检的原因其实是你大脑对这个检索过程的定义。</p><p>基于ChatGPT的内容式问答检索，这些轻量的知识检索过程完全可以以极低的时间成本实现，真正能够做到一遇一检。这虽然不能将你的知识网络的深度延伸到一个新的层次，但是拓展你知识网络的阔度是完全没有任何问题的。</p><p>由于我大二暑假做过一个“基于2D虚拟人语音驱动”的项目，通过现有的深度学习技术已经完全可以实现模仿一个虚拟的你出来了，再配合上语音克隆的技术和ChatGPT作为对话的中转站。一个在样貌、声线、甚至是语气与微表情都和你如出一辙的虚拟人完全可以应用到生产生活的方方面面。未来注定是元宇宙的时代！</p><p>而ChatGPT对一些传统行业的打击也同样是致命的，如翻译(包括同声翻译)、在线客服等，甚至我在想，ChatGPT结合具身智能在不远的将来能否替代掉那些以时间量度作为价值换算的行业。</p><p>那么说回程序员，本来这个职业还是有蛮高的技术门槛的，一门编程语言的学习，以及其周边技术的掌握确实都是需要时间花费的。而矛盾的是，一般的人想要用到编程这项技术往往只是希望用它来减轻一些重复工作，他们是没有强烈的通过编程生产变现需求的。因此对于他们来说编程只是工具不是目的，如果可以以其他方式达到他们的目的，这个工具是可有可无的。ChatGPT的出现，完全满足了这一需求。一个脚本、一个前端界面、一个数据处理分析程序，这种轻量的编程需求，ChatGPT完全可以胜任。因此无形之中，编程好像也变得不再是程序员的专利，行业的门槛无形之中就被连根拔起。</p><p>当然对于程序员来说，这个工具的出现也是利好的。只是从我的经历看来，我完全可以将重心全放在业务功能的如何实现上，而不用太多地去考虑如何用代码实现。现在基于ChatGPT的代码补全工具也层出不穷，虽然我体验下来也还是感觉没多大作用，但我们也还是需要给这项技术足够的包容，毕竟它刚出来还不到一年。</p><p>写到这里，实在江郎才尽了！Happy Ending~</p><h2 id="参考文章-文献"><a href="#参考文章-文献" class="headerlink" title="参考文章/文献"></a>参考文章/文献</h2><ol><li><a class="link" href="https://juejin.cn/post/7277802797473841186">让非算法同学也能了解 ChatGPT 等相关大模型 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a></li><li><a class="link" href="https://zhuanlan.zhihu.com/p/370859857">教你深入理解“预训练” <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a></li><li><a class="link" href="https://arxiv.org/abs/1706.03762">Attention Is All You Need <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a></li><li><a class="link" href="https://zhuanlan.zhihu.com/p/338817680">Transformer模型详解（图解最完整版） <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a></li></ol>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h2&gt;&lt;p&gt;作为一名非专精于NLP算法的同学，去详细论述ChatGPT的底层原理是很困难的。但好在最近为了搭建一个轻量化的RGM(Robotic Gra</summary>
      
    
    
    
    <category term="Notes" scheme="https://gme-hong.github.io/categories/Notes/"/>
    
    
    <category term="AI" scheme="https://gme-hong.github.io/tags/AI/"/>
    
  </entry>
  
</feed>
