<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Non-existent World</title>
  
  
  <link href="https://nano.ac/atom.xml" rel="self"/>
  
  <link href="https://nano.ac/"/>
  <updated>2025-06-19T02:33:56.905Z</updated>
  <id>https://nano.ac/</id>
  
  <author>
    <name>Konano</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>30°N 120°E</title>
    <link href="https://nano.ac/posts/63380003/"/>
    <id>https://nano.ac/posts/63380003/</id>
    <published>2025-04-10T05:17:25.000Z</published>
    <updated>2025-06-19T02:33:56.905Z</updated>
    
    <content type="html"><![CDATA[<html><head></head><body><h2 id="缘起"><a href="#缘起" class="headerlink" title="缘起"></a>缘起</h2><p>事情的一切都还要从一个叫 Null island 的地方说起。</p><p>Null island，也就是坐标 (0,0) 之处，在西非附近的大西洋里，远离非洲大陆，只能坐船到达。第一次注意到它是因为玩 Ingress 时，如果系统无法定位准确的经纬度位置，游戏就会将坐标定位在 (0,0) 这个海洋无人区内。虽然是无人区，但根据百科资料显示，这个地方似乎有一个名为 <a href="https://www.ndbc.noaa.gov/station_page.php?station=13010">Soul</a> 的气象浮标。</p><p>有一天我突发奇想，如果想要到达那里需要做什么样的准备，以及是否有人已经计划过并成功到达过这里？通过搜索，我发现了<a href="https://confluence.org/confluence.php?lat=0&amp;lon=0">四次真实到访记录</a>，并由此接触到「经纬度整数交点计划」（Degree Confluence Project，简称 DCP）。这个项目旨在到达全球每个整数经纬度交点，并记录该位置的实景。在中国大陆的 978 个交点中，目前仅有 429 个被探访过——大部分未被访问的交点都位于西部的荒漠深处。</p><p><img src="https://cache.nan.pub/imgs/e4a2cd321f16fd27fdcb0067391c9e2e0aa372b4948444501d789aad3fae181e.png" alt="DCP Project" loading="lazy" style="max-width: 80%">  </p><p>那么离我最近的交点在哪里呢？(N30,E120)。巧合的是，这个交点正好同时在经线和纬线的三分之一处！并且，某些地球仪上的经纬线是每隔 30 度标注一次，而在中国境内符合条件的只有两处经纬交汇点：一处位于西藏的北纬 30 度、东经 90 度坐标点，另一处就是这个北纬 30 度、东经 120 度坐标点。</p><p>虽然 DCP 网站上显示它并不是未访问状态，有人已经于 2005 年访问过这个地方，但从卫星图上能够看到它非常好到达，距离路边非常近，并且不在森林这种难以穿越的区域内，1km 范围内甚至就有一个高铁站！于是我计划着哪天自己有空去看看，毕竟上次有记录的访问可是 20 年前，那个时候交点的周围还都是农田和简陋的乡村民房，我很好奇 20 年后这个地方的周围都发生了什么样的变化。</p><p><img src="https://cache.nan.pub/imgs/2f3595d82c69f45f2aa3389d97652fd571ca2650fa8f7f7ce1750d634bce1172.png" alt="Satellite Map of 30°N 120°E" loading="lazy" style="max-width: 80%">  </p><h2 id="行动"><a href="#行动" class="headerlink" title="行动"></a>行动</h2><p>时间到了 2025 年 4 月 4 号星期五。这一天也是清明节，我选择在这一天开始我的第一次 confluence trip。</p><p>早上 09:41 分，我乘坐 G7357 次列车从杭州东火车站出发（插曲：由于从家出发得太晚，在检票口关闭前 5 秒才赶到），并于 10:03 分到达富阳火车站。这个火车站距离最终的目的地只有 1km 的距离。出站后，我又走了差不多 1.6km 的路，来到了一个分岔路口。出乎我意料的是，这个路口居然有一块「蝴蝶阁民居（经纬准交点）」的路牌，绷不住了，现在连导航都不用了。</p><p>穿过隧道后右转，10:43，我站在了 30°N 120°E 的交汇点上。</p><p>这个交点位于一条沥青小路上。我打开 GPS Status 应用，在路上不断小步挪动着位置，试图让屏幕显示的坐标变成一串 0。成功后，我把手机放在地上，并用另外一个手机拍摄了地上的手机屏幕。距离交汇点只有 0m，大功告成！</p><p><img src="https://cache.nan.pub/imgs/62f5a0c764ca93a0e63694a2c1acbb519c87d4ac28e746cc9eb7a9b846c093e3.jpg" alt="GPS" loading="lazy" style="max-width: 80%">  </p><p>完成最最最最主要的任务后，我开始四处探索。由于清明节假期的缘故，许多人从附近的登山处上山祭祖，所以还挺热闹的，不过平时可能没那么多人。附近还有一个路线图，展示附近的登山路线。</p><p>交汇点的西北方向有 14 户人家，门口的门牌上写着「亭山村蝴蝶阁」，每栋楼都四层楼高，且都有院子有车库（羡慕死了我也想要）周围与 2005 年照片里的农舍相比，恍如隔世。</p><p><img src="https://cache.nan.pub/imgs/f5d7fe56d2987a2a0a23667df9abf87e06c940b84844eef0cd5f7d47cbe45b5d.jpg" alt="New Households" loading="lazy" style="max-width: 80%">  </p><p>在经纬度交汇点附近有一个「阳平山」碑，立于辛巳（2001）年，坐标（N29.99983, E119.99996），距离交汇点大约 20m。碑记上貌似记载着孙钟的墓就在此处（孙钟，东汉末年吴郡富春『今浙江杭州富阳』人，孙武的后人，孙吴武烈皇帝孙坚的长辈）虽然我更倾向这是后人附会。</p><p><img src="https://cache.nan.pub/imgs/9eee270f35a6a7081ba021e95d78e2d723ad93eeecd8d034c24b892de10fe294.png" alt="Yangping Mountain Stele" loading="lazy" style="max-width: 80%">  </p><p>此前造访此地的记录者，曾由一位「knowledgeable local」指引下，参观过一块位于经纬度交汇点附近的石桩。根据之前拍摄的照片，我向山脚下一位临时担任森林防火员的老人询问，他很快确定了照片中的人物，并告诉我这个人就住在附近的 14 户人家中。凭借这一线索，我很快找到了那位向导。</p><p>时隔二十年，老人的外貌已有了很大变化。有意思的是，他和家人仍清楚记得当年曾有三位外国访客到访寻找汇合点的事。老人还向我指明了石桩的位置，我顺着他所指的方向，在坐标（N30.00006, E120.00077）处找到了这块标石。准确来说，这是一块三角点标石，属于永久性测量标志，由浙江省测绘局设立，用于标定测绘控制点的位置与高程。从周围环境看，该标石附近并无显著地理特征，推测其最初便是为标记（N30, E120）这一经纬度交汇点而设。不过由于早期定位误差，实际位置与理论交汇点存在 75 米左右的偏差。</p><p><img src="https://cache.nan.pub/imgs/d97227bdf6b07ad03bb139126a00af86e83654e9497c1da684894c953cffe04f.jpg" alt="Stone Market" loading="lazy" style="max-width: 80%">  </p><h2 id="余韵"><a href="#余韵" class="headerlink" title="余韵"></a>余韵</h2><p>中午 12 点，提交完 Ingress Portal 申请并藏好了一个<a href="https://coord.info/GCB5DCP">新的 Geocaching Cache</a> 后，我离开现场到马路边打车，前往鹳山公园附近享受我剩余的旅程。午饭选在了一家开了 17 年的老店「阿汤面馆」，点了份腰花面并加了份大肠（内脏爱好者狂喜），内脏洗得很干净，味道很赞！</p><p>饭后去鹳山公园欣赏春景，在富春江远眺。江边有不少垂钓者在钓鱼，甚至有个老爷爷同时操控十几根鱼竿，还用电动玩具船把鱼钩运到江面中心，不愧是钓鱼佬。在最南边的澄江亭附近，我还意外发现了一个水准点标石。这个标石上居然有个二维码可以扫，扫出来是这个标石的说明。</p><p><img src="https://cache.nan.pub/imgs/9704a6827e59af1aaa3ae28b94938a007cee4e4e45f10247bf3d4f1d9c41b068.jpg" alt="the Fuchun River" loading="lazy" style="max-width: 80%">  </p><p>傍晚打车到「桂花西路」地铁站，乘坐地铁六号线前往浙江省博物馆（之江馆区）参观，将这里作为当日旅程的终点站，结束一天的旅程 w</p><p>也许未来可以规划一下，访问一些没被访问过的经纬度交汇点？有时间再说了，打工的牛马是没有那么多时间的（</p><p>但，过程还挺有意思的 hhh</p><h2 id="CP-Visit-Details"><a href="#CP-Visit-Details" class="headerlink" title="CP Visit Details"></a>CP Visit Details</h2><ul><li>Time at the CP: 10:47</li><li>Minimal distance according to GPS: 0 m</li><li>Distance to the road: 0 m</li><li>Time to reach the CP from the road: 0 min</li><li>Measured height: 29.5 m</li><li>Topography: flat</li><li>Vegetation: none (asphalt road)</li><li>Weather: cloudy (forgot to record the temperature)</li><li>Details: <a href="https://confluence.org/confluence.php?visitid=23825">https://confluence.org/confluence.php?visitid=23825</a></li></ul></body></html>]]></content>
    
    
    <summary type="html">&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;/body&gt;&lt;/html&gt;</summary>
    
    
    
    <category term="Travel" scheme="https://nano.ac/categories/Travel/"/>
    
    
    <category term="Hangzhou" scheme="https://nano.ac/tags/Hangzhou/"/>
    
    <category term="DCP" scheme="https://nano.ac/tags/DCP/"/>
    
  </entry>
  
  <entry>
    <title>给博客添加 FancyBox 支持</title>
    <link href="https://nano.ac/posts/915559b/"/>
    <id>https://nano.ac/posts/915559b/</id>
    <published>2025-01-24T18:50:21.000Z</published>
    <updated>2025-06-19T02:33:56.907Z</updated>
    
    <content type="html"><![CDATA[<html><head></head><body><p>简单记录一下我是怎么给博客添加 <a href="https://fancyapps.com/fancybox/">FancyBox</a> 支持的。整件事的难度主要在于博客用的模板 fexo 已经好几年没更新了，自身不支持 FancyBox 扩展，所以只能自己加。</p><p>要把大象装进冰箱共分为三步：</p><p>第一步，在 <code>themes\&lt;theme_name&gt;\source\js</code> 目录下新建 <code>fancybox.js</code> 文件，内容如下：</p><figure class="highlight javascript"><table><tbody><tr><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 当 DOM 加载完成后初始化 FancyBox</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="built_in">document</span>.addEventListener(<span class="string">'DOMContentLoaded'</span>, <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line">    wrapImageWithFancyBox();</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 为博客文章中的图片添加 FancyBox 支持</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">wrapImageWithFancyBox</span>(<span class="params"></span>) </span>{</span><br><span class="line">    <span class="keyword">try</span> {</span><br><span class="line">        <span class="comment">// 只选择文章中的图片</span></span><br><span class="line">        <span class="keyword">const</span> images = <span class="built_in">document</span>.querySelectorAll(<span class="string">`article img`</span>);</span><br><span class="line">        <span class="comment">// 这里可以根据需要修改选择器，比如可以用 ":not()" 排除一些图片</span></span><br><span class="line"></span><br><span class="line">        <span class="built_in">console</span>.log(images);</span><br><span class="line"></span><br><span class="line">        images.forEach(<span class="function"><span class="keyword">function</span> (<span class="params">image</span>) </span>{</span><br><span class="line">            <span class="comment">// 获取图片标题</span></span><br><span class="line">            <span class="keyword">const</span> imageCaption = image.getAttribute(<span class="string">'alt'</span>);</span><br><span class="line">            <span class="comment">// 检查图片是否已经被链接包裹</span></span><br><span class="line">            <span class="keyword">let</span> imageWrapLink = image.closest(<span class="string">'a'</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> (!imageWrapLink) {</span><br><span class="line">                <span class="keyword">let</span> src = image.getAttribute(<span class="string">'src'</span>);</span><br><span class="line">                <span class="comment">// 移除 URL 中的查询参数</span></span><br><span class="line">                <span class="keyword">const</span> idx = src.lastIndexOf(<span class="string">'?'</span>);</span><br><span class="line">                <span class="keyword">if</span> (idx !== -<span class="number">1</span>) {</span><br><span class="line">                    src = src.substring(<span class="number">0</span>, idx);</span><br><span class="line">                }</span><br><span class="line"></span><br><span class="line">                <span class="comment">// 创建新的链接元素并包裹图片</span></span><br><span class="line">                imageWrapLink = <span class="built_in">document</span>.createElement(<span class="string">'a'</span>);</span><br><span class="line">                imageWrapLink.href = src;</span><br><span class="line">                image.parentNode.insertBefore(imageWrapLink, image);</span><br><span class="line">                imageWrapLink.appendChild(image);</span><br><span class="line">            }</span><br><span class="line"></span><br><span class="line">            <span class="comment">// 设置 FancyBox 属性</span></span><br><span class="line">            imageWrapLink.setAttribute(<span class="string">'data-fancybox'</span>, <span class="string">'images'</span>);</span><br><span class="line">            <span class="keyword">if</span> (imageCaption) {</span><br><span class="line">                imageWrapLink.setAttribute(<span class="string">'data-caption'</span>, imageCaption);</span><br><span class="line">            }</span><br><span class="line">        });</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 初始化 FancyBox</span></span><br><span class="line">        <span class="keyword">if</span> (<span class="keyword">typeof</span> Fancybox !== <span class="string">'undefined'</span>) {</span><br><span class="line">            Fancybox.bind(<span class="string">'[data-fancybox="images"]'</span>, {</span><br><span class="line">                Images: {</span><br><span class="line">                    <span class="comment">// 图片相关设置</span></span><br><span class="line">                    zoom: <span class="literal">false</span>,</span><br><span class="line">                },</span><br><span class="line">                Toolbar: {</span><br><span class="line">                    display: [</span><br><span class="line">                        <span class="string">"zoom"</span>,</span><br><span class="line">                        <span class="string">"slideshow"</span>,</span><br><span class="line">                        <span class="string">"fullscreen"</span>,</span><br><span class="line">                        <span class="string">"close"</span></span><br><span class="line">                    ],</span><br><span class="line">                },</span><br><span class="line">                Thumbs: {</span><br><span class="line">                    autoStart: <span class="literal">false</span>,  <span class="comment">// 禁用缩略图</span></span><br><span class="line">                },</span><br><span class="line">                Hash: <span class="literal">true</span>,            <span class="comment">// 启用 URL hash（不知道有没有用）</span></span><br><span class="line">                infinite: <span class="literal">false</span>,       <span class="comment">// 禁用循环浏览</span></span><br><span class="line">            });</span><br><span class="line">        } <span class="keyword">else</span> {</span><br><span class="line">            <span class="built_in">console</span>.warn(<span class="string">'FancyBox is not loaded'</span>);</span><br><span class="line">        }</span><br><span class="line">    } <span class="keyword">catch</span> (error) {</span><br><span class="line">        <span class="built_in">console</span>.error(<span class="string">'Error initializing FancyBox:'</span>, error);</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>第二步，将下面的代码添加到博客的 <code>head</code> 部分，也就是 <code>themes\&lt;theme_name&gt;\layout\_partial\head.ejs</code>：</p><figure class="highlight html"><table><tbody><tr><td class="code"><pre><span class="line"><span class="comment">&lt;!-- fancybox support --&gt;</span></span><br><span class="line">&lt;% if(theme.fancybox === true &amp;&amp; page.fancybox !== false) { %&gt;</span><br><span class="line">  <span class="comment">&lt;!-- for theme: default is false --&gt;</span></span><br><span class="line">  <span class="comment">&lt;!-- for page: default is true --&gt;</span></span><br><span class="line">  &lt;% if(page.path &amp;&amp; page.path.startsWith('posts/')) { %&gt;</span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">"/js/fancybox.js"</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">"https://cdn.jsdelivr.net/npm/@fancyapps/ui@4.0/dist/fancybox.umd.js"</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">link</span> <span class="attr">rel</span>=<span class="string">"stylesheet"</span> <span class="attr">href</span>=<span class="string">"https://cdn.jsdelivr.net/npm/@fancyapps/ui@4.0/dist/fancybox.css"</span>/&gt;</span></span><br><span class="line">  &lt;% } %&gt;</span><br><span class="line">&lt;% } %&gt;</span><br></pre></td></tr></tbody></table></figure><p>第三步，修改博客的配置文件 <code>themes\&lt;theme_name&gt;\_config.yml</code>，添加 <code>fancybox: true</code>。</p><p>这样就完成了，现在博客文章中的图片就可以点击放大查看了。</p><p><img src="/posts/915559b/mobile-view.png" alt="移动端效果图" loading="lazy" style="max-width: 80%"></p><blockquote><p>为了这个博文还特意修改了一下代码的 css，虽然还是挺一般的，但至少没有原来那么丑了。</p></blockquote><p>最后说一句，Claude 真香！</p></body></html>]]></content>
    
    
    <summary type="html">&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;/body&gt;&lt;/html&gt;</summary>
    
    
    
    <category term="Chore" scheme="https://nano.ac/categories/Chore/"/>
    
    
  </entry>
  
  <entry>
    <title>将评论系统从 Gitalk 迁移到 Giscus</title>
    <link href="https://nano.ac/posts/687ccc4a/"/>
    <id>https://nano.ac/posts/687ccc4a/</id>
    <published>2025-01-23T15:40:18.000Z</published>
    <updated>2025-06-19T02:33:56.907Z</updated>
    
    <content type="html"><![CDATA[<html><head></head><body><p>由于 Gitalk 依赖 GitHub Issue 偶尔罢工，Nano 决定迁移到更稳定的基于 GitHub Discussions 的 Giscus 系统！</p><blockquote><p>历史评论将会被清空并归档到另一个仓库的 <a href="https://github.com/NanoCommentBuilder/blog-comment/issues">GitHub Issues</a>。</p></blockquote><p>顺便将 PageSpeed Insights 上的性能优化问题也解决了，现在网站的性能评分已经有四个 100 分了！</p></body></html>]]></content>
    
    
    <summary type="html">&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;/body&gt;&lt;/html&gt;</summary>
    
    
    
    <category term="Chore" scheme="https://nano.ac/categories/Chore/"/>
    
    
  </entry>
  
  <entry>
    <title>CCBC15 出题笔记</title>
    <link href="https://nano.ac/posts/66848e61/"/>
    <id>https://nano.ac/posts/66848e61/</id>
    <published>2024-08-19T17:47:05.000Z</published>
    <updated>2025-06-19T02:33:56.906Z</updated>
    
    <content type="html"><![CDATA[<html><head></head><body><p>这次的 CCBC15 我作为出题人共出了五道题，分别是「CCBC MOE」、「西提沃克刮刮乐」、「NO​​‌t‍‍‍h‍‌​i‍​​n​‌‍g is Everything」、「遗失的解密卡」以及「大海捞针」。</p><p>这里我会分别介绍一下每道题的出题思路。题目解析的话见于题目链接。</p><h2 id="CCBC-MOE"><a href="#CCBC-MOE" class="headerlink" title="CCBC MOE"></a>CCBC MOE</h2><ul><li>题目链接：<a href="https://puzzle.cipherpuzzles.com/puzzle/4/29">https://puzzle.cipherpuzzles.com/puzzle/4/29</a> （待归档）</li><li>题目分区：三区「全球呼叫出题组」</li><li>通过队伍数：270</li></ul><p>这题的灵感源自于我有一天突发奇想，想着能不能将每个字都按照某种规则转换成扑克，这样我们就可以每人拿出一句诗然后组三个人打斗地主了？当然，最后没选择斗地主，而是选择了规则比较明确的德州扑克，从德扑 5+2N 比大小的形式也顺理成章地想出了「让多个两个字的人物互相比比谁更厉害」这种脑洞。</p><p>最终版本的题目套了一层萌战的皮，一方面我第一次接触这种投票应援的形式就是在萌战，另外一方面萌战和德扑完全就是八竿子打不着的两个事物，也能很好地隐藏真实的题目内容，以及最重要的，<strong>萌战的形式可以做成互动问卷题</strong>，一边展示虚假数据，一边搜集真实数据，给 Puzzle Hunt 带来点不一样的东西。</p><p>题目最终的呈现我还是很满意的，整个题目除了 ft 中提到的「筹码」「all in」以及 5+2N 的形式在隐约透露着德扑，其他地方看起来都和普通的萌战投票没有区别。每组投票的设计既有老生常谈的白色相簿，也有把闲散队五个人都拉来比比谁更萌更可爱，甚至加入了完全无厘头不可比的传统加密手段，就是想营造一种「这也可以比」的感觉。精雕细琢的投票交互细节也让大家比较难发现「票数不重要，大小关系才是题目的一部分」，毕竟可乐怎么可能打不过百事啊！！！！（锐评：做假票是萌战的一环？</p><p>当然，要发现「黑幕」还是有途径的。如果后端响应数据太慢的话，玩家是有一定机会发现每个人的票数是一个 1~n 的全排列。或者，玩家可以尝试给可乐刷票，然后他们就会发现，嗯，可乐永远打不过百事。嗯这真的太怪了，真的没有黑幕吗？？？</p><p>不过由于这一个大区很容易 backsolve，很多队伍到完赛后才知道有控票行为，节目效果属实拉满（目的达成！）</p><p>顺便一提，第一个做到这题的队伍（大概率是 4s，实际的确是 4s）会发现票数都在 1~26 范围内，这也是我的一个小设计，试图让最前头的队伍踩进 A1Z26 的坑（我太坏了），虽然但是随着投票数的增加应该很快就发现异样吧 hhhhh<br>（然后这题就被 4s 跳过了，最后靠 backsolve 拿首杀，预料之中）</p><p>以及现有的工具真的是太好用了，我要不在题解里面写「使用康托展开排序字母获得答案」，估计前排很多队伍都不会去想到选项的展示顺序是有意义的。</p><p>至于你问我对于这题大多数队伍都用 backsolve 解掉的情况有什么感想，我只能说，这个题目能出成这样我已经很满意了，其他的大家做题开心就好，Puzzle Hunt 就是应该让人感到开心！（然后我就在后面埋了两个雷）</p><p><img src="https://cache.nan.pub/imgs/2024/08/20/20240820-020703.png" alt="不可能二组" loading="lazy" style="max-width: 80%"></p><p>如上图：什么不可能二组 🤣</p><h2 id="西提沃克刮刮乐"><a href="#西提沃克刮刮乐" class="headerlink" title="西提沃克刮刮乐"></a>西提沃克刮刮乐</h2><ul><li>题目链接：<a href="https://puzzle.cipherpuzzles.com/puzzle/4/31">https://puzzle.cipherpuzzles.com/puzzle/4/31</a> （待归档）</li><li>题目分区：三区「全球呼叫出题组」</li><li>通过队伍数：315</li></ul><p>这题是我对于「线下户外谜题」这个自设命题交的一份答卷。</p><p>众所周知，我是 Ingress 老玩家，最近一年还入坑了 Geocaching 这个户外寻宝类的游戏，这次难得来给 CCBC 出题，就想夹点私货。于是，冲着和竞速解题背道而驰的这个点，想着出道题向让大家通过出门散步来解谜。</p><p>但线下解谜题有个设计难点在于，你无法提前预知做题者会在哪里做题，很难找到一个五湖四海的做题者出门都可以比较容易能找到的东西。哦，有的，大自然！我们可以结合 OpenStreetMap 这个开源的地图数据库，查询某个坐标周边范围内是否存在江河湖海或者公园森林之类的地方。</p><p>我自己同时也是「黑箱题」的爱好者，再加上江河湖海可以用蓝色代表，公园森林可以用绿色代表，那还差个红就可以构成 RGB 了！（顺序不太对，没关系没关系）于是，我又多设计了一层红色像素，用来隐藏蓝绿色的解锁 clue，顺便把红色的解锁条件设置得非常简单且无需黑箱探索，只需要真的听从题目的指引，出门 citywalk 一圈，红色基本上就可以解读了。在这样的机制下，所有的 clue 就都以一种一层套一层的机制藏起来了。clue 的隐藏方式也没弄得很复杂，毕竟这题的主要目的不是在这里，所以就随便转成 ASCII 丢进 color hex 里面了。题目整体的预期做题体验就是出门搜集碎片，然后轻松解谜。</p><p><strong>适度解谜益脑，沉迷解谜伤身；合理出门锻炼，享受健康生活！</strong></p><p>这题的设计灵感是比赛前两天才想出来的，技术论证和代码实现则是比赛前一天赶出来的，很高兴自己的代码能力还是顶得住的！</p><p>补充一句，我后来发现国内的 OpenStreetMap 数据并不完善，水域之类的数据基本都是完整的，但森林和公园之类的数据在部分地方可能还有所欠缺，所以最后一步的 clue 也是冲着「蓝色解完了也能出答案」的目的去设计。在此也欢迎有志人士给开源地图数据库OpenStreetMap 添砖加瓦！我国疆域实在是太大了 Orz</p><h2 id="NO​​‌t‍‍‍h‍‌​i‍​​n​‌‍g-is-Everything"><a href="#NO​​‌t‍‍‍h‍‌​i‍​​n​‌‍g-is-Everything" class="headerlink" title="NO​​‌t‍‍‍h‍‌​i‍​​n​‌‍g is Everything"></a>NO​​‌t‍‍‍h‍‌​i‍​​n​‌‍g is Everything</h2><ul><li>题目链接：<a href="https://puzzle.cipherpuzzles.com/puzzle/6/51">https://puzzle.cipherpuzzles.com/puzzle/6/51</a> （待归档）</li><li>题目分区：五区「我爱猫猫!」</li><li>通过队伍数：362</li></ul><p>这题是我对于「无题面无交互」这个自设命题交的一份答卷。</p><p>没有题面，没有其他互动方式（例如提示啊里程碑啊），只有一个标题。原本的题目设计就是 NOthing 里面塞了零宽字符，然后用 is Everything 强调不需要关注其他额外东西了。</p><p>然后，题目早期测试的时候被多个人眼爆出了个 NOisE，感觉甚妙，于是保留成里程碑，想着能起到一个引路牌的作用。（虽然我赛后觉得这应该不是里程碑，更像是一个彩蛋，希望明年能有彩蛋机制）</p><p>这个题目在内测时的版本会更难些，因为腾讯文档不能右键检查页面元素，导致大家纷纷觉得这题还是太难了容易卡住那些不知道零宽字符的队伍。但！是！CCBC 可以右键检查页面元素！然后你就会发现零宽字母被 HTML 转义了！于是本题难度大幅下降，成为了一道水题！喜！anyway 我觉得形式更重要，也不想让大家卡在「如何阅读零宽字符」而卡题。这波是 HTML 转义立大功。</p><p>以及这题的标题长度为 36，差点就是 CCBC15 最长题目标题了，如果没有哈基米的话。</p><h2 id="遗失的解密卡"><a href="#遗失的解密卡" class="headerlink" title="遗失的解密卡"></a>遗失的解密卡</h2><ul><li>题目链接：<a href="https://puzzle.cipherpuzzles.com/puzzle/7/68">https://puzzle.cipherpuzzles.com/puzzle/7/68</a> （待归档）</li><li>题目分区：六区「孬题杀死了出题明星」</li><li>通过队伍数：212</li></ul><p>这题是我对于「传统密码破译」这个自设命题交的一份答卷。论「当 nutri 成为出题人解题工具的时候题目会发生什么样的变化」。</p><p>早期的想法就是给出加密方式，不给密钥，来逆推解密，很符合 CCBC 中的 CB（Code Breaking）。</p><p>然后内测的时候被 yyao 一波简单 nutri 带走了（指完全没利用旋转限制直接大力 nutri 就爆出了 television 然后直接破题）这对吗？这不对。于是这题成为了内测后唯一加强难度的题目。</p><p>对于这题，我在比赛前以及比赛过程中的想法也是我想很想记录下来的。一开始，这题目我是觉得存在非编程解的可能性的，即不会敲代码的人也可以用其他常用工具解决本题，但我因为懒，所以也没去试。在题目被加强后我还和 yyao 讨论了很久，yyao 觉得预期解是编程解不合理，而我则是侥幸地觉得选手那么厉害总会有人非编程解出来的吧，到时候我只要抄一份就好了？！然而随着完赛队伍的陆续反馈，发现大家的解题方法分为两类：一类是靠编程解题，一类是靠买提示降低难度后解题，并没有看到非编程解题的人，大家也都在讨论这题「只能用程序解」。我有点不信邪，于是自己尝试用 excel+nutri 试着重新从头解一遍。当然，这里是没有利用到任何先验知识的（例如从已知答案逆推编个 nutri 直接出答案）否则这个解法就算不上是从头解一遍。这么做的目的一方面是为了证明这题存在非编程解，另一方面也是为了挑战自己。</p><p>最后就是，和自己预期一样，使用 excel 可以很方便地求解出确定某个单词作为前缀后，其他旋转方向上的字母分布，以此来判定这个单词是否合理，甚至可以用 excel 的字符串连接功能，串出一个能充分利用现有信息的 nutri。整个过程基本上就是靠着纯逻辑，从开始做表到完成破译差不多一个小时，如果前面选的词选歪了（例如选了 dream）多了一些逻辑判断，也不会超过两个小时……嘿嘿我好厉害（不是</p><p>不过这题的预期解和代码解都有点难，导致没有提示时的可做性差差差差差。想了想，这可能和我的出题风格也有关系。我出难题的步骤大致分为三步：「设置一个难以击破的靶子」「自己尝试击破它」「击破了，好，出成题」，最后就导致题目看上去一副不可做的样子，至少我自己出的 CTF 题目也是这样的……</p><p>可惜最后这题的 ft 还是写烂了，导致好多队伍都去别的题目找 6x6 方阵了，偏偏本届比赛 6x6 方阵还真就那么多……还是修改后的 ft 好！</p><p><img src="https://cache.nan.pub/imgs/2024/08/20/20240820-023602.png" alt="解密卡丢在了提示里" loading="lazy" style="max-width: 80%"></p><h2 id="大海捞针"><a href="#大海捞针" class="headerlink" title="大海捞针"></a>大海捞针</h2><ul><li>题目链接：<a href="https://puzzle.cipherpuzzles.com/puzzle/7/69">https://puzzle.cipherpuzzles.com/puzzle/7/69</a> （待归档）</li><li>题目分区：六区「孬题杀死了出题明星」</li><li>通过队伍数：127</li></ul><p>早期发散出题灵感的时候就想搞一次这样的行为艺术了，可惜没有场地支持，因为得找一个拍摄地一个操作地，还得自己买个设备录制。本来想借用一下别人的街景实况直播，但找来找去只在 Bilibili 上找到一个北京朝阳大悦城的街景直播。</p><p>然而，在比赛前一周，好巧不巧，我在住酒店的时候，发现房间窗户是落地窗且一览无余，视野范围内能看到那么几家酒店，碰巧就把基本条件满足了，于是就有了这题。</p><p>这题的成本至少得 1000+，设备花了 800+，酒店花了 250+，后面为了更好的拍摄条件还去前台升级房型了，要不然房间在四楼，窗户被前面的楼挡住，从拍摄地看过去根本拍不到，最后升房换到七楼的房间才好些。出这题的同时自己还在构思西提沃克刮刮乐，不想关房间的灯，所以选择了拉窗帘的方式来控制亮暗，并在 Macbook 设置了 120 个闹钟（为了防止自己铃声 PTSD 还特意混用了不同的铃声）拉窗帘拉了四个小时，从晚上 22 点拉到了第二天的凌晨 2 点。</p><p><img src="https://cache.nan.pub/imgs/2024/08/20/20240820-024930.png" alt="超大闹钟矩阵" loading="lazy" style="max-width: 80%"></p><p>到了最后几次操作，真的是困到不行，拉完最后一次后直接房卡拔掉下楼前台退房回家睡觉。</p><blockquote><p>前台：您确定是要现在（指凌晨二点）就退房且不需要享用早餐了？<br>我：是的</p></blockquote><p>还好，录制过程都很顺利，没有 NG 一遍通过！最后就是开了个 Bilibili 小号，上传了个视频，关闭了弹幕和评论区，静静地等待着大家第一眼看到 6:00:00 时的震撼以及不解。</p><p>至于可做性？没考虑过怎么优化。不过这题也不是完全不可做，还是有思路可循的。如果从出题的角度出发，出题人能控制的东西其实很有限，例如红绿灯这种东西肯定就控制不了吧！所以，其实可以通过图寻找到拍摄地所在的位置，然后检查拍摄区域内有哪些元素是出题人可以控制的（例如酒店），这可以大大减少搜寻的范围，进而快速找到有问题的地方。</p><p>anyway 既然都决定把这题放在最后了，那这题大概率就是被跳过的命吧！</p><p>下图是本次拍摄所用到的设备。辛苦了摄影机！就是你拍的东西实在是有点糊……</p><p><img src="https://cache.nan.pub/imgs/2024/08/20/20240820-025022.png" alt="拍摄设备" loading="lazy" style="max-width: 80%"></p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>最后在 after party 上公布的「最讨厌的题」上我总共占了 3/6 道题目，「大海捞针」也成功问鼎「最讨厌的题」！</p><p>同时「西提沃克刮刮乐」和「大海捞针」也占据了「最佳创新题」中 2/3 的席位！</p><p>只能说，这是创新的代价了 hhhhhhhhh</p><p>最后，希望未来的我也能像今年 CCBC15 一样有这么多的出题灵感！</p></body></html>]]></content>
    
    
    <summary type="html">&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;/body&gt;&lt;/html&gt;</summary>
    
    
    
    <category term="Puzzle" scheme="https://nano.ac/categories/Puzzle/"/>
    
    
    <category term="CCBC" scheme="https://nano.ac/tags/CCBC/"/>
    
  </entry>
  
  <entry>
    <title>AliyunCTF2023 NanoCatPlanet 出题笔记</title>
    <link href="https://nano.ac/posts/dcf52abe/"/>
    <id>https://nano.ac/posts/dcf52abe/</id>
    <published>2023-04-25T15:18:58.000Z</published>
    <updated>2025-06-19T02:33:56.905Z</updated>
    
    <content type="html"><![CDATA[<html><head></head><body><p>Misc, 400 points, 3 solves。</p><h2 id="出题思路"><a href="#出题思路" class="headerlink" title="出题思路"></a>出题思路</h2><p>一切的起源，还是来自 ByteCTF2021 Final 出了一道 Arnold’s cat map 水印题。傻傻的我以为加密参数没给，就去爆破参数，然后解出来了，然后看到预期解是去某个偏僻的地方找藏起来的参数……</p><p>藏参数坏文明！所以！大家一起来爆破参数解 Arnold’s cat map 吧！</p><h2 id="题解"><a href="#题解" class="headerlink" title="题解"></a>题解</h2><p>题目附件是一个 11500x11500 的卫星图片，其他什么都没有。</p><p>虽然做过猫图题的选手看题目背景和水印大体上都能猜到这张图片使用了 Arnold’s cat map 加密，但为了新手考虑，我还是藏了一些附加信息。</p><p>通过查看文件二进制信息可以发现存在一个 tEXt 段，里面的信息是 <code>Software=CatWatermark</code>。这一个 block 的 CRC 有误，所以如果用 PIL 读取图片的话也会有提醒。</p><p><img src="https://cache.nan.pub/imgs/2023/04/25/20230425-233909.png" alt="image-20230425233907722" loading="lazy" style="max-width: 80%"></p><p>通过在 GitHub 搜索确定加密工具是 <a href="https://github.com/Konano/CatWatermark%EF%BC%8C%E9%87%8C%E9%9D%A2%E8%87%AA%E5%B8%A6%E4%BA%86%E4%B8%80%E4%B8%AA">https://github.com/Konano/CatWatermark，里面自带了一个</a> encode 和 decode。</p><p><img src="https://cache.nan.pub/imgs/2023/04/25/20230425-234210.png" alt="image-20230425234208984" loading="lazy" style="max-width: 80%"></p><p>通过 README 可以知道，要 decode 这个加密过的卫星图需要原图和私钥，而私钥又分为三部分：x 轴偏移、y 轴偏移和迭代次数。原图其实不难找，这里略过（我也忘了在哪里找的）。</p><p>然后我们把目光转向 <code>encode.py</code>，发现这个代码实现了一个 $O(n^3)$ 的 Arnold’s cat map 算法。尝试下你会发现这代码巨慢无比，跑个 1000x1000 的图都已经特别慢了，更何况 11500x11500 的图，而且还要爆破三个参数，此时爆破复杂度为 $O(n^6)$，约为 $2313060765625000000000000$。</p><p>通过分析代码可以发现 Arnold’s cat map 的实现有问题。如下代码所示，new_image 在操作后并没有更新给 image，导致无论循环多少次，<strong>返回的 new_image 一直都是迭代 1 次的结果</strong>。此时爆破复杂度降到了 $O(n^4)$，约为 $17490062500000000$。</p><figure class="highlight python"><table><tbody><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">arnold_cat_map</span>(<span class="params">image, key=(<span class="params"><span class="number">1</span>, <span class="number">2</span>, <span class="number">1</span></span>)</span>):</span></span><br><span class="line">    <span class="string">"""</span></span><br><span class="line"><span class="string">    Implements Arnold's cat map transformation on an image.</span></span><br><span class="line"><span class="string">    """</span></span><br><span class="line">    height, width, *_ = image.shape</span><br><span class="line">    offset_x, offset_y, iterations = key</span><br><span class="line"></span><br><span class="line">    new_image = np.zeros(image.shape, dtype=np.uint8)</span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(iterations):</span><br><span class="line">        <span class="keyword">for</span> x <span class="keyword">in</span> <span class="built_in">range</span>(height):</span><br><span class="line">            <span class="keyword">for</span> y <span class="keyword">in</span> <span class="built_in">range</span>(width):</span><br><span class="line">                _x, _y = x, y</span><br><span class="line">                _y = (_y + offset_x * _x) % width</span><br><span class="line">                _x = (_x + offset_y * _y) % height</span><br><span class="line">                new_image[_x, _y] = image[x, y]</span><br><span class="line">    <span class="keyword">return</span> new_image</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">arnold_cat_map_rev</span>(<span class="params">image, key=(<span class="params"><span class="number">1</span>, <span class="number">2</span>, <span class="number">1</span></span>)</span>):</span></span><br><span class="line">    <span class="string">"""</span></span><br><span class="line"><span class="string">    Implements Arnold's cat map transformation on an image (reverse).</span></span><br><span class="line"><span class="string">    """</span></span><br><span class="line">    height, width, *_ = image.shape</span><br><span class="line">    offset_x, offset_y, iterations = key</span><br><span class="line"></span><br><span class="line">    new_image = np.zeros(image.shape, dtype=np.uint8)</span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(iterations):</span><br><span class="line">        <span class="keyword">for</span> x <span class="keyword">in</span> <span class="built_in">range</span>(height):</span><br><span class="line">            <span class="keyword">for</span> y <span class="keyword">in</span> <span class="built_in">range</span>(width):</span><br><span class="line">                _x, _y = x, y</span><br><span class="line">                _x = (_x - offset_y * _y) % height</span><br><span class="line">                _y = (_y - offset_x * _x) % width</span><br><span class="line">                new_image[_x, _y] = image[x, y]</span><br><span class="line">    <span class="keyword">return</span> new_image</span><br></pre></td></tr></tbody></table></figure><p>继续观察代码。在添加水印的代码实现中，水印的位置是图片的正中央，所以如果水印的大小比原图的大小还小的话，那么恢复后的水印像素是不可能在图像边缘的。实际上，我们后续的解法就是利用这个特点来加速爆破。</p><p>首先我们观测到，在迭代次数为 1 的情况下，Arnold’s cat map 逆过程相当于先将矩阵每一列循环偏移打乱，再将每一行循环偏移打乱，而在打乱行的时候，第 1 行的像素是不会发生位置偏移的。通过观察第 1 行的元素，也可以发现水印图片的大小是要小于原图图片的。</p><p>同时，由代码可知，第 2 行的偏移量为 offset_x，也就是其中一个需要爆破的私钥参数。通过比较前两行的水印像素，我们可以直接算出 offset_x 为 $5809$。当你将第 2 行的水印像素循环偏移 $5809$ 格时，你能看到前两行的水印像素重合度非常高，证明我们的思路是对的。此时需要爆破的参数值也就只有 offset_y 了，爆破复杂度降到了 $O(n^3)$，约为 $1520875000000$。</p><p>对于 offset_y，我们可以假设水印图片的高也小于原图的高，将此作为剪枝条件，在爆破 offset_y 时，如果发现有的水印像素在还原后跑到了图片边缘处，我们就可以直接判断该 offset_y 不合法。经过爆破可以得到 offset_y 的值为 $2901$。</p><p>本题最后得到的水印如下：</p><p><img src="https://cache.nan.pub/imgs/2023/04/26/20230426-001954.png" alt="image-20230426001953447" loading="lazy" style="max-width: 80%"></p><p>实际上，题目描述中「这次猫咪们又将他们抓来的 flag <strong>藏在了喵星球上</strong>，你们能找到吗？」以及「请注意：猫咪们 <strong>还尚未掌握航天科技</strong>。」也在二次提示选手，猫猫们只会把水印放在星球范围内！它们不会飞！！</p><p>以及原代码写的 Arnold’s cat map 算法实在是太<del>慢</del>啦~，其实也是故意写成这样的，给选手留点优化的空间。大家也可以尝试优化一下代码，让其跑得更快！所以最后复杂度能降到多少纯靠各位的代码优化能力了！</p><p>为了让大家动手复现和多思考，这里也不放 exp 了，不过复现过程中如果遇到什么困难也可以来跟我交流！</p><p>最后，敬请期待下一道难度加强版猫图题！喵 (=´ω｀=)</p></body></html>]]></content>
    
    
    <summary type="html">&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;/body&gt;&lt;/html&gt;</summary>
    
    
    
    <category term="CTF" scheme="https://nano.ac/categories/CTF/"/>
    
    
    <category term="Misc" scheme="https://nano.ac/tags/Misc/"/>
    
    <category term="Writeup" scheme="https://nano.ac/tags/Writeup/"/>
    
  </entry>
  
  <entry>
    <title>畸形二维码导致 APP 扫码模块崩溃</title>
    <link href="https://nano.ac/posts/86257129/"/>
    <id>https://nano.ac/posts/86257129/</id>
    <published>2023-04-24T03:38:24.000Z</published>
    <updated>2025-06-19T02:33:56.907Z</updated>
    
    <content type="html"><![CDATA[<html><head></head><body><p>简单来说，APP 在扫描畸形二维码时，由于畸形二维码内的错误数据块导致 <code>libqbar.so</code> 崩溃，进而导致软件闪退。</p><p>构造畸形二维码样本时需要注意的要点：</p><ul><li>数据块内不可以出现 Padding Pattern</li><li>最后一个 block 的内容为空，但是 Character Count Indicator 不为 0</li></ul><p>附上复现用的代码，可根据任意文本内容构造畸形二维码：</p><figure class="highlight python"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> qrcode</span><br><span class="line"><span class="keyword">from</span> qrcode.util <span class="keyword">import</span> QRData, MODE_8BIT_BYTE</span><br><span class="line"></span><br><span class="line">NUM_BLOCKS = [<span class="number">19</span>, <span class="number">34</span>, <span class="number">55</span>, <span class="number">80</span>, <span class="number">108</span>, <span class="number">136</span>, <span class="number">156</span>, <span class="number">194</span>, <span class="number">232</span>]</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">tencent_crash_qrcode</span>(<span class="params">message: <span class="built_in">str</span>, filename=<span class="string">'crash.png'</span></span>):</span></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">hack_put</span>(<span class="params">self, num, length</span>):</span></span><br><span class="line">        <span class="keyword">if</span> num == <span class="number">0</span>:</span><br><span class="line">            num = <span class="number">1</span></span><br><span class="line">        <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(length):</span><br><span class="line">            self.put_bit(((num &gt;&gt; (length - i - <span class="number">1</span>)) &amp; <span class="number">1</span>) == <span class="number">1</span>)</span><br><span class="line">    </span><br><span class="line">    data = message.encode(<span class="string">'utf-8'</span>)</span><br><span class="line">    data_len = <span class="built_in">len</span>(data)</span><br><span class="line"></span><br><span class="line">    version = <span class="number">1</span></span><br><span class="line">    <span class="keyword">while</span> version &lt;= <span class="built_in">len</span>(NUM_BLOCKS) <span class="keyword">and</span> data_len + <span class="number">3</span> &gt; NUM_BLOCKS[version-<span class="number">1</span>]:</span><br><span class="line">        version += <span class="number">1</span></span><br><span class="line">    <span class="keyword">if</span> version &gt; <span class="built_in">len</span>(NUM_BLOCKS):</span><br><span class="line">        <span class="keyword">raise</span> Exception(<span class="string">'message too long'</span>)</span><br><span class="line"></span><br><span class="line">    data += <span class="string">b' '</span> * (NUM_BLOCKS[version-<span class="number">1</span>] - data_len - <span class="number">3</span>)</span><br><span class="line"></span><br><span class="line">    print(data_len, version)</span><br><span class="line">    qr = qrcode.QRCode(version, qrcode.constants.ERROR_CORRECT_L)</span><br><span class="line">    </span><br><span class="line">    comm_data = QRData(data, MODE_8BIT_BYTE)</span><br><span class="line">    hack_data = QRData(<span class="string">b''</span>, MODE_8BIT_BYTE)</span><br><span class="line"></span><br><span class="line">    qr.add_data(comm_data, <span class="number">0</span>)</span><br><span class="line">    qr.add_data(hack_data, <span class="number">0</span>)</span><br><span class="line"></span><br><span class="line">    original_put = qrcode.util.BitBuffer.put</span><br><span class="line">    qrcode.util.BitBuffer.put = hack_put</span><br><span class="line">    qr.make_image().save(filename)</span><br><span class="line">    qrcode.util.BitBuffer.put = original_put</span><br><span class="line"></span><br><span class="line">tencent_crash_qrcode(<span class="string">'KFCVW50'</span>)</span><br></pre></td></tr></tbody></table></figure><p>最后，附上一个通过脚本生成的畸形二维码。</p><p><img src="https://cache.nan.pub/imgs/2023/04/24/20230424-115220.png" alt="crash" loading="lazy" style="max-width: 80%"></p><p>本文参考了 <a href="https://blog.gztime.cc/">@GZTime</a> 同学的 <a href="https://t.me/TimeAxis/892">复现脚本</a>。</p></body></html>]]></content>
    
    
    <summary type="html">&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;/body&gt;&lt;/html&gt;</summary>
    
    
    
    <category term="Technique" scheme="https://nano.ac/categories/Technique/"/>
    
    
  </entry>
  
  <entry>
    <title>MRCTF2022 Writeup</title>
    <link href="https://nano.ac/posts/e8de5668/"/>
    <id>https://nano.ac/posts/e8de5668/</id>
    <published>2022-04-25T10:27:13.000Z</published>
    <updated>2025-06-19T02:33:56.906Z</updated>
    
    <content type="html"><![CDATA[<html><head></head><body><p>这次的 MRCTF 我受邀参加，并解决了 Misc 系列所有的题，且拿到了 6 个一血。题目质量好评！</p><h2 id="Checkin"><a href="#Checkin" class="headerlink" title="Checkin"></a>Checkin</h2><p><img src="https://cache.nan.pub/imgs/2022/04/25/20220425-184010.png" alt="image-20220425184009822" loading="lazy" style="max-width: 80%"></p><p>好久没有看见过这么难的签到了（难是相对其他比赛的签到题而言）</p><p>以及怎么感觉自从我在 TQLCTF 出了 Wordle 之后就陆陆续续需要各种队出各种类型的 Wordle，也算是大开眼界了。等有机会整个 urandom Wordle。</p><p>浏览器的 Local Storage 可以很清楚地看到答案，所以一次就猜对也没啥难度。</p><p>找 flag 倒是找了好久，最后在 <code>app/src/lib/stats.ts</code> 找到了。</p><p><img src="https://cache.nan.pub/imgs/2022/04/25/20220425-183356.png" alt="image-20220425183354382" loading="lazy" style="max-width: 80%"></p><h2 id="ppd"><a href="#ppd" class="headerlink" title="ppd"></a>ppd</h2><p><img src="https://cache.nan.pub/imgs/2022/04/25/20220425-184504.png" alt="image-20220425184503588" loading="lazy" style="max-width: 80%"></p><p>伪造 IP 很容易，直接加 <code>X-Forwarded-For</code> 就行。但尝试了之后发现永远不可能到 100。</p><p>和服务器的通信总是会包含一个 <code>enc</code> ，于是仔细研究，发现是经过了 AES 的 CBC 模式加密，且被加密对象是个字符串，其中的 <code>name</code> 是可控的，也就是说我们可以得到任意明文经过加密后的密文。那直接伪造一个 100 的成绩然后发送给服务器就行了。</p><p>（这应该是 Web+Crypto 题吧……）</p><h2 id="ReadLongNovel"><a href="#ReadLongNovel" class="headerlink" title="ReadLongNovel"></a>ReadLongNovel</h2><p>下载小说全文然后搜关键字答题即可。为了保险起见我还每次只新加五个答案。</p><p>（虽然有 AI 解法但是 AI 解法也会比手动搜索要麻烦且费时，如果不是为了 AK Misc 的话的确不想做这题……）</p><h2 id="jpeg-and-the-tree"><a href="#jpeg-and-the-tree" class="headerlink" title="jpeg and the tree"></a>jpeg and the tree</h2><p>个人觉得全场最佳题目，需要手写一个 jpeg 解码器。</p><p>这题和 Google CTF 2021 的某道题很相似：<a href="https://room2042.gitlab.io/writeup/2021-07-24-google_ctf-david_and_the_tree/">DAVID and the Tree</a>，实际上解题思路也很像。</p><p>首先 jpeg 里面是有 Huffman Tree 的，用来压缩数据。Google CTF 的题是找不存在的 E 所对应的 01 编码，因为 E 根本没出现在原文，故 Huffman Tree 不可能出现 E 对应的 01 编码。换到 jpeg 就需要去研究 jpeg 是怎么编码的，然后找出 DHT1 这个块里面有哪个 01 编码从头到尾根本没用过，然后就会发现每张图都藏着一个没使用过的 01 编码。</p><p>全部打印出来保留二进制末尾 4 位串在一起，然后 bin2str 就能看到 flag 了。</p><figure class="highlight python"><table><tbody><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">number</span>(<span class="params">x</span>):</span></span><br><span class="line">    <span class="keyword">if</span> x[<span class="number">0</span>] == <span class="string">'0'</span>:</span><br><span class="line">        x = x.replace(<span class="string">'0'</span>, <span class="string">'.'</span>).replace(<span class="string">'1'</span>, <span class="string">'0'</span>).replace(<span class="string">'.'</span>, <span class="string">'1'</span>)</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">int</span>(x, <span class="number">2</span>)</span><br><span class="line"></span><br><span class="line">flag = <span class="string">''</span></span><br><span class="line">__flag = <span class="string">''</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> <span class="built_in">id</span> <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">78</span>):</span><br><span class="line">    filename = <span class="string">f'pic/<span class="subst">{<span class="built_in">id</span>}</span>.jpg'</span></span><br><span class="line">    <span class="keyword">with</span> <span class="built_in">open</span>(filename, <span class="string">'rb'</span>) <span class="keyword">as</span> f:</span><br><span class="line">        d = f.read()</span><br><span class="line">    DC, AC = {}, {}</span><br><span class="line"></span><br><span class="line">    ST = <span class="number">0x6a</span></span><br><span class="line">    L = <span class="built_in">int</span>.from_bytes(d[ST-<span class="number">2</span>:ST], <span class="string">'big'</span>)-<span class="number">2</span></span><br><span class="line">    B = d[ST:ST+L][<span class="number">1</span>:][:<span class="number">16</span>]</span><br><span class="line">    C = d[ST:ST+L][<span class="number">1</span>:][<span class="number">16</span>:]</span><br><span class="line">    v, n = <span class="number">0</span>, <span class="number">0</span></span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">1</span>, <span class="number">16</span>):</span><br><span class="line">        v &lt;&lt;= <span class="number">1</span></span><br><span class="line">        <span class="keyword">for</span> _ <span class="keyword">in</span> <span class="built_in">range</span>(B[i]):</span><br><span class="line">            DC[<span class="string">f'<span class="subst">{v:b}</span>'</span>.zfill(i+<span class="number">1</span>)] = C[n]</span><br><span class="line">            n += <span class="number">1</span></span><br><span class="line">            v += <span class="number">1</span></span><br><span class="line"></span><br><span class="line">    ST = <span class="number">0x6a</span>+L+<span class="number">4</span></span><br><span class="line">    L = <span class="built_in">int</span>.from_bytes(d[ST-<span class="number">2</span>:ST], <span class="string">'big'</span>)-<span class="number">2</span></span><br><span class="line">    B = d[ST:ST+L][<span class="number">1</span>:][:<span class="number">16</span>]</span><br><span class="line">    C = d[ST:ST+L][<span class="number">1</span>:][<span class="number">16</span>:]</span><br><span class="line">    v, n = <span class="number">0</span>, <span class="number">0</span></span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">1</span>, <span class="number">16</span>):</span><br><span class="line">        v &lt;&lt;= <span class="number">1</span></span><br><span class="line">        <span class="keyword">for</span> _ <span class="keyword">in</span> <span class="built_in">range</span>(B[i]):</span><br><span class="line">            AC[<span class="string">f'<span class="subst">{v:b}</span>'</span>.zfill(i+<span class="number">1</span>)] = C[n]</span><br><span class="line">            n += <span class="number">1</span></span><br><span class="line">            v += <span class="number">1</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># print(DC, AC)</span></span><br><span class="line">    ST = ST + L + <span class="number">0xa</span></span><br><span class="line">    bits = <span class="string">''</span>.join([<span class="built_in">bin</span>(x)[<span class="number">2</span>:].zfill(<span class="number">8</span>)</span><br><span class="line">                   <span class="keyword">for</span> x <span class="keyword">in</span> d[ST:-<span class="number">2</span>].replace(<span class="string">b'\xFF\x00'</span>, <span class="string">b'\xFF'</span>)])</span><br><span class="line">    st, ed = <span class="number">0</span>, <span class="number">0</span></span><br><span class="line">    <span class="comment"># print(bits[:100])</span></span><br><span class="line">    G_SET = <span class="built_in">set</span>()</span><br><span class="line">    <span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line">        <span class="comment"># print(ed, len(bits))</span></span><br><span class="line">        <span class="keyword">while</span> DC.get(bits[st: ed]) <span class="keyword">is</span> <span class="literal">None</span> <span class="keyword">and</span> ed &lt;= <span class="built_in">len</span>(bits):</span><br><span class="line">            ed += <span class="number">1</span></span><br><span class="line">        <span class="comment"># print('debug', bits[st: ed])</span></span><br><span class="line">        <span class="keyword">if</span> ed &gt; <span class="built_in">len</span>(bits):</span><br><span class="line">            <span class="keyword">break</span></span><br><span class="line">        DC_len = DC.get(bits[st: ed])</span><br><span class="line">        <span class="comment"># print(DC_len, st, ed)</span></span><br><span class="line">        st, ed = ed, ed + DC_len</span><br><span class="line">        <span class="keyword">if</span> DC_len:</span><br><span class="line">            G_0_0 = number(bits[st: ed])</span><br><span class="line">        st = ed</span><br><span class="line">        m = <span class="number">0</span></span><br><span class="line">        <span class="keyword">while</span> m &lt; <span class="number">63</span>:</span><br><span class="line">            <span class="keyword">while</span> AC.get(bits[st: ed]) <span class="keyword">is</span> <span class="literal">None</span>:</span><br><span class="line">                ed += <span class="number">1</span></span><br><span class="line">            <span class="comment"># print(AC.get(bits[st: ed]), AC.get(bits[st: ed]) &amp; 0b1111, AC.get(bits[st: ed]) &gt;&gt; 4)</span></span><br><span class="line">            G_SET.add(bits[st: ed])</span><br><span class="line">            <span class="keyword">if</span> AC.get(bits[st: ed]) == <span class="number">0</span>:</span><br><span class="line">                st = ed</span><br><span class="line">                <span class="keyword">break</span></span><br><span class="line">            <span class="comment"># 0</span></span><br><span class="line">            m += AC.get(bits[st: ed]) &gt;&gt; <span class="number">4</span></span><br><span class="line"></span><br><span class="line">            <span class="comment"># &gt; 0</span></span><br><span class="line">            AC_len = AC.get(bits[st: ed]) &amp; <span class="number">0b1111</span></span><br><span class="line">            st = ed = ed + AC_len</span><br><span class="line">            m += <span class="number">1</span></span><br><span class="line">            <span class="comment"># print(bits[st: st+100])</span></span><br><span class="line">    diff = <span class="built_in">list</span>(<span class="built_in">set</span>(<span class="built_in">list</span>(AC.keys())) - G_SET)</span><br><span class="line">    <span class="keyword">assert</span> <span class="built_in">len</span>(diff) == <span class="number">1</span></span><br><span class="line">    <span class="keyword">assert</span> diff[<span class="number">0</span>][:-<span class="number">4</span>] <span class="keyword">in</span> [<span class="string">'0'</span>, <span class="string">'00'</span>, <span class="string">'000'</span>, <span class="string">'0000'</span>]</span><br><span class="line">    __flag += diff[<span class="number">0</span>][-<span class="number">4</span>:]</span><br><span class="line">    <span class="keyword">if</span> <span class="built_in">len</span>(__flag) == <span class="number">8</span>:</span><br><span class="line">        flag += <span class="built_in">chr</span>(<span class="built_in">int</span>(__flag, <span class="number">2</span>))</span><br><span class="line">        __flag = <span class="string">''</span></span><br><span class="line">        print(flag)</span><br><span class="line"></span><br><span class="line">print(flag)</span><br><span class="line"></span><br></pre></td></tr></tbody></table></figure><h2 id="Spy-Dog"><a href="#Spy-Dog" class="headerlink" title="Spy_Dog"></a>Spy_Dog</h2><p>不就是 99.9% 嘛，我用最简单的加噪音的方法给你打下来（然后就打下来了）</p><p>期待预期解，感觉可能是和 resize 有关的攻击。</p><figure class="highlight python"><table><tbody><tr><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">import</span> base64</span><br><span class="line"><span class="keyword">import</span> cv2</span><br><span class="line"><span class="keyword">import</span> random</span><br><span class="line"><span class="keyword">import</span> numpy <span class="keyword">as</span> np</span><br><span class="line"><span class="keyword">from</span> keras.models <span class="keyword">import</span> load_model</span><br><span class="line"><span class="keyword">from</span> copy <span class="keyword">import</span> deepcopy</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">model = load_model(<span class="string">'simplenn.model'</span>)</span><br><span class="line">    </span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">checkSkin</span>(<span class="params">img1, img2</span>):</span></span><br><span class="line">    output = []</span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">0</span>, <span class="built_in">len</span>(img1)):</span><br><span class="line">        <span class="keyword">for</span> j <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">0</span>, <span class="built_in">len</span>(img1[i])):</span><br><span class="line">            output.append(img2[i][j]-img1[i][j])</span><br><span class="line">    maxnum = <span class="number">0</span></span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> output:</span><br><span class="line">        num = <span class="number">0</span></span><br><span class="line">        <span class="keyword">for</span> j <span class="keyword">in</span> i:</span><br><span class="line">            <span class="keyword">if</span> j &gt;= <span class="number">200</span>:</span><br><span class="line">                j = <span class="number">255</span> - j</span><br><span class="line">            num = j</span><br><span class="line">            <span class="keyword">if</span> num &gt;= maxnum:</span><br><span class="line">                maxnum = num</span><br><span class="line">                index = i</span><br><span class="line">    <span class="comment"># print(index)</span></span><br><span class="line">    <span class="comment"># print(maxnum)</span></span><br><span class="line">    <span class="keyword">if</span> maxnum &gt; <span class="number">10</span>:</span><br><span class="line">        <span class="keyword">return</span> <span class="number">0</span></span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        <span class="keyword">return</span> <span class="number">1</span></span><br><span class="line">    </span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">checkMask</span>(<span class="params">img</span>):</span></span><br><span class="line">    predict = model.predict(img)</span><br><span class="line">    <span class="keyword">return</span> predict[<span class="number">0</span>][<span class="number">1</span>]</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">origin = cv2.imread(<span class="string">'dog.bmp'</span>)</span><br><span class="line">origin = np.expand_dims(origin, axis=<span class="number">0</span>)</span><br><span class="line">origin_f = origin.astype(np.float32) / <span class="number">255.</span></span><br><span class="line"></span><br><span class="line">best_img = cv2.imread(<span class="string">'best.bmp'</span>)</span><br><span class="line">best_img = np.expand_dims(best_img, axis=<span class="number">0</span>)</span><br><span class="line">best_score = checkMask(best_img.astype(np.float32) / <span class="number">255.</span>)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">mutation</span>(<span class="params">img</span>):</span></span><br><span class="line">    <span class="keyword">for</span> _ <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">1</span>):</span><br><span class="line">        x = random.randint(<span class="number">0</span>, <span class="number">127</span>)</span><br><span class="line">        y = random.randint(<span class="number">0</span>, <span class="number">127</span>)</span><br><span class="line">        z = random.randint(<span class="number">0</span>, <span class="number">2</span>)</span><br><span class="line">        d = random.randint(-<span class="number">10</span>, <span class="number">10</span>)</span><br><span class="line">        img[<span class="number">0</span>, x, y, z] = origin[<span class="number">0</span>, x, y, z] + d</span><br><span class="line">    <span class="keyword">return</span> img</span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span> best_score &lt;= <span class="number">0.999</span>:</span><br><span class="line">    img = mutation(deepcopy(best_img))</span><br><span class="line">    img_f = img.astype(np.float32) / <span class="number">255.</span></span><br><span class="line">    score = checkMask(img_f)</span><br><span class="line">    <span class="keyword">if</span> score &gt; best_score:</span><br><span class="line">        <span class="comment"># assert checkSkin(img[0], origin[0]) == 1</span></span><br><span class="line">        best_img = img</span><br><span class="line">        best_score = score</span><br><span class="line">        print(best_score, score)</span><br><span class="line">        cv2.imwrite(<span class="string">'best.bmp'</span>, best_img[<span class="number">0</span>])</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># img = deepcopy(origin)</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># while True:</span></span><br><span class="line"><span class="comment">#     score = checkSkin(img, cv2.imread('dog.bmp'))</span></span><br><span class="line"><span class="comment"># img = cv2.resize(img, (128, 128))</span></span><br><span class="line"><span class="comment"># img_tensor = np.expand_dims(img, axis=0)</span></span><br><span class="line"><span class="comment"># img_tensor = img_tensor.astype(np.float32)</span></span><br><span class="line"><span class="comment"># img_tensor /= 255.</span></span><br><span class="line"><span class="comment"># score += checkMask(img_tensor)</span></span><br><span class="line"><span class="comment"># print(score)</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># from pwn import *</span></span><br><span class="line"><span class="comment"># context(log_level='debug', os='linux')</span></span><br><span class="line"><span class="comment"># r = remote('82.156.190.31', 17271)</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># r.sendafter(b'&gt;', b'2')</span></span><br><span class="line"><span class="comment"># r.recvuntil(b'looks like\n')</span></span><br><span class="line"><span class="comment"># img_base64 = r.recvline()</span></span><br><span class="line"><span class="comment"># print(img_base64)</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># r.interactive()</span></span><br></pre></td></tr></tbody></table></figure><h2 id="Bleach"><a href="#Bleach" class="headerlink" title="Bleach!"></a>Bleach!</h2><blockquote><p>A picture in the music is cool?<br>16bit &amp; 400x400</p></blockquote><p>怎么有人的 IP 正好是 <code>flag</code> 这四个字符的 ASCII 码啊 hhhhh</p><p>提取所有和这个 IP 有关的 UDP 数据，从题目描述可以猜测是音视频之类的数据，于是找协议，找到了 RTP 协议。</p><p>使用 Wireshark 提取所有的 RTP 数据，拿到一个不知道是什么格式的数据。然后又试了好久才发现是 raw 的音频数据（想想也是，RTP 传的当然不可能是包装过的 MP3 或者 wav 文件）。</p><p>使用 Audacity 的导入原始数据功能导入原始数据，注意由题目可知这里的读取方式得设置成带符号 16 位浮点数，获得一个清晰的人声音频！将其导出成 wav 准备进行下一步，也就是「Picture in the music」。</p><p>直接说正解的方法：用 uint8 读取 wav 然后提取每一个帧的 LSB，提取总数为 <code>400*400</code>，然后用这么多的 01 数据打印成一张 <code>400*400</code>图，就能看到水印和 flag 了。实际上，直接对 raw 做 LSB 得到的图会更加清晰。</p><p><img src="https://cache.nan.pub/imgs/2022/04/25/20220425-191750.png" alt="image-20220425191748362" loading="lazy" style="max-width: 80%"></p><p>所以最后这步真的是很脑洞……做完闪到腰了</p><h2 id="Pixel"><a href="#Pixel" class="headerlink" title="Pixel"></a>Pixel</h2><p>通过观察 blue 通道发现每张图都有一些 blue 不为 255 的像素点，且在 512 张图中这种像素点的数量恰好等于一张图的数量且位置都不一样，于是尝试将这些特殊像素点连同 RGB 三个通道的数据都提取出来，得到了一张红色的图：</p><p><img src="https://cache.nan.pub/imgs/2022/04/25/20220425-193131.png" alt="image-20220425193130070" loading="lazy" style="max-width: 80%"></p><p>发现该图 red0 通道有异常，考虑 zig-zag 变换，也就是将像素点重新按照某个顺序排列。注意这里可能是 zig-zag 的逆操作，总之都试一遍准没错。然后得到这个：</p><p><img src="https://cache.nan.pub/imgs/2022/04/25/20220425-193312.png" alt="image-20220425193311070" loading="lazy" style="max-width: 80%"></p><p>中间的空格在暗示说，这题就是 Arnold’s cat map 变换，且重复参数为 1，横纵参数分别为 20 和 22。然后就做一次逆操作就得到最后的 flag 了。</p><p><img src="https://cache.nan.pub/imgs/2022/04/25/20220425-193515.png" alt="image-20220425193514590" loading="lazy" style="max-width: 80%"></p><h2 id="Connecting…"><a href="#Connecting…" class="headerlink" title="Connecting…"></a>Connecting…</h2><p><code>.obj</code> 有个错误的面，找到这个面对应的数据行之后，经过 hex2str 后发现是可以阅读的文字：</p><p><img src="https://cache.nan.pub/imgs/2022/04/25/20220425-185330.png" alt="image-20220425185329172" loading="lazy" style="max-width: 80%">、</p><p><code>.png</code> 的 xml 信息里面有一句 <code>&lt;rdf:li xml:lang="x-default"&gt;Thank Fabien Petitcolas For his work.&lt;/rdf:li&gt;</code> 十分诡异，经过查找发现 Fabien Petitcolas 写了 MP3Stego，那么可以猜测 <code>.obj</code> 得到的可阅读文字是 MP3Stego 的密码。</p><p>对 <code>sound.wav</code> 解密得到 flag。</p><p>做完感觉就是套娃 + 工具题。按照做题的逻辑感觉应该是先看图片后看模型，但 Hint 先给的是模型的提示。以及还以为会涉及到和 3D 模型有关的一些有趣的知识点，但最后并没有，比较遗憾。</p></body></html>]]></content>
    
    
    <summary type="html">&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;/body&gt;&lt;/html&gt;</summary>
    
    
    
    <category term="CTF" scheme="https://nano.ac/categories/CTF/"/>
    
    
    <category term="Misc" scheme="https://nano.ac/tags/Misc/"/>
    
    <category term="Writeup" scheme="https://nano.ac/tags/Writeup/"/>
    
  </entry>
  
  <entry>
    <title>2021 年度总结</title>
    <link href="https://nano.ac/posts/dfe03bab/"/>
    <id>https://nano.ac/posts/dfe03bab/</id>
    <published>2022-01-01T04:37:49.000Z</published>
    <updated>2025-06-19T02:33:56.905Z</updated>
    
    <content type="html"><![CDATA[<html><head></head><body><p>就只是随便写写而已。</p><p>这一年，自己结束了大学四年的本科生生涯，然后继续待在学校混吃混喝。不得不说 Security 真的好玩，硕士也好考（Machine Learning 和 Reinforcement Learning 在贵系真的太卷了）慢慢了解后发现自己也挺适合 Security 的，那么未来两年半希望自己能在这个领域多做一点有趣的工作吧！</p><p>生活环境也发生了变化，从紫荆 2 号楼搬到了南区 25 号楼，也慢慢适应了在新环境的生活。怎么说呢，有好也有坏，但是 24 小时不断电配上 NAS 真的太爽了！</p><p>这一年也是第一次完整的在恋爱中度过的一年。从跨年夜的依偎，到毕业前彼此给予对方的生日惊喜，到毕业旅行的秦皇岛，再到异地许久后相遇的国庆，和王小姐度过的时光总是如此快乐和幸福！希望明年能继续和她在一起 w</p><p>电影看了《又见奈良》《真爱至上》，动漫则是去补了《私に天使が舞い降りた！》，音乐会也听了 3 场，后面疫情也就没怎么参加了。</p><p>GitHub 今年搞了个新的项目「arknights-mower 明日方舟辅助器」，希望明年能继续保持开发，顺便练练手。</p><p>比赛的话这一年高强度参加了好多 CTF 比赛，但因为时间的问题也没好好复盘和整理，今年也没捧杯。除了 CTF，今年还参加了三次个人赛，一次是腾讯的极客挑战赛，一次是 DataCon2021 大数据安全分析竞赛，这两次都拿到了第一，另外还有一个 CCSP2021 惊险过了全国的金牌线，也算是都不亏了。</p><p>投资方面，今年最大的收益来自新能源，现在持有收益跑到了 +60.56%；最亏的是中概互联，一直在捞，捞到现在收益是 -24.99%。但今年还是赚得比较多，收益也有四位数，和去年一样跑赢大盘 8 个点，也算还行。希望明年能收益上五位数，也不知道会不会有其他更合适的投资方式。</p><p>2021 的年度计划完成率只有 4/23：个人资产翻了个番，GitHub 今年的 Contribution 也四位数了，CodeForces Rating 也成功刷新了自己的最高记录。除此之外，其他的年度计划好像都泡汤了……</p><p>最大的问题还是睡眠质量，依旧平均 3 点半入睡，丝毫没有变化，这样下去不行的啊……</p><p>总之要开始做新的年度计划了！</p></body></html>]]></content>
    
    
    <summary type="html">&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;/body&gt;&lt;/html&gt;</summary>
    
    
    
    <category term="Record" scheme="https://nano.ac/categories/Record/"/>
    
    
    <category term="Life" scheme="https://nano.ac/tags/Life/"/>
    
  </entry>
  
  <entry>
    <title>通过 Google API 自动添加 Google Calendar Events</title>
    <link href="https://nano.ac/posts/3c72b917/"/>
    <id>https://nano.ac/posts/3c72b917/</id>
    <published>2021-09-19T12:40:12.000Z</published>
    <updated>2025-06-19T02:33:56.908Z</updated>
    
    <content type="html"><![CDATA[<html><head></head><body><p>前段时间在更新网络学堂监控脚本时，加了一个自动添加作业截止日到谷歌日历的功能。之前是将作业推送到 Trello 然后再通过它官方自带的自动化流程添加到日历上，但因为 Trello 的 npm 源太老了，于是构思着直接通过 Google API 添加到日历，没有中间商赚差价。途中得益于 Google 企业级的文档，陆陆续续踩了一堆坑，现记录下来。</p><p>首先登陆 <a href="https://console.cloud.google.com/">console.cloud.google.com</a> ，新建一个新的项目，然后在该项目中启用「API 和服务」并在 API 库中搜索添加 Google Calendar API。</p><p><img src="https://cache.nan.pub/imgs/image-20210919211040061.png" alt="image-20210919211040061" loading="lazy" style="max-width: 80%"></p><p>接着在「API 和服务」里面添加凭据，如果只是自己一个人用的话推荐添加一个「服务账号」就可以了，不需要启用 OAuth 2.0。然后在「IAM 和管理」中导出该服务账号的秘钥下载到本地，如果没有就自己建一个，使用 SDK 登陆的时候需要用到。</p><p><img src="https://cache.nan.pub/imgs/image-20210919220007364.png" alt="image-20210919220007364" loading="lazy" style="max-width: 80%"></p><p>至此我们已经可以通过认证了，但为了让脚本能够修改日历，我们还需要在日历上给服务账号加权限。打开我们想要修改的日历的「共享与设置」，点击「与特定的人分享」中的「添加共享对象」，将服务账号的邮箱添加进去并给予「更改活动」的权限，这样服务账号也就能看到该日历了，给予更高一级的「进行更改和管理共享设置」权限也没问题。</p><p><img src="https://cache.nan.pub/imgs/image-20210919222204166.png" alt="image-20210919222204166" loading="lazy" style="max-width: 80%"></p><p>注意，因为服务账号本质上也是一个可用的账号（只是不能登录而已），也可以拥有自己的私人日历，而 API docs 上的 CalendarList 只能看到私人日历，所以通过服务账号使用 CalendarList 函数是看不到任何日历的，即便是刚给予权限的日历，也只能通过指定 CalendarId 的方式获取到日历实例。</p><blockquote><p>Update 2024/06/02：上述说明有误。CalendarList 只能看到私人日历和已确认的共享日历，所以需要通过调用 calendarList().insert 的方法将共享日历添加进列表内，然后就可以通过 CalendarList 获取到该共享日历了。具体可见 <a href="https://stackoverflow.com/questions/60036292/google-calendar-api-dont-return-shared-calendars">StackOverflow</a>。</p></blockquote></body></html>]]></content>
    
    
    <summary type="html">&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;/body&gt;&lt;/html&gt;</summary>
    
    
    
    <category term="Tutorial" scheme="https://nano.ac/categories/Tutorial/"/>
    
    
  </entry>
  
  <entry>
    <title>随机公交之旅</title>
    <link href="https://nano.ac/posts/b56e5c40/"/>
    <id>https://nano.ac/posts/b56e5c40/</id>
    <published>2021-08-21T18:33:25.000Z</published>
    <updated>2025-06-19T02:33:56.906Z</updated>
    
    <content type="html"><![CDATA[<html><head></head><body><p>这次终于有时间来玩一把随机公交挑战了！</p><p>规则很简单：从任意一个公交站出发，登上开过来的第一路公交，坐 5 站后下车然后登上下一趟进站的公交。如此反复进行许多轮，看看最后会到达哪里。当然，考虑到会遇到终点站、临时站以及公交线路站数过少等情况，又加了一些限定条件：</p><ol><li>遇到不足 5 站的终点站就在终点站下车，等待下一趟进站或发车的公交再上车。</li><li>临时站也属于可用站点。</li><li>不考虑专线和快专，因为其线路比较短，站数较少，且线路相对比较独立。</li></ol><p>事情的起因是因为看到了<a href="https://weibo.com/2076598077/KncKbraXe">这则微博</a>，才发现有这么有趣逇挑战。的确，北京城的公交线路大约有 1000 条，公交的覆盖范围也非常广，坐着公交可以去到京郊，甚至出省都有可能。于是，我挑了一个天气晴朗的下午，把这个心愿给了却掉！</p><p>吃过午饭，我就带着手机和充电宝出发了，出发点就在离我宿舍最近的「清华附中」。然后抛硬币决定出发的南北走向，结果是向南。很快我就等到了 365 路，到达了「中关村南」。我看了眼车站牌，大多线路都是一直向南，以为大概率还是沿着中关村大街南下到二环，而且应该不会那么快就遇到终点站提前下车的情况。结果没想到等来的第二趟车 355 路直接把我带去了第一个终点站「三义庙」。「三义庙」也不算是一个标准的终点站，因为还是有很多路公交途径此站，但 355 路直接把我出海淀区的计划给整没了，因为我现在的方向是向西。</p><p>在坐了 614 和 539 路之后，我到达了第二个终点站「巴沟村」。特别的事情是，这个站居然有三个站台，而且两两之间距离一两百米。我所处的位置在 B 站台，但 B 站台只有两路公交停靠，而且都是终点站，于是我只好步行前往 C 站台接着等公交。方向又从西变成了东。</p><p><img src="https://cache.nan.pub/imgs/Screenshot_20210822_014020.jpg" alt="三个站台的巴沟村" loading="lazy" style="max-width: 80%"></p><p>在坐了 644 和 129 路后，我到达了第三个终点站「西苑」。644 路带我游览了北京大学西门，顺便和 365 路一起对北大形成两面包夹芝士。129 路我就坐了一站，从「颐和园路东口」到「西苑」，是整个过程中最快下车的一次。还好这里作为交通枢纽，我还是很有希望能在不久后等到下一趟公交的。我想着这次应该能去过我没到过的地方了吧，毕竟西北旺哪一片我还没去过。然后，哈哈，我上了 563 路的车，上了香山路……靠我不要去香山啊，怎么随机去的地方也都是我去过的地方啊！！！</p><p><img src="https://cache.nan.pub/imgs/Screenshot_20210822_025903_com.ilyabogdanovich.geotracker_edit_425205517321055.jpg" alt="对北大形成两面包夹芝士" loading="lazy" style="max-width: 80%"></p><p>香山路上只有 563 和 932 路两路公交，且走向直到香山环岛都是一致的，也就是说，我只能在这条路上，轮流坐这两路公交，五站五站地前往香山。这也是为什么 563 路坐了两次的原因。</p><p><img src="https://cache.nan.pub/imgs/IMG_20210816_144319.jpg" alt="932路公交全线详情，于青龙桥站摄" loading="lazy" style="max-width: 80%"></p><p><img src="https://cache.nan.pub/imgs/IMG_20210816_145428.jpg" alt="563路公交全线详情，于三一六医院摄" loading="lazy" style="max-width: 80%"></p><p>在「北京植物园」站，我坐上了 360 路，在香山公园的门口途径了一下，就接着沿五环南下，在西山国家森林公园附近的「南河滩」下了车。然后特殊情况又发生了，360 路是有快线的，于是我又上了 360 快的公交，沿着闵庄路东行。在那时，我还以为我顶多会坐两次 360 路，但事实是我想多了。</p><p>「闵庄」下车后，就到了本次旅行最搞笑的部分了。坐上 630 路后，我也不知道为什么，少数了一站，坐了四站就下车了，但关键是下车的站叫做「四海桥」。这是一个设立于单行道的车站，也就是它只有一个方向，而且好巧不巧，经过这个站的线路中只有 630 路是东行的，其他都是西行的，也就是我要沿着闵庄路原路返回了。我才知道，原路返回的情况不单单只有终点站有哦！一想到我又要浪费一大堆时间在经过的路上就气（</p><p>然后 360 路来了，这次和上次的方向不一样，上次是东行的，这次是西行的。于是我第三次坐着 360 路第二次经过了闵庄路。</p><p>然后在「小屯」站，我等来了 528 路，坐了三站我到了终点站「天香颐北里」。这是个标准终点站，我也只能等到 528 路的发车，这也意味着我不用原路返回了！我又可以第三次经过闵庄路然后向东进入四环！命运真的好喜欢捉弄人啊 hhhhhh</p><p>（顺便请忽视我 528 路返程又只坐了四站就下车的愚蠢行为）</p><p>然后通过乘坐第四次 360 路，我终于如愿以偿接着往东进入四环，如愿以偿逃离海淀进入西城。后面随着公交车的南下，我也进入了三环甚至二环内，也到过北京西站，可能是因为交通枢纽的缘故吧。大概的顺序是西四环→西三环→西二环→南二环→南三环。这一段时间刚好也是下班高峰期，路也堵，车上人也多，自然而然也就只能都站着了。在城区内也有个好处，就是下一趟不用等太久，基本上刚下车不到一分钟下一趟的车也就来了。</p><p><img src="https://cache.nan.pub/imgs/IMG_20210816_173459.jpg" alt="路过的北京西站，同时也是第五个终点站" loading="lazy" style="max-width: 80%"></p><p>最后一段路程也十分有趣。在「方庄桥南」坐上 84 路，五站过后在「南窑」下车，然后我等来了一辆……嗯？夜 28 路？等会，夜 28 路晚上十一点多才发车，现在才七点，还没深夜怎么就发车了？而且这一路也没经过这站啊……那一瞬间百思不得其解。但车都来了，还停在我面前了，那我还是上吧。</p><p><img src="https://cache.nan.pub/imgs/IMG_20210816_185358.jpg" alt="傍晚的夜28路公交" loading="lazy" style="max-width: 80%"></p><p>上了车，再三确认了刷卡器的显示和车行驶的路线，我确信这还是一辆 84 路的公交车，虽然不知道外面显示的为什么是夜 28 路……坐了两站，到达最后的终点站「宋家庄枢纽站」，看着时间也不早了，况且宋家庄这里就有 5 号和 10 号地铁线可以坐，也就决定结束这次活动。</p><p>感觉还挺有趣的！人也被公交车从北四环运到了南三环，斜跨了整个北京城。随机因素也让路线千变万化，最后形成的线路把颐和园和北坞公园那一片都给围了起来，可惜不是围地游戏要不就赚大发了。以及高德的公交预测是真的准！整个过程也出现了很多在出发前未曾设想过的情节，同一条路来回经过了 3 次，坐了 26 趟公交车，搭乘了共 20 条不同的公交线路，同一路公交最多坐了 4 次，经过了 110 个不同的公交站，其中也包括 6 个终点站。</p><p><img src="https://cache.nan.pub/imgs/Geo-Tracker-2021-08-22-03-07-05.png" alt="最后的路线图" loading="lazy" style="max-width: 80%"></p><p>其实我也没怎么设想过最后的路线会是什么样的，就期待着能有随机的特殊事件带给我惊喜。因为这次我晚上还有事情，所以心里还是会觉得终点离市区越近越好，但假如有时间再来一次的话，我希望能离市区越来越远，很想体验北京的公交网路究竟覆盖得有多广！</p><p><img src="https://cache.nan.pub/imgs/randomly_transit.png" alt="整个过程涉及到的站点和路线" loading="lazy" style="max-width: 80%"></p></body></html>]]></content>
    
    
    <summary type="html">&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;/body&gt;&lt;/html&gt;</summary>
    
    
    
    <category term="Travel" scheme="https://nano.ac/categories/Travel/"/>
    
    
    <category term="Beijing" scheme="https://nano.ac/tags/Beijing/"/>
    
    <category term="Transit" scheme="https://nano.ac/tags/Transit/"/>
    
  </entry>
  
  <entry>
    <title>腾讯极客挑战赛第四期-鹅罗斯方块 复盘</title>
    <link href="https://nano.ac/posts/9339a170/"/>
    <id>https://nano.ac/posts/9339a170/</id>
    <published>2021-08-10T20:29:45.000Z</published>
    <updated>2025-06-19T02:33:56.908Z</updated>
    
    <content type="html"><![CDATA[<html><head></head><body><p>七月末的时候看到了<a href="https://cloud.tencent.com/developer/competition/introduction/10015">腾讯极客挑战赛第四期</a>，发现这不是俄罗斯方块嘛，是之前 Botzone 玩过的 AI 游戏，于是决定来玩玩。没想到一玩玩了好几天，最后的程序也和之前在 Botzone 写的 AI 完全不一样了，最后以 <code>1413876</code> 的分数拿到了外网赛道的第一，同时该分数也是内外网赛道的最高分。</p><p>这次的赛题叫做「鹅罗斯方块」，名字很有鹅厂风格。其实就是在一个 Web 版俄罗斯方块游戏中尽可能取得最高分。贴心的鹅厂还准备了正常缩进的源代码，查看源代码可以知道方块总数共 10000 个，落完则游戏结束。方块的下落序列则是由一个伪随机数生成器生成的，生成器的种子也是固定的，这意味着方块的下落顺序是固定的。</p><p>这次赛题与普通俄罗斯方块不同的还有不同种类方块的生成概率，长条的出现概率被人为调低到 2/29，而最难处理的 S 型和 Z 型出现概率则各被上调到 6/29。赛题在计分规则上相比普通游戏也有所变化，消除时场地格子越多，一次性消除的行数越多，得到的分数越多。想玩游戏的可以点击<a href="https://geek.qq.com/tetris/#/">此处</a>，但不知鹅厂哪个时候会关掉入口。</p><p><img src="https://cache.nan.pub/imgs/image-20210811044023397.png" alt="计分规则" loading="lazy" style="max-width: 80%"></p><p>首先从下落方块数量恒定这点来看，我们要追求的反而不是「不死」了，而是在游戏能够继续的基础上尽可能拿到高分，而绝大多数 Tetris AI 算法都只追求「不死」，所以套用 Tetris AI 算法得到的分数一般都不高。我把我在 Botzone 上天梯第二的程序稍微改了改跑了跑，得到的分数也只有 10w 不到（因为写得太优秀了全程方块高度就没超过 4……）那么我第一个想法就是修改估价函数了。</p><p>原本在 Botzone 上的 AI 算法是 MCTS 蒙特卡洛搜索外加估价函数。在魔改了估价函数之后，分数从 10w 到 60w 再到 107w，但距离当时排行榜的第一名 137w 还有点距离。不愿意在调参上浪费太多的我只好决定换个思路。</p><p>首先为了追求理论最高分，我不能再用 MCTS 了，如果用 MCTS 找理论最高分的操作我觉得到比赛结束都找不出。所以要搜索，要在包含 $10000\times2^{200}$ 种状态的游戏中搜索出最优解。</p><p>「开玩笑呢，这么多状态数怎么搜？」「剪枝啊！」</p><p>然后我觉得也许可以用分阶段广搜。简单来说，对于初始局面，我们先搜索出 3~4 步后的局面有哪些，然后使用估价函数对局面做一个删减，丢掉不好的局面，只留下分数高且估价高的局面用于后续的拓展。这其实相当于每搜几步就用估价函数 + 贪心做一次剪枝，整体状态数也可以保持在一个合理的范围内。</p><p>然后就遇到了一个问题：估价函数势必要包含「分数」和「格子数」，这两个因素越高越好，但消行带来的影响却是矛盾的，格子数减少了但是分数增加了，这就需要写一个很好的估价函数才能让搜索搜到那个最优解。</p><p>「但估价函数很难写啊，还要各种脑洞各种比对测试……」</p><p>所以我对算法本体开刀了。实际上，上面提到的分阶段广搜中初步搜索的深度是可以随意调整的，调得越深（例如先搜索 10 步后的局面有哪些）状态数越多，但越能避免最优解在中途因为局面不够优秀而被丢弃。而且，这个搜索深度其实也是可以不固定的。为了解决消行带来的估价问题，我把操作序列中两次消行之间的操作定义为「阶段」，而初步搜索就是负责处理单个「阶段」的搜索。</p><p>具体来说，这次的算法分为两层。第一层的搜索树上，游戏局面每一阶段拓展一次；第二层的搜索树上，游戏局面每一轮拓展一次。第一层搜索其实有点像动态规划，我新建了 $10000\times200$ 个桶用来放置游戏局面，定义状态 $\text{bucket}[x][y]$，其中 $x$ 是游戏轮数，$y$ 是场地格子数。每个桶实际上是一个优先队列，只保留前 10 优的游戏局面。第一层搜索每次从桶里拿出一个游戏局面进行第二层搜索，再将搜索结果放回对应的桶内。第二层搜索则是一个简单的广搜，用来搜索可能的「阶段」。第二层搜索对游戏局面进行广度优先搜索，直到出现消行操作才停止拓展，也就是说这棵搜索树的叶子都是经过某次消行操作后的游戏局面。第二层搜索结束后会把搜索树叶子上的游戏局面返回给第一层搜索进行更新。</p><p>如何判断游戏局面的优劣呢？对于在同一轮且格子数相等的游戏局面，我通过判断<strong>分数</strong>和<strong>行列变化次数</strong>来给各个局面排序，分数高的优先，分数一样的行列变化次数少的优先。</p><figure class="highlight c++"><table><tbody><tr><td class="code"><pre><span class="line"><span class="comment">// 返回场面上已被占用格子数</span></span><br><span class="line"><span class="function"><span class="keyword">inline</span> <span class="keyword">int</span> <span class="title">getGrids</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line">    boradGrids = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> y = <span class="number">1</span>; y &lt;= LIMITHEIGHT; y++)</span><br><span class="line">        boradGrids += bit(gridInfo[nowGame][y]) - <span class="number">2</span>;</span><br><span class="line">    <span class="keyword">return</span> boradGrids;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 返回场面上纵行方向上方块从有到无的变化次数，越少场面越平整</span></span><br><span class="line"><span class="function"><span class="keyword">inline</span> <span class="keyword">int</span> <span class="title">getTransitions</span><span class="params">(<span class="keyword">bool</span> recount = <span class="literal">true</span>)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line">    <span class="keyword">if</span> (recount) <span class="comment">// 重新计算该值</span></span><br><span class="line">    {</span><br><span class="line">        boardRowTransitions = <span class="number">0</span>;</span><br><span class="line">        boardColTransitions = <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">int</span> y = <span class="number">1</span>; y &lt;= LIMITHEIGHT; y++)</span><br><span class="line">            boardRowTransitions += bit(gridInfo[nowGame][y] ^ (gridInfo[nowGame][y] &gt;&gt; <span class="number">1</span>)) - <span class="number">1</span>, <span class="comment">// 各行中变换之和</span></span><br><span class="line">            boardColTransitions += bit(gridInfo[nowGame][y] ^ gridInfo[nowGame][y - <span class="number">1</span>]);        <span class="comment">// 各列中变换之和</span></span><br><span class="line">    }</span><br><span class="line">    <span class="keyword">return</span> boardRowTransitions + boardColTransitions;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>boardRowTransitions 是指对于每一行小方格，从左往右看，从无小方格到有小方格是一种「变换」，从有小方格到无小方格也是一种「变换」，这个属性则是各行中「变换」之和。同理 boardColTransitions 是每一列的变换次数之和，boardTransitions 是行列的变换次数总和。</p><p><img src="https://cache.nan.pub/imgs/image-20210811165903263.png" alt="boardTransitions" loading="lazy" style="max-width: 80%"></p><p>具体实现方面，本来想全部代码都整合到 C++ 中的，但因为前期写的各个算法模块的耦合度太高了，自己又不想重写，于是只好将两层搜索分开，Python 负责第一层搜索，C++ 负责第二层搜索。反正代码能用就行了嘛……</p><p>代码见 <a href="https://github.com/Konano/geekTencent-4-Tetris">GitHub</a>，注释应该都写得很详细了。</p><p>还有一个小插曲，其实在 MCTS 卡到 107w 的时候我已经不知道该如何写 MCTS 的估价函数了，然后听说鹅厂要直播一些选手们目前提交的精彩成绩，其中就有 137w 的操作记录，就守着直播守了一晚。虽然直播也只展示了后 1000 块的操作，但也启示了我后面的解题思路。鹅厂你干得好啊！</p></body></html>]]></content>
    
    
    <summary type="html">&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;/body&gt;&lt;/html&gt;</summary>
    
    
    
    <category term="Algorithm" scheme="https://nano.ac/categories/Algorithm/"/>
    
    
  </entry>
  
  <entry>
    <title>使用 OpenVPN 搭建虚拟局域网</title>
    <link href="https://nano.ac/posts/357a264/"/>
    <id>https://nano.ac/posts/357a264/</id>
    <published>2021-01-07T19:32:07.000Z</published>
    <updated>2025-06-19T02:33:56.907Z</updated>
    
    <content type="html"><![CDATA[<html><head></head><body><p>本文于 2021/12/11 00:09 进行了修改。</p><hr><p>寒假到了，为了能更方便地和朋友本地联机游戏，有必要用 OpenVPN 搭建一个虚拟局域网。</p><p>服务器使用的 Docker 来自 <a href="https://hub.docker.com/r/kylemanna/openvpn/">kylemanna/openvpn</a>，如果是 arm64 则使用 <a href="https://hub.docker.com/r/nubacuk/docker-openvpn/">nubacuk/docker-openvpn:arm64</a>。</p><p>首先新建一个文件夹：</p><figure class="highlight bash"><table><tbody><tr><td class="code"><pre><span class="line">mkdir openvpn</span><br><span class="line"><span class="built_in">cd</span> openvpn</span><br></pre></td></tr></tbody></table></figure><p>创建 <code>docker-compose.yml</code>，文件内容如下：</p><figure class="highlight yaml"><table><tbody><tr><td class="code"><pre><span class="line"><span class="attr">version:</span> <span class="string">'2'</span></span><br><span class="line"></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">openvpn:</span></span><br><span class="line">    <span class="attr">cap_add:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">NET_ADMIN</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">kylemanna/openvpn</span></span><br><span class="line">    <span class="comment"># image: nubacuk/docker-openvpn:arm64</span></span><br><span class="line">    <span class="attr">container_name:</span> <span class="string">openvpn</span></span><br><span class="line">    <span class="attr">ports:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">"1194:1194/udp"</span> <span class="comment"># 冒号左边为外部映射端口</span></span><br><span class="line">    <span class="attr">restart:</span> <span class="string">unless-stopped</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">./config:/etc/openvpn</span></span><br></pre></td></tr></tbody></table></figure><p>执行下面的命令初始化配置文件，把里面的 VPN.SERVERNAME.COM 换成你的域名或者 IP 地址，把 <code>xxx.xxx.xxx.0</code> 换成你想要自定义的子网范围，例如 <code>192.168.255.0</code>：</p><figure class="highlight bash"><table><tbody><tr><td class="code"><pre><span class="line">docker-compose run --rm openvpn ovpn_genconfig -u udp://VPN.SERVERNAME.COM -s xxx.xxx.xxx.0/24</span><br><span class="line">docker-compose run --rm openvpn ovpn_initpki</span><br></pre></td></tr></tbody></table></figure><p>运行期间你需要输入 CA pass phrase 并再次输入确认，生成完毕后同样也要再次输入刚刚设置的 pass phrase。</p><p>成功后会出现一个 config 文件夹，里面有个 openvpn.conf 文件，我们要做一些修改：</p><figure class="highlight bash"><table><tbody><tr><td class="code"><pre><span class="line"><span class="comment"># 修改 tap/tun，若是搭建虚拟局域网则推荐使用 tap，若是搭建代理隧道则推荐使用 tun</span></span><br><span class="line"><span class="comment"># dev tun0</span></span><br><span class="line">dev tap</span><br><span class="line"></span><br><span class="line"><span class="comment"># 将原本的 dns 设置注释掉</span></span><br><span class="line"><span class="comment"># push "block-outside-dns"</span></span><br><span class="line"><span class="comment"># push "dhcp-option DNS 8.8.8.8"</span></span><br><span class="line"><span class="comment"># push "dhcp-option DNS 8.8.4.4"</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 告诉客户端代理下面的 IP 段，这里换上之前自定义的子网范围  </span></span><br><span class="line">push <span class="string">"route xxx.xxx.xxx.0 255.255.255.0 vpn_gateway"</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 添加一条 允许客户端通过 VPN 互相通讯</span></span><br><span class="line">client-to-client</span><br><span class="line"></span><br><span class="line"><span class="comment"># 允许多客户端复用 .oven 文件</span></span><br><span class="line">duplicate-cn</span><br></pre></td></tr></tbody></table></figure><p>然后生成 .ovpn 文件：</p><figure class="highlight bash"><table><tbody><tr><td class="code"><pre><span class="line"><span class="comment"># 生成客户端密钥</span></span><br><span class="line">docker-compose run --rm openvpn easyrsa build-client-full client nopass</span><br><span class="line"><span class="comment"># 生成客户端 ovpn 文件</span></span><br><span class="line">docker-compose run --rm openvpn ovpn_getclient client &gt; client.ovpn </span><br></pre></td></tr></tbody></table></figure><p>成功后当前文件夹下会出现名为 <code>client.ovpn</code> 的配置文件。</p><p>配置文件默认是 <code>tun</code>，如果我们使用 <code>tap</code> 则需要将配置文件中的 <code>dev tun</code> 修改成 <code>dev tap</code>。</p><p>默认配置文件是全局代理，但我们只希望代理局域网的流量，不想代理上网流量。编辑配置文件删除下面一行（一般在最后一行）：</p><figure class="highlight bash"><table><tbody><tr><td class="code"><pre><span class="line">redirect-gateway def1</span><br></pre></td></tr></tbody></table></figure><p>在配置文件中添加下列几行，使得客户端能够自动重连：</p><figure class="highlight plain"><table><tbody><tr><td class="code"><pre><span class="line">resolv-retry infinite</span><br><span class="line">persist-key</span><br><span class="line">persist-tun</span><br></pre></td></tr></tbody></table></figure><p>在手机、电脑上安装好对应操作系统的 OpenVPN 客户端，把修改好的 .ovpn 配置文件下载到手机、电脑。最后用下面的命令启动 OpenVPN 服务器，测试一下连接。</p><figure class="highlight bash"><table><tbody><tr><td class="code"><pre><span class="line"><span class="comment"># 加 -d 可在后台运行</span></span><br><span class="line">docker-compose up</span><br></pre></td></tr></tbody></table></figure><p>如果没有问题的话，连接上的设备会被分配到之前自定义的子网范围内的某一个 IP，并且可以互相 ping 通。</p><p>如果使用的是 Windows，那还可能还遇到被系统防火墙拦截的情况，推荐进入防火墙的高级设定，增加一个入站规则，让整个子网变成白名单即可。</p><p>参考教程：<a href="https://eyhn.in/eyhn-network/">https://eyhn.in/eyhn-network/</a></p></body></html>]]></content>
    
    
    <summary type="html">&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;/body&gt;&lt;/html&gt;</summary>
    
    
    
    <category term="Tutorial" scheme="https://nano.ac/categories/Tutorial/"/>
    
    
  </entry>
  
  <entry>
    <title>CodeForces April Fools Day Contest 2020</title>
    <link href="https://nano.ac/posts/d9290083/"/>
    <id>https://nano.ac/posts/d9290083/</id>
    <published>2020-04-02T09:00:00.000Z</published>
    <updated>2025-06-19T02:33:56.907Z</updated>
    
    <content type="html"><![CDATA[<html><head></head><body><p>CodeForces 一年一度的愚人节场。</p><p><del>一场比赛学了两种奇怪的语言，我赚了 CodeForces 亏了</del></p><a id="more"></a><h2 id="A-Is-it-rated"><a href="#A-Is-it-rated" class="headerlink" title="A. Is it rated?"></a>A. Is it rated?</h2><h4 id="题目描述"><a href="#题目描述" class="headerlink" title="题目描述"></a>题目描述</h4><p>一道全空白的题。</p><h4 id="解法"><a href="#解法" class="headerlink" title="解法"></a>解法</h4><figure class="highlight python"><table><tbody><tr><td class="code"><pre><span class="line">print(<span class="string">'No'</span>)</span><br></pre></td></tr></tbody></table></figure><h2 id="B-Limericks"><a href="#B-Limericks" class="headerlink" title="B. Limericks"></a>B. Limericks</h2><h4 id="题目描述-1"><a href="#题目描述-1" class="headerlink" title="题目描述"></a>题目描述</h4><figure class="highlight plain"><table><tbody><tr><td class="code"><pre><span class="line">There was once young lass called Mary,  </span><br><span class="line">Whose jokes were occasionally scary.  </span><br><span class="line">On this April's Fool  </span><br><span class="line">Fixed limerick rules  </span><br><span class="line">Allowed her to trip the unwary.</span><br><span class="line"></span><br><span class="line">Can she fill all the lines</span><br><span class="line">To work at all times?</span><br><span class="line">On juggling the words</span><br><span class="line">Right around two-thirds</span><br><span class="line">She nearly ran out of rhymes.</span><br></pre></td></tr></tbody></table></figure><p>输入一个数，输出一个数。</p><h4 id="解法-1"><a href="#解法-1" class="headerlink" title="解法"></a>解法</h4><p>藏头诗，提取首字母可以得到「TWO FACTORS」，结合样例可推测需要将输入分解质因数然后从小到大不加空格输出。</p><figure class="highlight python"><table><tbody><tr><td class="code"><pre><span class="line">n = <span class="built_in">int</span>(<span class="built_in">input</span>())</span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">2</span>, n):</span><br><span class="line">    <span class="keyword">if</span> n % i == <span class="number">0</span>:</span><br><span class="line">        print(<span class="built_in">str</span>(i) + <span class="built_in">str</span>(n//i))</span><br><span class="line">        <span class="keyword">break</span></span><br></pre></td></tr></tbody></table></figure><h2 id="C-And-after-happily-lived-ever-they"><a href="#C-And-after-happily-lived-ever-they" class="headerlink" title="C. And after happily lived ever they"></a>C. And after happily lived ever they</h2><h4 id="题目描述-2"><a href="#题目描述-2" class="headerlink" title="题目描述"></a>题目描述</h4><p>输入一个数（$0\le a \le 63$），输出一个数。</p><h4 id="解法-2"><a href="#解法-2" class="headerlink" title="解法"></a>解法</h4><p>题目突破口在 a 的范围（$2^6$ 以内）和题目标题。标题经过搜索可知其顺序是乱序，恢复成原序为「and they lived happily ever after」，且标题单词个数刚好也为 6。猜测题目是想让我们将输入的数的二进制重复进行标题的置换方式，将第 2 位和第 6 位互换，将第 3 位和第 4 位互换，最后输出。</p><figure class="highlight python"><table><tbody><tr><td class="code"><pre><span class="line">p = [<span class="number">4</span>, <span class="number">1</span>, <span class="number">3</span>, <span class="number">2</span>, <span class="number">0</span>, <span class="number">5</span>]</span><br><span class="line">s = <span class="built_in">str</span>(<span class="built_in">bin</span>(<span class="built_in">int</span>(<span class="built_in">input</span>())+<span class="number">64</span>))</span><br><span class="line">print(<span class="built_in">int</span>(<span class="string">''</span>.join(s[<span class="number">8</span>-p[<span class="number">5</span>-i]] <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">6</span>)), <span class="number">2</span>))</span><br></pre></td></tr></tbody></table></figure><h2 id="D-Again"><a href="#D-Again" class="headerlink" title="D. Again?"></a>D. Again?</h2><h4 id="题目描述-3"><a href="#题目描述-3" class="headerlink" title="题目描述"></a>题目描述</h4><p>给一个类似「Axxxxxx」格式的数，输出一个数。</p><h4 id="解法-3"><a href="#解法-3" class="headerlink" title="解法"></a>解法</h4><p>好像是以前愚人节的套路，和 OEIS 无关。实际上对这个十六进制数取模 2 后的值输出就行了。</p><figure class="highlight python"><table><tbody><tr><td class="code"><pre><span class="line">print(<span class="built_in">int</span>(<span class="built_in">input</span>(),<span class="number">16</span>)%<span class="number">2</span>)</span><br></pre></td></tr></tbody></table></figure><h2 id="E-Jordan-Smiley"><a href="#E-Jordan-Smiley" class="headerlink" title="E. Jordan Smiley"></a>E. Jordan Smiley</h2><h4 id="题目描述-4"><a href="#题目描述-4" class="headerlink" title="题目描述"></a>题目描述</h4><p>给一张图和坐标，询问坐标是否在闭合曲线内。</p><p><img src="https://espresso.codeforces.com/f5d68ed69f4ec8fcc71db0c55cf6acb9860b5e4a.png" alt="img" loading="lazy" style="max-width: 80%"></p><h4 id="解法-4"><a href="#解法-4" class="headerlink" title="解法"></a>解法</h4><p>简单点的做法就是打开图片编辑工具，将曲线内的色块填充成除了白色以外的其他色，然后再写一个图像处理程序把图片处理成大小为 64*64 的 01 矩阵（Python 可以使用 <code>matplotlib</code>），最后便可以用算出的矩阵进行判断。</p><p>小科普：图像为若当曲线。相关链接：<a href="http://www2.oberlin.edu/math/faculty/bosch/making-tspart-page.html">http://www2.oberlin.edu/math/faculty/bosch/making-tspart-page.html</a></p><h2 id="F-Elementary"><a href="#F-Elementary" class="headerlink" title="F. Elementary!"></a>F. Elementary!</h2><h4 id="题目描述-5"><a href="#题目描述-5" class="headerlink" title="题目描述"></a>题目描述</h4><p>给一个字符串，输出 Yes 或 No。</p><h4 id="解法-5"><a href="#解法-5" class="headerlink" title="解法"></a>解法</h4><p>标题乍一看还以为和美剧《Elementary》有关，但实际上 Elementary 也有和化学元素相关的含义。结合样例判断，如果字符串是可以被分割成由各个元素构成的字符串的话输出 YES，否则输出 NO。</p><figure class="highlight python"><table><tbody><tr><td class="code"><pre><span class="line">s = <span class="string">'H,HE,LI,BE,B,C,N,...,MC,LV,TS,OG'</span>.split(<span class="string">','</span>)</span><br><span class="line">x = <span class="built_in">input</span>()</span><br><span class="line">a = <span class="number">1</span></span><br><span class="line">b = <span class="number">0</span></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="built_in">len</span>(x)):</span><br><span class="line">a, b = x[i] <span class="keyword">in</span> s <span class="keyword">and</span> a <span class="keyword">or</span> x[i-<span class="number">1</span>:i+<span class="number">1</span>] <span class="keyword">in</span> s <span class="keyword">and</span> b, a</span><br><span class="line">print([<span class="string">'NO'</span>,<span class="string">'YES'</span>][a])</span><br></pre></td></tr></tbody></table></figure><h2 id="G-Lingua-Romana"><a href="#G-Lingua-Romana" class="headerlink" title="G. Lingua Romana"></a>G. Lingua Romana</h2><h4 id="题目描述-6"><a href="#题目描述-6" class="headerlink" title="题目描述"></a>题目描述</h4><figure class="highlight plain"><table><tbody><tr><td class="code"><pre><span class="line">per nextum in unam tum XI conscribementis fac sic</span><br><span class="line">    vestibulo perlegementum da varo.</span><br><span class="line">    morde varo.</span><br><span class="line">    seqis cumula varum.</span><br><span class="line">cis</span><br><span class="line"></span><br><span class="line">per nextum in unam tum XI conscribementis fac sic</span><br><span class="line">    seqis decumulamenta da varo.</span><br><span class="line">    varum privamentum fodementum da aresulto.</span><br><span class="line">    varum tum III elevamentum tum V multiplicamentum da bresulto.</span><br><span class="line">    aresultum tum bresultum addementum da resulto.</span><br><span class="line"></span><br><span class="line">    si CD tum resultum non praestantiam fac sic</span><br><span class="line">        dictum sic f(%d) = %.2f cis tum varum tum resultum egresso describe.</span><br><span class="line">        novumversum egresso scribe.</span><br><span class="line">    cis</span><br><span class="line">    si CD tum resultum praestantiam fac sic</span><br><span class="line">        dictum sic f(%d) = MAGNA NIMIS! cis tum varum egresso describe.</span><br><span class="line">        novumversum egresso scribe.        </span><br><span class="line">    cis</span><br><span class="line">cis</span><br></pre></td></tr></tbody></table></figure><h4 id="解法-6"><a href="#解法-6" class="headerlink" title="解法"></a>解法</h4><p>题目是用 <code>Perligata</code> 写的程序（拉丁语版的 Perl），任务就是翻译代码，翻译完可知这是 Trabb Pardo–Knuth algorithm。（或者可以尝试插值求得计算公式……）</p><figure class="highlight python"><table><tbody><tr><td class="code"><pre><span class="line">t = []</span><br><span class="line"><span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line"><span class="keyword">try</span>: x = <span class="built_in">int</span>(<span class="built_in">input</span>()); t += [x]</span><br><span class="line"><span class="keyword">except</span>: <span class="keyword">break</span></span><br><span class="line"><span class="keyword">for</span> x <span class="keyword">in</span> t[::-<span class="number">1</span>]:</span><br><span class="line">print(<span class="string">'f(%d) = %s'</span>%(x, <span class="string">'%.2f'</span>%(<span class="number">5</span> * x**<span class="number">3</span> + <span class="built_in">abs</span>(x)**<span class="number">.5</span>) <span class="keyword">if</span> x&lt;<span class="number">5</span> <span class="keyword">else</span> <span class="string">'MAGNA NIMIS!'</span>))</span><br></pre></td></tr></tbody></table></figure><h2 id="H-It’s-showtime"><a href="#H-It’s-showtime" class="headerlink" title="H. It’s showtime"></a>H. It’s showtime</h2><h4 id="题目描述-7"><a href="#题目描述-7" class="headerlink" title="题目描述"></a>题目描述</h4><p>给一个数 $n=1000\times n+a(1 \le n,a \le 999)$，求 $n!!\pmod a$。</p><p>其中 $n!!=n(n-2)(n-4)\times…4·2$ 或 $n!!=n(n-2)(n-4)\times…3·1$。</p><p>题目要求使用一种未知的语言 <code>UnknownX</code> 完成题目。</p><h4 id="解法-7"><a href="#解法-7" class="headerlink" title="解法"></a>解法</h4><p>首先随便提交一个文本，当然啦肯定会编译错误，主要是要看编译错误：</p><figure class="highlight plain"><table><tbody><tr><td class="code"><pre><span class="line">Can't compile file:</span><br><span class="line">WHAT THE FUCK DID I DO WRONG:</span><br><span class="line">Invalid input 'H', expected Root (line 1, pos 1):</span><br><span class="line">Hello World!</span><br><span class="line">^</span><br></pre></td></tr></tbody></table></figure><p>结合题目和搜索引擎可以知道这是一种叫做 <code>ArnoldC</code> 的语言，代码非常大白话，具体语法可见于 <a href="https://github.com/lhartikk/ArnoldC%E3%80%82">https://github.com/lhartikk/ArnoldC。</a></p><figure class="highlight plain"><table><tbody><tr><td class="code"><pre><span class="line">IT'S SHOWTIME</span><br><span class="line">// begin</span><br><span class="line"></span><br><span class="line">HEY CHRISTMAS TREE a</span><br><span class="line">YOU SET US UP 0</span><br><span class="line">HEY CHRISTMAS TREE b</span><br><span class="line">YOU SET US UP 0</span><br><span class="line">HEY CHRISTMAS TREE c</span><br><span class="line">YOU SET US UP 0</span><br><span class="line">HEY CHRISTMAS TREE d</span><br><span class="line">YOU SET US UP 0</span><br><span class="line">HEY CHRISTMAS TREE e</span><br><span class="line">YOU SET US UP 0</span><br><span class="line">HEY CHRISTMAS TREE f</span><br><span class="line">YOU SET US UP 1</span><br><span class="line"># a = b = c = d = e = 0, f = 1</span><br><span class="line"></span><br><span class="line">GET YOUR ASS TO MARS a</span><br><span class="line">DO IT NOW</span><br><span class="line">I WANT TO ASK YOU A BUNCH OF QUESTIONS AND I WANT TO HAVE THEM ANSWERED IMMEDIATELY</span><br><span class="line"># a = read()</span><br><span class="line"></span><br><span class="line">GET TO THE CHOPPER b</span><br><span class="line">HERE IS MY INVITATION a</span><br><span class="line">HE HAD TO SPLIT 1000</span><br><span class="line">ENOUGH TALK</span><br><span class="line"># b = a // 1000</span><br><span class="line"></span><br><span class="line">GET TO THE CHOPPER c</span><br><span class="line">HERE IS MY INVITATION b</span><br><span class="line">YOU'RE FIRED 1000</span><br><span class="line">ENOUGH TALK</span><br><span class="line"># c = b * 1000</span><br><span class="line"></span><br><span class="line">GET TO THE CHOPPER a</span><br><span class="line">HERE IS MY INVITATION a</span><br><span class="line">GET DOWN c</span><br><span class="line">ENOUGH TALK</span><br><span class="line"># a = a - c</span><br><span class="line"># a, b = a % 1000, a // 1000</span><br><span class="line"></span><br><span class="line">HEY CHRISTMAS TREE isGreetThan0</span><br><span class="line">YOU SET US UP @NO PROBLEMO</span><br><span class="line"># isGreetThan0 = True</span><br><span class="line"></span><br><span class="line">HEY CHRISTMAS TREE n</span><br><span class="line">YOU SET US UP b</span><br><span class="line"># n = b</span><br><span class="line"> </span><br><span class="line">STICK AROUND isGreetThan0</span><br><span class="line"># while isGreetThan0 is True</span><br><span class="line"></span><br><span class="line">GET TO THE CHOPPER e</span><br><span class="line">HERE IS MY INVITATION f</span><br><span class="line">YOU'RE FIRED n</span><br><span class="line">ENOUGH TALK</span><br><span class="line"># e = f * n</span><br><span class="line"></span><br><span class="line">GET TO THE CHOPPER d</span><br><span class="line">HERE IS MY INVITATION e</span><br><span class="line">HE HAD TO SPLIT a</span><br><span class="line">ENOUGH TALK</span><br><span class="line"># e = e // a</span><br><span class="line"></span><br><span class="line">GET TO THE CHOPPER c</span><br><span class="line">HERE IS MY INVITATION d</span><br><span class="line">YOU'RE FIRED a</span><br><span class="line">ENOUGH TALK</span><br><span class="line"># c = d * a</span><br><span class="line"></span><br><span class="line">GET TO THE CHOPPER f</span><br><span class="line">HERE IS MY INVITATION e</span><br><span class="line">GET DOWN c</span><br><span class="line">ENOUGH TALK</span><br><span class="line"># f = e - c</span><br><span class="line"># f = f * n % a</span><br><span class="line"></span><br><span class="line">GET TO THE CHOPPER n</span><br><span class="line">HERE IS MY INVITATION n</span><br><span class="line">GET DOWN 2</span><br><span class="line">ENOUGH TALK</span><br><span class="line"># n -= 2</span><br><span class="line"></span><br><span class="line">GET TO THE CHOPPER isGreetThan0</span><br><span class="line">HERE IS MY INVITATION n</span><br><span class="line">LET OFF SOME STEAM BENNET 0</span><br><span class="line">ENOUGH TALK</span><br><span class="line"># isGreetThan0 = n &gt; 0</span><br><span class="line"></span><br><span class="line">CHILL</span><br><span class="line"># end while</span><br><span class="line"> </span><br><span class="line">TALK TO THE HAND f</span><br><span class="line"># output(f)</span><br><span class="line"> </span><br><span class="line">YOU HAVE BEEN TERMINATED</span><br><span class="line"># end</span><br></pre></td></tr></tbody></table></figure></body></html>]]></content>
    
    
    <summary type="html">&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;p&gt;CodeForces 一年一度的愚人节场。&lt;/p&gt;
&lt;p&gt;&lt;del&gt;一场比赛学了两种奇怪的语言，我赚了 CodeForces 亏了&lt;/del&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</summary>
    
    
    
    <category term="Tutorial" scheme="https://nano.ac/categories/Tutorial/"/>
    
    
    <category term="CodeForces" scheme="https://nano.ac/tags/CodeForces/"/>
    
  </entry>
  
  <entry>
    <title>大二下总结</title>
    <link href="https://nano.ac/posts/4a75de73/"/>
    <id>https://nano.ac/posts/4a75de73/</id>
    <published>2019-07-05T18:37:32.000Z</published>
    <updated>2025-06-19T02:33:56.907Z</updated>
    
    <content type="html"><![CDATA[<html><head></head><body><p>半个大学过去了，过得好快……</p><hr><p>开学前说 GPA 这次得努力上 3.3，毕竟前三个学期的 GPA 真的特别难看（2.80→2.85→2.95）</p><p>嗯好险学期结束前赶上了 GPA 改革，统一上调 +3，稳了</p><hr><p>嗯还拿了《最强大脑》的脑王</p><p>这个我也不知道该说啥，拿了就拿了呗，生活还是得过的</p><p>被不认识的人捧着吹其实蛮尴尬的，心里「我其实是个菜鸡啊你们捧错人了吧」</p><p>自己真的过于幸运啊，果然得好好把握机会</p><hr><p>期中考前可以说是一边参与节目录制一边学，影响还是蛮大的</p><p>毕竟节目比赛那边自己也得尽全力去练习项目，没有精力再去学其他的了</p><p>所以期中考就只能退了大三的计算机系统结构和去年没学成的微积分</p><p>然后就发现这学期学分直接掉到了 15 学分，养老学期</p><p>其实并不……上半学期没学的下半学期还是得补</p><hr><p>自习场所新增北馆，把书放桌子上溜去上课啥的方便到不行</p><p>相比之下文图就只能「临时离开」……</p><p>给文图写了个脚本，搭配 Telegram Bot 就可以在外实时获取文图剩余座位的情况</p><p><img src="https://cache.nan.pub/imgs/Snipaste_2019-07-07_16-19-05.png" alt="文图脚本" loading="lazy" style="max-width: 80%"></p><p>哦这 Bot 还可以抓 info 上的信息，懒得开 info 了</p><hr><p>还去配了台台式机，和老邢一起去中关村拿机</p><p>水是真的深，就算配机单上型号都写了，等会拿到手的型号还是不对，只能再三让老板拿正确的型号</p><p>配完之后宿舍的生活质量提高了不止一个层次，现在回宿舍都只想玩游戏了，因此也不敢呆宿舍了（</p><hr><p>照例对课程做个 Summary</p><h2 id="计算机图形学基础-4-0"><a href="#计算机图形学基础-4-0" class="headerlink" title="计算机图形学基础    4.0"></a>计算机图形学基础    4.0</h2><p>早八的课，就去了几次，但其实没去对你完成作业也没啥影响</p><p>以至于最后几节课去的人真的好少，即便 hsm 后面几次都会发课程公告（用公告提醒你上课还行</p><p>记忆最深刻的大概是有一次下课之后 hsm 叫住我，问我是计算机系的么，他以为我是清华附中过来旁听的……我还是第一次有这样的待遇</p><p>啊课能不上但是习题课还是得去，基本上会讲作业怎么写以及作业的给分规则</p><p>期末就是大作业，想得高分请内卷</p><p>图形学的大作业因为录节目也没办法提前做，半个学期错过了两场习题课</p><p>期末花了十天没日没夜写代码加功能，虽然最后还是觉得时间不够啊还有一万个功能想加</p><p>渲染也蛮花时间的但是可以买服务器啊，阿里云买个 32 核一天也就一百来块，适合 DDL 赶工</p><p>总之图形学真有趣</p><h2 id="物理实验B-2-3-3"><a href="#物理实验B-2-3-3" class="headerlink" title="物理实验B(2)    3.3"></a>物理实验B(2)    3.3</h2><p>上半学期因为录节目就没去做实验，只能回来后连续两周做了四场物理实验</p><p>跟负责处的老师请假，一半时间老师各种好奇地问我和节目有关的问题……</p><p>最烦的还是写实验报告，实验报告上 9 真的难，无解</p><p>要是全部都是实验没有报告就好了</p><h2 id="初等数论-4-0"><a href="#初等数论-4-0" class="headerlink" title="初等数论    4.0"></a>初等数论    4.0</h2><p>老师口音真的太重，辨识难度：地狱</p><p>期中考后想补交作业，结果助教表示期末前交给老师就行？？？好随便啊</p><p>把补交的作业交给老师，两次都被老师以「期中试卷还没批改完」的理由拒掉</p><p>作业 20% 期中 40% 期末 40%</p><p>期中 overfit 往年原题就行，简直白给</p><p>期末也是 overfit，但因为一堆没写明引用的「定理 X.X」被扣了好多分</p><p>老师仿佛不会用网络学堂一样，查卷线下查就算了，分数怎么也线下查啊？</p><p>我寻思都录到 Excel 打印出来了为啥就不能动动手传上学堂啊？？？？</p><p>一个尽是谜的课</p><h2 id="图书馆概论-P"><a href="#图书馆概论-P" class="headerlink" title="图书馆概论    P"></a>图书馆概论    P</h2><p>PF 课，特别摸鱼，干货一堆</p><p>上完才知道图书馆有那么多可以用的资源</p><p>以及我永远也忘不了老师在课前公开放节目片段，还请我上讲台的举动</p><p>这 是 公 开 处 刑</p><h2 id="二年级男生花样轮滑-2-6"><a href="#二年级男生花样轮滑-2-6" class="headerlink" title="二年级男生花样轮滑    2.6"></a>二年级男生花样轮滑    2.6</h2><p>轮滑场拆了所以上课地点变成了网球场</p><p>教的是花滑，我原来以为是速滑</p><p>老师一对一手把手教的飞升速度远大于自己对着桩练，所以得脸皮厚点多让老师指导指导</p><p>专项分也差不多是白给了，40/50 是基本，其余的十分练练一两次也能拿个六七分</p><p>然而学了一学期的轮滑，还是不会上马路，毕竟路面情况不一样（马路粗糙，网球场滑一点</p><p>跑步的确慢了，50 米没进 7 秒</p><p>以及引体居然能有 7 个</p><h2 id="数字逻辑电路-4-0"><a href="#数字逻辑电路-4-0" class="headerlink" title="数字逻辑电路    4.0"></a>数字逻辑电路    4.0</h2><p>课就没去过，纯靠师兄的作业原件和 PPT 撑着</p><p>得益于认真做笔记，期末复习起来也很轻松</p><h2 id="数字逻辑实验-3-6"><a href="#数字逻辑实验-3-6" class="headerlink" title="数字逻辑实验    3.6"></a>数字逻辑实验    3.6</h2><p>在宿舍明明好好的，现场检查就冒出一堆问题，还死活调不出来，结果重启一下就好了</p><p>助教特别凶，还经常让你现场修改功能再验收</p><p>期末一个是接电路，一个是编程，其实都不难</p><h2 id="毛概-3-3"><a href="#毛概-3-3" class="headerlink" title="毛概    3.3"></a>毛概    3.3</h2><p>家族史因为录节目就没及时交上，就变成了学期最后一个作业（指补交 DDL</p><p>每周定期多开线程刷慕课，题到最后才一口气全答了，有个公众号上有全套题的答案</p><p>小组讨论可以随便举个相关的例子然后让台上的同学说下看法，嗯举手发言次数也可以水（</p><p>期中期末论文都随便写写，查查文献就都能应付过去</p><p>后半周是去教室上课，听 fgz 讲故事不会那么困</p><p>虽然最后还是翘课无数</p></body></html>]]></content>
    
    
    <summary type="html">&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;/body&gt;&lt;/html&gt;</summary>
    
    
    
    <category term="Record" scheme="https://nano.ac/categories/Record/"/>
    
    
    <category term="Study" scheme="https://nano.ac/tags/Study/"/>
    
  </entry>
  
  <entry>
    <title>我不会做多重</title>
    <link href="https://nano.ac/posts/2ebeb75e/"/>
    <id>https://nano.ac/posts/2ebeb75e/</id>
    <published>2019-03-15T16:00:01.000Z</published>
    <updated>2025-06-19T02:33:56.906Z</updated>
    
    <content type="html"><![CDATA[<html><head></head><body><p>双倍真的是，没忍住啊没忍住……</p><hr><p>XMA HK 回到学校后，一边感叹怎么各个肝帝都纷纷转生了，一边看着学校内逐渐增多的各色多重，啊好想肝</p><p>不行不行要学习要学习，看了眼，还差 1kw ap，那就赶紧双倍刷掉一劳永逸彻底咸鱼（喂</p><p>拿出了程序算了下，得益于四 MOD 就找了个 40 out 的方案。程序就之前公开过的（<a href="https://bjres.net/2017/11/01/%E5%A4%A7%E5%8E%89%E5%AE%B3%E8%A7%84%E5%88%92%E5%B7%A5%E5%85%B7%E6%95%99%E7%A8%8B-%E5%A6%82%E4%BD%95%E4%BC%98%E9%9B%85%E7%9A%84%E8%AE%A1%E5%88%92%E5%A4%9A%E9%87%8D-tools/">程序使用说明</a>）</p><p><img src="https://cache.nan.pub/imgs/1543322433913.png" alt="1543322433913" loading="lazy" style="max-width: 80%"></p><p>啊之前刷 2000 Field 活动就是这么搞的（和消失了的大黄 QwQ</p><p>一套 25w ap，啊也就 40 套，简单简单…吗……1600 Keys + 160 SBULs + 40 绿毒……</p><p>掰 Key 掰了两天，也得到了很多人的帮助，有 @HeShaoNan，有提供底点「硅化木」一桶 Key 的 @hzhwcmhf，还有某不愿透露 ID 的蓝绿军小伙伴 wwww 一边掰一边清理仓位也勉强把 Key 装上了</p><p>物资抱了 @Nephilim1202 的大腿，去找 @GadisOo 拿物资，感谢小姐姐顺带告诉了我最近的一点点在哪（然而奶茶一不小心加太多糖了</p><p>晚上因为还要排练学生节，@zbw 帮忙清了障，这样就能少骑一点路</p><p>连完底部回宿舍穿多衣服，事实证明都没有用的，手脚该冻还是冻</p><p>连的时候以为三小时最多了，结果还是花了四小时，一轮 6mins 最快来着</p><p>某不愿透露 ID 的蓝军小伙伴还带来了一杯暖烘烘的花生奶 QwQ 好喝！（虽然喝完好想上厕所……</p><p>于是就这么一劳永逸升 16 了（有时间写作业了棒</p><p><img src="https://cache.nan.pub/imgs/1543324811659.png" alt="1543324811659" loading="lazy" style="max-width: 80%"></p><h2 id="接下来是唠唠嗑时间"><a href="#接下来是唠唠嗑时间" class="headerlink" title="接下来是唠唠嗑时间"></a>接下来是唠唠嗑时间</h2><p>（零散的语句，跳跃的思维）</p><p>玩这游戏一开始是因为，在某英文杂志看到了 Ingress 的介绍，描述里有说到 Ingress 和现实的地理标志物是有关系的，立刻心动！自己找资料，估摸着把谷歌三件套装好，成功进入游戏，觉得蓝色好看就选了蓝色。</p><p>最先带我刷刷刷的是给王（ID 我给忘了），带我去炸了炸绿军的多重，然而没炸炸不动。说好的把菊花炸剩三个脚留给我解决，然后他就控制不当提前收 AP……算了算了他早早 AFK 了，就不计较了。</p><p>然后就加进粤东懒军群，在两年前的双倍升了八。确定保送完的高三真的是各种浪，做了五重竹笋，高考完又秒速做了六重竹笋，盖了 n 次场。去到清华的时候还又做了一次三小时不到的单人六重竹笋，想必这事是能吹好久的吧。</p><p>Mission Day 参加了两次，一次是趁着国庆假期溜回家的厦门 MD，一次是去香港打 XMA 顺便搞定的 MD。正当有时间想参加更多 MD 的时候，国内 MD 没了……</p><p>去过好多地方啊……其实国门都没出过，但 UPC 居然钛牌了，希望哪天能在旅游的时候顺路变黑吧。</p><p>面基了真的超多超多人，好多人我都还有印象，很多都是旅游的时候面基的，人都超好 w 也吃了好多好吃的（旅游三大要素：风景、美食、任务和 Po 群）</p><p>任务狂魔，去到一处新的地方一定得做组任务，实在没时间也会摸一下标志物的 Key。Ingress 的任务有两大种吧，一个是分布于 Po 群的拼图 Missions，一个是分布于各个景点的拼图 Missions。前者完成难度简单但挺无聊的（哪个游客喜欢在破公园转来转去啊而且图好看的话我可以自己复刻啊）后者虽然累，但真的有旅游的感觉，边 Hack 边看风景边拍照，要是觉得无聊就溜，觉得好看就多呆一会，哪怕任务可能没时间做完（啊我还没遇到这种情况）所以这就让我养成了一个习惯：去到一处新的地方先看有啥任务</p><p>习惯攒 Key，知名景点啊啥的都会顺路收藏一把 Key，还有就是各地的火车站和机场……反正 Key 桶空着也是空着</p><p>Biocard 收集了满满一盒（感谢 Mission Day），自己的 Biocard 两年过去了还是没变，不会设计啊 QAQ 顺带一提，狗粮 Biocard 已经在路上了（是糖</p><p>炸多重 &gt; 建多重，我真的懒，炸只需要废炸，而且我喜欢边骑自行车边玩 Ingress，单手骑车基本操作（危险操作</p><p>嘛，Lv16 也算是一个终点了，以后就不会再去看 AP 条走了多少了，也不会有念头炸多重了吧</p><p>但游戏是不会卸载哒！约饭面基走起！！！</p><h2 id="后记"><a href="#后记" class="headerlink" title="后记"></a>后记</h2><p>后面这部分咕了四个月……而且还烂尾了……</p><p>其实吧，随着时间的推移，我承认自己对于 Ingress 的热情其实是在逐步减少的。有时候想想，假如 Ingress 倒闭了，那么这些游戏数据是不是就都遗失在网络上了，虽然现在还可以下载数据了，但这毕竟只是数据，它只是记录，而不是感官体验。</p><p>抛开数据不谈，Ingress 到现在还深深吸引我的原因就是它能带我探索这个世界。无论是身边的也好，还是未到达过的地方也好，我都能在 Ingress 中发现特别之处。Ingress 能让你快速了解一座城市，也能让你更进一步了解周围这片你自认为熟悉的土地。特别还有申请 Portals 的功能，我常常因为发现一处有历史意义的标志物而激动万分，并将其记录在 Portals 网络内，想和大家分享这处被人忽略被人遗忘的历史痕迹，这还挺有自豪感的。</p><p>「这个世界，并非你所看到的那样」，也许不单单指的是蓝绿阵营之争呢</p><p>也愿 Ingress 能让更多的人享受探索的乐趣 w</p></body></html>]]></content>
    
    
    <summary type="html">&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;/body&gt;&lt;/html&gt;</summary>
    
    
    
    <category term="Record" scheme="https://nano.ac/categories/Record/"/>
    
    
    <category term="Ingress" scheme="https://nano.ac/tags/Ingress/"/>
    
  </entry>
  
  <entry>
    <title>天津之行</title>
    <link href="https://nano.ac/posts/efb047c3/"/>
    <id>https://nano.ac/posts/efb047c3/</id>
    <published>2018-08-22T11:41:36.000Z</published>
    <updated>2025-06-19T02:33:56.907Z</updated>
    
    <content type="html"><![CDATA[<html><head></head><body><p>那诺今年的暑假只有三周不到，要比开学早那么十几天回北京也是蛮惨的 QwQ</p><p>查了查直飞北京的机票，还是超贵……那就老计划吧，飞到天津再坐京津高铁，既便宜又快，赞</p><p>等等……我好像还没去天津玩过诶（思索</p><a id="more"></a><h2 id="Day-0"><a href="#Day-0" class="headerlink" title="Day #0"></a>Day #0</h2><p>凌晨一点，正当那诺想睡觉的时候，手机响了。「航班取消预警」。？？？？？？？？</p><p>查了查，哦，天津机场天气不好……嘛，明早再看看是否真的取消。于是放心睡觉。</p><p>醒来后，航班真的提前取消了诶，嘛航司好险还提供一个改签航班的选择，时间改到中午而已。携程，提交改签申请！</p><p>然后就提示说失败了，没座位了？那只好 Plan C 了……飞去济南再坐高铁去天津（毕竟宾馆都订好了且还是无法取消的）</p><p>（PS：听说提前取消然后改签最好还是直接和航司联系，携程会坑）</p><p>于是坐着飞机，潮汕-长沙-济南（对，中间还经停了），然后打的去济南西站（打车比去天津的高铁还贵，早知道坐地铁了）</p><p>酒店房间内的蓝牙音箱好评！一个人住感觉气氛超棒！（然后我就把内置的爵士乐全部换成音游曲了，听着脑力洗澡，特破坏气氛）以及还附了一个魔方，完成三面就可以送一杯咖啡？这对我来说岂不是十分简单……（然后我就失败了</p><p><img src="https://cache.nan.pub/imgs/tianjin-15.jpg" alt="失败的六面魔方" loading="lazy" style="max-width: 80%"></p><h2 id="Day-1"><a href="#Day-1" class="headerlink" title="Day #1"></a>Day #1</h2><p>早起，拖延了一下，打算边做任务边逛。出发去西开总堂，以及面到了热心大佬 @Swdta，讨了点八炸，虽然大佬嫌太热在我做完一排任务后就溜了（</p><p><img src="https://cache.nan.pub/imgs/tianjin-7.jpg" alt="西开总堂" loading="lazy" style="max-width: 80%"></p><p>从西开总堂出发，沿着滨江道步行街，走到中心公园和瓷房子。瓷房子人超多，感觉都是旅游团的人，进去参观还要门票还要排队，就没进去，路过看了看。墙壁里满眼的瓷器，整座房子像是用瓷器建起来的。</p><p>接着随着任务去五大道，各种风格各异的欧陆风情小洋楼，曾经是民国初年的军政工商各界要人居住的地方。</p><p><img src="https://cache.nan.pub/imgs/tianjin-8.jpg" alt="伪满洲政府" loading="lazy" style="max-width: 80%"></p><p><img src="https://cache.nan.pub/imgs/tianjin-9.jpg" alt="天津外国语大学" loading="lazy" style="max-width: 80%"></p><p><img src="https://cache.nan.pub/imgs/tianjin-2.jpg" alt="疙瘩楼" loading="lazy" style="max-width: 80%"></p><p>任务做完稍微在大龙邮局（只是分局而已）休息了下，接着骑着自行车去天塔，骑到一半阴了一整天的天还下起了雨（我没带伞），也只是下了一会。</p><p><img src="https://cache.nan.pub/imgs/tianjin-10.jpg" alt="天塔" loading="lazy" style="max-width: 80%"></p><p>然后是水上公园，到的时候天已经是黑压压的一片，凉风习习。飞得低到快扑你脸上的蜻蜓，湖里游着的鸭，三三两两刚从湖里游泳上来的大爷，以及好像在守门的几只猫咪。</p><p><img src="https://cache.nan.pub/imgs/tianjin-3.jpg" alt="乌云下的水上公园" loading="lazy" style="max-width: 80%"></p><p><img src="https://cache.nan.pub/imgs/tianjin-4.jpg" alt="守门的猫咪！" loading="lazy" style="max-width: 80%"></p><p>此时 18:00，出发鼓楼。啊……先去南市食品街吃点本地特产。</p><p><img src="https://cache.nan.pub/imgs/tianjin-11.jpg" alt="南市食品街" loading="lazy" style="max-width: 80%"></p><p>然而最后只吃了点熟梨糕。</p><p><img src="https://cache.nan.pub/imgs/tianjin-13.jpg" alt="熟梨糕" loading="lazy" style="max-width: 80%"></p><p>鼓楼的时候，天已经下着大雨。晚上的鼓楼，商户都已经关门了，暗得不行，再加上下雨，就算买了把伞也无济于事。鞋子还是那种极其容易进水的（鞋底有个透气孔，虽然有滤网，但鞋子是能轻微变形的，踩得时候就直接吸水上来了 ……）</p><p><img src="https://cache.nan.pub/imgs/tianjin-12.jpg" alt="雨中的鼓楼" loading="lazy" style="max-width: 80%"></p><p>走到古文化街，emmmmm 虽然对外开放但是店铺都关了？天津是没有夜生活么？</p><p>然后此时我做了个后来极其后悔的决定……接一个新的拼图任务……然后做了一排就因为鞋子进水十分烦躁 + 雨下的很大 + 周围都是暗的十分寂静十分恐怖，溜了溜了回宾馆了（好险行李箱有一双新的鞋子</p><p><img src="https://cache.nan.pub/imgs/tianjin-14.jpg" alt="海河夜景" loading="lazy" style="max-width: 80%"></p><h2 id="Day-2"><a href="#Day-2" class="headerlink" title="Day #2"></a>Day #2</h2><p>早起，拖延了一会，出发去天大 &amp; 南开，并且约了 @冰清tsin 面基。然后在雨中依旧不爽得刷完了天大的一排任务就溜去南开（南开和天大居然是南北连通的诶</p><p>此时雨停了，精打细算用八炸，把南开全部 Po 刷了遍蓝。</p><p><img src="https://cache.nan.pub/imgs/tianjin-5.jpg" alt="南开大学" loading="lazy" style="max-width: 80%"></p><p>接着 BK 解决中午饭，下午直奔机厅打街机去了，一待就是一个下午。什么？Ingress？我干嘛下雨玩游戏？有 SDVX Jubeat maimai 打我干嘛去受苦？（实际是被昨晚吓怕了</p><p><img src="https://cache.nan.pub/imgs/tianjin-6.jpg" alt="BEMANICH" loading="lazy" style="max-width: 80%"></p><p>SDVX 还第一次考了段位，五段「天极」分析完成！达成率 115%！</p><p>晚上是坐火车回北京的，京津高铁的班次是真的多，以至于我到站再买票完全来得及也不用等太久，甚至还有时间绕着火车站前的花坛走了两圈刷出一个走路钛牌（</p><p>数据变化：步行 32km，UPC/V 676/775</p></body></html>]]></content>
    
    
    <summary type="html">&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;p&gt;那诺今年的暑假只有三周不到，要比开学早那么十几天回北京也是蛮惨的 QwQ&lt;/p&gt;
&lt;p&gt;查了查直飞北京的机票，还是超贵……那就老计划吧，飞到天津再坐京津高铁，既便宜又快，赞&lt;/p&gt;
&lt;p&gt;等等……我好像还没去天津玩过诶（思索&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</summary>
    
    
    
    <category term="Travel" scheme="https://nano.ac/categories/Travel/"/>
    
    
  </entry>
  
  <entry>
    <title>大一下总结</title>
    <link href="https://nano.ac/posts/825873c/"/>
    <id>https://nano.ac/posts/825873c/</id>
    <published>2018-07-23T03:01:21.000Z</published>
    <updated>2025-06-19T02:33:56.906Z</updated>
    
    <content type="html"><![CDATA[<html><head></head><body><p>依旧是菜鸡。</p><a id="more"></a><p>额……这次就单单针对课程做个 Summary 吧……</p><h2 id="课程"><a href="#课程" class="headerlink" title="课程"></a>课程</h2><p>2.76，167/224，75%</p><p>Shit 一般的成绩。</p><h3 id="大学物理B-1-0"><a href="#大学物理B-1-0" class="headerlink" title="大学物理B 1.0"></a>大学物理B 1.0</h3><p>大概是考得最惨最惨的一个科目了……我有罪，我没认真学……</p><p>大概就全天翘课+作业没写，然后期末的时候边补作业边预习边复习？</p><p>王山鹰老师讲的课的确很好……现在是有点后悔了……</p><h3 id="微积分A-N-A"><a href="#微积分A-N-A" class="headerlink" title="微积分A N/A"></a>微积分A N/A</h3><p>期中考？退课退课</p><h3 id="线性代数-3-3"><a href="#线性代数-3-3" class="headerlink" title="线性代数 3.3"></a>线性代数 3.3</h3><p>比上学期不知道简单到哪里去了！</p><p>但为啥还是没 3.7 啊……明明期中 95 的啊……</p><h3 id="史纲-3-3"><a href="#史纲-3-3" class="headerlink" title="史纲 3.3"></a>史纲 3.3</h3><p>有慕课的政治类课程是真的赞！坐在宿舍动动手就行了，耳机都不用插</p><p>最后的论文简直有剧毒，老师给了我们大概八十多个主题吧，选一个来写，那应该有简单的吧……</p><p>看了眼，都是高中历史课本上没有的事件……惊了</p><p>最后靠着知网和众多引用才凑齐 3000 字</p><h3 id="体育-2-0"><a href="#体育-2-0" class="headerlink" title="体育 2.0"></a>体育 2.0</h3><p>清华拳到底是在干啥的啊……感觉好搞笑</p><p>引体成功做上 4 个，阳光长跑依旧跑一半甩一半</p><p>这学期的作弊方式由「自行车甩手」升级成「虚拟 GPS+物理模仿步频」，就老邢在淘宝买了个微信计步作弊摆……</p><h3 id="人智-2-7"><a href="#人智-2-7" class="headerlink" title="人智 2.7"></a>人智 2.7</h3><p>大作业十分有趣，做得十分欢乐</p><p>第一个是做个输入法，就一个马尔科夫链模型；第二个是四子屏风棋，游戏 AI；第三个是神经网络，识别手写数字</p><p>基本上作业的分数都挺高的</p><p>上了第一节课就没去上了，也没作业，结果快到期末的时候才知道期末是笔试？？?</p><p>于是考前疯狂看 PPT 复习，最后期末考的时候还是把核函数的定义给记错了……于是就没 3.0 了</p><h3 id="自动机-3-0"><a href="#自动机-3-0" class="headerlink" title="自动机 3.0"></a>自动机 3.0</h3><p>同样是属于被期末坑了的人</p><p>考前要是有认真看样题估计问题就不会那么大了</p><h3 id="概率论-3-3"><a href="#概率论-3-3" class="headerlink" title="概率论 3.3"></a>概率论 3.3</h3><p>这个是期末考砸了，要不然还能有个 3.7</p><p>是整个学期少数的没有翘课的课</p><h3 id="OOP-3-0"><a href="#OOP-3-0" class="headerlink" title="OOP 3.0"></a>OOP 3.0</h3><p>也是没怎么去上课，作业就一边看 PPT一边改代码</p><p>大作业爆炸，延期了几个小时才交上，最后一天 Rush 了个通宵，从晚上七点到第二天早上六点，最后还是成功交上了一个能运行的版本……</p><h3 id="离散数学-3-3"><a href="#离散数学-3-3" class="headerlink" title="离散数学 3.3"></a>离散数学 3.3</h3><h3 id="大学生心理健康-3-3"><a href="#大学生心理健康-3-3" class="headerlink" title="大学生心理健康 3.3"></a>大学生心理健康 3.3</h3><hr><h2 id="So"><a href="#So" class="headerlink" title="So?"></a>So?</h2><p>除了大物其他还是蛮好的，达成了我初期的目标？「大部分课程都达到 3.0」</p><p>但 GPA 还是太低了啊，大学的 GPA 估计是拉不回来了，就只能争取一学年的 GPA 排名了吧接下来</p><p>哎…难受…</p></body></html>]]></content>
    
    
    <summary type="html">&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;p&gt;依旧是菜鸡。&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</summary>
    
    
    
    <category term="Record" scheme="https://nano.ac/categories/Record/"/>
    
    
    <category term="Study" scheme="https://nano.ac/tags/Study/"/>
    
  </entry>
  
  <entry>
    <title>大连 Ingress First Saturday</title>
    <link href="https://nano.ac/posts/e3724fa6/"/>
    <id>https://nano.ac/posts/e3724fa6/</id>
    <published>2018-06-07T07:03:45.000Z</published>
    <updated>2025-06-19T02:33:56.906Z</updated>
    
    <content type="html"><![CDATA[<html><head></head><body><p>出门的理由：没参加过周边的线下活动；没参加过 IFS；没去过大连；期末到了想拖延症下……啊去玩的话哪里需要这么多理由（</p><p>花了六个小时做完攻略才知道来回要 800+……一狠心还是把票给买了</p><h2 id="出发"><a href="#出发" class="headerlink" title="出发"></a>出发</h2><p>周五上完最后一节课之后，直接前往火车站。</p><p>在火车上把下学期的课给选了，因此还差点晕火车……</p><p>睡了一觉，早上八点到的大连，下了火车先去肯德基吃早餐（大爱潮汕风味的粥啊干贝真好吃！以及居然有虾！！！</p><p>好像有点早，胜利广场好像都没怎么开，给人一种这里倒闭了的错觉</p><p>先开启铁脚模式做任务拼图【有轨电车系列】（走了估计不止三公里</p><p>中山广场从地图上看是一个圆圈，周围道路呈放射性，中间有个测位点还是</p><p>以及广场好多鸽子！我没买面包！！为什么没有卖鸟料的！！！</p><p>拼图做完，去做有轨列车 201 路。等车的时候看到了那种旧式的电车从马路对面驶过！！</p><p>然而等到的电车是……现代式的……干</p><p>第二个拼图是【Sky Calendar 1】，图神极力推荐的第一套，大概就是在胜利广场的平台上走个十几来圈吧，大夏天的真的晒</p><p>以及各种升脚任务……我到的时候这里的 Po 都是 88765544 的架势……升个鬼啊（绿毒 -5</p><p>做完就赶着去劳动公园合影了，期间路过才知道有地下商业街，四通八达完全摸不着头脑</p><p>公园内先是遇上了准备去接我的图神，然后就见到了大部队，Artemisssss 小姐姐好激动！（声音好好听啊</p><p>换 Biocard 的时候，拿出了自己的卡，发觉好像带太少了于是就拿出了校庆卡，一瞬间！可能就几秒钟！！被抢没了！！！（我的卡这么不受欢迎的么 QwQ</p><p>约饭去吃自助海鲜火锅，虽然啥海鲜我都没选，选了成吨的肉。火锅就是要吃肉啊！！！不接受反驳</p><p>有一个桌子坐的好像都是宅诶……同好！（于是掏出了音游</p><p>吃完回公园刷 IFS 拼图，和 Artemisssss 组了个队。刷完大家又坐在树荫下打音游（？？？？？</p><p>自己顺便去刷了凑字拼图【啥！你喜欢我】</p><p>接着才发现已经下午五点了，原先计划的星海广场和渔人码头……啊干脆都不去了，约饭要紧约饭要紧</p><p>好像此时来了受姐姐（不知道对不对</p><p>话题转到日语的时候，感觉整个大连的人都会说日语……</p><p>接着几个人去了东港，路过音乐喷泉，还没开始，大家说着等会回程的时候过去看啊（然而并没有</p><p>前往十五库（感觉人的确很少，用来作为拍照的场所很合适啊</p><p>啊海边真是美，特别还有日落 Buff，拍照拍照</p><p>找了家日式餐馆吃了牛肉，返程的时候说着要去酒吧，太困了于是我就回宾馆休息去了</p><p>锦江之星的房子好赞！装饰风格给人的感觉就很舒服，不像是廉价宾馆的风格</p><p>突然被拉到某行动组群，以及和一个在旅顺读书的蓝莓约好一起逛旅顺博物馆</p><p>晚上 work 到三点就睡了</p><p>早上六点半醒，发现附近居然有罗森便利店！早餐三明治解决</p><p>坐地铁去和图神拿东西，拿到了电信卡一张（听图神说老铁山那边信号除了电信可能会有一点信号以外其余都没有）和一把 Key（然后我游戏就莫名登陆不进去了……</p><p>坐着轻轨+打车，到了老铁山，售票阿姨翻来覆去看着我的学生证，大概是不相信怎么会有 THU 的周日跑去那里玩吧</p><p>需要爬一下山，然而昨天走得有点远，这一点爬坡我都要死要死的，还有就是好热啊</p><p>天气很好，虽然并不能看到山东，但是看黄渤海的分界线还是看得见的，两边的海水的确颜色差别很明显啊，黄海明显蓝点</p><p>以及信号屏蔽是增强了还是，电信也没信号……最后利用我手头两个手机外加一笔记本，搭个 WIFI 通道，才勉强摸得到 Po（然而结果是不可连接……</p><p>就因为这破信号花了我半个小时，蓝莓已经到博物馆了喂</p><p>打车前往博物馆（顺便一提打车是真的便宜</p><p>和蓝莓逛旅顺博物馆，还以为能看到二战时候的资料，结果怎么是石器时代到唐朝的文物？？？？很多还不是本地的……</p><p>飞速逛完，吃完午饭（拉面！）去旅顺火车站吧</p><p>以为火车站已经废弃了，结果里面居然还有个售票处！虽然月台已经不对外开放了（翻墙进还是挺轻松的）很赞！</p><p>爬上白玉山，在白玉塔前面又见到了一群鸽子，但这鸽子皮多了，完全不怕人，直接就停在手臂上的那种……撒玉米粒的时候总有种它们要飞过来骑你脸上的感觉（</p><p>在山顶上能看到旅顺口，的确是地理位置十分险要，以及看到了好多造船厂</p><p>中苏友谊碑隔壁就是公共篮球场？？？</p><p>嗯然后就和蓝莓告别，坐轻轨回火车站，在星爸爸坐了休息下顺便冲下点，进火车站等车（安检是真的松啊）</p><p>回校赶体育课啊啊啊啊！</p><h2 id="结束"><a href="#结束" class="headerlink" title="结束"></a>结束</h2><p>图呢？放 Ins 上了（</p></body></html>]]></content>
    
    
    <summary type="html">&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;/body&gt;&lt;/html&gt;</summary>
    
    
    
    <category term="Travel" scheme="https://nano.ac/categories/Travel/"/>
    
    
    <category term="Ingress" scheme="https://nano.ac/tags/Ingress/"/>
    
  </entry>
  
  <entry>
    <title>四子棋作业实验报告</title>
    <link href="https://nano.ac/posts/de5be7e5/"/>
    <id>https://nano.ac/posts/de5be7e5/</id>
    <published>2018-05-14T16:15:50.000Z</published>
    <updated>2025-06-19T02:33:56.906Z</updated>
    
    <content type="html"><![CDATA[<html><head></head><body><h2 id="概况"><a href="#概况" class="headerlink" title="概况"></a>概况</h2><ul><li><p>本文是一篇实验报告，主要介绍四子棋 AI 算法方面的思考和设计。</p></li><li><p>算法主体为蒙特卡洛树搜索，并在其中加入了 UCT 置信公式辅助选择子结点。</p></li><li><p>目前算法在 1 秒时限内与测试集的 50 个 dll 随机对战时胜率高达 95.6%，与 92,94,96,98,100 编号的 dll 对战时胜率高达 90%</p></li><li><p>代码：<a href="https://github.com/Konano/homework-connect-four">https://github.com/Konano/homework-connect-four</a></p></li></ul><h2 id="算法设计"><a href="#算法设计" class="headerlink" title="算法设计"></a>算法设计</h2><h3 id="UCT-改良"><a href="#UCT-改良" class="headerlink" title="UCT 改良"></a>UCT 改良</h3><p>算法为最为基础的蒙特卡洛树搜索，其中选择策略中运用了 UCT。这部分直接按照维基百科上所描述的实现即可。然而在实际运用中 UCT 也存在其问题，那就是在开头尝试次数过于少时容易产生的「偏见」。</p><p>UCT 每次选取的都是具有表达式最大值的子结点，但最后结果的选定还是会看各个子结点的胜率。所以会出现这样一种情况：某次优子结点在一开始被多次选中，其次数和胜率都比最优子结点高；到了中后期，最优子结点在 UCT 的补助下仍然会被选中几次，但其胜率增加量要小于次数增加所导致的补助量的下降，这就导致最优子结点无法被多次选中，其胜率无法通过多次尝试而纠正，从而使得算法最后选择了次优子结点。</p><p>对此我采取的解决方案是：假如有子结点未扩展过，则采取随机选择子结点扩展；当全部子结点都扩展了至少一遍后，使用 UCT 选择子结点扩展。实践发现这样能较大程度避免上文提到的「偏见」，但还是无法避免。</p><h3 id="制胜位"><a href="#制胜位" class="headerlink" title="制胜位"></a>制胜位</h3><p>对于游戏本身，我引入了一个概念：「制胜位」。一个格子是某玩家的制胜位，当且仅当该玩家把棋子放在这个格上时能形成四子连棋。</p><p>对于一个局面我们可以显然得到以下结论：</p><ul><li>假如我有制胜位，那么我直接把棋子下到制胜位即可取得胜利</li><li>假如我没有制胜位，且对方有两个制胜位，则直接告负，不继续扩展</li><li>假如我没有制胜位，且对方只有一个制胜位，那我必将把棋放置在对方的制胜位上</li></ul><p>这样便能去除大量无用的扩展节点，使得搜索范围更加广泛，搜索深度更加深。</p><h2 id="实验结果"><a href="#实验结果" class="headerlink" title="实验结果"></a>实验结果</h2><p>我用改良后的算法随机选择测试库中的某个 dll 进行对战，共进行 250 轮。</p><p>其在对战不大于 30 的 dll 中做到了全胜，在 30-90 的区间的 dll 则偶尔会丢掉一局，在 90-100 则有 90% 的胜率。</p><table><thead><tr><th></th><th>(0,10]</th><th>(10,20]</th><th>(20,30]</th><th>(30,40]</th><th>(40,50]</th></tr></thead><tbody><tr><td>胜率</td><td>100%</td><td>100%</td><td>100%</td><td>98%</td><td>98%</td></tr></tbody></table><table><thead><tr><th></th><th>(50,60]</th><th>(60,70]</th><th>(70,80]</th><th>(80,90]</th><th>(90,100]</th></tr></thead><tbody><tr><td>胜率</td><td>90%</td><td>98%</td><td>88%</td><td>96%</td><td>90%</td></tr></tbody></table><p>由于对战局数少，此处概率并不能准确表现出算法的真实水平。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>算法在未经过常数优化，甚至是在使用一半不到的时限内，就已经做到如此高的水平，这大大出乎我的预料。</p><p>对于这类小型游戏求解，蒙特卡洛树搜索跑 0.5s 和 3s 出来的结果其实优劣相差甚少，所以我预感常数优化应该不会给算法带来巨大的飞跃。</p><p>反而重要的是算法本身。对选择策略进行了改良后，被忽视的最优子结点的数量大大减小；加入了「制胜位」之后，搜索质量更是直接上了一个台阶。这两个改进直接使得胜率从 40% 上升到 95%。</p></body></html>]]></content>
    
    
    <summary type="html">&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;/body&gt;&lt;/html&gt;</summary>
    
    
    
    <category term="Homework" scheme="https://nano.ac/categories/Homework/"/>
    
    
    <category term="MCTS" scheme="https://nano.ac/tags/MCTS/"/>
    
  </entry>
  
  <entry>
    <title>拼音输入法编程作业实验报告</title>
    <link href="https://nano.ac/posts/56881cfd/"/>
    <id>https://nano.ac/posts/56881cfd/</id>
    <published>2018-05-01T16:15:50.000Z</published>
    <updated>2025-06-19T02:33:56.906Z</updated>
    
    <content type="html"><![CDATA[<html><head></head><body><h2 id="概况"><a href="#概况" class="headerlink" title="概况"></a>概况</h2><ul><li><p>本文是一篇实验报告，主要介绍了这几天以来我对拼音输入法的设计和思考。</p></li><li><p>算法实现基于字的二元模型，使用基于 sina news 的汉语语料库进行模型训练。</p></li><li><p>对于拼音到汉字的转换，目前算法可以轻松实现 80% 以上的准确率。</p></li><li><p>Git 仓库地址：<a href="https://github.com/Konano/homework_input_method/tree/master/release">https://github.com/Konano/homework_input_method/tree/master/release</a></p></li></ul><h2 id="算法初步实现"><a href="#算法初步实现" class="headerlink" title="算法初步实现"></a>算法初步实现</h2><h3 id="一、对训练数据的处理"><a href="#一、对训练数据的处理" class="headerlink" title="一、对训练数据的处理"></a>一、对训练数据的处理</h3><p>原语料库文件为 JSON 格式，一条 JSON 信息包含一条新闻。但每条新闻并不是单纯地由汉字构成，其内混杂了各种标点符号、数字、字母等非汉字字符，很难被直接利用，所以在训练前需要对原始语料进一步处理。</p><p>我将 JSON 的新闻内容提取出来后，将数字、字母和各种字符删去。其中的难点在于如何区别中文标点符号和汉字，在 ANSI 编码下中文标点符号和汉字都为双字节，最后在多次查验后确定了中文标点符号的编码范围，从而将其去除。</p><p>经过处理，便得到了可直接使用的句子片段。</p><h3 id="二、训练"><a href="#二、训练" class="headerlink" title="二、训练"></a>二、训练</h3><p>调用 <code>training.exe</code> 程序进行训练，主要是统计训练数据中单字出现频率和相邻二字所组成的二元组频率，分别保存到 <code>1-gram</code> 和 <code>2-gram</code> 文件内。拼音方面则是直接使用了提供的拼音汉字表。</p><h3 id="三、实现解码算法"><a href="#三、实现解码算法" class="headerlink" title="三、实现解码算法"></a>三、实现解码算法</h3><p>解法算法使用了 Viterbi 算法，该算法基于动态规划思想；并使用最大似然估计作为其概率。为了解决样本数量较少的问题，我还引入了 Laplace 平滑，缓解数据稀疏所带来的问题。</p><h2 id="进一步改进"><a href="#进一步改进" class="headerlink" title="进一步改进"></a>进一步改进</h2><h3 id="一、多音字处理"><a href="#一、多音字处理" class="headerlink" title="一、多音字处理"></a>一、多音字处理</h3><p>在对第一版程序（v1）进行测试后发现，转换结果中多音字的错误尤其多。这可能是由于训练的时候未对语料进行正确注音导致。</p><p>于是我调用了 python 下的 <code>pypinyin</code> 对语料进行注音。同时为了提升多音字注音正确性，我先调用了 python 下的「结巴分词」对语料进行分词再注音。训练的时候不再以单字作为基本单位，而是以单字单音作为基本单位，这样便将多音字分成多个单体，从而避免多音字内生僻读音的概率受常用读音的影响。</p><h3 id="二、句首句尾概率"><a href="#二、句首句尾概率" class="headerlink" title="二、句首句尾概率"></a>二、句首句尾概率</h3><p>训练的时候进一步统计了每个字在句首和句尾的频数。假如给句首和句尾多定义一个特殊字符的话，出现在句首和句尾的情况也可以看成是一种二元关系。解码的时候充分利用此信息，能使得转换后的句子开头和结尾不会太突兀。</p><h3 id="三、增加训练集"><a href="#三、增加训练集" class="headerlink" title="三、增加训练集"></a>三、增加训练集</h3><p>由于训练集多为新闻，在转换非新闻的句子（例如日常用语）的时候准确率就十分一般。所以我搜集了另外一些日常用语作为训练集的补充。</p><h3 id="四、基于字的三元模型"><a href="#四、基于字的三元模型" class="headerlink" title="四、基于字的三元模型"></a>四、基于字的三元模型</h3><p>尝试更高阶的 HMM 模型。训练的时候增加对相邻三字所构成的三元组的频数统计。难点在于不同三元组的数量过多，很难在程序内全部存下。我采取的解决方案是，先统计单个子训练集内出现的三元组信息，并有序输出到外部文件内；再对多个外部文件进行合并，取频数前 5,000,000 的三元组并储存。解码的时候以相邻二字为状态，并使用 Viterbi 算法解码。</p><h3 id="五、基于词的一、二元模型"><a href="#五、基于词的一、二元模型" class="headerlink" title="五、基于词的一、二元模型"></a>五、基于词的一、二元模型</h3><p>基于处理语料时的分词，我初步实现了基于词的一、二元模型的算法。由于词与词的二元关系实在过于庞大，在训练时采用了和处理字的三元关系一样「先单个再合并」的做法。解码方面则依旧使用 Viterbi 算法。</p><h3 id="六、平滑算法"><a href="#六、平滑算法" class="headerlink" title="六、平滑算法"></a>六、平滑算法</h3><p>在处理字三元组和词二元组的时候，数据稀疏性的问题越发突出，于是尝试使用 Good-Turing 估计和 Katz 回退法取代最大似然估计来计算其出现概率。</p><h2 id="实验效果"><a href="#实验效果" class="headerlink" title="实验效果"></a>实验效果</h2><p>由于作业附件并没有提供测试数据，于是我手动从最近的新闻中摘录了一些新闻片段组成了测试集，单字个数 4880，句子总数 122。各版本测试结果如下：</p><table><thead><tr><th></th><th>准确率</th></tr></thead><tbody><tr><td>1.字二元模型</td><td>82.541%</td></tr><tr><td>2.字二元模型 + 一</td><td>84.8566%</td></tr><tr><td>3.字二元模型 + 一 + 二</td><td>84.9795%</td></tr><tr><td>4.字二元模型 + 一 + 三</td><td>84.7336%</td></tr><tr><td>5.字二元模型 + 一 + 二 + 三</td><td>84.6721%</td></tr><tr><td>6.字三元模型 + 一 + 二</td><td>74.629%</td></tr><tr><td>7.词一元模型 + 一 + 二</td><td>61.6598%</td></tr><tr><td>8.词二元模型 + 一 + 二</td><td>86.168%</td></tr><tr><td>9.词二元模型 + 一 + 二 + 六</td><td>72.9158%</td></tr></tbody></table><p>从测试结果能够得到的信息有：</p><ul><li>对于多音字的处理可以看出是有效果的，准确率有所提高。</li><li>第 2 个和第 4 个的比较中可以看出新增和测试集（多为新闻）交集较小的训练集（多为日常用语）会降低准确率。当然实验结果两个版本准确率只相差了 0.1%，也不排除是数据正常误差导致。</li><li>字三元模型的准确率反而比最基本的字二元模型准确率要低。个人认为有三点原因：一是数据稀疏性；二是数据量过大无法全部储存；三是可能程序内三元 Viterbi 算法实现有误。</li><li>尝试给字二元和词二元模型更换平滑算法，换成 Katz 回退法，但是准确率反而下降了近 10%。尚不清楚是什么原因导致的。</li></ul><h2 id="总结-amp-ToDo"><a href="#总结-amp-ToDo" class="headerlink" title="总结 &amp; ToDo"></a>总结 &amp; ToDo</h2><ul><li>训练语料对于算法的影响十分明显。若训练语料和测试集是不同情景下的句子，则算法准确率便会大打折扣。今后可考虑用与测试集同类的训练语料进行训练。</li><li>对于训练语料的处理同样重要。原算法对语料库的处理方法实现起来简单粗暴。但一个完整的句子在这种分割下也变得破碎不堪，并没有很好利用标点符号两端字符连接的信息等。</li><li>若考虑最简单的储存方式，字二元模型的数据库大小在未压缩的情况下已经有 33ＭB 左右，所以可在数据库压缩方面做进一步的改进。对于字多元模型和词多元模型可采取舍弃频数较少的多元关系信息来缩小数据库。</li><li>今后可以尝试用各种平滑算法解决数据稀疏问题。</li><li>可以尝试完善基于字的三元模型的算法，和实现基于字的四元模型或基于词的三元模型的算法。</li><li>解码算法方面还有许多问题需要思考，比如音节切分歧义、大规模解码算法的剪枝、更高阶的 N 元模型的解码算法等。</li></ul></body></html>]]></content>
    
    
    <summary type="html">&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;/body&gt;&lt;/html&gt;</summary>
    
    
    
    <category term="Homework" scheme="https://nano.ac/categories/Homework/"/>
    
    
    <category term="NLP" scheme="https://nano.ac/tags/NLP/"/>
    
  </entry>
  
</feed>
