<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Forever丶CIL</title>
  
  <subtitle>QwQ</subtitle>
  <link href="http://www.fcayh.cn/atom.xml" rel="self"/>
  
  <link href="http://www.fcayh.cn/"/>
  <updated>2023-02-15T11:16:23.108Z</updated>
  <id>http://www.fcayh.cn/</id>
  
  <author>
    <name>FcAYH</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>ImageUpdateTool 开发经历</title>
    <link href="http://www.fcayh.cn/2023/02/15/ImageUpdateTool-development-exprience/"/>
    <id>http://www.fcayh.cn/2023/02/15/ImageUpdateTool-development-exprience/</id>
    <published>2023-02-15T10:08:52.000Z</published>
    <updated>2023-02-15T11:16:23.108Z</updated>
    
    <content type="html"><![CDATA[<h1 id="ImageUpdateTool-开发经历"><a href="#ImageUpdateTool-开发经历" class="headerlink" title="ImageUpdateTool 开发经历"></a>ImageUpdateTool 开发经历</h1><p><a href="https://github.com/FcAYH/ImageUpdateTool">Github仓库链接</a></p><h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>有时候我们需要在博客中插入图片，这个时候我们需要利用图床来存储这些在线的图片，此时通常有三种方式：</p><ol><li><p>直接花钱购买，例如阿里云OOS存储服务器，七牛云等。一般新用户会有一些免费的额度可以白嫖。</p></li><li><p>如果自己部署博客有自行部署/购买服务器，那可以用自己的服务器做个图床出来。</p></li><li><p>用Github仓库作图床。（纯白嫖，不用花钱，缺点是国内访问速度慢且不稳定）</p></li></ol><p>这里我用的Github仓库做的图床。最开始我也是使用的PicGo作为上传图片的工具，但是在使用了一段时间后感觉到了几个问题：</p><ol><li><p>应该是网络的问题，上传成功率没有保障。经常拖进去图片之后等半天没反应，然后反复托进去图片，过很久可能一下子传上去好几个。</p></li><li><p>图片都在根目录下面，不便于管理。我想在上传的时候根据日期建立目录。</p></li><li><p>不方便查看过去上传的全部图片，只能看最近上传的一部分。时间久了以前上传的图就看不到了。这样如果我再写文章需要用到过去的图，想要获取URL链接就会比较麻烦。</p></li></ol><p>当然以上问题可能是因为我PciGo用的不专业。但我还是决定自己做个软件实现我的需求。</p><h2 id="技术选型"><a href="#技术选型" class="headerlink" title="技术选型"></a>技术选型</h2><p>我思考了一下我的需求和使用场景，总结如下：</p><ol><li><p>我基本上没有跨平台的需求，我只有windows的设备。但是支持跨平台的话，也许以后可以继续拓展这个软件，例如以后在手机上看到喜欢的图可以快速传到图床上存下来，或者以后可能买Mac做开发等。</p></li><li><p>最好可以有较快的启动速度和较低的内存占用，使得我能够将这个软件一直挂在后台。</p></li><li><p>界面尽可能美观一点。</p></li></ol><p>而我“会”的开发框架如下：</p><ol><li><p>Wpf</p></li><li><p>MAUI</p></li><li><p>Unity</p></li><li><p>Qt</p></li><li><p>Electron</p></li></ol><blockquote><p>注：玩过 = 会 🤣 其实这几个我都只停留在做过一些简单的项目的层次上，比如图书管理系统，画板，扫雷，贪吃蛇等</p></blockquote><p>其中wpf没有跨平台能力，基本上可以被MAUI替代。Electron内存占用比较大，启动速度也比较慢（我肯定还不具备什么优化能力）。Unity的话是一个备选方案，有一说一Unity的UGUI非常好用，做一些小软件开发其实非常快的。但是它的逻辑是游戏的那一套思路，比如一直在不停的刷新，那可能对笔记本来说耗电会比较多，我不确定常驻后台是不是影响有点大，所以没选择Unity。Qt则是我三年前学的东西了，好久没用是一个方面，C++本身开发速度估计也比较慢的，想了想放弃了。</p><p>最后决定就是MAUI了，这是一个很新的框架，我感觉我这个软件涉及的内容也不是特别复杂，应该不会遇到特别多奇怪的坑。（当然还是踩了好久的坑）</p><h2 id="简单介绍MAUI"><a href="#简单介绍MAUI" class="headerlink" title="简单介绍MAUI"></a>简单介绍MAUI</h2><p><a href="https://learn.microsoft.com/zh-cn/dotnet/maui/what-is-maui?view=net-maui-7.0">什么是MAUI？</a></p><p>这是微软开发并维护的新一代跨平台UI框架（跨平台：Linux？？）。</p><p>其使用Xaml文件去定义静态的UI，使用C#代码完成动态的部分以及其功能（类似html与js）。同时Style也在Xaml中定义。</p><h2 id="开工？坑？"><a href="#开工？坑？" class="headerlink" title="开工？坑？"></a>开工？坑？</h2><p>在完成了Microsoft Learn中的六个MAUI入门教程后，我就开始了软件的开发工作。</p><h3 id="怎么获取路径"><a href="#怎么获取路径" class="headerlink" title="怎么获取路径"></a>怎么获取路径</h3><p>我遇到的第一个“坑”是路径问题。</p><p>最开始我找了<code>Environment</code>这个静态类，从中可以获取一些路径信息，例如:</p><figure class="highlight csharp"><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">Environment.CurrentDirectory <span class="comment">// C:\WINDOWS\system32</span></span><br><span class="line">Environment.ProcessPath <span class="comment">// 项目所在路径\ImageUpdateTool\bin\Debug\net7.0-windows10.0.19041.0\win10-x64\AppX\ImageUpdateTool.exe</span></span><br><span class="line">Environment.SystemDirectory <span class="comment">// C:\WINDOWS\system32</span></span><br><span class="line">Environment.GetFolderPath(SpecialFolder folder, SpecialFolderOption option)</span><br><span class="line"><span class="comment">// 根据SpecialFolder这个枚举，可以获取文档、桌面、ProgramFiles等特殊文件夹的路径</span></span><br></pre></td></tr></table></figure><p>当我想要在<code>C:\User\&#123;UserName&#125;\AppData\</code>之中做个文件夹存放软件的数据时，我使用<code>SpecialFolder.LocalApplicationData</code>和<code>SpecialFolder.ApplicationData</code>分别获取到了Local和Roaming这两个文件夹的路径。但当我尝试去创建文件夹时，我发现根本就没创建出来任何文件夹。经过一会研究后，我发现是因为MAUI会将这些路径重定向到<code>\Local\Packages\&#123;GUID&#125;\LocalCache</code>和<code>\Local\Packages\&#123;GUID&#125;\LocalState</code>这两个文件夹中。并且通过<code>FileSystem.CacheDirectory</code>和<code>FileSystem.AppDataDirectory</code>进行访问。</p><p>最开始我对此感觉比较迷惑，因为这对于用户来说想要查看应用的一些数据太过于麻烦了。GUID是很长的一串十六进制数，并且Packages文件夹下面有很多这样的子目录，那我怎么记得住每个个程序的GUID是啥。不过后来在 Stackoverflow 上与大佬们交流了一下之后，我能够从两点理解，其一是不同的程序可能有相同的名字，但不可能有相同的GUID；其二是微软在这方面的设计就是这样子，不希望用户直接去访问应用的这些数据。如果有访问的需要，也应该是应用程序提供一个访问/编辑的界面。</p><h4 id="关于新老文件系统思路的探讨"><a href="#关于新老文件系统思路的探讨" class="headerlink" title="关于新老文件系统思路的探讨"></a>关于新老文件系统思路的探讨</h4><p>这里插播一个题外话，可能是因为从小在Windows上玩游戏和使用破解软件的经历（比如经常需要直接将应用程序目录下的一部分文件用另一部分破解文件替换掉），使得我内心惯性的认为能够去访问这些文件，才是合理的。所以我所意识到的文件系统就类似这样子：每个应用分三部分存储，应用本体，用户数据，系统注册项。其中应用本体我们可以自己选择安装的位置，默认是Program Files，你也可以改到D盘等地方。用户数据则是操作系统不同用户使用该程序所产生的相互独立的数据，被分别存储在每个用户的AppData中。如果仅想为某个用户安装应用时，则将应用本体和用户数据放在一起，均在AppData下。系统注册项则是一个键值对，用于告诉操作系统有这个应用的存在。</p><p>Windows中提供了Pictures、Videos、Musics、Documents等文件夹，其目的我想就是希望可以用一个统一的方式去访问机器中的某一类型文件，这一点在手机上非常常见。但是很显然这个方式到目前为止还不成熟，我举个很简单的例子，我在微信上下载了一个文件，我从QQ上想要将其分享给其他人，我从文档中并看不到微信下载的内容。最后我还是需要一点点的去扒微信存储文档的目录，才能找到文件。在Windows上这个操作很容易，因为我安装微信的时候，我自己知道我把微信装在哪里了，我知道下载的内容去FileRecv这个文件夹查看。但是在手机中，我根本不知道微信被装到哪里了。所以最后我一般会选择用office打开那个文档，另存为到手机根目录下，这样我可以很容易的找到这个文档。</p><p>这种文件管理方式，对于移动端用户来说是方便了，也更容易让小白上手（毕竟对于一个不太懂计算机的人来说，你让他选择软件装在哪里，他都不知道该如何操作）。但也确实存在一些未解决的问题，包括软件的多开，比如在Windows上我可以登录10个qq，但是手机上最多双开。比如上面讲的文件在多个App中传递的问题，我也是经常被身边的人问到。Windows为MAUI采取这样的管理方式，也许是一种UWP的遗留，也许是为了跨平台而考虑的。不好说这两种文件管理方式哪个更优，只能说我更习惯用传统Windows的管理思路。</p><blockquote><p>以上仅为个人胡思乱想，可能存在错误</p></blockquote><h3 id="查看是否安装Git"><a href="#查看是否安装Git" class="headerlink" title="查看是否安装Git"></a>查看是否安装Git</h3><p>因为本软件的运行是依赖Git的，所以在运行程序之前，要先检测一下机器中是否已经安装了Git。因为当时我注册了一个ChatGPT的账号，所以这里我直接问的ChatGPT  🤣。</p><ol><li>检测注册表</li></ol><figure class="highlight csharp"><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="built_in">string</span> gitKey = <span class="string">&quot;SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Git_is1&quot;</span>;</span><br><span class="line">Microsoft.Win32.RegistryKey key = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(gitKey);</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (key == <span class="literal">null</span>) <span class="comment">// 未安装Git</span></span><br></pre></td></tr></table></figure><ol><li>检测环境变量</li></ol><figure class="highlight csharp"><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"><span class="built_in">string</span> path = Environment.GetEnvironmentVariable(<span class="string">&quot;Path&quot;</span>);</span><br><span class="line"><span class="keyword">if</span> (!path.Contains(<span class="string">&quot;Git\\cmd&quot;</span>)) <span class="comment">// 未安装Git</span></span><br></pre></td></tr></table></figure><p>通过注册表，我们可以确定是否安装了Git，但是其实我们并不知道Git的安装路径（虽然默认路径是<code>C:\Program Files\Git</code>，但显然安装时我们是可以修改它的）。不过如果用户已经将该路径写入环境变量了，我们就一定可以仅通过”git”指令就能调用到git程序。所以其实仅需在程序中检测一下环境变量中存不存在”Git\\cmd”这个路径就ok了。</p><blockquote><p>这里有个小坑，在Windows下，‘\\’‘/’均可以分割路径(默认是‘\\’)，所以如果用户自己手动在环境变量中敲入”C:/Program Files/Git/cmd”那我们的<code>Contains(&quot;Git\\cmd&quot;)</code>是无法匹配到这里的”Git/cmd”的，所以最好写两遍，把这两个都匹配一下，能匹配到一个就说明安装好了Git。</p><p>不过问题不大，Git的安装程序是会自己将自己写入环境变量的，并且是用的Windows的反斜杠‘\\’。</p></blockquote><h3 id="TreeView"><a href="#TreeView" class="headerlink" title="TreeView"></a>TreeView</h3><p>MAUI中的控件都比较基础，当然啦通过这些基础控件我们可以进行无数的组合。但是有些时候进行组合这个工作也是有一定难度的（自己造轮子）。所以我这里直接引入了<a href="https://enisn-projects.io/docs/en/uranium/latest">Uraniumui</a>库。这个库不仅提供了一整套比较美观的颜色主题，为每个控件设计了各种状态下的配色；还额外开发了几个常用的控件，例如TreeView，Divider等。</p><p>不过Uraniumui中的TreeView的可定制性仍然不够强大，例如仅能点击”&gt;”按钮进行展开或折叠，触发区域比较小。但是我们可以在他的Item部分做文章，来实现我们的各种点击效果。所以它仍然是足够优秀的。</p><h4 id="Button的文本限制太死了"><a href="#Button的文本限制太死了" class="headerlink" title="Button的文本限制太死了"></a>Button的文本限制太死了</h4><p>在这个阶段我遇到的困扰我最久的问题是MAUI中Button文本只能居中显示，具体如下图所示：</p><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images//2023/02/14/77abf1358085c0756974d381305fc588.png" alt="四种TreeView"></p><p>在图1中，可以看到文字和Icon是居中显示的，但是在TreeView中居中展示文本不整齐不好看。在图2中，虽然Icon和文字左对齐了，但是点击区域却只能跟着文本长度走，标题短，那点击区域就小；标题长，点击区域就长。这也不是我想要的，（注：这里Icon和文本在每个Button中其实还是居中对齐，只不过没有限定Button的宽度）。图3则是VScode这个软件的TreeView效果，可以看到整个横行都可以点击，看上去也很美观。图4则是经过我一番折腾实现的效果，实现了与VScode类似的样式。</p><p>MAUI中的UI逻辑与我想象中的不同，所以在我发现官方没有提供为Button修改文字对齐的属性时，下意识的想的就是自己做一个Button出来。因为在UGUI中，想Button这样的组件其实原理就是若干原始组件的组合，比如一个触发器接收各种点击事件，一个SpriteRender展示背景，一个Label显示文字等等。可能MAUI也是类似的思路，但是在自定义上我还没找到切入点，对着源码看了一段时间自己尝试去写一个<code>public class TreeViewButton : Label</code>，让其实现一些接口来获得点击功能。当然最后试了半天也没成功。</p><p>最后采取的思路是控件叠加，我在下面放一个没有文本的Button，再从其上面放一个Label，大功告成  🤣。</p><figure class="highlight xml"><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="tag">&lt;<span class="name">material:TreeView</span> <span class="attr">x:Name</span>=<span class="string">&quot;FolderTree&quot;</span> <span class="attr">Spacing</span>=<span class="string">&quot;5&quot;</span> <span class="attr">Padding</span>=<span class="string">&quot;5&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">material:TreeView.ItemTemplate</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">DataTemplate</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">Grid</span> <span class="attr">RowDefinitions</span>=<span class="string">&quot;20&quot;</span> </span></span><br><span class="line"><span class="tag">                    <span class="attr">ColumnDefinitions</span>=<span class="string">&quot;25, 30, 141&quot;</span> </span></span><br><span class="line"><span class="tag">                    <span class="attr">HorizontalOptions</span>=<span class="string">&quot;Fill&quot;</span></span></span><br><span class="line"><span class="tag">                    <span class="attr">Margin</span>=<span class="string">&quot;-19, 0, 0, 0&quot;</span>&gt;</span></span><br><span class="line"><span class="comment">&lt;!--这个-19是让Item部分向左偏移一部分距离，让Button的点击范围盖住&#x27;&gt;&#x27;按钮，使得一行看上去是一个整体--&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">Label</span> <span class="attr">x:Name</span>=<span class="string">&quot;FolderIcon&quot;</span> <span class="attr">Text</span>=<span class="string">&quot;📁&quot;</span></span></span><br><span class="line"><span class="tag">                        <span class="attr">Margin</span>=<span class="string">&quot;0&quot;</span></span></span><br><span class="line"><span class="tag">                        <span class="attr">Grid.Row</span>=<span class="string">&quot;0&quot;</span> <span class="attr">Grid.Column</span>=<span class="string">&quot;1&quot;</span>/&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">Label</span> <span class="attr">Text</span>=<span class="string">&quot;&#123;Binding Name&#125;&quot;</span></span></span><br><span class="line"><span class="tag">                        <span class="attr">FontAttributes</span>=<span class="string">&quot;Bold&quot;</span></span></span><br><span class="line"><span class="tag">                        <span class="attr">VerticalOptions</span>=<span class="string">&quot;Start&quot;</span></span></span><br><span class="line"><span class="tag">                        <span class="attr">Grid.Row</span>=<span class="string">&quot;0&quot;</span> <span class="attr">Grid.Column</span>=<span class="string">&quot;2&quot;</span>/&gt;</span></span><br><span class="line"><span class="comment">&lt;!--为了让每个Button的点击区域的右边界相同，需要在初始化Item的时候手动计算并设置ButtonWidthRequest--&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">Button</span> <span class="attr">HorizontalOptions</span>=<span class="string">&quot;Start&quot;</span></span></span><br><span class="line"><span class="tag">                        <span class="attr">Padding</span>=<span class="string">&quot;0&quot;</span></span></span><br><span class="line"><span class="tag">                        <span class="attr">StyleClass</span>=<span class="string">&quot;TextButton&quot;</span></span></span><br><span class="line"><span class="tag">                        <span class="attr">HeightRequest</span>=<span class="string">&quot;20&quot;</span></span></span><br><span class="line"><span class="tag">                        <span class="attr">WidthRequest</span>=<span class="string">&quot;&#123;Binding ButtonWidthRequest&#125;&quot;</span></span></span><br><span class="line"><span class="tag">                        <span class="attr">CornerRadius</span>=<span class="string">&quot;3&quot;</span></span></span><br><span class="line"><span class="tag">                        <span class="attr">Clicked</span>=<span class="string">&quot;FolderButton_Clicked&quot;</span></span></span><br><span class="line"><span class="tag">                        <span class="attr">Grid.Row</span>=<span class="string">&quot;0&quot;</span> <span class="attr">Grid.Column</span>=<span class="string">&quot;0&quot;</span> <span class="attr">Grid.ColumnSpan</span>=<span class="string">&quot;3&quot;</span></span></span><br><span class="line"><span class="tag">                        /&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">Grid</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">DataTemplate</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">material:TreeView.ItemTemplate</span>&gt;</span></span><br></pre></td></tr></table></figure><p>这个方案下，点击区间的左端会随着树形图层级而缩进，但是右端保持对齐，这样更有种层级感。</p><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images//2023/02/14/39ba6a07a022413aa353331e20423364.png" alt="点击区域随层级缩进"></p><p>如果想要做出和VScode那样子点击区域从头贯穿到尾的效果，只需要在Grid的Margin属性下手就好了，每次计算一下要向左偏移多少。</p><p>不过我这个还是做的不够好，<code>ButtonWidthRequest</code>这个属性，默认宽度是200，然后根据Item的层级深度，每深一层减少10长度。这样子有两个坏处，一是屏幕比较大的时候，我也只有宽为200的点击区域，不会自适应的变大；二是当我把窗口缩小时，其实点击范围的宽度不会跟着变小，而仅仅是因为TreeView所在的Grid列宽度缩小，展示不全了，效果就是左侧是有圆角的，右侧变成直角了（被截断了）。</p><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images//2023/02/14/1c8bbfa6da41ad5411828241e3c922a8.png" alt="左右两侧不同了"></p><blockquote><p>注：目前想到的方案是，让TreeView也关注页面的<code>SizeChanged</code>的事件，在发生SizeChanged时，重新计算一下每个Button的宽度。但是我认为这个方案性能很差，不如这样子：取消Button的圆角，然后默认长度设置的更长一些，这样子就看不出来被截断了，还不影响性能。然后就是可能有人会想到直接让Button的Horizontal属性是Fill不就行了，这个我尝试过，因为Button的Text是空的，所以即便设置了Fill，依然只有一点点大小，不能填充全部。</p></blockquote><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images//2023/02/14/4114df4d0dd37c22d951652b91491c1e.png" alt="设置Fill属性也不行"></p><h2 id="后续要解决的问题"><a href="#后续要解决的问题" class="headerlink" title="后续要解决的问题"></a>后续要解决的问题</h2><h3 id="异步"><a href="#异步" class="headerlink" title="异步"></a>异步</h3><p>第一次进入软件，配置好设置后，点击Apply。此时软件就会卡住，这自然是因为程序正在后台clone仓库。因为这个流程是通过<code>Process.Start</code>去开启一个git线程，然后我直接使用<code>WaitForExit()</code>，所以这个时候前台UI线程就被阻塞了。后续会采用异步的方式，在前台显示一个进度条，或者“加载”样式的图标，告诉用户现在后台正在执行程序，请耐心等待。</p><p>同样的，点击Select Image后，软件也会卡住一会，这里也需要做同样的异步处理。</p><h3 id="更方便的使用"><a href="#更方便的使用" class="headerlink" title="更方便的使用"></a>更方便的使用</h3><p>现在要想上传一个图片，需要点击Select Image，在MediaPicker窗口中选择图片。显然这仍然不够方便，如果我可以直接拖一个图片进来，或者直接将剪切板的图片复制过来就上传，那才叫方便。</p><h3 id="更方便的查看图片"><a href="#更方便的查看图片" class="headerlink" title="更方便的查看图片"></a>更方便的查看图片</h3><p>目前来说，在软件中确实可以查看所有图片的缩略图。但是缩略图未免有点太小了（固定了150×120的尺寸），后续应当加一个点击图片就可以放大详细查看一个图片。同时虽然我提供了一个Open Repository Folder按钮来打开仓库文件夹，但是如果想要在文件资源管理器中找到某个特定的图片，还是需要深入点击多层文件夹，后续应当为图片提供一个“在文件资源管理器中打开”的选项，也许可以为图片做一个右键菜单来加入这个功能。</p><h3 id="更方便的管理图片"><a href="#更方便的管理图片" class="headerlink" title="更方便的管理图片"></a>更方便的管理图片</h3><p>显然，对于这个图床来说，目前是“只进不出”。只有上传图片的功能，缺少了移除图片的功能。这样如果我搞错了，传错了文件，仍需要手动去移除它。后续将会加入删除图片的功能，这个功能也许也可以放在右键菜单中。</p><h3 id="更多细节"><a href="#更多细节" class="headerlink" title="更多细节"></a>更多细节</h3><ol><li><p>目前导航栏中浮出控件的Icon还都没做颜色主题的适配；</p></li><li><p>缺少语言切换功能，至少要支持中/英文切换；</p></li><li><p>直接为图片类型文件注册右键菜单，这样都不用打开软件界面，就能完成图片上传</p></li><li><p>在设置中添加可以允许开机自启功能</p></li><li><p>……</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>虽然只是一个很简单的软件开发，但是在这个过程中也学到了很多东西。不过软件整体还不够优秀，比如架构上代码写的比较随意，后续应该系统学习一下MVVM，依赖注入等等内容。再接再厉吧。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;ImageUpdateTool-开发经历&quot;&gt;&lt;a href=&quot;#ImageUpdateTool-开发经历&quot; class=&quot;headerlink&quot; title=&quot;ImageUpdateTool 开发经历&quot;&gt;&lt;/a&gt;ImageUpdateTool 开发经历&lt;/h1&gt;&lt;</summary>
      
    
    
    
    <category term="软件开发" scheme="http://www.fcayh.cn/categories/%E8%BD%AF%E4%BB%B6%E5%BC%80%E5%8F%91/"/>
    
    
    <category term="C#" scheme="http://www.fcayh.cn/tags/C/"/>
    
    <category term="dotNet" scheme="http://www.fcayh.cn/tags/dotNet/"/>
    
    <category term="MAUI" scheme="http://www.fcayh.cn/tags/MAUI/"/>
    
  </entry>
  
  <entry>
    <title>在Unity中实现体素化</title>
    <link href="http://www.fcayh.cn/2022/12/23/voxelization/"/>
    <id>http://www.fcayh.cn/2022/12/23/voxelization/</id>
    <published>2022-12-23T05:56:07.000Z</published>
    <updated>2023-02-25T16:32:33.423Z</updated>
    
    <content type="html"><![CDATA[<h1 id="在Unity中实现体素化"><a href="#在Unity中实现体素化" class="headerlink" title="在Unity中实现体素化"></a>在Unity中实现体素化</h1><h2 id="体素化"><a href="#体素化" class="headerlink" title="体素化"></a>体素化</h2><p>类似与用网格存储二维平面，将三维空间划分成大量尺寸相同的小方块的过程就称之为体素化。</p><h3 id="为什么要体素化"><a href="#为什么要体素化" class="headerlink" title="为什么要体素化"></a>为什么要体素化</h3><blockquote><p>以下是个人理解</p></blockquote><ol><li>当场景中多边形(Polygon)数量众多且相互没什么联系时(称其为Polygon Soup)，我们在计算处理起来会比较困难。如下图中有三个凌乱的三角形，它们相互有一些相交，同时也形成了一些小的狭缝。这些都会带来较大的计算量（比如重叠的区域要做一些判断/重复计算、小的接缝可能还有一些精度上的问题）。而将其转换为网格（体素）后，虽然折损了很多精度（可以通过控制体素的大小控制精度），但是大大简化了后续的计算。</li></ol><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images//2022/12/23/373f8ccb59cd98fc0cdad009f8f00a2b.png" alt="Polygon soup" style="zoom: 30%"></p><ol><li><p>易于处理动态生成的物体。比如像RTS游戏中玩家可以在游戏中建造很多建筑，动态的产生了很多障碍物。如果我们是用体素存储的世界，那么我们将建筑物体素化后直接标记对应的体素为不可通过即可。</p></li><li><p>对于一部分游戏类型（比如RTS）可能到体素化这一步就已经用起来很方便了。但是为了能够支持更大的地图，其实是需要利用体素化得到的数据去生成NavMesh。</p></li></ol><h2 id="体素存储方案"><a href="#体素存储方案" class="headerlink" title="体素存储方案"></a>体素存储方案</h2><h3 id="Dense-Array"><a href="#Dense-Array" class="headerlink" title="Dense Array"></a>Dense Array</h3><p>最简单的一种存储方式，即用数组记录每个体素的数据。例如创建三维数组 <code>VoxelState[][][] Voxels;</code> 这种方式非常暴力，需要消耗大量内存。但是优势是实现容易，且修改、查询的效率都非常高。</p><p>若用<code>voxelXNum, voxelYNum, voxelZNum</code>分别记录在<code>x, y, z</code>三个方向上的体素的数量，记总体素数量<code>voxelCount = voxelXNum * voxelYNum * voxelZNum</code>。则我们也可以使用一个一维数组<code>VoxelState[] Voxels</code>来存储，此时第<code>(i, j, k)</code>个体素存储的位置为<code>index = i * voxelYNum * voxelZNum + j * voxelZNum + k</code>，即<code>Voxels[index]</code>。</p><p>如果我们只需要存储一个体素是否被占用，即只有<code>0|1</code>两种状态，可以利用状态压缩的思路在一定程度上优化内存的使用量。首先假设我们开一个<code>Bool[] Voxels</code>来存储体素，需要开一个大小为<code>voxelCount</code> 的 <code>bool</code>数组。由于<code>bool</code>类型大小为1字节，故而共占用内存 <code>voxelCount</code> 字节。但是如果我们把数组中相邻的32个元素用一个<code>unsigned int</code>存储，那我们只需要<code>voxelCount / 32 * 4 = voxelCount / 8</code> 字节。这样就在一定程度上节省了空间。此时第<code>(i, j, k)</code>个体素存储的位置为<code>index = (i * voxelYNum * voxelZNum + j * voxelZNum + k) &gt;&gt; 5</code> ，但是这个位置存的是一个32位的无符号数，而体素<code>(i, j, k)</code>存在这个数的第<code>bit</code>位，其中<code>bit = (i * voxelYNum * voxelZNum + j * voxelZNum + k) % 32</code>。</p><p>在下图中画出了将一维<code>bool</code>数组每8位压缩成一个<code>unsigned int</code>存储的示意。那么每32位去做压缩也是一个原理。</p><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images//2022/12/23/10e60ca8e89028da4d980fe028bfe140.png" alt="bool数组压缩方法"></p><p>此时如果我们想要查找原数组第<code>i</code>位的值，其实就是查找压缩后数组第 <code>i / 8</code>  位的值的第<code>i % 8</code>位。我们可以用按位与<code>&amp;</code>操作去查：<code>_voxels[i / 8] &amp; (1 &lt;&lt; (7 - i % 8));</code></p><p>不过也可以将每个8位反过来存，这样就可以写成如下：<code>_voxels[i / 8] &amp; (1 &lt;&lt; (i % 8));</code> 在下面的代码中，我就是运用的这种方式。</p><figure class="highlight csharp"><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"><span class="doctag">///</span> <span class="doctag">&lt;summary&gt;</span></span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> 设置体素(i, j, k) 的状态为 state</span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;/summary&gt;</span></span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;param name=&quot;state&quot;&gt;</span>true -&gt; 标记体素被占用，false -&gt; 标记体素取消占用<span class="doctag">&lt;/param&gt;</span></span></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">SetVoxelState</span>(<span class="params"><span class="built_in">int</span> x, <span class="built_in">int</span> y, <span class="built_in">int</span> z, <span class="built_in">bool</span> state</span>)</span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="built_in">int</span> originalIndex = x * _voxelYNum * _voxelZNum + y * _voxelZNum + z;</span><br><span class="line"><span class="built_in">int</span> compressedIndex = originalIndex &gt;&gt; <span class="number">5</span>; <span class="comment">// 对应上文中的index</span></span><br><span class="line"><span class="built_in">int</span> offset = originalIndex - (compressedIndex &lt;&lt; <span class="number">5</span>); <span class="comment">// 对应上文中的bit</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (state)</span><br><span class="line">&#123;</span><br><span class="line">_voxels[compressedIndex] |= (uint)(<span class="number">1</span> &lt;&lt; offset);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">&#123;</span><br><span class="line">_voxels[compressedIndex] &amp;= ~(uint)(<span class="number">1</span> &lt;&lt; offset);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Solid-Height-Field"><a href="#Solid-Height-Field" class="headerlink" title="Solid Height Field"></a>Solid Height Field</h3><p>虽然我们用压缩相邻32位的方式，节省了一点点内存。但是在地图很大的情况下，其内存消耗依然不容乐观。不过我们很容易想到，地图上有大量的空的地方（尤其是半空中），我们没必要全都为其记录体素，我们只记录有障碍的地方即可。由此我们可以想到，以平面上的每个体素为头，向上建立链表，连接起来所有为障碍物的体素。</p><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images//2022/12/23/e52be5ea1d6cf7d83164adf3003ecfeb.png" alt="Solid Height Field示意图"></p><p>这个方法呢，能很大程度上节省内存空间，不过每次访问的时候要从下向上去遍历链表，算是用时间换空间了。</p><h3 id="Compact-Height-Field"><a href="#Compact-Height-Field" class="headerlink" title="Compact Height Field"></a>Compact Height Field</h3><p>这个方法的思路是，只记录可以行走的体素，而丢弃掉不可行走的体素。</p><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images//2022/12/23/db28ff67d4d95bcaa42d88b284f304fb.png" alt="Compact Height Field示意图"></p><p>这个方式在寻路上会有较快的效率，因为所有记录的体素都是可行走的。不过在处理加入新障碍物然后进行修改，以及不同大小的单位寻路会复杂度高一些。</p><p>在本文中，我们采用Dense Array来存储体素。</p><h2 id="在Unity中获取Mesh数据"><a href="#在Unity中获取Mesh数据" class="headerlink" title="在Unity中获取Mesh数据"></a>在Unity中获取Mesh数据</h2><h3 id="顶点和三角面"><a href="#顶点和三角面" class="headerlink" title="顶点和三角面"></a>顶点和三角面</h3><blockquote><p>Unity文档 <a href="https://docs.unity.cn/ScriptReference/MeshFilter-mesh.html">Mesh</a></p></blockquote><p>在Unity中，组件<code>MeshFilter</code>记录了物体所使用的Mesh，我们可以利用如下方式获取到：</p><figure class="highlight csharp"><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="comment">// go -&gt; 场景中的一个gameObject</span></span><br><span class="line"><span class="keyword">var</span> mf = go.GetComponent&lt;MeshFilter&gt;();</span><br><span class="line"><span class="keyword">var</span> mesh = mf.mesh;</span><br><span class="line"><span class="built_in">int</span>[] triangles = mesh.triangles;</span><br><span class="line">Vector3[] vertices = mesh.vertices;</span><br></pre></td></tr></table></figure><p>其中<code>vertices</code>就是<code>mesh</code>中的顶点，而<code>triangles</code>则是由这些顶点组成的三角面。我们可以获取一个<code>Quad</code>的Mesh，然后输出<code>vertices</code>和<code>triangles</code>如下：</p><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images//2022/12/23/b19cf91513e0b9195e207e31bec06e43.png" alt="顶点与三角面"></p><p>不难看出<code>triangles</code>数组中存的其实是顶点在<code>vertices</code>数组中的下标，连续的三个数顺时针描述了一个三角面的三个顶点。</p><p>不过<code>vertices</code>中顶点的坐标是本地坐标(localPosition)，在使用的时候我们要将其转为世界坐标(worldPosition)才可以去计算体素化。</p><figure class="highlight csharp"><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">// local -&gt; world</span></span><br><span class="line"><span class="comment">// go -&gt; 场景中的一个gameObject</span></span><br><span class="line">... <span class="comment">// 获取go的mesh、vertices、triangles</span></span><br><span class="line"><span class="keyword">for</span> (<span class="built_in">int</span> i = <span class="number">0</span>; i &lt; vertices.Length; i++)</span><br><span class="line">&#123;</span><br><span class="line">vertices[i] = go.transform.TransformPoint(vertices[i]);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight csharp"><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">// local -&gt; world 使用矩阵运算</span></span><br><span class="line"><span class="comment">// Unity 提供了 Matrix4x4 </span></span><br><span class="line"><span class="comment">// goTrans -&gt; go.transform</span></span><br><span class="line">Matrix4x4 transMatrix = <span class="keyword">new</span> Matrix4x4();</span><br><span class="line">transMatrix.SetTRS(goTrans.position, goTrans.rotation, goTrans.localScale);</span><br><span class="line"><span class="keyword">for</span> (<span class="built_in">int</span> i = <span class="number">0</span>; i &lt; vertices.Length; i++)</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">var</span> vertex = vertices[i];</span><br><span class="line">vertex = transMatrix.MultiplyPoint(vertex);</span><br><span class="line">vertices[i] = vertex;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>Unity 文档 <a href="https://docs.unity.cn/cn/current/ScriptReference/Transform.TransformPoint.html">TransformPoint</a><br>Unity 文档 <a href="https://docs.unity.cn/cn/current/ScriptReference/Matrix4x4.SetTRS.html">Matrix4x4.SetTRS</a></p></blockquote><h3 id="Bounds"><a href="#Bounds" class="headerlink" title="Bounds"></a>Bounds</h3><p>一个Mesh对应的AABB盒(Axis Aligned Bounding Box)即是Bounds，我们可以通过<code>mesh.bounds</code>获取它。不过和顶点一样，<code>mesh.bounds</code>是本地坐标下的，我们需要转换成世界坐标才能用。这时我们可以从<code>MeshRenderer</code>中获取它，<code>GetComponent&lt;MeshRenderer&gt;().mesh.bounds;</code> 就是世界坐标下的AABB盒。</p><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images//2022/12/23/d17abee77d91e532496a75c481dff6cf.png" alt="Bounds" style="zoom:50%"></p><p>我们拿到Bounds的目的是简化碰撞判断，当一个Mesh的Bounds与我们限制体素化范围的物体的Bounds相交，我们才去着手对其进行体素化操作。</p><figure class="highlight csharp"><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="comment">// 获取场景内所有的gameObject，逐个判断是否在VoxelizationBox范围内。</span></span><br><span class="line"><span class="keyword">foreach</span> (<span class="keyword">var</span> go <span class="keyword">in</span> Object.FindObjectsOfType&lt;GameObject&gt;())</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">if</span> (go.transform == _startPoint || go.transform == _destPoint)</span><br><span class="line"><span class="keyword">continue</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> mf = go.GetComponent&lt;MeshFilter&gt;();</span><br><span class="line"><span class="keyword">if</span> (mf == <span class="literal">null</span>) <span class="keyword">continue</span>;</span><br><span class="line"><span class="keyword">var</span> mesh = mf.mesh;</span><br><span class="line"><span class="keyword">var</span> bounds = go.GetComponent&lt;MeshRenderer&gt;().bounds;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (!_voxelBox.Intersects(bounds)) <span class="keyword">continue</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 物体和VoxelizationBox有交叉</span></span><br><span class="line"><span class="comment">// 获取物体Mesh的全部三角面，逐个光栅化（标记其占用的体素）</span></span><br><span class="line"><span class="built_in">int</span>[] triangles = mesh.triangles;</span><br><span class="line">Vector3[] vertices = mesh.vertices;</span><br><span class="line"><span class="keyword">var</span> goTrans = go.transform;</span><br><span class="line"></span><br><span class="line"><span class="comment">// local -&gt; worldPosition</span></span><br><span class="line">Matrix4x4 transMatrix = <span class="keyword">new</span> Matrix4x4();</span><br><span class="line">transMatrix.SetTRS(goTrans.position, goTrans.rotation, goTrans.localScale);</span><br><span class="line"><span class="keyword">for</span> (<span class="built_in">int</span> i = <span class="number">0</span>; i &lt; vertices.Length; i++)</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">var</span> vertex = vertices[i];</span><br><span class="line">vertex = transMatrix.MultiplyPoint(vertex);</span><br><span class="line">vertices[i] = vertex;</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="keyword">for</span> (<span class="built_in">int</span> i = <span class="number">0</span>; i &lt; triangles.Length; i += <span class="number">3</span>)</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">int</span> j = i + <span class="number">1</span>, k = i + <span class="number">2</span>;</span><br><span class="line">RasterizeTriangle(vertices[triangles[i]], </span><br><span class="line">vertices[triangles[j]], </span><br><span class="line">vertices[triangles[k]]);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="体素化三角面"><a href="#体素化三角面" class="headerlink" title="体素化三角面"></a>体素化三角面</h2><h3 id="基本思路"><a href="#基本思路" class="headerlink" title="基本思路"></a>基本思路</h3><p>如果是二维的三角面，体素化（网格化）会比较容易。假设我们的三角形在$XOZ$平面上，我们可以按照如下步骤：</p><ol><li><p>求出三角形的Bounds，获取其所处的网格$z$方向的取值范围；</p></li><li><p>逐个枚举$z$，将三角形分为上、下两部分，取下部分进行 3 操作；</p></li><li><p>对于 2 中下部分，求出其所处的网格$x$方向的取值范围；</p></li><li><p>逐个枚举$x$，标记左侧部分所在的网格，返回 2 。</p></li></ol><p>可以看如下图：</p><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images//2022/12/23/749feea8023d82d877382f2edb5fb6e6.png" alt="z方向切割" style="zoom:50%"></p><p>左边红色的线为我们枚举的$z$切割线，按照线可以将三角面分割成右侧6部分，每个部分对应$x$的范围用绿色框框起来了。</p><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images//2022/12/23/18de281a67b6a501d67a104de772ff2a.png" alt="x方向切割" style="zoom:50%"></p><p>右侧红色的线为我们枚举的$x$切割线，按照先将每个多边形分割到每个网格中，最后被标记的网格在左边用浅蓝色的线围起来了。</p><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images//2022/12/23/64a143b3db6167c332457685c3026ce6.png" alt="三维空间中的多边形" style="zoom:50%"></p><p>三维的其实也是同理，如上图多边形，我们先按照$z$轴分割。拿分割出的多边形，按照$x$轴进行分割。这时候得到的多边形在$XOZ$平面内的投影就在一个体素内了（如下图，红色线表示$z$轴分割，浅蓝色线表示$x$轴分割），我们只需要求出其在$y$轴上占几个体素，将其标记为占用即可。</p><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images//2022/12/23/a2cbffb8986fa0c3cb3cc046c910e857.png" alt="对投影面进行分割" style="zoom:50%"></p><p>结果如下图：</p><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images//2022/12/23/4a1bc04d25ad81f8049bc2e8b1ad04a6.png" alt="体素化结果" style="zoom:50%"></p><h3 id="分割三角面"><a href="#分割三角面" class="headerlink" title="分割三角面"></a>分割三角面</h3><p>现在我们思路已经很明确了，就要去解决分割三角面的问题了。</p><p>在$z$方向上的切割，详细过程可以见下图，我们维护两个<code>List</code>，<code>Current</code>和<code>Next</code>。<code>Current</code>表示切割线下方的多边形（即我们将要那它去做$x$轴切割），<code>Next</code>表示切割线上方的多边形（即处理完<code>Current</code>后再继续对它进行$z$方向切割）。</p><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images//2022/12/23/52d85f0a1c8743bbc871470735f89b61.png" alt="体素化过程"></p><p>按照顺时针方向枚举目前三角面上的边，例如这里我们按照AB、BC、CA的顺序。</p><p><strong>AB：</strong> A、B两点位于切割线异侧，故而要求AB与切割线的交点D，随后按照顺时针顺序（A -&gt; D -&gt; B）逐个将顶点放入<code>Current</code>或者<code>Next</code>。</p><p><strong>BC：</strong> B、C两点位于切割线异侧，故而要求BC与切割线的交点E，随后按照顺时针顺序（B -&gt; E -&gt; C）逐个将顶点放入<code>Current</code>或者<code>Next</code>。</p><p><strong>CA：</strong> C、A两点位于切割线同侧，直接按照顺时针顺序（C -&gt; A）逐个将顶点放入<code>Current</code>或者<code>Next</code>。</p><p><strong>放置规则：</strong> 位于切割线上侧，则放入<code>Next</code>；位于切割线下侧，则放入<code>Current</code>；为边线与切割线交点，则同时要被放入<code>Current</code>和<code>Next</code>。</p><p>当然同一个点不要在一个<code>List</code>中反复添加，所以下图中，重复添加的行被打上了灰色的删除线。由此在枚举完所有的边之后，我们可以发现不管是<code>Current</code>还是<code>Next</code>，其中记录的点都是按照顺时针顺序排列的，完整了记录了其所对应的多边形。</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;summary&gt;</span></span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> 将三角面abc光栅化(体素化)</span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;/summary&gt;</span></span></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">RasterizeTriangle</span>(<span class="params">Vector3 a, Vector3 b, Vector3 c</span>)</span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="comment">// Debug.Log($&quot;Triangle: a = &#123;a&#125;, b = &#123;b&#125;, c = &#123;c&#125;&quot;);</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 求出当前三角面abc的AABB盒</span></span><br><span class="line">Bounds triBound = <span class="keyword">new</span> Bounds();</span><br><span class="line">triBound.max = a.ComponentMax(b).ComponentMax(c);</span><br><span class="line">triBound.min = a.ComponentMin(b).ComponentMin(c);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 如果当前三角面不在体素化范围内，就返回，不处理了。</span></span><br><span class="line"><span class="keyword">if</span> (!_voxelBox.Intersects(triBound))</span><br><span class="line"><span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 求三角面abc在z方向上占用的体素的坐标范围</span></span><br><span class="line"><span class="keyword">var</span> z0 = Mathf.Clamp(</span><br><span class="line">Mathf.FloorToInt((triBound.min.z - _voxelBox.min.z) / _cellSize),</span><br><span class="line"><span class="number">0</span>,</span><br><span class="line">_voxelZNum - <span class="number">1</span></span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> z1 = Mathf.Clamp(</span><br><span class="line">Mathf.CeilToInt((triBound.max.z - _voxelBox.min.z) / _cellSize),</span><br><span class="line"><span class="number">0</span>,</span><br><span class="line">_voxelZNum - <span class="number">1</span></span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 一个三角形被正方形切割得到的图形最多有七个顶点</span></span><br><span class="line">List&lt;Vector3&gt; NextRow = <span class="keyword">new</span> List&lt;Vector3&gt;(<span class="number">7</span>);</span><br><span class="line">List&lt;Vector3&gt; CurrentRow = <span class="keyword">new</span> List&lt;Vector3&gt;(<span class="number">7</span>);</span><br><span class="line">List&lt;Vector3&gt; NextGrid = <span class="keyword">new</span> List&lt;Vector3&gt;(<span class="number">7</span>);</span><br><span class="line">List&lt;Vector3&gt; CurrentGrid = <span class="keyword">new</span> List&lt;Vector3&gt;(<span class="number">7</span>);</span><br><span class="line"></span><br><span class="line">NextRow.Add(a);</span><br><span class="line">NextRow.Add(b);</span><br><span class="line">NextRow.Add(c);</span><br><span class="line"></span><br><span class="line"><span class="comment">// Debug.Log($&quot;RasterizeTriangle: z0 = &#123;z0&#125;, z1 = &#123;z1&#125;&quot;);</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="built_in">int</span> z = z0; z &lt;= z1; z++)</span><br><span class="line">&#123;</span><br><span class="line"><span class="comment">// 分割线</span></span><br><span class="line"><span class="built_in">float</span> zSecant = _voxelBox.min.z + (z + <span class="number">1</span>) * _cellSize;</span><br><span class="line"></span><br><span class="line">DividePolygon(NextRow, CurrentRow, zSecant, <span class="literal">true</span>);</span><br><span class="line"><span class="keyword">if</span> (CurrentRow.Count &lt; <span class="number">3</span>)</span><br><span class="line"><span class="keyword">continue</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 求经过z分割线分割后，下方多边形的AABB盒</span></span><br><span class="line"><span class="built_in">float</span> minX = CurrentRow[<span class="number">0</span>].x, maxX = CurrentRow[<span class="number">0</span>].x;</span><br><span class="line"><span class="keyword">for</span> (<span class="built_in">int</span> i = <span class="number">1</span>; i &lt; CurrentRow.Count; i++)</span><br><span class="line">&#123;</span><br><span class="line">minX = Mathf.Min(minX, CurrentRow[i].x);</span><br><span class="line">maxX = Mathf.Max(maxX, CurrentRow[i].x);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 求多边形在x方向上占用体素x坐标范围</span></span><br><span class="line"><span class="keyword">var</span> x0 = Mathf.Clamp(</span><br><span class="line">Mathf.FloorToInt((minX - _voxelBox.min.x) / _cellSize),</span><br><span class="line"><span class="number">0</span>,</span><br><span class="line">_voxelXNum - <span class="number">1</span></span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> x1 = Mathf.Clamp(</span><br><span class="line">Mathf.CeilToInt((maxX - _voxelBox.min.x) / _cellSize),</span><br><span class="line"><span class="number">0</span>,</span><br><span class="line">_voxelXNum - <span class="number">1</span></span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="comment">// Debug.Log($&quot;RasterizeTriangle: x0 = &#123;x0&#125;, x1 = &#123;x1&#125;&quot;);</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="built_in">int</span> x = x0; x &lt;= x1; x++)</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">float</span> xSecant = _voxelBox.min.x + (x + <span class="number">1</span>) * _cellSize;</span><br><span class="line"></span><br><span class="line">DividePolygon(CurrentRow, CurrentGrid, xSecant, <span class="literal">false</span>);</span><br><span class="line"><span class="keyword">if</span> (CurrentGrid.Count &lt; <span class="number">3</span>)</span><br><span class="line"><span class="keyword">continue</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 求经过x分割后，左方多边形的AABB盒</span></span><br><span class="line"><span class="built_in">float</span> minY = CurrentGrid[<span class="number">0</span>].y, maxY = CurrentGrid[<span class="number">0</span>].y;</span><br><span class="line"><span class="keyword">for</span> (<span class="built_in">int</span> i = <span class="number">0</span>; i &lt; CurrentGrid.Count; i++)</span><br><span class="line">&#123;</span><br><span class="line">minY = Mathf.Min(minY, CurrentGrid[i].y);</span><br><span class="line">maxY = Mathf.Max(maxY, CurrentGrid[i].y);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (maxY &lt;= _voxelBox.min.y || minY &gt;= _voxelBox.max.y)</span><br><span class="line"><span class="keyword">continue</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 求多边形在y方向上占用体素y坐标范围</span></span><br><span class="line"><span class="keyword">var</span> y0 = Mathf.Clamp(</span><br><span class="line">Mathf.FloorToInt((minY - _voxelBox.min.y) / _cellHeight),</span><br><span class="line"><span class="number">0</span>,</span><br><span class="line">_voxelYNum - <span class="number">1</span></span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> y1 = Mathf.Clamp(</span><br><span class="line">Mathf.CeilToInt((maxY - _voxelBox.min.y) / _cellHeight),</span><br><span class="line">y0 + <span class="number">1</span>, </span><br><span class="line">_voxelYNum - <span class="number">1</span></span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="comment">// Debug.Log($&quot;RasterizeTriangle: y0 = &#123;y0&#125;, y1 = &#123;y1&#125;&quot;);</span></span><br><span class="line"><span class="keyword">for</span> (<span class="built_in">int</span> y = y0; y &lt; y1; y++)</span><br><span class="line">&#123;</span><br><span class="line">SetVoxelState(x, y, z, <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><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;summary&gt;</span></span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> 沿着 secant 将 divided 描述的多边形进行切分</span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;/summary&gt;</span></span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;remarks&gt;</span></span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> 在方法执行完毕后，位于 secant 上侧或右侧的多边形会被存储在 divided 中,</span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> 位于 secant 下侧或左侧的多边形会被存储在 result 中 </span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;/remarks&gt;</span></span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;param name=&quot;zAxis&quot;&gt;</span>为true说明 z = secant, 为false说明是 x = secant <span class="doctag">&lt;/param&gt;</span></span></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">DividePolygon</span>(<span class="params">List&lt;Vector3&gt; divided, List&lt;Vector3&gt; result, <span class="built_in">float</span> secant, <span class="built_in">bool</span> zAxis</span>)</span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">List&lt;Vector3&gt; nextPart = <span class="keyword">new</span> List&lt;Vector3&gt;(<span class="number">7</span>);</span><br><span class="line">result.Clear();</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="built_in">int</span> i = <span class="number">1</span>; i &lt;= divided.Count; i++)</span><br><span class="line">&#123;</span><br><span class="line">Vector3 a = divided[i - <span class="number">1</span>], b = divided[i % divided.Count];</span><br><span class="line"></span><br><span class="line"><span class="comment">// true -&gt; nextPart, false -&gt; result</span></span><br><span class="line"><span class="built_in">bool</span> aBelongs = <span class="literal">false</span>, bBelongs = <span class="literal">false</span>;</span><br><span class="line">aBelongs = zAxis ? (a.z &gt;= secant) : (a.x &gt;= secant);</span><br><span class="line">bBelongs = zAxis ? (b.z &gt;= secant) : (b.x &gt;= secant);</span><br><span class="line"></span><br><span class="line"><span class="comment">// Debug.Log($&quot;DividePolygon: aBelongs = &#123;aBelongs&#125;, bBelongs = &#123;bBelongs&#125;&quot;);</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (i == <span class="number">1</span>)</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">if</span> (aBelongs) nextPart.Add(a);</span><br><span class="line"><span class="keyword">else</span> result.Add(a);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (aBelongs ^ bBelongs)</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">float</span> proportion, intersectX, intersectY, intersectZ;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (zAxis)</span><br><span class="line">&#123;</span><br><span class="line">proportion = (secant - a.z) / (b.z - a.z);</span><br><span class="line">intersectX = a.x + (b.x - a.x) * proportion;</span><br><span class="line">intersectZ = secant;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">&#123;</span><br><span class="line">proportion = (secant - a.x) / (b.x - a.x);</span><br><span class="line">intersectX = secant;</span><br><span class="line">intersectZ = a.z + (b.z - a.z) * proportion;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">intersectY = a.y + (b.y - a.y) * proportion;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> intersect = <span class="keyword">new</span> Vector3(intersectX, intersectY, intersectZ);</span><br><span class="line">nextPart.Add(intersect);</span><br><span class="line">result.Add(intersect);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (i != divided.Count)</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">if</span> (bBelongs) nextPart.Add(b);</span><br><span class="line"><span class="keyword">else</span> result.Add(b);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">divided.Clear();</span><br><span class="line">divided.AddRange(nextPart);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;summary&gt;</span></span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> 设置体素(x, y, z) 的状态为 state/&gt;</span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;/summary&gt;</span></span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;param name=&quot;state&quot;&gt;</span>true -&gt; 标记体素被占用，false -&gt; 标记体素取消占用<span class="doctag">&lt;/param&gt;</span></span></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">SetVoxelState</span>(<span class="params"><span class="built_in">int</span> x, <span class="built_in">int</span> y, <span class="built_in">int</span> z, <span class="built_in">bool</span> state</span>)</span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="comment">// Debug.Log($&quot;Set Voxel (&#123;x&#125;, &#123;y&#125;, &#123;z&#125;) occupied!&quot;);</span></span><br><span class="line"><span class="built_in">int</span> originalIndex = x * _voxelYNum * _voxelZNum + y * _voxelZNum + z;</span><br><span class="line"><span class="built_in">int</span> compressedIndex = originalIndex &gt;&gt; <span class="number">5</span>;</span><br><span class="line"><span class="built_in">int</span> offset = originalIndex - (compressedIndex &lt;&lt; <span class="number">5</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (state)</span><br><span class="line">&#123;</span><br><span class="line">_voxels[compressedIndex] |= (uint)(<span class="number">1</span> &lt;&lt; offset);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">&#123;</span><br><span class="line">_voxels[compressedIndex] &amp;= ~(uint)(<span class="number">1</span> &lt;&lt; offset);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images//2022/12/23/414355d83799145f8f5228964904f391.png" alt="体素化结果"></p><h2 id="简单的寻路演示"><a href="#简单的寻路演示" class="headerlink" title="简单的寻路演示"></a>简单的寻路演示</h2><p>用BFS简单做了个基于体素的寻路，效果如下：</p><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images//2022/12/23/4549a044e25a5cf78a4f59663dc10aa2.png" alt="效果展示"></p><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images//2022/12/23/89482c6951f1c2f9ab3f01823aaadf6b.png" alt="效果展示"></p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> List&lt;Vector3Int&gt; <span class="title">PathFinding</span>(<span class="params">Vector3Int startVoxel, Vector3Int destVoxel</span>)</span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">Dictionary&lt;Vector3Int, Vector3Int&gt; precursorDict = <span class="keyword">new</span> Dictionary&lt;Vector3Int, Vector3Int&gt;();</span><br><span class="line">List&lt;Vector3Int&gt; path = <span class="keyword">new</span> List&lt;Vector3Int&gt;();</span><br><span class="line"></span><br><span class="line">Queue&lt;Vector3Int&gt; bfsQ = <span class="keyword">new</span> Queue&lt;Vector3Int&gt;();</span><br><span class="line">bfsQ.Enqueue(startVoxel);</span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span> (bfsQ.Count &gt; <span class="number">0</span>)</span><br><span class="line">&#123;</span><br><span class="line">Vector3Int current = bfsQ.Dequeue();</span><br><span class="line"><span class="keyword">if</span> (current == destVoxel)</span><br><span class="line">&#123;</span><br><span class="line"><span class="comment">// Debug.Log(&quot;Find Path!!!!&quot;);</span></span><br><span class="line">path.Add(destVoxel);</span><br><span class="line"><span class="keyword">var</span> prev = precursorDict[current];</span><br><span class="line"><span class="keyword">do</span></span><br><span class="line">&#123;</span><br><span class="line">path.Add(prev);</span><br><span class="line">prev = precursorDict[prev];</span><br><span class="line">&#125; <span class="keyword">while</span> (prev != startVoxel);</span><br><span class="line"></span><br><span class="line"><span class="keyword">break</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="built_in">int</span> i = <span class="number">0</span>; i &lt; <span class="number">6</span>; i++)</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">int</span> dx = _dirX[i], dy = _dirY[i], dz = _dirZ[i];</span><br><span class="line">Vector3Int nextVoxel = current + <span class="keyword">new</span> Vector3Int(dx, dy, dz);</span><br><span class="line"><span class="keyword">if</span> (IsVoxelInside(nextVoxel) </span><br><span class="line">&amp;&amp; IsStayableVoxel(nextVoxel) </span><br><span class="line">&amp;&amp; !precursorDict.ContainsKey(nextVoxel))</span><br><span class="line">&#123;</span><br><span class="line">bfsQ.Enqueue(nextVoxel);</span><br><span class="line">precursorDict.Add(nextVoxel, current);</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> path;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;summary&gt;</span></span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> 传入voxel坐标，判断这个位置是否可以停留</span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;/summary&gt;</span></span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;remarks&gt;</span></span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> 一个可以停留的voxel用以下三点判断：<span class="doctag">&lt;br/&gt;</span></span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> 1. 本身不是障碍物 <span class="doctag">&lt;br/&gt;</span></span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> 2. 下方是障碍物 （站在地面上） <span class="doctag">&lt;br/&gt;</span></span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> 3. 四周是障碍物 （爬墙） <span class="doctag">&lt;br/&gt;</span></span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> 4. 四周正下方一格是障碍物（进入向下爬墙状态） <span class="doctag">&lt;br/&gt;</span></span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> 其中 1 必须满足，2、3、4满足其一即可</span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;/remarks&gt;</span></span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;returns&gt;</span><span class="doctag">&lt;see langword=&quot;true&quot;/&gt;</span>-&gt; 可以停留，<span class="doctag">&lt;see langword=&quot;false&quot;/&gt;</span>-&gt; 不可停留<span class="doctag">&lt;/returns&gt;</span></span></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="built_in">bool</span> <span class="title">IsStayableVoxel</span>(<span class="params">Vector3Int voxel</span>)</span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> IsVoxelInside(voxel) &amp;&amp; !IsVoxelOccupied(voxel.x, voxel.y, voxel.z) <span class="comment">// 1.</span></span><br><span class="line">&amp;&amp; (IsVoxelOccupied(voxel.x - <span class="number">1</span>, voxel.y, voxel.z)                 <span class="comment">// 3.</span></span><br><span class="line">|| IsVoxelOccupied(voxel.x + <span class="number">1</span>, voxel.y, voxel.z)              <span class="comment">// 3.</span></span><br><span class="line">|| IsVoxelOccupied(voxel.x, voxel.y - <span class="number">1</span>, voxel.z)              <span class="comment">// 2.</span></span><br><span class="line">|| IsVoxelOccupied(voxel.x, voxel.y, voxel.z + <span class="number">1</span>)              <span class="comment">// 3.</span></span><br><span class="line">|| IsVoxelOccupied(voxel.x, voxel.y, voxel.z - <span class="number">1</span>)              <span class="comment">// 3.</span></span><br><span class="line">|| IsVoxelOccupied(voxel.x - <span class="number">1</span>, voxel.y - <span class="number">1</span>, voxel.z)          <span class="comment">// 4.</span></span><br><span class="line">|| IsVoxelOccupied(voxel.x + <span class="number">1</span>, voxel.y - <span class="number">1</span>, voxel.z)          <span class="comment">// 4.</span></span><br><span class="line">|| IsVoxelOccupied(voxel.x, voxel.y - <span class="number">1</span>, voxel.z - <span class="number">1</span>)          <span class="comment">// 4.</span></span><br><span class="line">|| IsVoxelOccupied(voxel.x, voxel.y - <span class="number">1</span>, voxel.z + <span class="number">1</span>)          <span class="comment">// 4.</span></span><br><span class="line">);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="built_in">bool</span> <span class="title">IsVoxelInside</span>(<span class="params">Vector3Int voxel</span>)</span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">return</span> voxel.x &gt;= <span class="number">0</span> &amp;&amp; voxel.x &lt; _voxelXNum</span><br><span class="line">&amp;&amp; voxel.y &gt;= <span class="number">0</span> &amp;&amp; voxel.y &lt; _voxelYNum</span><br><span class="line">&amp;&amp; voxel.z &gt;= <span class="number">0</span> &amp;&amp; voxel.z &lt; _voxelZNum;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="built_in">bool</span> <span class="title">IsVoxelInside</span>(<span class="params"><span class="built_in">int</span> x, <span class="built_in">int</span> y, <span class="built_in">int</span> z</span>)</span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">return</span> x &gt;= <span class="number">0</span> &amp;&amp; x &lt; _voxelXNum</span><br><span class="line">&amp;&amp; y &gt;= <span class="number">0</span> &amp;&amp; y &lt; _voxelYNum</span><br><span class="line">&amp;&amp; z &gt;= <span class="number">0</span> &amp;&amp; z &lt; _voxelZNum;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;在Unity中实现体素化&quot;&gt;&lt;a href=&quot;#在Unity中实现体素化&quot; class=&quot;headerlink&quot; title=&quot;在Unity中实现体素化&quot;&gt;&lt;/a&gt;在Unity中实现体素化&lt;/h1&gt;&lt;h2 id=&quot;体素化&quot;&gt;&lt;a href=&quot;#体素化&quot; class</summary>
      
    
    
    
    <category term="游戏开发" scheme="http://www.fcayh.cn/categories/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/"/>
    
    
    <category term="C#" scheme="http://www.fcayh.cn/tags/C/"/>
    
    <category term="Unity" scheme="http://www.fcayh.cn/tags/Unity/"/>
    
    <category term="NavMesh" scheme="http://www.fcayh.cn/tags/NavMesh/"/>
    
  </entry>
  
  <entry>
    <title>多边形分割成若干凸多边形（NavMesh的初步形成）</title>
    <link href="http://www.fcayh.cn/2022/11/28/meadow-map/"/>
    <id>http://www.fcayh.cn/2022/11/28/meadow-map/</id>
    <published>2022-11-27T19:35:22.000Z</published>
    <updated>2023-02-13T06:58:23.761Z</updated>
    
    <content type="html"><![CDATA[<h1 id="多边形分割成若干凸多边形（NavMesh的初步形成）"><a href="#多边形分割成若干凸多边形（NavMesh的初步形成）" class="headerlink" title="多边形分割成若干凸多边形（NavMesh的初步形成）"></a>多边形分割成若干凸多边形（NavMesh的初步形成）</h1><blockquote><p>本文基于 Arkin, Ronald C.的论文 “Path planning for a vision-based autonomous robot”.<br>论文链接 <a href="https://www.cc.gatech.edu/ai/robot-lab/online-publications/ieee/path86.pdf">Path planning for a vision-based autonomous robot</a><br>部分计算几何的算法基于<em>Computational Geometry in C</em><br>其源码可以参考：<a href="https://github.com/w8r/orourke-compc">orourke-compc</a></p></blockquote><p>现任佐治亚理工教授 Ronald C. Arkin，在1986年时发表了一篇叫做Path planning for a vison-based autonomous robot的报告，隶属robotics领域。文章提出了Meadow Map的方法以长时间存储地图。Meadow Map为现代Navmesh系统的雏形，<strong>提出了以下核心观点</strong>：</p><ul><li><p>使用凸多边形构建可行走区域</p></li><li><p>对生成的凸多边形集合，以其公共边中点为寻路节点，使用A*进行寻路</p></li><li><p>使用路径改进算法，对A*结果进行改良</p></li></ul><p>在本文中，我们主要探讨：通过Arkin教授的方法去实现给定一个任意多边形，得到其分割而成的若干凸多边形。</p><h2 id="方法步骤："><a href="#方法步骤：" class="headerlink" title="方法步骤："></a>方法步骤：</h2><ol><li><p>对于现有的多边形$P$，若其为凸多边形，则结束，否则进入步骤2</p></li><li><p>在$P$中找到一个<strong>凹</strong>的点，如图中点$A$</p></li><li><p>由点$A$去找一个同在$P$中的另一不相邻点，如图中$D, E, H$等</p></li><li><p>选择其中一个<strong>完全在$P$内部</strong>的线段，用于将$P$分割成两个子多边形$P_1, P_2$</p></li><li><p>对$P_1, P_2$做同样的操作</p></li></ol><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images//2022/11/26/59e9f968d0876bb4e344ed61e7669a4b.png" alt="线段是否完全在多边形内部"></p><p><strong>完全在$P$内部</strong>（例如图中线段$AD$）的判断方式：</p><ul><li><p>在角的内侧（对于$AD$来说就是要在$∠JAB$和$∠CDE$的内侧）</p></li><li><p>不与任何一条非相邻边相交（对于$AD$来说就是不与$AJ, AB, DC, DE$以外的边相交）</p></li></ul><p>在上图中，线段$AG, AH$都是反例，要被排除掉，我们可以在$AC, AD, AE, AF$中选择一条去分割多边形$P$。</p><p>关于在角的内侧，可以看这张图：</p><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images//2022/11/27/cce873833e8d854d7ece04ac2c338cf6.png" alt="线段在角的内侧"></p><p>图中对角线$AC$位于$∠HAB$和$∠BCD$外侧，而对角线$AD$位于$∠HAB$外侧和$∠CDE$内侧。</p><p>对于这个算法来说，其难点就在于：1. 如何找到一个凹的点，2. 如何找到一个完全在内部的对角线。</p><h3 id="1-如何找到一个凹的点"><a href="#1-如何找到一个凹的点" class="headerlink" title="1. 如何找到一个凹的点"></a>1. 如何找到一个凹的点</h3><p>我们按照逆时针顺序枚举多边形$P$上的顶点，如下图。当我们枚举到点A时，记录其上一个节点为$A^-$，其下一个节点为$A^+$。通过判断$A^+$与 $\boldsymbol{A^-A}$ 的关系，当$A^+$在$\boldsymbol{A^-A}$的右侧时，则说明点$A$是一个凹的点。</p><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images//2022/11/27/e99181f580f467e135c6490628a2498a.png" alt="凹的点"></p><p>我们可以通过向量的叉积来进行左右方向的判断，设$\boldsymbol{A^-A} = (x_1, y_1), \boldsymbol{A^-A^+} = (x_2, y_2)$ 则$\boldsymbol{A^-A} \times \boldsymbol{A^-A^+} = x_1y_2 - x_2y_1$ 。这是一个在$z$轴上的向量，即$(0, 0, x_1y_2-x_2y_1)$。当$A^+$位于$\boldsymbol{A^-A}$左侧时，$x_1y_2-x_2y_1&gt;0$，否则$x_1y_2-x_2y_1&lt;0$，当$A^+$于$\boldsymbol{A^-A}$共线时，$x_1y_2-x_2y_1=0$</p><figure class="highlight csharp"><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></pre></td><td class="code"><pre><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;summary&gt;</span></span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> 判断点c与向量ab的位置关系</span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;/summary&gt;</span></span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;returns&gt;</span>c在ab左侧，返回1；c在ab右侧，返回-1；c与ab共线，返回0。<span class="doctag">&lt;/returns&gt;</span></span></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">static</span> <span class="built_in">int</span> <span class="title">AreaSign</span>(<span class="params">Vector2 a, Vector2 b, Vector2 c</span>)</span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">Vector2 ab = b - a;</span><br><span class="line">Vector2 ac = c - a;</span><br><span class="line"></span><br><span class="line"><span class="built_in">float</span> area = ab.Cross(ac);</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (area &gt; EPS) <span class="keyword">return</span> <span class="number">1</span>; <span class="comment">// c 在 ab 左侧</span></span><br><span class="line"><span class="keyword">else</span> <span class="keyword">if</span> (area &lt; -EPS) <span class="keyword">return</span> <span class="number">-1</span>; <span class="comment">// c 在 ab 右侧</span></span><br><span class="line"><span class="keyword">else</span> <span class="keyword">return</span> <span class="number">0</span>; <span class="comment">//a, b, c共线</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;summary&gt;</span></span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> 叉积</span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;/summary&gt;</span></span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;returns&gt;</span>二维向量叉积的模<span class="doctag">&lt;/returns&gt;</span></span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="built_in">float</span> <span class="title">Cross</span>(<span class="params"><span class="keyword">this</span> Vector2 a, Vector2 b</span>)</span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">return</span> a.x * b.y - a.y * b.x;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;summary&gt;</span></span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> 用于判断点<span class="doctag">&lt;paramref name=&quot;c&quot;/&gt;</span> 是否位于向量ab的左侧。</span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;/summary&gt;</span></span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;returns&gt;</span><span class="doctag">&lt;paramref name=&quot;c&quot;/&gt;</span>位于向量ab左侧为true，否则为false<span class="doctag">&lt;/returns&gt;</span></span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="built_in">bool</span> <span class="title">Left</span>(<span class="params">Vector2 a, Vector2 b, Vector2 c</span>)</span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">return</span> AreaSign(a, b, c) &gt; <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;summary&gt;</span></span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> 用于判断点<span class="doctag">&lt;paramref name=&quot;c&quot;/&gt;</span> 是否位于向量ab的左侧或在ab上。</span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;/summary&gt;</span></span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;returns&gt;</span><span class="doctag">&lt;paramref name=&quot;c&quot;/&gt;</span>位于向量ab左侧或在ab上为true，否则为false<span class="doctag">&lt;/returns&gt;</span></span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="built_in">bool</span> <span class="title">LeftOn</span>(<span class="params">Vector2 a, Vector2 b, Vector2 c</span>)</span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">return</span> AreaSign(a, b, c) &gt;= <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;summary&gt;</span></span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> 判断点<span class="doctag">&lt;paramref name=&quot;a&quot;/&gt;</span>, <span class="doctag">&lt;paramref name=&quot;b&quot;/&gt;</span>, <span class="doctag">&lt;paramref name=&quot;c&quot;/&gt;</span>是否共线</span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;/summary&gt;</span></span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;returns&gt;</span>三点共线则返回true，否则false<span class="doctag">&lt;/returns&gt;</span></span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="built_in">bool</span> <span class="title">Collinear</span>(<span class="params">Vector2 a, Vector2 b, Vector2 c</span>)</span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">return</span> AreaSign(a, b, c) == <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-如何找到一个完全在内部的对角线"><a href="#2-如何找到一个完全在内部的对角线" class="headerlink" title="2. 如何找到一个完全在内部的对角线"></a>2. 如何找到一个完全在内部的对角线</h3><p>对于一个凹的顶点$A$，我们要逐个枚举多边形$P$中不与$A$相邻的其他顶点，然后判断其连线是否是1. 在角的内侧，2. 不与任何非相邻的边相交。</p><h4 id="判断是否在角的内侧"><a href="#判断是否在角的内侧" class="headerlink" title="判断是否在角的内侧"></a>判断是否在角的内侧</h4><p>如下图a、图b，当$∠A^-AA^+$是个劣角（即小于180°的角）时，要判断$AD$是否在角内部，仅需保证$D$同时在$\boldsymbol{A^-A}$和$\boldsymbol{AA^+}$的左侧。（这里一定要注意我们$A^-,A,A^+$是逆时针排列的）</p><p>对于图c，$AD$在$\boldsymbol{A^-A}$的左侧，但是在$\boldsymbol{AA^+}$的右侧，故而其在角的外侧。</p><p>在图d中，我们可以看到两个浅蓝色框分别代表$\boldsymbol{A^-A}$和$\boldsymbol{AA^+}$的左侧，则重叠的深色区域就是勇仕在两个向量的左侧的区域，即角的内侧。</p><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images//2022/11/27/6948d1d8fe76ee58e365571d3765b1e4.png" alt="判断是否在角的内侧"></p><p>那当$∠A^-AA^+$为优角（即大于180°小于360°的角）呢？其实可以反向思考以下，这个时候如果$AD$在$∠A^-AA^+$对应的劣角中，岂不就说明$AD$在优角$∠A^-AA^+$外侧了？</p><figure class="highlight csharp"><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="comment"><span class="doctag">///</span> <span class="doctag">&lt;summary&gt;</span></span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> 对角线ab是否在∠A内部</span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;/summary&gt;</span></span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;returns&gt;</span>ab在∠A内部返回true，否则false<span class="doctag">&lt;/returns&gt;</span></span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="built_in">bool</span> <span class="title">InCone</span>(<span class="params">Vertex a, Vertex b</span>)</span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">Vertex a0 = a.Prev, a1 = a.Next;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 若∠A为劣角(&lt;180°)</span></span><br><span class="line"><span class="keyword">if</span> (LeftOn(a0.Position, a.Position, a1.Position))</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">return</span> Left(a.Position, b.Position, a0.Position)</span><br><span class="line">&amp;&amp; Left(b.Position, a.Position, a1.Position);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> !(LeftOn(a.Position, b.Position, a1.Position)</span><br><span class="line">&amp;&amp; LeftOn(b.Position, a.Position, a0.Position));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在上述代码中，引入了一个新类型<code>Vertex</code>，其定义如下：</p><figure class="highlight csharp"><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">public</span> <span class="keyword">class</span> <span class="title">Vertex</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">public</span> Vector2 Position;</span><br><span class="line"><span class="keyword">public</span> Vertex Prev;</span><br><span class="line"><span class="keyword">public</span> Vertex Next;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>目的是将多边形$P$的所有顶点，逆时针方向连接起来。</p><h4 id="判断是否与其他边相交"><a href="#判断是否与其他边相交" class="headerlink" title="判断是否与其他边相交"></a>判断是否与其他边相交</h4><p>我们先来看一下两个线段的位置关系都有哪些，如下图：</p><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images//2022/11/27/9f376e8309c5d67dcea0a0edbd7c2531.png" alt="两个线段的位置关系"></p><p>对于<strong>严格相交</strong>这种情况，我们只需要确保$a, b$在线段$cd$的两侧，并且$c, d$在线段$ab$的两侧即可。如何判断两侧？这就又回到刚才我们判断点在向量左右那一步了，我们可以直接调用刚才写好的<code>Left</code>方法。</p><p>对比<strong>相交</strong>和<strong>不相交</strong>这两种情况，其区别是三点共线时，共线的点是不是在线段上，即当$bcd$三点共线时，要保证$b$ 位于$cd$之中，就也属于相交的情况。而三点共线也可以直接用刚才写好的<code>Collinear()</code>方法，我们需要再写一个<code>Between()</code>方法去判断当<code>Collinear()</code>满足时，是否满足$b$ 位于$cd$之中这种情况。</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;summary&gt;</span></span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> 线段ab与线段cd严格相交</span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;/summary&gt;</span></span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;remarks&gt;</span>严格相交: 不包含三点共线的情况，例如 T 形<span class="doctag">&lt;/remarks&gt;</span></span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;returns&gt;</span>ab与cd相交，返回true，否则false<span class="doctag">&lt;/returns&gt;</span></span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="built_in">bool</span> <span class="title">IntersectStrictly</span>(<span class="params">Vector2 a, Vector2 b, Vector2 c, Vector2 d</span>)</span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (Collinear(a, b, c) || Collinear(a, b, d)</span><br><span class="line">|| Collinear(c, d, a) || Collinear(c, d, b))</span><br><span class="line"><span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> (Left(a, b, c) ^ Left(a, b, d))</span><br><span class="line">&amp;&amp; (Left(c, d, a) ^ Left(c, d, b));</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;summary&gt;</span></span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> 线段ab与线段cd相交</span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;/summary&gt;</span></span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;returns&gt;</span>ab与cd相交则返回true，否则false<span class="doctag">&lt;/returns&gt;</span></span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="built_in">bool</span> <span class="title">Intersect</span>(<span class="params">Vector2 a, Vector2 b, Vector2 c, Vector2 d</span>)</span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (IntersectStrictly(a, b, c, d))</span><br><span class="line"><span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (Between(a, b, c) || Between(a, b, d)</span><br><span class="line">|| Between(c, d, a) || Between(c, d, b))</span><br><span class="line"><span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line"></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><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;summary&gt;</span></span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> 判断在abc共线时，<span class="doctag">&lt;paramref name=&quot;c&quot;/&gt;</span> 是否在 <span class="doctag">&lt;paramref name=&quot;a&quot;/&gt;</span>, <span class="doctag">&lt;paramref name=&quot;b&quot;/&gt;</span> 中间</span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;/summary&gt;</span></span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;returns&gt;</span><span class="doctag">&lt;paramref name=&quot;c&quot;/&gt;</span>在<span class="doctag">&lt;paramref name=&quot;a&quot;/&gt;</span>, <span class="doctag">&lt;paramref name=&quot;b&quot;/&gt;</span>中间则返回true，否则返回false，abc不共线直接返回false<span class="doctag">&lt;/returns&gt;</span></span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="built_in">bool</span> <span class="title">Between</span>(<span class="params">Vector2 a, Vector2 b, Vector2 c</span>)</span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (!Collinear(a, b, c)) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (a.x == b.x)</span><br><span class="line"><span class="keyword">return</span> (a.y &lt;= c.y &amp;&amp; c.y &lt;= b.y)</span><br><span class="line">|| (b.y &lt;= c.y &amp;&amp; c.y &lt;= a.y);</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line"><span class="keyword">return</span> (a.x &lt;= c.x &amp;&amp; c.x &lt;= b.x)</span><br><span class="line">|| (b.x &lt;= c.x &amp;&amp; c.x &lt;= a.x);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>解决了这两个问题后，我们可以用以下代码，解决问题：[[#2. 如何找到一个完全在内部的对角线]]</p><figure class="highlight csharp"><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="comment"><span class="doctag">///</span> <span class="doctag">&lt;summary&gt;</span></span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> 对角线ab是否与非点 <span class="doctag">&lt;paramref name=&quot;a&quot;/&gt;</span>, <span class="doctag">&lt;paramref name=&quot;b&quot;/&gt;</span> 相邻的边相交</span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;/summary&gt;</span></span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;returns&gt;</span>有相交返回true，否则false<span class="doctag">&lt;/returns&gt;</span></span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="built_in">bool</span> <span class="title">DiagonalWithoutIntersect</span>(<span class="params">Vertex a, Vertex b</span>)</span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">Vertex current = a.Next;</span><br><span class="line"><span class="keyword">while</span> (current.Next != a)</span><br><span class="line">&#123;</span><br><span class="line">Vertex next = current.Next;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (current != a &amp;&amp; current != b</span><br><span class="line">&amp;&amp; next != a &amp;&amp; next != b</span><br><span class="line">&amp;&amp; Intersect(current.Position, next.Position, a.Position, b.Position))</span><br><span class="line"><span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line">current = next;</span><br><span class="line">&#125;</span><br><span class="line"></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="comment"><span class="doctag">///</span> <span class="doctag">&lt;summary&gt;</span></span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> 对角线ab是否为内部对角线</span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;/summary&gt;</span></span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;remarks&gt;</span>内部对角线: 指对角线完全在多边形的内部<span class="doctag">&lt;/remarks&gt;</span></span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;returns&gt;</span>ab为内部对角线返回true，否则false<span class="doctag">&lt;/returns&gt;</span></span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="built_in">bool</span> <span class="title">Diagonal</span>(<span class="params">Vertex a, Vertex b</span>)</span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">return</span> InCone(a, b) &amp;&amp; InCone(b, a) &amp;&amp; DiagonalWithoutIntersect(a, b);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="递归求解"><a href="#递归求解" class="headerlink" title="递归求解"></a>递归求解</h3><p>由于我们的<code>Vertex</code>类型中记录了点的前驱<code>Prev</code>和后继<code>Next</code>，所以其实我们只需要拿到多边形中的任意一个点，就可以不断地通过<code>Next</code>获取到多边形中所有的顶点。所以我们可以定义如下多边形<code>polygon</code>类型：</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">Polygon</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">public</span> Vertex StartVertex =&gt; _startVertex;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> Vertex _startVertex;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">region</span> Methods...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>然后定义方法<code>Split()</code>去不断的完成本文最开头的5条方法步骤。不过在这里还有几个细节要处理。</p><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images//2022/11/27/844a90258687e95f0aac803bbc0cb064.png" alt="分割过程"></p><p>可以看到，在以$AC$为对角线，分割$P$为$P1,P2$后，需要补充两个点$A^\prime,C^\prime$。这一部分主要是各种链表的操作（因为我们用<code>Prev,Next</code>等将<code>Vertex</code>连接起来，其实就是一个双向循环链表）。在如下代码中，我标注了每个变量对应上图中的点，方便对照看去理解。</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;summary&gt;</span></span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> 将Polygon分割成若干凸多边形，使用 divide-and-conquer 方法；</span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> 该方法基于 Arkin, Ronald C.的论文</span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> &quot;Path planning for a vision-based autonomous robot&quot;.</span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;/summary&gt;</span></span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;returns&gt;</span>凸多边形数组，即此多边形的分割结果。<span class="doctag">&lt;/returns&gt;</span></span></span><br><span class="line"><span class="function"><span class="keyword">public</span> List&lt;Convex&gt; <span class="title">Split</span>(<span class="params"></span>)</span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">List&lt;Convex&gt; convexList = <span class="keyword">new</span> List&lt;Convex&gt;();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 寻找一个凹的顶点 concave 对应上图的C点</span></span><br><span class="line">Vertex concave = FindConcaveVertex();</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (concave == <span class="literal">null</span>)</span><br><span class="line">&#123;</span><br><span class="line"><span class="comment">// 没找到凹的顶点，说明this本身就是凸多边形</span></span><br><span class="line">convexList.Add(<span class="keyword">new</span> Convex(<span class="keyword">this</span>));</span><br><span class="line"><span class="keyword">return</span> convexList;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="comment">// 找一个poly上的点，和concave组一个内部对角线，将poly分割成两部分</span></span><br><span class="line">Vertex splitVertex = <span class="literal">null</span>;</span><br><span class="line">Vertex current = _startVertex;</span><br><span class="line"><span class="keyword">do</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">if</span> (BasicOperations.Diagonal(current, concave))</span><br><span class="line">&#123;</span><br><span class="line">splitVertex = current;</span><br><span class="line"><span class="keyword">break</span>;</span><br><span class="line">&#125;</span><br><span class="line">current = current.Next;</span><br><span class="line">&#125; <span class="keyword">while</span> (current != _startVertex);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 这种情况理论上不会发生 //<span class="doctag">TODO:</span> 报个Warning</span></span><br><span class="line"><span class="keyword">if</span> (splitVertex == <span class="literal">null</span>) &#123; <span class="keyword">return</span> <span class="literal">null</span>; &#125;</span><br><span class="line"><span class="comment">// splitVertex对应上图中的A点</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 将此多边形拆分成两个子多边形</span></span><br><span class="line">Vertex splitVertexNext = splitVertex.Next; <span class="comment">// 对应上图B点</span></span><br><span class="line">Vertex concavePrev = concave.Prev; <span class="comment">// 对应上图B点</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 将AC连接起来</span></span><br><span class="line">splitVertex.Next = concave;</span><br><span class="line">concave.Prev = splitVertex;</span><br><span class="line"></span><br><span class="line"><span class="comment">// A点的补充点 A&#x27;</span></span><br><span class="line">Vertex suppliedSplitVertex = <span class="keyword">new</span> Vertex(splitVertex.Position);</span><br><span class="line"><span class="comment">// C点的补充点 C&#x27;</span></span><br><span class="line">Vertex suppliedConcave = <span class="keyword">new</span> Vertex(concave.Position);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 将A&#x27;C&#x27;连接起来</span></span><br><span class="line">suppliedSplitVertex.Prev = suppliedConcave;</span><br><span class="line">suppliedConcave.Next = suppliedSplitVertex;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 将A&#x27;和B连接起来，C&#x27;和B连接起来</span></span><br><span class="line">suppliedSplitVertex.Next = splitVertexNext;</span><br><span class="line">splitVertexNext.Prev = suppliedSplitVertex;</span><br><span class="line">suppliedConcave.Prev = concavePrev;</span><br><span class="line">concavePrev.Next = suppliedConcave;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 生成P1，P2</span></span><br><span class="line">Polygon childPolygon1 = <span class="keyword">new</span> Polygon(concave);</span><br><span class="line">Polygon childPolygon2 = <span class="keyword">new</span> Polygon(suppliedConcave);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 递归求解</span></span><br><span class="line">convexList.AddRange(childPolygon1.Split());</span><br><span class="line">convexList.AddRange(childPolygon2.Split());</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> convexList;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里我们引入了一个新类型<code>Convex</code>表示凸多边形。当然我们可以让<code>Convex</code>类继承自<code>polygon</code>，不过我们并不需要<code>Convex</code>包含太多方法（例如<code>Split()</code>等），故而我们单独开一个类表示它。</p><figure class="highlight csharp"><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">public</span> <span class="keyword">class</span> <span class="title">Convex</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">public</span> Vertex StartVertex =&gt; _startVertex;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> Vertex _startVertex;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="孔洞的处理"><a href="#孔洞的处理" class="headerlink" title="孔洞的处理"></a>孔洞的处理</h2><p>到此为止其实我们已经可以很好的将一个任意多边形分割成若干凸多边形了，但是我们忽略了一个很重要的事，就是中间有孔的多边形应该怎么处理？</p><blockquote><p>对于孔洞的处理算法，来源于 <a href="https://github.com/recastnavigation/recastnavigation">Recast Navigation</a></p></blockquote><p>首先孔也是一个多边形，我们记为$Hole$ 。</p><ol><li><p>在$Hole$中选择一个顶点（如下图$F$）</p></li><li><p>在$P$上选择一个顶点（如下图$A$），且$AF$不与$P$或$Hole$上的任意非$A, F$相邻边相交</p></li></ol><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images//2022/11/27/1a6669a55668faeeacb7053888ef5c2c.png" alt="孔洞的处理"></p><p>不合法的选择，例如$FC$，会与$Hole$上的边$IH$相交；例如$GD$，会与$P$上的边$BC$相交。</p><ol><li>沿着选择的连线（如上图$AF$），将$P$与$Hole$融合</li></ol><p>这里要时刻记住我们的多边形，顶点都是按照逆时针顺序连接的，$Hole$也不例外。然而当我们融合时，需要顺时针将$Hole$上的顶点添加到$P$中，这样得到的$P$的顶点才满足逆时针连接的条件。所以这里我们需要经过一个类似反转链表的过程。当然和之前相同的，这里我们也需要补充两个点$A^\prime,F^\prime$。</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;summary&gt;</span></span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> 处理中间包含孔的多边形</span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;/summary&gt;</span></span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">MergeHole</span>(<span class="params">Polygon hole</span>)</span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (!HasInnerPolygon(hole)) <span class="comment">// hole必须在多边形内部才有必要去合并</span></span><br><span class="line"><span class="keyword">return</span>; </span><br><span class="line"></span><br><span class="line"><span class="built_in">bool</span> hasFound = <span class="literal">false</span>;</span><br><span class="line">Vertex linkVertPoly = <span class="literal">null</span>, linkVertHole = <span class="literal">null</span>;</span><br><span class="line">Vertex holeVertex = hole.StartVertex;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 在P和hole上找到两个点，用其连线合并P和hole </span></span><br><span class="line"><span class="keyword">do</span></span><br><span class="line">&#123;</span><br><span class="line">Vertex vert = StartVertex;</span><br><span class="line"><span class="keyword">do</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="comment">// vert - holeVertex 线段，不与P或者hole上的任何一个非相邻边相交</span></span><br><span class="line"><span class="keyword">if</span> (BasicOperations.DiagonalWithoutIntersect(vert, holeVertex)</span><br><span class="line">&amp;&amp; BasicOperations.DiagonalWithoutIntersect(holeVertex, vert))</span><br><span class="line">&#123;</span><br><span class="line">linkVertPoly = vert; <span class="comment">// 对应上图中的点A</span></span><br><span class="line">linkVertHole = holeVertex; <span class="comment">// 对应上图中的点F</span></span><br><span class="line">hasFound = <span class="literal">true</span>;</span><br><span class="line"><span class="keyword">break</span>;</span><br><span class="line">&#125;</span><br><span class="line">vert = vert.Next;</span><br><span class="line">&#125; <span class="keyword">while</span> (vert != StartVertex);</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (hasFound) <span class="keyword">break</span>;</span><br><span class="line">holeVertex = holeVertex.Next;</span><br><span class="line">&#125; <span class="keyword">while</span> (holeVertex != hole.StartVertex);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 将P与hole融合</span></span><br><span class="line"><span class="keyword">if</span> (hasFound)</span><br><span class="line">&#123;</span><br><span class="line"><span class="comment">// 将A和F连接起来</span></span><br><span class="line">Vertex linkVertPolyNext = linkVertPoly.Next; <span class="comment">// 对应上图点B</span></span><br><span class="line">linkVertPoly.Next = linkVertHole;</span><br><span class="line">Vertex linkVertHolePrev = linkVertHole.Prev; <span class="comment">// 对应上图点I</span></span><br><span class="line">linkVertHole.Prev = linkVertPoly;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 反转链表</span></span><br><span class="line"><span class="comment">// 在上图中可以看到F-&gt;I-&gt;H-&gt;G变成了F-&gt;G-&gt;H-&gt;I</span></span><br><span class="line"><span class="comment">// 但是在实现的时候，并不会更换顶点，而是将Next指针和Prev指针做个交换</span></span><br><span class="line">Vertex prev = linkVertHole, current = linkVertHolePrev;</span><br><span class="line"><span class="keyword">while</span> (current != linkVertHole)</span><br><span class="line">&#123;</span><br><span class="line">Vertex currentPrev = current.Prev;</span><br><span class="line">prev.Next = current;</span><br><span class="line">current.Prev = prev;</span><br><span class="line"></span><br><span class="line">prev = current;</span><br><span class="line">current = currentPrev;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 补充linkVertHole重合节点， 对应上图的点F&#x27;</span></span><br><span class="line"><span class="keyword">var</span> suppliedLinkVertHole = <span class="keyword">new</span> Vertex();</span><br><span class="line">suppliedLinkVertHole.Position = linkVertHole.Position;</span><br><span class="line"><span class="comment">// 连接I和F&#x27;</span></span><br><span class="line">suppliedLinkVertHole.Prev = prev;</span><br><span class="line">prev.Next = suppliedLinkVertHole; </span><br><span class="line"></span><br><span class="line"><span class="comment">// 补充linkVertPoly重合节点， 对应上图的点A&#x27;</span></span><br><span class="line"><span class="keyword">var</span> suppliedLinkVertPoly = <span class="keyword">new</span> Vertex();</span><br><span class="line">suppliedLinkVertPoly.Position = linkVertPoly.Position;</span><br><span class="line"><span class="comment">// 连接A&#x27;F&#x27;</span></span><br><span class="line">suppliedLinkVertPoly.Prev = suppliedLinkVertHole;</span><br><span class="line">suppliedLinkVertHole.Next = suppliedLinkVertPoly;</span><br><span class="line"><span class="comment">// 连接A&#x27;B</span></span><br><span class="line">suppliedLinkVertPoly.Next = linkVertPolyNext;</span><br><span class="line">linkVertPolyNext.Prev = suppliedLinkVertPoly;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="在Unity中展示"><a href="#在Unity中展示" class="headerlink" title="在Unity中展示"></a>在Unity中展示</h2><p>当我们完成了全部的计算任务后，就可以在Unity中，将多边形绘制出来了。在这里我利用Unity自带的<code>lineRenderer</code>进行线段的绘制。</p><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images//2022/11/27/8c4fcf4dc48b319417c79564b2ae17ef.png" alt="演示效果"></p><p>再上图中，可以在<code>Inspector</code>窗口编辑多边形$P$以及孔洞$Hole$的个顶点坐标（注意要按照逆时针顺序），用黑色的线绘制出了$P$，用红色的线绘制出了$Hole$，蓝色的线即分割线。注意有一条黑色的线连接了$P$和$Hole$，这即是我们选择来融合$P$和$Hole$的连接线</p><h2 id="补充知识"><a href="#补充知识" class="headerlink" title="补充知识"></a>补充知识</h2><h3 id="判断一个多边形是否在另一个多边形内部"><a href="#判断一个多边形是否在另一个多边形内部" class="headerlink" title="判断一个多边形是否在另一个多边形内部"></a>判断一个多边形是否在另一个多边形内部</h3><p>在<code>MergeHole()</code>方法中有一段代码：</p><figure class="highlight csharp"><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"><span class="keyword">if</span> (!HasInnerPolygon(hole)) <span class="comment">// hole必须在多边形内部才有必要去合并</span></span><br><span class="line"><span class="keyword">return</span>; </span><br></pre></td></tr></table></figure><p>进行了两个多边形相容性的判断，当$hole$完全被$P$包含时，返回<code>true</code>，并进行后续的融合操作。</p><p>而这里如何判断一个多边形是否被另一个多边形包含，是通过判断$hole$中的每个顶点是否在$P$中，若$hole$的全部顶点都在$P$中，则认为$hole$被$P$包含。由此将问题转化为了：如何判断一个顶点是否在一个多边形中。</p><p>解决这个问题有一个很经典的方法：</p><p><strong>引射线法：</strong> 从目标点出发引一条射线（我们可以取水平向右的射线），看这条射线和多边形所有边的交点数目。如果有奇数个交点，则说明在内部，如果有偶数个交点，则说明在外部。</p><p>看上去很好实现，只需要$O(n)$枚举多边形的每条边，然后判断是否相交即可，但是其实还是有不少细节的。</p><p>例如下图中这种过顶点的情况，如果不确定好统计规则，很容易顶点两条边都被记录一次，导致本在内部被判定为在外部（例如最下方的红点，经过多边形的顶点，会被记为经过了两条边）</p><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images//2022/11/27/8a26d2584a0e6086e70c774164024861.png" alt="引射线法"></p><p>此外，线段与射线的关系可以用下图展示：</p><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images//2022/11/27/3ead29b1aeb745e8fa86cd9b1d9b2db3.png" alt="线段与射线的关系"></p><p>我们可以很容易的判断线段在射线的上、下、左、重合/平行。为了处理前面说的过顶点的情况，我们认为经过线段下侧端点的射线与线段不相交。而剩下的就是计算一下线段上对应射线所在$y$坐标点的$x$坐标值，和射线起点的$x$坐标值作比较，如果大于射线起点的$x$坐标，则说明有交点。</p><figure class="highlight csharp"><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"><span class="doctag">///</span> <span class="doctag">&lt;summary&gt;</span></span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> 从 <span class="doctag">&lt;paramref name=&quot;raySource&quot;/&gt;</span>发出的水平向右的射线，是否与线段ab相交</span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;/summary&gt;</span></span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;returns&gt;</span>相交则返回true，否则返回false<span class="doctag">&lt;/returns&gt;</span></span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="built_in">bool</span> <span class="title">HorizontalRayIntersectSegment</span>(<span class="params">Vector2 raySource, Vector2 a, Vector2 b</span>)</span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (a.y == b.y) <span class="keyword">return</span> <span class="literal">false</span>;                               <span class="comment">// 线段与射线平行、重合</span></span><br><span class="line"><span class="keyword">if</span> (a.y &gt; raySource.y &amp;&amp; b.y &gt; raySource.y) <span class="keyword">return</span> <span class="literal">false</span>;   <span class="comment">// 线段在射线上方</span></span><br><span class="line"><span class="keyword">if</span> (a.y &lt; raySource.y &amp;&amp; b.y &lt; raySource.y) <span class="keyword">return</span> <span class="literal">false</span>;   <span class="comment">// 线段在射线下方</span></span><br><span class="line"><span class="keyword">if</span> ((b.y &lt; a.y &amp;&amp; b.y == raySource.y)</span><br><span class="line">|| (a.y &lt; b.y &amp;&amp; a.y == raySource.y)) <span class="keyword">return</span> <span class="literal">false</span>;     <span class="comment">// 射线与下方端点相交</span></span><br><span class="line"><span class="keyword">if</span> (a.x &lt; raySource.x &amp;&amp; b.x &lt; raySource.x) <span class="keyword">return</span> <span class="literal">false</span>;   <span class="comment">// 线段在射线左边</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> x = b.x - (b.x - a.x) * (b.y - raySource.y) / (b.y - a.y); <span class="comment">// 求交点x坐标</span></span><br><span class="line"><span class="keyword">return</span> x &gt;= raySource.x;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="补充阅读"><a href="#补充阅读" class="headerlink" title="补充阅读"></a>补充阅读</h3><p><a href="https://zhuanlan.zhihu.com/p/579209277">留白- Recast Navigation 源码剖析 01 - Meadow Map论文解析与实验</a></p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;多边形分割成若干凸多边形（NavMesh的初步形成）&quot;&gt;&lt;a href=&quot;#多边形分割成若干凸多边形（NavMesh的初步形成）&quot; class=&quot;headerlink&quot; title=&quot;多边形分割成若干凸多边形（NavMesh的初步形成）&quot;&gt;&lt;/a&gt;多边形分割成若干</summary>
      
    
    
    
    <category term="游戏开发" scheme="http://www.fcayh.cn/categories/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/"/>
    
    
    <category term="C#" scheme="http://www.fcayh.cn/tags/C/"/>
    
    <category term="Unity" scheme="http://www.fcayh.cn/tags/Unity/"/>
    
    <category term="计算几何" scheme="http://www.fcayh.cn/tags/%E8%AE%A1%E7%AE%97%E5%87%A0%E4%BD%95/"/>
    
    <category term="NavMesh" scheme="http://www.fcayh.cn/tags/NavMesh/"/>
    
  </entry>
  
  <entry>
    <title>用网格去拟合一条线段</title>
    <link href="http://www.fcayh.cn/2022/10/18/draw-a-line/"/>
    <id>http://www.fcayh.cn/2022/10/18/draw-a-line/</id>
    <published>2022-10-17T16:53:55.000Z</published>
    <updated>2022-12-23T05:59:09.444Z</updated>
    
    <content type="html"><![CDATA[<h1 id="用网格去拟合一条线段"><a href="#用网格去拟合一条线段" class="headerlink" title="用网格去拟合一条线段"></a>用网格去拟合一条线段</h1><p>** 源代码: ** <a href="https://github.com/FcAYH/Draw-A-Line">Draw-A-Line</a></p><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>最开始是在（不记得那家公司）的笔试中，遇到了这个问题，说是给定一个线段的起点和终点，问这个线段经过了多少个网格？</p><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images/2022/10/17/22625bbe2012973bff0cfa4caea93ad9.png" alt="示例"></p><p>如上图，这个线段经过了12个格子。</p><p>当时考场上自己想了个插值法，就是每隔固定的间距$\Delta$做一次判断，将该点所在的方格添加到线段经过的方格集合中。当然这个方法是一定有bug的啦，例如：</p><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images/2022/10/17/6cb9b593121f4ff15f582559c740a797.png" alt="采样点太少"></p><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images/2022/10/17/bc40a38ff382a49abd906f325982bec7.png" alt="采样点永远无法覆盖全部"></p><p>即便采样点多一些，依然会出现上图中间那种情况，同时无限制的增多采样点也会带来比较大的性能问题。</p><p>所以我们可以用类似bfs的思路去解决这个问题。不过这个其实不是今天的重点，今天的重点是拟合，也就是说我们不需要真的把线段穿过的网格都涂黑，我们是寻找一个比较优的思路去涂黑网格，来使得得到的图像与我们画的直线尽可能拟合。</p><h2 id="插值法"><a href="#插值法" class="headerlink" title="插值法"></a>插值法</h2><p>插值法的思想很好理解，我们有起点<code>(x1, y1)</code>和终点<code>(x2, y2)</code>。那么我们就可以得到线段的长度<code>length</code>了。随后假设我们要在线段上设置3个采样点，也就是起点，中点，终点啦，那么三个采样点的坐标都可以用着一个公式去计算：</p><p>$\begin{cases}x_i = x_1 + length \times \frac{i}{2} \\ y_i = y_1 + length \times \frac{i}{2} \end{cases} \ (i = 0, 1, 2)$</p><p>推广到n个采样点，公式如下：</p><p>$\begin{cases}x_i = x_1 + length \times \frac{i}{n - 1} \\ y_i = y_1 + length \times \frac{i}{n - 1} \end{cases} \ (i = 0, 1, 2 … n - 1)$</p><p>接下来就是插值法最重要的一个抉择了，到底选多少个采样点呢？ 这里我们可以这样子思考，假设<code>(x1 = 0, y1 = 0)</code>, <code>(x2 = 5, y2 = 3)</code>。那么选择采样点数目2，3，4，5，8，10的效果如下图：</p><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images/2022/10/17/ec1dccc656483e2757788fbefd8f5004.png" alt="插值法示例"></p><p>我们发现，在点数等于5的时候，我们得到了一个比较好的结果，即所有的格子都连起来了（上下左右或者对角线相邻）。小于5个点，得到的图像是中间有断点的，大于5个点，也没有明显更优（例如8个点时和5个点相同，10个点时倒是将线段经过的网格都显示出来了，但是计算量翻倍了呀）</p><p>最后其实得到的小结论就是用$Max(|x2 - x1|, |y2 - y1|)$作为采样点的个数最优，可以保证得到的结果是一个连续的图形，并且有着最少的计算次数。</p><p>证明的话，（说实话感觉画个图自己就明白了其实）</p><script type="math/tex; mode=display">$\begin{align*}&设格子边长为单位长度1, \\&线段上现在有某点 (x, y), \\&容易算出来该点所在格子坐标为(\left\lfloor x \right\rfloor, \left\lfloor y \right\rfloor); \\&\! \\&设|x_2 - x_1| \ge |y_2 - y_1|, \\&此时线段斜率为[-1, 1], \\&那么设 x + 1 后，带入线段得点(x + 1, y + \Delta y); \\&\! \\&由斜率，易知 \Delta y \in [-1, 1], \\&由已知格子长1，和\Delta y的范围, 可得:  \\&点(x + 1, y + \Delta y) 存在于线段 (x + 1, y - 1) \sim (x + 1, y + 1)上; \\&\! \\&而整个线段(x + 1, y - 1) \sim (x + 1, y + 1)所在的格子均与格子(\left\lfloor x \right\rfloor, \left\lfloor y \right\rfloor)相邻; \\&\! \\&同理证明|x_2 - x_1| < |y_2 - y_1|时,\\&线段斜率为(-\infty, -1) \cup (1, \infty), \\&\! \\&所以y + 1后，带入线段得点(x + \Delta x, y + 1), \\&且\Delta x \in (-1, 1), \\&故而点(x + \Delta x, y + 1)存在于线段(x - 1, y + 1) \sim (x + 1, y + 1)上; \\&\! \\&而整个线段(x - 1, y + 1) \sim (x + 1, y + 1)所在的格子均与格子(\left\lfloor x \right\rfloor, \left\lfloor y \right\rfloor)相邻; \\&\! \\&同理，当x-1，y-1的情况与上述情况类似, \\&所以可以证明用Max(|x2 - x1|, |y2 - y1|)作为采样点的个数，可以保证得到的是一个连续的图形。\end{align*}$</script><p>代码的话也比较简单：</p><figure class="highlight csharp"><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">/*</span></span><br><span class="line"><span class="comment">_start      -&gt; 线段起点 </span></span><br><span class="line"><span class="comment">_end        -&gt; 线段终点</span></span><br><span class="line"><span class="comment">_pointCount -&gt; 采样点数目</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="keyword">for</span> (<span class="built_in">int</span> i = <span class="number">0</span>; i &lt; _pointCount; i++)</span><br><span class="line">&#123;</span><br><span class="line">    Vector2 pos = <span class="keyword">new</span> Vector2();</span><br><span class="line">    pos.x = start.x + (_end.x - start.x) * i / (_pointCount - <span class="number">1</span>);</span><br><span class="line">    pos.y = start.y + (_end.y - start.y) * i / (_pointCount - <span class="number">1</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 用于在界面上显示，提前初始化了若干个gameObject，</span></span><br><span class="line">    <span class="comment">// 用到的时候就直接设置其位置，并显示出来</span></span><br><span class="line">    <span class="keyword">var</span> point = _pointList[i];</span><br><span class="line">    point.transform.position = pos;</span><br><span class="line">    point.SetActive(<span class="literal">true</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="扫描法（bfs）"><a href="#扫描法（bfs）" class="headerlink" title="扫描法（bfs）"></a>扫描法（bfs）</h2><p>扫描法，这个名字是我自己瞎起的，因为感觉像是从起点一点点扫到终点，核心思想是bfs。</p><p>首先将起点所在的格子添加到队列中（假设是格子<code>(x,y)</code>），再假设线段终点在起点左上方时，则我们只需要去看一下格子<code>(x + 1, y)</code>, <code>(x + 1, y + 1)</code>, <code>(x, y + 1)</code>是不是被该线段穿过，如果穿过了，则将其添加到队列中。这样不断地从队列中拿出格子来，对其右上三个格子做判断，并将符合条件的加入队列，直到走到了终点为止。这样我们就可以把线段穿过的所有格子都求到。</p><p>当然，起点和终点的位置关系不同时，要检查的格子是不同的，这里其实一共只有八种方向：</p><figure class="highlight plain"><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">  x,  y</span><br><span class="line">( 1,  1) -&gt; 左上</span><br><span class="line">( 1,  0) -&gt; 左侧</span><br><span class="line">( 1, -1) -&gt; 左下</span><br><span class="line">( 0, -1) -&gt; 下侧</span><br><span class="line">(-1, -1) -&gt; 右下</span><br><span class="line">(-1,  0) -&gt; 右侧</span><br><span class="line">(-1,  1) -&gt; 右上</span><br><span class="line">( 0,  1) -&gt; 上侧</span><br></pre></td></tr></table></figure><p>对于方向<code>(dirX, dirY)</code>，对于枚举出的格子<code>(x, y)</code>需要去检查<code>(x + dirX, y)</code>, <code>(x + dirX, y + dirY)</code>, <code>(x, y + dirY)</code>三个格子。</p><p>接下来就是处理，如何判断线段是否经过一个格子了，这里我采用的方法是，暴力（大雾）。即计算出直线一般表达式，带入格子四个顶点，如果值全大于等于0或者全小于等于0，说明不经过。如果有大于零有小于零，说明经过。</p><p>代码稍微长一点：</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">_curEnd   -&gt; 线段终点</span></span><br><span class="line"><span class="comment">_curStart -&gt; 线段起点</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 求方向向量，如果大小小于1明显是起点终点都在一个格子内，不用管的</span></span><br><span class="line"><span class="comment">// 注：“方向向量”应该是单位向量，但是我们这里只需要其值的正负，所以省去Normalized过程</span></span><br><span class="line">Vector2 dirVec = _curEnd - _curStart;</span><br><span class="line"><span class="keyword">if</span> (dirVec.sqrMagnitude &lt; <span class="number">1</span>) <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 根据方向向量，计算出我们的八个方向的向量</span></span><br><span class="line">Vector2Int dir = <span class="keyword">new</span> Vector2Int();</span><br><span class="line">dir.x = dirVec.x &gt; <span class="number">0</span> ? <span class="number">1</span> : dirVec.x &lt; <span class="number">0</span> ? <span class="number">-1</span> : <span class="number">0</span>;</span><br><span class="line">dir.y = dirVec.y &gt; <span class="number">0</span> ? <span class="number">1</span> : dirVec.y &lt; <span class="number">0</span> ? <span class="number">-1</span> : <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Debug.Log(&quot;dir: &quot; + dir.x + &quot; &quot; + dir.y);</span></span><br><span class="line"></span><br><span class="line">Queue&lt;(<span class="built_in">int</span>, <span class="built_in">int</span>)&gt; gridQ = <span class="keyword">new</span> Queue&lt;(<span class="built_in">int</span>, <span class="built_in">int</span>)&gt;();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 为起点染色</span></span><br><span class="line"><span class="built_in">int</span> startX = Mathf.FloorToInt(_curStart.x);</span><br><span class="line"><span class="built_in">int</span> startY = Mathf.FloorToInt(_curStart.y);</span><br><span class="line">(<span class="built_in">int</span> startGridX, <span class="built_in">int</span> startGridY) = WorldPointToGrid(startX, startY); <span class="comment">// 将世界坐标转为格子gameObject数组下标</span></span><br><span class="line">ColorAGrid(startGridX, startGridY);</span><br><span class="line">gridQ.Enqueue((startX, startY));</span><br><span class="line"></span><br><span class="line">(<span class="built_in">int</span>, <span class="built_in">int</span>) endGrid = (Mathf.FloorToInt(_curEnd.x), Mathf.FloorToInt(_curEnd.y)); <span class="comment">// 终点格子坐标</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">int</span> layer = <span class="number">0</span>;</span><br><span class="line"><span class="keyword">while</span> (<span class="literal">true</span>)</span><br><span class="line">&#123;</span><br><span class="line">    layer++;</span><br><span class="line">    <span class="keyword">if</span> (layer &gt; <span class="number">200</span>) <span class="keyword">break</span>; <span class="comment">// 总感觉写个while(true)会有死循环卡死程序的风险所以加了个魔法数</span></span><br><span class="line"></span><br><span class="line">    (<span class="built_in">int</span> x, <span class="built_in">int</span> y) = gridQ.Dequeue();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 当扫描到终点后就可以退出循环了</span></span><br><span class="line">    <span class="keyword">if</span> (x == endGrid.Item1 &amp;&amp; y == endGrid.Item2)</span><br><span class="line">    &#123;</span><br><span class="line">        (<span class="built_in">int</span> gridX, <span class="built_in">int</span> gridY) = WorldPointToGrid(x, y);</span><br><span class="line">        ColorAGrid(gridX, gridY);</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 预处理出当前要被检查的格子坐标</span></span><br><span class="line">    <span class="built_in">int</span>[,] worldPoints = <span class="keyword">new</span> <span class="built_in">int</span>[,]</span><br><span class="line">    &#123;</span><br><span class="line">        &#123;x + dir.x, y&#125;,</span><br><span class="line">        &#123;x + dir.x, y + dir.y&#125;,</span><br><span class="line">        &#123;x, y + dir.y&#125;</span><br><span class="line">    &#125;;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (<span class="built_in">int</span> i = <span class="number">0</span>; i &lt; <span class="number">3</span>; i++)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="built_in">int</span> worldPointX = worldPoints[i, <span class="number">0</span>], worldPointY = worldPoints[i, <span class="number">1</span>];</span><br><span class="line">        <span class="keyword">if</span> (IsLineThroughGrid(worldPointX, worldPointY)) <span class="comment">// 判断线段是否经过该格子</span></span><br><span class="line">        &#123;</span><br><span class="line">            (<span class="built_in">int</span> gridX, <span class="built_in">int</span> gridY) = WorldPointToGrid(worldPointX, worldPointY);</span><br><span class="line"></span><br><span class="line">            <span class="comment">// 超出地图范围了，不用处理，</span></span><br><span class="line">            <span class="keyword">if</span> (gridX &lt; <span class="number">0</span> || gridX &gt;= Row || gridY &lt; <span class="number">0</span> || gridY &gt;= Column)</span><br><span class="line">                <span class="keyword">continue</span>;</span><br><span class="line"></span><br><span class="line">            <span class="comment">// 已被染色的，不再处理</span></span><br><span class="line">            <span class="keyword">if</span> (_grids[gridX, gridY].color != Color.gray)</span><br><span class="line">            &#123;</span><br><span class="line">                ColorAGrid(gridX, gridY);</span><br><span class="line">                gridQ.Enqueue((worldPointX, worldPointY));</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><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">careVertex -&gt; 是否关心顶点，为true则线段经过格子顶点也算穿过格子</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="built_in">bool</span> <span class="title">IsLineThroughGrid</span>(<span class="params"><span class="built_in">int</span> nextGridX, <span class="built_in">int</span> nextGridY, <span class="built_in">bool</span> careVertex = <span class="literal">false</span></span>)</span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="comment">// Ax + by + c = 0; 直线的一般表达式</span></span><br><span class="line">    <span class="comment">// A = y2 - y1, B = x1 - x2, C = x2y1 - x1y2</span></span><br><span class="line">    <span class="built_in">float</span> A = _curEnd.y - _curStart.y;</span><br><span class="line">    <span class="built_in">float</span> B = _curStart.x - _curEnd.x;</span><br><span class="line">    <span class="built_in">float</span> C = _curEnd.x * _curStart.y - _curStart.x * _curEnd.y;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 格子的四个顶点</span></span><br><span class="line">    <span class="built_in">int</span>[,] vertexes = <span class="keyword">new</span> <span class="built_in">int</span>[,]</span><br><span class="line">    &#123;</span><br><span class="line">        &#123;nextGridX, nextGridY&#125;,</span><br><span class="line">        &#123;nextGridX + <span class="number">1</span>, nextGridY&#125;,</span><br><span class="line">        &#123;nextGridX, nextGridY + <span class="number">1</span>&#125;,</span><br><span class="line">        &#123;nextGridX + <span class="number">1</span>, nextGridY + <span class="number">1</span>&#125;</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="built_in">float</span>[] values = <span class="keyword">new</span> <span class="built_in">float</span>[<span class="number">4</span>];</span><br><span class="line">    <span class="keyword">for</span> (<span class="built_in">int</span> i = <span class="number">0</span>; i &lt; <span class="number">4</span>; i++)</span><br><span class="line">    &#123;</span><br><span class="line">        values[i] = A * vertexes[i, <span class="number">0</span>] + B * vertexes[i, <span class="number">1</span>] + C;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">bool</span> through;</span><br><span class="line">    <span class="keyword">if</span> (careVertex)</span><br><span class="line">    &#123;</span><br><span class="line">        through = !((values[<span class="number">0</span>] &gt; <span class="number">0</span> &amp;&amp; values[<span class="number">1</span>] &gt; <span class="number">0</span> &amp;&amp; values[<span class="number">2</span>] &gt; <span class="number">0</span> &amp;&amp; values[<span class="number">3</span>] &gt; <span class="number">0</span>)</span><br><span class="line">                    || (values[<span class="number">0</span>] &lt; <span class="number">0</span> &amp;&amp; values[<span class="number">1</span>] &lt; <span class="number">0</span> &amp;&amp; values[<span class="number">2</span>] &lt; <span class="number">0</span> &amp;&amp; values[<span class="number">3</span>] &lt; <span class="number">0</span>));</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">    &#123;</span><br><span class="line">        through = !((values[<span class="number">0</span>] &gt;= <span class="number">0</span> &amp;&amp; values[<span class="number">1</span>] &gt;= <span class="number">0</span> &amp;&amp; values[<span class="number">2</span>] &gt;= <span class="number">0</span> &amp;&amp; values[<span class="number">3</span>] &gt;= <span class="number">0</span>)</span><br><span class="line">                    || (values[<span class="number">0</span>] &lt;= <span class="number">0</span> &amp;&amp; values[<span class="number">1</span>] &lt;= <span class="number">0</span> &amp;&amp; values[<span class="number">2</span>] &lt;= <span class="number">0</span> &amp;&amp; values[<span class="number">3</span>] &lt;= <span class="number">0</span>));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> through;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Extremely-Fast-Line-Algorithm（EFLA）"><a href="#Extremely-Fast-Line-Algorithm（EFLA）" class="headerlink" title="Extremely Fast Line Algorithm（EFLA）"></a>Extremely Fast Line Algorithm（EFLA）</h2><p>困了，抽空补上，，<br>参考的Po-Han Lin的算法：<br><a href="http://www.edepot.com/algorithm.html">EFLA</a></p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;用网格去拟合一条线段&quot;&gt;&lt;a href=&quot;#用网格去拟合一条线段&quot; class=&quot;headerlink&quot; title=&quot;用网格去拟合一条线段&quot;&gt;&lt;/a&gt;用网格去拟合一条线段&lt;/h1&gt;&lt;p&gt;** 源代码: ** &lt;a href=&quot;https://github.com</summary>
      
    
    
    
    <category term="游戏开发" scheme="http://www.fcayh.cn/categories/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/"/>
    
    
    <category term="C#" scheme="http://www.fcayh.cn/tags/C/"/>
    
    <category term="Unity" scheme="http://www.fcayh.cn/tags/Unity/"/>
    
    <category term="模拟" scheme="http://www.fcayh.cn/tags/%E6%A8%A1%E6%8B%9F/"/>
    
    <category term="BFS" scheme="http://www.fcayh.cn/tags/BFS/"/>
    
    <category term="搜索" scheme="http://www.fcayh.cn/tags/%E6%90%9C%E7%B4%A2/"/>
    
  </entry>
  
  <entry>
    <title>C++类对象在内存中的存储</title>
    <link href="http://www.fcayh.cn/2022/09/08/cplusplus-class-in-memery/"/>
    <id>http://www.fcayh.cn/2022/09/08/cplusplus-class-in-memery/</id>
    <published>2022-09-08T13:32:12.000Z</published>
    <updated>2022-12-23T05:59:54.404Z</updated>
    
    <content type="html"><![CDATA[<h1 id="C-类对象在内存中的存储"><a href="#C-类对象在内存中的存储" class="headerlink" title="C++类对象在内存中的存储"></a>C++类对象在内存中的存储</h1><blockquote><p>事情是因为面试的时候，被问到了一些奇奇怪怪的用法，所以来研究了一下C++中类对象在内存中是怎么存储的。</p></blockquote><h2 id="malloc和new的区别"><a href="#malloc和new的区别" class="headerlink" title="malloc和new的区别"></a>malloc和new的区别</h2><ul><li>[ ] 这一块内容挺多的，有必要单独开个文章写</li></ul><p>先记录一下malloc和new的区别：</p><ol><li><p>malloc返回一个void*类型的指针，需要自己进行强转。而new根据指定类型返回指定类型的指针。</p></li><li><p>malloc需要自己指定分配内存的大小，以参数形式传入。而new不需要。</p></li><li><p>malloc是函数，而new是运算符。new可以被重载。</p></li><li><p>malloc不会调用类的构造函数，而new会调用。同理，free不会调用类的析构函数，而delete会。</p></li></ol><h2 id="奇怪的写法"><a href="#奇怪的写法" class="headerlink" title="奇怪的写法"></a>奇怪的写法</h2><blockquote><p>当然了，个人认为这一系列的写法，都不符合标准，不管是在学习还是在生产中都应该避免使用上述写法。但是有时研究一下这些写法的结果，会让我们对C++的理解更加深刻。</p></blockquote><p>首先我们定义类如下：</p><figure class="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><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">CppTest</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="keyword">int</span> val;</span><br><span class="line"></span><br><span class="line">    CppTest(<span class="keyword">int</span> x) : val(x) &#123;&#125;</span><br><span class="line">    ~CppTest()</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="built_in">cout</span> &lt;&lt; <span class="string">&quot;free&quot;</span> &lt;&lt; <span class="built_in">endl</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">FunA</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">cout</span> &lt;&lt; <span class="string">&quot;Hello world&quot;</span> &lt;&lt; <span class="built_in">endl</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">FunB</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">cout</span> &lt;&lt; <span class="string">&quot;Good night&quot;</span> &lt;&lt; <span class="built_in">endl</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    CppTest* p = <span class="keyword">new</span> CppTest(<span class="number">2</span>);</span><br><span class="line"></span><br><span class="line">    p-&gt;FunA();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">delete</span> p;</span><br><span class="line"></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><h3 id="在析构函数中delete"><a href="#在析构函数中delete" class="headerlink" title="在析构函数中delete"></a>在析构函数中delete</h3><p>如果我们魔改一下析构函数，改成下面这样子：</p><figure class="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">~CppTest()</span><br><span class="line">&#123;</span><br><span class="line">    <span class="built_in">cout</span> &lt;&lt; <span class="string">&quot;free&quot;</span> &lt;&lt; <span class="built_in">endl</span>;</span><br><span class="line">    <span class="keyword">delete</span> <span class="keyword">this</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>会发生什么事？</p><h3 id="在成员函数中delete"><a href="#在成员函数中delete" class="headerlink" title="在成员函数中delete"></a>在成员函数中delete</h3><p>如果我们魔改一下FunA，改成下面这样子：</p><figure class="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="function"><span class="keyword">void</span> <span class="title">FunA</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">delete</span> <span class="keyword">this</span>;</span><br><span class="line">    <span class="built_in">cout</span> &lt;&lt; <span class="string">&quot;Hello world&quot;</span> &lt;&lt; <span class="built_in">endl</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>会发生什么事？</p><p>如果再魔改一下，这样子：</p><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">FunA</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">delete</span> <span class="keyword">this</span>;</span><br><span class="line">    <span class="built_in">cout</span> &lt;&lt; <span class="string">&quot;Hello world&quot;</span> &lt;&lt; <span class="built_in">endl</span>;</span><br><span class="line"></span><br><span class="line">    FunB();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>又会有怎么样的输出呢？</p><h3 id="空指针能否调用函数？"><a href="#空指针能否调用函数？" class="headerlink" title="空指针能否调用函数？"></a>空指针能否调用函数？</h3><p>如果我们修改调用方式如下：</p><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    CppTest* p = nullpter;</span><br><span class="line">    p-&gt;FunA();</span><br><span class="line"></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><p>能否成功调用？</p><h2 id="解答"><a href="#解答" class="headerlink" title="解答"></a>解答</h2><p><strong>1. 当在析构函数中调用delete时，会触发循环递归，导致程序无法正常退出</strong></p><p>首先delete会调用析构函数，故而<code>delete p;</code>会跳转到我们的<code>~CppTest()</code>，然而我们在<code>~CppTest()</code>中又触发<code>delete this</code>，就导致了又跳转回了<code>~CppTest()</code>，反复循环递归。</p><p><strong>2. 在成员函数中delete，并不会中断成员函数的运行，也不会影响再调用其他成员函数</strong></p><p>即在<code>FunA()</code>中，运行<code>delete this;</code>，程序仍然会照常输出<code>Hello world</code>，同时也能正常调用<code>FunB()</code>，输出<code>Good night</code>。</p><p>这个现象我们就能够猜测，对于一个类对象，该对象本身并不会存储成员函数相关的内容，所以即使<code>delete this;</code>，CppTest类型的指针依然可以毫无障碍的调用到FunA，FunB。</p><p><strong>3. nullpter也可以调用到成员函数</strong></p><p>即<code>CppTest* p = nullpter;</code>，<code>p-&gt;FunA();</code>可以输出<code>Hello world</code>。</p><p>基于这个现象我们基本就可以猜测，在C++中，成员函数的实现，应该就是在调用时，隐含着将自身作为参数传入。只要是CppTest类型的指针，不管有没有真正实例化一个对象，都可以调用成员函数。</p><h2 id="再做几组测试"><a href="#再做几组测试" class="headerlink" title="再做几组测试"></a>再做几组测试</h2><h3 id="delete后调用val"><a href="#delete后调用val" class="headerlink" title="delete后调用val"></a>delete后调用val</h3><figure class="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="keyword">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    CppTest* p = <span class="keyword">new</span> CppTest(<span class="number">2</span>);</span><br><span class="line">    <span class="keyword">delete</span> p;</span><br><span class="line">    <span class="built_in">cout</span> &lt;&lt; p-&gt;val &lt;&lt; <span class="built_in">endl</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="添加一个虚函数，并在delete后调用"><a href="#添加一个虚函数，并在delete后调用" class="headerlink" title="添加一个虚函数，并在delete后调用"></a>添加一个虚函数，并在delete后调用</h3><figure class="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></pre></td><td class="code"><pre><span class="line">...</span><br><span class="line">    <span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">FunVirtual</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">cout</span> &lt;&lt; <span class="string">&quot;Virtual function&quot;</span> &lt;&lt; <span class="built_in">endl</span>;</span><br><span class="line">    &#125;</span><br><span class="line">...</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    CppTest* p = <span class="keyword">new</span> CppTest(<span class="number">2</span>);</span><br><span class="line">    <span class="keyword">delete</span> p;</span><br><span class="line">    p-&gt;FunVirtual();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="nullpter调用用val和虚函数"><a href="#nullpter调用用val和虚函数" class="headerlink" title="nullpter调用用val和虚函数"></a>nullpter调用用val和虚函数</h3><figure class="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="keyword">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    CppTest* p = nullpter;</span><br><span class="line">    <span class="built_in">cout</span> &lt;&lt; p-&gt;val &lt;&lt; <span class="built_in">endl</span>;</span><br><span class="line">    <span class="comment">// p-&gt;FunVirtual(); 这两行二选一执行一下</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="验证"><a href="#验证" class="headerlink" title="验证"></a>验证</h3><p>前两个运行一下我们发现，delete了之后，仍然可以正确输出val的值，以及调用虚函数可以看到输出了<code>Virtual function</code>。这里我们能够猜测，delete仅仅只是标记了这块内存可以被再次使用，并没有清理这块内存的内容。</p><p>第三个我们发现运行起来会崩溃，结合之前的猜测，我们可以得知，类对象内存中包含的就是类的成员变量，同时还有虚函数表指针。</p><p>想要去验证的话自然是应该去看编译后的汇编代码，以及代码运行时的内存分配情况。</p><h2 id="结论"><a href="#结论" class="headerlink" title="结论"></a>结论</h2><ol><li><p>类中成员函数保存在代码段中，只要是该类型的指针，就可以调用该类型的普通成员函数。实例化一个对象时，占用内存包括指向虚函数表的指针以及成员变量。</p></li><li><p>delete并不会清理内存，只是会标记该区域可再次被分配，同理new操作本身也不会清理内存。故而我们最好在构造函数中完成对各个参数的初始化。</p></li><li><p>如果类中包含虚函数，则内存最开始是一个指向虚函数表的指针。而虚函数表的存储，在不同的编译器中不太相同，微软的编译器将其放在了常量数据段，有的则在只读数据段。（从网上查阅，以后自己去确认一下看看）</p></li></ol><p>最后有一段，通过分析汇编代码验证，等我修复了图床之后把图片传上来。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;C-类对象在内存中的存储&quot;&gt;&lt;a href=&quot;#C-类对象在内存中的存储&quot; class=&quot;headerlink&quot; title=&quot;C++类对象在内存中的存储&quot;&gt;&lt;/a&gt;C++类对象在内存中的存储&lt;/h1&gt;&lt;blockquote&gt;
&lt;p&gt;事情是因为面试的时候，被问到了</summary>
      
    
    
    
    <category term="计算机基础" scheme="http://www.fcayh.cn/categories/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80/"/>
    
    
    <category term="C++" scheme="http://www.fcayh.cn/tags/C/"/>
    
  </entry>
  
  <entry>
    <title>字节跳动暑期实习经历</title>
    <link href="http://www.fcayh.cn/2022/09/01/internship-experience-in-bytedance/"/>
    <id>http://www.fcayh.cn/2022/09/01/internship-experience-in-bytedance/</id>
    <published>2022-09-01T13:11:29.000Z</published>
    <updated>2023-02-25T16:14:52.040Z</updated>
    
    <content type="html"><![CDATA[<h1 id="字节跳动暑期实习经历"><a href="#字节跳动暑期实习经历" class="headerlink" title="字节跳动暑期实习经历"></a>字节跳动暑期实习经历</h1><h2 id="先回顾一下找暑期实习的经历："><a href="#先回顾一下找暑期实习的经历：" class="headerlink" title="先回顾一下找暑期实习的经历："></a>先回顾一下找暑期实习的经历：</h2><p>2021年12月25号，印象中微软的暑期实习提前批是这个时候开始的。当然了，我投递了，石沉大海，正式批也是，给了笔试之后就杳无音讯了，看来微软对学历或者学校的要求还是有点高的。</p><p>2022年3月，这个时候很多国内的互联网公司就开始暑期实习的招聘了，先投了字节TikTok直播，当天投了当天就打电话给我说符合免笔试的条件，并且给我约了面试。不过很可惜，我准备的太少了，八股文什么的也没背，自然是寄了。</p><p>而后其实自己也没太重视，总是零零碎碎的看各种面经和常问题目之类的文章。刷了刷题，背了一些常会问道的计网和OS的问题。</p><p>第二个进入流程的是西山居，印象中笔试最后一题只过了一半的测试点。面试是两个人一起面的，问了大概一个小时，其实没啥八股文，倒是问了好几个算法题，以及一些代码设计相关的问题。由于算法题口胡就行，所以自然难不倒我。面试后没几天hr就联系我OC了，剑三部门。但是实习的薪资很低，而且网上看上去风评也不是很好(因为自己也不是剑网三粉丝，当时关注西山居是因为《东方：平野孤鸿》)，所以挺纠结要不要去的。</p><p>第三波进入流程的是网易雷火，网易互娱，腾讯光子工作室。其中雷火和光子都给我免了笔试，互娱是笔试3/4好像是(也可能是2/3，总之最后一题没做上来)。先面的雷火，感觉不错。当晚面了光子，算法题做的不太好，做复杂了，但其他感觉回答的还行。但是很不幸当晚光子就把我挂了，说实话心态小崩。而后第二天上午互娱直接寄，算法题上来都错题了，吭哧吭哧写半天发现搞错了，面试官自然是没给我时间再改，整体面试下来，感觉面试官对我很不屑，就总是很阴阳，比如说90名也能拿银牌？你OS能考85？之类的，当然我确实菜，但是也确实被打击的不轻。同时复盘了一下雷火的面试，感觉自己好像也回答得不好。随后失去了找实习的勇气，感觉自己跌入了低谷，直接一气之下开始考研。书也买了，张宇的网课也看了，都把高数一极限和微分看完了，题也刷了，而且还拒掉了字节游戏的笔试。结果最后收到了雷火和互娱面试通过的邮件。</p><p>于是我考研考了一周，又开始面试，不过还好，我不仅学了数学，也听了408第一遍的网课，感觉受益匪浅。雷火二面也很顺利，面试官甚至去看了我github中的代码，指出了一些写的不好的地方(说实话我都忘记了当时怎么写的了)。互娱二面也感觉很顺利，甚至下麦之前还听见面试官自己嘀咕“这个感觉还可以”。</p><p>但是很不幸互娱最后是面试不通过，但是雷火通过了，而且字节又给我发了笔试邀请，说实话这个时候我真的以为这个就是刷kpi的，字节肯定早就招满了，但是我还是做了。两小时的笔试，4个题，都很简单，我一小时ak了。</p><p>然后就是雷火的hr面，以及字节的1，2，hr面。不得不说字节是真的快，当天面完当天给我打电话说通过，并约下一面。这里要表扬一下字节这种雷厉风行的风格，像网易每轮都得一周，二面和hr面要两周，hr过后还要两周半，从投递到offer足足花了两个月。而字节，一两个星期就能从投递到offer。</p><p>最后雷火和字节的offer是同一天来的，其实我也不知道字节能做出什么游戏来，但是我雷火投的是元宇宙，当初我以为网易游研是用的自研引擎，而元宇宙标明了U3D，所以才投的元宇宙，其实自己并不想做这个。当然还有一点就是工资居然比游研还低，感觉内部也不重视呀，于是放弃了雷火，去了字节。</p><p>其实还有EA和育碧成都，这两个简直慢的离谱，总之EA面试一直问我骨骼动画，蒙皮，渲染，恕我无能，这一块懂的不太多。而育碧则是非常简单，两个人轮流问都没问出我不会的。自然是挂了EA，拿了育碧的offer。说实话我很喜欢育碧，但我想去蒙特利尔育碧或者法国育碧。</p><p>总结就是，找实习的时候准备太不充分了，甚至都不知道国内有啥游戏厂，就知道几个大的投了(米哈游实习只招AI相关的，所以没投)，倒是对国外的厂子很熟悉，可惜咱也去不了哇。</p><h2 id="初入字节"><a href="#初入字节" class="headerlink" title="初入字节"></a>初入字节</h2><p>上午报道，和我一起入职的还有两个实习生，不过都在另一个部门。在讲了些基础的事情后，学习了怎么连公司网。然后就被各自的leader或者mentor领到工位去了。不过这个时候已经快到饭店了，mentor让我先等会，吃完饭再去领电脑。感觉整体办公环境还是挺好的，桌子比较宽敞，大家都坐在一起，我后面就是leader，前面就是mentor，旁边的同事也非常善良，拿了一包抽纸送给我。</p><p>设备的话，双显示器，3070Ti，64G内存，i710700k，人体工学椅。</p><h2 id="工作一周"><a href="#工作一周" class="headerlink" title="工作一周"></a>工作一周</h2><p>第一周主要任务就是熟悉环境，完成一个minidemo，其实这个时候已经感觉出自己有点菜了，因为不怎么会lua(就只会基本语法)，加之这边框架确实特别大，自己闷头看代码，也没人带，真的挺自闭的。</p><p>mentor在第一周给了个小活，原理也挺简单的，所以也还好。只不过mentor给我讲的时候说你估计十分钟就能搞定，然鹅我捣鼓了快俩小时。emm不知道mentor有没有觉得自己怎么收了这么一个菜逼。</p><h2 id="工作三周"><a href="#工作三周" class="headerlink" title="工作三周"></a>工作三周</h2><p>这个时候是我最自闭的时候，因为第三周的单子挺难的。mentor感觉很厉害，就我去找他问一下这个单子怎么做，他可以给你画一个很大的饼(在我的角度来看)，听完我就感觉很自闭，因为不知道从何做起，但是他能给你说的让你感觉你是在做一个非常有用的大东西，让我很想去做好。</p><p>但是现实往往是事与愿违的，由于对项目还是不够熟悉，很多地方自己感觉都不敢下手去改，还有就是自己脑子的境界可能也比较低，所以导致提出的方案也总是不太理想。mentor会给我一些思路，但是对我来说感觉有点高？或者说有点空，就是我听了感觉若有所思，但是不知道怎么下手写。最后导致把单子的需求阉割了，仍然延期了。</p><p>说实话我非常讨厌延期，感觉看着自己单子上挂了个“延”字生不如死的，也感觉很丢人。但是也不好意思说，因为mentor每个单子就给1.5天或者1天的估时，但是听mentor讲一通，感觉可能我得做3天、5天。但我也没说，我总感觉不能辜负了期望，而且别人身上也很少有估时超过2天的单子，所以我一直沉浸在一个觉得自己太菜太垃圾的低沉的心情之下，感觉自己工作的太差了，甚至都想离职。</p><h2 id="团建"><a href="#团建" class="headerlink" title="团建"></a>团建</h2><p>比较幸运，八月中旬的一个周三(活动日)赶上了一起团建，我们组人很少，和另一个组凑起来10个人，中午吃了日料自助，晚上去了鬼屋，我头一次去鬼屋，自然是心态崩了，太鸡儿吓人了。最后我们去掉了npc，纯解密，然鹅我还是不敢自己一个人走，唉全队最胆小实锤了。突然想起来貌似这次花销超预算了，群里之前说超预算先自费来着，但是也没人找我要钱，我自己压根没寻思，就这么过去了，反正自己没花钱。</p><h2 id="Last-Week"><a href="#Last-Week" class="headerlink" title="Last Week"></a>Last Week</h2><p>最后一周我自己写了个总结报告，在组内给大家讲了讲自己实习来都做了啥，大概提交了忘记了是六十几次还是八十几次了，提了大概三四千行代码？然后补充了一些之前没来得及搞的，修复了一点小bug。个人感觉mentor应该感觉我不太行，因为mentor给的预期可能对我来说确实高了点，我个人感觉自己应该算是比较努力的了，同事都调侃我实习怎么还这么卷，走的这么晚之类的。走的晚真不是卷什么的，是走早了需求根本做不完。</p><h2 id="整体评价"><a href="#整体评价" class="headerlink" title="整体评价"></a>整体评价</h2><p>两个月的实习，其实做不了什么太多事，而且就到我走，也不能说把这边的打包管线看明白了，只能说了解了个框架，看了一些具体实现而已吧。但是对这边的具体情况和生活方式还是有一些自己的体会的。</p><h3 id="优点"><a href="#优点" class="headerlink" title="优点"></a>优点</h3><ol><li><p>工资高，是我收到offer中工资最高的</p></li><li><p>工时比较弹性，不打卡。宣传的1075，10倒是确实，而且如果第一天走得晚，比如十一二点，第二天可以晚点来(有时候同事十点半多才来)，但是对我来说来晚了没早饭的，所以每天我九点半多就来吃饭。</p></li><li><p>工作氛围比较好，感觉不出上下级那种严肃的规矩，至少说看上去大家都挺和善，都挺有好的。而且大家工位都在一起，至少我实习生和项目主程工位没啥区别。</p></li><li><p>设备配置比较好，我这个机箱还是低配版</p></li><li><p>福利还是不错的，包三餐(自助) + 下午茶 + 零食柜。三餐的话，早饭不是特别对我胃口，会有一些奇怪的菜(对我来说)，不过有面包，有豆浆，就还行。午饭晚饭都是两个全荤，三个半荤，两个全素菜，三个主食，三个汤/粥 + 饮料。下午茶的话，比较随缘，有时候很简单，有时候多一点。遇到A-Soul生日会还会有特别礼品。零食柜的话，种类还行吧，不过我不怎么吃零食，偶尔下班的时候，那几个奥利奥吃。</p></li></ol><h3 id="缺点？"><a href="#缺点？" class="headerlink" title="缺点？"></a>缺点？</h3><p>之所以打问号，是因为这里的“缺点”比较主观，有些可能是因为我太菜还要强，导致的，可能对大佬来说都不算什么。</p><ol><li><p>工作比较赶，从个人角度感觉就是给我估时都有点短，因为每周我大概3到4.5估时的需求单子，里面通常有一个半天解决的，剩下的做满一周。而且可能做完的还是阉割版的，或者代码上做了些妥协，写的代码比较差，我自己反正不太满意。从公司角度看就是，大家加班还是挺多的。周会的时候可以看到很多人身上都是4.5d到7天估时的需求单子(每周除了需求还有bug呢)，而且leader走的挺晚的，看上真的很忙很忙，有可能是因为我来的时候正好要赶cbt2，导致这样子，所以可能以偏概全了。</p></li><li><p>工作热情，以及对游戏的热情，好像不太能看得出来。这点就是更理想了，我感觉周围人对游戏没有什么热情，也没有什么要一起做个大东西的想法，虽然主程会在群里发信息鼓励大家，说这将是我们大家一起共同创造的第一个世界级的游戏，但是我自己对这个游戏都没这个信心。其实大家可能都不是因为热爱游戏来到这里，而是因为其他原因(比如钱)，所以字节生产的其实就是一个商品而已。当然了这也不是什么错，可能等我工作几年，我的一腔热血也没了，也就是一个机器了，完成上面给的任务，拿钱走人。</p></li><li><p>规模有点超量了。这点是指的不建议新人考虑加入了，因为里面已经是一个很庞大的团队了，我个人对这个游戏未来的猜测，感觉可能达不到上面的预期。当然我不是专业分析经济和市场的，我只是本着一个玩家的心态和视角去看待的。而且去年校招进来的人就已经只有前年的一半了，貌似今年更少的，近期还砍掉了一个项目组。</p></li></ol><p>最后说一下工作时间，根据我两个月的观察，这边的具体情况是这样的：每周二是版本日，所以可以想象，周一周二会比较忙，周二通常要到10点半之后，甚至12点之后，当然并不是每个人。再累，12点的时候，一层楼可能也就十几个了，大部分11点之前都走了。周三周四周五，这三天在通常情况下，大家9点前就走了，甚至有时候吃完饭就走了。但是在忙的时候，比如开测前，有日版本的阶段，这时候每天都有“今日必修bug”，很多人天天到不早。自己估计一下大概正常平均1095？忙的时候平均1010.55？公司是双休，而且中午12点吃饭，到两点都是午休时间，当然你可以这个时候继续工作，不过很多人会玩会，看会视频，或者睡觉。对了每周项目部都有大概都有三四十人申请周末加班，不过就我观察连续加班两周的很少。</p><h3 id="图片分享"><a href="#图片分享" class="headerlink" title="图片分享"></a>图片分享</h3><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images//2022/09/01/IMG_20220705_094535.jpg" alt="第一天的流程" style="zoom:50%"></p><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images//2022/09/01/IMG_20220826_124955.jpg" alt="工牌"></p><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images//2022/09/01/IMG_20220705_180612.jpg" alt="第一天的工位" style="zoom:50%"></p><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images//2022/09/01/IMG_20220705_122239.jpg" alt="字节的伙食" style="zoom:50%"></p><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images//2022/09/01/IMG_20220705_142245.jpg" alt="从顶楼看风景" style="zoom:50%"></p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;字节跳动暑期实习经历&quot;&gt;&lt;a href=&quot;#字节跳动暑期实习经历&quot; class=&quot;headerlink&quot; title=&quot;字节跳动暑期实习经历&quot;&gt;&lt;/a&gt;字节跳动暑期实习经历&lt;/h1&gt;&lt;h2 id=&quot;先回顾一下找暑期实习的经历：&quot;&gt;&lt;a href=&quot;#先回顾一下找暑</summary>
      
    
    
    
    <category term="杂谈" scheme="http://www.fcayh.cn/categories/%E6%9D%82%E8%B0%88/"/>
    
    
    <category term="经历" scheme="http://www.fcayh.cn/tags/%E7%BB%8F%E5%8E%86/"/>
    
  </entry>
  
  <entry>
    <title>简单的2D游戏角色控制器的实现</title>
    <link href="http://www.fcayh.cn/2022/05/06/player-controller-simple/"/>
    <id>http://www.fcayh.cn/2022/05/06/player-controller-simple/</id>
    <published>2022-05-06T07:58:05.000Z</published>
    <updated>2023-02-25T15:57:31.280Z</updated>
    
    <content type="html"><![CDATA[<h1 id="简单2D游戏角色控制器的实现"><a href="#简单2D游戏角色控制器的实现" class="headerlink" title="简单2D游戏角色控制器的实现"></a>简单2D游戏角色控制器的实现</h1><blockquote><p>学习自 Matthew-J-Spencer 的 Ultimate 2D Controller</p><p>链接： <a href="https://github.com/Matthew-J-Spencer/Ultimate-2D-Controller">Ultimate 2D Controller</a></p></blockquote><h2 id="需求"><a href="#需求" class="headerlink" title="需求"></a>需求</h2><ul><li><p>碰撞检测 ✔</p></li><li><p>左右横向移动 ✔</p></li><li><p>跳跃</p><ul><li><p>一次跳跃 ✔</p></li><li><p>中断跳跃 ✔</p></li><li><p>二段/多段跳</p></li><li><p>蹬墙跳</p></li></ul></li><li><p>下落/重力 ✔</p></li><li><p>容错机制</p><ul><li><p>可以在离开边缘短时间内起跳 ✔</p></li><li><p>在差一点点就可跳上平台时，帮用户上平台 ✔</p></li><li><p>起跳时碰到了一点点上平台的边缘，让用户不会被平台阻挡跳跃 ✔</p></li><li><p>在还没完全落地时，就可以按跳跃键连续跳跃了 ✔</p></li></ul></li><li><p>Dash 冲刺</p></li></ul><p>在Matthew-J-Spencer的最初版本代码中，仅完成了✔的部分功能。我会在后续尝试将其他功能补齐。</p><h2 id="流程"><a href="#流程" class="headerlink" title="流程"></a>流程</h2><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images@9aad4488994531a171179515d5cd12d82d70e591/2022/05/06/5ad659174dc4e4c85f9dc4e3f46c2d89.png" alt="流程图"></p><h3 id="代码"><a href="#代码" class="headerlink" title="代码"></a>代码</h3><figure class="highlight csharp"><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="keyword">private</span> <span class="keyword">void</span> <span class="title">Update</span>(<span class="params"></span>)</span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    GatherInput(); <span class="comment">// 获取输入</span></span><br><span class="line">    RunCollisionChecks(); <span class="comment">// 碰撞检测</span></span><br><span class="line"></span><br><span class="line">    CalculateWalk(); <span class="comment">// 水平移动（计算水平速度）</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 下坠/重力（计算垂直速度）</span></span><br><span class="line">    CalculateJumpApex();  </span><br><span class="line">    CalculateGravity(); </span><br><span class="line">    </span><br><span class="line">    CalculateJump(); <span class="comment">// 跳跃（设置垂直速度）</span></span><br><span class="line"></span><br><span class="line">    MoveCharacter(); <span class="comment">// 移动角色</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="获取输入"><a href="#获取输入" class="headerlink" title="获取输入"></a>获取输入</h2><p>在Player中包含有一个FrameInput类型的属性<code>Input</code>，用于记录当前帧接收到的输入情况。</p><h3 id="代码-1"><a href="#代码-1" class="headerlink" title="代码"></a>代码</h3><figure class="highlight csharp"><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="keyword">public</span> <span class="keyword">struct</span> FrameInput</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="built_in">float</span> X; <span class="comment">// 水平方向的输入值</span></span><br><span class="line">    <span class="keyword">public</span> <span class="built_in">float</span> Y; <span class="comment">// 垂直方向的输入值</span></span><br><span class="line">    <span class="keyword">public</span> <span class="built_in">bool</span> JumpDown; <span class="comment">// 跳跃键按下为true</span></span><br><span class="line">    <span class="keyword">public</span> <span class="built_in">bool</span> JumpUp; <span class="comment">// 跳跃键抬起为true</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里并没用到Y，在后续添加向各个方向Dash的功能时，就要用到Y啦，不过目前我们还没做Dash功能。</p><figure class="highlight csharp"><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="keyword">private</span> <span class="keyword">void</span> <span class="title">GatherInput</span>(<span class="params"></span>)</span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    Input = <span class="keyword">new</span> FrameInput</span><br><span class="line">    &#123;</span><br><span class="line">        JumpDown = UnityEngine.Input.GetButtonDown(<span class="string">&quot;Jump&quot;</span>),</span><br><span class="line">        JumpUp = UnityEngine.Input.GetButtonUp(<span class="string">&quot;Jump&quot;</span>),</span><br><span class="line">        X = UnityEngine.Input.GetAxisRaw(<span class="string">&quot;Horizontal&quot;</span>)</span><br><span class="line">    &#125;;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 这里记录了松开跳跃键的时间，</span></span><br><span class="line">    <span class="comment">// 目的是实现容错机制中的第三条，还没完全落地时，即可再次跳跃，</span></span><br><span class="line">    <span class="keyword">if</span> (Input.JumpDown)</span><br><span class="line">    &#123;</span><br><span class="line">        _lastJumpPressed = Time.time;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="碰撞检测"><a href="#碰撞检测" class="headerlink" title="碰撞检测"></a>碰撞检测</h2><p>碰撞检测依靠向上下左右四个方向分别发射若干条射线，进行射线检测实现的。</p><p>Player自身无需Rigidbody2D或者Collider2D, 地板、墙、平台等，须包含Collider2D组件，同时设定好Layer。</p><h3 id="射线检测"><a href="#射线检测" class="headerlink" title="射线检测"></a>射线检测</h3><p>如下图，我们在Player的四个方向，分别发射了3条射线（图中蓝色短线），并且我们维护四个bool类型的变量<code>_colUp</code>，<code>_colRight</code>，<code>_colDown</code>，<code>_colLeft</code>用来表示在四个方向上是否有发生碰撞。</p><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images//2022/05/06/84daef9632cbc98b829a398f3bd7d111.png" alt="射线检测"></p><p>在这个过程中，最重要的当属更新<code>_colDown</code>了，因为他于我们的跳跃功能息息相关。这里有两个特殊情况需要我们处理一下：</p><ol><li><p><code>_colDown</code>为true，但当前帧向下的射线检测值为false，说明这是我们起跳或者离开平台边缘的第一帧</p></li><li><p><code>_colDown</code>为false，但是当前帧向下的射线检测值为true，说明这是我们落到地上的第一帧</p></li></ol><p>对于情况2，我们要将Player的<code>LandingThisFrame</code>属性设为true，以表示已经落地，从而使得Player可以再次起跳。</p><p>对于情况1，我们要用<code>_timeLeftGrounded</code>记录当前的时间，这是为了实现“可以在离开边缘短时间内起跳”这一容错机制，具体实现细节在<strong>跳跃/下降与重力</strong>小结讲。</p><h3 id="代码-2"><a href="#代码-2" class="headerlink" title="代码"></a>代码</h3><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br></pre></td><td class="code"><pre><span class="line">[<span class="meta">Header(<span class="meta-string">&quot;COLLISION&quot;</span>)</span>]</span><br><span class="line"></span><br><span class="line">[<span class="meta">SerializeField, Tooltip(<span class="meta-string">&quot;角色的碰撞检测盒&quot;</span>)</span>]</span><br><span class="line"><span class="keyword">private</span> Bounds _characterBounds;</span><br><span class="line"></span><br><span class="line">[<span class="meta">SerializeField, Tooltip(<span class="meta-string">&quot;射线检测的Layer&quot;</span>)</span>]</span><br><span class="line"><span class="keyword">private</span> LayerMask _groundLayer;</span><br><span class="line"></span><br><span class="line">[<span class="meta">SerializeField, Tooltip(<span class="meta-string">&quot;每个方向发射的射线数量&quot;</span>)</span>]</span><br><span class="line"><span class="keyword">private</span> <span class="built_in">int</span> _detectorCount = <span class="number">3</span>;</span><br><span class="line"></span><br><span class="line">[<span class="meta">SerializeField, Tooltip(<span class="meta-string">&quot;射线检测距离&quot;</span>)</span>]</span><br><span class="line"><span class="keyword">private</span> <span class="built_in">float</span> _detectionRayLength = <span class="number">0.1f</span>;</span><br><span class="line"></span><br><span class="line">[<span class="meta">SerializeField, Tooltip(<span class="meta-string">&quot;发射线区域与边缘的缓冲区大小&quot;</span>)</span>]</span><br><span class="line">[<span class="meta">Range(0.1f, 0.3f)</span>]</span><br><span class="line"><span class="keyword">private</span> <span class="built_in">float</span> _rayBuffer = <span class="number">0.1f</span>; <span class="comment">//增大数值可以尽量避免侧向的射线碰撞到地板</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> RayRange _raysUp, _raysRight, _raysDown, _raysLeft; <span class="comment">// 四个方向的RayRange参数</span></span><br><span class="line"><span class="keyword">private</span> <span class="built_in">bool</span> _colUp, _colRight, _colDown, _colLeft; <span class="comment">// 分别表示四个方向是否发生碰撞</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="built_in">float</span> _timeLeftGrounded; <span class="comment">// 记录离开地面时的时间</span></span><br><span class="line"></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;summary&gt;</span></span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> 通过在四个方向上发射若干射线，进行四个方向上的碰撞检测</span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;/summary&gt;</span></span></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">RunCollisionChecks</span>(<span class="params"></span>)</span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="comment">// 初始化四个方向上的RagRange参数</span></span><br><span class="line">    CalculateRayRanged();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Ground</span></span><br><span class="line">    LandingThisFrame = <span class="literal">false</span>;</span><br><span class="line">    <span class="keyword">var</span> groundedCheck = RunDetection(_raysDown);</span><br><span class="line">    <span class="keyword">if</span> (_colDown &amp;&amp; !groundedCheck) _timeLeftGrounded = Time.time; <span class="comment">// 对应情况1 </span></span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">if</span> (!_colDown &amp;&amp; groundedCheck)</span><br><span class="line">    &#123;</span><br><span class="line">        _coyoteUsable = <span class="literal">true</span>; <span class="comment">// 这个参数后面再讲 </span></span><br><span class="line"></span><br><span class="line">        <span class="comment">// 对应情况2</span></span><br><span class="line">        LandingThisFrame = <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    _colDown = groundedCheck;</span><br><span class="line"></span><br><span class="line">    _colUp = RunDetection(_raysUp);</span><br><span class="line">    _colLeft = RunDetection(_raysLeft);</span><br><span class="line">    _colRight = RunDetection(_raysRight);</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="built_in">bool</span> <span class="title">RunDetection</span>(<span class="params">RayRange range</span>)</span></span><br><span class="line"><span class="function"></span>    &#123;</span><br><span class="line">        <span class="keyword">return</span> EvaluateRayPositions(range).Any(point =&gt; Physics2D.Raycast(point, range.Dir, _detectionRayLength, _groundLayer));</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"><span class="doctag">///</span> <span class="doctag">&lt;summary&gt;</span></span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> 计算四个方向上发射射线的范围；</span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> 即确定射线的起点线，终点线，方向。</span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> 影响参数：_raysUp, _raysRight, _raysDown, _raysLeft</span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;/summary&gt;</span></span></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">CalculateRayRanged</span>(<span class="params"></span>)</span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="comment">// 根据当前位置和参数中设定的检测盒大小，生成一个检测盒，以检测盒四个边界为准，修改RayRange</span></span><br><span class="line">    <span class="keyword">var</span> b = <span class="keyword">new</span> Bounds(transform.position, _characterBounds.size);</span><br><span class="line"></span><br><span class="line">    _raysDown = <span class="keyword">new</span> RayRange(b.min.x + _rayBuffer, b.min.y, b.max.x - _rayBuffer, b.min.y, Vector2.down);</span><br><span class="line">    _raysUp = <span class="keyword">new</span> RayRange(b.min.x + _rayBuffer, b.max.y, b.max.x - _rayBuffer, b.max.y, Vector2.up);</span><br><span class="line">    _raysLeft = <span class="keyword">new</span> RayRange(b.min.x, b.min.y + _rayBuffer, b.min.x, b.max.y - _rayBuffer, Vector2.left);</span><br><span class="line">    _raysRight = <span class="keyword">new</span> RayRange(b.max.x, b.min.y + _rayBuffer, b.max.x, b.max.y - _rayBuffer, Vector2.right);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">private</span> IEnumerable&lt;Vector2&gt; <span class="title">EvaluateRayPositions</span>(<span class="params">RayRange range</span>)</span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">var</span> i = <span class="number">0</span>; i &lt; _detectorCount; i++)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">var</span> t = (<span class="built_in">float</span>)i / (_detectorCount - <span class="number">1</span>);</span><br><span class="line">        <span class="keyword">yield</span> <span class="keyword">return</span> Vector2.Lerp(range.Start, range.End, t);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="各参数效果展示"><a href="#各参数效果展示" class="headerlink" title="各参数效果展示"></a>各参数效果展示</h3><p>在上文 <em>发射射线示意图</em> 中，<code>_characterBounds</code>的<code>Extend</code>属性的值为<code>&#123;x = 0.5, y = 0.65, z = 0.0&#125;</code>，<code>_detectorCount</code>为3，<code>_rayBuffer</code>为0.1，<code>_detectionRayLength</code>为0.1。</p><p>当<code>_characterBounds</code>的<code>Extend</code>属性的值为<code>&#123;x = 0.65, y = 0.25, z = 0.0&#125;</code>时，效果如下</p><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images//2022/05/06/0534583d967594a19d2449d75f5da6f0.png" alt="_characterBounds效果"></p><p>当<code>_detectorCount</code>为10时，效果如下</p><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images//2022/05/06/26172608b1dafd94fd0432fbee4f3bfd.png" alt="_detectorCount效果"></p><p>当<code>_rayBuffer</code>为0.3时，效果如下</p><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images//2022/05/06/40888f61c0f22480bd103c58aa49b13c.png" alt="_rayBuffer效果"></p><p>当<code>_detectionRayLength</code>为0.5时，效果如下</p><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images//2022/05/06/c5ec2b0a901acb322ad7715d85da8450.png" alt="_detectionRayLength效果"></p><h2 id="水平移动"><a href="#水平移动" class="headerlink" title="水平移动"></a>水平移动</h2><p>所有的移动处理（包括后面的跳跃，重力下坠），均仅对Player的<code>_currentHorizontalSpeed</code>，<code>_currentVerticalSpeed</code>属性进行修改，在每帧的最后一步去根据Player的水平/垂直速度，修改<code>transform.position</code>。</p><p>为了优化手感，我们在跳跃过程中，会小幅增加水平移动的最大速度，并且越接近跳跃最高点，增幅越大。具体思路在<strong>跳跃/下降与重力</strong>中详细讲。</p><h3 id="代码-3"><a href="#代码-3" class="headerlink" title="代码"></a>代码</h3><figure class="highlight csharp"><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></pre></td><td class="code"><pre><span class="line">[<span class="meta">Header(<span class="meta-string">&quot;WALKING&quot;</span>)</span>]</span><br><span class="line"></span><br><span class="line">[<span class="meta">SerializeField, Tooltip(<span class="meta-string">&quot;加速度&quot;</span>)</span>]</span><br><span class="line"><span class="keyword">private</span> <span class="built_in">float</span> _acceleration = <span class="number">90</span>;</span><br><span class="line"></span><br><span class="line">[<span class="meta">SerializeField, Tooltip(<span class="meta-string">&quot;最大移动速度&quot;</span>)</span>]</span><br><span class="line"><span class="keyword">private</span> <span class="built_in">float</span> _moveClamp = <span class="number">13</span>;</span><br><span class="line"></span><br><span class="line">[<span class="meta">SerializeField, Tooltip(<span class="meta-string">&quot;减速度&quot;</span>)</span>]</span><br><span class="line"><span class="keyword">private</span> <span class="built_in">float</span> _deAcceleration = <span class="number">60f</span>;</span><br><span class="line"></span><br><span class="line">[<span class="meta">SerializeField, Tooltip(<span class="meta-string">&quot;在跳跃中对移速的加成系数&quot;</span>)</span>]</span><br><span class="line"><span class="keyword">private</span> <span class="built_in">float</span> _apexBonus = <span class="number">2</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;summary&gt;</span></span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> 根据按键和碰撞情况，修改Player水平速度，实现左右移动</span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> 影响参数：_currentHorizontalSpeed</span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;/summary&gt;</span></span></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">CalculateWalk</span>(<span class="params"></span>)</span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="comment">// 有“Horizontal”按键按下</span></span><br><span class="line">    <span class="keyword">if</span> (Input.X != <span class="number">0</span>)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="comment">// 设置水平速度，根据加速度加速</span></span><br><span class="line">        _currentHorizontalSpeed += Input.X * _acceleration * Time.deltaTime;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 将速度限制在最大移动速度范围内</span></span><br><span class="line">        _currentHorizontalSpeed = Mathf.Clamp(_currentHorizontalSpeed, -_moveClamp, _moveClamp);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 根据跳跃高度对速度给予加成</span></span><br><span class="line">        <span class="keyword">var</span> apexBonus = Mathf.Sign(Input.X) * _apexBonus * _apexPoint;</span><br><span class="line">        _currentHorizontalSpeed += apexBonus * Time.deltaTime;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="comment">// 松开按键后，逐渐减速</span></span><br><span class="line">        _currentHorizontalSpeed = Mathf.MoveTowards(_currentHorizontalSpeed, <span class="number">0</span>, _deAcceleration * Time.deltaTime);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 如果左右两侧撞到墙壁，则将速度强制设成 0，不允许穿墙</span></span><br><span class="line">    <span class="keyword">if</span> (_currentHorizontalSpeed &gt; <span class="number">0</span> &amp;&amp; _colRight || _currentHorizontalSpeed &lt; <span class="number">0</span> &amp;&amp; _colLeft)</span><br><span class="line">    &#123;</span><br><span class="line">        _currentHorizontalSpeed = <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images//2022/05/06/5ad659174dc4e4c85f9dc4e3f46c2d89.png"></p><h2 id="跳跃-下降与重力"><a href="#跳跃-下降与重力" class="headerlink" title="跳跃/下降与重力"></a>跳跃/下降与重力</h2><p>首先在这里我们手动模拟了一个重力系统。为了让游戏操作“手感更好”，我们并没有按照真实的物理规律去实现重力，而是做了一些改动：</p><ul><li><p>限制了下落的最大速度<br>这使得我们从较高的平台向下跳时，在跳跃过程中，不会由于重力一直加速导致速度过快，难以操控。</p></li><li><p>在跳到最高处附近时给予水平移动一些速度补偿<br>这使得我们在跳跃过程中可以更流畅的调整人物的横向移动。</p></li></ul><p>同时在跳跃时，我们做了以下几点：</p><ul><li><p>中断跳跃（即在跳跃上升中如果松开空格，则会即刻开始下落）<br>这个是通过临时将Player的向下加速度变大很多来实现。</p></li><li><p>可以在离开边缘短时间内起跳<br>这个是通过在前文中提到的<code>_timeLeftGrounded</code>来实现的，只要当前时间减去<code>_timeLeftGrounded</code>小于我们设定的阈值<code>_coyoteTimeThreshold</code>，即使现在我们已经走出了平台边缘（悬空了），我们仍可以跳跃。</p></li><li><p>在还没完全落地时，就可以按跳跃键连续跳跃了<br>由于人眼并不能很精确的分辨Player是否已经落下了（当Player就快要落到地面但是还没完全接触地面时），如果每次必须在落地那一帧之后才可以再次起跳的话，会导致有时候我们以为Player已经落下了，便按下了跳跃键企图让Player连续的跳跃。当然结果就是Player只会呆呆地站在原地，这样会使得我们的操作手感不佳。故而我们在前文<strong>获取输入</strong>中，只要跳跃键被按下，就用<code>_lastJumpPressed</code>参数不断记录当前时间，如果在落地前<code>_jumpBuffer</code>时间内按下过跳跃键（即<code>_lastJumpPressed + _jumpBuffer &gt; Time.time</code>时），则会在落地后自动起跳，实现流畅的连续跳跃。</p></li><li><p>跳跃中给予水平移速加成<br>在跳跃中，我们的水平移动速度可以突破最大值<code>_moveClamp</code>，且越接近跳跃最高点，加成越高。这通过<code>_jumpApexThreshold</code>、<code>_apexPoint</code>和<code>_apexBonus</code>三个参数实现，其中<code>_apexPoint = Mathf.InverseLerp(_jumpApexThreshold, 0, Mathf.Abs(Velocity.y));</code>，速度加成为<code>Mathf.Sign(Input.X) * _apexBonus * _apexPoint</code>。</p></li></ul><h3 id="代码-4"><a href="#代码-4" class="headerlink" title="代码"></a>代码</h3><figure class="highlight csharp"><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="meta">Header(<span class="meta-string">&quot;GRAVITY&quot;</span>)</span>]</span><br><span class="line"></span><br><span class="line">[<span class="meta">SerializeField, Tooltip(<span class="meta-string">&quot;最大下落速度&quot;</span>)</span>]</span><br><span class="line"><span class="keyword">private</span> <span class="built_in">float</span> _fallClamp = <span class="number">-40f</span>;</span><br><span class="line"></span><br><span class="line">[<span class="meta">SerializeField, Tooltip(<span class="meta-string">&quot;最小下落加速度&quot;</span>)</span>]</span><br><span class="line"><span class="keyword">private</span> <span class="built_in">float</span> _minFallSpeed = <span class="number">80f</span>;</span><br><span class="line"></span><br><span class="line">[<span class="meta">SerializeField, Tooltip(<span class="meta-string">&quot;最大下落加速度&quot;</span>)</span>]</span><br><span class="line"><span class="keyword">private</span> <span class="built_in">float</span> _maxFallSpeed = <span class="number">120f</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="built_in">float</span> _fallSpeed; <span class="comment">// 当前下落加速度</span></span><br><span class="line"></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;summary&gt;</span></span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> 实现重力</span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;/summary&gt;</span></span></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">CalculateGravity</span>(<span class="params"></span>)</span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (_colDown) <span class="comment">// 说明落地了</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="comment">// 落地后将垂直速度归零</span></span><br><span class="line">        <span class="keyword">if</span> (_currentVerticalSpeed &lt; <span class="number">0</span>) _currentVerticalSpeed = <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="comment">// 当松开跳跃键并且此时还在上升，调大下落加速度使Player快速减速到下落状态</span></span><br><span class="line">        <span class="built_in">float</span> fallSpeed = _endedJumpEarly &amp;&amp; _currentVerticalSpeed &gt; <span class="number">0</span> ? _fallSpeed * _jumpEndEarlyGravityModifier : _fallSpeed;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 依据当前下落加速度，修改当前垂直速度</span></span><br><span class="line">        _currentVerticalSpeed -= fallSpeed * Time.deltaTime;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 因为有最大下落速度的限制，向下时不能快过最大下落速度</span></span><br><span class="line">        <span class="keyword">if</span> (_currentVerticalSpeed &lt; _fallClamp) _currentVerticalSpeed = _fallClamp;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br></pre></td><td class="code"><pre><span class="line">[<span class="meta">Header(<span class="meta-string">&quot;JUMPING&quot;</span>)</span>]</span><br><span class="line"></span><br><span class="line">[<span class="meta">SerializeField, Tooltip(<span class="meta-string">&quot;跳跃初速度&quot;</span>)</span>]</span><br><span class="line"><span class="keyword">private</span> <span class="built_in">float</span> _jumpHeight = <span class="number">30</span>;</span><br><span class="line"></span><br><span class="line">[<span class="meta">SerializeField, Tooltip(<span class="meta-string">&quot;当上升时速度小于该值时认为接近跳跃最高点了&quot;</span>)</span>]</span><br><span class="line"><span class="keyword">private</span> <span class="built_in">float</span> _jumpApexThreshold = <span class="number">10f</span>;</span><br><span class="line"></span><br><span class="line">[<span class="meta">SerializeField, Tooltip(<span class="meta-string">&quot;离开平台边缘仍可起跳的时间&quot;</span>)</span>]</span><br><span class="line"><span class="keyword">private</span> <span class="built_in">float</span> _coyoteTimeThreshold = <span class="number">0.1f</span>;</span><br><span class="line"></span><br><span class="line">[<span class="meta">SerializeField, Tooltip(<span class="meta-string">&quot;在离落地前多少时间内就可以响应跳跃按键&quot;</span>)</span>]</span><br><span class="line"><span class="keyword">private</span> <span class="built_in">float</span> _jumpBuffer = <span class="number">0.1f</span>;</span><br><span class="line"></span><br><span class="line">[<span class="meta">SerializeField, Tooltip(<span class="meta-string">&quot;中断跳跃时附加的乡下加速度倍数&quot;</span>)</span>]</span><br><span class="line"><span class="keyword">private</span> <span class="built_in">float</span> _jumpEndEarlyGravityModifier = <span class="number">3</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="built_in">bool</span> _coyoteUsable; <span class="comment">// 并没在跳跃中</span></span><br><span class="line"><span class="keyword">private</span> <span class="built_in">bool</span> _endedJumpEarly = <span class="literal">true</span>; <span class="comment">// 是否中断了跳跃</span></span><br><span class="line"><span class="keyword">private</span> <span class="built_in">float</span> _apexPoint; <span class="comment">// 起跳时为0，跳到最高点时为</span></span><br><span class="line"><span class="keyword">private</span> <span class="built_in">float</span> _lastJumpPressed; <span class="comment">// 上次按下跳跃键的时间</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 是否脱离平台边缘并且可以跳起</span></span><br><span class="line"><span class="keyword">private</span> <span class="built_in">bool</span> CanUseCoyote =&gt; _coyoteUsable &amp;&amp; !_colDown &amp;&amp; _timeLeftGrounded + _coyoteTimeThreshold &gt; Time.time;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 是否在落地后自动跳起</span></span><br><span class="line"><span class="keyword">private</span> <span class="built_in">bool</span> HasBufferedJump =&gt; _colDown &amp;&amp; _lastJumpPressed + _jumpBuffer &gt; Time.time;</span><br><span class="line"></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;summary&gt;</span></span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> 根据跳跃的程度，调整向下的加速度</span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> 越接近跳跃的最高点（即Velocity.y -&gt; 0）时，_apexPoint -&gt; 1</span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> 向下的加速度也受_apexPoint影响，_apexPoint -&gt; 1，_fallSpeed -&gt; max</span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;/summary&gt;</span></span></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">CalculateJumpApex</span>(<span class="params"></span>)</span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (!_colDown)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="comment">// 越接近跳跃最高点（即垂直速度接近0）时，向下加速度越大</span></span><br><span class="line">        _apexPoint = Mathf.InverseLerp(_jumpApexThreshold, <span class="number">0</span>, Mathf.Abs(Velocity.y));</span><br><span class="line">        _fallSpeed = Mathf.Lerp(_minFallSpeed, _maxFallSpeed, _apexPoint);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">    &#123;</span><br><span class="line">        _apexPoint = <span class="number">0</span>;</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"><span class="doctag">///</span> <span class="doctag">&lt;summary&gt;</span></span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> 处理跳跃</span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;/summary&gt;</span></span></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">CalculateJump</span>(<span class="params"></span>)</span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="comment">// 如果 按下跳跃键且处于CanUseCoyote时，或者 处于HasBufferedJump时，跳跃</span></span><br><span class="line">    <span class="keyword">if</span> ((Input.JumpDown &amp;&amp; CanUseCoyote) || HasBufferedJump)</span><br><span class="line">    &#123;</span><br><span class="line">        _currentVerticalSpeed = _jumpHeight; <span class="comment">// 设置初始速度</span></span><br><span class="line">        _endedJumpEarly = <span class="literal">false</span>;             <span class="comment">// 并未中断跳跃</span></span><br><span class="line">        _coyoteUsable = <span class="literal">false</span>;               <span class="comment">// 已经在跳跃中，使CanUseCoyote一定为false</span></span><br><span class="line">        _timeLeftGrounded = <span class="built_in">float</span>.MinValue;  <span class="comment">// -3.40282347E+38，使CanUseCoyote一定为false</span></span><br><span class="line">        JumpingThisFrame = <span class="literal">true</span>;             <span class="comment">// 在当前帧跳跃了</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">    &#123;</span><br><span class="line">        JumpingThisFrame = <span class="literal">false</span>; <span class="comment">// 在当前帧没有跳跃</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 如果当前帧松开了跳跃键，并且此时Player还在上升，则说明是中断跳跃</span></span><br><span class="line">    <span class="keyword">if</span> (!_colDown &amp;&amp; Input.JumpUp &amp;&amp; !_endedJumpEarly &amp;&amp; Velocity.y &gt; <span class="number">0</span>)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="comment">// 这里可以粗暴的将垂直速度设为0，但是这样手感不好，我们不这样做</span></span><br><span class="line">        <span class="comment">// _currentVerticalSpeed = 0;</span></span><br><span class="line">        _endedJumpEarly = <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 如果向上撞到了障碍物，得强制速度为零，</span></span><br><span class="line">    <span class="keyword">if</span> (_colUp)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">if</span> (_currentVerticalSpeed &gt; <span class="number">0</span>) _currentVerticalSpeed = <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="移动角色"><a href="#移动角色" class="headerlink" title="移动角色"></a>移动角色</h2><p>移动Player时，我们根据Player当前的水平、垂直速度，计算出下一帧Player应处的位置，并依据<code>_characterBounds</code>的大小，在对应位置进行碰撞检测，如果此时并没有碰到任何物体，则直接把Player移动到对应位置即可。否则要根据<code>_freeColliderIterations</code>参数，一小步一小步试探。</p><p>例如<code>_freeColliderIterations = 3</code>，当前Player处于<code>&#123;x = 0, y = 0.5, z = 0&#125;</code>的位置，下一帧理论位置为<code>&#123;x = 1.2, y = 0.5, z = 0&#125;</code>，而在这个位置的碰撞检测检测到了障碍物，则我们需要在<code>&#123;x = 0.4, y = 0.5, z = 0&#125;</code>位置进行碰撞检测，如果无障碍物，则将Player移动到该位置，并在<code>&#123;x = 0.8, y = 0.5, z = 0&#125;</code>位置再检测，以此类推。故而<code>_freeColliderIterations</code>越大，移动则会更精细，但是计算量也会同时提升很多。</p><p>同时，在移动过程中，我们要完成一点容错处理：</p><ul><li>在差一点点就可跳上平台时，帮用户上平台</li></ul><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images//2022/05/07/a73b8ec75ef648978f1151686ddb48f9.png" alt="差一点跳上平台"></p><ul><li>起跳时碰到了一点点上平台的边缘，让用户不会被平台阻挡跳跃</li></ul><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images//2022/05/07/7a40d1f8b2c7bcdc5a0d0719f73df910.png" alt="起跳时碰到了一点点边缘"></p><p>这两点其实处理方法是一样的，首先在下一帧理论位置存在障碍物，且进行小步移动时，第一步就有障碍物，则触发这两种容错。我们仅需在此时将Player往碰撞点的反方向轻推一下即可。</p><p>效果：</p><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images//2022/05/07/a864687d02e8a0b3a71617b41fe8028c.png" alt="差一点跳上平台"></p><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images//2022/05/07/d0f7daa9cd5eb51303a7c9eb9cb549b6.png" alt="起跳时碰到了一点点边缘"></p><blockquote><p>这样处理大部分情况都没问题，不过还是有bug的，这一轻推可能会把Player推到墙里卡住，所以后续可以思考更好的解决方案。</p></blockquote><h3 id="代码-5"><a href="#代码-5" class="headerlink" title="代码"></a>代码</h3><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><td class="code"><pre><span class="line">[<span class="meta">Header(<span class="meta-string">&quot;MOVE&quot;</span>)</span>]</span><br><span class="line"></span><br><span class="line">[<span class="meta">SerializeField, Tooltip(<span class="meta-string">&quot;碰撞检测精度&quot;</span>)</span>]</span><br><span class="line"><span class="keyword">private</span> <span class="built_in">int</span> _freeColliderIterations = <span class="number">10</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;summary&gt;</span></span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> 移动角色</span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;/summary&gt;</span></span></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">MoveCharacter</span>(<span class="params"></span>)</span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    Vector3 pos = transform.position;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 根据Player当前帧的水平、垂直移动速度计算出下一帧应处于的位置</span></span><br><span class="line">    RawMovement = <span class="keyword">new</span> Vector3(_currentHorizontalSpeed, _currentVerticalSpeed);</span><br><span class="line">    Vector3 move = RawMovement * Time.deltaTime;</span><br><span class="line">    Vector3 furthestPoint = pos + move;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 如果没有发生碰撞，则可以直接移动Player</span></span><br><span class="line">    <span class="keyword">var</span> hit = Physics2D.OverlapBox(furthestPoint, _characterBounds.size, <span class="number">0</span>, _groundLayer);</span><br><span class="line">    <span class="keyword">if</span> (!hit)</span><br><span class="line">    &#123;</span><br><span class="line">        transform.position += move;</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 否则我们要根据_freeColliderIterations，将原本一帧的移动拆分成若干更小的几步，逐步移动。</span></span><br><span class="line">    Vector3 positionToMoveTo = transform.position;</span><br><span class="line">    <span class="keyword">for</span> (<span class="built_in">int</span> i = <span class="number">1</span>; i &lt; _freeColliderIterations; i++)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="comment">// 由近到远，一步一步试探</span></span><br><span class="line">        <span class="built_in">float</span> t = (<span class="built_in">float</span>)i / _freeColliderIterations;</span><br><span class="line">        Vector2 posToTry = Vector2.Lerp(pos, furthestPoint, t);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (Physics2D.OverlapBox(posToTry, _characterBounds.size, <span class="number">0</span>, _groundLayer))</span><br><span class="line">        &#123;</span><br><span class="line">            transform.position = positionToMoveTo;</span><br><span class="line"></span><br><span class="line">            <span class="comment">// 这说明我们差一点就跳上一个平台，可以轻推一下Player，让其可以跳上平台</span></span><br><span class="line">            <span class="comment">// 或者起跳时头顶碰到了平台的角，可以轻轻让Player再靠外一点，不会被平台挡住跳跃</span></span><br><span class="line">            <span class="keyword">if</span> (i == <span class="number">1</span>)</span><br><span class="line">            &#123;</span><br><span class="line">                <span class="keyword">if</span> (_currentVerticalSpeed &lt; <span class="number">0</span>) _currentVerticalSpeed = <span class="number">0</span>;</span><br><span class="line">                Vector3 dir = transform.position - hit.transform.position;</span><br><span class="line">                transform.position += dir.normalized * move.magnitude;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        positionToMoveTo = posToTry;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="完整代码"><a href="#完整代码" class="headerlink" title="完整代码"></a>完整代码</h2><p>添加了中文注释的完整代码已上传至<a href="https://github.com/FcAYH/Ultimate-2D-Controller">Github</a></p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;简单2D游戏角色控制器的实现&quot;&gt;&lt;a href=&quot;#简单2D游戏角色控制器的实现&quot; class=&quot;headerlink&quot; title=&quot;简单2D游戏角色控制器的实现&quot;&gt;&lt;/a&gt;简单2D游戏角色控制器的实现&lt;/h1&gt;&lt;blockquote&gt;
&lt;p&gt;学习自 Matth</summary>
      
    
    
    
    <category term="游戏开发" scheme="http://www.fcayh.cn/categories/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/"/>
    
    
    <category term="C#" scheme="http://www.fcayh.cn/tags/C/"/>
    
    <category term="Unity" scheme="http://www.fcayh.cn/tags/Unity/"/>
    
  </entry>
  
  <entry>
    <title>游戏编程模式学习笔记（一） 命令模式</title>
    <link href="http://www.fcayh.cn/2022/04/12/game-promramming-pattern-study-notes-1/"/>
    <id>http://www.fcayh.cn/2022/04/12/game-promramming-pattern-study-notes-1/</id>
    <published>2022-04-12T13:33:37.000Z</published>
    <updated>2022-04-13T11:59:17.700Z</updated>
    
    <content type="html"><![CDATA[<h1 id="游戏设计模式学习笔记（一）-命令模式"><a href="#游戏设计模式学习笔记（一）-命令模式" class="headerlink" title="游戏设计模式学习笔记（一） 命令模式"></a>游戏设计模式学习笔记（一） 命令模式</h1><blockquote><p>学习参考自 Robert Nystrom编写的 《游戏编程模式》<br>电子书链接： <a href="https://gpp.tkchu.me/command.html">https://gpp.tkchu.me/command.html</a></p></blockquote><p>该书中使用C++作为编程语言，我将使用C#和Unity。</p><p><strong>命令模式</strong>：将一个请求封装为一个对象，从而使你可用不同的请求对客户进行参数化； 对请求排队或记录请求日志，以及支持可撤销的操作。</p><p>简单地说，命令是具现化的方法调用。</p><h2 id="以角色动作控制为例"><a href="#以角色动作控制为例" class="headerlink" title="以角色动作控制为例"></a>以角色动作控制为例</h2><p>举个简单的例子，假设我们现在要控制一个Player的动作，他有跳跃，开火，向前冲刺，切换武器四个动作，那么最朴素的方法莫过于如下写法：</p><figure class="highlight csharp"><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">public</span> MotionController : MonoBehaviour</span><br><span class="line">&#123;</span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">Update</span>(<span class="params"></span>)</span></span><br><span class="line"><span class="function"></span>    &#123;</span><br><span class="line">        <span class="keyword">if</span> (Input.GetKeyDown(KeyCode.Space))</span><br><span class="line">            <span class="comment">// 跳跃的功能实现</span></span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (Input.GetKeyDown(KeyCode.J))</span><br><span class="line">            <span class="comment">// 开火的功能实现</span></span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (Input.GetKeyDown(KeyCode.LeftShift))</span><br><span class="line">            <span class="comment">// 向前冲刺的功能实现</span></span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (Input.GetKeyDown(KeyCode.K))</span><br><span class="line">            <span class="comment">// 切换武器的功能实现</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这个方法显而易见的一个问题就是，我们将用户的输入和程序行为硬编码在了一起，想要修改按键对应的功能是需要对源码进行大量修改的。但是通常我们都希望我们的游戏可以支持用户自己配置按键的功能，为了支持这一点，我们应该修改各个功能的实现部分。</p><p>首先我们可以引入一个ICommand接口：</p><figure class="highlight csharp"><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="keyword">public</span> <span class="keyword">interface</span> <span class="title">ICommand</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">void</span> <span class="title">Execute</span>(<span class="params"></span>)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>我们可以为不同的功能创建不同的对象，并且均要实现ICommand接口，这样对于不同功能，只需要实例化出对应的对象，然后调用Execute()方法。</p><p>例如跳跃：</p><figure class="highlight csharp"><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="keyword">public</span> JumpCommand : ICommand</span><br><span class="line">&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">Execute</span>(<span class="params"></span>)</span></span><br><span class="line"><span class="function"></span>    &#123;</span><br><span class="line">        <span class="comment">// ...</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在将开火、冲刺、切换武器等功能均实现后，我们的MotionController可改为如下形式：</p><figure class="highlight csharp"><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="keyword">public</span> MotionController : MonoBehaviour</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">private</span> ICommand keySpace;</span><br><span class="line">    <span class="keyword">private</span> ICommand keyJ;</span><br><span class="line">    <span class="keyword">private</span> ICommand keyLeftShift;</span><br><span class="line">    <span class="keyword">private</span> ICommand keyK;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">Update</span>(<span class="params"></span>)</span></span><br><span class="line"><span class="function"></span>    &#123;</span><br><span class="line">        <span class="keyword">if</span> (Input.GetKeyDown(KeyCode.Space))</span><br><span class="line">            keySpace.Execute();</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (Input.GetKeyDown(KeyCode.J))</span><br><span class="line">            keyJ.Execute();</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (Input.GetKeyDown(KeyCode.LeftShift))</span><br><span class="line">            keyLeftShift.Execute();</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (Input.GetKeyDown(KeyCode.K))</span><br><span class="line">            keyK.Execute();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>此时如果我们想要更换某个按键的功能，只需要将其ICommand用对应功能的类实例化即可</p><blockquote><p>不过我有点好奇，书中是面向手柄的，按键分别为XYBA，如果是键盘游戏，为了让任意键均可被绑定，岂不是需要预先将所有按键的ICommand定义出来，未免有些麻烦和多余。故而我在下面会根据自己的理解再写一种方式，即预先将Player每个动作的KeyCode和ICommand均定义出来，这样更改按键对应的功能只需将其KeyCode更改即可，感觉更方便？</p></blockquote><figure class="highlight csharp"><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">public</span> MotionController : MonoBehaviour</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">public</span> KeyCode JumpKey;</span><br><span class="line">    <span class="keyword">public</span> KeyCode FireKey;</span><br><span class="line">    <span class="keyword">public</span> KeyCode DashKey;</span><br><span class="line">    <span class="keyword">public</span> KeyCode SwitchWeaponKey;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> ICommand jumpCommand;</span><br><span class="line">    <span class="keyword">private</span> ICommand fireCommand;</span><br><span class="line">    <span class="keyword">private</span> ICommand dashCommand;</span><br><span class="line">    <span class="keyword">private</span> ICommand switchWeaponCommand;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">Update</span>(<span class="params"></span>)</span></span><br><span class="line"><span class="function"></span>    &#123;</span><br><span class="line">        <span class="keyword">if</span> (Input.GetKeyDown(JumpKey))</span><br><span class="line">            jumpCommand.Execute();</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (Input.GetKeyDown(FireKey))</span><br><span class="line">            fireCommand.Execute();</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (Input.GetKeyDown(DashKey))</span><br><span class="line">            dashCommand.Execute();</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (Input.GetKeyDown(SwitchWeaponKey))</span><br><span class="line">            switchWeaponCommand.Execute();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在书中，作者推荐了类似以下用法：</p><figure class="highlight csharp"><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="keyword">public</span> <span class="keyword">interface</span> <span class="title">ICommand</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">void</span> <span class="title">Execute</span>(<span class="params">GameObject actor</span>)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight csharp"><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="keyword">public</span> JumpCommand : ICommand</span><br><span class="line">&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">Execute</span>(<span class="params">GameObject actor</span>)</span></span><br><span class="line"><span class="function"></span>    &#123;</span><br><span class="line">        actor.GetComponent&lt;...&gt;().Jump();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>将Command独立出来，每个Player，或者游戏中的其他AI，都需要自己事先写好各项运动的逻辑，然后通过将自己传入Command中，触发功能。</p><figure class="highlight csharp"><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="keyword">public</span> <span class="keyword">class</span> <span class="title">InputHandler</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">private</span> ICommand keySpace;</span><br><span class="line">    <span class="keyword">private</span> ICommand keyJ;</span><br><span class="line">    <span class="keyword">private</span> ICommand keyLeftShift;</span><br><span class="line">    <span class="keyword">private</span> ICommand keyK;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> ICommand <span class="title">HandleInput</span>(<span class="params"></span>)</span></span><br><span class="line"><span class="function"></span>    &#123;</span><br><span class="line">        <span class="keyword">if</span> (Input.GetKeyDown(KeyCode.Space))</span><br><span class="line">            <span class="keyword">return</span> keySpace;</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (Input.GetKeyDown(KeyCode.J))</span><br><span class="line">            <span class="keyword">return</span> keyJ;</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (Input.GetKeyDown(KeyCode.LeftShift))</span><br><span class="line">            <span class="keyword">return</span> keyLeftShift;</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (Input.GetKeyDown(KeyCode.K))</span><br><span class="line">            <span class="keyword">return</span> keyK;</span><br><span class="line">        </span><br><span class="line">        retrun <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">MotionController</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">private</span> InputHandler inputHandler;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">Update</span>(<span class="params"></span>)</span></span><br><span class="line"><span class="function"></span>    &#123;</span><br><span class="line">        ICommand command = inputHandler.HandleInput();</span><br><span class="line">        <span class="keyword">if</span> (command != <span class="literal">null</span>)</span><br><span class="line">        &#123;</span><br><span class="line">            command.Execute(<span class="keyword">this</span>.gameObject);</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><p>这样写也有一个好处就是方便实现我们的Player去控制游戏中的其他单位移动，因为只需将相应的gameObject传进去。</p><h2 id="命令的撤销和重做"><a href="#命令的撤销和重做" class="headerlink" title="命令的撤销和重做"></a>命令的撤销和重做</h2><blockquote><p>这一部分看上去应该是备忘录模式去做的事情，不过在书中作者提到： 由于命令趋向于修改对象状态的一小部分，对数据其他部分的快照就是浪费内存。手动内存管理的消耗更小。</p></blockquote><p>这部分以战棋类型游戏的移动为例，如果仅支持一步的撤销和重做，可以在每个Command下记录以下上一步所在的坐标即可。当然，复杂一点的话，我们可以创建一个链表或者栈，用来记录执行过的命令，撤销或者重做只需要执行链表中的前一个Command或者后一个。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;游戏设计模式学习笔记（一）-命令模式&quot;&gt;&lt;a href=&quot;#游戏设计模式学习笔记（一）-命令模式&quot; class=&quot;headerlink&quot; title=&quot;游戏设计模式学习笔记（一） 命令模式&quot;&gt;&lt;/a&gt;游戏设计模式学习笔记（一） 命令模式&lt;/h1&gt;&lt;blockquo</summary>
      
    
    
    
    <category term="设计模式" scheme="http://www.fcayh.cn/categories/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
    
    
    <category term="命令模式" scheme="http://www.fcayh.cn/tags/%E5%91%BD%E4%BB%A4%E6%A8%A1%E5%BC%8F/"/>
    
  </entry>
  
  <entry>
    <title>Intel CPU 发展历程</title>
    <link href="http://www.fcayh.cn/2021/09/21/TheHistroyOfIntelCPU/"/>
    <id>http://www.fcayh.cn/2021/09/21/TheHistroyOfIntelCPU/</id>
    <published>2021-09-21T15:39:17.000Z</published>
    <updated>2022-04-12T13:40:41.470Z</updated>
    
    <content type="html"><![CDATA[<h1 id="Intel-CPU-发展历程"><a href="#Intel-CPU-发展历程" class="headerlink" title="$Intel\ \ CPU$ 发展历程"></a>$Intel\ \ CPU$ 发展历程</h1><h2 id="一：起点"><a href="#一：起点" class="headerlink" title="一：起点"></a>一：起点</h2><p><u>1971年 4004——世界上第一款商用微型处理器</u></p><p>1971年1月15日，Intel 公司的工程师霍夫发明了世界上第一款商用计算机微处理器 4004，从此这一天被当作具有全球 IT 界里程碑意义的日子，被永远的载入了史册。这款4位微处理器虽然只有45条指令，每秒也只能执行5万条指令，频率只有108KHz，甚至比不上世界第一台计算机 ENIAC。但它的集成度却要高很多，集成晶体管2300只，一块4004的重量还不到一盅司。这一突破性的发明最先应用于 Busicom 计算器，为生命体和个人计算机的智能嵌入铺平了道路。</p><p><img src = "https://cdn.jsdelivr.net/gh/FcAYH/Images@b13595391738a87cbebb199d8860f076be43042d/2021/09/21/a037e93a63531f0ae435d3b4909c4070.png" alt = "4004微处理器" style = "zoom: 30%;" /></p><h2 id="二：早年发展"><a href="#二：早年发展" class="headerlink" title="二：早年发展"></a>二：早年发展</h2><h3 id="x86指令集的形成"><a href="#x86指令集的形成" class="headerlink" title="x86指令集的形成"></a>x86指令集的形成</h3><p><u>1972年 8008微处理器</u></p><p>8008芯片原本是为德克萨斯州的 Datapoint 公司设计的，但是这家公司最终却没有足够的财力支付这笔费用。于是双方达成协议，Intel拥有这款芯片所有的知识产权，而且还获得了由 Datapoint 公司开发的指令集。<u>这套指令集奠定了今天Intel公司X86系列微处理器指令集的基础。</u></p><p><u>1978年 8086，8088微处理器</u></p><p><u>8086这款处理器是x86架构的鼻祖。</u> 趁着市场销售正好的时机，以及市场需求的提升，Intel在同一年推出了性能更出色的8088处理器。两款处理器都拥有29000只晶体管，频率分别为5MHz、10MHz，内部数据总线(处理器内部传输数据的总线)、外部数据总线(处理器外部传输数据的总线)均为16位，地址总线为20位，可寻址1MB内存。 随着个人电脑的流行，Intel也开始名扬四海。8088的大获成功使Intel顺利跻身财富500强之列，《财富》杂志将该公司评为“七十大商业奇迹之一(Business Triumphs of the Seventies)”。</p><p><img src = "https://cdn.jsdelivr.net/gh/FcAYH/Images@47f820aa09264864caf09d180a11d256ee517b7c/2021/09/21/d03d67e88da4138568cb76e3a1e612b7.png" alt = "8086微处理器" style = "zoom: 30%;" /></p><p><img src = "https://cdn.jsdelivr.net/gh/FcAYH/Images@2c37628402b509bec6af1896ecfe729645ac137a/2021/09/21/68d42f5535b1631454782745d567e5ab.png" alt = "8088微处理器" style = "zoom: 30%;" /></p><h3 id="多线程处理功能"><a href="#多线程处理功能" class="headerlink" title="多线程处理功能"></a>多线程处理功能</h3><p><u>1985年 80386——Intel的第一代32位处理器，同时也是第一款具有多线程功能的处理器</u></p><p><u>1989年 80486——Intel最后一款以数字为编号的处理器</u></p><p>1989年，Intel发布了80486处理器。486处理器是Intel非常成功的商业项目。很多厂商也看清了Intel处理器的发展规律，因此很快就随着Intel的营销战而转型成功。80486处理器集成了125万个晶体管，频率由25MHz逐步提升到33MHz、40MHz、50MHz及后来的100Mhz。 486处理器的应用意味着用户从此摆脱了命令形式的计算机，进入“选中并点击(point-and-click)”的计算时代。Intel 486处理器首次采用内建的数学协处理器，将负载的数学运算功能从中央处理器中分离出来，从而显著加快了计算速度。Intel在芯片领域的霸主地位日益凸现。</p><h3 id="Pentium-奔腾"><a href="#Pentium-奔腾" class="headerlink" title="$Pentium$ 奔腾"></a>$Pentium$ 奔腾</h3><p><u>1993年,Intel推出了新一代CPU，但是这一代CPU并没有按照惯例用80586进行命名，为了同市面上其他厂商的CPU命名区分开来，Intel用Pentium来命名新一代CPU。</u>Intel公司还替它起了一个中文名字“奔腾”，从此人们都记住了Intel和奔腾。它包含了310万个以上晶体管，内置16K的一级 Cache，时钟频率由最初的60Mhz和66Mhz到后来的200MHz。均采用Socket 7架构。</p><p><img src = "https://cdn.jsdelivr.net/gh/FcAYH/Images@0cd79164e89028a870289420520dec382d11959b/2021/09/21/3fa907ec4ddbc501712e3ff01b601f11.png" alt = "pentium1" style = "zoom: 50%"></p><p>1996年，Intel推出了Pentium MMX。它集成了450万个以上晶体管，只有166/200/233三种频率，一级缓存均为32KB，使用Socket 7接口，这也是最后一款采用陶瓷封装的处理器。该处理器首次引入了MMX多媒体指令扩展集，增强了CPU的多媒体处理能力，音像、图形和通信应用方面而采取的新技术，它为CPU增加了57条MMX指令，除了指令集中增加MMX 指令外，还将CPU芯片内的一级缓存由原来的16KB增加到32KB，因此MMX CPU比普通CPU在运行含有MMX指令的程序时，处理多媒体的能力有了极大的提高。当时采用MMX CPU 的PC在出售时被称为“多媒体计算机”。</p><p><img src = "https://cdn.jsdelivr.net/gh/FcAYH/Images@04784661251a53cae61fca7ce3ead3b4aa87e9d3/2021/09/21/c87b4cd2d5363039f84cc2d5a5576fba.png" alt = "pentium MMX" style = "zoom: 50%"></p><p>随后Intel还接连推出了主要用于服务器和工作站的Pentium II Xeon 和 Pentium III等cpu。</p><h3 id="Celeron-赛扬"><a href="#Celeron-赛扬" class="headerlink" title="$Celeron$ 赛扬"></a>$Celeron$ 赛扬</h3><p>1997年，为了占领低端市场，Intel推出了Celeron(赛扬)，该CPU有266/300两种频率，外频为66MHz，不带二级高速缓存，该CPU超频性能极佳，但由于没有二级高速缓存，故机器整数运算性能虽然极佳，浮点运算性能却并不好，所以在推出一段时间后，没有获得成功，很快就淡出了市场。</p><p>1998年，为了弥补Celeron的不足，Intel又推出了Celeron A处理器，这款处理器堪称经典。它集成了128K二级缓存，采用了66MHz的前端总线，其中Celeron 300A的外频可以轻松上到100MHz，使得这款处理器可以稳定运行在450MHz的速度。超频到450MHz的Celeron 300A处理器足以与比它贵不少的Pentium II 400抗衡,该处理器成为了当时人们购置电脑的首选CPU。该CPU使用Slot 1接口，主频有300/333/366几种。</p><p><img src = "https://cdn.jsdelivr.net/gh/FcAYH/Images@e7680dd021b50070f2ede392738c6f47878753f0/2021/09/21/548c86af3d6e072a59d7294e5899d241.png" alt = "Celeron300A" style = "zoom: 50%"></p><p>2000年，为了进一步提高CPU的运行频率同时降低制造成本，Intel推出了Celeron II。Celeron II 所使用的接口为Socket 370，二级缓存为128KB。开始Celeron II处理器的外频为66MHz，随后Intel把Celeron II处理器的外频提高到了100MHz。其中核心为Tulatain的CPU集成了256K二级缓存。图所示为Intel Celeron II CPU。</p><h2 id="新的世纪新的起步"><a href="#新的世纪新的起步" class="headerlink" title="新的世纪新的起步"></a>新的世纪新的起步</h2><h3 id="CORE-酷睿"><a href="#CORE-酷睿" class="headerlink" title="$CORE$ 酷睿"></a>$CORE$ 酷睿</h3><p><u>2006年7月，英特尔公司面向家用和商用个人电脑与笔记本电脑，发布了十款全新英特尔酷睿2双核处理器和英特尔酷睿至尊处理器。</u> 英特尔酷睿2双核处理器设计用于提供出色的能效表现，并更快速地运行多种复杂应用，支持用户改进各种任务的处理，例如：更流畅地观看和播放高清晰度视频；在电子商务交易过程中更好地保护电脑及其资产；以及提供更耐久的电池使用时间和更加纤巧时尚的笔记本电脑外形。</p><p>全新处理器实现了高达40%的性能提升，其能效比最出色的英特尔奔腾处理器高出40%。英特尔酷睿2双核处理器包含2.91亿个晶体管。<u>不过，Pentium D谈不上是一套完美的双核架构，Intel只是将两个完全独立的CPU核心做在同一枚芯片上，通过同一条前端总线与芯片组相连。</u> 两个核心缺乏必要的协同和资源共享能力，而且还必须频繁地对二级缓存作同步化刷新动作，以避免两个核心的工作步调出问题。从这个意义上说，Pentium D带来的进步并没有人们预想得那么大！</p><h3 id="重新确定处理器产品架构"><a href="#重新确定处理器产品架构" class="headerlink" title="重新确定处理器产品架构"></a>重新确定处理器产品架构</h3><p>2011年3月，使用32nm工艺全新桌面级和移动端处理器采用了i3、i5和i7的产品分级架构。其中i3主攻低端市场，采用双核处理器架构，约2MB二级缓存。i5处理器主攻主流市场，采用四核处理器架构，4MB二级缓存。i7主攻高端市场，采用四核八线程或六核十二线程架构，二级缓存不少于8MB。</p><p><img src = "https://cdn.jsdelivr.net/gh/FcAYH/Images@5e930c737c64812afbea87c6ef351fbb9ef53696/2021/09/21/6ef19fb02f4fb98cb117a3268c9f2d59.png" alt = "Intel CORE i7 970" style = "zoom: 70%"></p><h3 id="首发桌面级8核心16线程处理器"><a href="#首发桌面级8核心16线程处理器" class="headerlink" title="首发桌面级8核心16线程处理器"></a>首发桌面级8核心16线程处理器</h3><p>2014年9月上市的i7-5960X处理器是第一款基于22nm工艺的八核心桌面级处理器，拥有高达20MB的三级缓存，主频达到3.5GHz，热功耗140W。此处理器的处理能力可谓超群，浮点数计算能力是普通办公电脑的10倍以上。随着这一“怪兽”处理器的问世，INTEL公司在处理器领域与AMD的差距越拉越大，已经完全形成了一家独大的局面。</p><p><img src = "https://cdn.jsdelivr.net/gh/FcAYH/Images@62f0649a9db3d32fde404bd27ae6914b4e43280e/2021/09/21/55425899d067a393433f3bcff0228510.png" alt = "Intel CORE i7 5960X" style = "zoom: 15%"></p><h3 id="14nm-工艺"><a href="#14nm-工艺" class="headerlink" title="14nm 工艺"></a>14nm 工艺</h3><p>随着CES 2015的到来，Intel 14nm处理器终于迎来了第一轮的爆发，第五代Core系列处理器正式登场。新处理器除了拥有更强的性能和功耗优化外，同时支持Intel RealSense技术，带来更加强大的体感交互体验。2015年1月，INTEL发布的处理器共计17款，全部为Broadwell-U处理器，低至赛扬，高至i7，覆盖高中低端产品线。功耗方面，除了配备Iris 6100核显的四款处理器TDP为28W，其他全部产品均为15W。核显中，Iris 6100和HD 6000具备48个EU，HD 5500具备24个EU（i3为23个），而奔腾、赛扬的HD Graphics则只有12个EU。最低功耗仅有15W，将明显提升移动设备的待机时间和用户体验。</p><h2 id="最新进展"><a href="#最新进展" class="headerlink" title="最新进展"></a>最新进展</h2><h3 id="Intel-Iris-Xe"><a href="#Intel-Iris-Xe" class="headerlink" title="$Intel\ \ Iris\ \  Xe$"></a>$Intel\ \ Iris\ \  Xe$</h3><p><u>2021年3月16日，Intel发布第11代酷睿产品。</u>第十一代智能英特尔酷睿处理器实现了空前协调的执行性能，搭载全新核心和显卡架构、基于人工智能的智能性能和一流的无线和有线连接，为笔记本电脑和台式机提供前所未有的卓越性能。英特尔 Xe 显卡架构在笔记本电脑和台式机上支持令人难以置信的丰富娱乐体验，如 4K HDR 和 1080p 游戏。同时，多达 20 条 PCIe 4.0 信道支持最新的独立 GPU。高端内置人工智能引入了全新功能，并且能够与您日常使用的应用程序协同工作，以优化任务的速度和流程，令完成任务变得更加容易。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;Intel-CPU-发展历程&quot;&gt;&lt;a href=&quot;#Intel-CPU-发展历程&quot; class=&quot;headerlink&quot; title=&quot;$Intel\ \ CPU$ 发展历程&quot;&gt;&lt;/a&gt;$Intel\ \ CPU$ 发展历程&lt;/h1&gt;&lt;h2 id=&quot;一：起点&quot;&gt;&lt;</summary>
      
    
    
    
    <category term="作业-考试" scheme="http://www.fcayh.cn/categories/%E4%BD%9C%E4%B8%9A-%E8%80%83%E8%AF%95/"/>
    
    
    <category term="微机与接口技术A" scheme="http://www.fcayh.cn/tags/%E5%BE%AE%E6%9C%BA%E4%B8%8E%E6%8E%A5%E5%8F%A3%E6%8A%80%E6%9C%AFA/"/>
    
  </entry>
  
  <entry>
    <title>第 246 场周赛</title>
    <link href="http://www.fcayh.cn/2021/07/27/weekly-contest-246/"/>
    <id>http://www.fcayh.cn/2021/07/27/weekly-contest-246/</id>
    <published>2021-07-27T06:23:29.000Z</published>
    <updated>2022-09-22T07:18:59.905Z</updated>
    
    <content type="html"><![CDATA[<h1 id="LeetCode-第-246-场周赛"><a href="#LeetCode-第-246-场周赛" class="headerlink" title="$LeetCode$ 第$ 246 $场周赛"></a>$LeetCode$ 第$ 246 $场周赛</h1><p><a href="https://leetcode-cn.com/contest/weekly-contest-246/">比赛链接</a></p><h2 id="A-字符串中的最大奇数"><a href="#A-字符串中的最大奇数" class="headerlink" title="A-字符串中的最大奇数"></a>A-字符串中的最大奇数</h2><p>给你一个长度为 $n$​ 字符串 $num$​，表示一个大整数。请你在字符串 $num$​ 的所有非空子字符串中找出值最大的奇数，并以字符串形式返回。如果不存在奇数，则返回一个空字符串 “” 。（子字符串是字符串中的一个连续的字符序列）（$1 \le n \le 10^5$​​）</p><p>签到题，一个数是奇数还是偶数，只取决于它的最后一位是奇数还是偶数。因此我们可以从后往前枚举，找到第一个奇数（假设是第$i$位），则字符串前$i$位组成最大的奇数。</p><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Solution</span> </span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="built_in">string</span> <span class="title">largestOddNumber</span><span class="params">(<span class="built_in">string</span> num)</span> </span></span><br><span class="line"><span class="function">    </span>&#123;</span><br><span class="line">        <span class="keyword">int</span> n = num.length();</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">int</span> pos = <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">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">if</span> ((num[i] - <span class="string">&#x27;0&#x27;</span>) % <span class="number">2</span> == <span class="number">1</span>)</span><br><span class="line">            &#123;</span><br><span class="line">                pos = i;</span><br><span class="line">                <span class="keyword">break</span>;</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">// 将这个大整数的前i位取出来</span></span><br><span class="line">        <span class="built_in">string</span> Ans = <span class="string">&quot;&quot;</span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt;= pos; i++)</span><br><span class="line">            Ans += num[i];</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (pos == <span class="number">0</span> &amp;&amp; (num[<span class="number">0</span>] - <span class="string">&#x27;0&#x27;</span>) % <span class="number">2</span> == <span class="number">0</span>)</span><br><span class="line">            <span class="keyword">return</span> <span class="string">&quot;&quot;</span>;</span><br><span class="line">        <span class="keyword">else</span> </span><br><span class="line">            <span class="keyword">return</span> Ans;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h2 id="B-你完成的完整对局数"><a href="#B-你完成的完整对局数" class="headerlink" title="B-你完成的完整对局数"></a>B-你完成的完整对局数</h2><p>这个游戏在每小时的$00, 15, 30, 45$​​分钟时，会有一场开始一场对局（每局15分钟）。现在告诉你你进入游戏的时间和你退出游戏的时间，问你一共进行了几个完整的对局。（输入格式为$HH:MM$​，$24$​小时制，保证时间合法）</p><p>模拟题，可以先统计自己在游戏中度过了几个完整的小时，如果我们度过了$i$​​个完整的小时，那我们至少完成了$4\times i$​​个完整的对局。然后如果开头和结尾没有度过完整的一个小时，单独处理一下即可（用$if$​语句暴力写就行）。不过我这里，直接不管它开头结尾是不是满一个小时，我都单独去计算了，这样后果就是，如果开头和结尾在同一个小时中，就计算重复了。故而再次添加特判处理开头结尾在同一小时的情况。</p><figure class="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><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="class"><span class="keyword">class</span> <span class="title">Solution</span> </span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="keyword">int</span> <span class="title">numberOfRounds</span><span class="params">(<span class="built_in">string</span> startTime, <span class="built_in">string</span> finishTime)</span> </span></span><br><span class="line"><span class="function">    </span>&#123;</span><br><span class="line">        <span class="comment">// 将小时和分钟单独取出来，方便后面计算。</span></span><br><span class="line">        <span class="keyword">int</span> startH = (startTime[<span class="number">0</span>] - <span class="string">&#x27;0&#x27;</span>) * <span class="number">10</span> + (startTime[<span class="number">1</span>] - <span class="string">&#x27;0&#x27;</span>);</span><br><span class="line">        <span class="keyword">int</span> endH = (finishTime[<span class="number">0</span>] - <span class="string">&#x27;0&#x27;</span>) * <span class="number">10</span> + (finishTime[<span class="number">1</span>] - <span class="string">&#x27;0&#x27;</span>);</span><br><span class="line">        <span class="keyword">int</span> startM = (startTime[<span class="number">3</span>] - <span class="string">&#x27;0&#x27;</span>) * <span class="number">10</span> + (startTime[<span class="number">4</span>] - <span class="string">&#x27;0&#x27;</span>);</span><br><span class="line">        <span class="keyword">int</span> endM = (finishTime[<span class="number">3</span>] - <span class="string">&#x27;0&#x27;</span>) * <span class="number">10</span> + (finishTime[<span class="number">4</span>] - <span class="string">&#x27;0&#x27;</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">int</span> Ans = <span class="number">0</span>;</span><br><span class="line">        <span class="comment">// 这是开头和结尾在同一个小时中的情况。</span></span><br><span class="line">        <span class="keyword">if</span> (startH == endH &amp;&amp; startM &lt;= endM) </span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">int</span> flag = <span class="number">0</span>;</span><br><span class="line">            <span class="comment">// 非常暴力，直接一分钟一分钟的度过，然后判断。</span></span><br><span class="line">            <span class="keyword">for</span> (<span class="keyword">int</span> i = startM; i &lt;= endM; i++)</span><br><span class="line">            &#123;</span><br><span class="line">                <span class="keyword">if</span> (flag == <span class="number">1</span> &amp;&amp; (i == <span class="number">0</span> || i == <span class="number">15</span> || i == <span class="number">30</span> || i == <span class="number">45</span> || i == <span class="number">60</span>))</span><br><span class="line">                    Ans++;</span><br><span class="line">                <span class="keyword">else</span> <span class="keyword">if</span> ((i == <span class="number">0</span> || i == <span class="number">15</span> || i == <span class="number">30</span> || i == <span class="number">45</span> || i == <span class="number">60</span>))</span><br><span class="line">                    flag = <span class="number">1</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">else</span> </span><br><span class="line">        &#123;</span><br><span class="line">            <span class="comment">// 单独处理开头。</span></span><br><span class="line">            <span class="keyword">int</span> flag = <span class="number">0</span>;</span><br><span class="line">            <span class="keyword">for</span> (<span class="keyword">int</span> i = startM; i &lt;=<span class="number">60</span>; i++)</span><br><span class="line">            &#123;    </span><br><span class="line">                <span class="keyword">if</span> (flag == <span class="number">1</span> &amp;&amp; (i == <span class="number">0</span> || i == <span class="number">15</span> || i == <span class="number">30</span> || i == <span class="number">45</span> || i == <span class="number">60</span>))</span><br><span class="line">                    Ans++;</span><br><span class="line">                <span class="keyword">else</span> <span class="keyword">if</span> ((i == <span class="number">0</span> || i == <span class="number">15</span> || i == <span class="number">30</span> || i == <span class="number">45</span> || i == <span class="number">60</span>))</span><br><span class="line">                    flag = <span class="number">1</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 计算中间有几个完整的一小时。</span></span><br><span class="line">            <span class="keyword">if</span> (startH &lt; endH)</span><br><span class="line">                Ans += max(<span class="number">0</span>, (endH - startH - <span class="number">1</span>)) * <span class="number">4</span>;</span><br><span class="line">            <span class="keyword">else</span></span><br><span class="line">            &#123;</span><br><span class="line">                Ans += max(<span class="number">0</span>, (<span class="number">24</span> - startH - <span class="number">1</span>)) * <span class="number">4</span>;</span><br><span class="line">                Ans += max(<span class="number">0</span>, (endH)) * <span class="number">4</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 单独处理结尾。</span></span><br><span class="line">            <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt;= endM; i++)</span><br><span class="line">            &#123;</span><br><span class="line">                <span class="keyword">if</span> (flag == <span class="number">1</span> &amp;&amp; (i == <span class="number">15</span> || i == <span class="number">30</span> || i == <span class="number">45</span> || i == <span class="number">60</span>))</span><br><span class="line">                    Ans++;</span><br><span class="line">                <span class="keyword">else</span> <span class="keyword">if</span> ((i == <span class="number">0</span> || i == <span class="number">15</span> || i == <span class="number">30</span> || i == <span class="number">45</span> || i == <span class="number">60</span>))</span><br><span class="line">                    flag = <span class="number">1</span>;</span><br><span class="line">            &#125;   </span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> Ans;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h2 id="C-统计子岛屿"><a href="#C-统计子岛屿" class="headerlink" title="C-统计子岛屿"></a>C-统计子岛屿</h2><p>给你两个 $m \times n$​​​ 的二进制矩阵 $grid1$​​​ 和 $grid2$​​​，它们只包含 $0$​​​ （表示水域）和 $1$​​​ （表示陆地）。一个岛屿是由四个方向 （水平或者竖直）上相邻的 $1$​​​ 组成的区域。任何矩阵以外的区域都视为水域。如果 $grid2$​​​ 的一个岛屿，被 $grid1$​​​ 的一个岛屿 完全 包含，也就是说 $grid2$​​​ 中该岛屿的每一个格子都被 $grid1$​​​ 中同一个岛屿完全包含，那么我们称 $grid2$​​​ 中的这个岛屿为子岛屿 。而我们要求 $grid2$​​​ 中子岛屿的数目 。（$1 \le m,n \le 500$​）</p><p>搜索+模拟。</p><p>我采取了一个很暴力的做法，首先我对$grid1$​进行$dfs$​，并使用$vis1$​数组，把其中的不同岛屿标记上不同的数字。具体过程就是首先$vis1$​清零，随后由每一个$vis1$​为零且$grid1$​不为零的点开始$dfs$，将相邻的且$grid1$均为$1$的点，在$vis1$中标记为一个相同的整数。随后再对$grid2$​使用$dfs$​，并使用$vis2$​进行标记，不过在对$vis2$​进行标记的过程中，要不断和同位置上的$vis1$​进行比较，如果始终没有发现不同，则说明这是一个子岛屿。</p><p>由于两个 $m \times n$ 的矩阵中的每个点都最多被$dfs$访问一次，所以复杂度为$O(m \times n)$。</p><figure class="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><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span> vis1[<span class="number">510</span>][<span class="number">510</span>];</span><br><span class="line"><span class="keyword">int</span> vis2[<span class="number">510</span>][<span class="number">510</span>];</span><br><span class="line"></span><br><span class="line"><span class="comment">// 方向数组，便于dfs过程中确定下一个要求的点。</span></span><br><span class="line"><span class="keyword">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="keyword">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><br><span class="line"><span class="keyword">int</span> checkflag = <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Solution</span> </span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="keyword">int</span> n;</span><br><span class="line">    <span class="keyword">int</span> m;</span><br><span class="line">    </span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">dfs1</span><span class="params">(<span class="keyword">int</span> x,<span class="keyword">int</span> y, <span class="built_in">vector</span>&lt;<span class="built_in">vector</span>&lt;<span class="keyword">int</span>&gt;&gt;&amp; grid1, <span class="keyword">int</span> cnt)</span></span></span><br><span class="line"><span class="function">    </span>&#123;</span><br><span class="line">        <span class="comment">// 标记。</span></span><br><span class="line">        vis1[x][y] = cnt;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; <span class="number">4</span>; i++)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">int</span> newx = x + dx[i];</span><br><span class="line">            <span class="keyword">int</span> newy = y + dy[i];</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">if</span> (newx &gt;= <span class="number">0</span> &amp;&amp; newx &lt; n &amp;&amp; newy &gt;= <span class="number">0</span> &amp;&amp; newy &lt; m)</span><br><span class="line">            &#123;</span><br><span class="line">                <span class="comment">// 去相邻的，grid1不为0且vis1不为0（即没被访问过的）。</span></span><br><span class="line">                <span class="keyword">if</span> (grid1[newx][newy] == <span class="number">1</span> &amp;&amp; !vis1[newx][newy])</span><br><span class="line">                    dfs1(newx, newy, grid1, cnt);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">dfs2</span><span class="params">(<span class="keyword">int</span> x,<span class="keyword">int</span> y, <span class="built_in">vector</span>&lt;<span class="built_in">vector</span>&lt;<span class="keyword">int</span>&gt;&gt;&amp; grid2, <span class="keyword">int</span> cnt)</span></span></span><br><span class="line"><span class="function">    </span>&#123;</span><br><span class="line">        vis2[x][y] = cnt;</span><br><span class="line">        <span class="comment">// 这说明在grid2中是1，但是在grid1中为0，必然不是子岛屿，故返回。</span></span><br><span class="line">        <span class="keyword">if</span> (vis1[x][y] == <span class="number">0</span>)</span><br><span class="line">            checkflag = <span class="number">0</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; <span class="number">4</span>; i++)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">int</span> newx = x + dx[i];</span><br><span class="line">            <span class="keyword">int</span> newy = y + dy[i];</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">if</span> (newx &gt;= <span class="number">0</span> &amp;&amp; newx &lt; n &amp;&amp; newy &gt;= <span class="number">0</span> &amp;&amp; newy &lt; m)</span><br><span class="line">            &#123;</span><br><span class="line">                <span class="keyword">if</span> (grid2[newx][newy] == <span class="number">1</span> &amp;&amp; !vis2[newx][newy])</span><br><span class="line">                    dfs2(newx, newy, grid2, cnt);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="keyword">int</span> <span class="title">countSubIslands</span><span class="params">(<span class="built_in">vector</span>&lt;<span class="built_in">vector</span>&lt;<span class="keyword">int</span>&gt;&gt;&amp; grid1, <span class="built_in">vector</span>&lt;<span class="built_in">vector</span>&lt;<span class="keyword">int</span>&gt;&gt;&amp; grid2)</span> </span></span><br><span class="line"><span class="function">    </span>&#123;</span><br><span class="line">        n = grid1.size();</span><br><span class="line">        m = grid1[<span class="number">0</span>].size();</span><br><span class="line">        </span><br><span class="line">        <span class="built_in">memset</span>(vis1, <span class="number">0</span>, <span class="keyword">sizeof</span>(vis1));</span><br><span class="line">        <span class="built_in">memset</span>(vis2, <span class="number">0</span>, <span class="keyword">sizeof</span>(vis1));</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">int</span> count1 = <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; n; i++)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="keyword">int</span> j = <span class="number">0</span>; j &lt; m; j++)</span><br><span class="line">            &#123;</span><br><span class="line">                <span class="comment">// 对于每一个vis1不为0且grid1也不为零的点，dfs</span></span><br><span class="line">                <span class="keyword">if</span> (!vis1[i][j] &amp;&amp; grid1[i][j] == <span class="number">1</span>)</span><br><span class="line">                    dfs1(i, j, grid1, ++count1);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">int</span> Ans = <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">int</span> count2 = <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; n; i++)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="keyword">int</span> j = <span class="number">0</span>; j &lt; m; j++)</span><br><span class="line">            &#123;</span><br><span class="line">                <span class="keyword">if</span> (!vis2[i][j] &amp;&amp; grid2[i][j] == <span class="number">1</span>)</span><br><span class="line">                &#123;</span><br><span class="line">                    checkflag = <span class="number">1</span>;</span><br><span class="line">                    dfs2(i, j, grid2, ++count2);</span><br><span class="line">                    </span><br><span class="line">                    <span class="keyword">if</span> (checkflag)</span><br><span class="line">                        Ans++;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> Ans;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h2 id="D-查询差绝对值的最小值"><a href="#D-查询差绝对值的最小值" class="headerlink" title="D-查询差绝对值的最小值"></a>D-查询差绝对值的最小值</h2><p>从给定的$n$​个数中任取两个不同的数，其之差的绝对值的最小值为这$n$​个数的差绝对值的最小值。现在给你一个长度为$n$​的数组$a$​，和$m$​个询问，每个询问给定一组$l, r$​，要求出对于每个询问，区间$l,r$​中数的差绝对值的最小值。（$1 \le n \le 10^5$​， $1 \le m \le 2\times 10^4$​，$1 \le a[i] \le 100$​）</p><p>直接暴力的话，最差复杂度得有$n^2 \times m$​​​​​了。如果我们每次可以<strong>快速获得一个排好序的</strong>$l,r$​​​​​区间，那后续计算的复杂度就变成最差$O(n)$​​​​​了，因为只需要比较每一对相邻两数的差就可以了。当然这样就算我们可以$O(1)$​​获得排好序的区间，总复杂度仍有$O(n \times m)$​​，我们依旧接受不了，还要再优化。这时候我们发现：题目说数的范围仅$[1,100]$​​。这不就说明，如果我们可以<strong>快速获得一个去重且排好序的</strong>$l,r$​​区间，那么后续计算复杂度就变成了最差$O(100)$​​​​了，便可以接受了。</p><p>那么现在的任务是<strong>快速获得一个去重且排好序的</strong>$l,r$​​​​​​区间，其实这个也很容易，答案是利用二分。（自从发现自己屡次手写二分出$bug$​​​​​​改半天改不完后，我就暂时放弃了使用C#写算法题，回归了C++）我们可以开一个<code>vector Count[101]</code>，其中$Count[i]$​​​​用来记录数字$i$​​​​都出现在了哪些位置。这样当我们面对一个询问区间$l, r$​​​​，我们可以从$1\sim100$​​​​枚举数字$i$​​​​，因为我们是按顺序将每个数字出现的位置存到$Count$​​​​中的，故而可以对$Count[i]$​​​​二分查找其中有没有属于$[l,r]$​​​​的数，如果有，则说明数字 $i$​​​​ 在$[l,r]$​​​​中。我们可以另一个<code>vector CompareA</code>存储下所有这样的$i$​​​，显然在$CompareA$​​中最多只有$100$​​​​​​个数，然后暴力计算$CompareA$中数的差的绝对值的最小值，即为询问$[l,r]$的答案。</p><figure class="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><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></pre></td><td class="code"><pre><span class="line"><span class="built_in">vector</span>&lt;<span class="keyword">int</span>&gt; Count[<span class="number">101</span>];</span><br><span class="line"><span class="built_in">vector</span>&lt;<span class="keyword">int</span>&gt; CompareA;</span><br><span class="line"><span class="built_in">vector</span>&lt;<span class="keyword">int</span>&gt; Ans;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Solution</span> </span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="built_in">vector</span>&lt;<span class="keyword">int</span>&gt; <span class="title">minDifference</span><span class="params">(<span class="built_in">vector</span>&lt;<span class="keyword">int</span>&gt;&amp; nums, <span class="built_in">vector</span>&lt;<span class="built_in">vector</span>&lt;<span class="keyword">int</span>&gt;&gt;&amp; queries)</span> </span></span><br><span class="line"><span class="function">    </span>&#123;</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt;= <span class="number">100</span>; i++) Count[i].clear();</span><br><span class="line">        <span class="keyword">int</span> n = nums.size();</span><br><span class="line">        <span class="comment">// 记录下每个数字出现的位置</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; n; i++)</span><br><span class="line">            Count[nums[i]].push_back(i);</span><br><span class="line">        </span><br><span class="line">        Ans.clear();</span><br><span class="line">        <span class="keyword">int</span> m = queries.size();</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; m; i++)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">int</span> l = queries[i][<span class="number">0</span>], r = queries[i][<span class="number">1</span>];</span><br><span class="line">            </span><br><span class="line">            CompareA.clear();</span><br><span class="line">            <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">1</span>; i &lt;= <span class="number">100</span>; i++)</span><br><span class="line">            &#123;</span><br><span class="line">                <span class="keyword">if</span> (Count[i].size() != <span class="number">0</span>)</span><br><span class="line">                &#123;</span><br><span class="line">                    <span class="keyword">int</span> pos = lower_bound(Count[i].begin(), Count[i].end(), l) - Count[i].begin(); </span><br><span class="line">                    </span><br><span class="line">                    <span class="comment">// 这说明数字i存在于[l,r]中</span></span><br><span class="line">                    <span class="keyword">if</span> (pos &lt; Count[i].size())</span><br><span class="line">                    &#123;</span><br><span class="line">                        <span class="keyword">if</span> (Count[i][pos] &gt;= l &amp;&amp; Count[i][pos] &lt;= r)</span><br><span class="line">                            CompareA.push_back(i);</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">int</span> ans = <span class="number">0x3f3f3f3f</span>;</span><br><span class="line">            <span class="keyword">if</span> (CompareA.size() == <span class="number">1</span>)</span><br><span class="line">                ans = <span class="number">-1</span>;</span><br><span class="line">            <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">1</span>; i &lt; CompareA.size(); i++)</span><br><span class="line">                ans = min(ans, CompareA[i] - CompareA[i - <span class="number">1</span>]);</span><br><span class="line">            </span><br><span class="line">            Ans.push_back(ans);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> Ans;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;LeetCode-第-246-场周赛&quot;&gt;&lt;a href=&quot;#LeetCode-第-246-场周赛&quot; class=&quot;headerlink&quot; title=&quot;$LeetCode$ 第$ 246 $场周赛&quot;&gt;&lt;/a&gt;$LeetCode$ 第$ 246 $场周赛&lt;/h1&gt;&lt;</summary>
      
    
    
    
    <category term="算法" scheme="http://www.fcayh.cn/categories/%E7%AE%97%E6%B3%95/"/>
    
    
    <category term="模拟" scheme="http://www.fcayh.cn/tags/%E6%A8%A1%E6%8B%9F/"/>
    
    <category term="搜索" scheme="http://www.fcayh.cn/tags/%E6%90%9C%E7%B4%A2/"/>
    
    <category term="LeetCode" scheme="http://www.fcayh.cn/tags/LeetCode/"/>
    
    <category term="二分查找" scheme="http://www.fcayh.cn/tags/%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE/"/>
    
  </entry>
  
  <entry>
    <title>「微爱思扣 以 Code 会友」专场竞赛</title>
    <link href="http://www.fcayh.cn/2021/07/13/lc-vscode/"/>
    <id>http://www.fcayh.cn/2021/07/13/lc-vscode/</id>
    <published>2021-07-13T04:11:09.000Z</published>
    <updated>2023-02-25T15:40:45.506Z</updated>
    
    <content type="html"><![CDATA[<h1 id="「微爱思扣-以-Code-会友」专场竞赛"><a href="#「微爱思扣-以-Code-会友」专场竞赛" class="headerlink" title="「微爱思扣 以 Code 会友」专场竞赛"></a>「微爱思扣 以 Code 会友」专场竞赛</h1><p><a href="https://leetcode-cn.com/contest/lc-vscode/">比赛链接</a></p><h2 id="A-下载插件"><a href="#A-下载插件" class="headerlink" title="A-下载插件"></a>A-下载插件</h2><p>小扣打算给自己的 $VS code$ 安装使用插件，初始状态下带宽每分钟可以完成 $1 $个插件的下载。假定每分钟选择以下两种策略之一:</p><ul><li><p>使用当前带宽下载插件；</p></li><li><p>将带宽加倍（下载插件数量随之加倍）。</p></li></ul><p>请返回小扣完成下载 $n$ 个插件最少需要多少分钟。$(1\le n \le 10^5)$</p><p>注意：实际的下载的插件数量可以超过 $n$​ 个。</p><hr><p>这个题我们可以这样想，如果$n$是$2$的次幂，即$n = 2^k$，则连续加倍$k$次，然后下载一次需要$k + 1$分钟。而如果我们仅连续加倍$k - 1$次，则需要下载两次，最后还是$k + 1$分钟。不过我们再减小，只连续加倍$k - 2$ 次，我们就需要下载$4$次了。由此我们可以看出，在$n = 2^k$的情况下，一直加倍直到可以一次下载完是最优的。</p><p>那么其他情况呢？显然对于任意一个数$n$，我们都可以找到一个合适的$ k \\in N$使得$n \\in [2^k,2^{k+1}]$，则根据红字，我们至少有了一种在$k + 2$分钟下载完$n$个插件的方案。那么还能更快么？ 答案是不能，道理也同上，减少加倍的次数，会使得下载次数增多的更多，故而无法使答案更优。</p><p>所以我们只需要把$n$转换为不小于自己的最小的$2^k$，则答案就是$k + 2$。</p><p>复杂度$O(log(n))$。</p><figure class="highlight c#"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">Solution</span> </span><br><span class="line">&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="built_in">int</span> <span class="title">LeastMinutes</span>(<span class="params"><span class="built_in">int</span> n</span>) </span></span><br><span class="line"><span class="function"></span>    &#123;</span><br><span class="line">        <span class="built_in">int</span> Ans = <span class="number">0</span>, val = <span class="number">1</span>;</span><br><span class="line">        <span class="keyword">while</span>(val &lt; n)</span><br><span class="line">        &#123;</span><br><span class="line">            val &lt;&lt;= <span class="number">1</span>;</span><br><span class="line">            Ans++;</span><br><span class="line">        &#125;        </span><br><span class="line">        <span class="keyword">return</span> Ans + <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="B-完成一半题目"><a href="#B-完成一半题目" class="headerlink" title="B-完成一半题目"></a>B-完成一半题目</h2><p>有 $N$ 位扣友参加了微软与力扣举办了「以扣会友」线下活动。主办方提供了$ 2\\times N$ 道题目，整型数组 $questions$ 中每个数字对应了每道题目所涉及的知识点类型。若每位扣友选择不同的一题，请返回被选的$ N$ 道题目至少包含多少种知识点类型。$(1\le N \le 5 \times 10^4)$</p><p>大体意思可以抽象为，给你一个有$2\times N$个正整数的数组，让你从中选$N$​个数，问至少会有多少不同的数。</p><hr><p>做法也很简单，按照数的数目排个序，从最多的开始取即可(贪心嘛)。</p><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">inline</span> <span class="keyword">int</span> <span class="title">cmp</span><span class="params">(<span class="keyword">int</span> x, <span class="keyword">int</span> y)</span></span>&#123; <span class="keyword">return</span> x &gt; y; &#125;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Solution</span> </span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="keyword">int</span> <span class="title">halfQuestions</span><span class="params">(<span class="built_in">vector</span>&lt;<span class="keyword">int</span>&gt;&amp; questions)</span> </span></span><br><span class="line"><span class="function">    </span>&#123;</span><br><span class="line">        <span class="keyword">int</span> MaxN = <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">int</span> n = questions.size();</span><br><span class="line"></span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; n; i++)</span><br><span class="line">            MaxN = max(MaxN, questions[i]);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">int</span> *vis = <span class="keyword">new</span> <span class="keyword">int</span>[MaxN + <span class="number">1</span>]; </span><br><span class="line">        <span class="built_in">memset</span>(vis, <span class="number">0</span>, <span class="keyword">sizeof</span>(<span class="keyword">int</span>) * (MaxN + <span class="number">1</span>));</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; n; i++)</span><br><span class="line">            vis[questions[i]]++; </span><br><span class="line"></span><br><span class="line">        sort(vis + <span class="number">1</span>, vis + <span class="number">1</span> + MaxN, cmp);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">int</span> person = questions.size() &gt;&gt; <span class="number">1</span>;</span><br><span class="line">        <span class="keyword">int</span> Ans = <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">1</span>; i &lt;= MaxN; i++)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">if</span> (vis[i])</span><br><span class="line">            &#123;</span><br><span class="line">                Ans++;</span><br><span class="line">                <span class="keyword">while</span> (vis[i])</span><br><span class="line">                &#123;</span><br><span class="line">                    vis[i]--;</span><br><span class="line">                    person--;</span><br><span class="line">                    <span class="keyword">if</span> (person == <span class="number">0</span>)</span><br><span class="line">                        <span class="keyword">return</span> Ans;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> Ans;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h2 id="C-主题空间"><a href="#C-主题空间" class="headerlink" title="C-主题空间"></a>C-主题空间</h2><p>给你一个$n \times m$的矩阵，每个元素的值都是$0 \sim5$的整数。相邻且数字一样的格子我们认为是同一个主题空间。$0$表示走廊，矩阵外面认为全是走廊。现在要求不和走廊相邻的面积最大的主题空间。</p><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images//2021/07/13/eefa5d48cdefec3beb06a24b48f7a54e.png"></p><p>例如上图为$4\times 11$的矩阵，其中不与走廊相邻的主题空间有5个，分别用不同的颜色标记在下图：</p><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images//2021/07/13/cc77dd8c61d33c70b649994eea5545e5.png"></p><p>其大小分别为$3,1,1,1,2$​。故最大的为$3$​。$(1\le n,m\le 500)$​</p><hr><p>对于这个数据范围，我们可以大胆的使用$BFS$搜索，枚举每一个不在边上且不是道路的格子，并向四周搜索。在搜索过程中记录当前扩展的面积为多大，并且判断相邻的格子是不是走廊。</p><p>不过有些需要考虑的点：</p><ul><li><p>开一个二维数组$vis$记录每个格子有没有被访问过，从而防止重复访问同样的格子。</p></li><li><p>每次$BFS$的时候，将当前主题空间的所有格均在$vis$中标记为$1$，防止同一主题空间的格多次开启$BFS$。</p></li></ul><figure class="highlight c#"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">Solution</span> </span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="built_in">int</span> row, col;</span><br><span class="line">    <span class="keyword">private</span> <span class="built_in">int</span>[] dx = <span class="keyword">new</span> <span class="built_in">int</span>[]&#123;<span class="number">0</span>, <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="keyword">private</span> <span class="built_in">int</span>[] dy = <span class="keyword">new</span> <span class="built_in">int</span>[]&#123;<span class="number">0</span>, <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="keyword">private</span> <span class="built_in">int</span>[,] vis;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="built_in">int</span> <span class="title">Bfs</span>(<span class="params"><span class="built_in">int</span> x, <span class="built_in">int</span> y, <span class="built_in">string</span>[] grid</span>)</span></span><br><span class="line"><span class="function"></span>    &#123;</span><br><span class="line">        Queue&lt;<span class="built_in">int</span>&gt; qX = <span class="keyword">new</span> Queue&lt;<span class="built_in">int</span>&gt;();</span><br><span class="line">        Queue&lt;<span class="built_in">int</span>&gt; qY = <span class="keyword">new</span> Queue&lt;<span class="built_in">int</span>&gt;();</span><br><span class="line">        qX.Enqueue(x);</span><br><span class="line">        qY.Enqueue(y);</span><br><span class="line">        vis[x, y] = <span class="number">1</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="built_in">int</span> tempVal = <span class="number">0</span>, flag = <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">while</span> (qX.Count() != <span class="number">0</span>)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="built_in">int</span> nowX = qX.Dequeue(), nowY = qY.Dequeue();</span><br><span class="line">            tempVal++;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">for</span> (<span class="built_in">int</span> i = <span class="number">1</span>; i &lt;= <span class="number">4</span>; i++)</span><br><span class="line">            &#123;</span><br><span class="line">                <span class="built_in">int</span> nextX = nowX + dx[i], nextY = nowY + dy[i];</span><br><span class="line">            </span><br><span class="line">                <span class="comment">//超出范围</span></span><br><span class="line">                <span class="keyword">if</span> (nextX &lt; <span class="number">0</span> || nextX &gt; row - <span class="number">1</span> || nextY &lt; <span class="number">0</span> || nextY &gt; col - <span class="number">1</span>)</span><br><span class="line">                    <span class="keyword">continue</span>;</span><br><span class="line"></span><br><span class="line">                <span class="comment">//标记一下这次bfs的主题空间是和走廊接壤的</span></span><br><span class="line">                <span class="keyword">if</span> (grid[nextX][nextY] == <span class="string">&#x27;0&#x27;</span>)</span><br><span class="line">                &#123;   </span><br><span class="line">                    flag = <span class="number">1</span>;</span><br><span class="line">                    <span class="keyword">continue</span>;</span><br><span class="line">                &#125;</span><br><span class="line">                </span><br><span class="line">                <span class="keyword">if</span> (grid[nextX][nextY] == grid[nowX][nowY])</span><br><span class="line">                &#123;</span><br><span class="line">                    <span class="comment">//同上，标记一下这个主题空间是和走廊接壤的</span></span><br><span class="line">                    <span class="keyword">if</span> (nextX == <span class="number">0</span> || nextX == row - <span class="number">1</span> || nextY == <span class="number">0</span> || nextY == col - <span class="number">1</span>)</span><br><span class="line">                        flag = <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line">                    <span class="keyword">if</span> (vis[nextX, nextY] == <span class="number">0</span>)</span><br><span class="line">                    &#123;</span><br><span class="line">                        qX.Enqueue(nextX);</span><br><span class="line">                        qY.Enqueue(nextY);</span><br><span class="line">                        vis[nextX, nextY] = <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">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> (flag == <span class="number">1</span>) ? <span class="number">0</span> : tempVal;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="built_in">int</span> <span class="title">LargestArea</span>(<span class="params"><span class="built_in">string</span>[] grid</span>) </span></span><br><span class="line"><span class="function"></span>    &#123;</span><br><span class="line">        row = grid.Length;</span><br><span class="line">        col = grid[<span class="number">0</span>].Length;</span><br><span class="line"></span><br><span class="line">        vis = <span class="keyword">new</span> <span class="built_in">int</span>[row, col];</span><br><span class="line"></span><br><span class="line">        <span class="built_in">int</span> Ans = <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="built_in">int</span> i = <span class="number">1</span>; i &lt; row - <span class="number">1</span>; i++)</span><br><span class="line">            <span class="keyword">for</span> (<span class="built_in">int</span> j = <span class="number">1</span>; j &lt; col - <span class="number">1</span>; j++)</span><br><span class="line">                <span class="keyword">if</span> (vis[i, j] == <span class="number">0</span> &amp;&amp; grid[i][j] != <span class="string">&#x27;0&#x27;</span>)</span><br><span class="line">                    Ans = Math.Max(Ans, Bfs(i, j, grid));</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> Ans;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;「微爱思扣-以-Code-会友」专场竞赛&quot;&gt;&lt;a href=&quot;#「微爱思扣-以-Code-会友」专场竞赛&quot; class=&quot;headerlink&quot; title=&quot;「微爱思扣 以 Code 会友」专场竞赛&quot;&gt;&lt;/a&gt;「微爱思扣 以 Code 会友」专场竞赛&lt;/h1&gt;&lt;</summary>
      
    
    
    
    <category term="算法" scheme="http://www.fcayh.cn/categories/%E7%AE%97%E6%B3%95/"/>
    
    
    <category term="贪心" scheme="http://www.fcayh.cn/tags/%E8%B4%AA%E5%BF%83/"/>
    
    <category term="BFS" scheme="http://www.fcayh.cn/tags/BFS/"/>
    
    <category term="搜索" scheme="http://www.fcayh.cn/tags/%E6%90%9C%E7%B4%A2/"/>
    
    <category term="LeetCode" scheme="http://www.fcayh.cn/tags/LeetCode/"/>
    
  </entry>
  
  <entry>
    <title>LeetCode810. 黑板异或游戏</title>
    <link href="http://www.fcayh.cn/2021/05/22/leetcode810/"/>
    <id>http://www.fcayh.cn/2021/05/22/leetcode810/</id>
    <published>2021-05-22T02:58:57.000Z</published>
    <updated>2022-04-12T13:40:02.460Z</updated>
    
    <content type="html"><![CDATA[<h1 id="LeetCode810-黑板异或游戏"><a href="#LeetCode810-黑板异或游戏" class="headerlink" title="LeetCode810. 黑板异或游戏"></a>LeetCode810. 黑板异或游戏</h1><p><a href="https://leetcode-cn.com/problems/chalkboard-xor-game/">810. 黑板异或游戏</a></p><p>题意是有一个数组，两个人轮流从中取数，当一个人在要取数时，数组中剩余数的异或值为$0$，则该人获胜。问：给你这个数组，判断先手能否必胜</p><p>数据范围 $1 \le N \le 1000,\quad 0 \le nums[i] \le 2^{16}$</p><p>这个题很巧妙，乍一看感觉是个博弈论，但是仔细一思考，其实没有那么难。</p><p>首先，<strong style = "color:red">因为是两个人轮流取数，那么如果刚开始是偶数个数，则每次他取数的时候，数组中都是剩余偶数个数，奇数也同理。</strong>那么我们就可以从奇偶性上下手，假设$N$个数的异或值为$S$，若$S = 0$ 则先手直接胜利，若$S \neq 0$，若我们从中取走$nums[i]$之后，剩余数的异或值为$A_i$。当我们任意取走一个$nums[i]$后，$A_i$均为$0$，则说明此时先手必输。</p><p>由$A_i \bigoplus nums[i] = S$可推出$A_i = nums[i] \bigoplus S$。</p><p>若任意$A_i$均为$0$，那么有$A_1 \bigoplus A_2 \bigoplus … A_n = 0$，即</p><p>$nums[1] \bigoplus S \bigoplus nums[2] \bigoplus S \bigoplus… nums[n] \bigoplus S = 0$</p><p>$\begin{matrix}N\\\overbrace{S \bigoplus S \cdots\bigoplus S}\\\\\end{matrix}  \bigoplus nums[1] \bigoplus …nums[n] = 0  \Longrightarrow \begin{matrix}N + 1\\\overbrace{S \bigoplus …S }\\\\\end{matrix}= 0$</p><p>因为$S \neq 0$，故当$N$为偶数时，$N+1$为奇数，此时$N+1$个$S$异或起来必然不为$0$，矛盾。</p><p>故而得出结论。当$N$为偶数时，不可能出现不管我们取哪一个数，剩余数的异或值均为$0$的情况。也就是说，当一个人要取数时，此时场上剩下偶数个数，那这个人取完数一定不会输。而结合上文红字，若开局数组中数的个数为偶数，则先手每次取数时，面对的均为有偶数个数的数组，则他必然不会输，及必胜。而当先手面临的是奇数个数时，就说明后手面临的是偶数个数，则后手必胜。但是也有一个特殊情况就是刚开局的时候，虽然数组中有奇数个数，但是他们的异或值为$0$，此时仍是先手胜。</p><p>复杂度$O(N)$</p><figure class="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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;bits/stdc++.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> <span class="built_in">std</span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Solve</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> N = <span class="number">0</span>;</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><br><span class="line">    <span class="keyword">int</span> *nums = <span class="keyword">new</span> <span class="keyword">int</span>[N + <span class="number">1</span>];</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">1</span>; i &lt;= N; i++)</span><br><span class="line">        <span class="built_in">scanf</span>(<span class="string">&quot;%d&quot;</span>, &amp;nums[i]);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (!(N % <span class="number">2</span>))</span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot;true&quot;</span>);</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">2</span>; i &lt;= N; i++)</span><br><span class="line">            nums[<span class="number">1</span>] ^= nums[i];</span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot;%s&quot;</span>, (nums[<span class="number">1</span>]) ? <span class="string">&quot;false&quot;</span> : <span class="string">&quot;true&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">delete</span>[] nums;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    Solve();</span><br><span class="line"></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><p>使用C#提交至$Leetcode$的方式：</p><figure class="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">/*</span></span><br><span class="line"><span class="comment"> * @lc app=leetcode.cn id=810 lang=csharp</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * [810] 黑板异或游戏</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// @lc code=start</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">Solution</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="built_in">bool</span> <span class="title">CalcSum</span>(<span class="params"><span class="built_in">int</span>[] nums</span>)</span></span><br><span class="line"><span class="function"></span>    &#123;</span><br><span class="line">        <span class="keyword">for</span> (<span class="built_in">int</span> i = <span class="number">1</span>; i &lt; nums.Length; i++)</span><br><span class="line">            nums[<span class="number">0</span>] ^= nums[i];</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> (nums[<span class="number">0</span>] == <span class="number">0</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="built_in">bool</span> <span class="title">XorGame</span>(<span class="params"><span class="built_in">int</span>[] nums</span>)</span></span><br><span class="line"><span class="function"></span>    &#123;</span><br><span class="line">        <span class="keyword">return</span> (nums.Length % <span class="number">2</span> == <span class="number">0</span>) ? <span class="literal">true</span> : CalcSum(nums);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// @lc code=end</span></span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;LeetCode810-黑板异或游戏&quot;&gt;&lt;a href=&quot;#LeetCode810-黑板异或游戏&quot; class=&quot;headerlink&quot; title=&quot;LeetCode810. 黑板异或游戏&quot;&gt;&lt;/a&gt;LeetCode810. 黑板异或游戏&lt;/h1&gt;&lt;p&gt;&lt;a h</summary>
      
    
    
    
    <category term="算法" scheme="http://www.fcayh.cn/categories/%E7%AE%97%E6%B3%95/"/>
    
    
    <category term="数论-数学" scheme="http://www.fcayh.cn/tags/%E6%95%B0%E8%AE%BA-%E6%95%B0%E5%AD%A6/"/>
    
    <category term="Leetcode" scheme="http://www.fcayh.cn/tags/Leetcode/"/>
    
    <category term="巧妙思路" scheme="http://www.fcayh.cn/tags/%E5%B7%A7%E5%A6%99%E6%80%9D%E8%B7%AF/"/>
    
  </entry>
  
  <entry>
    <title>Codeforces Round#609(Div.2)</title>
    <link href="http://www.fcayh.cn/2021/04/03/cf-609div2/"/>
    <id>http://www.fcayh.cn/2021/04/03/cf-609div2/</id>
    <published>2021-04-02T23:26:31.000Z</published>
    <updated>2022-03-13T09:20:45.731Z</updated>
    
    <content type="html"><![CDATA[<h1 id="Codeforces-Round-609-Div-2"><a href="#Codeforces-Round-609-Div-2" class="headerlink" title="Codeforces Round #609 (Div. 2)"></a>Codeforces Round #609 (Div. 2)</h1><p><a href="https://codeforces.com/contest/1269">比赛链接</a></p><h2 id="A-Equation"><a href="#A-Equation" class="headerlink" title="A - Equation"></a>A - Equation</h2><p>签到题</p><p>题意是给你一个整数，让你找两个比它大的合数，并且它们的差是这个整数。</p><p>数据范围 $1\le n\le10^7$</p><p>你输出的两个整数$a,b$均要满足 $1 \le a,b\le10^9$</p><p>那么先预处理出质数来，然后暴力枚举。 当然，因为合数太多，你不预处理质数也无妨，每次用 $sqrt(i)$的复杂度判断也可。</p><figure class="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><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;bits/stdc++.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> <span class="built_in">std</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="keyword">int</span> Maxn = <span class="number">20000000</span>;</span><br><span class="line"><span class="keyword">const</span> <span class="keyword">int</span> MaxE = <span class="number">11000000</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">bool</span> Not_prime[Maxn + <span class="number">10</span>];</span><br><span class="line"><span class="keyword">int</span> prime[Maxn + <span class="number">10</span>];</span><br><span class="line"><span class="keyword">int</span> n, len = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Linear_sieve_prime</span><span class="params">()</span> <span class="comment">//线性筛素数</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    Not_prime[<span class="number">1</span>] = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">2</span>; i &lt;= MaxE; i++)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">if</span> (Not_prime[i] == <span class="number">0</span>)</span><br><span class="line">            prime[++len] = i;</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">int</span> j = <span class="number">1</span>; j &lt;= len; j++)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">if</span> (prime[j] * i &gt; MaxE)</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            Not_prime[i * prime[j]] = <span class="number">1</span>;</span><br><span class="line">            <span class="keyword">if</span> (!(i % prime[j]))</span><br><span class="line">                <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><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Solve</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    Linear_sieve_prime();</span><br><span class="line"></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="keyword">for</span> (<span class="keyword">int</span> i = n + <span class="number">1</span>; i &lt;= Maxn; i++)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="comment">//printf(&quot;%d %d\n&quot;,Not_prime[9],Not_prime[8]);</span></span><br><span class="line">        <span class="keyword">if</span> (Not_prime[i - n] &amp;&amp; Not_prime[i]) <span class="comment">//暴力枚举</span></span><br><span class="line">        &#123;</span><br><span class="line">            <span class="built_in">printf</span>(<span class="string">&quot;%d %d\n&quot;</span>, i, i - n);</span><br><span class="line">            <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><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    Solve();</span><br><span class="line"></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><h2 id="B-Modulo-Equality"><a href="#B-Modulo-Equality" class="headerlink" title="B - Modulo Equality"></a>B - Modulo Equality</h2><p>给你一个初始集合，和一个目标集合，你要给初始集合中的每一个数加$x$，再让初始集合中的数都模$m$，让初始集合和目标集合相等。求最小的$x$。</p><p>$1≤n≤2000,1≤m≤109$</p><p>也挺简单的，你想想，假设我们不模$m$，那么是不是初始集合中小的对应目标集合中的小的，初始集合中的大的对应目标集合中的大的，因为同时加一个相同的数嘛，大小关系不变。</p><p>其实模$m$之后还是一样的做法，举个例子，初始集合是$\{1,2,3\}$ 目标集合是$\{0,1,2\}$ ，模$m=4$。它加了$x$之后，要么是$1\rightarrow0,2\rightarrow1,3\rightarrow2$ ,要么是$1\rightarrow1,2\rightarrow2,3\rightarrow0$，要么是$1\rightarrow2,2\rightarrow0,3\rightarrow1$，你看它都是顺着来的，他就不可能$1\rightarrow2,3\rightarrow0$去，我这都是排好序的了，所以我们只需要从上面三种情况中找一个$x$最小的就好啦</p><p>那么详细过程就是，先分别排序，然后把第一个集合延长一倍，类似于破环为链，然后每种情况只需要看一下第一个数对应过去$x$是多少就好啦。</p><p>再举个例子：</p><div class="table-container"><table><thead><tr><th>0</th><th>0</th><th>1</th><th>2</th><th>0</th><th>0</th><th>1</th></tr></thead><tbody><tr><td>0</td><td>0</td><td>1</td><td>2</td><td></td><td></td><td></td></tr><tr><td></td><td>0</td><td>0</td><td>1</td><td>2</td><td></td><td></td></tr><tr><td></td><td></td><td>0</td><td>0</td><td>1</td><td>2</td><td></td></tr><tr><td></td><td></td><td></td><td>0</td><td>0</td><td>1</td><td>2</td></tr></tbody></table></div><p>就这个样子对应就好啦</p><figure class="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><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;bits/stdc++.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> <span class="built_in">std</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="keyword">int</span> Maxn = <span class="number">6000</span>;</span><br><span class="line"><span class="keyword">const</span> <span class="keyword">int</span> Inf = <span class="number">0x3f3f3f3f</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">int</span> n, m;</span><br><span class="line"><span class="keyword">int</span> A[Maxn], B[Maxn];</span><br><span class="line"><span class="keyword">int</span> ans = Inf;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">inline</span> <span class="keyword">int</span> <span class="title">cmp</span><span class="params">(<span class="keyword">int</span> a, <span class="keyword">int</span> b)</span> </span>&#123; <span class="keyword">return</span> a &lt; b; &#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Solve</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">scanf</span>(<span class="string">&quot;%d%d&quot;</span>, &amp;n, &amp;m);</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">1</span>; i &lt;= n; i++)</span><br><span class="line">        <span class="built_in">scanf</span>(<span class="string">&quot;%d&quot;</span>, &amp;A[i]);</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">1</span>; i &lt;= n; i++)</span><br><span class="line">        <span class="built_in">scanf</span>(<span class="string">&quot;%d&quot;</span>, &amp;B[i]);</span><br><span class="line"></span><br><span class="line">    sort(A + <span class="number">1</span>, A + <span class="number">1</span> + n, cmp);</span><br><span class="line">    sort(B + <span class="number">1</span>, B + <span class="number">1</span> + n, cmp);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = n + <span class="number">1</span>; i &lt;= <span class="number">2</span> * n; i++)</span><br><span class="line">        A[i] = A[i - n];</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">1</span>; i &lt;= n; i++)</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">1</span>; i &lt;= n; i++)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">int</span> Add = <span class="number">0</span>, flag = <span class="number">0</span>;</span><br><span class="line">            <span class="keyword">if</span> (B[<span class="number">1</span>] &gt;= A[i])</span><br><span class="line">                Add = B[<span class="number">1</span>] - A[i];</span><br><span class="line">            <span class="keyword">else</span></span><br><span class="line">                Add = m - A[i] + B[<span class="number">1</span>];</span><br><span class="line">            <span class="comment">//printf(&quot;&lt;%d %d&gt;\n&quot;,i,Add);</span></span><br><span class="line"></span><br><span class="line">            <span class="keyword">for</span> (<span class="keyword">int</span> j = i + <span class="number">1</span>; j &lt;= n + i - <span class="number">1</span>; j++)</span><br><span class="line">            &#123;</span><br><span class="line">                <span class="keyword">if</span> ((A[j] + Add) % m != B[j - i + <span class="number">1</span>])</span><br><span class="line">                &#123;</span><br><span class="line">                    flag = <span class="number">1</span>;</span><br><span class="line">                    <span class="keyword">break</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span> (!flag)</span><br><span class="line">                ans = min(ans, Add);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;%d\n&quot;</span>, ans);</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    Solve();</span><br><span class="line"></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><h2 id="C-Long-Beautiful-Integer"><a href="#C-Long-Beautiful-Integer" class="headerlink" title="C - Long Beautiful Integer"></a>C - Long Beautiful Integer</h2><p>这个题先定义了一个$beautiful$数，啥是$beautiful$数啊，就是给你一个$k$，这个数如果满足第$i$位和第$i+k$位相同，那它就是$beautiful$数。</p><p>现在给你一个$n$位的数，和$k$，问比这个数大的最小的$beautiful$数是啥。</p><p>$2 \le n \le 200000,1 \le k\le n$</p><p>你看这个数据范围，那肯定得构造一个数出来啊，首先我们得知道，这个数字啊，他有一个性质，假设$a$的第i位比$b$的第i位大，然后他俩前$i-1$位都一样，那甭管后面是多少了，反正$a$比$b$大就对了。</p><p>所以我们只要把前$k$位构造好，就$ok$了</p><p>前$k$位咋构造？为了保证是比原数大的最小的数，那么能和原数一样咱就让他们一样,用$s$数组存那个$n$位数，咱再搞个$A$数组，前$k$位和$s$一样，然后后面的就按照$A[i]=A[i-k]$的方式赋上初始值，然后咱比较一下$A$和$s$谁大，要是$A$大，那咱就求完了，要是$s$大，那咱就给$A$的第k位加$1$，这就提到我前面说的那个数的性质，你这里加个$1$，肯定比$s$大了，当然要注意，可能要进位，这个考虑到就可以了。</p><figure class="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><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;bits/stdc++.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> <span class="built_in">std</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="keyword">int</span> Maxn = <span class="number">3000000</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">int</span> n, k;</span><br><span class="line"><span class="keyword">char</span> s[Maxn];</span><br><span class="line"><span class="keyword">char</span> A[Maxn];</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Solve</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">scanf</span>(<span class="string">&quot;%d%d&quot;</span>, &amp;n, &amp;k);</span><br><span class="line">    <span class="built_in">scanf</span>(<span class="string">&quot;%s&quot;</span>, s + <span class="number">1</span>);</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">1</span>; i &lt;= k; i++)</span><br><span class="line">        A[i] = s[i];</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = k + <span class="number">1</span>; i &lt;= n; i++)</span><br><span class="line">        A[i] = A[i - k];</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (<span class="built_in">strcmp</span>(s + <span class="number">1</span>, A + <span class="number">1</span>) &gt; <span class="number">0</span>)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">int</span> p = k;</span><br><span class="line">        <span class="keyword">if</span> (A[p] &lt;= <span class="string">&#x27;8&#x27;</span>)</span><br><span class="line">            A[p]++;</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (A[p] == <span class="string">&#x27;9&#x27;</span>)</span><br><span class="line">        &#123;</span><br><span class="line">            A[p] = <span class="string">&#x27;0&#x27;</span>;</span><br><span class="line">            <span class="keyword">for</span> (<span class="keyword">int</span> i = p - <span class="number">1</span>; i &gt;= <span class="number">1</span>; i--)</span><br><span class="line">            &#123;</span><br><span class="line">                <span class="keyword">if</span> (A[i] &lt;= <span class="string">&#x27;8&#x27;</span>)</span><br><span class="line">                &#123;</span><br><span class="line">                    A[i]++;</span><br><span class="line">                    <span class="keyword">break</span>;</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">else</span></span><br><span class="line">                    A[i] = <span class="string">&#x27;0&#x27;</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">int</span> i = k + <span class="number">1</span>; i &lt;= n; i++)</span><br><span class="line">            A[i] = A[i - k];</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">cout</span> &lt;&lt; <span class="built_in">strlen</span>(A + <span class="number">1</span>) &lt;&lt; <span class="built_in">endl</span></span><br><span class="line">         &lt;&lt; (A + <span class="number">1</span>) &lt;&lt; <span class="built_in">endl</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    Solve();</span><br><span class="line"></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><h2 id="D-Domino-for-Young"><a href="#D-Domino-for-Young" class="headerlink" title="D - Domino for Young"></a>D - Domino for Young</h2><p>这个题挺巧妙的啊，给你一个底是平的，高大于等于$1$的棋盘，让你放$1×2$或者$2×1$的多米诺骨牌，问你最多可以放几个？</p><p>就这个样子的：</p><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images@7d859b5e0218e00a996cca9408629e5d2123ab85/2021/04/03/9a2c36c5229dfcd418f75bc1ce667eec.png"  /></p><p>做法特别简单，两步，第一步：涂色，第二步：数，做完了。</p><p>涂色：</p><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images@d0d6738f30cd070bb3cf443051913858b5ae5a62/2021/04/03/3b0a4b8b06b0357a600d2cb4e9b6be30.png"  /></p><p>注意看啊，黑白相间的，就国际象棋棋盘啥样你就照着那个涂。</p><p>数：</p><p>数数黑的多还是白的多，哪个少输出哪个。</p><p>解释：甭管你是$1×2$还是$2×1$的多米诺骨牌，你只要躺棋盘上，都得是正好的一个黑格子一个白格子，所以这样肯定可以把其中一个颜色的格子都盖满啊，是吧。</p><figure class="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><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;bits/stdc++.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> <span class="built_in">std</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">typedef</span> <span class="keyword">long</span> <span class="keyword">long</span> ll;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">inline</span> <span class="keyword">int</span> <span class="title">read</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> fl = <span class="number">1</span>, rt = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">char</span> ch = getchar();</span><br><span class="line">    <span class="keyword">while</span> (ch &lt; <span class="string">&#x27;0&#x27;</span> || ch &gt; <span class="string">&#x27;9&#x27;</span>)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">if</span> (ch == <span class="string">&#x27;-&#x27;</span>)</span><br><span class="line">            fl = <span class="number">-1</span>;</span><br><span class="line">        ch = getchar();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">while</span> (ch &gt;= <span class="string">&#x27;0&#x27;</span> &amp;&amp; ch &lt;= <span class="string">&#x27;9&#x27;</span>)</span><br><span class="line">    &#123;</span><br><span class="line">        rt = rt * <span class="number">10</span> + ch - <span class="string">&#x27;0&#x27;</span>;</span><br><span class="line">        ch = getchar();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> fl * rt;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Solve</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    ll w = <span class="number">0l</span>l, b = <span class="number">0l</span>l;</span><br><span class="line">    <span class="keyword">int</span> n, a, si = <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line">    n = read();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">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">        a = read();</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (si)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">if</span> (a % <span class="number">2</span>)</span><br><span class="line">                w += (ll)(a / <span class="number">2</span> + <span class="number">1</span>), b += (ll)(a / <span class="number">2</span>);</span><br><span class="line">            <span class="keyword">else</span></span><br><span class="line">                w += (ll)(a / <span class="number">2</span>), b += (ll)(a / <span class="number">2</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">else</span></span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">if</span> (!(a % <span class="number">2</span>))</span><br><span class="line">                w += (ll)(a / <span class="number">2</span>), b += (ll)(a / <span class="number">2</span>);</span><br><span class="line">            <span class="keyword">else</span></span><br><span class="line">                w += (ll)(a / <span class="number">2</span>), b += (ll)(a / <span class="number">2</span> + <span class="number">1</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        si ^= <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;%lld\n&quot;</span>, min(w, b));</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    Solve();</span><br><span class="line"></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><h2 id="E-K-Integers"><a href="#E-K-Integers" class="headerlink" title="E - K Integers"></a>E - K Integers</h2><p>这个题就是给你一个n的排列，每次你可以交换两个数，让你把$f(1),f(2),f(3)…f(n)$输出出来，$f(i)$代表通过两两交换弄出$1234…i$需要的最小交换次数。</p><p>$1\le n \le 10^5$</p><p>这个题还是有点难度的。</p><p>假设它不让你求那么多，他就只让你求$f(n)$，那这个题就贼水了，直接归并排序或者树状数组求个逆序对就行了。</p><p>但其实他让你求$f(1) f(2)… f(n)$，也是用一样的方法，逆序对就用树状数组啦，关键要处理的就是不连续的情况。</p><p>你看看：</p><p>$1XXXX2X3X4$</p><p>$X$代表其他数，这里$1234$已经是顺序了，我们主要研究$X$如何处理，对于一个$X$我们要么把他搞到最左边，要么把他搞到最右边，对吧，他要交换几次才可以到最左或者最右，要看他左边有几个要被换过来的数呗，那么为了交换次数最少，肯定是把数聚集到中位数左右，也就是变成：</p><p>$XXXX1234XX$</p><p>这样我们一共交换了几次？$7$次。</p><p>看一下怎么算的：</p><p>$1\,XX\,2\,X\,3\,XX\,4$</p><p>如果我们要把$123$都移到$4$的紧左边。<br>实际上答案就是$\sum_{}($目标位置$-$初始位置$)$。</p><p>因为最后要变成 $XXXXX1\,2\,3\,4\,$</p><p>这样$1$移动了$5$位，$2$移动了$3$位，$3$移动了$2$位，一共$10$步</p><p>为了好算，我们可以表示成$1,2,3$均移动到$4$上，再$-1-2-3$，这样我们的算式就可以表示成：</p><p>$(9\times4-1-4-6)-1-2-3$</p><p>那么问题来了，这个$9\times4$用求逆序对的树状数组就可求出来，$-1-2-3$也不过是等差数列求和罢了，那个$-1-4-6$咋办？</p><p>答案是再开一个树状数组，每次把位置插入进去不就完了。$OK$</p><p>这是把左边的靠过来的计算方法，右边同理就好了。</p><figure class="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><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;bits/stdc++.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> <span class="built_in">std</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">typedef</span> <span class="keyword">long</span> <span class="keyword">long</span> ll;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="keyword">int</span> MaxT = <span class="number">800000</span>;</span><br><span class="line"><span class="keyword">const</span> <span class="keyword">int</span> Maxn = <span class="number">201000</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">int</span> n;</span><br><span class="line"><span class="keyword">int</span> Pos[Maxn];</span><br><span class="line">ll T1[MaxT + <span class="number">10</span>];</span><br><span class="line">ll T2[MaxT + <span class="number">10</span>];</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">read</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> fl = <span class="number">1</span>, rt = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">char</span> ch = getchar();</span><br><span class="line">    <span class="keyword">while</span> (ch &lt; <span class="string">&#x27;0&#x27;</span> || ch &gt; <span class="string">&#x27;9&#x27;</span>)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">if</span> (ch == <span class="string">&#x27;-&#x27;</span>)</span><br><span class="line">            fl = <span class="number">-1</span>;</span><br><span class="line">        ch = getchar();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">while</span> (ch &gt;= <span class="string">&#x27;0&#x27;</span> &amp;&amp; ch &lt;= <span class="string">&#x27;9&#x27;</span>)</span><br><span class="line">    &#123;</span><br><span class="line">        rt = rt * <span class="number">10</span> + ch - <span class="string">&#x27;0&#x27;</span>;</span><br><span class="line">        ch = getchar();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> fl * rt;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Insert</span><span class="params">(ll *T, <span class="keyword">int</span> x, ll y)</span> <span class="comment">//树状数组的插入操作</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">while</span> (x &lt;= MaxT)</span><br><span class="line">        T[x] += y, x += x &amp; (-x);</span><br><span class="line">&#125;</span><br><span class="line"><span class="function">ll <span class="title">Search</span><span class="params">(ll *T, <span class="keyword">int</span> x)</span> <span class="comment">//查询操作</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    ll ret = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">while</span> (x &gt; <span class="number">0</span>)</span><br><span class="line">        ret += T[x], x -= x &amp; (-x);</span><br><span class="line">    <span class="keyword">return</span> ret;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Solve</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    n = read();</span><br><span class="line">    <span class="keyword">int</span> a;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">1</span>; i &lt;= n; i++)</span><br><span class="line">        a = read(), Pos[a] = i;</span><br><span class="line"></span><br><span class="line">    ll ans1 = <span class="number">0l</span>l, ans2 = <span class="number">0l</span>l;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">1</span>; i &lt;= n; i++)</span><br><span class="line">    &#123;</span><br><span class="line">        ans1 += i - <span class="number">1</span> - Search(T1, Pos[i]); <span class="comment">//求逆序对</span></span><br><span class="line"></span><br><span class="line">        Insert(T1, Pos[i], <span class="number">1l</span>l);</span><br><span class="line">        Insert(T2, Pos[i], (ll)Pos[i]);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">int</span> l = <span class="number">1</span>, r = n, mid = (l + r) &gt;&gt; <span class="number">1</span>; <span class="comment">//求中点</span></span><br><span class="line">        <span class="keyword">while</span> (l &lt;= r)</span><br><span class="line">        &#123;</span><br><span class="line">            mid = (l + r) &gt;&gt; <span class="number">1</span>;</span><br><span class="line">            <span class="keyword">if</span> ((Search(T1, mid) &lt;&lt; <span class="number">1</span>) &lt;= i)</span><br><span class="line">                l = mid + <span class="number">1</span>;</span><br><span class="line">            <span class="keyword">else</span></span><br><span class="line">                r = mid - <span class="number">1</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        ans2 = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">        <span class="comment">//左边</span></span><br><span class="line">        ll cnt = (ll)Search(T1, mid), sum = (ll)Search(T2, mid);</span><br><span class="line">        ans2 += mid * cnt - sum - cnt * (cnt - <span class="number">1l</span>l) / <span class="number">2l</span>l;</span><br><span class="line"></span><br><span class="line">        <span class="comment">//右边</span></span><br><span class="line">        cnt = i - cnt, sum = Search(T2, n) - sum;</span><br><span class="line">        ans2 += sum - cnt * (ll)(mid + <span class="number">1l</span>l) - cnt * (cnt - <span class="number">1l</span>l) / <span class="number">2l</span>l;</span><br><span class="line"></span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot;%lld &quot;</span>, ans1 + ans2);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    Solve();</span><br><span class="line"></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>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;Codeforces-Round-609-Div-2&quot;&gt;&lt;a href=&quot;#Codeforces-Round-609-Div-2&quot; class=&quot;headerlink&quot; title=&quot;Codeforces Round #609 (Div. 2)&quot;&gt;&lt;/a&gt;Code</summary>
      
    
    
    
    <category term="算法" scheme="http://www.fcayh.cn/categories/%E7%AE%97%E6%B3%95/"/>
    
    
    <category term="codeforces" scheme="http://www.fcayh.cn/tags/codeforces/"/>
    
    <category term="枚举" scheme="http://www.fcayh.cn/tags/%E6%9E%9A%E4%B8%BE/"/>
    
    <category term="构造" scheme="http://www.fcayh.cn/tags/%E6%9E%84%E9%80%A0/"/>
    
    <category term="数论-数学" scheme="http://www.fcayh.cn/tags/%E6%95%B0%E8%AE%BA-%E6%95%B0%E5%AD%A6/"/>
    
    <category term="树状数组" scheme="http://www.fcayh.cn/tags/%E6%A0%91%E7%8A%B6%E6%95%B0%E7%BB%84/"/>
    
  </entry>
  
  <entry>
    <title>2020蓝桥杯A组省赛第二场</title>
    <link href="http://www.fcayh.cn/2021/02/09/11lanqiao/"/>
    <id>http://www.fcayh.cn/2021/02/09/11lanqiao/</id>
    <published>2021-02-09T02:06:51.000Z</published>
    <updated>2023-02-25T15:38:09.263Z</updated>
    
    <content type="html"><![CDATA[<h1 id="2020蓝桥杯A组省赛第二场"><a href="#2020蓝桥杯A组省赛第二场" class="headerlink" title="2020蓝桥杯A组省赛第二场"></a>2020蓝桥杯A组省赛第二场</h1><h2 id="试题A-门牌制作"><a href="#试题A-门牌制作" class="headerlink" title="试题A 门牌制作"></a>试题A 门牌制作</h2><p>本题总分：$5$ 分</p><h3 id="【问题描述】"><a href="#【问题描述】" class="headerlink" title="【问题描述】"></a>【问题描述】</h3><p>小蓝要为一条街的住户制作门牌号。这条街一共有$2020$位住户，门牌号从$1$到$2020$编号。小蓝制作门牌的方法是先制作$0$到$9$这几个数字字符，最后根据需要将字符粘贴到门牌上，例如门牌$1017$需要依次粘贴字符$1、0、1、7$，即需要$1$个字符$0$，$2$个字符$1$，$1$个字符$7$。请问要制作所有的$1$到$2020$号门牌，总共需要多少个字符2？</p><h3 id="【答案提交】"><a href="#【答案提交】" class="headerlink" title="【答案提交】"></a>【答案提交】</h3><p>这是一道结果填空的题，你只需要算出结果后提交即可。本题的结果为一个整数，在提交答案时只填写这个整数，填写多余的内容将无法得分。</p><h3 id="【解题思路】"><a href="#【解题思路】" class="headerlink" title="【解题思路】"></a>【解题思路】</h3><p>暴力枚举。</p><p>利用$for$循环，从$1$到$2020$。并利用一个变量$count$来记录数字$2$出现了几次，最后输出即可。</p><figure class="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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;bits/stdc++.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> <span class="built_in">std</span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">calc_2</span><span class="params">(<span class="keyword">int</span> x)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> ret = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">while</span> (x)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">if</span> (x % <span class="number">10</span> == <span class="number">2</span>)</span><br><span class="line">            ret++;</span><br><span class="line">        x /= <span class="number">10</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> ret;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Solve</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> count = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">1</span>; i &lt;= <span class="number">2020</span>; i++)</span><br><span class="line">        count += calc_2(i);</span><br><span class="line"></span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;%d\n&quot;</span>, count);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    Solve();</span><br><span class="line"></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><p>运行输出答案为 $624$。</p><h2 id="试题B-既约分数"><a href="#试题B-既约分数" class="headerlink" title="试题B: 既约分数"></a>试题B: 既约分数</h2><p>本题总分：$5$ 分</p><h3 id="【问题描述】-1"><a href="#【问题描述】-1" class="headerlink" title="【问题描述】"></a>【问题描述】</h3><p>如果一个分数的分子和分母的最大公约数是$1$，这个分数称为既约分数。例如，$\dfrac{3}{4}$, $\dfrac{5}{2}$ , $\dfrac{1}{8}$ , $\dfrac{7}{1}$都是既约分数。请问，有多少个既约分数，分子和分母都是$1$到$2020$之间的整数（包括$1$和$2020$）？</p><h3 id="【答案提交】-1"><a href="#【答案提交】-1" class="headerlink" title="【答案提交】"></a>【答案提交】</h3><p>这是一道结果填空的题，你只需要算出结果后提交即可。本题的结果为一个整数，在提交答案时只填写这个整数，填写多余的内容将无法得分。</p><h3 id="【解题思路】-1"><a href="#【解题思路】-1" class="headerlink" title="【解题思路】"></a>【解题思路】</h3><p>还是暴力枚举。</p><p>分子分母均从$1$到$2020$枚举，用变量$count$记录答案，如果分子分母的最大公约数 $(\ gcd\ )$ 为$1$，则$count++$。</p><figure class="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="meta-keyword">include</span> <span class="meta-string">&lt;bits/stdc++.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> <span class="built_in">std</span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">gcd</span><span class="params">(<span class="keyword">int</span> x, <span class="keyword">int</span> y)</span> </span>&#123; <span class="keyword">return</span> !y ? x : gcd(y, x % y); &#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Solve</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> count = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">1</span>; i &lt;= <span class="number">2020</span>; i++)</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">int</span> j = <span class="number">1</span>; j &lt;= <span class="number">2020</span>; j++)</span><br><span class="line">            <span class="keyword">if</span> (gcd(i, j) == <span class="number">1</span>)</span><br><span class="line">                count++;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;%d\n&quot;</span>, count);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    Solve();</span><br><span class="line"></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><p>运行输出答案为 $2481215$。</p><h2 id="试题C-蛇形填数"><a href="#试题C-蛇形填数" class="headerlink" title="试题C: 蛇形填数"></a>试题C: 蛇形填数</h2><p>本题总分：$10$ 分</p><h3 id="【问题描述】-2"><a href="#【问题描述】-2" class="headerlink" title="【问题描述】"></a>【问题描述】</h3><p>如下图所示，小明用从$1$开始的正整数“蛇形”填充无限大的矩阵。</p><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images@546080696e2c5de5d0d4f95fb2ad139ada309392/2021/02/20/1365801eb24825a1967f76c1a3b72303.png"  /></p><p>容易看出矩阵第二行第二列中的数是$5$。请你计算矩阵中第$20$行第$20$列的数是多少？</p><h3 id="【答案提交】-2"><a href="#【答案提交】-2" class="headerlink" title="【答案提交】"></a>【答案提交】</h3><p>这是一道结果填空的题，你只需要算出结果后提交即可。本题的结果为一个整数，在提交答案时只填写这个整数，填写多余的内容将无法得分。</p><h3 id="【解题思路】-2"><a href="#【解题思路】-2" class="headerlink" title="【解题思路】"></a>【解题思路】</h3><p>模拟。</p><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images@19b3307f0721e42c0054291b63910dad4c4b2514/2021/02/20/45fb2baa3ef58e750c8d78d8051a4a53.png" /></p><p>按红线记录层数，第$20$行$20$列的数应当在第$39$层。奇数层数字的增长方向为右上，偶数层数字增长方向为右下。</p><p>首先算出前38层共有多少数，再加上第39层的前半部分数，即可算出第20行20列的数。</p><script type="math/tex; mode=display">\dfrac{38\times(38+1)}{2}+\left\lfloor\dfrac{39}{2}\right\rfloor = 761</script><h2 id="试题D-七段码"><a href="#试题D-七段码" class="headerlink" title="试题D: 七段码"></a>试题D: 七段码</h2><p>本题总分：$10$ 分</p><h3 id="【问题描述】-3"><a href="#【问题描述】-3" class="headerlink" title="【问题描述】"></a>【问题描述】</h3><p>小蓝要用七段码数码管来表示一种特殊的文字。</p><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images@7b906396c948d7b10b840bd7e4d87452a0b616ce/2021/02/20/0e51b1a816c6207d229d21434bb92db1.png"  /></p><p>上图给出了七段码数码管的一个图示，数码管中一共有$7$ 段可以发光的二极管，分别标记为$a, b, c, d, e, f, g$。小蓝要选择一部分二极管（至少要有一个）发光来表达字符。在设计字符的表达时，要求所有发光的二极管是连成一片的。<br>例如：$b$ 发光，其他二极管不发光可以用来表达一种字符。<br>例如：$c$ 发光，其他二极管不发光可以用来表达一种字符。这种方案与上一行的方案可以用来表示不同的字符，尽管看上去比较相似。<br>例如：$a, b, c, d, e$ 发光，$f, g$ 不发光可以用来表达一种字符。<br>例如：$b, f$ 发光，其他二极管不发光则不能用来表达一种字符，因为发光的二极管没有连成一片。<br>请问，小蓝可以用七段码数码管表达多少种不同的字符？</p><h3 id="【答案提交】-3"><a href="#【答案提交】-3" class="headerlink" title="【答案提交】"></a>【答案提交】</h3><p>这是一道结果填空的题，你只需要算出结果后提交即可。本题的结果为一个整数，在提交答案时只填写这个整数，填写多余的内容将无法得分。</p><h3 id="【解题思路】-3"><a href="#【解题思路】-3" class="headerlink" title="【解题思路】"></a>【解题思路】</h3><p>搜索。</p><p>此题可以先用排列组合算一下所有的选法一共$\sum\limits_{i=0}^7C^i_7=2^7=128$种。</p><p>所以也可以把所有情况都列举出来，然后手动去判断。</p><p>如果用代码判断，则可以将$\\ a\\sim g\\ $ 编号为$1\sim7$。并建立如下图。</p><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images@5841674e895fbb9e2eb7d39203f34a496d2f2488/2021/02/20/29b07e20607e705dc00dc4fe4ca8f99f.png" /></p><p>使用变量$count$记录答案，通过枚举边的编号，通过$dfs$判断在仅使用枚举到的边的情况下，图中存在几个连通块，如果仅有一个，则$count++$。</p><figure class="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><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;bits/stdc++.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> <span class="built_in">std</span>;</span><br><span class="line"></span><br><span class="line"><span class="built_in">vector</span>&lt;<span class="keyword">int</span>&gt; Map[<span class="number">10</span>];</span><br><span class="line"></span><br><span class="line"><span class="keyword">int</span> cnt = <span class="number">0</span>;</span><br><span class="line"><span class="keyword">int</span> vis[<span class="number">10</span>];</span><br><span class="line"><span class="keyword">int</span> uesd[<span class="number">10</span>];</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">DFS</span><span class="params">(<span class="keyword">int</span> u)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    uesd[u] = <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; Map[u].size(); i++)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">int</span> v = Map[u][i];</span><br><span class="line">        <span class="keyword">if</span> (uesd[v] || !vis[v])</span><br><span class="line">            <span class="keyword">continue</span>;</span><br><span class="line">        DFS(v);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">check</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">vector</span>&lt;<span class="keyword">int</span>&gt; p;</span><br><span class="line">    p.clear();</span><br><span class="line">    <span class="built_in">memset</span>(uesd, <span class="number">0</span>, <span class="keyword">sizeof</span>(uesd));</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">1</span>; i &lt;= <span class="number">7</span>; i++)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">if</span> (vis[i])</span><br><span class="line">            p.push_back(i);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">int</span> tmp = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; p.size(); i++)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">if</span> (!uesd[p[i]])</span><br><span class="line">        &#123;</span><br><span class="line">            DFS(p[i]);</span><br><span class="line">            tmp++;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (tmp == <span class="number">1</span>)</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><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">dfs</span><span class="params">(<span class="keyword">int</span> num)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (num == <span class="number">8</span>)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">if</span> (check())</span><br><span class="line">            cnt++;</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    vis[num] = <span class="number">0</span>;</span><br><span class="line">    dfs(num + <span class="number">1</span>);</span><br><span class="line">    vis[num] = <span class="number">1</span>;</span><br><span class="line">    dfs(num + <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="keyword">void</span> <span class="title">BuildMap</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    Map[<span class="number">1</span>].push_back(<span class="number">2</span>), Map[<span class="number">1</span>].push_back(<span class="number">6</span>);</span><br><span class="line">    Map[<span class="number">2</span>].push_back(<span class="number">1</span>), Map[<span class="number">2</span>].push_back(<span class="number">3</span>), Map[<span class="number">2</span>].push_back(<span class="number">7</span>);</span><br><span class="line">    Map[<span class="number">3</span>].push_back(<span class="number">2</span>), Map[<span class="number">3</span>].push_back(<span class="number">4</span>), Map[<span class="number">3</span>].push_back(<span class="number">7</span>);</span><br><span class="line">    Map[<span class="number">4</span>].push_back(<span class="number">3</span>), Map[<span class="number">4</span>].push_back(<span class="number">5</span>);</span><br><span class="line">    Map[<span class="number">5</span>].push_back(<span class="number">4</span>), Map[<span class="number">5</span>].push_back(<span class="number">6</span>), Map[<span class="number">5</span>].push_back(<span class="number">7</span>);</span><br><span class="line">    Map[<span class="number">6</span>].push_back(<span class="number">1</span>), Map[<span class="number">6</span>].push_back(<span class="number">5</span>), Map[<span class="number">6</span>].push_back(<span class="number">7</span>);</span><br><span class="line">    Map[<span class="number">7</span>].push_back(<span class="number">2</span>), Map[<span class="number">7</span>].push_back(<span class="number">3</span>), Map[<span class="number">7</span>].push_back(<span class="number">5</span>), Map[<span class="number">7</span>].push_back(<span class="number">6</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Solve</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    BuildMap();</span><br><span class="line"></span><br><span class="line">    dfs(<span class="number">1</span>);</span><br><span class="line"></span><br><span class="line">    <span class="built_in">cout</span> &lt;&lt; cnt;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    Solve();</span><br><span class="line"></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><p>运行输出答案为$80$。</p><h2 id="试题E：平面分割"><a href="#试题E：平面分割" class="headerlink" title="试题E：平面分割"></a>试题E：平面分割</h2><p>本题总分：$15$ 分</p><h3 id="【问题描述】-4"><a href="#【问题描述】-4" class="headerlink" title="【问题描述】"></a>【问题描述】</h3><p>$20$ 个圆和$20$ 条直线最多能把平面分成多少个部分？</p><h3 id="【答案提交】-4"><a href="#【答案提交】-4" class="headerlink" title="【答案提交】"></a>【答案提交】</h3><p>这是一道结果填空的题，你只需要算出结果后提交即可。本题的结果为一个整数，在提交答案时只填写这个整数，填写多余的内容将无法得分。</p><h3 id="【解题思路】（转载）"><a href="#【解题思路】（转载）" class="headerlink" title="【解题思路】（转载）"></a>【解题思路】（转载）</h3><blockquote><p>作者：$Bluevarpi$<br>链接：<a href="https://www.zhihu.com/question/426034179/answer/1529833661">https://www.zhihu.com/question/426034179/answer/1529833661</a><br>来源：知乎<br>著作权归作者所有。</p></blockquote><p>我们将问题推广到更一般的情况：</p><p>设$m$个圆和$n$条直线做多能把平面分成$f(m,n)$个部分。</p><p>我们首先考虑$m$个圆的情况：</p><p>$2$个圆最多有$2$个交点，则$m$个圆最多有$2 \cdot \dbinom{m}{2}=m(m-1) $个交点。</p><p>每个圆都与其它$m-1$个圆各交于$2$个点，所以每个圆上都有$2(m-1)$个交点，则每个圆都被分割成了$2(m-1)$个小段，因此$m$个圆有$2m(m-1)$个小段</p><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images@22de755eff39e32360adbd648e042e6756ba81e8/2021/02/20/1e1f8b2f0cbbce6e758d1561d9024788.png" alt="m个圆两两相交"  /></p><h4 id="法一："><a href="#法一：" class="headerlink" title="法一："></a><strong>法一</strong>：</h4><p>每增加一个圆弧小段就增加$2$个区域，因此第$m$个圆使平面增加了$2(m-1)$个区域。</p><p>则$f(m,0)=f(m-1,0)+2(m-1)$</p><p>等号两边对$2$到$m$进行求和，即</p><p>$\sum\limits_{k=2}^m f(k,0)=\sum\limits_{k=2}^m[f(k-1,0)+2(k-1)]$</p><p>注意到</p><p>$\sum\limits_{k=2}^mf(k,0)=\sum\limits_{k=2}^mf(k-1,0)+f(k,0)-f(1,0)$</p><p>则有</p><p>$f(m,0)=\sum\limits_{k=2}^m2(k-1)+f(1,0)$</p><p>显然，$1$个圆和$0$条直线将平面分成$2$个部分，即$f(1,0)=2$</p><p>$\therefore f(m,0)=m^2-m+2$</p><h4 id="法二：根据平面图的欧拉公式-Euler’s-formula-1"><a href="#法二：根据平面图的欧拉公式-Euler’s-formula-1" class="headerlink" title="法二：根据平面图的欧拉公式(Euler’s formula)[^1]"></a>法二：根据平面图的欧拉公式(Euler’s formula)[^1]</h4><p>$V-E+F=2$ (包括外边的平面)</p><p>$\therefore f(m,0)=F=2-V+E=2-m(m-1)+2m(m-1)=m^2-m+2$</p><p>于是我们就得到了$m$个圆最多将平面分割为$m^2-m+2$个部分</p><p>下面加入$n$条直线的情况</p><p>不难发现，第$n$条直线与其它$n-1$条直线最多交于$n-1$个点，与$m$个圆最多交于$2m$个点，如下图所示：</p><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images@797a741e7df6b982064b68c91aecf7a3f42eb980/2021/02/20/b30a943a3cf5fe76c4fd6a4e8846616b.png"  /></p><p>因此第$n$条直线与原来的图形最多交于$2m+(n-1)=2m+n-1$个点，则此时比$n-1$条直线的情况增加了$2m+n$个区域，</p><p>即$f(m,n)=f(m,n-1)+2m+n$</p><p>等号两边对$2$到$n$求和</p><p>$\sum\limits_{k=2}^nf(m,k)=\sum\limits_{k=2}^n[f(m,k-1)+2m+k]$</p><p>注意到</p><p>$\sum\limits_{k=2}^n=\sum\limits_{k=2}^n f(m,k-1)+f(m,n)-f(m,1)$</p><p>$\therefore f(m,n)=\sum\limits_{k=2}^n(2m+k)+f(m,1)$</p><p>因为$1$条直线和$m$个圆最多可将平面分为$(m^2-m+2)+2m=m^2+m+2$个部分，即$f(m,1)=m^2+m+2$</p><p>$\therefore f(m,n)=m^2+\dfrac{1}{2}n^2+2mn-m+\dfrac{1}{2}n+1$</p><p>我们再回归到本题</p><p>已知$(m,n)=(20,20)$，则$f(m,n)=1391$</p><p>故$20$个圆和$20$条直线最多能把平面分成$1391$个部分</p><h4 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h4><p>[^ 1]: 维基百科-欧拉示性数 <a href="https://en.m.wikipedia.org/wiki/Euler_characteristic">https://en.m.wikipedia.org/wiki/Euler_characteristic</a></p><h2 id="试题F：成绩统计"><a href="#试题F：成绩统计" class="headerlink" title="试题F：成绩统计"></a>试题F：成绩统计</h2><p>本题总分：$15$ 分</p><h3 id="【问题描述】-5"><a href="#【问题描述】-5" class="headerlink" title="【问题描述】"></a>【问题描述】</h3><p>小蓝给学生们组织了一场考试，卷面总分为$100$ 分，每个学生的得分都是一个$0$ 到$100$ 的整数。请计算这次考试的最高分、最低分和平均分。</p><h4 id="【输入格式】"><a href="#【输入格式】" class="headerlink" title="【输入格式】"></a>【输入格式】</h4><p>输入的第一行包含一个整数$n$，表示考试人数。</p><p>接下来$n$ 行，每行包含一个$0$ 至$100$ 的整数，表示一个学生的得分。</p><h4 id="【输出格式】"><a href="#【输出格式】" class="headerlink" title="【输出格式】"></a>【输出格式】</h4><p>输出三行。</p><p>第一行包含一个整数，表示最高分。</p><p>第二行包含一个整数，表示最低分。</p><p>第三行包含一个实数，四舍五入保留正好两位小数，表示平均分。</p><h4 id="【样例输入】"><a href="#【样例输入】" class="headerlink" title="【样例输入】"></a>【样例输入】</h4><figure class="highlight plain"><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">7 </span><br><span class="line">80 </span><br><span class="line">92 </span><br><span class="line">56 </span><br><span class="line">74 </span><br><span class="line">88 </span><br><span class="line">99 </span><br><span class="line">10</span><br></pre></td></tr></table></figure><h4 id="【样例输出】"><a href="#【样例输出】" class="headerlink" title="【样例输出】"></a>【样例输出】</h4><figure class="highlight plain"><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">99 </span><br><span class="line">10 </span><br><span class="line">71.29</span><br></pre></td></tr></table></figure><h4 id="【评测用例规模与约定】"><a href="#【评测用例规模与约定】" class="headerlink" title="【评测用例规模与约定】"></a>【评测用例规模与约定】</h4><p>对于$50\%$的评测用例， $1 \le n \le 100$。</p><p>对于所有评测用例，$1 \le n \le10000$。</p><h3 id="【解题思路】-4"><a href="#【解题思路】-4" class="headerlink" title="【解题思路】"></a>【解题思路】</h3><p>仅需五个变量，$n,Max,Min,Average,score$，分别存储考试人数，最高分，最低分，平均值，学生得分。在读取过程中就可以将答案求出。</p><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;bits/stdc++.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> <span class="built_in">std</span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Solve</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> n, Max = <span class="number">0</span>, Min = <span class="number">100</span>, score;</span><br><span class="line">    <span class="keyword">double</span> Average = <span class="number">0</span>;</span><br><span class="line"></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><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">1</span>; i &lt;= n; i++)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="built_in">scanf</span>(<span class="string">&quot;%d&quot;</span>, &amp;score);</span><br><span class="line">        Max = max(Max, score);</span><br><span class="line">        Min = min(Min, score);</span><br><span class="line">        Average += (<span class="keyword">double</span>)score;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;%d\n%d\n%.2lf&quot;</span>, Max, Min, Average / (<span class="keyword">double</span>)n);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    Solve();</span><br><span class="line"></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><h2 id="试题G：回文日期"><a href="#试题G：回文日期" class="headerlink" title="试题G：回文日期"></a>试题G：回文日期</h2><p>本题总分：$20$ 分</p><h3 id="【问题描述】-6"><a href="#【问题描述】-6" class="headerlink" title="【问题描述】"></a>【问题描述】</h3><p>$ 2020$ 年春节期间，有一个特殊的日期引起了大家的注意：$2020$年$2$月$2$日。因为如果将这个日期按$“yyyymmdd”$ 的格式写成一个$8$ 位数是$20200202$， 恰好是一个回文数。我们称这样的日期是回文日期。 有人表示$20200202$ 是“千年一遇” 的特殊日子。对此小明很不认同，因为不到$2$年之后就是下一个回文日期：$20211202$ 即$2021$年$12$月$2$日。 也有人表示$20200202$ 并不仅仅是一个回文日期，还是一个$ABABBABA$型的回文日期。对此小明也不认同，因为大约$100$年后就能遇到下一个$ABABBABA$ 型的回文日期：$21211212$ 即$2121$ 年$12$ 月$12$ 日。算不上“千年一遇”，顶多算“千年两遇”。 给定一个$8$ 位数的日期，请你计算该日期之后下一个回文日期和下一个$ABABBABA$型的回文日期各是哪一天。</p><h4 id="【输入格式】-1"><a href="#【输入格式】-1" class="headerlink" title="【输入格式】"></a>【输入格式】</h4><p>输入包含一个八位整数$N$，表示日期。</p><h4 id="【输出格式】-1"><a href="#【输出格式】-1" class="headerlink" title="【输出格式】"></a>【输出格式】</h4><p>输出两行，每行$1$ 个八位数。</p><p>第一行表示下一个回文日期，第二行表示下 一个$ABABBABA$ 型的回文日期。</p><h4 id="【样例输入】-1"><a href="#【样例输入】-1" class="headerlink" title="【样例输入】"></a>【样例输入】</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">20200202</span><br></pre></td></tr></table></figure><h4 id="【样例输出】-1"><a href="#【样例输出】-1" class="headerlink" title="【样例输出】"></a>【样例输出】</h4><figure class="highlight plain"><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">20211202 </span><br><span class="line">21211212</span><br></pre></td></tr></table></figure><h4 id="【评测用例规模与约定】-1"><a href="#【评测用例规模与约定】-1" class="headerlink" title="【评测用例规模与约定】"></a>【评测用例规模与约定】</h4><p>对于所有评测用例，$10000101 \le N \le 89991231$，保证$N$ 是一个合法日期的$8$位数表示。</p><h3 id="【解题思路】-5"><a href="#【解题思路】-5" class="headerlink" title="【解题思路】"></a>【解题思路】</h3><p>用$int$存$8$位的日期，直接暴力从当前日期开始枚举。</p><p>判断三点，是否为合法的日期，在合法的前提下去判断是否是回文日期，再去判断是否是$ABABBABA$型。</p><figure class="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><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;bits/stdc++.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> <span class="built_in">std</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">int</span> Month1[<span class="number">13</span>] = &#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"><span class="keyword">int</span> Month2[<span class="number">13</span>] = &#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;;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">inline</span> <span class="keyword">int</span> <span class="title">isLeap</span><span class="params">(<span class="keyword">int</span> x)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">return</span> (!(x % <span class="number">4</span>) &amp;&amp; (x % <span class="number">100</span>)) || (!(x % <span class="number">400</span>));</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">inline</span> <span class="keyword">int</span> <span class="title">Check_Date</span><span class="params">(<span class="keyword">int</span> x)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> Y = x / <span class="number">10000</span>;</span><br><span class="line">    <span class="keyword">int</span> M = (x / <span class="number">100</span>) % <span class="number">100</span>;</span><br><span class="line">    <span class="keyword">int</span> D = x % <span class="number">100</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (!M || M &gt; <span class="number">12</span>)</span><br><span class="line">        <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">if</span> (isLeap(Y))</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">if</span> (!D || D &gt; Month2[M])</span><br><span class="line">            <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">if</span> (!D || D &gt; Month1[M])</span><br><span class="line">            <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <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="keyword">int</span> <span class="title">Check1</span><span class="params">(<span class="keyword">int</span> x)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> ix = x, y = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">while</span> (ix)</span><br><span class="line">    &#123;</span><br><span class="line">        y = y * <span class="number">10</span> + ix % <span class="number">10</span>;</span><br><span class="line">        ix /= <span class="number">10</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> (x == y);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">inline</span> <span class="keyword">int</span> <span class="title">Check2</span><span class="params">(<span class="keyword">int</span> x)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="comment">//A B A B B A B A</span></span><br><span class="line">    <span class="keyword">int</span> pre1 = (x / <span class="number">10000000</span>), pre2 = (x / <span class="number">1000000</span>) % <span class="number">10</span>;</span><br><span class="line">    <span class="keyword">int</span> C1 = ((pre1 == ((x / <span class="number">100000</span>) % <span class="number">10</span>)) &amp;&amp; (pre1 == (x / <span class="number">100</span>) % <span class="number">10</span>) &amp;&amp; (pre1 == x % <span class="number">10</span>));</span><br><span class="line">    <span class="keyword">int</span> C2 = ((pre2 == (x / <span class="number">10000</span>) % <span class="number">10</span>) &amp;&amp; (pre2 == (x / <span class="number">1000</span>) % <span class="number">10</span>) &amp;&amp; (pre2 == (x / <span class="number">10</span>) % <span class="number">10</span>));</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> (C1 &amp;&amp; C2);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Solve</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> Date;</span><br><span class="line">    <span class="built_in">scanf</span>(<span class="string">&quot;%d&quot;</span>, &amp;Date);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">int</span> flag = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">while</span> (<span class="number">1</span>)</span><br><span class="line">    &#123;</span><br><span class="line">        Date++;</span><br><span class="line">        <span class="keyword">if</span> (Check_Date(Date))</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">if</span> (Check1(Date) &amp;&amp; !flag)</span><br><span class="line">            &#123;</span><br><span class="line">                <span class="built_in">printf</span>(<span class="string">&quot;%d\n&quot;</span>, Date);</span><br><span class="line">                flag = <span class="number">1</span>;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span> (Check2(Date))</span><br><span class="line">            &#123;</span><br><span class="line">                <span class="built_in">printf</span>(<span class="string">&quot;%d\n&quot;</span>, Date);</span><br><span class="line">                <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><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    Solve();</span><br><span class="line"></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><h2 id="试题H：子串分值"><a href="#试题H：子串分值" class="headerlink" title="试题H：子串分值"></a>试题H：子串分值</h2><p>本题总分：$20$ 分</p><h3 id="【问题描述】-7"><a href="#【问题描述】-7" class="headerlink" title="【问题描述】"></a>【问题描述】</h3><p>对于一个字符串$S$，我们定义$S$ 的分值 $f(S)$ 为$S$中恰好出现一次的字符个数。例如$f (”aba”) = 1,\\ $ $f (”abc”) = 3,\\ $ $f (”aaa”) = 0$。</p><p>现在给定一个字符串$S[0…n-1]$（长度为$n$），请你计算对于所有$S$的非空子串 $S\left[i…j\right] (0 \le i \le j &lt; n)$, $f (S\left[i… j\right])$ 的和是多少。</p><h4 id="【输入格式】-2"><a href="#【输入格式】-2" class="headerlink" title="【输入格式】"></a>【输入格式】</h4><p>输入一行包含一个由小写字母组成的字符串$S$。</p><h4 id="【输出格式】-2"><a href="#【输出格式】-2" class="headerlink" title="【输出格式】"></a>【输出格式】</h4><p>输出一个整数表示答案。</p><h4 id="【样例输入】-2"><a href="#【样例输入】-2" class="headerlink" title="【样例输入】"></a>【样例输入】</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ababc</span><br></pre></td></tr></table></figure><h4 id="【样例输出】-2"><a href="#【样例输出】-2" class="headerlink" title="【样例输出】"></a>【样例输出】</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">21</span><br></pre></td></tr></table></figure><h4 id="【样例说明】"><a href="#【样例说明】" class="headerlink" title="【样例说明】"></a>【样例说明】</h4><div class="table-container"><table><thead><tr><th>子串</th><th>分值</th></tr></thead><tbody><tr><td>$a$</td><td>$1$</td></tr><tr><td>$ab$</td><td>$2$</td></tr><tr><td>$aba$</td><td>$1$</td></tr><tr><td>$abab$</td><td>$0$</td></tr><tr><td>$ababc$</td><td>$1$</td></tr><tr><td>$b$</td><td>$1$</td></tr><tr><td>$ba$</td><td>$2$</td></tr><tr><td>$bab$</td><td>$1$</td></tr><tr><td>$babc$</td><td>$2$</td></tr><tr><td>$a$</td><td>$1$</td></tr><tr><td>$ab$</td><td>$2$</td></tr><tr><td>$abc$</td><td>$3$</td></tr><tr><td>$b$</td><td>$1$</td></tr><tr><td>$bc$</td><td>$2$</td></tr><tr><td>$c$</td><td>$1$</td></tr></tbody></table></div><p>总分值为$21$分。</p><h4 id="【评测用例规模与约定】-2"><a href="#【评测用例规模与约定】-2" class="headerlink" title="【评测用例规模与约定】"></a>【评测用例规模与约定】</h4><p>对于$20\%$ 的评测用例，$1 \le n \le 10$；</p><p>对于$40\%$ 的评测用例，$1 \le n \le 100$；</p><p>对于$50\%$ 的评测用例，$1 \le n \le 1000$；</p><p>对于$60\%$ 的评测用例，$1 \le n \le 10000$；</p><p>对于所有评测用例，$1 \le n \le 100000$。</p><h3 id="【解题思路】-6"><a href="#【解题思路】-6" class="headerlink" title="【解题思路】"></a>【解题思路】</h3><p>如果使用朴素的暴力算法，即枚举子串，再逐个数有多少个字母仅出现了一次，复杂度为$O(n^3)$。可以得到$40$分。写的好一点说不定可以拿到$50$分。所以这个题不能用直白的思路去解决。那么我们反过来，先看字母，再去求在多少个子串中仅出现了一次呢？</p><p>逐个枚举$S$中的字母。先从当前位置，向前枚举找到第一个和当前字母相同的字母。再从当前位置，向后枚举找到第一个和当前字母相同的字母，</p><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images@2fa92464e84c664ac36ac52b1cca4332ed0cfa62/2021/02/23/5cad4470cc4ba7713780f3d0c92592cc.png"  style="zoom: 50%;" /></p><p>例如：对于红色箭头指向的$a$（以下简称为$Ra$），我们要找的即为绿色箭头所指向的$a$（以下简称为$LGa,RGa$，$L,R$表示左右）。我们要求$Ra$对答案的贡献，即为在$LGa,RGa$之间，任意选择包含$Ra$的子串的方案数。由于$Ra$与$LGa$之间有$4$个字母(包含$Ra$，不包含$LGa$，下面也一样)，$Ra$与$RGa$之间有$2$个字母。故包含$Ra$的子串的个数共$4\times2=8$。</p><p>按照这种做法，复杂度最差为$O(n^2)$，可以拿到$60$分。如何再优化呢？</p><p>我们发现，要按照枚举字母，再求它的贡献的思路，$O(n)$去把每个字母都枚举一遍是必不可少的，而每次都向前枚举和向后枚举则需要重复花费大量的时间。例如上面那个例子，在枚举到$Ra$的时候，寻找$LGa$时便利了$s,d,c$,而枚举到$Ra$后面的$b$时，寻找前面的第一个$b$的过程中，又便利了$s,d,c$，这个地方显然是有优化的空间的。</p><p>我们可以通过定义数组$pos[27]$来记录从前往后遍历的过程中$a\sim z$，上次一出现的位置。这样就能在$O(n)$的复杂度内完成<strong style="color:green">“当前位置，向前枚举找到第一个和当前字母相同的字母”</strong>的任务。同理，<strong style="color:green">“当前位置，向后枚举找到第一个和当前字母相同的字母”</strong>也可以用一个相同的过程完成。之后我们只需要$O(n)$遍历一遍$S$，并且做做乘法，做做加法，就可以把答案求出来了。</p><figure class="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><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;bits/stdc++.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> <span class="built_in">std</span>;</span><br><span class="line"><span class="keyword">typedef</span> <span class="keyword">long</span> <span class="keyword">long</span> ll;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="keyword">int</span> Maxn = <span class="number">100010</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">char</span> s[Maxn];</span><br><span class="line"><span class="keyword">int</span> pos[<span class="number">27</span>];</span><br><span class="line"><span class="keyword">int</span> Before[Maxn], Behind[Maxn];</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Solve</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">scanf</span>(<span class="string">&quot;%s&quot;</span>, s + <span class="number">1</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">int</span> len = <span class="built_in">strlen</span>(s + <span class="number">1</span>);</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">1</span>; i &lt;= <span class="number">26</span>; i++)</span><br><span class="line">        pos[i] = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">1</span>; i &lt;= len; i++)</span><br><span class="line">    &#123;</span><br><span class="line">        Before[i] = pos[s[i] - <span class="string">&#x27;a&#x27;</span> + <span class="number">1</span>];</span><br><span class="line">        pos[s[i] - <span class="string">&#x27;a&#x27;</span> + <span class="number">1</span>] = i;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">1</span>; i &lt;= <span class="number">26</span>; i++)</span><br><span class="line">        pos[i] = len + <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = len; i &gt;= <span class="number">1</span>; i--)</span><br><span class="line">    &#123;</span><br><span class="line">        Behind[i] = pos[s[i] - <span class="string">&#x27;a&#x27;</span> + <span class="number">1</span>];</span><br><span class="line">        pos[s[i] - <span class="string">&#x27;a&#x27;</span> + <span class="number">1</span>] = i;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    ll Ans = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">1</span>; i &lt;= len; i++)</span><br><span class="line">        Ans += (ll)(i - Before[i]) * (ll)(Behind[i] - i);</span><br><span class="line"></span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;%lld&quot;</span>, Ans);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    Solve();</span><br><span class="line"></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><h2 id="试题I：荒岛探测"><a href="#试题I：荒岛探测" class="headerlink" title="试题I：荒岛探测"></a>试题I：荒岛探测</h2><p>本题总分：$25$ 分</p><h3 id="【题目描述】"><a href="#【题目描述】" class="headerlink" title="【题目描述】"></a>【题目描述】</h3><p>科学家小蓝来到了一个荒岛，准备对这个荒岛进行探测考察。小蓝使用了一个超声定位设备来对自己进行定位。为了使用这个设备，小蓝需要在不同的点分别安装一个固定的发射器和一个固定的接收器。小蓝手中还有一个移动设备。定位设备需要从发射器发射一个信号到移动设备，移动设备收到后马上转发，最后由接收器接收，根据这些设备之间传递的时间差就能计算出移动设备距离发射器和接收器的两个距离，从而实现定位。 小蓝在两个位置已经安装了发射器和接收器，其中发射器安装在坐标$(x_A,y_A)$，接收器安装在坐标 $(x_B,y_B)$。小蓝的发射器和接收器可能在岛上，也可能不在岛上。小蓝的定位设备设计有些缺陷，当发射器到移动设备的距离加上移动设备到接收器的距离之和大于$L$ 时，定位设备工作不正常。当和小于等于$L$ 时，定位设备工作正常。为了安全，小蓝只在定位设备工作正常的区域探测考察。 已知荒岛是一个三角形，三个顶点的坐标分别为$(x_1,y_1),(x_2, y_2)(x_3, y_3)$。 请计算，小蓝在荒岛上可以探测到的面积有多大？</p><h4 id="【输入格式】-3"><a href="#【输入格式】-3" class="headerlink" title="【输入格式】"></a>【输入格式】</h4><p>输入的第一行包含五个整数，分别为$x_A, y_A, x_B, y_B, L$。 第二行包含六个整数，分别为$ x_1, y_1, x_2, y_2, x_3, y_3$。</p><h4 id="【输出格式】-3"><a href="#【输出格式】-3" class="headerlink" title="【输出格式】"></a>【输出格式】</h4><p>输出一行，包含一个实数，四舍五入保留$2$位小数，表示答案。 考虑到计算中的误差，只要你的输出与参考输出相差不超过$0.01$即可得分。</p><h4 id="【样例输入】-3"><a href="#【样例输入】-3" class="headerlink" title="【样例输入】"></a>【样例输入】</h4><figure class="highlight plain"><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">10 6 4 12 12 </span><br><span class="line">0 2 13 2 13 15</span><br></pre></td></tr></table></figure><h4 id="【样例输出】-3"><a href="#【样例输出】-3" class="headerlink" title="【样例输出】"></a>【样例输出】</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">39.99</span><br></pre></td></tr></table></figure><h4 id="【样例说明】-1"><a href="#【样例说明】-1" class="headerlink" title="【样例说明】"></a>【样例说明】</h4><p>当输出为39.98、39.99或40.00时可以得分。</p><h4 id="【评测用例规模与约定】-3"><a href="#【评测用例规模与约定】-3" class="headerlink" title="【评测用例规模与约定】"></a>【评测用例规模与约定】</h4><p>对于所有评测用例， 保证发射器的两个坐标不同。</p><p>$-1000\le x_A,y_A,x_B,y_B\le 1000,-1000\le x_1,y_1,x_2,y_2,x_3,y_3\le1000,-1000\le L\le 1000$</p><h3 id="【解题思路】-7"><a href="#【解题思路】-7" class="headerlink" title="【解题思路】"></a>【解题思路】</h3><p>不会QAQ。</p><h2 id="试题J：字串排序"><a href="#试题J：字串排序" class="headerlink" title="试题J：字串排序"></a>试题J：字串排序</h2><p>本题总分：$25$ 分</p><h3 id="【题目描述】-1"><a href="#【题目描述】-1" class="headerlink" title="【题目描述】"></a>【题目描述】</h3><p>小蓝最近学习了一些排序算法，其中冒泡排序让他印象深刻。在冒泡排序中，每次只能交换相邻的两个元素。小蓝发现，如果对一个字符串中的字符排序，只允许交换相邻的两个字符，则在所有可能的排序方案中，冒泡排序的总交换次数是最少的。</p><p>例如，对于字符串 $lan$ 排序，只需要 $1 $次交换。对于字符串 $qiao$ 排序， 总共需要 $4$ 次交换。小蓝找到了很多字符串试图排序，他恰巧碰到一个字符串，需要$ V$ 次交换，可是他忘了把这个字符串记下来，现在找不到了。</p><p>请帮助小蓝找一个只包含小写英文字母的字符串，对该串进行冒泡排序，正好需要 $V $次交换。如果可能找到多个，请告诉小蓝最短的那个。如果最短的仍然有多个，请告诉小蓝字典序最小的那个。请注意字符串中可以包含相同的字符。</p><h4 id="【输入格式】-4"><a href="#【输入格式】-4" class="headerlink" title="【输入格式】"></a>【输入格式】</h4><p>输入的第一行包含一个整数V，小蓝的幸运数字。</p><h4 id="【输出格式】-4"><a href="#【输出格式】-4" class="headerlink" title="【输出格式】"></a>【输出格式】</h4><p>题面要求的一行字符串。</p><h4 id="【样例输入1】"><a href="#【样例输入1】" class="headerlink" title="【样例输入1】"></a>【样例输入1】</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">4</span><br></pre></td></tr></table></figure><h4 id="【样例输出1】"><a href="#【样例输出1】" class="headerlink" title="【样例输出1】"></a>【样例输出1】</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">bbaa</span><br></pre></td></tr></table></figure><h4 id="【样例输入2】"><a href="#【样例输入2】" class="headerlink" title="【样例输入2】"></a>【样例输入2】</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">100</span><br></pre></td></tr></table></figure><h4 id="【样例输出2】"><a href="#【样例输出2】" class="headerlink" title="【样例输出2】"></a>【样例输出2】</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">jihgfeeddccbbaa</span><br></pre></td></tr></table></figure><h4 id="【评测用例规模与约定】-4"><a href="#【评测用例规模与约定】-4" class="headerlink" title="【评测用例规模与约定】"></a>【评测用例规模与约定】</h4><p>对于$30\%$ 的评测用例，$1 ≤ V ≤ 20$；</p><p>对于$50\%$ 的评测用例，$1 ≤ V ≤ 100$；</p><p>对于$100\%$ 的评测用例，$1 ≤ V ≤ 10000$；</p><h3 id="【解题思路】-8"><a href="#【解题思路】-8" class="headerlink" title="【解题思路】"></a>【解题思路】</h3><p><strong style="color:red">暂时不会，下面的是错误做法。待更新…</strong></p><p>题目要求最短，字典序最小。我们可以大胆猜测，答案一定是一个单调不增的字符串，即从$z$降到$a$，并且一定是连续的，也就是说，不会出现$dba$这样的字符串，因为中间少了$c$。同时，对于这样的单调不增的字符串来说，将其冒泡排序，对于其中某个字符$x$而言，需要交换的次数为比他小的字符的个数的和，例如：$ccbbaa$，对于每个$c$，要交换$4$次，对于每个$b$，交换次数为$2$，故总交换次数为$4\times2+2\times2=12$</p><p>然后我们打一个表：</p><div class="table-container"><table><thead><tr><th>字符串</th><th>交换次数</th></tr></thead><tbody><tr><td>$a$</td><td>$0$</td></tr><tr><td>$ba$</td><td>$1$</td></tr><tr><td>$cba$</td><td>$3$</td></tr><tr><td>$dcba$</td><td>$6$</td></tr><tr><td>$edcba$</td><td>$10$</td></tr></tbody></table></div><p>其实根据这个表，我们可以得出一个很重要的结论：==表中的字符串，为对应长度的字符串中交换次数最多的==。例如$cba$交换次数为$3$，而$dcba$交换次数为$6$，那么交换次数为$4 \sim 6$的字符串长度一定是$4$。当字符串长度大于$26$时，交换次数最多的肯定是前面若干个$z$，随后是$yxwvu…cba$。</p><p>那么一个大胆的构造思路，就从脑中浮现出来了：</p><ol><li><p>计算出交换$V$次的字符串长度应该是多少，记为$length$；</p></li><li><p>建立一长度为$length$且全为$a$的字符串，记为$S$；</p></li><li><p>根据$V$，修改$S$中的字符。</p></li></ol><p>例如$V=5$，先计算出长度应该为$4$，再令$S=”aaaa”$。此时交换次数为$1$，我们将其变成$”baaa”$，此时交换次数为$3$。再变成$”bbaa”$，此时交换次数为$4$。此时$b$和$a$的个数相同了，不能再加$b$了，改成把$b$变成$c$的过程。于是得到了$”cbaa”$，交换次数为$5$，从而将答案求出。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;2020蓝桥杯A组省赛第二场&quot;&gt;&lt;a href=&quot;#2020蓝桥杯A组省赛第二场&quot; class=&quot;headerlink&quot; title=&quot;2020蓝桥杯A组省赛第二场&quot;&gt;&lt;/a&gt;2020蓝桥杯A组省赛第二场&lt;/h1&gt;&lt;h2 id=&quot;试题A-门牌制作&quot;&gt;&lt;a href</summary>
      
    
    
    
    <category term="算法" scheme="http://www.fcayh.cn/categories/%E7%AE%97%E6%B3%95/"/>
    
    
    <category term="蓝桥杯" scheme="http://www.fcayh.cn/tags/%E8%93%9D%E6%A1%A5%E6%9D%AF/"/>
    
  </entry>
  
  <entry>
    <title>十进制可逆计数器&amp;译码显示</title>
    <link href="http://www.fcayh.cn/2021/01/20/counterdecoder/"/>
    <id>http://www.fcayh.cn/2021/01/20/counterdecoder/</id>
    <published>2021-01-20T03:14:55.000Z</published>
    <updated>2022-04-12T13:39:29.918Z</updated>
    
    <content type="html"><![CDATA[<h1 id="十进制可逆计数器-amp-译码显示"><a href="#十进制可逆计数器-amp-译码显示" class="headerlink" title="十进制可逆计数器&amp;译码显示"></a>十进制可逆计数器&amp;译码显示</h1><h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>这次仅仅就只需要在“十进制可逆计数器”那一个实验的基础上，加上用数码管显示数。大概就是第一次实验与第三次实验的结合体，用层次化写一下就可以了。</p><h2 id="过程"><a href="#过程" class="headerlink" title="过程"></a>过程</h2><p>无</p><h2 id="代码"><a href="#代码" class="headerlink" title="代码"></a>代码</h2><p>顶层文件 $gal\underline{}3035\underline{}5.v$</p><figure class="highlight verilog"><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">module</span> gal_3035_5 (ld,clk,d,ud,clr,Q,co,codeout);</span><br><span class="line"><span class="keyword">input</span> ld,clk,ud,clr;</span><br><span class="line"><span class="keyword">input</span> [<span class="number">3</span>: <span class="number">0</span>]d;</span><br><span class="line"><span class="keyword">output</span> [<span class="number">3</span>: <span class="number">0</span>]Q;</span><br><span class="line"><span class="keyword">output</span> co;</span><br><span class="line"><span class="keyword">output</span> [<span class="number">6</span>: <span class="number">0</span>]codeout;</span><br><span class="line"></span><br><span class="line">gal_3035_5_1 a(co,ld,clk,d,ud,clr,Q);</span><br><span class="line">gal_3035_5_2 b(codeout,Q);</span><br><span class="line"></span><br><span class="line"><span class="keyword">endmodule</span></span><br></pre></td></tr></table></figure><p>计数器模块 $gal\underline{}3035\underline{}5\underline{}1.v$</p><figure class="highlight verilog"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">module</span> gal_3035_5_1 (co,ld,clk,d,ud,clr,Q);</span><br><span class="line"><span class="keyword">input</span> ld,ud,clr,clk; </span><br><span class="line">    <span class="keyword">input</span> [<span class="number">3</span>:<span class="number">0</span>]d; </span><br><span class="line">    <span class="keyword">output</span> <span class="keyword">reg</span> [<span class="number">3</span>:<span class="number">0</span>]Q; </span><br><span class="line">    <span class="keyword">output</span> <span class="keyword">reg</span> co; </span><br><span class="line">    </span><br><span class="line">    <span class="keyword">always</span>@(<span class="keyword">posedge</span> clk) </span><br><span class="line">    <span class="keyword">begin</span> </span><br><span class="line">        <span class="keyword">if</span>(!clr) <span class="comment">// clr为0时，不进行清零操作</span></span><br><span class="line">            <span class="keyword">begin</span> </span><br><span class="line">                <span class="keyword">if</span>(ld) <span class="comment">// ld为1时，进行置数操作 </span></span><br><span class="line">                    <span class="keyword">begin</span> </span><br><span class="line">                        Q[<span class="number">3</span>]=d[<span class="number">3</span>]; </span><br><span class="line">                        Q[<span class="number">2</span>]=d[<span class="number">2</span>]; </span><br><span class="line">                        Q[<span class="number">1</span>]=d[<span class="number">1</span>]; </span><br><span class="line">                        Q[<span class="number">0</span>]=d[<span class="number">0</span>]; </span><br><span class="line">                    <span class="keyword">end</span> </span><br><span class="line">                <span class="keyword">else</span> </span><br><span class="line">                    <span class="keyword">begin</span> </span><br><span class="line">                        <span class="keyword">if</span>(ud) <span class="comment">//ud是1时，倒序置数 </span></span><br><span class="line">                            <span class="keyword">begin</span> </span><br><span class="line">                                <span class="keyword">if</span>(Q==<span class="number">4&#x27;b0000</span>) </span><br><span class="line">                                    <span class="keyword">begin</span> </span><br><span class="line">                                        Q&lt;=<span class="number">4&#x27;b1001</span>; </span><br><span class="line">                                        co&lt;=<span class="number">1</span>; </span><br><span class="line">                                    <span class="keyword">end</span> </span><br><span class="line">                                <span class="keyword">else</span> </span><br><span class="line">                                    <span class="keyword">begin</span> </span><br><span class="line">                                        Q&lt;=Q-<span class="number">1</span>;</span><br><span class="line">                                        co&lt;=<span class="number">0</span>;</span><br><span class="line">                                    <span class="keyword">end</span> </span><br><span class="line">                            <span class="keyword">end</span> </span><br><span class="line">                        <span class="keyword">else</span> <span class="comment">// ud是0时，顺序置数 </span></span><br><span class="line">                            <span class="keyword">begin</span> </span><br><span class="line">                                <span class="keyword">if</span>(Q==<span class="number">4&#x27;b1001</span>) </span><br><span class="line">                                    <span class="keyword">begin</span> </span><br><span class="line">                                        Q&lt;=<span class="number">4&#x27;b0000</span>; </span><br><span class="line">                                        co&lt;=<span class="number">1</span>; </span><br><span class="line">                                    <span class="keyword">end</span> </span><br><span class="line">                                <span class="keyword">else</span> </span><br><span class="line">                                    <span class="keyword">begin</span> </span><br><span class="line">                                        Q&lt;=Q+<span class="number">1</span>;</span><br><span class="line">                                        co&lt;=<span class="number">0</span>;</span><br><span class="line">                                    <span class="keyword">end</span> </span><br><span class="line">                            <span class="keyword">end</span> </span><br><span class="line">                    <span class="keyword">end</span> </span><br><span class="line">            <span class="keyword">end</span>  </span><br><span class="line">        <span class="keyword">else</span> </span><br><span class="line">            <span class="keyword">begin</span> </span><br><span class="line">                Q&lt;=<span class="number">4&#x27;b0000</span>; </span><br><span class="line">            <span class="keyword">end</span></span><br><span class="line">    <span class="keyword">end</span> </span><br><span class="line"><span class="keyword">endmodule</span></span><br></pre></td></tr></table></figure><p>译码显示模块 $gal\underline{}3035\underline{}5\underline{}2.v$</p><figure class="highlight verilog"><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="keyword">module</span> gal_3035_5_2 (codeout,Q);</span><br><span class="line"><span class="keyword">input</span>[<span class="number">3</span>: <span class="number">0</span>] Q;</span><br><span class="line"><span class="keyword">output</span> <span class="keyword">reg</span>[<span class="number">6</span>: <span class="number">0</span>] codeout;</span><br><span class="line"></span><br><span class="line"><span class="keyword">always</span> @ (Q)</span><br><span class="line"><span class="keyword">begin</span></span><br><span class="line"><span class="keyword">case</span> (Q) </span><br><span class="line"><span class="number">4&#x27;d0</span>: codeout=<span class="number">7&#x27;b1111110</span>;</span><br><span class="line"><span class="number">4&#x27;d1</span>: codeout=<span class="number">7&#x27;b0110000</span>;</span><br><span class="line"><span class="number">4&#x27;d2</span>: codeout=<span class="number">7&#x27;b1101101</span>;</span><br><span class="line"><span class="number">4&#x27;d3</span>: codeout=<span class="number">7&#x27;b1111001</span>;</span><br><span class="line"><span class="number">4&#x27;d4</span>: codeout=<span class="number">7&#x27;b0110011</span>;</span><br><span class="line"><span class="number">4&#x27;d5</span>: codeout=<span class="number">7&#x27;b1011011</span>;</span><br><span class="line"><span class="number">4&#x27;d6</span>: codeout=<span class="number">7&#x27;b1011111</span>;</span><br><span class="line"><span class="number">4&#x27;d7</span>: codeout=<span class="number">7&#x27;b1110000</span>;</span><br><span class="line"><span class="number">4&#x27;d8</span>: codeout=<span class="number">7&#x27;b1111111</span>;</span><br><span class="line"><span class="number">4&#x27;d9</span>: codeout=<span class="number">7&#x27;b1111011</span>;</span><br><span class="line"><span class="keyword">default</span>: codeout=<span class="number">7&#x27;dx</span>;</span><br><span class="line"><span class="keyword">endcase</span></span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"><span class="keyword">endmodule</span></span><br></pre></td></tr></table></figure><h2 id="仿真波形"><a href="#仿真波形" class="headerlink" title="仿真波形"></a>仿真波形</h2><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images@447d8c76f9cf0b5618a67a0d409e74df68f1b28c/2021/01/20/f8add6e046015fb1d4e1e6b53ea77ca6.png" style="zoom: 80%;" /></p><h2 id="引脚分配"><a href="#引脚分配" class="headerlink" title="引脚分配"></a>引脚分配</h2><p>$Family:Cyclone\ IV\ E\ \ \ \ \ \ \ \ Device:EP4CE22E22C8$</p><div class="table-container"><table><thead><tr><th>信号名</th><th>主板器件</th><th>PIN</th></tr></thead><tbody><tr><td>Q[3]</td><td>LED3</td><td>54</td></tr><tr><td>Q[2]</td><td>LED2</td><td>52</td></tr><tr><td>Q[1]</td><td>LED1</td><td>50</td></tr><tr><td>Q[0]</td><td>LED0</td><td>46</td></tr><tr><td>clr</td><td>KEY7</td><td>44</td></tr><tr><td>co</td><td>LED4</td><td>58</td></tr><tr><td>codeout[6]</td><td>a</td><td>122</td></tr><tr><td>codeout[5]</td><td>b</td><td>100</td></tr><tr><td>codeout[4]</td><td>c</td><td>104</td></tr><tr><td>codeout[3]</td><td>d</td><td>111</td></tr><tr><td>codeout[2]</td><td>e</td><td>106</td></tr><tr><td>codeout[1]</td><td>f</td><td>110</td></tr><tr><td>codeout[0]</td><td>g</td><td>103</td></tr><tr><td>clk</td><td>CLK0</td><td>88</td></tr><tr><td>d[3]</td><td>KEY4</td><td>32</td></tr><tr><td>d[2]</td><td>KEY3</td><td>33</td></tr><tr><td>d[1]</td><td>KEY2</td><td>30</td></tr><tr><td>d[0]</td><td>KEY1</td><td>31</td></tr><tr><td>ld</td><td>KEY5</td><td>42</td></tr><tr><td>ud</td><td>KEY6</td><td>39</td></tr></tbody></table></div>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;十进制可逆计数器-amp-译码显示&quot;&gt;&lt;a href=&quot;#十进制可逆计数器-amp-译码显示&quot; class=&quot;headerlink&quot; title=&quot;十进制可逆计数器&amp;amp;译码显示&quot;&gt;&lt;/a&gt;十进制可逆计数器&amp;amp;译码显示&lt;/h1&gt;&lt;h2 id=&quot;简介&quot;&gt;&lt;</summary>
      
    
    
    
    <category term="作业-考试" scheme="http://www.fcayh.cn/categories/%E4%BD%9C%E4%B8%9A-%E8%80%83%E8%AF%95/"/>
    
    
    <category term="数字电子技术实验" scheme="http://www.fcayh.cn/tags/%E6%95%B0%E5%AD%97%E7%94%B5%E5%AD%90%E6%8A%80%E6%9C%AF%E5%AE%9E%E9%AA%8C/"/>
    
  </entry>
  
  <entry>
    <title>可控分频器&amp;Modelsim仿真</title>
    <link href="http://www.fcayh.cn/2021/01/20/splittor/"/>
    <id>http://www.fcayh.cn/2021/01/20/splittor/</id>
    <published>2021-01-20T01:49:42.000Z</published>
    <updated>2022-04-12T13:40:18.072Z</updated>
    
    <content type="html"><![CDATA[<h1 id="可控分频器-amp-Modelsim仿真"><a href="#可控分频器-amp-Modelsim仿真" class="headerlink" title="可控分频器&amp;Modelsim仿真"></a>可控分频器&amp;Modelsim仿真</h1><h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>啥是分频器呢？首先我们知道如果有一个时钟信号$CLK$的波形如下：</p><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images@7551ad396378a3b76af49dbc9d863603bbaaafd1/2021/01/20/ec03ade9e3fb975f783098c4073107d0.png" style="zoom:33%;" /></p><p>那么我们定义一个$Q$，每当接收到时钟信号的上升沿，则变一次电平，那么就是这样：</p><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images@a6618a31a54f1b9cb6ec850bdf85d9a85fcffab6/2021/01/20/e10a958b4a17cf86d6d2a36b5ff83879.png" style="zoom: 33%;" /></p><p>这样我们就得到了$Q$的频率为$CLK$的一半。</p><p>而我们的试验箱中有一个频率为$50MHz$的时钟信号源，我们要做的就是通过分频，获得频率为学号后四位以及后五位的信号，对我来说就是$3035Hz$以及$13035Hz$。</p><p>此次实验需要使用$Modelsim$进行仿真。</p><h2 id="过程"><a href="#过程" class="headerlink" title="过程"></a>过程</h2><p>$50MHz=50\\times10^6Hz\\ 50\\times10^6\\div 3035 =16474\\16474\\div2=8237 $</p><p>那么我们用$Q$当作计数器，每接受$8237$次来自$CLK$的上升沿信号，$c0$就反转一次，测$c0$就约是$3035Hz$的频率了。</p><p>同时这次实验需要学会如何为$Modelsim$写仿真测试文件:</p><figure class="highlight verilog"><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="meta">`<span class="meta-keyword">timescale</span> 10ns/1ns // 仿真时间单位/时间精度</span></span><br><span class="line"><span class="keyword">initial</span> </span><br><span class="line"><span class="keyword">begin</span> </span><br><span class="line">clk = <span class="number">0</span>;    <span class="comment">// 将clk置0</span></span><br><span class="line">en = <span class="number">0</span>;     <span class="comment">// 使能信号</span></span><br><span class="line">#<span class="number">5</span>          <span class="comment">// 50ns时</span></span><br><span class="line">en = <span class="number">1</span>;     <span class="comment">// 将使能端置1</span></span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">always</span>#<span class="number">1</span> clk=~clk;  <span class="comment">//每10ns clk翻转一次</span></span><br></pre></td></tr></table></figure><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images@41d657c73d04fe7236f3adf81dc737acbe49df39/2021/01/20/cb2116513def883e1664970dd9b536b2.png" style="zoom: 80%;" /></p><p>通过这张图我们可以看到，$clk$确实每$10ns$翻转一次，且 $50ns$时$en$从$0$跳变至$1$。</p><h2 id="代码"><a href="#代码" class="headerlink" title="代码"></a>代码</h2><p>测试文件 $test\underline{}gal\underline{}3035\underline{}4.v$</p><figure class="highlight verilog"><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="meta-keyword">timescale</span> 10ns/1ns</span></span><br><span class="line"><span class="keyword">module</span> test_gal_3035_4;</span><br><span class="line"><span class="keyword">reg</span> clk;</span><br><span class="line"><span class="keyword">reg</span> en;</span><br><span class="line"><span class="keyword">reg</span> x;</span><br><span class="line"><span class="keyword">wire</span> [<span class="number">13</span>:<span class="number">0</span>]Q;</span><br><span class="line"><span class="keyword">wire</span> c0;</span><br><span class="line"></span><br><span class="line"><span class="keyword">initial</span> </span><br><span class="line"><span class="keyword">begin</span> </span><br><span class="line">clk = <span class="number">0</span>;</span><br><span class="line">en = <span class="number">0</span>;</span><br><span class="line">x = <span class="number">0</span>;</span><br><span class="line">#<span class="number">5</span></span><br><span class="line">en = <span class="number">1</span>;</span><br><span class="line">#<span class="number">1304923</span></span><br><span class="line">x = <span class="number">1</span>;</span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">always</span>#<span class="number">1</span> clk=~clk;</span><br><span class="line"></span><br><span class="line">gal_3035_4 test4(<span class="variable">.clk</span>(clk),<span class="variable">.en</span>(en),<span class="variable">.Q</span>(Q),<span class="variable">.c0</span>(c0),<span class="variable">.x</span>(x));</span><br><span class="line"></span><br><span class="line"><span class="keyword">endmodule</span></span><br></pre></td></tr></table></figure><p>分频器代码 $gal\underline{}3035\underline{}4.v$</p><figure class="highlight verilog"><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="keyword">module</span> gal_3035_4(clk,c0,Q,en,x);     </span><br><span class="line"></span><br><span class="line">    <span class="keyword">input</span> clk,en,x;</span><br><span class="line">    <span class="keyword">output</span> c0,Q;</span><br><span class="line">    <span class="keyword">reg</span>[<span class="number">13</span>:<span class="number">0</span>] Q;</span><br><span class="line">    <span class="keyword">reg</span> c0 ;   <span class="comment">//可为寄存器输出型</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">always</span>@(<span class="keyword">posedge</span> clk <span class="keyword">or</span> <span class="keyword">negedge</span> en)</span><br><span class="line">    <span class="keyword">begin</span></span><br><span class="line">        <span class="keyword">if</span>(!en)     </span><br><span class="line">        <span class="keyword">begin</span>      </span><br><span class="line">            Q &lt;= <span class="number">0</span>;</span><br><span class="line">            c0 &lt;= <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">end</span></span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span>(!x)</span><br><span class="line">                <span class="keyword">begin</span></span><br><span class="line">                    <span class="keyword">if</span>(Q == <span class="number">8237</span>)      </span><br><span class="line">                    <span class="keyword">begin</span></span><br><span class="line">                        c0 &lt;= ~c0; <span class="comment">//时钟翻转</span></span><br><span class="line">                        Q&lt;= <span class="number">0</span>;     <span class="comment">//计数清零</span></span><br><span class="line">                    <span class="keyword">end</span></span><br><span class="line">                    <span class="keyword">else</span> Q &lt;= Q + <span class="number">1</span>;</span><br><span class="line">                <span class="keyword">end</span></span><br><span class="line">                <span class="keyword">else</span></span><br><span class="line">                <span class="keyword">begin</span></span><br><span class="line">                    <span class="keyword">if</span>(Q &gt;= <span class="number">1918</span>)    <span class="comment">//这里要用&gt;=，因为当x变成1时，可能Q是1918到8237之间的数</span></span><br><span class="line">                    <span class="keyword">begin</span></span><br><span class="line">                        c0 &lt;= ~c0; <span class="comment">//时钟翻转</span></span><br><span class="line">                        Q&lt;= <span class="number">0</span>;     <span class="comment">//计数清零</span></span><br><span class="line">                    <span class="keyword">end</span></span><br><span class="line">                    <span class="keyword">else</span> Q &lt;= Q + <span class="number">1</span>;</span><br><span class="line">                <span class="keyword">end</span></span><br><span class="line">            </span><br><span class="line">    <span class="keyword">end</span></span><br><span class="line">    </span><br><span class="line"><span class="keyword">endmodule</span></span><br></pre></td></tr></table></figure><h2 id="仿真波形"><a href="#仿真波形" class="headerlink" title="仿真波形"></a>仿真波形</h2><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images@70ccc2bafe99fc749ce174e161dded8355f61931/2021/01/20/3fe1f4281afcc24455b572c492bdaee5.png" style="zoom:80%;" /></p><p>从上图可以看到在$13049230ns$之后$c0$的频率明显加快了，这里对应了测试文件第$17$行的<code>x = 1</code>。</p><p>放大一点去观察的话，插入两个$Cursor$，下图红圈圈出的数为两个$Cursor$之间的时间，即周期。</p><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images@53130831c70a106ce39a6ac2ee2038efcf76e412/2021/01/20/1735fc87a11cfec0b3f041cbae579da5.png" style="zoom:80%;" /></p><p>$T=329520ns\\f=\frac{1}{T}=3035Hz$</p><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images@17b9c385ca02540ebaa662c161b0933dbef25730/2021/01/20/66daf9ced43fac8c8808f58821cf2af2.png" style="zoom:80%;" /></p><p>再看另一部分：</p><p>$T=76760ns\\f=\frac{1}{T}=13028Hz$</p><p>这里存在$10Hz$以内的误差。</p><h2 id="引脚分配"><a href="#引脚分配" class="headerlink" title="引脚分配"></a>引脚分配</h2><p>$Family:Cyclone\ IV\ E\ \ \ \ \ \ \ \ Device:EP4CE22E22C8$</p><div class="table-container"><table><thead><tr><th>信号名</th><th>主板器件</th><th>PIN</th></tr></thead><tbody><tr><td>co</td><td>LED0</td><td>46</td></tr><tr><td>clk</td><td>50MHz</td><td>90</td></tr><tr><td>en</td><td>KEY2</td><td>30</td></tr><tr><td>x</td><td>KEY1</td><td>31</td></tr></tbody></table></div>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;可控分频器-amp-Modelsim仿真&quot;&gt;&lt;a href=&quot;#可控分频器-amp-Modelsim仿真&quot; class=&quot;headerlink&quot; title=&quot;可控分频器&amp;amp;Modelsim仿真&quot;&gt;&lt;/a&gt;可控分频器&amp;amp;Modelsim仿真&lt;/h1&gt;&lt;</summary>
      
    
    
    
    <category term="作业-考试" scheme="http://www.fcayh.cn/categories/%E4%BD%9C%E4%B8%9A-%E8%80%83%E8%AF%95/"/>
    
    
    <category term="数字电子技术实验" scheme="http://www.fcayh.cn/tags/%E6%95%B0%E5%AD%97%E7%94%B5%E5%AD%90%E6%8A%80%E6%9C%AF%E5%AE%9E%E9%AA%8C/"/>
    
  </entry>
  
  <entry>
    <title>十进制可逆计数器</title>
    <link href="http://www.fcayh.cn/2021/01/19/tencounter/"/>
    <id>http://www.fcayh.cn/2021/01/19/tencounter/</id>
    <published>2021-01-19T13:07:21.000Z</published>
    <updated>2022-04-12T13:40:21.377Z</updated>
    
    <content type="html"><![CDATA[<h1 id="十进制可逆计数器"><a href="#十进制可逆计数器" class="headerlink" title="十进制可逆计数器"></a>十进制可逆计数器</h1><h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>这次试验要做的是，通过在试验箱中选取$4$个$LED$灯，亮则为$1$，灭则为$0$，拼成一个4位二进制数。并且要实现从$0\ -\ 9$的顺序变换，即从$0000$到$1001$。</p><p>不过老师的要求还要多一点，要支持清零，置数，倒转三个功能。清零即当清零按键按下时，$LED$灯的状态变为$0000$。置数即还需额外选择$4$个按键，闭合为$1$，断开为$0$，当置数按键按下时，将$LED$灯的状态变为$4$个按键组成的状态。倒转即当倒转按键按下时，状态从$0000$到$1001$，而断开时为$1001$到$0000$。</p><p>还有一点，当从$1001$跳变到$0000$时要输出借位信号为$1$。</p><h2 id="过程"><a href="#过程" class="headerlink" title="过程"></a>过程</h2><p>其实和上一个实验是类似的，定义一个$4$位二进制数$Q$，通过试验箱上的$CLK$器件产生时间脉冲，每当接受到一次脉冲$Q=Q+1$，当$Q$为$1001$时，就要变成$0000$。</p><p>而清零，置数，倒转，均通过$if/else$语句实现，写法较为简单，可以直接看代码。</p><h2 id="代码"><a href="#代码" class="headerlink" title="代码"></a>代码</h2><figure class="highlight verilog"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">module</span> gal_3035_3(ld,clk,d,ud,clr,Q,co); </span><br><span class="line">    <span class="keyword">input</span> ld,ud,clr,clk; </span><br><span class="line">    <span class="keyword">input</span> [<span class="number">3</span>:<span class="number">0</span>]d; </span><br><span class="line">    <span class="keyword">output</span> <span class="keyword">reg</span> [<span class="number">3</span>:<span class="number">0</span>]Q; </span><br><span class="line">    <span class="keyword">output</span> <span class="keyword">reg</span> co; </span><br><span class="line">    </span><br><span class="line">    <span class="keyword">always</span>@(<span class="keyword">posedge</span> clk) </span><br><span class="line">    <span class="keyword">begin</span> </span><br><span class="line">        <span class="keyword">if</span>(!clr) <span class="comment">// clr为0时，不进行清零操作</span></span><br><span class="line">            <span class="keyword">begin</span> </span><br><span class="line">                <span class="keyword">if</span>(ld) <span class="comment">// ld为1时，进行置数操作 </span></span><br><span class="line">                    <span class="keyword">begin</span> </span><br><span class="line">                        Q[<span class="number">3</span>]=d[<span class="number">3</span>]; </span><br><span class="line">                        Q[<span class="number">2</span>]=d[<span class="number">2</span>]; </span><br><span class="line">                        Q[<span class="number">1</span>]=d[<span class="number">1</span>]; </span><br><span class="line">                        Q[<span class="number">0</span>]=d[<span class="number">0</span>]; </span><br><span class="line">                    <span class="keyword">end</span> </span><br><span class="line">                <span class="keyword">else</span> </span><br><span class="line">                    <span class="keyword">begin</span> </span><br><span class="line">                        <span class="keyword">if</span>(ud) <span class="comment">//ud是1时，倒序置数 </span></span><br><span class="line">                            <span class="keyword">begin</span> </span><br><span class="line">                                <span class="keyword">if</span>(Q==<span class="number">4&#x27;b0000</span>) </span><br><span class="line">                                    <span class="keyword">begin</span> </span><br><span class="line">                                        Q&lt;=<span class="number">4&#x27;b1001</span>; </span><br><span class="line">                                        co&lt;=<span class="number">1</span>; </span><br><span class="line">                                    <span class="keyword">end</span> </span><br><span class="line">                                <span class="keyword">else</span> </span><br><span class="line">                                    <span class="keyword">begin</span> </span><br><span class="line">                                        Q&lt;=Q-<span class="number">1</span>;</span><br><span class="line">                                        co&lt;=<span class="number">0</span>;</span><br><span class="line">                                    <span class="keyword">end</span> </span><br><span class="line">                            <span class="keyword">end</span> </span><br><span class="line">                        <span class="keyword">else</span> <span class="comment">// ud是0时，顺序置数 </span></span><br><span class="line">                            <span class="keyword">begin</span> </span><br><span class="line">                                <span class="keyword">if</span>(Q==<span class="number">4&#x27;b1001</span>) </span><br><span class="line">                                    <span class="keyword">begin</span> </span><br><span class="line">                                        Q&lt;=<span class="number">4&#x27;b0000</span>; </span><br><span class="line">                                        co&lt;=<span class="number">1</span>; </span><br><span class="line">                                    <span class="keyword">end</span> </span><br><span class="line">                                <span class="keyword">else</span> </span><br><span class="line">                                    <span class="keyword">begin</span> </span><br><span class="line">                                        Q&lt;=Q+<span class="number">1</span>;</span><br><span class="line">                                        co&lt;=<span class="number">0</span>;</span><br><span class="line">                                    <span class="keyword">end</span> </span><br><span class="line">                            <span class="keyword">end</span> </span><br><span class="line">                    <span class="keyword">end</span> </span><br><span class="line">            <span class="keyword">end</span>  </span><br><span class="line">        <span class="keyword">else</span> </span><br><span class="line">            <span class="keyword">begin</span> </span><br><span class="line">                Q&lt;=<span class="number">4&#x27;b0000</span>; </span><br><span class="line">            <span class="keyword">end</span></span><br><span class="line">    <span class="keyword">end</span> </span><br><span class="line"><span class="keyword">endmodule</span></span><br></pre></td></tr></table></figure><h2 id="波形仿真"><a href="#波形仿真" class="headerlink" title="波形仿真"></a>波形仿真</h2><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images@f0e2b20527975ea6350ab12119a9d96efdf0f23e/2021/01/19/53b56dd31322b5e1f68c3d4da9787813.png"></p><h2 id="引脚分配"><a href="#引脚分配" class="headerlink" title="引脚分配"></a>引脚分配</h2><p>$Family:Cyclone\ IV\ E\ \ \ \ \ \ \ \ Device:EP4CE22E22C8$</p><div class="table-container"><table><thead><tr><th>信号名</th><th>主板器件</th><th>PIN</th></tr></thead><tbody><tr><td>Q[3]</td><td>LED3</td><td>54</td></tr><tr><td>Q[2]</td><td>LED2</td><td>52</td></tr><tr><td>Q[1]</td><td>LED1</td><td>50</td></tr><tr><td>Q[0]</td><td>LED0</td><td>46</td></tr><tr><td>clr</td><td>KEY1</td><td>31</td></tr><tr><td>co</td><td>LED4</td><td>58</td></tr><tr><td>clk</td><td>CLK0</td><td>88</td></tr><tr><td>d[3]</td><td>KEY5</td><td>42</td></tr><tr><td>d[2]</td><td>KEY4</td><td>32</td></tr><tr><td>d[1]</td><td>KEY3</td><td>33</td></tr><tr><td>d[0]</td><td>KEY2</td><td>30</td></tr><tr><td>ld</td><td>KEY6</td><td>39</td></tr><tr><td>ud</td><td>KEY7</td><td>44</td></tr></tbody></table></div>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;十进制可逆计数器&quot;&gt;&lt;a href=&quot;#十进制可逆计数器&quot; class=&quot;headerlink&quot; title=&quot;十进制可逆计数器&quot;&gt;&lt;/a&gt;十进制可逆计数器&lt;/h1&gt;&lt;h2 id=&quot;简介&quot;&gt;&lt;a href=&quot;#简介&quot; class=&quot;headerlink&quot; titl</summary>
      
    
    
    
    <category term="作业-考试" scheme="http://www.fcayh.cn/categories/%E4%BD%9C%E4%B8%9A-%E8%80%83%E8%AF%95/"/>
    
    
    <category term="数字电子技术实验" scheme="http://www.fcayh.cn/tags/%E6%95%B0%E5%AD%97%E7%94%B5%E5%AD%90%E6%8A%80%E6%9C%AF%E5%AE%9E%E9%AA%8C/"/>
    
  </entry>
  
  <entry>
    <title>彩灯控制器</title>
    <link href="http://www.fcayh.cn/2021/01/14/colorcontrol/"/>
    <id>http://www.fcayh.cn/2021/01/14/colorcontrol/</id>
    <published>2021-01-14T14:42:53.000Z</published>
    <updated>2023-02-25T15:34:15.085Z</updated>
    
    <content type="html"><![CDATA[<h1 id="彩灯控制器"><a href="#彩灯控制器" class="headerlink" title="彩灯控制器"></a>彩灯控制器</h1><h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>​上次是我们通过设置几个开关，用他们的$0/1$状态表示一个数，然后$BCD$译码管就显示相应的数字。而这次的内容则是，让这个$”8”$的外面一圈$a,b,c,d,e,f$这$6$个灯管轮流亮。</p><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images//2021/01/14/6c9e6abd0c2d72c4cf09cd6f835d7bdd.png" alt="数码管" style="zoom:50%"/></p><p>​哦对了，这次还要求要层次化，需要写个顶层文件。</p><h2 id="过程"><a href="#过程" class="headerlink" title="过程"></a>过程</h2><p>​如何让译码管特定的灯管点亮我们已经在上次实验中学会了，那么如何控制译码管的灯循环轮流依次去亮呢？这里我们引入一个变量$Q$，并且使用实验箱中$CLK$器件，它会产生可调的时间脉冲，每当接收到一次脉冲，$Q+1$，然后根据$Q$的值去给$codeout$分配每一位的值，就可以控制译码管显示的字了。</p><h2 id="代码"><a href="#代码" class="headerlink" title="代码"></a>代码</h2><p>顶层文件 $gal\underline{}3035\underline{}2.v$</p><figure class="highlight verilog"><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="keyword">module</span> gal_3035_2(clk,en,Q,codeout); <span class="comment">//彩灯控制器</span></span><br><span class="line"><span class="keyword">input</span> clk,en;</span><br><span class="line"><span class="keyword">output</span> [<span class="number">2</span>:<span class="number">0</span>] Q;        <span class="comment">//计数器输出</span></span><br><span class="line"><span class="keyword">output</span> [<span class="number">6</span>:<span class="number">0</span>] codeout;  <span class="comment">//译码器输出</span></span><br><span class="line">gal_3035_2_1 a(clk,en,Q);  <span class="comment">//调用计数器子模块</span></span><br><span class="line">gal_3035_2_2 b(Q,codeout); <span class="comment">//调用译码器子模块</span></span><br><span class="line"><span class="keyword">endmodule</span></span><br></pre></td></tr></table></figure><p>计数器模块 $gal\underline{}3035\underline{}2\underline{}1.v$</p><figure class="highlight verilog"><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="keyword">module</span> gal_3035_2_1(clk,en,Q);<span class="comment">//计数器</span></span><br><span class="line"><span class="keyword">input</span> clk,en;       <span class="comment">//输入时钟和使能</span></span><br><span class="line"><span class="keyword">output</span> <span class="keyword">reg</span>[<span class="number">2</span>:<span class="number">0</span>] Q;  <span class="comment">//计数器输出</span></span><br><span class="line"><span class="keyword">always</span>@(<span class="keyword">posedge</span> clk)<span class="comment">//时钟上升沿执行</span></span><br><span class="line"><span class="keyword">begin</span></span><br><span class="line">    <span class="keyword">if</span>(en == <span class="number">1&#x27;b1</span>)  <span class="comment">//使能端处于高电平自加</span></span><br><span class="line">    <span class="keyword">begin</span></span><br><span class="line">        <span class="keyword">if</span>(Q &lt; <span class="number">3&#x27;d6</span>)</span><br><span class="line">        Q &lt;= Q + <span class="number">1&#x27;b1</span>;</span><br><span class="line">        <span class="keyword">else</span></span><br><span class="line">        Q &lt;= <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">end</span></span><br><span class="line">        <span class="keyword">else</span></span><br><span class="line">        Q &lt;= Q;  </span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"><span class="keyword">endmodule</span></span><br></pre></td></tr></table></figure><p>译码器模块$gal\underline{}3035\underline{}2\underline{}2.v$</p><figure class="highlight verilog"><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="keyword">module</span> gal_3035_2_2(Q,codeout); <span class="comment">//译码器</span></span><br><span class="line"><span class="keyword">input</span> [<span class="number">2</span>:<span class="number">0</span>]Q;  <span class="comment">//译码器输入接收计数器输出</span></span><br><span class="line"><span class="keyword">output</span> <span class="keyword">reg</span>[<span class="number">6</span>:<span class="number">0</span>]codeout; <span class="comment">//译码器输出</span></span><br><span class="line"><span class="keyword">always</span>@(Q)</span><br><span class="line"><span class="keyword">begin</span></span><br><span class="line">    <span class="keyword">case</span>(Q)</span><br><span class="line">    <span class="number">3&#x27;d0</span>:codeout = <span class="number">7&#x27;b0000001</span>;    <span class="comment">//Q为0    点亮a管</span></span><br><span class="line">    <span class="number">3&#x27;d1</span>:codeout = <span class="number">7&#x27;b0000010</span>;    <span class="comment">//Q为1    点亮b管</span></span><br><span class="line">    <span class="number">3&#x27;d2</span>:codeout = <span class="number">7&#x27;b0000100</span>;    <span class="comment">//Q为2    点亮c管</span></span><br><span class="line">    <span class="number">3&#x27;d3</span>:codeout = <span class="number">7&#x27;b0001000</span>;    <span class="comment">//Q为3    点亮d管</span></span><br><span class="line">    <span class="number">3&#x27;d4</span>:codeout = <span class="number">7&#x27;b0010000</span>;    <span class="comment">//Q为4    点亮e管</span></span><br><span class="line">    <span class="number">3&#x27;d5</span>:codeout = <span class="number">7&#x27;b0100000</span>;    <span class="comment">//Q为5    点亮f管</span></span><br><span class="line">    <span class="keyword">default</span>:codeout = <span class="number">7&#x27;b0000000</span>;</span><br><span class="line">    <span class="keyword">endcase</span></span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"><span class="keyword">endmodule</span></span><br></pre></td></tr></table></figure><h2 id="波形仿真"><a href="#波形仿真" class="headerlink" title="波形仿真"></a>波形仿真</h2><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images@fc15f97bef27bd63e5d9371093f9a7ebf26c3e80/2021/01/14/d95ba865cc91ff8caab3d222bea9133c.png"></p><h2 id="引脚分配"><a href="#引脚分配" class="headerlink" title="引脚分配"></a>引脚分配</h2><p>$Family:Cyclone\ IV\ E\ \ \ \ \ \ \ \ Device:EP4CE22E22C8$</p><div class="table-container"><table><thead><tr><th>信号名</th><th>主板器件</th><th>PIN</th></tr></thead><tbody><tr><td>Q[2]</td><td>LED2</td><td>52</td></tr><tr><td>Q[1]</td><td>LED1</td><td>50</td></tr><tr><td>Q[0]</td><td>LED0</td><td>46</td></tr><tr><td>clk</td><td>CLK0</td><td>88</td></tr><tr><td>codeout[6]</td><td>a</td><td>112</td></tr><tr><td>codeout[5]</td><td>b</td><td>100</td></tr><tr><td>codeout[4]</td><td>c</td><td>104</td></tr><tr><td>codeout[3]</td><td>d</td><td>111</td></tr><tr><td>codeout[2]</td><td>e</td><td>106</td></tr><tr><td>codeout[1]</td><td>f</td><td>110</td></tr><tr><td>codeout[0]</td><td>g</td><td>103</td></tr></tbody></table></div>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;彩灯控制器&quot;&gt;&lt;a href=&quot;#彩灯控制器&quot; class=&quot;headerlink&quot; title=&quot;彩灯控制器&quot;&gt;&lt;/a&gt;彩灯控制器&lt;/h1&gt;&lt;h2 id=&quot;简介&quot;&gt;&lt;a href=&quot;#简介&quot; class=&quot;headerlink&quot; title=&quot;简介&quot;&gt;&lt;/a&gt;简</summary>
      
    
    
    
    <category term="作业-考试" scheme="http://www.fcayh.cn/categories/%E4%BD%9C%E4%B8%9A-%E8%80%83%E8%AF%95/"/>
    
    
    <category term="数字电子技术实验" scheme="http://www.fcayh.cn/tags/%E6%95%B0%E5%AD%97%E7%94%B5%E5%AD%90%E6%8A%80%E6%9C%AF%E5%AE%9E%E9%AA%8C/"/>
    
  </entry>
  
  <entry>
    <title>BCD-7段译码器</title>
    <link href="http://www.fcayh.cn/2021/01/14/BCDdecoder/"/>
    <id>http://www.fcayh.cn/2021/01/14/BCDdecoder/</id>
    <published>2021-01-14T06:07:12.000Z</published>
    <updated>2023-02-25T15:17:38.404Z</updated>
    
    <content type="html"><![CDATA[<h1 id="BCD-7段译码器"><a href="#BCD-7段译码器" class="headerlink" title="BCD-7段译码器"></a>BCD-7段译码器</h1><h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>​这是整个学期第一次数电实验，我完全不知道要做什么其实，整个人都是很懵的境界，虽然这次的这个实验很简单，但是不妨碍我不会啊，从来没有用过 $QuartusII$ 软件，也从来没有接触过$verilogHDL$语言，老师课前大概讲了20分钟，演示了一下如果操作，就让我们各自回到自己的机位上进行试验了。</p><p>​而这次实验到底要做个什么？</p><p>​这是我们学校提供的试验箱：</p><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images//2021/01/14/2bf18991f3b5f7b97186aa3d80cf1e0f.png" alt="试验箱" style="zoom:30%"/></p><p>​我们要做的就是让左上角那$8$个$”8”$，轮流从$0-9$显示 。</p><h2 id="过程"><a href="#过程" class="headerlink" title="过程"></a>过程</h2><p>​每一个$”8”$都是由$7$个灯管组成，他们的编号如下：</p><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images//2021/01/14/6c9e6abd0c2d72c4cf09cd6f835d7bdd.png" alt="数码管" style="zoom:50%"/></p><p>​如果我们用$0$表示灯不亮，$1$表示灯亮，那么我们可以用一个7位二进制数$codeout$来表示一个$”8”$表示的数。</p><p>例如 $6$ 可以表示为 $codeout = 7’b1011111;$ 即除$b$以外，其他6个灯均亮。</p><h2 id="代码"><a href="#代码" class="headerlink" title="代码"></a>代码</h2><figure class="highlight verilog"><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="keyword">module</span> gal_3035_1 (codeout,indec);</span><br><span class="line">    <span class="keyword">input</span>[<span class="number">3</span>: <span class="number">0</span>] indec;</span><br><span class="line">    <span class="keyword">output</span> <span class="keyword">reg</span>[<span class="number">6</span>: <span class="number">0</span>] codeout;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">always</span> @ (indec)</span><br><span class="line">    <span class="keyword">begin</span></span><br><span class="line">        <span class="keyword">case</span> (indec)</span><br><span class="line">        <span class="number">4&#x27;d0</span>: codeout=<span class="number">7&#x27;b1111110</span>;</span><br><span class="line">        <span class="number">4&#x27;d1</span>: codeout=<span class="number">7&#x27;b0110000</span>;</span><br><span class="line">        <span class="number">4&#x27;d2</span>: codeout=<span class="number">7&#x27;b1101101</span>;</span><br><span class="line">        <span class="number">4&#x27;d3</span>: codeout=<span class="number">7&#x27;b1111001</span>;</span><br><span class="line">        <span class="number">4&#x27;d4</span>: codeout=<span class="number">7&#x27;b0110011</span>;</span><br><span class="line">        <span class="number">4&#x27;d5</span>: codeout=<span class="number">7&#x27;b1011011</span>;</span><br><span class="line">        <span class="number">4&#x27;d6</span>: codeout=<span class="number">7&#x27;b1011111</span>;</span><br><span class="line">        <span class="number">4&#x27;d7</span>: codeout=<span class="number">7&#x27;b1110000</span>;</span><br><span class="line">        <span class="number">4&#x27;d8</span>: codeout=<span class="number">7&#x27;b1111111</span>;</span><br><span class="line">        <span class="number">4&#x27;d9</span>: codeout=<span class="number">7&#x27;b1111011</span>;</span><br><span class="line">        <span class="keyword">default</span>: codeout=<span class="number">7&#x27;dx</span>;</span><br><span class="line">        <span class="keyword">endcase</span></span><br><span class="line">    <span class="keyword">end</span></span><br><span class="line"><span class="keyword">endmodule</span></span><br></pre></td></tr></table></figure><h2 id="波形仿真"><a href="#波形仿真" class="headerlink" title="波形仿真"></a>波形仿真</h2><p><img src="https://cdn.jsdelivr.net/gh/FcAYH/Images//2021/01/14/6abeb72e68badfb740ffb5e4bcdb61ca.png" alt="波形仿真"></p><h2 id="引脚分配"><a href="#引脚分配" class="headerlink" title="引脚分配"></a>引脚分配</h2><p>$Family:Cyclone\ IV\ E\ \ \ \ \ \ \ \ Device:EP4CE22E22C8$</p><div class="table-container"><table><thead><tr><th>信号名</th><th>主板器件</th><th>PIN</th></tr></thead><tbody><tr><td>codeout[6]</td><td>a</td><td>112</td></tr><tr><td>codeout[5]</td><td>b</td><td>100</td></tr><tr><td>codeout[4]</td><td>c</td><td>104</td></tr><tr><td>codeout[3]</td><td>d</td><td>111</td></tr><tr><td>codeout[2]</td><td>e</td><td>106</td></tr><tr><td>codeout[1]</td><td>f</td><td>110</td></tr><tr><td>codeout[0]</td><td>g</td><td>103</td></tr><tr><td>indec[3]</td><td>KEY4</td><td>32</td></tr><tr><td>indec[2]</td><td>KEY3</td><td>33</td></tr><tr><td>indec[1]</td><td>KEY2</td><td>30</td></tr><tr><td>indec[0]</td><td>KEY1</td><td>31</td></tr></tbody></table></div>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;BCD-7段译码器&quot;&gt;&lt;a href=&quot;#BCD-7段译码器&quot; class=&quot;headerlink&quot; title=&quot;BCD-7段译码器&quot;&gt;&lt;/a&gt;BCD-7段译码器&lt;/h1&gt;&lt;h2 id=&quot;简介&quot;&gt;&lt;a href=&quot;#简介&quot; class=&quot;headerlink&quot; </summary>
      
    
    
    
    <category term="作业-考试" scheme="http://www.fcayh.cn/categories/%E4%BD%9C%E4%B8%9A-%E8%80%83%E8%AF%95/"/>
    
    
    <category term="数字电子技术实验" scheme="http://www.fcayh.cn/tags/%E6%95%B0%E5%AD%97%E7%94%B5%E5%AD%90%E6%8A%80%E6%9C%AF%E5%AE%9E%E9%AA%8C/"/>
    
  </entry>
  
  <entry>
    <title>POI2019 Pomniejszenie</title>
    <link href="http://www.fcayh.cn/2020/12/09/Pomniejszenie/"/>
    <id>http://www.fcayh.cn/2020/12/09/Pomniejszenie/</id>
    <published>2020-12-09T15:18:22.000Z</published>
    <updated>2023-02-25T15:17:05.420Z</updated>
    
    <content type="html"><![CDATA[<h1 id="POI2019-Pomniejszenie"><a href="#POI2019-Pomniejszenie" class="headerlink" title="POI2019 Pomniejszenie"></a>POI2019 Pomniejszenie</h1><p><strong>题目大意：</strong>给两个数$A，B$。 要求在$A$里面恰好选$k$位，改变它们的值，让$A$小于$B$ 且$A$最大。</p><p>先复习一下，两个$n$位的数$A,B$，如何比较它们的大小？</p><p>很简单，如果$A,B$的前$i-1$位都一模一样，但是$A$的第$i$位大于$B$的第$i$位那么后边不用看了，$A$必然大于$B$ 。</p><p>好了回来继续看这个题，那么我们要让$A$小于$B$，就要选择一个目标位$Tar$，使得$A$的前$Tar$位都和$B$的前$Tar$位相同，而$A$的$Tar$位恰好等于$B$的$Tar$位$-1$。</p><p>现在$A$小于$B$在思想上已经搞定了，我们需要考虑还要让$A$最大的事情了，这个事情也很简单，剩下所有位都是$9$的话不就可以了么。</p><p>以上两段就是本题最关键的贪心思想，现在我们来开始做这个题。</p><p>首先是找这个$Tar$。 $Tar$怎么找？可以一位一位枚举，看看这一位可以不可做这个$Tar$，而且为了让$A$尽可能大，这个$Tar$我们要让它尽量靠后。（例子：$A:234$ ; $B:547$ 例子里我们先不考虑k啊; 因为第$Tar$位是决定$A$和$B$大小的一位，如果$Tar=1$，那么按照我们的贪心，$A=499$, 而$Tar=2$时，$A=539$, 当$Tar=3$时，$A=546$，很容易看出来$Tar$越大，得到的$A$越大）</p><p>但是因为有k的限制，我们的$Tar$也不能太大了，至少说当我们想要取第$i$位做$Tar$时，得保证前$i$位$A,B$不同的位数+$i$后面还剩下的位数得大于等于$k$。</p><p>现在我们有疑问了，首先是我前面一直在说“前$i$位$A,B$的不同的位数”，但是我们还没算第$Tar$位呢，第$Tar$位如果说$A$大于$B$，那么我们的操作是让$A_{Tar}=B_{Tar}-1$吧？其实不一定，如果$B_{Tar}=0$怎么办？  其次是我前面说 “剩下所有位都是$9$的话不就可以了么。” ，很显然啊，如果原来$A$的后面几位中就有 $’9’$怎么办啊。</p><p>现在我们一个个解决疑问，首先如果$B_{i}=0$ 那么这一位不可能成为$Tar$，我们在枚举的时候要跳过去，(要是还需要问这个的原因的话：因为如果$A,B$前$Tar$位相同，而$B_{Tar}=0$那么不管$A_{Tar}$等于几，都不能让$A &lt; B$，和我们最开始给$Tar$的定义就矛盾了)</p><p>其次如果$A$的后几位中本来就有$’9’$那么当我们把$A$的$Tar$位后面的不是$9$的位都变成$9$之后，如果这个时候变换次数仍然不够$k$次，我们应该从后往前把$A$中原来就是$9$的位变成$8$（如果还需要问为什么这样最优：因为在前$Tar$位都确定了，并且$Tar$后面的位都是$9$的前提下，假如，我们还需要变$x$个数，然后呢，$A$数组的$Tar$位之后有$y$个本来就是$9$的位, $x\le y$ ,那么，为了把这$x$个操作做完，我们必须得拿这些原来就是$9$的位开刀了，而且一过脑子就能想出来，肯定是从后往前把那些本来就是$9$的位变成$8$）</p><p>我感觉我写的非常啰嗦，主要是为了能让更多的人看明白。（<del>其实是我语文太差了</del>）</p><p>上面一堆废话只为了介绍思想，现在我们把所有东西串起来，说一下写法。</p><p>首先读入数据，用$len$表示数的长度，然后从$1$到$len$枚举每一位，当枚举到第$i$位时，如果$B_{i}=0$直接跳过，否则来判断这个$i$能不能当$Tar$，最后枚举完了，我们也找到了最优的$Tar$。现在我们要开始构造答案了，第一步是$Tar$以前的每一位$A_{i}=B_{i}$，第二步是决定第$Tar$位怎么变，如果$A_{Tar}\ge B_{Tar}$ 或者$A_{Tar}&lt; B_{Tar}-1$，那么$A_{Tar}= B_{Tar}-1$ ； 如果$A_{Tar}=B_{Tar}-1$ 那就先不动它了。 第三步是把$Tar$以后的不等于$9$的位都变成$9$，第四步是把原来就是$9$的从后往前变成$8$，第五步是，如果在第三步中 “那就先不动它了” 这样的情况发生的话，有可能这个时候我们的变换次数离$k$还差$1$，这个时候我们最优的操作就是 “动它” 把$A_{Tar}$减去$1$;（注意：以上五步，第一步是必走的，在$A_{Tar}\ge B_{Tar}$的情况下 ，第二步必走，其他几步，都是要看我们当前的操作数，有没有到$k$，不到的话，再操作。）</p><p>到这里可能还有一个疑问，就是为啥这样子，到最后能保证恰好换$k$次？ 因为如果换不到$k$次，我们会第二步，第三步，一直进行下去。 那为啥不会出现第五步走完还不够$k$次？ 前面写了$-&gt;$“我们的$Tar$也不能太大了，至少说当我们想要取第$i$位做$Tar$时，得保证前$i$位$A,B$不同的位数+$i$后面还剩下的位数得大于等于$k$。”不过这句话不够严谨，应该分三部分，前$i-1$位中不同的位数（$cnt1$），$i$位之后剩余的位数($cnt2$)，第i位（$1$）, $cnt1+cnt2+1\ge k$ 这样子，如果$cnt1+cnt2+1&gt;k$ 即$cnt1+cnt2\ge k$ 那么就算第二步得到时候我们 “那就先不动它了” 也最多进行到第四步结束。如果$cnt1+cnt2+1=k$ 那么如果第二步我们 “那就先不动它了” 就会出现，当把第四步做完时，$cnt1,cnt2$都做了，还差$1$，就要做第五步了。</p><p>参考代码： 仅供参考，因为菜菜的我改了好几遍才过，所以代码中赘余的成分比较多，比如一个if就行的整了好几个if，一个变量就够的用了好几个变量23333.</p><p>而且大概是我自带大常数，O(n)的算法，我写出来就是900ms险过，我同学就是400ms稳过，qwq哭了///</p><hr><figure class="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><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span><span class="meta-string">&lt;bits/stdc++.h&gt;</span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> <span class="built_in">std</span>;</span><br><span class="line"><span class="keyword">typedef</span> <span class="keyword">long</span> <span class="keyword">long</span> ll;</span><br><span class="line"><span class="keyword">const</span> <span class="keyword">int</span> Maxn=<span class="number">100010</span>;</span><br><span class="line"><span class="keyword">int</span> t,k;</span><br><span class="line"><span class="keyword">char</span> A[Maxn],B[Maxn];</span><br><span class="line"><span class="keyword">int</span> a[Maxn],b[Maxn]; <span class="comment">//用a,b分别存A,B</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Solve</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">scanf</span>(<span class="string">&quot;%d&quot;</span>,&amp;t);</span><br><span class="line">    <span class="keyword">while</span>(t--)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="built_in">scanf</span>(<span class="string">&quot;%s%s%d&quot;</span>,A+<span class="number">1</span>,B+<span class="number">1</span>,&amp;k);</span><br><span class="line">        <span class="keyword">int</span> len=<span class="built_in">strlen</span>(A+<span class="number">1</span>);</span><br><span class="line">        <span class="keyword">for</span>(<span class="keyword">int</span> i=<span class="number">1</span>;i&lt;=len;i++) a[i]=A[i]-<span class="string">&#x27;0&#x27;</span>,b[i]=B[i]-<span class="string">&#x27;0&#x27;</span>;</span><br><span class="line">        <span class="keyword">int</span> cnt=<span class="number">0</span>,Tar=<span class="number">-1</span>;</span><br><span class="line">        <span class="comment">//cnt是用来记录前i位不同的位数，而nowc呢，我这里写的比较冗长，其实并没啥用。</span></span><br><span class="line">        <span class="keyword">for</span>(<span class="keyword">int</span> i=<span class="number">1</span>;i&lt;=len;i++)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">if</span>(b[i])</span><br><span class="line">            &#123;</span><br><span class="line">                <span class="keyword">int</span> nowc=<span class="number">0</span>;</span><br><span class="line">                <span class="keyword">if</span>(a[i]&gt;=b[i])</span><br><span class="line">                &#123;</span><br><span class="line">                    nowc=cnt+<span class="number">1</span>;</span><br><span class="line">                    <span class="keyword">if</span>(nowc&lt;=k&amp;&amp;nowc+len-i&gt;=k) Tar=i;</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">else</span> </span><br><span class="line">                &#123;</span><br><span class="line">                    <span class="keyword">if</span>(b[i]&gt;<span class="number">1</span>)</span><br><span class="line">                    &#123;</span><br><span class="line">                        nowc=cnt;</span><br><span class="line">                        <span class="keyword">if</span>(nowc&lt;=k&amp;&amp;nowc+len-i+<span class="number">1</span>&gt;=k) Tar=i;</span><br><span class="line">                    &#125;</span><br><span class="line">                    <span class="keyword">else</span></span><br><span class="line">                    &#123;</span><br><span class="line">                        nowc=cnt;</span><br><span class="line">                        <span class="keyword">if</span>(nowc&lt;=k&amp;&amp;nowc+len-i&gt;=k) Tar=i;</span><br><span class="line">                    &#125;</span><br><span class="line">                    </span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span>(a[i]!=b[i]) cnt++;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span>(Tar==<span class="number">-1</span>)&#123;<span class="built_in">printf</span>(<span class="string">&quot;-1\n&quot;</span>); <span class="keyword">continue</span> ;&#125;</span><br><span class="line">        </span><br><span class="line">        cnt=<span class="number">0</span>;</span><br><span class="line">        <span class="keyword">for</span>(<span class="keyword">int</span> i=<span class="number">1</span>;i&lt;=Tar<span class="number">-1</span>;i++) <span class="keyword">if</span>(a[i]!=b[i]) cnt++,a[i]=b[i];</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span>(a[Tar]&gt;=b[Tar]) cnt++,a[Tar]=b[Tar]<span class="number">-1</span>;</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span>(cnt&lt;k&amp;&amp;a[Tar]!=b[Tar]<span class="number">-1</span>) cnt++,a[Tar]=b[Tar]<span class="number">-1</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span>(cnt&lt;k)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">for</span>(<span class="keyword">int</span> i=Tar+<span class="number">1</span>;i&lt;=len;i++) <span class="keyword">if</span>(A[i]!=<span class="string">&#x27;9&#x27;</span>&amp;&amp;cnt&lt;k) cnt++,a[i]=<span class="number">9</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span>(cnt&lt;k)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">for</span>(<span class="keyword">int</span> i=len;i&gt;=Tar+<span class="number">1</span>;i--) <span class="keyword">if</span>(A[i]==<span class="string">&#x27;9&#x27;</span>&amp;&amp;cnt&lt;k) cnt++,a[i]=<span class="number">8</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span>(cnt==k<span class="number">-1</span>) cnt++,a[Tar]--;</span><br><span class="line">        <span class="keyword">for</span>(<span class="keyword">int</span> i=<span class="number">1</span>;i&lt;=len;i++) <span class="built_in">printf</span>(<span class="string">&quot;%d&quot;</span>,a[i]); <span class="built_in">puts</span>(<span class="string">&quot;&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    Solve();</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>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;POI2019-Pomniejszenie&quot;&gt;&lt;a href=&quot;#POI2019-Pomniejszenie&quot; class=&quot;headerlink&quot; title=&quot;POI2019 Pomniejszenie&quot;&gt;&lt;/a&gt;POI2019 Pomniejszenie&lt;/</summary>
      
    
    
    
    <category term="算法" scheme="http://www.fcayh.cn/categories/%E7%AE%97%E6%B3%95/"/>
    
    
    <category term="贪心" scheme="http://www.fcayh.cn/tags/%E8%B4%AA%E5%BF%83/"/>
    
  </entry>
  
</feed>
