<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/scripts/pretty-feed-v3.xsl" type="text/xsl"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:h="http://www.w3.org/TR/html4/"><channel><title>EH&apos;s Blog</title><description>EH is not god, but EH</description><link>https://ehnotgod.site</link><item><title>EH的保研之路</title><link>https://ehnotgod.site/blog/eh%E7%9A%84%E4%BF%9D%E7%A0%94%E4%B9%8B%E8%B7%AF</link><guid isPermaLink="true">https://ehnotgod.site/blog/eh%E7%9A%84%E4%BF%9D%E7%A0%94%E4%B9%8B%E8%B7%AF</guid><description>夏令营全聚德、预推免翻盘，最后落脚南开</description><pubDate>Sat, 26 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;绩点与综测波折&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;images/01-rk.png&quot; alt=&quot;绩点排名&quot;&gt;&lt;/p&gt;
&lt;p&gt;我大一到大三的绩点排名逐年提升，每提升一步我将更接近保研。但是我主要的优势其实还是素养。我们学校基础60分的素养里我拿了48分，在我们班保研的那伙人里，这个分数是最高的。&lt;/p&gt;
&lt;p&gt;我直到大三下前，都在为保研名额发愁，我们班往年的保研率是30%以上，正常来说是25个保8个的。但是去年是22个保7个，这让排名第8的我非常害怕，一方面是我后面的人可能突然拿个国奖把我斩杀，另一方面是假如今年跟随去年的脚步，不履行30%的约定，而是直接跟随着保7个呢？&lt;/p&gt;
&lt;p&gt;这让我一度产生了出国的想法。我在26年2月份跟家里人讨论说要不要润港校，但是他们认为还有保研的希望就得抓住，可能是家里经济情况并不乐观不便明说吧，假如真的保研失败了，我再一两个月突击托福也还是来得及的，所以接下来我还是坚持保研大业了，幸运的是最终排名第8，并且前排有一个选择出国了的，实际保研名额发到了第9。&lt;/p&gt;
&lt;p&gt;然后我开始正式地准备保研工作。&lt;/p&gt;
&lt;p&gt;我的bg（背景）是
学业：中下9，绩点分数89.7，rk10/25，百分比为40％&lt;/p&gt;
&lt;p&gt;科研：有两段科研实习经历（怀柔实验室/南大智科吉炜老师组），发了三篇论文，一篇国内期刊，两篇CCF-A会二作（都排名第二+二作），一篇CCF-A会一作在研&lt;/p&gt;
&lt;p&gt;竞赛：CCPC邀请赛金牌，ICPC区域赛银牌，蓝桥杯国一，节能减排国三&lt;/p&gt;
&lt;h2&gt;夏0营&lt;/h2&gt;
&lt;p&gt;5月份开始报夏令营，按照往年学长的经验，我至少能去计算所，华五也有不小的机会上。我彼时的导师一个是ailab的，一个是南大智科的。我非常又自信啊，先后报名了上海ailab、计算所春闱计划、中科大科学岛、北大深圳院、人大信院、中科院工业人工智能研究所、天大人工智能等一系列学校，全部被拒！这时候我感觉，应该是夏令营门槛太高了导致的吧，那我等待预推免得了呗。&lt;/p&gt;
&lt;h2&gt;预推免&lt;/h2&gt;
&lt;p&gt;结果预推免也没有好到哪里去，我报名了哈工大、西交、东南、同济等学校，就只有西交软院让我入了，可是我又不想去西交软啊。早在报名东南之前，我7月份可谓是一个地方都没去，8月份回到家后，家里也有压力，非常难受，其实家里也没有多少压力我，就是我一直都拿不到下一个offer导致的父母跟着一起焦虑罢了。&lt;/p&gt;
&lt;p&gt;我在8月份正式决定我不考虑任何直博。因为现在再联系直博多半只能找到普导了。我选择学硕。我的绩点排名非常不好看，所以我决定放弃华五，因为华五硕士多半会卡一下排名嘛。我把眼光下放到东南之类的学校。在8月5号的时候，我套磁了一下一个palm实验室的老师，希望它可以把我捞进去。面试很简单我回答得非常顺利，那边的博士生学长本科是来自山大的，问我这么好的背景为啥来东南而不是华五，我很感谢他的安慰，但我确实也只能来这儿了，因为华五必然被卡。我还投了一个南开大学cmm组的老师。本来是投好几个的，结果东南那边投了好些都回复+加微信了，感觉同时联系好几个老师怪不好意思的，于是南开这边我只联系了一个。&lt;/p&gt;
&lt;p&gt;这个老师很快回复了，经过40分钟的面试，我通过了线上考核，并答应去他们的线下考核，这是我的第一个offer。线下的考核第一天是体能活动，由老师带路，我们绕着校园跑了整整一公里。然后第二天是机试，我40多分钟直接ak了，线下面试我没有多在意，其实也是顺利通过了已经，因为我是打算把这个当作保底的。&lt;/p&gt;
&lt;p&gt;结果后来传出来消息说，今年palm的老师没有捞人的能力，我确实也没有入营东南，不过好在我已经拿到了cmm组的学硕offer，我非常高兴呐，至少比这个差一级的学校和组都不需要考虑了。而且天津也是一个好地方，离北京近，实习机会非常多。&lt;/p&gt;
&lt;p&gt;ailab的老师最后说他找不到我的简历所以捞不了我，说我是被卡机筛了。南大的老师更是直说他的名额用完了，让我自己报名硕士去......我顿时感觉被这两个老师抛弃了，就连跟他们目前正在合作的论文我也没有多少继续做的动力了。但是我总得去更好的地方吧！我尝试投了一下哈工大深圳，还有北航。哈深没入，结果北航我依靠银牌入营了，但是我没有在北航联系任何一个老师啊！而且，北航和山大人工智能的时间冲突了，由于先前的屡屡受挫，我不敢再尝试北航了，即使我拿到了优营，我估计也不会去的（好的老师早就没了）。然后我顺利拿到了sdu ai的优营，甚至是总分第二。假如我提前联系了北航的好导，结果会不会好一些？&lt;/p&gt;
&lt;p&gt;可是没有假如！目前的结果已经非常满意了！接下来的故事将在南开——天津展开了，让我们拭目以待。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/cat.jpg&quot; alt=&quot;南开校园里的橘猫&quot;&gt;&lt;/p&gt;
&lt;p&gt;猫猫，没想到我还会再来吧!&lt;/p&gt;</content:encoded><h:img src="/_astro/cat.D6mQrK96.jpg"/><enclosure url="/_astro/cat.D6mQrK96.jpg"/></item><item><title>大三生活</title><link>https://ehnotgod.site/blog/%E5%A4%A7%E4%B8%89%E7%94%9F%E6%B4%BB</link><guid isPermaLink="true">https://ehnotgod.site/blog/%E5%A4%A7%E4%B8%89%E7%94%9F%E6%B4%BB</guid><description>第一次科研、三站区域赛与保研前夜</description><pubDate>Fri, 25 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;大三上&lt;/h2&gt;
&lt;h3&gt;9月&lt;/h3&gt;
&lt;p&gt;这个月的主目标是数模，副目标是网络赛，因为网络赛打不好的话，我们连区域赛的资格都没有！结果是数模做得差强人意，我觉得再差也至少有个省一的那种。由于数模的影响，我们第一场网络赛并没有打好（睡太少了）。不过第二场和第三场我们的发挥都相当亮眼，最终斩获两个IC名额，一个CC名额。&lt;/p&gt;
&lt;p&gt;具体的 ACM 系列可以移步至 &lt;a href=&quot;/zh/blog/acm%E7%94%9F%E6%B6%AF&quot;&gt;ACM 生涯&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ehnotgod.site/_astro/ccpc.CzX0i2GD_Z2v6byW.webp&quot; alt=&quot;2025CCPC&quot;&gt;&lt;/p&gt;
&lt;p&gt;与此同时，22学长推荐我去怀柔实验室的科研实习，我9月底主要的工作就是在那里干了一篇论文出来，负责的东西是制定电网问题的评估框架，以及强化学习。这是我第一次做科研，我也是第一次感受到，其他学校/研究所的算力资源居然如此丰富。&lt;/p&gt;
&lt;p&gt;不过第一次做科研还是吃了不少苦的，我环境配置不熟练，也不知道怎样多卡训练，也不会开并行/后台运行，文件管理也十分混乱。不过这些问题随着我对科研的深入逐步解决了，它是一个长期的工程素养。&lt;/p&gt;
&lt;h3&gt;10月&lt;/h3&gt;
&lt;p&gt;数模出结果了，我只拿到了省二。9月份的那个期刊也投出去了，那个论文没啥含金量，但是也算够用吧，这时候那边的学长让我跟他一起干一篇CCF-A论文出来，本来是赶CVPR的，但是彼时我还得打ICPC区域赛啊。赶论文的机会又很多，但是区域赛只有3场能打了。不过这段时间距离期末还有很长的一段距离，需要考的科目还是我擅长的运筹学。&lt;/p&gt;
&lt;p&gt;我索性all in区域赛+科研，科研做完了就加训。这段时间的科研任务也不算重，主要就是利用blender做一些物体在不同材质下的渲染，然后拍照，去生产这样的图像对而已。&lt;/p&gt;
&lt;p&gt;非常顺利的，我西安区域赛拿到了铜牌。穿插一个小趣事，由于主办方的失误，他们把我们的重复提交当成有效提交了，我们还混到了“顽强拼搏奖”。在西安我没有跟随队友去玩，我选择待在酒店去完成科研任务。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../ACM%E7%94%9F%E6%B6%AF/images/06-xian.jpg&quot; alt=&quot;西安&quot;&gt;&lt;/p&gt;
&lt;h3&gt;11月&lt;/h3&gt;
&lt;p&gt;第二站是哈尔滨站，这场是CCPC，难度偏高，我们的预期目标是拿到铜牌就行。让我意外的是，队友开始发力，把一道至关重要的“猜猜题”给过掉了，我们最后极限拿铜。&lt;/p&gt;
&lt;p&gt;第三站是沈阳站，它居然提供 PyPy3，于是我们直接手速起飞。这场我和队友配合紧密，我负责写板子逻辑，他负责写主算法逻辑，一点错误都没犯。最后，队友还极限发力过掉了那道卡了半个场的结论题，我们破天荒地拿到了银牌。&lt;/p&gt;
&lt;p&gt;这部分内容复制自 &lt;a href=&quot;/zh/blog/acm%E7%94%9F%E6%B6%AF&quot;&gt;ACM 生涯&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;我的任务线依旧是科研+竞赛，不管课内。&lt;/p&gt;
&lt;h3&gt;12月&lt;/h3&gt;
&lt;p&gt;在73群水群的时候，2.333他打算赶ICML会议论文，因为他要给来年留出足够的时间考研，想赶紧把论文投出去了。但是他目前的实验还没有明显的成果，主题框架是搭建好了，需要找个人去帮忙跑跑实验。我评估了一下自己的时间安排，最终决定帮助他。因为此时我已经拿到了银牌，没有任何牵挂。&lt;/p&gt;
&lt;p&gt;学长那边给了我快手的卡使用，我就用那边的卡跑这边的实验。两边都干科研说实话有点小忙，还好我们的期末考算简单的，我草草复习了一周也拿到了不错的成果。&lt;/p&gt;
&lt;h3&gt;1月&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;期末考+科研&lt;/strong&gt;要把我折磨疯了。这科研很坐牢啊，每天9点起来去文学楼自习+讨论，跑实验/画图/写论文，持续了整整半个月！好在结果是好的，大概在1月底的时候提前干完了。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ehnotgod.site/_astro/texeditor.BANLhKNU_15xMYo.webp&quot; alt=&quot;alt text&quot;&gt;&lt;/p&gt;
&lt;h2&gt;大三下&lt;/h2&gt;
&lt;p&gt;每个上学期会超神，那么下学期就会超鬼。&lt;/p&gt;
&lt;p&gt;我去打了CACC决赛，没获奖。去打了山东省赛，依旧银牌。去打了CCPC福州邀请赛，虽然金牌但毕竟是CCPC，没啥用。&lt;/p&gt;
&lt;p&gt;我尝试去做我自己的一作论文，计划发AAAI，但是和今年一篇6月份刚发的文章的idea完全撞了，被迫加内容，到现在也没有新的进展。我在今年5月份才得知我之前3篇论文都中了的消息，但那毕竟是上学期的工作了。&lt;/p&gt;
&lt;p&gt;整个大三下学期像是在虚度光阴，但我在想，更多的内容应该是保研之路了，这里就不多赘述了，详见 &lt;a href=&quot;/zh/blog/eh%E7%9A%84%E4%BF%9D%E7%A0%94%E4%B9%8B%E8%B7%AF&quot;&gt;EH 的保研之路&lt;/a&gt;。&lt;/p&gt;</content:encoded><h:img src="/_astro/texeditor.BANLhKNU.png"/><enclosure url="/_astro/texeditor.BANLhKNU.png"/></item><item><title>大二生活</title><link>https://ehnotgod.site/blog/%E5%A4%A7%E4%BA%8C%E7%94%9F%E6%B4%BB</link><guid isPermaLink="true">https://ehnotgod.site/blog/%E5%A4%A7%E4%BA%8C%E7%94%9F%E6%B4%BB</guid><description>绩点翻盘、创赛折腾、省赛银牌与蓝桥杯国一</description><pubDate>Thu, 24 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;大二上&lt;/h2&gt;
&lt;h3&gt;9-来年1月&lt;/h3&gt;
&lt;p&gt;接回大一上。到了这个时候，我终于逐渐意识到一件事——我好像真的可以保研。&lt;/p&gt;
&lt;p&gt;于是我立马开始卷绩点。&lt;/p&gt;
&lt;p&gt;这个学期的主线其实非常单纯：&lt;strong&gt;学习、卷分、大作业&lt;/strong&gt;。除此之外倒也没什么特别忙的事情，整个学期过得异常平平淡淡。我每天就在数学分析、概率论、集群网络、数据库这些课里来回折腾，中间还夹着毛泽东思想和中国特色社会主义理论体系概论、习近平新时代中国特色社会主义思想概论这两门偏文科的课。&lt;/p&gt;
&lt;p&gt;也是在这个学期，我当上了学委。不得不说，学委的“权力”可谓非常之大呐。尤其碰上这种偏文科、平时分占比又不低的课程，作为天天和老师、作业、通知打交道的学委，我的平时分自然也是混得相当滋润。&lt;/p&gt;
&lt;p&gt;至于期末，我依旧延续了自己的传统艺能——考前速通，但这次，我不再是一周速通，而是一个月速通！考完后本来我也没觉得自己能考多高，结果成绩出来以后发现多少有点逆天：数学分析 97 分，专业第二；概率论 91 分，专业第五。最离谱的是，我明明感觉自己学得也就那么回事，最后一看成绩单：怎么又这么高？&lt;/p&gt;
&lt;p&gt;然后这学期我的绩点大抵拿到了班级第二左右，这是我保研路上的一个重大的翻盘点。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ehnotgod.site/_astro/buluweisi.rDTAn0eI_18xoxP.webp&quot; alt=&quot;布鲁维斯号&quot;&gt;&lt;/p&gt;
&lt;p&gt;这学期我们还去布鲁维斯号那边玩了，大概在国庆的时候——自此之后我的旅游行动就停滞了。当时我的好多高中同学也来威海旅游了，我们是自驾游去的布鲁维斯号！那个船真的很壮观很出片！然后第二天曹公带我们逛了哈威，我们带他们逛了山威。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ehnotgod.site/_astro/ai.CYFOjXfN_Z1kdeYF.webp&quot; alt=&quot;AI对话网站&quot;&gt;&lt;/p&gt;
&lt;p&gt;这学期还有一个主线任务是大作业，是全栈开发，做一个校园知识对话网站。我使用Vue3+pinia+Element plus搭建前端，用fastapi+mysql搭建后端，不过由于网站没有做完美，最终没有拿到高分。&lt;/p&gt;
&lt;h2&gt;大二下&lt;/h2&gt;
&lt;h3&gt;2-8月&lt;/h3&gt;
&lt;p&gt;我的比赛之魂，也差不多就是从这个时候彻底觉醒的。&lt;/p&gt;
&lt;p&gt;这其实源自我大二上学期的一个判断：既然我期末靠突击一个月就能拿到不错的成绩，那平时那些相对空闲的时间，好像完全可以拿去做点别的事情。毕竟那时候保研还没有稳到让我彻底放心，而之前一起打美赛的队友又来问我要不要参加她的大挑和节能减排，于是我几乎没怎么犹豫就去了。&lt;/p&gt;
&lt;p&gt;当时我的目标其实很朴素：拿个国家三等奖，给保研再加一点额外的筹码。&lt;/p&gt;
&lt;p&gt;打创赛的那段时间是真的忙，也是真的累。做材料、改方案、准备答辩，来来回回折腾了很久。我在团队里主要负责建模，这件事本身不算特别难，但又是项目里很重要的一环。比起最后拿到什么奖，我后来反而更庆幸自己在这段经历里第一次真正明白了：一个项目究竟要怎么讲，计划书究竟要怎么写，又应该怎样站在评审者的视角去判断一个项目到底好不好。&lt;/p&gt;
&lt;p&gt;当然，如果单纯从结果来看，这其实是一个非常“不值当”的计划。它占用了我大量时间，最后换来的不过是一个含金量并不算高的节能减排国三。后来再回头算投入产出比，这笔账怎么看都不漂亮。&lt;/p&gt;
&lt;p&gt;但如果人生里的所有事情都要拿“性价比”来衡量，那我后来大概也不会去打那么多比赛了。有些事情值不值，并不完全取决于它最后换来了什么。想做就去做，活得潇洒一点，这也很重要。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ehnotgod.site/_astro/chuangsai.C5jh61im_1eki63.webp&quot; alt=&quot;节能减排&quot;&gt;&lt;/p&gt;
&lt;p&gt;然后就是程序设计大赛，在这一点上我发现我们并不菜，逐渐成长为了校里数一数二的队伍。&lt;/p&gt;
&lt;p&gt;我们依旧去打了山东省程序设计大赛。这场我信心满满，但最后还是差点翻车了，还好队友稳住心态，极限开了一题。最终我们封榜后通过两题，获得银牌。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ehnotgod.site/_astro/sdcpc.BCzzkeWs_1FpOkn.webp&quot; alt=&quot;山东省赛&quot;&gt;&lt;/p&gt;
&lt;p&gt;紧接着我们还去打了蓝桥杯，淄博烧烤真的非常好吃实惠呐！这场蓝桥杯我的发挥堪称完美，顺利拿到了国家一等奖。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ehnotgod.site/_astro/basketball.C0G_DB2e_AN74P.webp&quot; alt=&quot;蓝桥杯&quot;&gt;&lt;/p&gt;
&lt;p&gt;不过在我意料之外的，由于我过度相信期末突击，实际上我考得非常差！我只拿到86的平均绩点！这非常差，把我大二上学期的优势全部搞没了！&lt;/p&gt;
&lt;p&gt;暑假期间我主要的工作还是ACM加训，因为我现在什么也没有了，我都盘算好保研不上，那我就在大三上拿牌，然后寒假考雅思，直接润港得了......这是一个非常黑暗的时期，不过我还是忙里偷闲了一下，去宁波培训了一下ACM，顺带旅游，拿几天非常难忘，每天吃好喝好还不需要交钱，也就是在这个暑假，我的算法水平突飞猛进，有了区域赛拿牌的迹象。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ehnotgod.site/_astro/cacc.BfOb8tJh_1VQnaO.webp&quot; alt=&quot;宁波&quot;&gt;&lt;/p&gt;</content:encoded><h:img src="/_astro/buluweisi.rDTAn0eI.jpg"/><enclosure url="/_astro/buluweisi.rDTAn0eI.jpg"/></item><item><title>大一生活</title><link>https://ehnotgod.site/blog/%E5%A4%A7%E4%B8%80%E7%94%9F%E6%B4%BB</link><guid isPermaLink="true">https://ehnotgod.site/blog/%E5%A4%A7%E4%B8%80%E7%94%9F%E6%B4%BB</guid><description>EH的大一生活，初识校园</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;大一上&lt;/h2&gt;
&lt;p&gt;我在大一上没有做出什么轰轰烈烈的事情，整体上还是在探索学校，非常单纯啊！带些许稚嫩的心理，可惜以后再也没有这么纯粹过了......&lt;/p&gt;
&lt;h3&gt;9-10月&lt;/h3&gt;
&lt;p&gt;初到山东大学，映入眼帘的便是一座气势恢宏的校门。虽然这里是威海校区，但实话实说，它带给我的震撼丝毫不逊于本部。校园给人的第一印象很不错，而食堂也同样让人惊喜——饭菜确实很好吃。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ehnotgod.site/_astro/01-campus-gate.BKCJidPa_1l4zMQ.webp&quot; alt=&quot;山东大学（威海）校门&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ehnotgod.site/_astro/02-canteen.CGFoqCim_ZtIFGk.webp&quot; alt=&quot;学生食堂&quot;&gt;&lt;/p&gt;
&lt;p&gt;我们&lt;strong&gt;宿舍&lt;/strong&gt;的氛围也特别好，闲的那会儿还和两个舍友一起开黑吃鸡，忙的那会儿整个宿舍齐心协力备战期末考。一个好的宿舍氛围真的很重要，它带来良好的生活习惯。&lt;/p&gt;
&lt;p&gt;很快我迎来了&lt;strong&gt;数科班&lt;/strong&gt;考试和泰山学堂考试，事实证明进入这个数科班是一个错误的选择，小班制带来的保研排名百分比劣势极其严重，但当时的我引以为荣，本身我对计算机就挺感兴趣的，数学+计算机真是一个巨大的诱惑。&lt;/p&gt;
&lt;p&gt;我确实顺利进入了数科班，也通过了泰山学堂的笔试（数学方向），不过面试没过（这太难了）。由于是高考落榜来的山威，我并不认为自己有多差，反而有一种鹤立鸡群的优越感：“你看，我不需要多少努力就考进了数科班！”这也为后来的失败埋下了伏笔。&lt;/p&gt;
&lt;p&gt;刚进入大学的我&lt;strong&gt;广交友&lt;/strong&gt;，乐观开朗，短时间之内不少人就知道我是个“很有能力的人”，我因此感到沾沾自喜。平时上课也是积极地坐在第一排，好好听老师讲课，按时完成课后作业。也因此开学那会儿很多人会积极地来请教我数学问题。在数科班也如此，我早早地在暑假和军训期间学习python语法，在数科班前几次课我就展现了出色的编程能力，进一步提升了自己的知名度。&lt;/p&gt;
&lt;h3&gt;11-12月&lt;/h3&gt;
&lt;p&gt;逐渐认识到，这个数学分析有点枯燥无趣啊，这么理论的东西真的可以做出成果来吗？我是不是不适合学数学，应该是高中时期的自己对数学的滤镜太重了，实际上数学并非如此有趣。或许我应该高考报考计算机专业的......但是那得631分，我也还差个1分呐。&lt;/p&gt;
&lt;p&gt;我加了学校的数据科学协会群、飞桨领航团，我还去找了22数科的学长并加入了他的科研立项，从此我的目标改为人工智能方向。只不过那个时候的AI并不发达，大家挤破头了在研究CNN等现在看起来非常基础的东西。最好的模型为GPT3，并不好用，文心一言就更不用说了，用来写实验报告还差不多。&lt;/p&gt;
&lt;p&gt;威海的雪真的非常震撼，它不回一直下，但是会集中性地下，下的那几天真的很疯狂啊！每天跨越风雪去上课，都显得很有生活气息。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ehnotgod.site/_astro/03-huojubajie.elJ7Er6-_ZGin8b.webp&quot; alt=&quot;火炬八街雪景&quot;&gt;&lt;/p&gt;
&lt;h3&gt;来年的1-2月&lt;/h3&gt;
&lt;p&gt;期末考总体上还是顺利的，不过我并不清楚我以后的发展规划。站在大四的角度评判大一的自己还是太过火了。当时并不明白绩点于保研的重要性，以为只要有名额就完事了，所以我卷课内也不是那么用功。我歧视那些必修的文科比如近代史纲要——因此毫无准备，期末我只拿了53分。我的绩点排名为8/25，当时感觉保研要没希望了。&lt;/p&gt;
&lt;p&gt;寒假期间我在学习深度学习相关知识，不过那时候的代码能力并不行。所以当我学到CNN那边的时候，我就已经有点看不懂了。结合AI的能力，我才算是写出来一个看起来还行的项目——动物分类模型。我因此非常高兴，甚至把它转发给各个同学和学长来评价。&lt;/p&gt;
&lt;h2&gt;大一下&lt;/h2&gt;
&lt;p&gt;每个第二学期都是人生最黑暗的时候。中学如此，大学也如此。我并不明白这是怎么一回事，事实如此。&lt;/p&gt;
&lt;h3&gt;3-6月&lt;/h3&gt;
&lt;p&gt;我参加了校程序设计大赛新星赛——这是一个历史性的时刻，他严重影响了EH的人生轨迹。我再什么都没有学的情况下，顺利地通过了5题，极限地加入了校队，并和两个数院的队友组队。从此我们参与ACM集训，打天梯赛、打省赛，虽然菜菜的，但是还挺滋润。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ehnotgod.site/_astro/04-traval.kfRjtSOt_Z1hvsNM.webp&quot; alt=&quot;旅游&quot;&gt;&lt;/p&gt;
&lt;p&gt;如上图，我在这学期期间的旅游计划是相当丰富的。从左到右，从上到下，我先后去了武平梁野山顶、武汉黄鹤楼、蓬莱欧乐堡、济南大明湖、烟台养马岛、威海海钓。真是一个充实的大学生活呐。&lt;/p&gt;
&lt;p&gt;这段时间的学习也不能落下，我加入ACM集训队后，在不断地学习C++语法，因为这是解题的必备语言！并同步学习数模的一些知识......不过至于课内我就没有管那么多了，毕竟大一上已经感觉保研要没希望了......然后我大一下的绩点全面崩盘了，平均只有86分多。拉上综合成绩一算，好家伙，居然还有8/25，又燃起了保研的希望。&lt;/p&gt;
&lt;h3&gt;7-8月&lt;/h3&gt;
&lt;p&gt;我的大作业完成得非常出色，拿到了最高分。除此之外，我发现了董晓算法这个宝藏up，我几乎的算法都是在他那里学的。&lt;/p&gt;
&lt;p&gt;然后，没了。大一的生活如此，纯粹，干净。&lt;/p&gt;</content:encoded><h:img src="/_astro/small-campus.OMnXM6Ro.png"/><enclosure url="/_astro/small-campus.OMnXM6Ro.png"/></item><item><title>ACM生涯</title><link>https://ehnotgod.site/blog/acm%E7%94%9F%E6%B6%AF</link><guid isPermaLink="true">https://ehnotgod.site/blog/acm%E7%94%9F%E6%B6%AF</guid><description>EH与ACM的爱恨情仇</description><pubDate>Mon, 07 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;我对 ACM 一直怀着两种近乎矛盾的态度。一方面，若能拿到区域赛银牌及以上的奖项，它确实会成为履历上相当漂亮的一笔；可另一方面，为了加训所付出的时间与精力，也注定会挤占掉大学生活中许多其他可能性。更何况，ACM 归根到底仍是一项带有“做题家”气质的竞赛，它无法替代真正的工程能力、科研积累。于我而言，“锦上添花”或许是对它最恰当的形容。&lt;/p&gt;
&lt;p&gt;但无论如何，它终究是一段难忘的经历。它极大地丰富了我的大学生活，也让我遇见了许多志同道合的人。接下来，就简单讲讲我与 ACM 的这段缘分。&lt;/p&gt;
&lt;h2&gt;大一学期——加入ACM校队&lt;/h2&gt;
&lt;p&gt;第一次接触 ACM，是在大一参加“可达鸭杯”的时候。当时比赛通知刚发出来，还允许线上参赛，我便下意识以为这不过是一场“水赛”，没有任何准备，赛时连输入输出都得现场百度。写五子棋那道题时，我被各种边界情况卡得死去活来，最后遗憾无缘决赛。那大概是我第一次意识到：看似简单的题目，真写起来也未必那么简单。&lt;/p&gt;
&lt;p&gt;到了大一下，我又看到了新星赛的通知。有了“可达鸭杯”的前车之鉴，这一次我提前准备了一些输入输出模板，算是稍微认真了些。可彼时我的主要精力仍放在数学建模上，编程语言也只会 Python。面对这种显然更利好 C++ 这种编程语言的程序设计竞赛，我多少有些硬着头皮上场的意味。所幸结果还不错，单纯凭借一些数学直觉和思维能力，我拿到了银奖，也顺利进入了 ACM 校队。也正是在校队里，我认识了另外两位来自数院的选手 ，后来他们便成了我的队友。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/01-school-contest-silver.jpg&quot; alt=&quot;校赛银牌&quot;&gt;&lt;/p&gt;
&lt;p&gt;说来惭愧，我们的加训状态一度颇有些“三天打鱼，两天晒网”的味道。而在队伍里，我的水平又是最低的。平日训练赛中，我基本只能负责一些数学题、结论题；至于更复杂的算法实现，则常常只能在一旁望题兴叹。更要命的是，那时的我依旧只会 Python，很多时候连语言本身都像是一道额外的门槛。后来，我们参加了省赛选拔赛，毫无悬念地喜提 no 名额。不过老师仍然相信我们的潜力，建议我们以打星的身份参赛，提前感受一下正式比赛的氛围。&lt;/p&gt;
&lt;p&gt;于是，便有了 2024 年山东省赛兼邀请赛之行。&lt;/p&gt;
&lt;p&gt;那场比赛规模很大，大到光是告诉别人“我要去参加这样的比赛”，就已经是一件颇有面子的事情。真正坐到赛场上时，我们才发现自己仍然稚嫩得很：整场比赛只做出了 3 题，连二分都还不太会，成绩就算放在正式队里也获不了奖。现在回想起来，那多少有些狼狈，却也足够真实，“打印机”成了我们忘不了的一题（那题卡long long，而且刚需二分算法，而且我当时没学过）。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/02-jinan-venue.jpg&quot; alt=&quot;气派不气派&quot;&gt;&lt;/p&gt;
&lt;p&gt;不过，济南还是很好玩的。队友纸船是本地人，赛后便带着我们四处转了转。我们去了趵突泉，也顺路去了SDU趵突泉校区，还在大明湖畔吹了晚风。夜色、湖风、泉水、旧城，还有一群刚从赛场上败下阵来的ACMer——这些画面混在一起，反倒比比赛本身更清晰地留在了记忆里。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/03-daminghu.jpg&quot; alt=&quot;大明湖畔&quot;&gt;&lt;/p&gt;
&lt;h2&gt;大二学期——至暗时刻？&lt;/h2&gt;
&lt;p&gt;2024 年上半年，其实还有蓝桥杯、天梯赛和百度之星。非常遗憾的是，这些比赛我都没有获奖（蓝桥杯没报）。也正因如此，后来的暑假加训多少也带着些“水水而已”的味道；至于区域赛名额，自然也没有轮到我们。对这样一支队伍来说，未来真的还有拿奖的可能吗？那时的我自己也很怀疑。也许属于我们的 ACM 之路，差不多就要到此为止了。&lt;/p&gt;
&lt;p&gt;2024 年下半年的秋季选拔赛带来了一些我心态上的转变。那场比赛里，我作为老队员带榜，和我们学校同级的传奇新人 7_divided_by_3 同台竞技。那时我的加训题量还不到他的四分之一，可最后他也只是比我多做出了一题；甚至在比赛前期，领跑的还是我（不得不说，Python 写起来是真的快）。也正是在那一刻，我感受到：原来在算法竞赛里打出一点名堂，好像也并没有想象中那么难。于是，我开始比较系统地加训，把过去欠下的漏洞一点点补上（B 站up董晓算法讲得不错）。也是在 2024 年底，我开始打 Codeforces，并成功上了青名。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/04-autumn-selection.jpg&quot; alt=&quot;秋季选拔赛的趣事，我一直记得&quot;&gt;&lt;/p&gt;
&lt;p&gt;大二下学期，我开始更加频繁地加训。Codeforces 被我打到了 1800 分，我们队也拿到了省赛银牌；我个人则拿到了蓝桥杯国一、百度之星省一。那时我的总刷题数其实也不过 600 左右。算不上多么夸张，却已经足够支撑我走到一个曾经不敢想的位置。我个人认为自己在这方面确实有点小天赋。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/05-lanqiao-zibo.jpg&quot; alt=&quot;蓝桥杯是在淄博的山东理工大学打的&quot;&gt;&lt;/p&gt;
&lt;h2&gt;大三学期——区域赛&lt;/h2&gt;
&lt;p&gt;到了 2025 年下半年，我意识到，这大概会是我唯一一次打区域赛的机会了。因为我在积极地备战保研，而假如想让牌子发挥作用，只能依靠这个学期了，大四再拿就迟了。我们暑假的加训其实并不拼命，还是比较水水的状态，可能是还没经历过区域赛的毒打吧。因此，对于一支主要从大二才开始认真加训的队伍来说，想在区域赛拿牌，怎么看都有些不太现实，但我还是想试一试。好在天无绝人之路——我们选到了两个强度最低的 ICPC 站，以及一个强度最低的 CCPC 站。&lt;/p&gt;
&lt;p&gt;第一站是西安站。那一场几乎是我一个人在开题、写题，题目分别是签到+二分+LCA+优先队列这么4题，硬是把队伍撑到了铜牌。其实那场我并没有那么高兴，我在反思，假如只靠我一个人发力，最好也只能是这样的成果了，那后续的赛站岂不是难度更大？银牌多半是没什么指望了。&lt;/p&gt;
&lt;p&gt;第二站是哈尔滨站，这场是CCPC，难度偏高，我们的预期目标是拿到铜牌就行。让我意外的是，队友开始发力，把一道至关重要的“猜猜题”给过掉了，我们最后极限拿铜。这场我们的配合非常不错，我主要负责偏模板的题目，而队友主要负责强思维的题目。&lt;/p&gt;
&lt;p&gt;第三站是沈阳站，它居然提供 PyPy3，于是我们直接手速起飞。这场我和队友配合紧密，我负责写板子逻辑，他负责写主算法逻辑，一点代码错误都没犯。最后，队友还极限发力过掉了那道卡了半个场的结论题，我们破天荒地拿到了银牌。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/06-xian.jpg&quot; alt=&quot;西安&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/07-harbin.jpg&quot; alt=&quot;哈尔滨&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/08-shenyang.jpg&quot; alt=&quot;沈阳&quot;&gt;&lt;/p&gt;
&lt;p&gt;沈阳站拿银的那一刻，可能是我整个 ACM 生涯里最开心的时候。回到学校后，我一边等 cpcfinder 更新，一边等洛谷的官方气球认证，还顺手兑现了之前在 73 群里立下的 flag。现在想想，那种快乐甚至有点幼稚，但也正因为幼稚才显得格外纯粹。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/09-flag.jpg&quot; alt=&quot;u1s1，我在女装方面是不是也很有天赋？&quot;&gt;&lt;/p&gt;
&lt;p&gt;然后就是百度之星了。此时我已无任何牵挂，反正拿银了，这个 astar 也不重要了，我就是去北京玩的！我在北京逛了好一大圈，还顺路去沙河校区打了 cacc。不过从这时候开始，我的水平就已经在走下坡路了——百度之星只过了签到拿到铜牌，今年的 cacc 决赛我没有获奖，省赛也只是拿到了银牌，太可惜了。&lt;/p&gt;
&lt;p&gt;省赛银牌确实是我个人的严重发挥失误，我对不起队友们，这是我们蟒蛇队ACM的最后一程，却没有走好。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/10-three-shames.jpg&quot; alt=&quot;拿牌后的三大耻辱&quot;&gt;&lt;/p&gt;
&lt;p&gt;但仔细想想，也没有什么不能接受的。&lt;/p&gt;
&lt;p&gt;我并不是一个多么合格的 ACMer。毕竟我没有 OI 经历，全身心投入的时间也不算多，更没有那种“一上赛场就必须拿牌”的决心。我常常摇摆，常常偷懒，也常常在训练、学业与科研之间反复权衡。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/11-quanjuan.jpg&quot; alt=&quot;卷起来！&quot;&gt;&lt;/p&gt;
&lt;p&gt;可我终究还是来过一趟。&lt;/p&gt;
&lt;p&gt;我体验过和队友并肩作战时的全神贯注，体验过封榜后过题、最终拿到牌子的狂喜，也体验过凌晨归校的火车、赛后城市的晚风，以及无数次调不出 bug 时的怀疑人生。&lt;/p&gt;
&lt;p&gt;这些就已经足够了。&lt;/p&gt;
&lt;p&gt;ACM 没有定义我的大学生活，却实实在在地丰富了它。它让我认识了朋友，见过了更大的赛场，也在某些瞬间，让我相信自己并没有那么平庸。&lt;/p&gt;
&lt;h2&gt;大四学期——新的起点&lt;/h2&gt;
&lt;p&gt;随着队友陆续离开，我的心似乎也一点点退役了，因为 ACM 对我再无更多意义，我已经圆满了。不过，6 月的时候，因为隔壁队伍临时缺人，7_divided_by_3 把我拉去打了 CCPC 福州邀请赛。没想到，我们一路打下来，最后竟然拿到了金牌。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/12-f-rank.jpg&quot; alt=&quot;F题歪榜了&quot;&gt;&lt;/p&gt;
&lt;p&gt;那一刻，原本已经沉下去的心，好像又重新燃起了一点什么。&lt;/p&gt;
&lt;p&gt;但后来想想，那其实也不太像“希望”。我很清楚，属于自己的竞赛生涯已经快走到尽头了，9 月就是网络赛，我也未必还在期待着靠最后几场比赛去证明什么。相比之下，那种感觉更像是“化作春泥更护花”。&lt;/p&gt;
&lt;p&gt;我只是觉得，在真正离开之前，总还应该为校队做些什么。&lt;/p&gt;
&lt;p&gt;而 73 队愿意接纳我，对我来说，也就不仅仅意味着又有了一支队伍、又有了一次比赛的机会。它更像是给了我一个最后继续留在赛场上的理由，也给了我一个能够把自己这些年积累下来的东西，再为校队贡献一点的平台。&lt;/p&gt;
&lt;p&gt;然后，时间就来到了九月份的网络赛。那时候的我正忙着保研——其实说“忙”也不太准确。早在八月份，我就已经拿到了保底的 offer，按理说，这反而给了我相当充足的时间去准备网络赛。但我并没有怎么训练。保研顺利之后，整个人一下子松了下来，天天水群、刷视频，沉浸在一种“终于可以歇一歇了”的消遣里。校队上下倒是气势很足，第一场网络赛前还在群里叫嚣着要拿 1.5 倍名额，仿佛区域赛门票已经唾手可得。&lt;/p&gt;
&lt;p&gt;结果现实很快就给了我们一记耳光。第一场 IC 网络赛，我们的发挥甚至还不如去年的自己。比赛结束后，整个校队加起来只拿到了两个区域赛名额。此前那些豪言壮语一下子全没了，群里的气氛肉眼可见地沉了下去，一时间颇有种“今年是不是要完蛋了”的感觉。好在第二场，我们顶住了压力，最终队排四百名出头，成功拿到了单倍名额……成绩当然谈不上多么亮眼，但它至少把校队从悬崖边上拽了回来。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/13-d-corner-cases.jpg&quot; alt=&quot;D是我写的，很多Corner case没有考虑到，自己真是老了&quot;&gt;&lt;/p&gt;
&lt;p&gt;下一场是 CC 网络赛。不过彼时我和一个队友还在本部进行预推免面试，在网络赛前我极限赶了回来，但这次只有两个人打，有个队友没赶回来。前期 73 迅速地秒了 5 个签到题，随后我开出了拓扑排序 + dp，以及范德蒙德卷积 + 计数题，最后一题是计算几何，正好碰到我的长处，于是也迅速通过了，本场打出了最好战绩第 231 名，并拿到了一个额外名额。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/14-eight-problems.jpg&quot; alt=&quot;8题？省赛来了这是&quot;&gt;&lt;/p&gt;
&lt;p&gt;接下来是区域赛，不过具体去哪一场“旅游”，还得看导师以及队友的时间安排嘛。&lt;/p&gt;</content:encoded><h:img src="/_astro/fengmian.B1QT22no.jpg"/><enclosure url="/_astro/fengmian.B1QT22no.jpg"/></item><item><title>G31 圆排列、错排数</title><link>https://ehnotgod.site/blog/g31-%E5%9C%86%E6%8E%92%E5%88%97%E9%94%99%E6%8E%92%E6%95%B0</link><guid isPermaLink="true">https://ehnotgod.site/blog/g31-%E5%9C%86%E6%8E%92%E5%88%97%E9%94%99%E6%8E%92%E6%95%B0</guid><description>圆排列与错排数：环形排列与全错位排列计数。</description><pubDate>Wed, 02 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;圆排列：把 $n$ 个不同元素排成一圈（旋转视为同一种）的方案数；错排数 $D_n$：把 $n$ 个元素重新排列，使得每个元素都不在原来的位置上的方案数。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;说明/提示&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;本页为算法笔记。&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;圆排列&lt;/strong&gt;：$n$ 个不同元素的直线排列有 $n!$ 种；排成一圈时，任意旋转后相同算同一种，且每 $n$ 个旋转对应一个圆排列，故圆排列数为 $\frac{n!}{n}=(n-1)!$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;错排数&lt;/strong&gt;：$D_n$ 表示 $n$ 个元素的错排数（每个元素都不在自己原位）。递推式 $D_n=(n-1)(D_{n-1}+D_{n-2})$，边界 $D_0=1,\ D_1=0$。含义：元素 $1$ 与某个 $i\ (i\ne1)$ 互换后，剩余 $n-2$ 个错排（$D_{n-2}$）；或 $1$ 与 $i$ 互换但 $i$ 仍可能错排（先看成 $n-1$ 规模的错排 $D_{n-1}$），共 $n-1$ 种选择。也可用容斥 $D_n=n!\sum_{i=0}^n\frac{(-1)^i}{i!}$。&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;MOD = 10**9 + 7

# 圆排列：(n-1)!  mod MOD
def circle(n):
    res = 1
    for i in range(2, n):
        res = res * i % MOD
    return res

# 错排数：D_n = (n-1)(D_{n-1}+D_{n-2})
def derange(n):
    d = [0] * (n + 1)
    d[0], d[1] = 1, 0  # D0 = 1, D1 = 0
    for i in range(2, n + 1):
        d[i] = (i - 1) * (d[i - 1] + d[i - 2]) % MOD
    return d[n]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include &amp;#x3C;bits/stdc++.h&gt;
using namespace std;

typedef long long LL;
const LL MOD = 1e9 + 7;

LL circle(int n) { // 圆排列 (n-1)!
  LL res = 1;
  for (int i = 2; i &amp;#x3C; n; i++) res = res * i % MOD;
  return res;
}

LL derange(int n) { // 错排数 D_n
  vector&amp;#x3C;LL&gt; d(n + 1);
  d[0] = 1; d[1] = 0;
  for (int i = 2; i &amp;#x3C;= n; i++)
    d[i] = (i - 1) * (d[i - 1] + d[i - 2]) % MOD;
  return d[n];
}

int main() {
  int n;
  cin &gt;&gt; n;
  cout &amp;#x3C;&amp;#x3C; &quot;圆排列: &quot; &amp;#x3C;&amp;#x3C; circle(n) &amp;#x3C;&amp;#x3C; &quot;\n&quot;;
  cout &amp;#x3C;&amp;#x3C; &quot;错排数: &quot; &amp;#x3C;&amp;#x3C; derange(n) &amp;#x3C;&amp;#x3C; &quot;\n&quot;;
  return 0;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>G32 莫比乌斯反演</title><link>https://ehnotgod.site/blog/g32-%E8%8E%AB%E6%AF%94%E4%B9%8C%E6%96%AF%E5%8F%8D%E6%BC%94</link><guid isPermaLink="true">https://ehnotgod.site/blog/g32-%E8%8E%AB%E6%AF%94%E4%B9%8C%E6%96%AF%E5%8F%8D%E6%BC%94</guid><description>莫比乌斯反演：μ 函数与反演公式。</description><pubDate>Wed, 02 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/G32-1.png&quot; alt=&quot;题目：企鹅收集石头&quot;&gt;&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;设 $F(k)$ 为选出的 $m$ 个数都能被 $k$ 整除的方案数，则 $F(k)=\binom{\lfloor n/k\rfloor}{m}$（从 $k,2k,\dots$ 中选 $m$ 个）。设 $G(k)$ 为选出的 $m$ 个数 $\gcd$ &lt;strong&gt;恰等于&lt;/strong&gt; $k$ 的方案数，则 $F(k)=\sum_{k\mid d}G(d)$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;莫比乌斯反演&lt;/strong&gt;：由 $F(k)=\sum_{k\mid d}G(d)$ 反演得 $G(k)=\sum_{k\mid d}\mu(d/k)F(d)$。本题要求 $\gcd\ne1$，即总数减去 $\gcd=1$：&lt;/p&gt;
&lt;p&gt;$$
\text{ans}=\binom{n}{m}-\sum_{d=1}^{n}\mu(d)\binom{\lfloor n/d\rfloor}{m}
$$&lt;/p&gt;
&lt;p&gt;其中用到了 $\sum_{d\mid g}\mu(d)=[g=1]$ 这一核心恒等式。复杂度瓶颈在线性筛求 $\mu$ 与预处理阶乘/逆元到 $n$（$O(n)$），再 $O(n)$ 求和，可过 $n\le4\times10^7$。&lt;/p&gt;
&lt;h3&gt;C++代码：&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;/*EHnotgod————
..............#######.....#.....#..............
..............#...........#.....#..............
..............#######.....#######..............
..............#...........#.....#..............
..............#######.....#.....#..............
*/
#include &amp;#x3C;bits/stdc++.h&gt;
using namespace std;
// debug 宏：在编译时定义 -DLOCAL 会启用打印，否则为 no-op
#ifdef LOCAL
template&amp;#x3C;class T&gt; string ts(const T&amp;#x26; v){
    stringstream ss; ss &amp;#x3C;&amp;#x3C; v; return ss.str();
}
template&amp;#x3C;class A,class B&gt; string ts(const pair&amp;#x3C;A,B&gt;&amp;#x26; p){ return &quot;(&quot;+ts(p.first)+&quot;,&quot;+ts(p.second)+&quot;)&quot;; }
template&amp;#x3C;class T&gt; string ts(const vector&amp;#x3C;T&gt;&amp;#x26; v){ string s=&quot;{&quot;; for (auto &amp;#x26;x:v) s += ts(x)+&quot;,&quot;; return s+&quot;}&quot;; }
template&amp;#x3C;class T, class C, class A&gt; string ts(const set&amp;#x3C;T,C,A&gt;&amp;#x26; v){ string s=&quot;{&quot;; for (auto &amp;#x26;x:v) s += ts(x)+&quot;,&quot;; return s+&quot;}&quot;; }
template&amp;#x3C;class T, class C, class A&gt; string ts(const multiset&amp;#x3C;T,C,A&gt;&amp;#x26; v){ string s=&quot;{&quot;; for (auto &amp;#x26;x:v) s += ts(x)+&quot;,&quot;; return s+&quot;}&quot;; }
template&amp;#x3C;class K,class V,class C, class A&gt; string ts(const map&amp;#x3C;K,V,C,A&gt;&amp;#x26; m){ string s=&quot;{&quot;; for (auto &amp;#x26;kv:m) s += ts(kv.first)+&quot;:&quot;+ts(kv.second)+&quot;,&quot;; return s+&quot;}&quot;; }

void dbg_out(){ cerr &amp;#x3C;&amp;#x3C; &quot;\n&quot;; }
template&amp;#x3C;class H, class...T&gt; void dbg_out(const H&amp;#x26; h, const T&amp;#x26;...t){
    cerr &amp;#x3C;&amp;#x3C; &quot; &quot; &amp;#x3C;&amp;#x3C; ts(h);
    dbg_out(t...);
}
#define debug(...) cerr&amp;#x3C;&amp;#x3C;&quot;[&quot;&amp;#x3C;&amp;#x3C;#__VA_ARGS__&amp;#x3C;&amp;#x3C;&quot;]:&quot;,dbg_out(__VA_ARGS__)
#else
#define debug(...) 114514
#endif
#define endl &quot;\n&quot;
#define int long long
#define range(i, a, b) for (int i = (a); i &amp;#x3C; (b); ++i)
#define def(name, ...) auto name = [&amp;#x26;](__VA_ARGS__)


const int N = 40000010;
const int mod = 1000000007;
int p[N], vis[N], cnt;
int mu[N];
int fac[N];
int inv[N];


int qpow(int a,int b,int p){ //快速幂
  int s=1LL;
  while(b){
    if(b&amp;#x26;1) s=s*a%p;
    a=a*a%p;
    b&gt;&gt;=1LL;
  }
  return s;
}
void init(int n){
    fac[0] = 1;
    range(i, 1, n){
        fac[i] = i * fac[i - 1] % mod;
    }
    inv[n - 1] = qpow(fac[n - 1], mod - 2, mod);
    for (int i = n - 1; i &gt; 0; i--){
        inv[i - 1] = inv[i] * i % mod;
    }
}
int comb(int n, int r){
    if (n &amp;#x3C; r){
        return 0;
    }
    int ans = fac[n] * inv[r] % mod * inv[n - r] % mod;
    return ans;
}
void get_mu(int n){//筛法求莫比乌斯函数
  mu[1] = 1;
  for(int i=2; i&amp;#x3C;=n; i++){
    if(!vis[i]){
      p[++cnt] = i;
      mu[i] = -1;
    }
    for(int j=1; i*p[j]&amp;#x3C;=n; j++){
      int m = i*p[j]; 
      vis[m] = 1;
      if(i%p[j] == 0){
        mu[m] = 0;
        break;
      } 
      else
        mu[m] = -mu[i];
    }
  }
}

// vector&amp;#x3C;int&gt; a(n, 0);
// vector&amp;#x3C;vector&amp;#x3C;int&gt;&gt; dp(n + 1, vector&amp;#x3C;int&gt;(n + 1, 0));
void solve() {
	int n, m; cin &gt;&gt; n &gt;&gt; m;
	get_mu(n + 1);
    init(n + 1);
    int ans = comb(n, m);
    range(i, 1, n + 1){
        ans -= (mu[i] * comb(n/i, m) % mod);
        ans %= mod;
    }
    cout &amp;#x3C;&amp;#x3C; (ans + mod) % mod;
}

signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
	int t;
	t = 1;
	while (t--) {
		solve();
	}
	return 0;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>H2 Fraction</title><link>https://ehnotgod.site/blog/h2-fraction</link><guid isPermaLink="true">https://ehnotgod.site/blog/h2-fraction</guid><description>Fraction：分数相关算法。</description><pubDate>Wed, 02 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;亿万富翁 Cai 有一个非常特别的执念：只有当他的双脚牢牢踩在瓷砖的&lt;strong&gt;边或顶点&lt;/strong&gt;上——也就是说，踩在地板瓷砖的边界上——他才会觉得舒服。&lt;/p&gt;
&lt;p&gt;最近，他购入了一批完全相同的高档&lt;strong&gt;三角形地砖&lt;/strong&gt;，准备使用这些地砖铺满他新豪宅里一条又长又直的走廊。&lt;/p&gt;
&lt;p&gt;走廊可以视为二维笛卡尔平面上的一条&lt;strong&gt;无限长带状区域&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;为了满足 Cai 极其严格的要求，每一块三角形地砖都必须横跨走廊的整个宽度，也就是说：每一块三角形地砖都必须同时接触走廊的两条平行边界线。&lt;/p&gt;
&lt;p&gt;这样可以形成一个连续的、单行的三角形铺砌。&lt;/p&gt;
&lt;p&gt;其中有一块地砖必须恰好与 Cai 最喜欢的参考三角形&lt;/p&gt;
&lt;p&gt;$$
\triangle ABC
$$&lt;/p&gt;
&lt;p&gt;完全重合，而所有其他地砖都必须与它&lt;strong&gt;全等&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;Cai 目前正站在走廊中的一个固定位置。&lt;/p&gt;
&lt;p&gt;经过一天漫长而疲惫的改造工作之后，他已经筋疲力尽，并且无论如何都不愿意再移动。&lt;/p&gt;
&lt;p&gt;他希望这些瓷砖能够从参考三角形开始，自动向走廊两侧不断铺开，并且最终形成一个满足上述规则的铺砌。&lt;/p&gt;
&lt;p&gt;当然，只有当：Cai 当前所在的点 (D) 恰好位于某块三角形地砖的&lt;strong&gt;一条边上或者一个顶点上&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;时，他才认为这次铺砌是成功的。&lt;/p&gt;
&lt;p&gt;你的任务是判断：是否存在一种合法的走廊铺砌方式，使得点 (D) 恰好位于包含 (\triangle ABC) 的整个三角形铺砌中的某条边或某个顶点上？&lt;/p&gt;
&lt;p&gt;走廊的两条边界定义为两条唯一确定的平行直线&lt;/p&gt;
&lt;p&gt;$$
l_1,\ l_2,
$$&lt;/p&gt;
&lt;p&gt;其中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;(l_1) 经过点 (A) 和 (B)；&lt;/li&gt;
&lt;li&gt;(l_2) 经过点 (C)。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;由 ((l_1,l_2)) 所形成的带状区域的一种&lt;strong&gt;铺砌（tessellation）&lt;/strong&gt;，指的是：使用与 (\triangle ABC) 全等的三角形，通过平移、旋转和翻转，将整个无限长带状区域恰好铺满，且所有三角形之间&lt;strong&gt;没有重叠，也没有空隙&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;**输入格式&lt;/p&gt;
&lt;p&gt;有多组测试数据。&lt;/p&gt;
&lt;p&gt;第一行包含一个整数&lt;/p&gt;
&lt;p&gt;$$
T\qquad(1\le T\le10^3),
$$&lt;/p&gt;
&lt;p&gt;表示测试用例数量。&lt;/p&gt;
&lt;p&gt;对于每组测试数据：&lt;/p&gt;
&lt;p&gt;第一行包含两个整数&lt;/p&gt;
&lt;p&gt;$$
x_a,\ y_a
\qquad(-100\le x_a,y_a\le100),
$$&lt;/p&gt;
&lt;p&gt;表示点 (A) 的坐标。&lt;/p&gt;
&lt;p&gt;第二行包含两个整数&lt;/p&gt;
&lt;p&gt;$$
x_b,\ y_b
\qquad(-100\le x_b,y_b\le100),
$$&lt;/p&gt;
&lt;p&gt;表示点 (B) 的坐标。&lt;/p&gt;
&lt;p&gt;第三行包含两个整数&lt;/p&gt;
&lt;p&gt;$$
x_c,\ y_c
\qquad(-100\le x_c,y_c\le100),
$$&lt;/p&gt;
&lt;p&gt;表示点 (C) 的坐标。&lt;/p&gt;
&lt;p&gt;第四行包含两个分数&lt;/p&gt;
&lt;p&gt;$$
\frac{x_{d1}}{x_{d2}}
\qquad\text{和}\qquad
\frac{y_{d1}}{y_{d2}},
$$&lt;/p&gt;
&lt;p&gt;其中&lt;/p&gt;
&lt;p&gt;$$
-10^7&amp;#x3C;x_{d1},y_{d1}&amp;#x3C;10^7,
$$&lt;/p&gt;
&lt;p&gt;并且&lt;/p&gt;
&lt;p&gt;$$
0&amp;#x3C;x_{d2},y_{d2}&amp;#x3C;10^7.
$$&lt;/p&gt;
&lt;p&gt;它们分别表示点 (D) 的 (x) 坐标与 (y) 坐标，即&lt;/p&gt;
&lt;p&gt;$$
D=
\left(
\frac{x_{d1}}{x_{d2}},
\frac{y_{d1}}{y_{d2}}
\right).
$$&lt;/p&gt;
&lt;p&gt;保证：&lt;/p&gt;
&lt;p&gt;$$
A,B,C
$$&lt;/p&gt;
&lt;p&gt;构成一个&lt;strong&gt;非退化三角形&lt;/strong&gt;，即三点不共线。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;对于每组测试数据：&lt;/p&gt;
&lt;p&gt;如果存在一种合法铺砌，使得点 (D) 位于铺砌中的至少一个三角形的&lt;strong&gt;边或顶点&lt;/strong&gt;上，输出：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Yes
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;否则输出：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;No
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;样例输入&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;4
-2 1
0 0
0 2
2/1 0/1
-2 1
0 0
0 2
1/1 0/1
0 1
-2 1
0 2
1/2 7/4
0 1
-2 1
0 2
-3/2 3/4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;样例输出&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Yes
No
Yes
No
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;图中的前两个样例可以理解为：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Case 1：Yes&lt;/strong&gt;：点 (D) 正好落在某条三角形边界上；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Case 2：No&lt;/strong&gt;：点 (D) 落在某个三角形的内部，因此不满足要求。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这道题的核心其实不是一般意义上的“三角形铺砖搜索”，而是要研究：&lt;strong&gt;一个三角形横跨两条平行线以后，沿走廊方向不断翻折/镜像铺砌，其所有边界点会形成什么样的周期结构。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H2-1.png&quot; alt=&quot;铺砌示意图&quot;&gt;&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;略，主要是介绍Fraction的用法&lt;/p&gt;
&lt;h3&gt;py代码：&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import fractions
t = int(input())

def cross(a, b):
    return a.x * b.y - b.x * a.y

class pt:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __add__(self, p):
        return pt(self.x + p.x,
                  self.y + p.y)
    def __sub__(self, p):
        return pt(self.x - p.x,
                  self.y - p.y)
    def __mul__(self, d):
        return pt(self.x * d,
                self.y * d)
    def __truediv__(self, d):
        return pt(self.x / d,
                self.y / d)
    def __str__(self):
        return f&quot;({self.x}, {self.y})&quot;
    def __eq__(self, p):
        return self.x == p.x and self.y == p.y

class line:
    def __init__(self, p, q):
        self.v = q - p
        self.c = cross(self.v, p)

def side(ln, p):
    return cross(ln.v, p) - ln.c

def inter(l1, l2):
    d = cross(l1.v, l2.v)
    return (l2.v * l1.c - l1.v * l2.c) / d

def dot(v, w):
    return v.x* w.x+ v.y* w.y




def slave():
    xa, ya = map(int, input().split())
    xb, yb = map(int, input().split())
    xc, yc = map(int, input().split())
    xa_f = fractions.Fraction(xa, 1)
    ya_f = fractions.Fraction(ya, 1)
    xb_f = fractions.Fraction(xb, 1)
    yb_f = fractions.Fraction(yb, 1)
    xc_f = fractions.Fraction(xc, 1)
    yc_f = fractions.Fraction(yc, 1)
    a = pt(xa_f, ya_f)
    b = pt(xb_f, yb_f)
    c = pt(xc_f, yc_f)
    xd, yd = input().split()
    xd1, xd2 = map(int, xd.split(&quot;/&quot;))
    yd1, yd2 = map(int, yd.split(&quot;/&quot;))
    xd_f = fractions.Fraction(xd1, xd2)
    yd_f = fractions.Fraction(yd1, yd2)
    d = pt(xd_f, yd_f)

    ln_ab = line(a, b)
    ln_c = line(c, b - a + c)

    # 看看在不在线上
    if cross(a - b, a - d) == 0 or cross(a - b, c - d) == 0:
        return 1
    # 看看在不在线内
    if side(ln_ab, d) * side(ln_c, d) &gt; 0:
        return 0
    ln1 = line(d, d + c - a)
    ln2 = line(d, d + c - b)
    h1 = inter(ln1, ln_c)
    h2 = inter(ln2, ln_c)
    def pan(h, can=c):
        f1 = 0; f2 = 0
        if (b - a).x == 0 or (h - can).x % abs((b - a).x) == 0:
            f1 = 1
        if (b - a).y == 0 or (h - can).y % abs((b - a).y) == 0:
            f2 = 1
        return f1 and f2

    if pan(h1) or pan(h2):
        return 1
    if dot(a - b, a - c) == 0:
        ln3 = line(d, d + c - a * 2+ b)
        h3 = inter(ln3, ln_ab)
        if h3 != a and pan(h3, a):
            return 1
    if dot(a - b, b - c) == 0:
        ln3 = line(d, d + c + a - b * 2)
        h3 = inter(ln3, ln_ab)
        if h3 != b and pan(h3, b):
            return 1

    return 0

for _ in range(t):
    print(&quot;Yes&quot; if slave() else &quot;No&quot;)
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>H3 Green公式</title><link>https://ehnotgod.site/blog/h3-green%E5%85%AC%E5%BC%8F</link><guid isPermaLink="true">https://ehnotgod.site/blog/h3-green%E5%85%AC%E5%BC%8F</guid><description>Green 公式：格林公式。</description><pubDate>Wed, 02 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://qoj.ac/contest/2585/problem/14939&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H3-1.png&quot; alt=&quot;Problem D. 很多喷洒器&quot;&gt;&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;这题求多个圆的面积并。做法是枚举每个圆，计算它的圆周被其他圆覆盖的角度区间，将这些区间合并，剩下的“裸露圆弧”就是圆并边界。然后利用 &lt;strong&gt;Green 公式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;$$
S=\frac12\oint_{\partial\Omega}(x,dy-y,dx)
$$&lt;/p&gt;
&lt;p&gt;把边界积分转成面积。对圆心 ((x_0,y_0))、半径 (R) 的圆弧 (\theta\in[l,r])，参数化为&lt;/p&gt;
&lt;p&gt;$$
x=x_0+R\cos\theta,\quad y=y_0+R\sin\theta
$$&lt;/p&gt;
&lt;p&gt;代入 Green 公式，可得该圆弧的面积贡献&lt;/p&gt;
&lt;p&gt;$$
\frac12\left[R^2(r-l)+Rx_0(\sin r-\sin l)+Ry_0(\cos l-\cos r)\right].
$$&lt;/p&gt;
&lt;p&gt;把所有未被覆盖圆弧的贡献累加，即得到所有圆的并集面积。整体复杂度为&lt;/p&gt;
&lt;p&gt;$$
O(n^2\log n).
$$&lt;/p&gt;
&lt;h3&gt;py代码&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import math

PI = math.pi
TAU = 2 * PI
EPS = 1e-12

def solve():
    n = int(input())
    c = [tuple(map(float, input().split())) for _ in range(n)]
    ans = 0.0

    def arc(x, y, R, l, r):
        return (R*R*(r-l)
                + R*x*(math.sin(r)-math.sin(l))
                + R*y*(math.cos(l)-math.cos(r))) / 2

    for i, (x, y, R) in enumerate(c):
        seg = []
        covered = False

        for j, (X, Y, r) in enumerate(c):
            if i == j:
                continue

            dx, dy = X-x, Y-y
            d = math.hypot(dx, dy)

            # 重合圆，只保留一个
            if d &amp;#x3C; EPS and abs(R-r) &amp;#x3C; EPS:
                if j &amp;#x3C; i:
                    covered = True
                    break
                continue

            # 当前圆被完全覆盖
            if d + R &amp;#x3C;= r + EPS:
                covered = True
                break

            # 相离，或另一个圆完全在当前圆内部
            if d &gt;= R+r-EPS or d+r &amp;#x3C;= R+EPS:
                continue

            a = math.atan2(dy, dx)
            co = (R*R + d*d - r*r) / (2*R*d)
            b = math.acos(max(-1, min(1, co)))

            l = (a-b) % TAU
            rr = (a+b) % TAU

            if l &amp;#x3C;= rr:
                seg.append((l, rr))
            else:
                seg.append((l, TAU))
                seg.append((0, rr))

        if covered:
            continue

        seg.sort()
        now = 0.0

        for l, r in seg:
            if l &gt; now:
                ans += arc(x, y, R, now, l)
            now = max(now, r)

        if now &amp;#x3C; TAU:
            ans += arc(x, y, R, now, TAU)

    print(f&quot;{ans:.15f}&quot;)

T = int(input())
for _ in range(T):
    solve()
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>H4 超绝浮点精度</title><link>https://ehnotgod.site/blog/h4-%E8%B6%85%E7%BB%9D%E6%B5%AE%E7%82%B9%E7%B2%BE%E5%BA%A6</link><guid isPermaLink="true">https://ehnotgod.site/blog/h4-%E8%B6%85%E7%BB%9D%E6%B5%AE%E7%82%B9%E7%B2%BE%E5%BA%A6</guid><description>高精度浮点：用 Decimal 处理精度极限。</description><pubDate>Wed, 02 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：待补充&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&quot;荒泷卡牌天下第一斗&quot;酷爱卡牌，尤其是最近马上要上线的&quot;七圣召唤&quot;，荒泷一斗想在 2.3 版本前集齐全部种类的卡牌，但他不精于计算，所以他找来了你、战力第一的旅行者帮忙。&lt;/p&gt;
&lt;p&gt;已知&quot;七圣召唤&quot;共有 $K$ 张不同的卡牌，荒泷一斗的资金够买 $N$ 个卡包。每个卡包能随机从 $K$ 种卡牌中抽到一张。现给出 $N$ 和 $K$，他想问你要集齐全部种类的卡牌所需的期望卡包数，以及 $N$ 个卡包能开出的期望卡牌种类数。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行输入两个整数 $N$ 和 $K$。$N$ 表示卡包数，$K$ 表示卡牌种类数（$1\le N\le 10^6,\ 1\le K\le 10^6$）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行输出两个浮点数，分别是集齐全部种类的卡牌所需的期望卡包数，和 $N$ 个卡包能开出的期望卡牌种类数，中间用空格间隔（答案与标准答案相差 $10^{-6}$ 以内就算正确）。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;3 3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;5.50000000 2.11111111
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输入 #2&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;4 3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #2&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;5.50000000 2.40740741
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;说明/提示&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;本邪教为 py 邪教，方法是使用 Python 的 Decimal 提升精度，在 WA 与 T 之间徘徊。&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;两问本质都是期望计算：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;集齐全部 $K$ 种所需的期望卡包数&lt;/strong&gt;：经典优惠券收集问题。当已集齐 $i$ 种时，抽到新种类的概率为 $\frac{K-i}{K}$，期望还需 $\frac{K}{K-i}$ 包。故总期望 $=\sum_{i=0}^{K-1}\frac{K}{K-i}=K\sum_{j=1}^{K}\frac1j$。直接浮点累加 $K$ 项在 $K$ 大时误差累积，用高精度 &lt;code&gt;Decimal&lt;/code&gt; 提升精度。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;$N$ 个卡包能开出的期望种类数&lt;/strong&gt;：对每种卡牌，$N$ 包都没开到的概率为 $\left(1-\frac1K\right)^N$，故至少开到一次的概率为 $1-\left(1-\frac1K\right)^N$，期望种类数 $=K\left(1-\left(1-\frac1K\right)^N\right)$。幂运算 &lt;code&gt;**&lt;/code&gt; 在 Decimal 下同样高精度。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from decimal import Decimal, getcontext

getcontext().prec = 200

n, k = map(int, input().split())
n = Decimal(n)
k = Decimal(k)

ans1 = Decimal(0)
for i in range(int(k)):
    ans1 += k / Decimal(i + 1)

ans2 = k * (Decimal(1) - (Decimal(1) - Decimal(1) / k) ** n)

print(ans1, ans2)
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>H1 计算几何通用</title><link>https://ehnotgod.site/blog/h1-%E8%AE%A1%E7%AE%97%E5%87%A0%E4%BD%95%E9%80%9A%E7%94%A8</link><guid isPermaLink="true">https://ehnotgod.site/blog/h1-%E8%AE%A1%E7%AE%97%E5%87%A0%E4%BD%95%E9%80%9A%E7%94%A8</guid><description>计算几何通用：向量、点、线、多边形等基础模板。</description><pubDate>Tue, 01 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;计算几何通用模板：向量运算、点与线、多边形、凸包、极角排序等基础内容（整理自&quot;计算几何专题讲座&quot;）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;说明/提示&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;本页为计算几何通用笔记，逐页以图片形式呈现。&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;计算几何通用：以向量叉积、点积为核心，覆盖点的比较、直线相交、多边形面积/包含、凸包与旋转卡壳等基础模板。以下为完整笔记。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-01.png&quot; alt=&quot;H1-01&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-02.png&quot; alt=&quot;H1-02&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-03.png&quot; alt=&quot;H1-03&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-04.png&quot; alt=&quot;H1-04&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-05.png&quot; alt=&quot;H1-05&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-06.png&quot; alt=&quot;H1-06&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-07.png&quot; alt=&quot;H1-07&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-08.png&quot; alt=&quot;H1-08&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-09.png&quot; alt=&quot;H1-09&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-10.png&quot; alt=&quot;H1-10&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-11.png&quot; alt=&quot;H1-11&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-12.png&quot; alt=&quot;H1-12&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-13.png&quot; alt=&quot;H1-13&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-14.png&quot; alt=&quot;H1-14&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-15.png&quot; alt=&quot;H1-15&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-16.png&quot; alt=&quot;H1-16&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-17.png&quot; alt=&quot;H1-17&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-18.png&quot; alt=&quot;H1-18&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-19.png&quot; alt=&quot;H1-19&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-20.png&quot; alt=&quot;H1-20&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-21.png&quot; alt=&quot;H1-21&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-22.png&quot; alt=&quot;H1-22&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-23.png&quot; alt=&quot;H1-23&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-24.png&quot; alt=&quot;H1-24&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-25.png&quot; alt=&quot;H1-25&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-26.png&quot; alt=&quot;H1-26&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-27.png&quot; alt=&quot;H1-27&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-28.png&quot; alt=&quot;H1-28&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-29.png&quot; alt=&quot;H1-29&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-30.png&quot; alt=&quot;H1-30&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-31.png&quot; alt=&quot;H1-31&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-32.png&quot; alt=&quot;H1-32&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-33.png&quot; alt=&quot;H1-33&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-34.png&quot; alt=&quot;H1-34&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-35.png&quot; alt=&quot;H1-35&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-36.png&quot; alt=&quot;H1-36&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-37.png&quot; alt=&quot;H1-37&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-38.png&quot; alt=&quot;H1-38&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-39.png&quot; alt=&quot;H1-39&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-40.png&quot; alt=&quot;H1-40&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-41.png&quot; alt=&quot;H1-41&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-42.png&quot; alt=&quot;H1-42&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-43.png&quot; alt=&quot;H1-43&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-44.png&quot; alt=&quot;H1-44&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-45.png&quot; alt=&quot;H1-45&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-46.png&quot; alt=&quot;H1-46&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-47.png&quot; alt=&quot;H1-47&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-48.png&quot; alt=&quot;H1-48&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-49.png&quot; alt=&quot;H1-49&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-50.png&quot; alt=&quot;H1-50&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-51.png&quot; alt=&quot;H1-51&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-52.png&quot; alt=&quot;H1-52&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-53.png&quot; alt=&quot;H1-53&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-54.png&quot; alt=&quot;H1-54&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-55.png&quot; alt=&quot;H1-55&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-56.png&quot; alt=&quot;H1-56&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-57.png&quot; alt=&quot;H1-57&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-58.png&quot; alt=&quot;H1-58&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-59.png&quot; alt=&quot;H1-59&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-60.png&quot; alt=&quot;H1-60&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-61.png&quot; alt=&quot;H1-61&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-62.png&quot; alt=&quot;H1-62&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-63.png&quot; alt=&quot;H1-63&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-64.png&quot; alt=&quot;H1-64&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-65.png&quot; alt=&quot;H1-65&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-66.png&quot; alt=&quot;H1-66&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-67.png&quot; alt=&quot;H1-67&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-68.png&quot; alt=&quot;H1-68&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-69.png&quot; alt=&quot;H1-69&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-70.png&quot; alt=&quot;H1-70&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-71.png&quot; alt=&quot;H1-71&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-72.png&quot; alt=&quot;H1-72&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-73.png&quot; alt=&quot;H1-73&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-74.png&quot; alt=&quot;H1-74&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-75.png&quot; alt=&quot;H1-75&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-76.png&quot; alt=&quot;H1-76&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-77.png&quot; alt=&quot;H1-77&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-78.png&quot; alt=&quot;H1-78&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-79.png&quot; alt=&quot;H1-79&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-80.png&quot; alt=&quot;H1-80&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/H1-81.png&quot; alt=&quot;H1-81&quot;&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;/*EHnotgod————
..............#######.....#.....#..............
..............#...........#.....#..............
..............#######.....#######..............
..............#...........#.....#..............
..............#######.....#.....#..............
*/

// 1.头文件(EH:?这也能算第一点?)
#include &amp;#x3C;bits/stdc++.h&gt;
using namespace std;
#define endl &quot;\n&quot;
#define int long long
#define M_PI acos(-1)
#define range(i, a, b) for (int i = (a); i &amp;#x3C; (b); ++i)
typedef long double T;
// template&amp;#x3C;class T&gt; string ts(const T&amp;#x26; v){
//     stringstream ss; ss &amp;#x3C;&amp;#x3C; v; return ss.str();
// }
// template&amp;#x3C;class A,class B&gt; string ts(const pair&amp;#x3C;A,B&gt;&amp;#x26; p){ return &quot;(&quot;+ts(p.first)+&quot;,&quot;+ts(p.second)+&quot;)&quot;; }
// template&amp;#x3C;class T&gt; string ts(const vector&amp;#x3C;T&gt;&amp;#x26; v){ string s=&quot;{&quot;; for (auto &amp;#x26;x:v) s += ts(x)+&quot;,&quot;; return s+&quot;}&quot;; }
// template&amp;#x3C;class T, class C, class A&gt; string ts(const set&amp;#x3C;T,C,A&gt;&amp;#x26; v){ string s=&quot;{&quot;; for (auto &amp;#x26;x:v) s += ts(x)+&quot;,&quot;; return s+&quot;}&quot;; }
// template&amp;#x3C;class T, class C, class A&gt; string ts(const multiset&amp;#x3C;T,C,A&gt;&amp;#x26; v){ string s=&quot;{&quot;; for (auto &amp;#x26;x:v) s += ts(x)+&quot;,&quot;; return s+&quot;}&quot;; }
// template&amp;#x3C;class K,class V,class C, class A&gt; string ts(const map&amp;#x3C;K,V,C,A&gt;&amp;#x26; m){ string s=&quot;{&quot;; for (auto &amp;#x26;kv:m) s += ts(kv.first)+&quot;:&quot;+ts(kv.second)+&quot;,&quot;; return s+&quot;}&quot;; }
// void dbg_out(){ cerr &amp;#x3C;&amp;#x3C; &quot;\n&quot;; }
// template&amp;#x3C;class H, class...T&gt; void dbg_out(const H&amp;#x26; h, const T&amp;#x26;...t){
//     cerr &amp;#x3C;&amp;#x3C; &quot; &quot; &amp;#x3C;&amp;#x3C; ts(h);
//     dbg_out(t...);
// }
// #define debug(...) cerr&amp;#x3C;&amp;#x3C;&quot;[&quot;&amp;#x3C;&amp;#x3C;#__VA_ARGS__&amp;#x3C;&amp;#x3C;&quot;]:&quot;,dbg_out(__VA_ARGS__)

// 2.1 点
// 2.1.1基本定义
struct pt{
    T x, y;
    pt operator+(pt p){return{x+p.x, y+p.y};}
    pt operator-(pt p){return{x-p.x, y-p.y};}
    pt operator*(T d){return{x*d, y*d};}
    pt operator/(T d){return{x/d, y/d};} //注意不能除以0
    bool operator&amp;#x3C;(const pt&amp;#x26; a) const {
    if (x != a.x) return x &amp;#x3C; a.x;
        return y &amp;#x3C; a.y;
    }
    bool operator==(const pt&amp;#x26; other) const { return x == other.x &amp;#x26;&amp;#x26; y == other.y; }
    bool operator!=(const pt&amp;#x26; other) const { return !(*this == other); }
};
T sq(pt p) {return p.x*p.x+ p.y*p.y;}
T absv(pt p){return sqrt(sq(p));}
ostream&amp;#x26;operator&amp;#x3C;&amp;#x3C;(ostream&amp;#x26;os, pt p){
    return os &amp;#x3C;&amp;#x3C;&quot;(&quot;&amp;#x3C;&amp;#x3C; p.x&amp;#x3C;&amp;#x3C;&quot;,&quot;&amp;#x3C;&amp;#x3C; p.y&amp;#x3C;&amp;#x3C;&quot;)&quot;;
}
int sgn(T x){return(T(0)&amp;#x3C; x)-(x &amp;#x3C;T(0));}
// 2.1.3基本变换：平移
pt translate(pt v, pt p){
    return p + v;
}
// 2.1.4基本变换：缩放
pt scale(pt c,T factor, pt p){
    return c +(p - c)* factor;
}
// 2.1.5基本变换：旋转（逆时针）
pt rot(pt p,T a){
    return{p.x*cos(a)- p.y*sin(a), p.x*sin(a)+ p.y*cos(a)};}
pt perp(pt p){
    return{-p.y, p.x};
}
// 2.1.6 点积
T dot(pt v, pt w){
    return v.x* w.x+ v.y* w.y;
}
bool isPerp(pt v, pt w){
    return dot(v, w)==0;
}
// 2.1.7 叉积
T cross(pt v, pt w){return v.x* w.y- v.y* w.x;}
T angle(pt v, pt w){
    return abs(atan2(cross(v, w), dot(v, w)));
}
T orient(pt a, pt b, pt c){return cross(b - a, c - a);}
bool inAngle(pt a, pt b, pt c, pt p){
    assert(orient(a, b, c)!=0);
	if(orient(a, b, c)&amp;#x3C;0)swap(b, c);
	return orient(a, b, p)&gt;=0&amp;#x26;&amp;#x26;orient(a, c, p)&amp;#x3C;=0;
}
T orientedAngle(pt a, pt b, pt c){
    if(orient(a, b, c)&gt;=0){return angle(b - a, c - a);}
	else{return 2* M_PI -angle(b - a, c - a);
}
}
// 2.1.8 极角排序
bool half(pt p){
	return p.y&gt;0||(p.y==0&amp;#x26;&amp;#x26; p.x&amp;#x3C;0);
}
void polarSort(vector&amp;#x3C;pt&gt;&amp;#x26;v){
    sort(v.begin(), v.end(),[](pt v, pt w){
        return make_tuple(half(v),0,sq(v))&amp;#x3C;
            make_tuple(half(w),cross(v,w),sq(w));
    });
}

// 2.2 直线
// 2.2.1 基本定义
struct line{
    pt v; T c;
    line(pt v, T c):v(v),c(c){} // 点斜式
    line(T a, T b, T c):v({b,-a}),c(c){} // 标准式(ax+by+c=0)
    line(pt p, pt q):v(q-p),c(cross(v,p)){} // 两点式
};
// 2.2.2点与直线的位置关系
T side(line ln, pt p){
    return cross(ln.v, p)- ln.c;}
// 2.2.3点到直线的距离
T dist(line ln, pt p){return abs(side(ln, p))/absv(ln.v);}
// 2.2.4过一点的垂线
line perpThrough(line ln, pt p){return{p, p +perp(ln.v)};}  
// 2.2.5沿直线排序   
bool cmpProj(line ln, pt p, pt q){return dot(ln.v, p)&amp;#x3C;dot(ln.v, q);}   
// 2.2.6沿指定方向平移    
line translate(line ln, pt t){return{ln.v, ln.c +cross(ln.v, t)};} 
// 2.2.7直线交点  
bool inter(line l1, line l2, pt &amp;#x26;out){
    T d =cross(l1.v, l2.v); if(d == 0)return false;
    out =(l2.v* l1.c- l1.v* l2.c)/ d;
    return true;
}
// 2.2.8正交投影与反射
pt proj(line ln, pt p){return p -perp(ln.v)*side(ln, p)/sq(ln.v);}
pt refl(line ln, pt p){return p -perp(ln.v)*2*side(ln, p)/sq(ln.v);}
// 2.2.9点到直线的平方距离
T sqDist(line ln, pt p) {
    return side(ln, p) * side(ln, p) / sq(ln.v); 
}
// 2.3 线段
// 2.3.0 定义?没有定义，线段不TM就两个点吗
// 2.3.1点在线段为直径的圆上    
bool inDisk(pt a, pt b, pt p){return dot(a - p, b - p)&amp;#x3C;=0;}
// 2.3.2点在线段上    
bool onSegment(pt a, pt b, pt p){
    return orient(a, b, p)==0 &amp;#x26;&amp;#x26; inDisk(a, b, p);
}
// 2.3.3线段与线段交点    
bool properInter(pt a, pt b, pt c, pt d, pt &amp;#x26;out){    
    T oa =orient(c, d, a), ob =orient(c, d, b),         
      oc =orient(a, b, c), od =orient(a, b, d);
	if(oa * ob &amp;#x3C;0&amp;#x26;&amp;#x26; oc * od &amp;#x3C;0){
    	out =(a * ob - b * oa)/(ob - oa);
    	return true;
    }
	return false;
}
set&amp;#x3C;pt&gt;inters(pt a, pt b, pt c, pt d){
  pt out;        
  if(properInter(a, b, c, d, out))return{out};  
  set&amp;#x3C;pt&gt; s;      
  if(onSegment(c, d, a)) s.insert(a);
  if(onSegment(c, d, b)) s.insert(b);
  if(onSegment(a, b, c)) s.insert(c);
  if(onSegment(a, b, d)) s.insert(d);
  return s;
}// 如果|s|=1则只有一个交点；如果|s|=2则说明相交的区域是一条线段，并且中存的是其他两个端点

// 2.3.4点到线段的距离
T segPoint(pt a, pt b, pt p){
    if(a != b){
    	line l(a, b);
    	if(cmpProj(l, a, p)&amp;#x26;&amp;#x26; cmpProj(l, p, b))
            return dist(l, p);
		return min(absv(p - a),absv(p - b));
    }
	return absv(p - a);
}

// 2.3.5线段到线段的距离  
T segSeg(pt a, pt b, pt c, pt d){
  pt dummy;    
  if(properInter(a, b, c, d, dummy))return 0;
  return min({segPoint(a, b, c),segPoint(a, b, d), 
              segPoint(c, d, a),segPoint(c, d, b)});
}

// 2.4 多边形
// 2.4.1多边形面积
T areaPolygon(vector&amp;#x3C;pt&gt;p){
    T area =0.0;
	for(int i =0, n = p.size(); i &amp;#x3C; n; i++){
    	area +=cross(p[i], p[(i +1)% n]);
    }
	return abs(area)/2.0;
}
// 2.4.2 射线法：判断点是否在多边形内部
bool above(pt a, pt p){
    return p.y&gt;= a.y;
}
bool crossesRay(pt a, pt p, pt q){
    return(above(a, q)-above(a, p))*
    	orient(a, p, q)&gt;0;
}
// 这个strict可以自己调控，如果 strict=true（默认值），点在边上返回 false。如果 strict=false，点在边上返回 true。
bool inPolygon(vector&amp;#x3C;pt&gt;p, pt a, bool strict=true){
    int numCrossings =0;
	for(int i =0, n = p.size(); i &amp;#x3C; n; i++){
        if(onSegment(p[i], p[(i +1)% n], a))return!strict;
            numCrossings +=crossesRay(a, p[i], p[(i +1)% n]);
    }
    return numCrossings &amp;#x26; 1;
}
// 2.4.3凸包
bool turn_left(pt a, pt b, pt c) { 
    return sgn(cross(b - a, c - a)) &gt; 0; 
}
vector&amp;#x3C;pt&gt; convex_hull(vector&amp;#x3C;pt&gt; a) {
    if (a.size() &amp;#x3C;= 2) return a;
    pt base = *min_element(a.begin(), a.end());
    sort(a.begin(), a.end(), [&amp;#x26;](auto u, auto v) {
        return make_tuple(half(u - base), 0) &amp;#x3C; make_tuple(half(u - base), cross(u - base, v - base));
    });
    vector&amp;#x3C;pt&gt; ret;
    for (auto i : a) {
        while (ret.size() &gt; 1 &amp;#x26;&amp;#x26; !turn_left(ret[ret.size() - 2], ret[ret.size() - 1], i)) 
            ret.pop_back();
        ret.push_back(i);
    }
    return ret;
}

// 2.4.4 CCW判点-严格判断点 p 是否在凸多边形 A（CCW）内部，但是A必须逆时针排列
bool pointInConvexStrict(const vector&amp;#x3C;pt&gt;&amp;#x26; A, const pt&amp;#x26; p) {
    int n = (int)A.size();
    if (n &amp;#x3C; 3) return false;
    if (sgn(orient(A[0], A[1], p)) &amp;#x3C;= 0) return false;
    if (sgn(orient(A[0], A[n-1], p)) &gt;= 0) return false;

    int l = 1, r = n - 1;
    while (r - l &gt; 1) {
        int mid = (l + r) &gt;&gt; 1;
        if (sgn(orient(A[0], A[mid], p)) &gt; 0) l = mid;
        else r = mid;
    }
    return sgn(orient(A[l], A[l+1], p)) &gt; 0;
}

// 2.5圆
// 2.5.1 外接圆，返回的是外心！别问我为啥不返回半径，要算的话，自己逮个点算不就得了
pt circumCenter(pt a, pt b, pt c){
  b = b - a, c = c - a;
  assert(cross(b, c)!=0);
  return a +perp(b *sq(c)- c *sq(b))/cross(b, c)/2;
}

// 2.5.2圆与直线的交点
int circleLine(pt o,T r, line ln,pair&amp;#x3C;pt, pt&gt;&amp;#x26;out){
    T h2 = r * r - sqDist(ln, o);
	if(h2 &gt;= 0){
    	pt p = proj(ln, o);
    	pt h = ln.v*sqrt(h2)/absv(ln.v);
        out = {p - h, p + h};
    }
	return 1+sgn(h2);
}

// 2.5.3圆与圆的交点
int circleCircle(pt o1,T r1, pt o2,T r2, pair&amp;#x3C;pt, pt&gt;&amp;#x26;out){
    pt d = o2 - o1;    
    T d2 =sq(d);
    if(d2 == 0){
        assert(r1 != r2);
		return 0;
    }
	T pd =(d2 + r1 * r1 - r2 * r2)/2;
    T h2 = r1 * r1 - pd * pd / d2;
    if(h2 &gt;=0){ pt p = o1 + d * pd / d2, h =perp(d)*sqrt(h2 / d2); out = {p - h, p + h};}
    return 1 + sgn(h2);
}
bool inTri(pt a,pt b,pt c,pt p){
 return orient(a,b,p)&gt;=0&amp;#x26;&amp;#x26;orient(b,c,p)&gt;=0&amp;#x26;&amp;#x26;orient(c,a,p)&gt;=0;
}
vector&amp;#x3C;tuple&amp;#x3C;pt,pt,pt&gt;&gt; earCut(vector&amp;#x3C;pt&gt;p){
 int n=p.size(),c=n;vector&amp;#x3C;int&gt;l(n),r(n),v(n,1);deque&amp;#x3C;int&gt;q;
 vector&amp;#x3C;tuple&amp;#x3C;pt,pt,pt&gt;&gt;z;
 range(i,0,n)l[i]=(i+n-1)%n,r[i]=(i+1)%n,q.push_back(i);
 auto E=[&amp;#x26;](int x){if(!v[x]||orient(p[l[x]],p[x],p[r[x]])&amp;#x3C;=0)return 0;
  range(i,0,n)if(v[i]&amp;#x26;&amp;#x26;i!=l[x]&amp;#x26;&amp;#x26;i!=x&amp;#x26;&amp;#x26;i!=r[x]&amp;#x26;&amp;#x26;inTri(p[l[x]],p[x],p[r[x]],p[i]))return 0;
  return 1;};
 while(c&gt;3){int x=q.front();q.pop_front();if(!E(x))continue;
  int L=l[x],R=r[x];z.push_back({p[L],p[x],p[R]});
  v[x]=0;r[L]=R;l[R]=L;c--;q.push_back(L);q.push_back(R);}
 range(i,0,n)if(v[i]){z.push_back({p[i],p[r[i]],p[r[r[i]]]});break;}
 return z;
}

// 快捷CV处
// vector&amp;#x3C;int&gt; a(n, 0);
// vector&amp;#x3C;vector&amp;#x3C;int&gt;&gt; dp(n + 1, vector&amp;#x3C;int&gt;(n + 1, 0));
void solve() {
	int n; cin &gt;&gt; n;
	vector&amp;#x3C;pt&gt; a(n);
	for (int i = 0; i &amp;#x3C; n; i++){
		cin &gt;&gt; a[i].x; cin &gt;&gt; a[i].y;
	}
}

signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
	int t;
	cin &gt;&gt; t;
	while (t--) {
		solve();
	}
	return 0;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>决策树算法</title><link>https://ehnotgod.site/blog/%E5%86%B3%E7%AD%96%E6%A0%91%E7%AE%97%E6%B3%95</link><guid isPermaLink="true">https://ehnotgod.site/blog/%E5%86%B3%E7%AD%96%E6%A0%91%E7%AE%97%E6%B3%95</guid><description>决策树：信息增益 / 基尼指数划分，可解释性强。</description><pubDate>Sun, 09 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;决策树算法（分类 + 回归）&lt;/h1&gt;
&lt;h2&gt;一、决策树到底在干嘛？&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;决策树&lt;/strong&gt;就是一种&quot;不断问问题&quot;的模型。它长成&lt;strong&gt;树&lt;/strong&gt;的样子，每个分叉点问一个问题，顺着答案往下走，最后走到&lt;strong&gt;叶子&lt;/strong&gt;得到一个结论。&lt;/p&gt;
&lt;p&gt;先看一个生活化的例子——&lt;strong&gt;周末去不去打球&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;           今天天气如何？
          /      |      \
       晴天     阴天      雨天
        /        |          \
   风大吗？    → 去！      湿度高吗？
    /    \                  /      \
 风大→不去  风小→去      湿度低→去  湿度高→不去
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;核心问题：&lt;strong&gt;这些&quot;问题&quot;是怎么选出来的？凭什么先问&quot;天气&quot;而不是先问&quot;风大不大&quot;？&lt;/strong&gt; 这就是决策树算法的精髓——它从数据里&quot;学&quot;出最该先问哪个问题。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2&gt;二、拿真实数据，手动建棵树&lt;/h2&gt;
&lt;p&gt;决策树不是凭空设计的，它要从&lt;strong&gt;带标签的历史数据&lt;/strong&gt;里学。下面是我们&quot;打球数据集&quot;，一共 14 天记录（4 个特征 + 1 个结果）：&lt;/p&gt;
&lt;p&gt;| 天数 | 天气 Outlook | 温度 Temperature | 湿度 Humidity | 风 Wind | 是否打球 Play |
|------|-------------|-----------------|--------------|---------|--------------|
| D1 | 晴 Sunny | 热 Hot | 高 High | 小 Weak | 否 No |
| D2 | 晴 Sunny | 热 Hot | 高 High | 大 Strong | 否 No |
| D3 | 阴 Overcast | 热 Hot | 高 High | 小 Weak | 是 Yes |
| D4 | 雨 Rain | 温和 Mild | 高 High | 小 Weak | 是 Yes |
| D5 | 雨 Rain | 凉 Cool | 正常 Normal | 小 Weak | 是 Yes |
| D6 | 雨 Rain | 凉 Cool | 正常 Normal | 大 Strong | 否 No |
| D7 | 阴 Overcast | 凉 Cool | 正常 Normal | 大 Strong | 是 Yes |
| D8 | 晴 Sunny | 温和 Mild | 高 High | 小 Weak | 否 No |
| D9 | 晴 Sunny | 凉 Cool | 正常 Normal | 小 Weak | 是 Yes |
| D10 | 雨 Rain | 温和 Mild | 正常 Normal | 小 Weak | 是 Yes |
| D11 | 晴 Sunny | 温和 Mild | 正常 Normal | 大 Strong | 是 Yes |
| D12 | 阴 Overcast | 热 Hot | 高 High | 大 Strong | 是 Yes |
| D13 | 阴 Overcast | 热 Hot | 正常 Normal | 小 Weak | 是 Yes |
| D14 | 雨 Rain | 温和 Mild | 高 High | 大 Strong | 否 No |&lt;/p&gt;
&lt;p&gt;统计一下结果：&lt;strong&gt;14 天里，9 天去打球（Yes），5 天不去（No）&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;我们要做的：选一个特征当&quot;根问题&quot;。直觉上，我们希望&lt;strong&gt;问完之后，每一堆里尽可能只含一种答案&lt;/strong&gt;（全是&quot;去&quot;或全是&quot;不去&quot;），这样判断就&quot;干净&quot;。&lt;/p&gt;
&lt;p&gt;那怎么量化&quot;干净不干净&quot;？请看下面的&lt;strong&gt;信息熵&lt;/strong&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;三、分类决策树：怎么选特征（手把手计算）&lt;/h2&gt;
&lt;h3&gt;3.1 信息熵：衡量&quot;乱不乱&quot;&lt;/h3&gt;
&lt;p&gt;信息熵（Entropy）用来量化一个集合有多&quot;乱&quot;。公式：&lt;/p&gt;
&lt;p&gt;$$
H(D) = -\sum_{k=1}^{K} p_k \log_2 p_k
$$&lt;/p&gt;
&lt;p&gt;其中 $p_k$ 是第 $k$ 类样本占的比例，$K$ 是类别数。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;熵 $= 0$：&lt;strong&gt;完全纯&lt;/strong&gt;（全是一种）&lt;/li&gt;
&lt;li&gt;熵越大：&lt;strong&gt;越乱&lt;/strong&gt;（各类掺在一起）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;用我们的数据算一遍&lt;/strong&gt;：14 天里 9 个 Yes、5 个 No，所以 $p_{Yes} = \frac{9}{14}$，$p_{No} = \frac{5}{14}$：&lt;/p&gt;
&lt;p&gt;$$
H(D) = -\frac{9}{14}\log_2\frac{9}{14} - \frac{5}{14}\log_2\frac{5}{14}
= -0.643 \times (-0.637) - 0.357 \times (-1.485)
\approx 0.940
$$&lt;/p&gt;
&lt;p&gt;这个 &lt;strong&gt;0.940&lt;/strong&gt; 就是整个数据集的&quot;混乱度&quot;，我们记作起点。&lt;/p&gt;
&lt;h3&gt;3.2 信息增益：每个特征能&quot;降低多少混乱&quot;&lt;/h3&gt;
&lt;p&gt;如果按某个特征划分后，每一堆内部都变&quot;纯&quot;了（熵下降），说明这个特征问得好。下降得越多越值得先问。定义&lt;strong&gt;信息增益&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;$$
Gain(D, a) = H(D) - \sum_{v=1}^{V} \frac{|D^v|}{|D|} H(D^v)
$$&lt;/p&gt;
&lt;p&gt;就是：&lt;strong&gt;原来的熵 − 划分后各堆熵的加权平均&lt;/strong&gt;。我们要挑增益最大的特征。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;下面完整算一遍&quot;按天气 Outlook 划分&quot;的增益：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第一步&lt;/strong&gt;：按天气分成三堆：&lt;/p&gt;
&lt;p&gt;| 天气 | 包含的天 | 去/不去 |
|------|---------|--------|
| 晴 Sunny | D1,D2,D8,D9,D11（5天） | Yes 2，No 3 |
| 阴 Overcast | D3,D7,D12,D13（4天） | Yes 4，No 0 |
| 雨 Rain | D4,D5,D6,D10,D14（5天） | Yes 3，No 2 |&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第二步&lt;/strong&gt;：算每堆的熵：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;晴：$H = -\frac{2}{5}\log_2\frac{2}{5} - \frac{3}{5}\log_2\frac{3}{5} \approx 0.971$&lt;/li&gt;
&lt;li&gt;阴：$H = -\frac{4}{4}\log_2\frac{4}{4} - 0 = 0$（全是 Yes，完全纯！）&lt;/li&gt;
&lt;li&gt;雨：$H = -\frac{3}{5}\log_2\frac{3}{5} - \frac{2}{5}\log_2\frac{2}{5} \approx 0.971$&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;第三步&lt;/strong&gt;：加权平均（每堆按比例加权）：&lt;/p&gt;
&lt;p&gt;$$
\frac{5}{14}\times 0.971 + \frac{4}{14}\times 0 + \frac{5}{14}\times 0.971 \approx 0.694
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第四步&lt;/strong&gt;：信息增益 = 原来的熵 − 加权平均熵：&lt;/p&gt;
&lt;p&gt;$$
Gain = 0.940 - 0.694 = 0.246
$$&lt;/p&gt;
&lt;p&gt;同理可以算出按&quot;温度&quot;&quot;湿度&quot;&quot;风&quot;划分的增益，都&lt;strong&gt;小于 0.246&lt;/strong&gt;。所以第一个问题应该问&lt;strong&gt;天气&lt;/strong&gt;！&lt;/p&gt;
&lt;h3&gt;3.3 基尼指数（CART 分类树用的标准）&lt;/h3&gt;
&lt;p&gt;除了信息熵，CART 分类树用&lt;strong&gt;基尼指数&lt;/strong&gt;，同样衡量&quot;纯不纯&quot;，而且&lt;strong&gt;不用算 log&lt;/strong&gt;，更省事：&lt;/p&gt;
&lt;p&gt;$$
Gini(D) = 1 - \sum_{k=1}^{K} p_k^2
$$&lt;/p&gt;
&lt;p&gt;还是越小越纯。例：14 天数据集 $Gini(D) = 1 - (\frac{9}{14})^2 - (\frac{5}{14})^2 \approx 0.459$。&lt;/p&gt;
&lt;p&gt;划分时选&quot;划分后基尼加权和最小&quot;的特征。&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;四、递归建树：一棵树是怎么长出来的&lt;/h2&gt;
&lt;p&gt;有了&quot;选特征&quot;的规则，建树就是&lt;strong&gt;递归&lt;/strong&gt;的过程：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;在当前数据里，算每个特征的信息增益&lt;/li&gt;
&lt;li&gt;选增益&lt;strong&gt;最大&lt;/strong&gt;的特征作为判断点（节点）&lt;/li&gt;
&lt;li&gt;按这个特征的每个取值，把数据&lt;strong&gt;分成几堆&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;对每一堆，&lt;strong&gt;重复第 1~3 步&lt;/strong&gt;（递归！）&lt;/li&gt;
&lt;li&gt;直到：某堆&lt;strong&gt;只剩一种类别&lt;/strong&gt;（纯了），或者&lt;strong&gt;没有特征可用&lt;/strong&gt;，或者达到停止条件 → 把当前堆变成&lt;strong&gt;叶子节点&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;用打球数据演示根节点之后的部分：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;                     天气 = ?          ← 信息增益最大，当根
               /        |        \
            晴        阴         雨
         （5天）     （4天）      （5天）
            熵0.971   熵0（纯）    熵0.971
           继续分↓    叶子=去！     继续分↓
        风大吗？                   湿度高吗？
        /      \                 /        \
     大→不去   小→去           高→不去    正常→去
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样整棵树就长出来了，以后来新的一天，顺着问题走就能预测&quot;去不去&quot;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;五、回归决策树：预测连续数值&lt;/h2&gt;
&lt;p&gt;上面是&lt;strong&gt;分类&lt;/strong&gt;（输出&quot;去/不去&quot;这种类别）。如果输出是&lt;strong&gt;连续数值&lt;/strong&gt;（比如房价、成绩、温度），就用&lt;strong&gt;回归树&lt;/strong&gt;。&lt;/p&gt;
&lt;h3&gt;5.1 回归树叶子存的是&quot;一个数&quot;&lt;/h3&gt;
&lt;p&gt;回归树的叶子节点不再是&quot;类别&quot;，而是&lt;strong&gt;一个数值&lt;/strong&gt;——通常是这个叶子内所有样本的&lt;strong&gt;平均值&lt;/strong&gt;。&lt;/p&gt;
&lt;h3&gt;5.2 划分标准：均方误差 MSE&lt;/h3&gt;
&lt;p&gt;分类树看&quot;纯不纯&quot;，回归树看&quot;预测准不准&quot;。定义&lt;strong&gt;均方误差&lt;/strong&gt;（MSE），衡量&quot;用均值 $\bar{y}$ 预测所有样本 $y_i$ 的误差&quot;：&lt;/p&gt;
&lt;p&gt;$$
MSE = \frac{1}{n} \sum_{i=1}^{n} (y_i - \bar{y})^2
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;越小说明这一堆里的数越接近，越好。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;划分时，试遍所有特征的每个阈值 $t$，找让&quot;划分后两堆 MSE 之和最小&quot;的分法：&lt;/p&gt;
&lt;p&gt;$$
\min_{a, t} \left[ \sum_{x_i \in R_1} (y_i - c_1)^2 + \sum_{x_i \in R_2} (y_i - c_2)^2 \right]
$$&lt;/p&gt;
&lt;p&gt;其中 $c_1, c_2$ 是 $R_1, R_2$ 两堆的均值。&lt;/p&gt;
&lt;h3&gt;5.3 直观例子：预测房价&lt;/h3&gt;
&lt;p&gt;假设有 6 套房，特征&quot;面积&quot;，目标&quot;价格&quot;：&lt;/p&gt;
&lt;p&gt;| 面积(m²) | 40 | 50 | 60 | 80 | 90 | 100 |
|----------|----|----|----|----|----|-----|
| 价格(万) | 80 | 90 | 110 | 200 | 220 | 250 |&lt;/p&gt;
&lt;p&gt;想找一个分界点 $t$，让两边的价格都&quot;尽量聚集&quot;（MSE 小）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;试 $t = 60$：左边 {40,50,60}→价格{80,90,110}，均值 93；右边 {80,90,100}→价格{200,220,250}，均值 223。两堆都很&quot;整齐&quot;，MSE 很小 ✅&lt;/li&gt;
&lt;li&gt;试 $t = 90$：左边 5 个数价格混杂（80~250），均值很大，MSE 很大 ❌&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;显然选 $t = 60$ 左右。于是：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;        面积 &amp;#x3C; 60 ?
        /        \
   左边均值93万   右边均值223万
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;以后来一套 70m² 的房子，走右边，预测 &lt;strong&gt;223 万&lt;/strong&gt;。这就是回归树：&lt;strong&gt;把空间切块，每块填一个均值&lt;/strong&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;六、过拟合与剪枝&lt;/h2&gt;
&lt;h3&gt;6.1 什么是过拟合？&lt;/h3&gt;
&lt;p&gt;决策树可以无限往下分，直到每个叶子只有一个样本。这样&lt;strong&gt;训练集&lt;/strong&gt; 100% 正确，但遇到&lt;strong&gt;新数据&lt;/strong&gt;就乱猜——这就是&lt;strong&gt;过拟合&lt;/strong&gt;（&quot;把答案背下来&quot;）。&lt;/p&gt;
&lt;p&gt;打个比方：一个学生把练习册答案全背下来了，换一套卷子就不会做。&lt;/p&gt;
&lt;h3&gt;6.2 两种剪枝&lt;/h3&gt;
&lt;p&gt;| 方式 | 做法 | 优点 |
|------|------|------|
| &lt;strong&gt;预剪枝&lt;/strong&gt; | 建树时就限制：最大深度 &lt;code&gt;max_depth&lt;/code&gt;、叶子最少样本 &lt;code&gt;min_samples_leaf&lt;/code&gt; 等 | 简单高效，最常用 |
| &lt;strong&gt;后剪枝&lt;/strong&gt; | 树建好后，从下往上，把&quot;对验证集没帮助&quot;的子树砍掉 | 更优，但耗时 |&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;实操建议：直接用 sklearn 的 &lt;code&gt;max_depth&lt;/code&gt;（比如 3~5）和 &lt;code&gt;min_samples_leaf&lt;/code&gt;（比如 2~5）就能得到不错的效果。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2&gt;七、完整可运行的 Python 代码&lt;/h2&gt;
&lt;p&gt;下面用 sklearn 直接跑上面的&quot;打球数据集&quot;（分类），再演示回归：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import numpy as np
from sklearn.tree import DecisionTreeClassifier, DecisionTreeRegressor, plot_tree
import matplotlib.pyplot as plt

# ---------- 分类：打球数据集 ----------
# 特征：天气、温度、湿度、风（先转成数字）
X = np.array([
    [0, 0, 0, 0], [0, 0, 0, 1], [1, 0, 0, 0], [2, 1, 0, 0],
    [2, 2, 1, 0], [2, 2, 1, 1], [1, 2, 1, 1], [0, 1, 0, 0],
    [0, 2, 1, 0], [2, 1, 1, 0], [0, 1, 1, 1], [1, 0, 0, 1],
    [1, 0, 1, 0], [2, 1, 0, 1],
])
# 0=晴/热/高/小, 1=阴/温和/正常/大, 2=雨/凉
y = np.array([0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0])  # 0=不去 1=去

clf = DecisionTreeClassifier(max_depth=3, random_state=0)
clf.fit(X, y)
print(&quot;训练集准确率:&quot;, clf.score(X, y))

# 可视化这棵树
plt.figure(figsize=(10, 6))
plot_tree(clf, filled=True, feature_names=[&quot;天气&quot;, &quot;温度&quot;, &quot;湿度&quot;, &quot;风&quot;],
          class_names=[&quot;不去&quot;, &quot;去&quot;])
plt.show()

# ---------- 回归：房价 ----------
Xh = np.array([[40], [50], [60], [80], [90], [100]])
yh = np.array([80, 90, 110, 200, 220, 250])

reg = DecisionTreeRegressor(max_depth=2, random_state=0)
reg.fit(Xh, yh)
print(&quot;预测 70m² 房价:&quot;, reg.predict([[70]]))  # 会落在均值 223 万那个叶子
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>随机森林</title><link>https://ehnotgod.site/blog/%E9%9A%8F%E6%9C%BA%E6%A3%AE%E6%9E%97</link><guid isPermaLink="true">https://ehnotgod.site/blog/%E9%9A%8F%E6%9C%BA%E6%A3%AE%E6%9E%97</guid><description>随机森林：多棵决策树集成，降低过拟合。</description><pubDate>Sun, 09 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;随机森林（Random Forest）&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;上一篇学了决策树：它能画成一棵&quot;不断问问题&quot;的树，可解释性很强。但它有个大毛病——&lt;strong&gt;容易过拟合、还不稳定&lt;/strong&gt;（训练数据稍微变一点，整棵树就大变样）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;随机森林&lt;/strong&gt;就是专门治这个毛病的：种&lt;strong&gt;很多棵决策树&lt;/strong&gt;，让它们&quot;投票&quot;做决定。这就是最经典的&lt;strong&gt;集成学习&lt;/strong&gt;（Ensemble Learning）方法之一。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2&gt;一、为什么要&quot;一堆树&quot;而不是&quot;一棵树&quot;？&lt;/h2&gt;
&lt;p&gt;先看一个朴素的道理：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;三个臭皮匠，顶个诸葛亮。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;一个专家（单棵决策树）可能判断失误&lt;/li&gt;
&lt;li&gt;但如果问&lt;strong&gt;很多个独立的专家&lt;/strong&gt;，然后&lt;strong&gt;少数服从多数&lt;/strong&gt;，整体就靠谱得多&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这就是集成学习的核心思想：&lt;strong&gt;把多个弱模型组合成一个强模型&lt;/strong&gt;。&lt;/p&gt;
&lt;h3&gt;决策树的缺点（随机森林要解决的）&lt;/h3&gt;
&lt;p&gt;回忆上一篇，单棵决策树有两个毛病：&lt;/p&gt;
&lt;p&gt;| 缺点 | 表现 |
|------|------|
| &lt;strong&gt;容易过拟合&lt;/strong&gt; | 深度设得大时，把训练集&quot;背&quot;下来，新数据表现差 |
| &lt;strong&gt;不稳定&lt;/strong&gt; | 训练数据稍微变一点，树的结构可能完全不同，预测结果波动大 |&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;随机森林的解决思路&lt;/strong&gt;：种很多棵树，每棵树都&quot;不一样&quot;，让它们的错误互相抵消。&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;二、随机森林的两个&quot;随机&quot;&lt;/h2&gt;
&lt;p&gt;名字里的&quot;随机&quot;就藏在这里。每棵树的训练都不是用全部数据，而是做了&lt;strong&gt;两次随机&lt;/strong&gt;：&lt;/p&gt;
&lt;h3&gt;随机 1：样本抽样（Bagging）&lt;/h3&gt;
&lt;p&gt;对每棵树，&lt;strong&gt;从原始数据里&quot;有放回&quot;地随机抽&lt;/strong&gt;一部分样本（专业词叫 &lt;strong&gt;Bootstrap 自助采样&lt;/strong&gt;）。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;有放回&quot;什么意思？抽到一个样本记录后，把它&lt;strong&gt;放回去再抽&lt;/strong&gt;，所以同一个样本可能被抽到多次，也可能一次都没被抽到。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;比如打球数据集有 14 条记录，每棵树：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;原始 14 条： D1 D2 D3 D4 D5 D6 D7 D8 D9 D10 D11 D12 D13 D14
                ↓ 有放回随机抽 14 次
第 1 棵树：   D1 D1 D4 D2 D8 D3 D1 D9 D5 D6 D7 D9 D14 D2   （有的重复，有的缺失）
第 2 棵树：   D5 D8 D2 D9 D1 D3 D6 D1 D7 D4 D9 D10 D12 D1
第 3 棵树：   ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;每棵树的训练数据&lt;strong&gt;都不一样&lt;/strong&gt;，这就是&quot;袋装&quot;（&lt;strong&gt;Bagging&lt;/strong&gt; = Bootstrap + Aggregating）。&lt;/p&gt;
&lt;h3&gt;随机 2：特征随机选择&lt;/h3&gt;
&lt;p&gt;每棵树的&lt;strong&gt;每个节点&lt;/strong&gt;在选划分特征时，不是看全部特征，而是从所有特征里&lt;strong&gt;随机挑一部分&lt;/strong&gt;（比如 $\sqrt{m}$ 个），再从中选最优的。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;目的：让树与树之间&lt;strong&gt;尽量不相关&lt;/strong&gt;。如果每棵树都用同样的特征，那它们会&quot;长得差不多&quot;，投票就失去意义了。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2&gt;三、随机森林的构建流程（手把手）&lt;/h2&gt;
&lt;p&gt;造一棵&quot;森林&quot;就四步：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;确定要种多少棵树&lt;/strong&gt; $T$（比如 100 棵）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;对每棵树 $t$&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;从原始数据有放回地抽样本（Bootstrap）→ 得到该树的训练集&lt;/li&gt;
&lt;li&gt;用这些样本&lt;strong&gt;建一棵决策树&lt;/strong&gt;，但每个节点划分时&lt;strong&gt;随机挑一部分特征&lt;/strong&gt;再选最优&lt;/li&gt;
&lt;li&gt;不剪枝，让它长到足够深（随机森林靠&quot;随机&quot;防过拟合，不靠剪枝）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;重复第 2 步 $T$ 次，得到 $T$ 棵互不相同的树&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;预测时&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;分类&lt;/strong&gt;：$T$ 棵树各自投票，&lt;strong&gt;票数最多的类别&lt;/strong&gt;胜出&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;回归&lt;/strong&gt;：$T$ 棵树各自给出数值，取&lt;strong&gt;平均值&lt;/strong&gt;作为结果&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;用打球数据示意（假设 3 棵树）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;        新样本（今天：晴、风大）
              ↓
  树1: 不去   树2: 去   树3: 不去
              ↓ 投票（少数服从多数）
        结果：不去
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;四、为什么&quot;随机&quot;能让它更好？&lt;/h2&gt;
&lt;h3&gt;4.1 降低方差（更稳定）&lt;/h3&gt;
&lt;p&gt;单棵决策树方差大（不稳定）。如果造 $n$ 棵独立同分布的树，每棵的方差是 $\sigma^2$，那么&lt;strong&gt;平均之后&lt;/strong&gt;方差会缩小：&lt;/p&gt;
&lt;p&gt;$$
Var\left(\frac{1}{n}\sum_{i=1}^{n}X_i\right) = \frac{\sigma^2}{n}
$$&lt;/p&gt;
&lt;p&gt;树越多，波动越小，结果越稳定。&lt;/p&gt;
&lt;h3&gt;4.2 特征随机让树&quot;不相关&quot;&lt;/h3&gt;
&lt;p&gt;如果所有树都用相同的特征，它们相关性高，上面的方差公式就不成立了（因为 $X_i$ 之间相关）。&lt;/p&gt;
&lt;p&gt;随机挑特征 → 树之间尽量独立 → 方差真正降下来 → 更抗过拟合。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;一句话：&lt;strong&gt;&quot;随机&quot;制造了多样性，多样性带来了稳定性。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2&gt;五、两个重要补充概念&lt;/h2&gt;
&lt;h3&gt;5.1 OOB（袋外样本）&lt;/h3&gt;
&lt;p&gt;因为有放回抽样，每棵树的训练集&lt;strong&gt;没用到&lt;/strong&gt;的数据（约 37% 的样本）叫做&lt;strong&gt;袋外样本（Out-Of-Bag）&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;这些样本没参与训练，正好可以当&quot;免费验证集&quot;：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;用 OOB 样本测试这棵树 → 得到&lt;strong&gt;袋外误差&lt;/strong&gt;，无需另外划分验证集就能评估模型好坏。这是随机森林的隐藏福利。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;5.2 特征重要性&lt;/h3&gt;
&lt;p&gt;随机森林还能告诉我们&lt;strong&gt;哪个特征更重要&lt;/strong&gt;（通常用&quot;某个特征被随机打乱后，预测误差上升多少&quot;来衡量）。这对特征筛选很有用。&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;六、优缺点&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;优点：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;抗过拟合&lt;/strong&gt;：比单棵决策树稳得多&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;对数据扰动不敏感&lt;/strong&gt;：很鲁棒&lt;/li&gt;
&lt;li&gt;能处理高维数据、无需特征缩放&lt;/li&gt;
&lt;li&gt;自带 OOB 误差评估、能算特征重要性&lt;/li&gt;
&lt;li&gt;分类、回归通吃，几乎不用调参就能有不错效果&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;缺点：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;可解释性下降&lt;/strong&gt;：几百棵树没法像单棵树那样画出来讲给人听&lt;/li&gt;
&lt;li&gt;训练和预测比单棵树&lt;strong&gt;慢&lt;/strong&gt;（要建很多树）&lt;/li&gt;
&lt;li&gt;对噪声大的数据也可能过拟合&lt;/li&gt;
&lt;li&gt;模型偏大，占用内存&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;七、Python 代码：和决策树对比&lt;/h2&gt;
&lt;p&gt;复用上一篇的&quot;打球数据集&quot;和&quot;房价数据集&quot;，直观对比&lt;strong&gt;一棵树 vs 森林&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import numpy as np
from sklearn.ensemble import RandomForestClassifier, RandomForestRegressor
from sklearn.tree import DecisionTreeClassifier, DecisionTreeRegressor

# ---------- 分类：打球数据集（同上一篇） ----------
X = np.array([
    [0,0,0,0],[0,0,0,1],[1,0,0,0],[2,1,0,0],[2,2,1,0],[2,2,1,1],
    [1,2,1,1],[0,1,0,0],[0,2,1,0],[2,1,1,0],[0,1,1,1],[1,0,0,1],
    [1,0,1,0],[2,1,0,1],
])
y = np.array([0,0,1,1,1,0,1,0,1,1,1,1,1,0])

# 单棵树 vs 随机森林
tree = DecisionTreeClassifier(random_state=0)
forest = RandomForestClassifier(n_estimators=100, random_state=0)
tree.fit(X, y)
forest.fit(X, y)
print(&quot;单棵树训练集准确率:&quot;, tree.score(X, y))
print(&quot;随机森林训练集准确率:&quot;, forest.score(X, y))

# 特征重要性（随机森林的隐藏福利）
print(&quot;特征重要性:&quot;, forest.feature_importances_)

# ---------- 回归：房价 ----------
Xh = np.array([[40],[50],[60],[80],[90],[100]])
yh = np.array([80,90,110,200,220,250])

reg_tree = DecisionTreeRegressor(random_state=0)
reg_forest = RandomForestRegressor(n_estimators=100, random_state=0)
reg_tree.fit(Xh, yh)
reg_forest.fit(Xh, yh)
print(&quot;单棵树预测 70m²:&quot;, reg_tree.predict([[70]]))
print(&quot;随机森林预测 70m²:&quot;, reg_forest.predict([[70]]))
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;注意：数据量小的时候，随机森林的优势不明显（数据多、特征多时优势才大）。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2&gt;八、总结&lt;/h2&gt;
&lt;p&gt;一句话记住随机森林：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;随机森林 = 种很多棵&quot;用随机样本、随机特征&quot;长出来的决策树，分类靠投票、回归靠平均，用&quot;人多力量大&quot;换来稳定和抗过拟合。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;它是机器学习里&lt;strong&gt;最常用、最好上手的集成方法&lt;/strong&gt;之一。理解了它，再学 GBDT、XGBoost 就有基础了——那些是另一条路线（&quot;串行&quot;地一棵棵补错），而不是&quot;并行&quot;地投票。&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>A1 高精度加法</title><link>https://ehnotgod.site/blog/a1-%E9%AB%98%E7%B2%BE%E5%BA%A6%E5%8A%A0%E6%B3%95</link><guid isPermaLink="true">https://ehnotgod.site/blog/a1-%E9%AB%98%E7%B2%BE%E5%BA%A6%E5%8A%A0%E6%B3%95</guid><description>高精度加法模板：模拟竖式逐位相加并进位。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P1601&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;高精度加法，相当于 a+b problem，&lt;strong&gt;不用考虑负数&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;分两行输入。$a,b\le 10^{500}$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输出只有一行，代表 $a+b$ 的值。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入输出样例：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1
1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输入 #2&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1001
9099
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #2&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;10100
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;算法解析&lt;/h3&gt;
&lt;p&gt;高精度计算实际上用数组存储和模拟运算。&lt;/p&gt;
&lt;p&gt;高精度加法的步骤：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;高精度数字利用字符串读入；&lt;/li&gt;
&lt;li&gt;把字符串&lt;strong&gt;翻转存入&lt;/strong&gt;两个整型数组 A,B；&lt;/li&gt;
&lt;li&gt;从低位到高位，累加，进位，存余；&lt;/li&gt;
&lt;li&gt;把数组 C 从高位到低位依次输出。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;时间复杂度O(n)&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;
a = int(input())
b = int(input())
print(a + b)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include &amp;#x3C;iostream&gt;
using namespace std;

const int N=505;
int a[N],b[N],c[N];
int la,lb,lc;

void add(int a[],int b[],int c[]){ //a+b=c
    for(int i=1; i&amp;#x3C;=lc; i++){
        c[i]+=a[i]+b[i]; //求和
        c[i+1]+=c[i]/10; //进位
        c[i]%=10;        //存余
    }
    if(c[lc+1]) lc++;  //最高位
}
int main(){
    string sa,sb; cin&gt;&gt;sa&gt;&gt;sb;
    la=sa.size(),lb=sb.size(),lc=max(la,lb);
    for(int i=1; i&amp;#x3C;=la; i++) a[i]=sa[la-i]-&apos;0&apos;;
    for(int i=1; i&amp;#x3C;=lb; i++) b[i]=sb[lb-i]-&apos;0&apos;;
    add(a,b,c);
    for(int i=lc; i; i--) printf(&quot;%d&quot;,c[i]);
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>A2 高精度减法</title><link>https://ehnotgod.site/blog/a2-%E9%AB%98%E7%B2%BE%E5%BA%A6%E5%87%8F%E6%B3%95</link><guid isPermaLink="true">https://ehnotgod.site/blog/a2-%E9%AB%98%E7%B2%BE%E5%BA%A6%E5%87%8F%E6%B3%95</guid><description>高精度减法模板：模拟竖式逐位相减并借位。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P2142&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;高精度减法。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;两个整数 &lt;em&gt;a&lt;/em&gt;,&lt;em&gt;b&lt;/em&gt;（第二个可能比第一个大）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;结果（是负数要输出负号）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入输出样例&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2
1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;算法解析&lt;/h3&gt;
&lt;p&gt;高精度计算实际上用数组存储和模拟运算。
高精度减法的步骤：&lt;/p&gt;
&lt;p&gt;高精度数字利用字符串读入；&lt;/p&gt;
&lt;p&gt;把字符串翻转存入两个整型数组 A,B；&lt;/p&gt;
&lt;p&gt;若 A&amp;#x3C;B，则交换 A,B，输出负号；&lt;/p&gt;
&lt;p&gt;从低位到高位，逐位求差，借位，存差；&lt;/p&gt;
&lt;p&gt;把数组 C 从高位到低位依次输出。&lt;/p&gt;
&lt;p&gt;时间复杂度O(n)&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;a = int(input())
b = int(input())
print(a - b)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include &amp;#x3C;iostream&gt;
using namespace std;

const int N=20000;
int a[N],b[N],c[N];
int la,lb,lc;

bool cmp(int a[],int b[]){
    if(la!=lb) return la&amp;#x3C;lb;
    for(int i=la; i; i--)
        if(a[i]!=b[i]) return a[i]&amp;#x3C;b[i];
    return false;  //相等时,避免-0
}
void sub(int a[],int b[],int c[]){ //a-b=c
    for(int i=1; i&amp;#x3C;=lc; i++){
        if(a[i]&amp;#x3C;b[i]) a[i+1]--,a[i]+=10;
        c[i]=a[i]-b[i];
    }
    while(c[lc]==0&amp;#x26;&amp;#x26;lc&gt;1) lc--; //去0
}
int main(){
    string sa,sb; cin&gt;&gt;sa&gt;&gt;sb;
    la=sa.size(),lb=sb.size(),lc=max(la,lb);
    for(int i=1;i&amp;#x3C;=la;i++) a[i]=sa[la-i]-&apos;0&apos;;
    for(int i=1;i&amp;#x3C;=lb;i++) b[i]=sb[lb-i]-&apos;0&apos;;
    if(cmp(a,b)) swap(a,b),cout&amp;#x3C;&amp;#x3C;&apos;-&apos;;
    sub(a,b,c);
    for(int i=lc;i;i--) printf(&quot;%d&quot;,c[i]);
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>A3 高精度乘法</title><link>https://ehnotgod.site/blog/a3-%E9%AB%98%E7%B2%BE%E5%BA%A6%E4%B9%98%E6%B3%95</link><guid isPermaLink="true">https://ehnotgod.site/blog/a3-%E9%AB%98%E7%B2%BE%E5%BA%A6%E4%B9%98%E6%B3%95</guid><description>高精度乘法模板：模拟竖式逐位相乘并累加。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P1303&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;高精度乘法模板题。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;两个非负整数 &lt;em&gt;a&lt;/em&gt;,&lt;em&gt;b&lt;/em&gt;，求它们的乘积。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输出一个非负整数表示乘积。每个非负整数不超过$$10^{2000}$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入输出样例&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2
1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;算法解析&lt;/h3&gt;
&lt;p&gt;高精度计算实际上用数组存储和模拟运算。&lt;/p&gt;
&lt;p&gt;高精度乘法的步骤：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;高精度数字利用字符串读入；&lt;/li&gt;
&lt;li&gt;把字符串翻转存入两个整型数组 A,B；&lt;/li&gt;
&lt;li&gt;从低位到高位，累加乘积，进位，存余；&lt;/li&gt;
&lt;li&gt;把数组 C 从高位到低位依次输出。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;时间复杂度O($$n^{2}$$)&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;a = int(input())
b = int(input())
print(a * b)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include &amp;#x3C;iostream&gt;
#include &amp;#x3C;cstring&gt;
#include &amp;#x3C;algorithm&gt;
using namespace std;

const int N=4001;
int a[N],b[N],c[N];
int la,lb,lc;

void mul(int a[],int b[],int c[]){ //a*b=c
    for(int i=1;i&amp;#x3C;=la;i++)
        for(int j=1;j&amp;#x3C;=lb;j++)
            c[i+j-1]+=a[i]*b[j]; //存乘积

    for(int i=1;i&amp;#x3C;lc;i++){
        c[i+1]+=c[i]/10;  //存进位
        c[i]%=10;         //存余数
    }
    while(c[lc]==0&amp;#x26;&amp;#x26;lc&gt;1) lc--; //去0
}
int main(){
    char A[N],B[N];
    cin&gt;&gt;A&gt;&gt;B;
    la=strlen(A); lb=strlen(B); lc=la+lb;
    for(int i=1;i&amp;#x3C;=la;i++)a[i]=A[la-i]-&apos;0&apos;;
    for(int i=1;i&amp;#x3C;=lb;i++)b[i]=B[lb-i]-&apos;0&apos;;
    mul(a,b,c);
    for(int i=lc;i&gt;=1;i--) cout&amp;#x3C;&amp;#x3C;c[i];
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>A4 高精度除法</title><link>https://ehnotgod.site/blog/a4-%E9%AB%98%E7%B2%BE%E5%BA%A6%E9%99%A4%E6%B3%95</link><guid isPermaLink="true">https://ehnotgod.site/blog/a4-%E9%AB%98%E7%B2%BE%E5%BA%A6%E9%99%A4%E6%B3%95</guid><description>高精度除法模板：高精除以低精，模拟除法过程。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P1480&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输入两个整数 &lt;em&gt;a&lt;/em&gt;,&lt;em&gt;b&lt;/em&gt;，输出它们的商。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;两行，第一行是被除数a，第二行是除数b。&lt;/p&gt;
&lt;p&gt;0 ≤ &lt;em&gt;a&lt;/em&gt; ≤ $$10^{5000}$$，1 ≤&lt;em&gt;b&lt;/em&gt;≤ $$10^{9}$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;一行，商的整数部分。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入输出样例&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2
1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;算法解析&lt;/h3&gt;
&lt;p&gt;高精度/低精度步骤：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;高精度数以字符串读入&lt;/li&gt;
&lt;li&gt;翻转存入整型数组A&lt;/li&gt;
&lt;li&gt;高位到低位逐位计算，存商求余&lt;/li&gt;
&lt;li&gt;高位到低位输出数组C
此算法的除数要求不能是高精度的
时间复杂度O($$n$$)&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;a = int(input())
b = int(input())
print(a // b)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include &amp;#x3C;iostream&gt;
#include &amp;#x3C;cstring&gt;
#include &amp;#x3C;algorithm&gt;
using namespace std;

const int N=5005;
int a[N],b,c[N];
int len;

void div(int a[],int b,int c[]){ //a/b=c
    long long t=0;
    for(int i=len;i&gt;=1;i--){
        t=t*10+a[i];  //被除数
        c[i]=t/b;     //存商
        t%=b;         //余数
    }
    while(c[len]==0&amp;#x26;&amp;#x26;len&gt;1) len--; //去0
}
int main(){
    char A[N]; cin&gt;&gt;A&gt;&gt;b; len=strlen(A);
    for(int i=1;i&amp;#x3C;=len;i++) a[i]=A[len-i]-&apos;0&apos;;
    div(a,b,c);
    for(int i=len;i;i--) cout&amp;#x3C;&amp;#x3C;c[i];
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>A5 二分查找</title><link>https://ehnotgod.site/blog/a5-%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE</link><guid isPermaLink="true">https://ehnotgod.site/blog/a5-%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE</guid><description>二分查找模板：在单调不减序列中查找数字首次出现的位置。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P2249&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输入 $$n$$ 个不超过 $$10^9$$ 的单调不减的（就是后面的数字不小于前面的数字）非负整数 $$a_1,a_2,\dots,a_{n}$$，然后进行 $$m$$ 次询问。对于每次询问，给出一个整数 $$q$$，要求输出这个数字在序列中第一次出现的编号，如果没有找到的话输出 $$-1$$ 。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行 $$2$$ 个整数 $$n$$ 和 $$m$$，表示数字个数和询问次数。&lt;/p&gt;
&lt;p&gt;第二行 $$n$$ 个整数，表示这些待查询的数字。&lt;/p&gt;
&lt;p&gt;第三行 $$m$$ 个整数，表示询问这些数字的编号，从 $$1$$ 开始编号。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输出一行，$$m$$ 个整数，以空格隔开，表示答案。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入输出样例&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;11 3
1 3 3 3 5 7 9 11 13 15 15
1 3 6
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1 2 -1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;数据保证，$$1 \leq n \leq 10^6$$，$$0 \leq a_i,q \leq 10^9$$，$$1 \leq m \leq 10^5$$&lt;/p&gt;
&lt;h3&gt;算法解析&lt;/h3&gt;
&lt;p&gt;二分的原理为，利用单调性快速地缩小可行域&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;指针的跳跃次数 $$\log n$$&lt;/li&gt;
&lt;li&gt;$$l+1=r$$ 时结束&lt;/li&gt;
&lt;li&gt;可行区的指针最后一定指向答案&lt;/li&gt;
&lt;li&gt;开区间可以正确处理边界&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;时间复杂度：$$O(n\log max(a_i))$$&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import bisect
n, m = map(int, input().split())
lis = list(map(int, input().split()))
q_lis = list(map(int, input().split()))
for i in range(m):
    q = q_lis[i]
    id = bisect.bisect_left(lis, q)
    if id &gt;= n or lis[id] != q:
        print(-1, end=&quot; &quot;)
    else:
        print(id + 1, end=&quot; &quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;// 我喜欢的板子
#include&amp;#x3C;cstdio&gt;
using namespace std;
int n,m,q,a[1000005];

int find(int q){
    int l=0,r=n+1;//开区间
    while(l+1&amp;#x3C;r){ //l+1=r时结束
        int mid=l+r&gt;&gt;1;
        if(a[mid]&gt;=q) r=mid; //最小化
        else l=mid;
    }
    return a[r]==q ? r : -1;
}
int main(){
    scanf(&quot;%d %d&quot;,&amp;#x26;n,&amp;#x26;m);
    for(int i=1;i&amp;#x3C;=n;i++)scanf(&quot;%d&quot;,&amp;#x26;a[i]);
    for(int i=1;i&amp;#x3C;=m;i++)
        scanf(&quot;%d&quot;,&amp;#x26;q), printf(&quot;%d &quot;,find(q));
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>A6 二分答案</title><link>https://ehnotgod.site/blog/a6-%E4%BA%8C%E5%88%86%E7%AD%94%E6%A1%88</link><guid isPermaLink="true">https://ehnotgod.site/blog/a6-%E4%BA%8C%E5%88%86%E7%AD%94%E6%A1%88</guid><description>二分答案模板：把木头切成 k 段等长小段，二分求每段最大长度。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P2440&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;木材厂有 $$n$$ 根原木，现在想把这些木头切割成 $$k$$ 段长度&lt;strong&gt;均&lt;/strong&gt;为 $$l$$ 的小段木头（木头有可能有剩余）。请求出 $$l$$ 的最大值。&lt;/p&gt;
&lt;p&gt;确保长度都是正整数。&lt;/p&gt;
&lt;p&gt;例如有两根原木长度分别为 $$11$$ 和 $$21$$，要求切割成等长的 $$6$$ 段，很明显能切割出来的小段木头长度最长为 $$5$$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行是两个正整数 $$n,k$$，分别表示原木的数量，需要得到的小段的数量。&lt;/p&gt;
&lt;p&gt;接下来 $$n$$ 行，每行一个正整数 $$L_i$$，表示一根原木的长度。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;仅一行，即 $$l$$ 的最大值。&lt;/p&gt;
&lt;p&gt;如果连 $$\text{1cm}$$ 长的小段都切不出来，输出 &lt;code&gt;0&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入输出样例&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;3 7
232
124
456
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;114
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于 $$100%$$ 的数据，有 $$1\le n\le 10^5$$，$$1\le k\le 10^8$$，$$1\le L_i\le 10^8(i\in[1,n])$$。&lt;/p&gt;
&lt;h3&gt;算法解析&lt;/h3&gt;
&lt;p&gt;二分答案就是说用 二分 的方法枚举答案。&lt;/p&gt;
&lt;p&gt;我现在在一堆木头中找最长的，我可以用一下方法：&lt;/p&gt;
&lt;p&gt;最找到可行范围内最中间的数,用 check函数判断他是否可行&lt;/p&gt;
&lt;p&gt;原理是：长度为 len 的情况下如果可以分到的段数比 k 多，返回true,否则返回 false&lt;/p&gt;
&lt;p&gt;时间复杂度O($$nlog(max(L_i))$$)&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def check(x):
    y = 0
    for i in range(1, n + 1):
        y += a[i] // x
    return y &gt;= k

def find():
    l = 0
    r = int(1e8 + 1)
    while l + 1 &amp;#x3C; r:
        mid = (l + r) // 2
        if check(mid):
            l = mid
        else:
            r = mid
    return l
n, k = map(int, input().split())
a = [int(input()) for _ in range(n)]
a.insert(0, 0)
print(find())
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include &amp;#x3C;iostream&gt;
#include &amp;#x3C;cstring&gt;
#include &amp;#x3C;algorithm&gt;
using namespace std;

typedef long long LL;
const int N = 100005;
int n, k, a[N];

bool check(int x){
  LL y=0; //段数
  for(int i=1;i&amp;#x3C;=n;i++) y+=a[i]/x;
  return y&gt;=k; //x小,y大
}
int find(){
  int l=0, r=1e8+1;
  while(l+1&amp;#x3C;r){
    int mid=l+r&gt;&gt;1;
    if(check(mid)) l=mid; //最大化
    else r=mid;
  }
  return l;
}
int main(){
  scanf(&quot;%d%d&quot;,&amp;#x26;n,&amp;#x26;k);
  for(int i=1; i&amp;#x3C;=n; i++)scanf(&quot;%d&quot;,&amp;#x26;a[i]);
  printf(&quot;%d\n&quot;,find());
  return 0;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>A7 分数规划</title><link>https://ehnotgod.site/blog/a7-%E5%88%86%E6%95%B0%E8%A7%84%E5%88%92</link><guid isPermaLink="true">https://ehnotgod.site/blog/a7-%E5%88%86%E6%95%B0%E8%A7%84%E5%88%92</guid><description>分数规划：二分答案比值，最大化/最小化目标函数。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：http://poj.org/problem?id=2976&lt;/p&gt;
&lt;p&gt;用途：求分数的最值。&lt;/p&gt;
&lt;p&gt;问题：每种物品有两个权值 $$a_i$$ 和 $$b_i$$，选出若干个物品使得 $$\dfrac{\sum a_i}{\sum b_i}$$ 最大/最小。&lt;/p&gt;
&lt;p&gt;等价：给出 $$a_i$$ 和 $$b_i$$，求一组 $$w_i \in {0,1}$$，最大化或最小化 $$\dfrac{\sum w_i \cdot a_i}{\sum w_i \cdot b_i}$$。&lt;/p&gt;
&lt;p&gt;若干个物品：满足给定约束条件的物品。&lt;/p&gt;
&lt;h3&gt;算法解析&lt;/h3&gt;
&lt;p&gt;最大化：二分一个答案 $$x$$，
$$
\frac{\sum w_i \cdot a_i}{\sum w_i \cdot b_i} \ge x
$$
$$
\sum w_i \cdot a_i - x\sum w_i \cdot b_i \ge 0
$$
$$
\sum w_i \cdot (a_i - xb_i) \ge 0
$$
如果和式 $$\ge 0$$，说明 $$x$$ 是可行的，否则不可行。&lt;/p&gt;
&lt;p&gt;最小化：二分一个答案 $$x$$，
$$
\frac{\sum w_i \cdot a_i}{\sum w_i \cdot b_i} \le x
$$
$$
\sum w_i \cdot (a_i - xb_i) \le 0
$$
如果和式 $$\le 0$$，说明 $$x$$ 是可行的，否则不可行。&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;
def check(x):
    s = 0
    for i in range(1, n + 1):
        c[i] = a[i] - x * b[i]
    c[1:n+1] = sorted(c[1:n+1])
    for i in range(k + 1, n + 1):
        s += c[i]
    return s &gt;= 0
def find():
    l = 0
    r = 1
    while r - l &gt; 1e-4:
        mid = (l + r) / 2
        if check(mid):
            l = mid
        else:
            r = mid
    return l


while 1:
    n, k = map(int, input().split())
    if n == 0 and k == 0:
        break
    a = list(map(int, input().split()))
    b = list(map(int, input().split()))
    a.insert(0, 0)
    b.insert(0, 0)
    c = [0] * (n + 1)
    print(&quot;{0:.0f}&quot;.format(100 * find()))
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;//分数规划+二分+排序 复杂度：nlogn*log(1e4)
#include &amp;#x3C;iostream&gt;
#include &amp;#x3C;cstring&gt;
#include &amp;#x3C;algorithm&gt;
using namespace std;

const int N=1010;
int n,k;
double a[N], b[N], c[N];

bool check(double x){
  double s=0;
  for(int i=1;i&amp;#x3C;=n;++i)c[i]=a[i]-x*b[i];
  sort(c+1, c+n+1);
  for(int i=k+1;i&amp;#x3C;=n;++i) s+=c[i];
  return s&gt;=0;
}
double find(){
  double l=0, r=1;
  while(r-l&gt;1e-4){
    double mid=(l+r)/2;
    if(check(mid)) l=mid;//最大化
    else r=mid;
  }
  return l;
}
int main(){
  while(scanf(&quot;%d%d&quot;,&amp;#x26;n,&amp;#x26;k),n){
    for(int i=1;i&amp;#x3C;=n;i++)scanf(&quot;%lf&quot;,&amp;#x26;a[i]);
    for(int i=1;i&amp;#x3C;=n;i++)scanf(&quot;%lf&quot;,&amp;#x26;b[i]);
    printf(&quot;%.0lf\n&quot;, 100*find());
  }
  return 0;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>A8 前缀和</title><link>https://ehnotgod.site/blog/a8-%E5%89%8D%E7%BC%80%E5%92%8C</link><guid isPermaLink="true">https://ehnotgod.site/blog/a8-%E5%89%8D%E7%BC%80%E5%92%8C</guid><description>前缀和模板：预处理前缀和，O(1) 求任意区间和。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P8218&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;给定 $$n$$ 个正整数组成的数列 $$a_1, a_2, \cdots, a_n$$ 和 $$m$$ 个区间 $$[l_i,r_i]$$，分别求这 $$m$$ 个区间的区间和。&lt;/p&gt;
&lt;p&gt;对于所有测试数据，$$n,m\le10^5,a_i\le 10^4$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行，为一个正整数 $$n$$ 。&lt;/p&gt;
&lt;p&gt;第二行，为 $$n$$ 个正整数 $$a_1,a_2, \cdots ,a_n$$&lt;/p&gt;
&lt;p&gt;第三行，为一个正整数 $$m$$ 。&lt;/p&gt;
&lt;p&gt;接下来 $$m$$ 行，每行为两个正整数 $$l_i,r_i$$ ，满足$$1\le l_i\le r_i\le n$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;共 $$m$$ 行。&lt;/p&gt;
&lt;p&gt;第 $$i$$ 行为第 $$i$$ 组答案的询问。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入输出样例&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;4
4 3 2 1
2
1 4
2 3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;10
5
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于 $$100 %$$ 的数据：$$1 \le n, m\le 10^5$$，$$1 \le a_i\le 10^4$$&lt;/p&gt;
&lt;h3&gt;算法解析&lt;/h3&gt;
&lt;p&gt;把每项的和加起来，然后利用相减的方式得出和的值。适用于静态的区间求和，应用面非常广泛。
&lt;img src=&quot;images/A8-1.png&quot; alt=&quot;img&quot;&gt;&lt;/p&gt;
&lt;p&gt;时间复杂度$$O(n+m)$$&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;n = int(input())
a = list(map(int, input().split()))
s = [0] * (n + 1)
for i in range(1, n + 1):
    s[i] = s[i - 1] + a[i - 1]
m = int(input())
for i in range(m):
    l, r = map(int, input().split())
    print(s[r] - s[l - 1])
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include &amp;#x3C;iostream&gt;
#include &amp;#x3C;cstring&gt;
#include &amp;#x3C;algorithm&gt;
using namespace std;

int n,m;
int a[100005],s[100005];

int main(){
    scanf(&quot;%d&quot;,&amp;#x26;n);
    for(int i=1;i&amp;#x3C;=n;i++){
      scanf(&quot;%d&quot;,&amp;#x26;a[i]);
        s[i]=s[i-1]+a[i];
    }

    scanf(&quot;%d&quot;,&amp;#x26;m);
    for(int i=1;i&amp;#x3C;=m;i++){
        int l,r; scanf(&quot;%d%d&quot;,&amp;#x26;l,&amp;#x26;r);
        printf(&quot;%d\n&quot;,s[r]-s[l-1]);
    }
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>A9 二维前缀和</title><link>https://ehnotgod.site/blog/a9-%E4%BA%8C%E7%BB%B4%E5%89%8D%E7%BC%80%E5%92%8C</link><guid isPermaLink="true">https://ehnotgod.site/blog/a9-%E4%BA%8C%E7%BB%B4%E5%89%8D%E7%BC%80%E5%92%8C</guid><description>二维前缀和模板：O(1) 求子矩阵和。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P1387&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在一个 $$n\times m$$ 的只包含 $$0$$ 和 $$1$$ 的矩阵里找出一个不包含 $$0$$ 的最大正方形，输出边长。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输入文件第一行为两个整数 $$n,m(1\leq n,m\leq 100)$$，接下来 $$n$$ 行，每行 $$m$$ 个数字，用空格隔开，$$0$$ 或 $$1$$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;一个整数，最大正方形的边长。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入输出样例&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;4 4
0 1 1 1
1 1 1 0
0 1 1 0
1 1 0 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;算法解析&lt;/h3&gt;
&lt;p&gt;EH在偷偷开摆&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/A9-1.jpg&quot; alt=&quot;img&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/A9-2.png&quot; alt=&quot;img&quot;&gt;&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;a = [[0 for i in range(103)] for j in range(103)]
b = [[0 for i in range(103)] for j in range(103)]
n, m = map(int, input().split())
for i in range(1, n + 1):
    temp = list(map(int, input().split()))
    for j in range(1, m + 1):
        a[i][j] = temp[j - 1]
        b[i][j] = b[i][j - 1] + b[i - 1][j] - b[i - 1][j - 1] + a[i][j]
ans = 1
for l in range(2, min(m, n) + 1):
    for i in range(l, n + 1):
        for j in range(l, m + 1):
            if b[i][j]-b[i-l][j]-b[i][j-l]+b[i-l][j-l] == l * l:
                ans = l
print(ans)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include &amp;#x3C;algorithm&gt;
#include &amp;#x3C;iostream&gt;
using namespace std;

int a[103][103];
int b[103][103];

int main(){
  int n,m;
  cin&gt;&gt;n&gt;&gt;m;
  for(int i=1; i&amp;#x3C;=n; i++)
    for(int j=1; j&amp;#x3C;=m; j++){
      cin&gt;&gt;a[i][j];
      b[i][j]=b[i][j-1]+b[i-1][j]-b[i-1][j-1]+a[i][j];
    }

  int ans=1;
  for(int l=2;l&amp;#x3C;=min(n,m);l++)
    for(int i=l; i&amp;#x3C;=n; i++)
      for(int j=l; j&amp;#x3C;=m; j++)
        if(b[i][j]-b[i-l][j]-b[i][j-l]+b[i-l][j-l]==l*l)
          ans=l;
  cout&amp;#x3C;&amp;#x3C;ans;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>A10 差分</title><link>https://ehnotgod.site/blog/a10-%E5%B7%AE%E5%88%86</link><guid isPermaLink="true">https://ehnotgod.site/blog/a10-%E5%B7%AE%E5%88%86</guid><description>差分模板：区间加/减操作 O(1) 完成，再求前缀还原。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P4552&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;给定一个长度为 $$n$$ 的数列 $${a_1,a_2,\cdots,a_n}$$，每次可以选择一个区间$$[l,r]$$，使这个区间内的数都加 $$1$$ 或者都减 $$1$$。&lt;/p&gt;
&lt;p&gt;请问至少需要多少次操作才能使数列中的所有数都一样，并求出在保证最少次数的前提下，最终得到的数列有多少种。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行一个正整数 $$n$$&lt;br&gt;
接下来 $$n$$ 行,每行一个整数,第 $$i+1 $$行的整数表示 $$a_i$$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行输出最少操作次数&lt;br&gt;
第二行输出最终能得到多少种结果&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入输出样例&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;4
1
1
2
2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1
2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于 $$100%$$ 的数据，$$n\le 100000, 0 \le a_i \le 2^{31}$$。&lt;/p&gt;
&lt;h3&gt;算法解析&lt;/h3&gt;
&lt;p&gt;差分思想&lt;/p&gt;
&lt;p&gt;把序列 $$a$$ 的区间 $$[l,r]$$ 加 $$d$$，等价于其差分序列 $$b$$ 的点 $$b[l]$$ 加 $$d$$，点 $$b[r + 1]$$ 减 $$d$$，其他位置不变。&lt;/p&gt;
&lt;p&gt;即把原序列的“区间操作”转化为差分序列的“两点操作”。多次区间操作完成后，再利用前缀和还原。&lt;/p&gt;
&lt;p&gt;时间复杂度从 $$O(n^2)$$ 降为 $$O(n)$$。&lt;/p&gt;
&lt;p&gt;示例：
$$a$$: 1 2 3 1 2 3
$$b$$: 1 1 1 -2 1 1&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;
n = int(input())
a = [0]
b = [0] * (n + 1)
for i in range(1, n + 1):
    a.append(int(input()))
    b[i] = a[i] - a[i - 1]
p, q = 0, 0
for i in range(2, n + 1):
    if b[i] &gt; 0:
        p += b[i]
    else:
        q += abs(b[i])
print(max(p, q))
print(abs(p - q) + 1)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include &amp;#x3C;iostream&gt;
#include &amp;#x3C;cstring&gt;
using namespace std;

typedef long long LL;
const int N=100010;
int a[N], b[N];

int main(){
  int n;
  cin&gt;&gt;n;
  for(int i=1; i&amp;#x3C;=n; i++) cin&gt;&gt;a[i];
  for(int i=1; i&amp;#x3C;=n; i++) b[i]=a[i]-a[i-1];

  LL p=0, q=0;
  for(int i=2; i&amp;#x3C;=n; i++)
    if(b[i]&gt;0) p+=b[i];
    else q+=abs(b[i]);

  cout&amp;#x3C;&amp;#x3C;max(p,q)&amp;#x3C;&amp;#x3C;&apos;\n&apos;&amp;#x3C;&amp;#x3C;abs(p-q)+1;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>A11 ST表</title><link>https://ehnotgod.site/blog/a11-st%E8%A1%A8</link><guid isPermaLink="true">https://ehnotgod.site/blog/a11-st%E8%A1%A8</guid><description>ST 表模板：倍增预处理，O(1) 查询区间最大值。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P3865&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;给定一个长度为 $$N$$ 的数列，和 $$ M $$ 次询问，求出每一次询问的区间内数字的最大值。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行包含两个整数 $$N,M$$，分别表示数列的长度和询问的个数。&lt;/p&gt;
&lt;p&gt;第二行包含 $$N$$ 个整数（记为 $$a_i$$），依次表示数列的第 $$i$$ 项。&lt;/p&gt;
&lt;p&gt;接下来 $$M$$ 行，每行包含两个整数 $$l_i,r_i$$，表示查询的区间为 $$[l_i,r_i]$$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输出包含 $$M$$ 行，每行一个整数，依次表示每一次询问的结果。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入输出样例&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;8 8
9 3 1 7 5 6 0 8
1 6
1 5
2 7
2 6
1 8
4 8
3 7
1 8
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;9
9
7
7
9
8
7
9
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;说明/提示&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;对于 $$30%$$ 的数据，满足 $$1\le N,M\le 10$$。&lt;/p&gt;
&lt;p&gt;对于 $$70%$$ 的数据，满足 $$1\le N,M\le {10}^5$$。&lt;/p&gt;
&lt;p&gt;对于 $$100%$$ 的数据，满足 $$1\le N\le {10}^5$$，$$1\le M\le 2\times{10}^6$$，$$a_i\in[0,{10}^9]$$，$$1\le l_i\le r_i\le N$$。&lt;/p&gt;
&lt;h3&gt;算法解析&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;images/A11-1.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/A11-2.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;
import math
n, m = map(int, input().split())
h = list(map(int, input().split()))

# 使用ST表处理最大值
max_log = 20
f = [[0] * (max_log + 1) for _ in range(n + 1)]
for i in range(1, n + 1):
    f[i][0] = h[i - 1]
for j in range(1, max_log + 1):
    i = 1
    while i + 2 ** j - 1 &amp;#x3C;= n:
        mid = i + 2 ** (j - 1)
        f[i][j] = max(f[i][j - 1], f[mid][j - 1])
        i += 1
def find(l, r):
    k = int(math.log2(r - l + 1))
    ma = max(f[l][k], f[r - 2 ** k + 1][k])
    return ma
for i in range(m):
    l, r = map(int, input().split())
    print(find(l, r))
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include &amp;#x3C;iostream&gt;
#include &amp;#x3C;cstring&gt;
#include &amp;#x3C;algorithm&gt;
#include &amp;#x3C;cmath&gt;
using namespace std;

int f[100005][22];

int main(){
  int n,m; scanf(&quot;%d%d&quot;,&amp;#x26;n,&amp;#x26;m);

  for(int i=1;i&amp;#x3C;=n;i++) scanf(&quot;%d&quot;,&amp;#x26;f[i][0]);
  for(int j=1;j&amp;#x3C;=20;j++) //枚举区间长度
    for(int i=1;i+(1&amp;#x3C;&amp;#x3C;j)-1&amp;#x3C;=n;i++) //枚举起点
      f[i][j]=max(f[i][j-1],f[i+(1&amp;#x3C;&amp;#x3C;(j-1))][j-1]);

  for(int i=1,l,r;i&amp;#x3C;=m;i++){
    scanf(&quot;%d%d&quot;,&amp;#x26;l,&amp;#x26;r);
    int k=log2(r-l+1); //区间长度的指数
    printf(&quot;%d\n&quot;,max(f[l][k],f[r-(1&amp;#x3C;&amp;#x3C;k)+1][k]));
  }
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>A12 单调栈</title><link>https://ehnotgod.site/blog/a12-%E5%8D%95%E8%B0%83%E6%A0%88</link><guid isPermaLink="true">https://ehnotgod.site/blog/a12-%E5%8D%95%E8%B0%83%E6%A0%88</guid><description>单调栈模板：求每个数右边第一个比它大的数的下标。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P5788&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;给出项数为 $$n$$ 的整数数列 $$a_{1 \dots n}$$。&lt;/p&gt;
&lt;p&gt;定义函数 $$f(i)$$ 代表数列中第 $$i$$ 个元素之后第一个大于 $$a_i$$ 的元素的&lt;strong&gt;下标&lt;/strong&gt;，即 $$f(i)=\min_{i&amp;#x3C;j\leq n, a_j &gt; a_i} {j}$$。若不存在，则 $$f(i)=0$$。&lt;/p&gt;
&lt;p&gt;试求出 $$f(1\dots n)$$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行一个正整数 $$n$$。&lt;/p&gt;
&lt;p&gt;第二行 $$n$$ 个正整数 $$a_{1\dots n}$$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;一行 $$n$$ 个整数表示 $$f(1), f(2), \dots, f(n)$$ 的值。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入输出样例&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;5
1 4 2 3 5
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2 5 4 5 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于 $$100%$$ 的数据，$$1 \le n\leq 3\times 10^6$$，$$1\leq a_i\leq 10^9$$。&lt;/p&gt;
&lt;h3&gt;算法解析&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;单调栈&lt;/strong&gt;是一种维护元素单调性的栈结构，用于高效求解&quot;下一个更大/更小元素&quot;类问题。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;核心思路：&lt;/strong&gt; 从右向左遍历数组，维护一个&lt;strong&gt;单调递减栈&lt;/strong&gt;（栈底到栈顶递减），栈中存储下标。&lt;/p&gt;
&lt;p&gt;对于每个位置 $$i$$：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;弹出栈中所有值 $$\leq a_i$$ 的元素（它们不可能成为 $$i$$ 左侧任何元素的答案）；&lt;/li&gt;
&lt;li&gt;若栈非空，则栈顶下标即为 $$f(i)$$（第一个大于 $$a_i$$ 的元素）；&lt;/li&gt;
&lt;li&gt;将 $$i$$ 压入栈中。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;复杂度：&lt;/strong&gt; 每个元素至多入栈、出栈各一次，时间复杂度为 $$O(n)$$，空间复杂度为 $$O(n)$$。&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;n = int(input())
a = list(map(int, input().split()))
f = [0] * n
q = []
for i in range(n - 1, -1, -1):
    while q and a[q[-1]] &amp;#x3C; a[i]:
        q.pop()
    if q:
        f[i] = q[-1]
    q.append(i)
print(*f)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#include &amp;#x3C;cstdio&gt;
using namespace std;

const int N = 3000005;
int a[N], f[N], stk[N];

int main() {
    int n;
    scanf(&quot;%d&quot;, &amp;#x26;n);
    for (int i = 1; i &amp;#x3C;= n; i++) scanf(&quot;%d&quot;, &amp;#x26;a[i]);

    int top = 0;
    // 从右往左，维护单调递减栈，求每个位置右边第一个更大元素的下标
    for (int i = n; i &gt;= 1; i--) {
        while (top &amp;#x26;&amp;#x26; a[stk[top]] &amp;#x3C;= a[i]) top--;  // 弹出所有不大于 a[i] 的元素
        f[i] = top ? stk[top] : 0;                // 栈顶即答案，空栈则为 0
        stk[++top] = i;                            // 当前下标入栈
    }

    for (int i = 1; i &amp;#x3C;= n; i++) printf(&quot;%d &quot;, f[i]);
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>A13 第k小的数</title><link>https://ehnotgod.site/blog/a13-%E7%AC%ACk%E5%B0%8F%E7%9A%84%E6%95%B0</link><guid isPermaLink="true">https://ehnotgod.site/blog/a13-%E7%AC%ACk%E5%B0%8F%E7%9A%84%E6%95%B0</guid><description>快速选择：求序列中第 k 小的数。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;https://www.luogu.com.cn/problem/P1923&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输入 $$n$$ 个数字 $$a_i$$，输出这些数字中第 $$k$$ 小的数。最小的数是第 $$0$$ 小。&lt;/p&gt;
&lt;p&gt;请尽量不要使用 &lt;code&gt;nth_element&lt;/code&gt; 来写本题，因为本题的重点在于练习分治算法。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行有两个整数，分别表示 $$n$$ 和 $$k$$。&lt;/p&gt;
&lt;p&gt;第二行有 $$n$$ 个整数，第 $$i$$ 个数表示 $$a_i$$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;一个整数，表示第 $$k$$ 小的数。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入输出样例&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;5 1
4 3 2 1 5

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;说明/提示&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;对于 $$100%$$ 的数据，$$1\le a_i&amp;#x3C;{10}^9$$，$$1 \le n &amp;#x3C; 5\times 10^6$$，且 $$n$$ 为奇数。&lt;/p&gt;
&lt;h3&gt;算法解析&lt;/h3&gt;
&lt;p&gt;QuickSelect（快速选择）算法，用来在无序数组中快速找到第 k 小的元素。它先选一个基准值 &lt;code&gt;x&lt;/code&gt;，像快速排序一样把数组划分成“左边 ≤ x、右边 ≥ x”两部分，但不会递归处理两边，而是只进入包含第 &lt;code&gt;k&lt;/code&gt; 小元素的那一边继续查找，因此平均时间复杂度是 &lt;code&gt;O(n)&lt;/code&gt;，比完整排序的 &lt;code&gt;O(n log n)&lt;/code&gt; 更快。&lt;/p&gt;
&lt;p&gt;C++里可以直接使用nth_element(begin, nth, end)&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;
import sys
sys.setrecursionlimit(5000000)
def qnth_element(l, r):
    global a
    if l == r:
        return a[l]
    i = l - 1
    j = r + 1
    x = a[(l + r) // 2]
    while i &amp;#x3C; j:
        while 1:
            i += 1
            if not a[i] &amp;#x3C; x:
                break
        while 1:
            j -= 1
            if not a[j] &gt; x:
                break
        if i &amp;#x3C; j:
            a[i], a[j] = a[j], a[i]
    if k &amp;#x3C;= j:
        return qnth_element(l, j)
    else:
        return qnth_element(j + 1, r)
n, k = map(int, input().split())
a = list(map(int, input().split()))
print(qnth_element(0, n - 1))
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include &amp;#x3C;iostream&gt;
using namespace std;

int n,k,a[5000010];

int qnth_element(int l, int r){
  if(l==r) return a[l];
  int i=l-1, j=r+1, x=a[(l+r)/2];
  while(i&amp;#x3C;j){
    do i++; while(a[i]&amp;#x3C;x); //向右找&gt;=x的数
    do j--; while(a[j]&gt;x); //向左找&amp;#x3C;=x的数
    if(i&amp;#x3C;j) swap(a[i],a[j]);
  }
  if(k&amp;#x3C;=j) return qnth_element(l,j);
  else return qnth_element(j+1,r);
}

int main(){
  scanf(&quot;%d%d&quot;,&amp;#x26;n,&amp;#x26;k);
  for(int i=0;i&amp;#x3C;n;i++) scanf(&quot;%d&quot;,&amp;#x26;a[i]);
  printf(&quot;%d\n&quot;,qnth_element(0,n-1));
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>A14 归并排序、逆序对</title><link>https://ehnotgod.site/blog/a14-%E5%BD%92%E5%B9%B6%E6%8E%92%E5%BA%8F%E9%80%86%E5%BA%8F%E5%AF%B9</link><guid isPermaLink="true">https://ehnotgod.site/blog/a14-%E5%BD%92%E5%B9%B6%E6%8E%92%E5%BA%8F%E9%80%86%E5%BA%8F%E5%AF%B9</guid><description>归并排序模板：排序同时统计逆序对数量。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P1908&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;猫猫 TOM 和小老鼠 JERRY 最近又较量上了，但是毕竟都是成年人，他们已经不喜欢再玩那种你追我赶的游戏，现在他们喜欢玩统计。&lt;/p&gt;
&lt;p&gt;最近，TOM 老猫查阅到一个人类称之为“逆序对”的东西，这东西是这样定义的：对于给定的一段正整数序列，逆序对就是序列中 $$a_i&gt;a_j$$ 且 $$i&amp;#x3C;j$$ 的有序对。知道这概念后，他们就比赛谁先算出给定的一段正整数序列中逆序对的数目。注意序列中可能有重复数字。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行，一个数 $$n$$，表示序列中有 $$n$$ 个数。&lt;/p&gt;
&lt;p&gt;第二行 $$n$$ 个数，表示给定的序列。序列中每个数字不超过 $$10^9$$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输出序列中逆序对的数目。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入输出样例&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;6
5 4 2 6 3 1

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;11
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;说明/提示&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;对于所有数据，$$1 \leq n \leq 5 \times 10^5$$。&lt;/p&gt;
&lt;h3&gt;算法解析&lt;/h3&gt;
&lt;p&gt;归并排序&lt;/p&gt;
&lt;p&gt;主要利用&lt;strong&gt;分治思想&lt;/strong&gt;，时间复杂度 $$O(nlogn)$$&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;对数列不断等长拆分，直到一个数的长度。&lt;/li&gt;
&lt;li&gt;回溯时，按升序合并左右两段。&lt;/li&gt;
&lt;li&gt;重复以上两个过程，直到递归结束。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;
import sys
sys.setrecursionlimit(1000000)

def merge(l, r):
    global res, a, b
    if l &gt;= r:
        return
    mid = (l + r) // 2
    merge(l, mid)
    merge(mid + 1, r)

    i = l; j = mid + 1; k = l
    while i &amp;#x3C;= mid and j &amp;#x3C;= r:
        if a[i] &amp;#x3C;= a[j]:
            b[k] = a[i]
            k += 1; i += 1
        else:
            b[k] = a[j]
            k += 1; j += 1
            res += mid - i + 1
    while i &amp;#x3C;= mid:
        b[k] = a[i]
        k += 1; i += 1
    while j &amp;#x3C;= r:
        b[k] = a[j]
        k += 1; j += 1
    for i in range(l, r + 1):
        a[i] = b[i]

n = int(input())
a = list(map(int, input().split()))
b = [0] * n
res = 0
merge(0, n - 1)
print(res)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include &amp;#x3C;iostream&gt;
using namespace std;

int n,a[500010],b[500010];
long long res;

void merge(int l,int r){
  if(l&gt;=r) return;
  int mid=l+r&gt;&gt;1;
  merge(l,mid);
  merge(mid+1,r); //拆分

  int i=l,j=mid+1,k=l; //合并
  while(i&amp;#x3C;=mid &amp;#x26;&amp;#x26; j&amp;#x3C;=r){
    if(a[i]&amp;#x3C;=a[j]) b[k++]=a[i++];
    else b[k++]=a[j++], res+=mid-i+1;
  }
  while(i&amp;#x3C;=mid) b[k++]=a[i++];
  while(j&amp;#x3C;=r) b[k++]=a[j++];
  for(i=l; i&amp;#x3C;=r; i++) a[i]=b[i];
}

int main(){
  cin&gt;&gt;n;
  for(int i=0;i&amp;#x3C;n;i++) scanf(&quot;%d&quot;,&amp;#x26;a[i]);
  merge(0,n-1);
  printf(&quot;%lld\n&quot;,res);
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>A15 堆</title><link>https://ehnotgod.site/blog/a15-%E5%A0%86</link><guid isPermaLink="true">https://ehnotgod.site/blog/a15-%E5%A0%86</guid><description>堆（优先队列）模板：支持插入、删除、取最值。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P3378&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;给定一个数列，初始为空，请支持下面三种操作：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;给定一个整数 $$x$$，请将 $$x$$ 加入到数列中。&lt;/li&gt;
&lt;li&gt;输出数列中最小的数。&lt;/li&gt;
&lt;li&gt;删除数列中最小的数（如果有多个数最小，只删除 $1$ 个）。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行是一个整数，表示操作的次数 $$n$$。&lt;br&gt;
接下来 $$n$$ 行，每行表示一次操作。每行首先有一个整数 $$op$$ 表示操作类型。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;若 $$op = 1$$，则后面有一个整数 $$x$$，表示要将 $$x$$ 加入数列。&lt;/li&gt;
&lt;li&gt;若 $$op = 2$$，则表示要求输出数列中的最小数。&lt;/li&gt;
&lt;li&gt;若 $$op = 3$$，则表示删除数列中的最小数。如果有多个数最小，只删除 $$1$$ 个。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;对于每个操作 $$2$$，输出一行一个整数表示答案。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入输出样例&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;5
1 2
1 5
2
3
2

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2
5
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;说明/提示&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;对于 $$100%$$ 的数据，保证 $$1 \leq n \leq 10^6$$，$$1 \leq x \lt 2^{31}$$，$$op \in {1, 2, 3}$$。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;算法解析&lt;/h3&gt;
&lt;p&gt;py里的堆可以用heapq高效实现，你不需要知道它是什么，什么是堆（优先队列）。这样吧，你把一个py里的列表q想象成一个盒子，你每次花费logn的时间往里面丢东西（heapq.heappush(q, ai)），它总是会弹出盒子里最小那个数，查询方式为q[0]。你也可以花费logn的时间把这个最小数取出来（heapq.heappop(q)），此时其他数会成为最小的数并置顶。&lt;/p&gt;
&lt;p&gt;heapq默认小根堆（弹出最小数），你可以通过加个负号让它变成大根堆。&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import heapq

n = int(input())
q = []
while n:
    n -= 1
    op = list(map(int, input().split()))
    if op[0] == 1:
        heapq.heappush(q, op[1])
    elif op[0] == 2:
        print(q[0])
    else:
        heapq.heappop(q)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;// STL代码
#include &amp;#x3C;iostream&gt;
#include &amp;#x3C;cstring&gt;
#include &amp;#x3C;algorithm&gt;
#include &amp;#x3C;queue&gt;
using namespace std;

priority_queue&amp;#x3C;int,vector&amp;#x3C;int&gt;,greater&amp;#x3C;int&gt; &gt; q;

int main(){
  int n; scanf(&quot;%d&quot;,&amp;#x26;n); //操作次数
  while(n--){
    int op,x; scanf(&quot;%d&quot;,&amp;#x26;op);
    if(op==1) scanf(&quot;%d&quot;,&amp;#x26;x), q.push(x);
    else if(op==2) printf(&quot;%d\n&quot;,q.top());
    else q.pop();
  }
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>A16 对顶堆</title><link>https://ehnotgod.site/blog/a16-%E5%AF%B9%E9%A1%B6%E5%A0%86</link><guid isPermaLink="true">https://ehnotgod.site/blog/a16-%E5%AF%B9%E9%A1%B6%E5%A0%86</guid><description>对顶堆模板：动态维护序列中位数/第 k 大。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P7072&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;NOI2130 即将举行。为了增加观赏性，CCF 决定逐一评出每个选手的成绩，并直播即时的获奖分数线。本次竞赛的获奖率为 $w%$，即当前排名前 $$w%$$ 的选手的最低成绩就是即时的分数线。&lt;/p&gt;
&lt;p&gt;更具体地，若当前已评出了 $$p$$ 个选手的成绩，则当前计划获奖人数为 $$\max(1, \lfloor p \times w %\rfloor)$$，其中 $$w$$ 是获奖百分比，$$\lfloor x \rfloor$$ 表示对 $$x$$ 向下取整，$$\max(x,y)$$ 表示 $$x$$ 和 $$y$$ 中较大的数。如有选手成绩相同，则所有成绩并列的选手都能获奖，因此实际获奖人数可能比计划中多。&lt;/p&gt;
&lt;p&gt;作为评测组的技术人员，请你帮 CCF 写一个直播程序。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行有两个整数 $$n, w$$。分别代表选手总数与获奖率。&lt;br&gt;
第二行有 $$n$$ 个整数，依次代表逐一评出的选手成绩。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;只有一行，包含 $$n$$ 个非负整数，依次代表选手成绩逐一评出后，即时的获奖分数线。相邻两个整数间用一个空格分隔。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入输出样例&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;10 60
200 300 400 500 600 600 0 300 200 100

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;200 300 400 400 400 500 400 400 300 300
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输入 #2&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;10 30
100 100 600 100 100 100 100 100 100 100
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #2&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;100 100 600 600 600 600 100 100 100 100
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;算法解析&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;images/A16-2.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;
import heapq
a = [] # 大根堆
b = [] # 小根堆
n, w = map(int, input().split())
lis = list(map(int, input().split()))
for i in range(n):
    x = lis[i]
    k = max(1, int((i + 1) * w / 100))
    if len(b) == 0 or x &gt;= b[0]:
        heapq.heappush(b, x)
    else:
        heapq.heappush(a, -x)
    while len(b) &gt; k:
        heapq.heappush(a, -heapq.heappop(b))
    while len(b) &amp;#x3C; k:
        heapq.heappush(b, -heapq.heappop(a))
    print(b[0], end=&quot; &quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include&amp;#x3C;cstring&gt;
#include&amp;#x3C;iostream&gt;
#include&amp;#x3C;algorithm&gt;
#include&amp;#x3C;queue&gt;
using namespace std;

int main(){
  int n,w; scanf(&quot;%d%d&quot;,&amp;#x26;n,&amp;#x26;w); //选手总数,获奖率
  priority_queue&amp;#x3C;int&gt; a; //大根堆
  priority_queue&amp;#x3C;int,vector&amp;#x3C;int&gt;,greater&amp;#x3C;int&gt; &gt; b;

  for(int i=1; i&amp;#x3C;=n; i++){
    int x; scanf(&quot;%d&quot;,&amp;#x26;x);
    if(b.empty()||x&gt;=b.top()) b.push(x); //插入
    else a.push(x);

    int k=max(1,i*w/100); //第k大
    while(b.size()&gt;k) a.push(b.top()), b.pop(); //调整
    while(b.size()&amp;#x3C;k) b.push(a.top()), a.pop();
    printf(&quot;%d &quot;, b.top()); //取值
  }
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>A17 距离之和最小、中位数</title><link>https://ehnotgod.site/blog/a17-%E8%B7%9D%E7%A6%BB%E4%B9%8B%E5%92%8C%E6%9C%80%E5%B0%8F%E4%B8%AD%E4%BD%8D%E6%95%B0</link><guid isPermaLink="true">https://ehnotgod.site/blog/a17-%E8%B7%9D%E7%A6%BB%E4%B9%8B%E5%92%8C%E6%9C%80%E5%B0%8F%E4%B8%AD%E4%BD%8D%E6%95%B0</guid><description>中位数性质：使各点距离和最小的点恰是中位数。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://codeforces.com/problemset/problem/1486/B&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/A17-1.png&quot; alt=&quot;image-20250416092236944&quot;&gt;&lt;/p&gt;
&lt;h3&gt;算法解析&lt;/h3&gt;
&lt;p&gt;中位数性质：使各点距离和最小的点恰是中位数。&lt;/p&gt;
&lt;p&gt;因为求的是曼哈顿距离，我们可以把x和y分开看，然后分别找到各自的x和y即为所求点。&lt;/p&gt;
&lt;p&gt;本题的本质是贪心。&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;t = int(input())
for _ in range(t):
    n = int(input())
    a = [0] * n
    b = [0] * n
    for i in range(n):
        a[i], b[i] = map(int, input().split())
    a.sort()
    b.sort()
    x = a[n // 2] - a[(n - 1) // 2] + 1
    y = b[n // 2] - b[(n - 1) // 2] + 1
    print(x * y)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include &amp;#x3C;iostream&gt;
#include &amp;#x3C;cstring&gt;
#include &amp;#x3C;algorithm&gt;
using namespace std;

const int N=1010;
int n,a[N],b[N];

int main(){
  int t; cin&gt;&gt;t;
  while(t--){
    cin&gt;&gt;n;
    for(int i=0; i&amp;#x3C;n; i++) cin&gt;&gt;a[i]&gt;&gt;b[i];
    sort(a,a+n); sort(b,b+n);

    int x=a[n/2]-a[(n-1)/2]+1;
    int y=b[n/2]-b[(n-1)/2]+1;
    cout&amp;#x3C;&amp;#x3C;1LL*x*y&amp;#x3C;&amp;#x3C;&apos;\n&apos;;
  }
  return 0;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>A18 双指针 尺取法</title><link>https://ehnotgod.site/blog/a18-%E5%8F%8C%E6%8C%87%E9%92%88-%E5%B0%BA%E5%8F%96%E6%B3%95</link><guid isPermaLink="true">https://ehnotgod.site/blog/a18-%E5%8F%8C%E6%8C%87%E9%92%88-%E5%B0%BA%E5%8F%96%E6%B3%95</guid><description>双指针尺取法：随端点单调移动维护区间。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P1147&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;给定一个正整数 $$M$$，求出所有的连续的正整数段（每一段至少有两个数），使得这些连续的正整数段中的全部数之和为 $$M$$。&lt;/p&gt;
&lt;p&gt;例如，$$1998+1999+2000+2001+2002=10000$$，所以从 $$1998$$ 到 $$2002$$ 的一个正整数段为 $$M=10000$$ 的一个解。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输入一行一个正整数，表示 $$M$$ 的值（$$10\le M\le 2\times 10^6$$）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输出每行包含两个正整数，表示一个满足条件的连续正整数段的左右端点，两数之间用一个空格隔开。&lt;/p&gt;
&lt;p&gt;输出按左端点大小升序排列。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入输出样例&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;10000

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;18 142 
297 328 
388 412 
1998 2002

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;说明/提示&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;对于 $$100%$$ 的数据，保证至少有一个解。&lt;/p&gt;
&lt;h3&gt;算法解析&lt;/h3&gt;
&lt;p&gt;本题可以使用双指针尺取法求解，维护一个左端点 $$i$$ 和右端点 $$j$$，并维护当前区间和 $$sum=i+(i+1)+\cdots+j$$。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;初始令 $$i=1,j=1,sum=1$$。&lt;/li&gt;
&lt;li&gt;若 $$sum &amp;#x3C; M$$，则右指针右移：$$j\leftarrow j+1$$，并把新的值加到 $$sum$$ 中。&lt;/li&gt;
&lt;li&gt;若 $$sum &gt; M$$，则左指针右移：把 $$i$$ 从 $$sum$$ 中减去，$$i\leftarrow i+1$$。&lt;/li&gt;
&lt;li&gt;若 $$sum = M$$，则记录当前区间 $$[i,j]$$，再移动左指针继续搜索。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;由于每个指针最多右移 $$O(n)$$ 次，整体时间复杂度为 $$O(n)$$。&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;
m = int(input())
i = 1
j = 1
sum = 1
while i &amp;#x3C;= m // 2:
    if sum &amp;#x3C; m:
        j += 1
        sum += j
    if sum &gt;= m:
        if sum == m:
            print(i, j)
        sum -= i
        i += 1
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include&amp;#x3C;cstdio&gt;

int main(){
  int m; scanf(&quot;%d&quot;,&amp;#x26;m);
  int i=1,j=1,sum=1;
  while(i&amp;#x3C;=m/2){      //i&amp;#x3C;=j
    if(sum&amp;#x3C;m){
      j++;
      sum+=j;
    }
    if(sum&gt;=m){
      if(sum==m) printf(&quot;%d %d\n&quot;,i,j);
      sum-=i;
      i++;
    }
  }
  return 0;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>A19 双指针 动态窗口</title><link>https://ehnotgod.site/blog/a19-%E5%8F%8C%E6%8C%87%E9%92%88-%E5%8A%A8%E6%80%81%E7%AA%97%E5%8F%A3</link><guid isPermaLink="true">https://ehnotgod.site/blog/a19-%E5%8F%8C%E6%8C%87%E9%92%88-%E5%8A%A8%E6%80%81%E7%AA%97%E5%8F%A3</guid><description>双指针滑动窗口：动态调整窗口覆盖目标。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P1381&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;灵梦有 $$n$$ 个单词想要背，但她想通过一篇文章中的一段来记住这些单词。&lt;/p&gt;
&lt;p&gt;文章由 $$m$$ 个单词构成，她想在文章中找出连续的一段，其中包含最多的她想要背的单词（重复的只算一个）。并且在背诵的单词量尽量多的情况下，还要使选出的文章段落尽量短，这样她就可以用尽量短的时间学习尽可能多的单词了。&lt;/p&gt;
&lt;p&gt;每个单词仅包含小写字母。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第 $$1$$ 行一个数 $$n$$，接下来 $$n$$ 行每行是一个长度不超过 $$10$$ 的字符串，表示一个要背的单词。&lt;/p&gt;
&lt;p&gt;接着是一个数 $$m$$，然后是 $$m$$ 行长度不超过 $$10$$ 的字符串，每个表示文章中的一个单词。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输出文件共 $$2$$ 行。第 $$1$$ 行为文章中最多包含的要背的单词数，第 $$2$$ 行表示在文章中包含最多要背单词的最短的连续段的长度。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入输出样例&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;3
hot
dog
milk
5
hot
dog
dog
milk
hot

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;3
3

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;说明/提示&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;对于 $$30%$$ 的数据，$$n \le 50$$，$$m \le 500$$；&lt;/li&gt;
&lt;li&gt;对于 $$60%$$ 的数据，$$n \le 300$$，$$m \le 5000$$；&lt;/li&gt;
&lt;li&gt;对于 $$100%$$ 的数据，$$1 \le n \le 1000$$，$$1 \le m \le 10^5$$。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;算法解析&lt;/h3&gt;
&lt;p&gt;本题使用双指针滑动窗口维护当前文章段的左右端点 $$i$$ 和 $$j$$，并统计当前窗口内目标单词的出现次数。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用 &lt;code&gt;word&lt;/code&gt; 集合标记要背的单词；&lt;/li&gt;
&lt;li&gt;使用 &lt;code&gt;cnt&lt;/code&gt; 记录当前区间内每个目标单词的出现次数；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sum&lt;/code&gt; 表示当前窗口内不同目标单词的个数；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;len&lt;/code&gt; 记录在目标单词数量最大时的最短窗口长度。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;当新的单词 $$s[j]$$ 进入窗口时：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;如果它是目标单词，则 &lt;code&gt;cnt[s[j]]++&lt;/code&gt;；若 &lt;code&gt;cnt[s[j]]==1&lt;/code&gt;，说明当前窗口新增了一个不同目标单词；&lt;/li&gt;
&lt;li&gt;在窗口右扩后，尝试收缩左端点 $$i$$：
&lt;ul&gt;
&lt;li&gt;若 $$s[i]$$ 是非目标单词，则直接移动 $$i$$；&lt;/li&gt;
&lt;li&gt;若 $$s[i]$$ 是目标单词且 &lt;code&gt;cnt[s[i]]&gt;=2&lt;/code&gt;，则可以将其出现次数减一并移动 $$i$$；&lt;/li&gt;
&lt;li&gt;若 &lt;code&gt;cnt[s[i]]==1&lt;/code&gt;，则不能再收缩，否则会丢失一个目标单词；&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;每次收缩后，用当前窗口长度更新最短长度 &lt;code&gt;len&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这样可以在保证目标单词数量不减少的前提下，使窗口尽可能短。整个过程中，左右指针均最多移动 $$O(m)$$ 次，时间复杂度为 $$O(m)$$。&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# P1381 单词背诵
# 24年新星赛-小猪的纸花
from collections import defaultdict
n = int(input())
word = defaultdict(bool)
cnt = defaultdict(int)
len = 0
sum = 0
for i in range(n):
    s1 = input()
    word[s1] = True
m = int(input())
s = [0] * (m + 1)
i = 1
for j in range(1, m + 1):
    s[j] = input()
    if word[s[j]]:
        cnt[s[j]] += 1
    if cnt[s[j]] == 1:
        sum += 1
        len = j - i + 1
    while i &amp;#x3C;= j:
        if cnt[s[i]] == 1:
            break
        if cnt[s[i]] &gt;= 2:
            cnt[s[i]] -= 1
            i += 1
        if not word[s[i]]:
            i += 1
    len = min(len, j - i + 1)
print(sum)
print(len)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include&amp;#x3C;bits/stdc++.h&gt;
using namespace std;

int n,m;
string s[100005],s1;
map&amp;#x3C;string,bool&gt; word;
map&amp;#x3C;string,int&gt; cnt;
int sum,len;
//s[] 记录文章中的单词
//word[] 记录单词表中的单词
//cnt[] 记录文章当前区间内单词出现次数
//sum 记录文章中出现单词表的单词数
//len 记录包含表中单词最多的区间的最短长度

int main(){
  cin&gt;&gt;n;
  for(int i=1;i&amp;#x3C;=n;i++)cin&gt;&gt;s1,word[s1]=1;
  cin&gt;&gt;m;
  for(int j=1,i=1; j&amp;#x3C;=m; j++){  //i&amp;#x3C;=j
    cin&gt;&gt;s[j];
    if(word[s[j]]) cnt[s[j]]++;
    if(cnt[s[j]]==1) sum++, len=j-i+1;
    while(i&amp;#x3C;=j){
      if(cnt[s[i]]==1) break; //保持i指针位置不动
      if(cnt[s[i]]&gt;=2) cnt[s[i]]--,i++; //去重,更优
      if(!word[s[i]]) i++; //去掉非目标单词,更优
    }
    len=min(len,j-i+1); //更新
  }
  cout&amp;#x3C;&amp;#x3C;sum&amp;#x3C;&amp;#x3C;endl&amp;#x3C;&amp;#x3C;len&amp;#x3C;&amp;#x3C;endl;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>A20 双指针区间合并</title><link>https://ehnotgod.site/blog/a20-%E5%8F%8C%E6%8C%87%E9%92%88%E5%8C%BA%E9%97%B4%E5%90%88%E5%B9%B6</link><guid isPermaLink="true">https://ehnotgod.site/blog/a20-%E5%8F%8C%E6%8C%87%E9%92%88%E5%8C%BA%E9%97%B4%E5%90%88%E5%B9%B6</guid><description>双指针区间合并：合并相交/相邻的区间。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P1496&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;曹操平定北方以后，公元 208 年，率领大军南下，进攻刘表。他的人马还没有到荆州，刘表已经病死。他的儿子刘琮听到曹军声势浩大，吓破了胆，先派人求降了。&lt;/p&gt;
&lt;p&gt;孙权任命周瑜为都督，拨给他三万水军，叫他同刘备协力抵抗曹操。&lt;/p&gt;
&lt;p&gt;隆冬的十一月，天气突然回暖，刮起了东南风。&lt;/p&gt;
&lt;p&gt;没想到东吴船队离开北岸大约二里距离，前面十条大船突然同时起火。火借风势，风助火威。十条火船，好比十条火龙一样，闯进曹军水寨。那里的船舰，都挤在一起，又躲不开，很快地都烧起来。一眨眼工夫，已经烧成一片火海。&lt;/p&gt;
&lt;p&gt;曹操气急败坏的把你找来，要你钻入火海把连环线上着火的船只的长度统计出来！&lt;/p&gt;
&lt;p&gt;给定每个起火部分的起点和终点，请你求出燃烧位置的长度之和。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行一个整数，表示起火的信息条数 $$n$$。&lt;br&gt;
接下来 $$n$$ 行，每行两个整数 $$a, b$$，表示一个着火位置的起点和终点（&lt;strong&gt;注意：左闭右开&lt;/strong&gt;）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输出一行一个整数表示答案。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入输出样例&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;3
-1 1
5 11
2 9
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;11
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;说明/提示&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;对于全部的测试点，保证 $$1 \leq n \leq 2 \times 10^4$$，$$-2^{31} \leq a &amp;#x3C; b &amp;#x3C; 2^{31}$$，且答案小于 $$2^{31}$$。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;算法解析&lt;/h3&gt;
&lt;p&gt;本题通过区间合并求出被燃烧区域的总长度。思路是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;将所有区间按起点升序排序；&lt;/li&gt;
&lt;li&gt;维护当前合并区间 $$[l, r)$$，遍历每个新区间 $$[a,b)$$；&lt;/li&gt;
&lt;li&gt;若新区间在当前区间右侧完全不相交，则将当前区间长度 $$r-l$$ 累加到答案，并开始新的合并区间；&lt;/li&gt;
&lt;li&gt;若新区间与当前区间相交或相接，则更新右端点 $$r=\max(r,b)$$；&lt;/li&gt;
&lt;li&gt;遍历结束后，再累加最后一个区间长度。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这种做法只需一次排序加一次线性扫描，时间复杂度为 $$O(n \log n)$$，空间复杂度为 $$O(n)$$。&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# py代码大概率超时，这是洛谷的问题，不要在意，其他地方py给开额外时间的。

n = int(input())
lis = []
for i in range(n):
    a, b = map(int, input().split())
    lis.append([a, b])
lis.sort(key=lambda x: (x[0], x[1]))
l = -1e18
r = -1e18
ans = 0
for i in range(n):
    a, b = lis[i]
    if r &amp;#x3C; a:
        ans += r - l
        l = a
        r = b
    else:
        r = max(r, b)
ans += r - l
print(int(ans))
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include&amp;#x3C;iostream&gt;
#include&amp;#x3C;cstdio&gt;
#include&amp;#x3C;algorithm&gt;
using namespace std;

#define N 20005
struct line{    //线段
  int l,r;
  bool operator&amp;#x3C;(line &amp;#x26;t){
    return l&amp;#x3C;t.l;
  }
}a[N];
int n,st,ed,sum;
//a[] 存储每条线段的起点,终点
//st  存储合并区间的起点
//ed  存储合并区间的终点
//sum 存储合并区间的长度

int main(){
  scanf(&quot;%d&quot;,&amp;#x26;n);
  for(int i=1;i&amp;#x3C;=n;i++)
    cin&gt;&gt;a[i].l&gt;&gt;a[i].r;
  sort(a+1,a+n+1); //按起点排序

  st=a[1].l; ed=a[1].r;
  sum+=a[1].r-a[1].l;
  for(int i=2; i&amp;#x3C;=n; i++){
    if(a[i].l&amp;#x3C;=ed){
      if(a[i].r&amp;#x3C;ed) //覆盖
        continue;
      else {        //重叠
        st=ed;
        ed=a[i].r;
        sum+=ed-st;
      }
    }
    else{           //相离
      st=a[i].l;
      ed=a[i].r;
      sum+=ed-st;
    }
  }
  cout&amp;#x3C;&amp;#x3C;sum&amp;#x3C;&amp;#x3C;endl;
  return 0;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>A21 堆序列</title><link>https://ehnotgod.site/blog/a21-%E5%A0%86%E5%BA%8F%E5%88%97</link><guid isPermaLink="true">https://ehnotgod.site/blog/a21-%E5%A0%86%E5%BA%8F%E5%88%97</guid><description>堆+贪心：多路归并求最小的 N 个和。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P1631&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;有两个长度为 $$N$$ 的&lt;strong&gt;单调不降&lt;/strong&gt;序列 $$A,B$$，在 $$A,B$$ 中各取一个数相加可以得到 $$N^2$$ 个和，求这 $$N^2$$ 个和中最小的 $$N$$ 个。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行一个正整数 $$N$$；&lt;/p&gt;
&lt;p&gt;第二行 $N$ 个整数 $A_{1\dots N}$。&lt;/p&gt;
&lt;p&gt;第三行 $$N$$ 个整数 $$B_{1\dots N}$$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;一行 $$N$$ 个整数，从小到大表示这 $$N$$ 个最小的和。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入输出样例&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;3
2 6 6
1 4 8
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;3 6 7
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;说明/提示&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;对于 $$50%$$ 的数据，$$N \le 10^3$$。&lt;/p&gt;
&lt;p&gt;对于 $$100%$$ 的数据，$$1 \le N \le 10^5$$，$$1 \le a_i,b_i \le 10^9$$。&lt;/p&gt;
&lt;h3&gt;算法解析&lt;/h3&gt;
&lt;p&gt;让 A 中的每个数与 B 中一个数相加，生成 N 个有序队列，&lt;/p&gt;
&lt;p&gt;1: A[1] + B[1] ≤ A[2] + B[1] ≤ … ≤ A[N] + B[1]
2: A[1] + B[2] ≤ A[2] + B[2] ≤ … ≤ A[N] + B[2]
k: ……
N: A[1] + B[N] ≤ A[2] + B[N] ≤ … ≤ A[N] + B[N]&lt;/p&gt;
&lt;p&gt;要将这 N 个有序队列进行合并排序：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;将这 N 个队列中的第一列元素放入一个小根堆中；&lt;/li&gt;
&lt;li&gt;每次取出堆中的最小值。若这个最小值来自于第 k 个队列，就将第 k 个队列的下一个元素放入堆中。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;时间复杂度：O(NlogN)。&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import heapq
n = int(input())
q = []
a = list(map(int, input().split()))
a.insert(0, 0)
b = list(map(int, input().split()))
b.insert(0, 0)
id = [0] * (n + 1)
for i in range(1, n + 1):
    id[i] = 1
    heapq.heappush(q, [a[1] + b[i], i])
for j in range(n):
    print(q[0][0], end=&quot; &quot;)
    i = q[0][1]
    heapq.heappop(q)
    id[i] += 1
    heapq.heappush(q, [a[id[i]] + b[i], i])
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include &amp;#x3C;cstdio&gt;
#include &amp;#x3C;queue&gt;
using namespace std;

const int N=100005;
int a[N],b[N],id[N];
priority_queue&amp;#x3C;pair&amp;#x3C;int,int&gt;,
       vector&amp;#x3C;pair&amp;#x3C;int,int&gt;&gt;,
       greater&amp;#x3C;pair&amp;#x3C;int,int&gt;&gt;&gt;q;
//id[i]: 记录b[i]的搭档的下标
//q: 小根堆，存储&amp;#x3C;两数和,组的下标&gt;

int main(){
  int n; scanf(&quot;%d&quot;,&amp;#x26;n);
  for(int i=1; i&amp;#x3C;=n; i++) scanf(&quot;%d&quot;,&amp;#x26;a[i]);
  for(int i=1; i&amp;#x3C;=n; i++){
    scanf(&quot;%d&quot;,&amp;#x26;b[i]);
    id[i]=1;
    q.push({a[1]+b[i],i});
  }
  while(n--){
    printf(&quot;%d &quot;,q.top().first);
    int i=q.top().second; q.pop();
    q.push({a[++id[i]]+b[i],i});
  }
  return 0;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>A22 贪心</title><link>https://ehnotgod.site/blog/a22-%E8%B4%AA%E5%BF%83</link><guid isPermaLink="true">https://ehnotgod.site/blog/a22-%E8%B4%AA%E5%BF%83</guid><description>贪心模板：局部最优策略推出全局最优。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;https://www.luogu.com.cn/problem/P1842&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Farmer John 养了 $$N$$ 头牛，她们已经按 $$1\sim N$$ 依次编上了号。FJ 所不知道的是，他的所有牛都梦想着从农场逃走，去参加马戏团的演出。可奶牛们很快发现她们那笨拙的蹄子根本无法在钢丝或晃动的秋千上站稳（她们还尝试过把自己装在大炮里发射出去，但可想而知，结果是悲惨的）。最终，她们决定练习一种最简单的杂技：把所有牛都摞在一起，比如说，第一头牛站在第二头的身上，同时第二头牛又站在第三头牛的身上……最底下的是第 $$N$$ 头牛。&lt;/p&gt;
&lt;p&gt;每头牛都有自己的体重以及力量，编号为 $$i$$ 的奶牛的体重为 $$W_i$$，力量为 $$S_i$$。&lt;/p&gt;
&lt;p&gt;当某头牛身上站着另一些牛时它就会在一定程度上被压扁，我们不妨把它被压扁的程度叫做它的压扁指数。对于任意的牛，她的压扁指数等于摞在她上面的所有奶牛的总重（当然不包括她自己）减去她的力量。奶牛们按照一定的顺序摞在一起后，她们的总压扁指数就是被压得最扁的那头奶牛的压扁指数。&lt;/p&gt;
&lt;p&gt;你的任务就是帮助奶牛们找出一个摞在一起的顺序，使得总压扁指数最小。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;每头牛都有自己的体重以及力量，编号为 $i$ 的奶牛的体重为 $W_i$，力量为 $S_i$。&lt;/p&gt;
&lt;p&gt;当某头牛身上站着另一些牛时它就会在一定程度上被压扁，我们不妨把它被压扁的程度叫做它的压扁指数。对于任意的牛，她的压扁指数等于摞在她上面的所有奶牛的总重（当然不包括她自己）减去她的力量。奶牛们按照一定的顺序摞在一起后， 她们的总压扁指数就是被压得最扁的那头奶牛的压扁指数。&lt;/p&gt;
&lt;p&gt;你的任务就是帮助奶牛们找出一个摞在一起的顺序，使得总压扁指数最小。&lt;/p&gt;
&lt;p&gt;第一行一个整数 $$N$$。&lt;/p&gt;
&lt;p&gt;接下来 $$N$$ 行，每行两个整数 $$W_i$$ 和 $$S_i$$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;一行一个整数表示最小总压扁指数。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入输出样例&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;3
10 3
2 5
3 3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;说明/提示&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;对于 $$100%$$ 的数据，$$1 \le N \le 5\times 10^4$$，$$1 \le W_i \le 10^4$$，$$1 \le S_i \le 10^9$$。&lt;/p&gt;
&lt;h3&gt;算法解析&lt;/h3&gt;
&lt;p&gt;本题的关键在于找到一个排序，使得每头牛受到的压扁指数尽可能小。一个常见贪心方法是按照 $$W_i+S_i$$ 升序排序：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;若牛 $$i$$ 在牛 $$j$$ 上方，则牛 $$j$$ 需要承受的总重量中包含 $$W_i$$；&lt;/li&gt;
&lt;li&gt;排序后，前面的牛更适合放在上层，因为它们相对较轻且力量较小；&lt;/li&gt;
&lt;li&gt;这样可以使得每一次累积的总重量与当前牛的力量差值更小。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;具体做法：先按照 $$W_i+S_i$$ 升序排序所有奶牛，然后依次将它们放到塔上，记录当前已放入的上层牛总重 $$t$$，并更新压扁指数为 $$t-S_i$$ 的最大值，最终得到最小的总压扁指数。&lt;/p&gt;
&lt;p&gt;这种排序+扫描的过程时间复杂度为 $$O(N \log N)$$。&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;n = int(input())
a = []
for i in range(n):
    w, s = map(int, input().split())
    a.append((w, s, w + s))
a.sort(key=lambda x: x[2])
res = int(-2e9)
t = 0
for i in range(n):
    res = max(res, t - a[i][1])
    t += a[i][0]
print(res)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include &amp;#x3C;iostream&gt;
#include &amp;#x3C;cstring&gt;
#include &amp;#x3C;algorithm&gt;
using namespace std;

const int N=50005;
struct node{
  int w,s;
  bool operator&amp;#x3C;(node &amp;#x26;t){
    return w+s&amp;#x3C;t.w+t.s;
  }
}a[N];

int main(){
  int n; cin&gt;&gt;n;
  for(int i=1; i&amp;#x3C;=n; i++)
    cin&gt;&gt;a[i].w&gt;&gt;a[i].s;
  sort(a+1,a+n+1);

  int res=-2e9, t=0;
  for(int i=1; i&amp;#x3C;=n; i++){
    res=max(res,t-a[i].s);
    t+=a[i].w;
  }
  cout&amp;#x3C;&amp;#x3C;res&amp;#x3C;&amp;#x3C;endl;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>A23 三分小数</title><link>https://ehnotgod.site/blog/a23-%E4%B8%89%E5%88%86%E5%B0%8F%E6%95%B0</link><guid isPermaLink="true">https://ehnotgod.site/blog/a23-%E4%B8%89%E5%88%86%E5%B0%8F%E6%95%B0</guid><description>三分模板（实数）：求单峰函数的极值。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P1883&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;给定 $$n$$ 个二次函数 $$f_1(x),f_2(x),\dots,f_n(x)$$（均形如 $$ax^2+bx+c$$），设 $$F(x)=\max{f_1(x),f_2(x),...,f_n(x)}$$，求 $$F(x)$$ 在区间 $$[0,1000]$$ 上的最小值。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输入第一行为正整数 $$T$$，表示有 $$T$$ 组数据。&lt;/p&gt;
&lt;p&gt;每组数据第一行一个正整数 $$n$$，接着 $$n$$ 行，每行 $$3$$ 个整数 $$a,b,c$$，用来表示每个二次函数的 $$3$$ 个系数，注意二次函数有可能退化成一次。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;每组数据输出一行，表示 $$F(x)$$ 的在区间 $$[0,1000]$$ 上的最小值。答案精确到小数点后四位，四舍五入。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入输出样例&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2
1
2 0 0
2
2 0 0
2 -4 2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;0.0000
0.5000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于 $$100%$$ 的数据，$$T&amp;#x3C;10$$，$$n\le 10^4$$，$$0\le a\le 100$$，$$|b| \le 5\times 10^3$$，$$|c| \le 5\times 10^3$$。&lt;/p&gt;
&lt;h3&gt;算法解析&lt;/h3&gt;
&lt;p&gt;本题求的是若干二次函数上包络线 $$F(x)=\max_i f_i(x)$$ 在区间 $$[0,1000]$$ 的最小值。&lt;/p&gt;
&lt;p&gt;对每个固定的 $$x$$，$$F(x)$$ 是若干二次函数值中的最大者，因此 $$F(x)$$ 本身是一个凸函数。我们可以在区间上使用三分法（Ternary Search）寻找最小值：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;保持区间 $$[l,r]$$，取两个三分点 $$m_1=(2l+r)/3$$ 和 $$m_2=(l+2r)/3$$；&lt;/li&gt;
&lt;li&gt;计算 $$F(m_1)$$ 和 $$F(m_2)$$；&lt;/li&gt;
&lt;li&gt;若 $$F(m_1) &amp;#x3C; F(m_2)$$，说明最小值在左半区间，令 $$r=m_2$$；否则令 $$l=m_1$$；&lt;/li&gt;
&lt;li&gt;反复收窄区间直到区间足够小，然后取当前点近似最小值。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;注意本题的函数是浮点连续的，使用三分法可以在精度范围内快速逼近最优解。&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import sys
import math

INF = float(&apos;inf&apos;)
EPS = 1e-9

def f(x, n, a, b, c):
    maxn = -INF
    for i in range(n):
        val = a[i] * x * x + b[i] * x + c[i]
        maxn = max(maxn, val)
    return maxn

t = int(input())
for _ in range(t):
    n = int(input())
    a = []
    b = []
    c = []
    for _ in range(n):
        ai, bi, ci = map(int, input().split())
        a.append(ai)
        b.append(bi)
        c.append(ci)

    l = 0.0
    r = 1000.0
    while r - l &gt; EPS:
        m1 = (2 * l + r) / 3
        m2 = (l + 2 * r) / 3
        if f(m1, n, a, b, c) &amp;#x3C; f(m2, n, a, b, c):
            r = m2
        else:
            l = m1
    print(f&quot;{f(l, n, a, b, c):.4f}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include&amp;#x3C;bits/stdc++.h&gt;
using namespace std;

const int N=114514,inf=INT_MAX;
const double eps=1e-9;

int n;
int a[N],b[N],c[N];
inline double f(double x){
	double maxn=-inf;
	for(int i=1;i&amp;#x3C;=n;i++){
		maxn=max(maxn,a[i]*x*x+b[i]*x+c[i]);
	}
	return maxn;
}

signed main() {
	int t;
	cin&gt;&gt;t;
	while(t--){
		cin&gt;&gt;n;
		for(int i=1;i&amp;#x3C;=n;i++){
			cin&gt;&gt;a[i]&gt;&gt;b[i]&gt;&gt;c[i];
		}

		double l=0,r=1000;
		while(r-l&gt;eps){
			double m1=(2*l+r)/3;
			double m2=(l+2*r)/3;
			if(f(m1)&amp;#x3C;f(m2)){
				r=m2;
			}else{
				l=m1;
			}
		}

		cout&amp;#x3C;&amp;#x3C;fixed&amp;#x3C;&amp;#x3C;setprecision(4)&amp;#x3C;&amp;#x3C;f(l)&amp;#x3C;&amp;#x3C;endl;
	}
	return 0;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>A24 三分整数</title><link>https://ehnotgod.site/blog/a24-%E4%B8%89%E5%88%86%E6%95%B4%E6%95%B0</link><guid isPermaLink="true">https://ehnotgod.site/blog/a24-%E4%B8%89%E5%88%86%E6%95%B4%E6%95%B0</guid><description>三分模板（整数）：求单峰函数的极值。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;暂无题目链接，本题为后续自行补充的，使用极其广泛，正确性已验证&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;定义含绝对值的一元一次函数 $$f(x)=|kx+a|+b$$ ($x\in \mathbb{Z}$)，其中 $$k$$ 为由外部给定的正整数，$$a,b$$ 为整数。&lt;/p&gt;
&lt;p&gt;现在，对于给定的 $$n$$ 个含绝对值的一元一次函数，定义&lt;/p&gt;
&lt;p&gt;$$F(x)=f_1(x)+f_2(x)+\cdots+f_n(x)，$$&lt;/p&gt;
&lt;p&gt;求解 $$F(x)$$ 在区间 $$[l,r]$$ 上的最小值。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;每个测试文件包含多组测试数据。第一行输入一个整数 $$T$$ ($1\le T\le 5\times 10^5$) 代表数据组数。每组测试数据描述如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;第一行输入三个整数 $$n,l,r$$，其中 $$1\le n\le 5\times 10^5$$，$$-10^7\le l\le r\le 10^7$$，表示函数个数和值域；&lt;/li&gt;
&lt;li&gt;接下来 $$n$$ 行，每行输入三个整数 $$k_i,a_i,b_i$$，用于描述第 $$i$$ 个函数 $$f_i(x)=|k_ix+a_i|+b_i$$，满足 $$0\le k_i,|a_i|,|b_i|\le 10^6$$。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;此外，保证所有测试数据中 $$n$$ 的总和不超过 $$5\times 10^5$$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;对于每组测试数据，在一行输出一个整数，表示 $$F(x)$$ 在区间 $$[l,r]$$ 上的最小值。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入输出样例&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;3
4 -15 20
1 -11 2
2 -2 0
1 20 -20
0 0 -10
3 10 100000
1 -11 -4
2 5 -10
1 -13 -10
3 12 100000
1 -11 -4
2 5 -10
1 -13 -10
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;3
5
7
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;算法解析&lt;/h3&gt;
&lt;p&gt;每个函数 $$f_i(x)=|k_ix+a_i|+b_i$$ 是一个凸的分段线性函数，若将它们相加得到的 $$F(x)$$ 依然是一个凸的分段线性函数。因为 $$x$$ 要求取整数，所以可以在整数区间上使用离散的三分法求最小值。&lt;/p&gt;
&lt;p&gt;具体做法：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;计算当前区间 $$[l,r]$$ 的两个三分点 $$m_1=l+(r-l)//3$$ 和 $$m_2=r-(r-l)//3$$；&lt;/li&gt;
&lt;li&gt;分别计算 $$F(m_1)$$ 和 $$F(m_2)$$；&lt;/li&gt;
&lt;li&gt;若 $$F(m_1)\le F(m_2)$$，则最小值可能在左半区间，令 $$r=m_2-1$$；否则令 $$l=m_1+1$$；&lt;/li&gt;
&lt;li&gt;重复上述过程，直到区间缩小到常数范围，再直接枚举区间端点取最小值。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;由于每个测试数据组的函数个数之和总计不超过 $$5\times 10^5$$，这个方法的总时间复杂度可以保证在可接受范围内。&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def get_sum(f, x):
    total = 0
    for k, a, b in f:
        total += abs(k * x + a) + b
    return total

 t = int(input())
for _ in range(t):
    n, l, r = map(int, input().split())
    f = [tuple(map(int, input().split())) for _ in range(n)]

    while l + 2 &amp;#x3C;= r:
        diff = (r - l) // 3
        mid1 = l + diff
        mid2 = r - diff
        sum1 = get_sum(f, mid1)
        sum2 = get_sum(f, mid2)
        if sum1 &amp;#x3C;= sum2:
            r = mid2 - 1
        else:
            l = mid1 + 1

    print(min(get_sum(f, l), get_sum(f, r)))
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include &amp;#x3C;iostream&gt;
#include &amp;#x3C;vector&gt;
using namespace std;

struct fun {
    int k, a, b;
};

long long getSum(vector&amp;#x3C;fun&gt;&amp;#x26; f, long long x) {
    long long ans = 0;
    for (fun&amp;#x26; fi : f) {
        ans += abs(fi.k * x + fi.a) + fi.b;
    }
    return ans;
}

int main() {
    int t, n, l, r;
    cin &gt;&gt; t;
    while (t--) {
        cin &gt;&gt; n &gt;&gt; l &gt;&gt; r;
        vector&amp;#x3C;fun&gt; f(n);
        for (int i = 0; i &amp;#x3C; n; ++i) {
            cin &gt;&gt; f[i].k &gt;&gt; f[i].a &gt;&gt; f[i].b;
        }
        while (l + 2 &amp;#x3C;= r) {
            int diff = (r - l) / 3;
            int mid1 = l + diff;
            int mid2 = r - diff;
            long long sum1 = getSum(f, mid1);
            long long sum2 = getSum(f, mid2);
            if (sum1 &amp;#x3C;= sum2) {
                r = mid2 - 1;
            } else {
                l = mid1 + 1;
            }
        }
        cout &amp;#x3C;&amp;#x3C; min(getSum(f, l), getSum(f, r)) &amp;#x3C;&amp;#x3C; endl;
    }
}
// 64 位输出请用 printf(&quot;%lld&quot;)
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>B1 DFS深搜</title><link>https://ehnotgod.site/blog/b1-dfs%E6%B7%B1%E6%90%9C</link><guid isPermaLink="true">https://ehnotgod.site/blog/b1-dfs%E6%B7%B1%E6%90%9C</guid><description>DFS 深度优先搜索：递归遍历所有状态。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P1219&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;一个如下的 $6 \times 6$ 的跳棋棋盘，有六个棋子被放置在棋盘上，使得每行、每列有且只有一个，每条对角线（包括两条主对角线的所有平行线）上至多有一个棋子。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/3h71x0yf.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;上面的布局可以用序列 $2\ 4\ 6\ 1\ 3\ 5$ 来描述，第 $i$ 个数字表示在第 $i$ 行的相应位置有一个棋子，如下：&lt;/p&gt;
&lt;p&gt;行号 $1\ 2\ 3\ 4\ 5\ 6$&lt;/p&gt;
&lt;p&gt;列号 $2\ 4\ 6\ 1\ 3\ 5$&lt;/p&gt;
&lt;p&gt;这只是棋子放置的一个解。请编一个程序找出所有棋子放置的解。&lt;br&gt;
并把它们以上面的序列方法输出，解按字典顺序排列。&lt;br&gt;
请输出前 $3$ 个解。最后一行是解的总个数。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;一行一个正整数 $n$，表示棋盘是 $n \times n$ 大小的。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;前三行为前三个解，每个解的两个数字之间用一个空格隔开。第四行只有一个数字，表示解的总数。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;6

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2 4 6 1 3 5
3 6 2 5 1 4
4 1 5 2 6 3
4

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;说明/提示&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;对于 $100%$ 的数据，$6 \le n \le 13$。&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;从第1行开始放，然后尝试放第2·n行。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;对于第i行，依次枚举第1·n列，如果第j列能放下，则记住位置，宣布占领该位置（i）的辐射区域，然后继续搜索第i+1行。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果第i+1行的n列均放不下，则退回第i行的状态空间，恢复现场，尝试第i行的下一列。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果能放满n行.说明找到了一种合法方案，则ans+1，打印方案，接着返回上一行，继续搜索另一个合法方案，直到搜完所有可能方案。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;因为是逐行逐列搜的，先搜到的字典序一定最小。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;images/B1-3.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# P1219 [USACO1.5] 八皇后 Checker Challenge

N = 30
pos = [0] * N
c = [0] * N
p = [0] * N
q = [0] * N
ans = 0
def pr():
    if ans &amp;#x3C;= 3:
        for i in range(1, n + 1):
            print(pos[i], end=&quot; &quot;)
        print()
def dfs(i):
    global ans
    if i &gt; n:
        ans += 1
        pr()
        return
    for j in range(1, n + 1):
        if c[j] or p[i + j] or q[i - j + n]:
            continue
        pos[i] = j
        c[j] = p[i + j] = q[i - j + n] = 1
        dfs(i + 1)
        c[j] = p[i + j] = q[i - j + n] = 0
n = int(input())
dfs(1)
print(ans)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include &amp;#x3C;iostream&gt;
#include &amp;#x3C;cstring&gt;
#include &amp;#x3C;algorithm&gt;
using namespace std;

const int N=30;
int n, ans;
int pos[N],c[N],p[N],q[N];

void print(){
  if(ans&amp;#x3C;=3){
    for(int i=1;i&amp;#x3C;=n;i++)
      printf(&quot;%d &quot;,pos[i]);
    puts(&quot;&quot;);
  }
}
void dfs(int i){
  if(i&gt;n){
    ans++; print(); return;
  }
  for(int j=1; j&amp;#x3C;=n; j++){
    if(c[j]||p[i+j]||q[i-j+n])continue;
    pos[i]=j; //记录第i行放在了第j列
    c[j]=p[i+j]=q[i-j+n]=1; //宣布占领
    dfs(i+1);
    c[j]=p[i+j]=q[i-j+n]=0; //恢复现场
  }
}
int main(){
  cin &gt;&gt; n;
  dfs(1);
  cout &amp;#x3C;&amp;#x3C; ans;
  return 0;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>B2 BFS广搜</title><link>https://ehnotgod.site/blog/b2-bfs%E5%B9%BF%E6%90%9C</link><guid isPermaLink="true">https://ehnotgod.site/blog/b2-bfs%E5%B9%BF%E6%90%9C</guid><description>BFS 广度优先搜索：队列逐层扩展求最短路。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P1588&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;FJ 丢了一头牛，决定将其找回。FJ 和牛位于数轴上，初始位置分别为 $x$ 和 $y$，牛保持不动。每次移动时，若 FJ 处于位置 $x$，他可移动至 $x+1$、$x-1$ 或 $2x$。计算 FJ 抓住牛所需的最少移动次数。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行为一个整数 $T$，表示数据组数；接下来每行包含两个正整数 $x,y$，分别表示 FJ 和牛的坐标。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;对于每组数据，输出最少步数，每组数据间用换行隔开。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1
5 17
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;说明/提示&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;对于 $100%$ 的数据，$0 \le x,y \le 10^5$。&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;把数轴上的每个位置看作图上的一个点，从起点 $x$ 出发，每次可移动到 $x+1$、$x-1$ 或 $2x$，用 BFS 逐层向外扩展。BFS 第一次访问到终点 $y$ 时，经过的层数就是最少步数——每一层都代表当前步数内的所有可达位置，先到即最优。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/B2-2.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# P1588 [USACO07OPEN] Catch That Cow S

def bfs():
    global x, y
    N = int(2e5 + 1)
    dis = [-1 for i in range(N)]
    dis[x] = 0
    l = 0
    q = []; q.append(x)
    while l &amp;#x3C; len(q):
        x = q[l]
        l += 1
        if x + 1 &amp;#x3C; N and dis[x + 1] == -1:
            dis[x + 1] = dis[x] + 1
            q.append(x + 1)
        if x - 1 &gt; 0 and dis[x - 1] == -1:
            dis[x - 1] = dis[x] + 1
            q.append(x - 1)
        if 2 * x &amp;#x3C; N and dis[x * 2] == -1:
            dis[x * 2] = dis[x] + 1
            q.append(x * 2)
        if x == y:
            print(dis[y])
            return

t = int(input())
for i in range(t):
    x, y = map(int, input().split())
    bfs()
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include &amp;#x3C;cstring&gt;
#include &amp;#x3C;iostream&gt;
#include &amp;#x3C;algorithm&gt;
#include &amp;#x3C;queue&gt;
using namespace std;

const int N=100005;
int x, y, dis[N];

void bfs(){
  memset(dis,-1,sizeof dis); dis[x]=0;
  queue&amp;#x3C;int&gt; q; q.push(x);
  while(q.size()){
    int x=q.front(); q.pop();
    if(x+1&amp;#x3C;N &amp;#x26;&amp;#x26; dis[x+1]==-1){
      dis[x+1]=dis[x]+1; //前进一步
      q.push(x+1);
    }
    if(x-1&gt;0 &amp;#x26;&amp;#x26; dis[x-1]==-1){
      dis[x-1]=dis[x]+1; //后退一步
      q.push(x-1);
    }
    if(2*x&amp;#x3C;N &amp;#x26;&amp;#x26; dis[2*x]==-1){
      dis[2*x]=dis[x]+1; //走到2x位置
      q.push(2*x);
    }
    if(x==y){printf(&quot;%d\n&quot;,dis[y]);return;}
  }
}
int main(){
  int T; cin&gt;&gt;T;
  while(T--) cin&gt;&gt;x&gt;&gt;y, bfs();
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>B3 01BFS</title><link>https://ehnotgod.site/blog/b3-01bfs</link><guid isPermaLink="true">https://ehnotgod.site/blog/b3-01bfs</guid><description>01BFS：边权为 0/1 的最短路，用双端队列优化。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://ac.nowcoder.com/acm/contest/115184/D&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/B3-1.png&quot; alt=&quot;image-20250930163432863&quot;&gt;&lt;/p&gt;
&lt;p&gt;输入&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1
3 3 2
RLD
UDU
UUL
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;YES
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;把网格中的每个格子看成图上的一个点，从当前格子出发可以向上下左右移动：若移动方向与格子上的字符（R/L/U/D）一致，边权为 0；否则边权为 1。求从起点 $(0,0)$ 走到终点 $(n-1,m-1)$ 的最少改变方向次数，若不超过 $k$ 则可行。&lt;/p&gt;
&lt;p&gt;边权只有 0 和 1，用 01BFS 优化：边权为 0 的边从队头插入，边权为 1 的边从队尾插入，队列始终按距离递增排列，每个点第一次出队时即得到最短路。相当于把 Dijkstra 的优先队列换成 deque，省掉一个 log。&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from collections import deque

t = int(input())
for _ in range(t):
    n, m, k = map(int, input().split())
    e = [[] for _ in range(n * m)]
    for i in range(n):
        s = input()
        for j in range(m):
            idx = i * m + j
            if i &gt; 0:
                if s[j] == &apos;U&apos;:
                    e[idx].append((idx - m, 0))
                else:
                    e[idx].append((idx - m, 1))
            if j &gt; 0:
                if s[j] == &apos;L&apos;:
                    e[idx].append((idx - 1, 0))
                else:
                    e[idx].append((idx - 1, 1))
            if i &amp;#x3C; n - 1:
                if s[j] == &apos;D&apos;:
                    e[idx].append((idx + m, 0))
                else:
                    e[idx].append((idx + m, 1))
            if j &amp;#x3C; m - 1:
                if s[j] == &apos;R&apos;:
                    e[idx].append((idx + 1, 0))
                else:
                    e[idx].append((idx + 1, 1))

    q = deque()
    vis = [-1] * (n * m)
    q.append((0, 0))
    while q:
        u, dis = q.popleft()
        if vis[u] != -1:
            continue
        vis[u] = dis
        for v, le in e[u]:
            if vis[v] == -1:
                if le == 0:
                    q.appendleft((v, dis))
                else:
                    q.append((v, dis + 1))

    if vis[n * m - 1] &gt; k:
        print(&quot;NO&quot;)
    else:
        print(&quot;YES&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include &amp;#x3C;bits/stdc++.h&gt;
using namespace std;
#define endl &quot;\n&quot;
#define int long long
#define range(i, a, b) for (int i = (a); i &amp;#x3C; (b); ++i)
#define def(name, ...) auto name = [&amp;#x26;](__VA_ARGS__)
vector&amp;#x3C;vector&amp;#x3C;pair&amp;#x3C;int, int&gt;&gt;&gt; e;
// vector&amp;#x3C;int&gt; a(n, 0);
// vector&amp;#x3C;vector&amp;#x3C;int&gt;&gt; dp(n + 1, vector&amp;#x3C;int&gt;(n + 1, 0));
void solve() {
	int n, m, k; cin &gt;&gt; n &gt;&gt; m &gt;&gt; k;
    e.assign(n * m, vector&amp;#x3C;pair&amp;#x3C;int, int&gt;&gt;());
    range(i, 0, n){
        string s; cin &gt;&gt; s;
        range(j, 0, m){
            int idx = i * m + j;
            if (i &gt; 0){
                if (s[j] == &apos;U&apos;)e[idx].push_back({idx - m, 0});
                else e[idx].push_back({idx - m, 1});
            }
            if (j &gt; 0){
                if (s[j] == &apos;L&apos;)e[idx].push_back({idx - 1, 0});
                else e[idx].push_back({idx - 1, 1});
            }
            if (i &amp;#x3C; n - 1){
                if (s[j] == &apos;D&apos;)e[idx].push_back({idx + m, 0});
                else e[idx].push_back({idx + m, 1});
            }
            if (j &amp;#x3C; m - 1){
                if (s[j] == &apos;R&apos;)e[idx].push_back({idx + 1, 0});
                else e[idx].push_back({idx + 1, 1});
            }
        }
    }
    deque&amp;#x3C;pair&amp;#x3C;int, int&gt;&gt; q;
    vector&amp;#x3C;int&gt; vis(n * m, -1);
    q.push_back({0, 0});
    while (q.size()){
        auto [u, dis] = q.front();
        q.pop_front();
        if (vis[u] != -1) continue;
        vis[u] = dis;
        for (auto[v, le]: e[u]){
            if (vis[v] == -1){
                if (le == 0){
                    q.push_front({v, dis});
                }
                else {
                    q.push_back({v, dis + 1});
                }
            }
        }
    }
    if (vis[n*m-1] &gt; k){
        cout &amp;#x3C;&amp;#x3C; &quot;NO&quot; &amp;#x3C;&amp;#x3C; endl;
    }
    else {
        cout &amp;#x3C;&amp;#x3C; &quot;YES&quot; &amp;#x3C;&amp;#x3C; endl;
    }
}

signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
	int t;
	cin &gt;&gt; t;
	while (t--) {
		solve();
	}
	return 0;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>C1 树状数组-点修区查</title><link>https://ehnotgod.site/blog/c1-%E6%A0%91%E7%8A%B6%E6%95%B0%E7%BB%84-%E7%82%B9%E4%BF%AE%E5%8C%BA%E6%9F%A5</link><guid isPermaLink="true">https://ehnotgod.site/blog/c1-%E6%A0%91%E7%8A%B6%E6%95%B0%E7%BB%84-%E7%82%B9%E4%BF%AE%E5%8C%BA%E6%9F%A5</guid><description>树状数组模板：单点修改 + 区间求和。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;如题，已知一个数列，你需要进行下面两种操作：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;将某一个数加上 $$x$$；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;求出某区间每一个数的和。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行包含两个正整数 $$n,m$$，分别表示该数列数字的个数和操作的总个数。&lt;/p&gt;
&lt;p&gt;第二行包含 $$n$$ 个用空格分隔的整数，其中第 $$i$$ 个数字表示数列第 $$i$$ 项的初始值。&lt;/p&gt;
&lt;p&gt;接下来 $$m$$ 行每行包含 $$3$$ 个整数，表示一个操作，具体如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;1 x k&lt;/code&gt;  含义：将第 $$x$$ 个数加上 $$k$$；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;2 x y&lt;/code&gt;  含义：输出区间 $$[x,y]$$ 内每个数的和。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输出包含若干行整数，即为所有操作 $$2$$ 的结果。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;5 5
1 5 4 2 3
1 1 3
2 2 5
1 3 -1
1 4 2
2 1 4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;14
16
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;【数据范围】对于 $$100%$$ 的数据，$$1\le n,m \le 5\times 10^5$$。&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;树状数组（Fenwick Tree）用 $O(\log n)$ 维护&quot;单点修改 + 区间求和&quot;。核心是 $lowbit(x) = x\ &amp;#x26;\ (-x)$，它取 $x$ 二进制最低位的 $1$ 对应的值：&lt;code&gt;change&lt;/code&gt; 修改时沿 $x + lowbit(x)$ 向上传播，&lt;code&gt;query&lt;/code&gt; 查询时沿 $x - lowbit(x)$ 向下累加前缀和，区间 $[l,r]$ 的和就是 $query(r)-query(l-1)$。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/C1-1.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import sys
input = sys.stdin.readline

class BIT:
    def __init__(self, n):
        self.n = n
        self.s = [0] * (n + 1)

    def change(self, x, w):
        while x &amp;#x3C;= self.n:
            self.s[x] += w
            x += x &amp;#x26; -x

    def query(self, x):
        res = 0
        while x &gt; 0:
            res += self.s[x]
            x -= x &amp;#x26; -x
        return res

if __name__ == &quot;__main__&quot;:
    n, m = map(int, input().split())
    T = BIT(n)

    a = list(map(int, input().split()))
    for i, k in enumerate(a, 1):  # 下标从1开始
        T.change(i, k)

    for _ in range(m):
        op, *rest = map(int, input().split())
        if op == 1:  # 单点修改
            x, k = rest
            T.change(x, k)
        else:        # 区间查询
            x, y = rest
            print(T.query(y) - T.query(x - 1))
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import sys
input = sys.stdin.readline
sys.setrecursionlimit(10 ** 6)

class Node():
    def __init__(self, l, r, he):
        self.l = l
        self.r = r
        self.he = he

def build(p, l, r):
    tr[p] = Node(l, r, 0)
    if l == r:
        tr[p].he = w[l]
        return
    m = (l + r) // 2
    build(p * 2, l, m)
    build(p * 2 + 1, m + 1, r)
    tr[p].he = tr[p * 2].he + tr[p * 2 + 1].he

def update(p, idx, k):
    if tr[p].l == idx and tr[p].r == idx:
        tr[p].he += k
        return
    m = (tr[p].l + tr[p].r) // 2
    if idx &amp;#x3C;= m:
        update(p * 2, idx, k)
    if idx &gt; m:
        update(p * 2 + 1, idx, k)
    tr[p].he = tr[p * 2].he + tr[p * 2 + 1].he

def query(p, l, r):
    if tr[p].l &gt;= l and tr[p].r &amp;#x3C;= r:
        return tr[p].he
    m = (tr[p].l + tr[p].r) // 2
    he = 0
    if l &amp;#x3C;= m:
        he += query(p * 2, l, r)
    if r &gt; m:
        he += query(p * 2 + 1, l, r)
    return he

n, m = map(int, input().split())
w = list(map(int, input().split()))
N = n * 4 + 1
tr = [Node(0, 0, 0) for _ in range(N * 4)]
build(1, 0, n - 1)
for i in range(m):
    tmp = list(map(int, input().split()))
    if tmp[0] == 2:
        print(query(1, tmp[1] - 1, tmp[2] - 1))
    else:
        update(1, tmp[1] - 1, tmp[2])
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include &amp;#x3C;bits/stdc++.h&gt;
using namespace std;
const int N = 500010;
int n, m;
struct BIT {
    vector&amp;#x3C;int&gt; s; // 用 vector 更灵活
    BIT(int n = 0) { s.assign(n + 1, 0); }
    void change(int x, int w) {
        for (; x &amp;#x3C; (int)s.size(); x += x &amp;#x26; -x) s[x] += w;
    }
    int query(int x) {
        int res = 0;
        for (; x; x -= x &amp;#x26; -x) res += s[x];
        return res;
    }
    int kth(int k) {
        int pos = 0;
        int sum = 0;
        int step = 1;
        while ((step &amp;#x3C;&amp;#x3C; 1) &amp;#x3C; (int)s.size())
            step &amp;#x3C;&amp;#x3C;= 1;
        for (; step; step &gt;&gt;= 1) {
            int nxt = pos + step;
            if (nxt &amp;#x3C; (int)s.size() &amp;#x26;&amp;#x26; sum + s[nxt] &amp;#x3C; k) {
                pos = nxt;
                sum += s[nxt];
            }
        }
        return pos + 1;
    }
};
int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin &gt;&gt; n &gt;&gt; m;
    BIT T(n);  // 局部创建，大小为 n
    int op, x, y, k;
    for (int i = 1; i &amp;#x3C;= n; i++) {
        cin &gt;&gt; k;
        T.change(i, k);
    }
    for (int i = 1; i &amp;#x3C;= m; i++) {
        cin &gt;&gt; op &gt;&gt; x;
        if (op == 1) {
            cin &gt;&gt; k;
            T.change(x, k);
        } else {
            cin &gt;&gt; y;
            cout &amp;#x3C;&amp;#x3C; T.query(y) - T.query(x - 1) &amp;#x3C;&amp;#x3C; &quot;\n&quot;;
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上文的kth其实可以不用，那个是树状数组上二分的过程。&lt;/p&gt;
&lt;p&gt;它返回：最小的 pos，满足 query(pos) &gt;= k。&lt;/p&gt;
&lt;p&gt;另外，有小线段树：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;//洛谷P3374
#include&amp;#x3C;bits/stdc++.h&gt;
using namespace std;
#define endl &quot;\n&quot;
#define range(i, a, b) for (int i = (a); i &amp;#x3C; (b); ++i)
#define lc (p&amp;#x3C;&amp;#x3C;1)
#define rc (p&amp;#x3C;&amp;#x3C;1|1)
#define N 500005
int n, w[N];
struct node {
    int l, r, sum;
} tr[N * 4];

void build(int p, int l, int r) {
    tr[p].l = l;
    tr[p].r = r;
    tr[p].sum = w[l];
    if (l == r) return;
    int m = (l + r) / 2;
    build(lc, l, m);
    build(rc, m + 1, r);
    tr[p].sum = tr[lc].sum + tr[rc].sum;
}

// 点修改（从根递归进入）
void update(int p, int x, int k) {
    if (tr[p].l == x &amp;#x26;&amp;#x26; tr[p].r == x) {
        tr[p].sum += k;
        return;
    }
    int m = (tr[p].l + tr[p].r) / 2;
    if (x &amp;#x3C;= m) update(lc, x, k);
    else update(rc, x, k);
    tr[p].sum = tr[lc].sum + tr[rc].sum;
}

int query(int p, int x, int y) {
    if (x &amp;#x3C;= tr[p].l &amp;#x26;&amp;#x26; tr[p].r &amp;#x3C;= y) return tr[p].sum;
    int m = (tr[p].l + tr[p].r) / 2;
    int sum = 0;
    if (x &amp;#x3C;= m) sum += query(lc, x, y);
    if (y &gt; m)  sum += query(rc, x, y);
    return sum;
}
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int n, m;
    cin &gt;&gt; n &gt;&gt; m;
    range(i, 0, n){
        cin &gt;&gt; w[i + 1];
    }
    build(1, 1, n);
    range(i, 0, m){
        int op; cin &gt;&gt; op;
        int x, y, k;
        if (op == 1){
            cin &gt;&gt; x &gt;&gt; k;  // 单点更新
            update(1, x, k);
        }
        else{
            cin &gt;&gt; x &gt;&gt; y;  // 区间查询
            cout &amp;#x3C;&amp;#x3C; query(1, x, y) &amp;#x3C;&amp;#x3C; endl;
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>C2 树状数组-区修点查</title><link>https://ehnotgod.site/blog/c2-%E6%A0%91%E7%8A%B6%E6%95%B0%E7%BB%84-%E5%8C%BA%E4%BF%AE%E7%82%B9%E6%9F%A5</link><guid isPermaLink="true">https://ehnotgod.site/blog/c2-%E6%A0%91%E7%8A%B6%E6%95%B0%E7%BB%84-%E5%8C%BA%E4%BF%AE%E7%82%B9%E6%9F%A5</guid><description>树状数组进阶：区间修改 + 单点查询（差分）。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P3368&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;如题，已知一个数列，你需要进行下面两种操作：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;将某区间每一个数加上 $$x$$；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;求出某一个数的值。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行包含两个整数 $$N$$、$$M$$，分别表示该数列数字的个数和操作的总个数。&lt;/p&gt;
&lt;p&gt;第二行包含 $$N$$ 个用空格分隔的整数，其中第 $$i$$ 个数字表示数列第 $$i $$ 项的初始值。&lt;/p&gt;
&lt;p&gt;接下来 $$M$$ 行每行包含 $$2$$ 或 $$4$$ 个整数，表示一个操作，具体如下：&lt;/p&gt;
&lt;p&gt;操作 $$1$$： 格式：&lt;code&gt;1 x y k&lt;/code&gt; 含义：将区间 $$[x,y]$$ 内每个数加上 $$k$$；&lt;/p&gt;
&lt;p&gt;操作 $$2$$： 格式：&lt;code&gt;2 x&lt;/code&gt; 含义：输出第 $$x$$ 个数的值。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输出包含若干行整数，即为所有操作 $$2$$ 的结果。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;5 5
1 5 4 2 3
1 2 4 2
2 3
1 1 5 -1
1 3 5 7
2 4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;6
10
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于 $$100%$$ 的数据：$$1 \leq N, M\le 500000$$，$$1 \leq x, y \leq n$$，保证任意时刻序列中任意元素的绝对值都不大于 $$2^{30}$$。&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;区间修改、单点查询可以用树状数组维护差分数组 $d$：$d_1=a_1$，$d_i=a_i-a_{i-1}$。对原数组区间 $[l,r]$ 加上 $k$，等价于在差分数组上做两个单点修改 $d_l += k$、$d_{r+1} -= k$；查询第 $x$ 个数的值就是差分数组的前缀和 $a_x=\sum_{i=1}^{x}d_i$。树状数组恰好支持 $O(\log n)$ 的单点修改和前缀和查询，所以总复杂度为 $O((n+m)\log n)$。&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import sys
input = sys.stdin.readline

class BIT:
    def __init__(self, n):
        self.n = n
        self.s = [0] * (n + 1)

    def change(self, x, w):
        while x &amp;#x3C;= self.n:
            self.s[x] += w
            x += x &amp;#x26; -x

    def query(self, x):
        res = 0
        while x &gt; 0:
            res += self.s[x]
            x -= x &amp;#x26; -x
        return res

if __name__ == &quot;__main__&quot;:
    n, m = map(int, input().split())
    T = BIT(n)

    a = list(map(int, input().split()))
    # 用差分数组初始化：d[1]=a1, d[i]=a[i]-a[i-1]
    prev = 0
    for i, val in enumerate(a, 1):  # 下标从1开始
        d = val - prev
        T.change(i, d)
        prev = val

    for _ in range(m):
        op, *rest = map(int, input().split())
        if op == 1:  # 区间修改： op=1 l r k
            l, r, k = rest
            T.change(l, k)
            if r + 1 &amp;#x3C;= n:
                T.change(r + 1, -k)
        else:        # 点查询： op=2 x
            x = rest[0]
            print(T.query(x))
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;// 树状数组 区修+点查 O(nlogn)
#include&amp;#x3C;bits/stdc++.h&gt;
using namespace std;

const int N=500010;
int n,m,a[N];

struct BIT{
  int s[N]; //差分的区间和
  void change(int x,int w){
    for(;x&amp;#x3C;=n;x+=x&amp;#x26;-x) s[x]+=w;
  }
  int query(int x){
    int res=0;
    for(;x;x-=x&amp;#x26;-x) res+=s[x];
    return res;
  }
}T;
int main(){
  ios::sync_with_stdio(0);
  cin&gt;&gt;n&gt;&gt;m; int op,x,y,k;
  for(int i=1;i&amp;#x3C;=n;i++) cin&gt;&gt;a[i];
  for(int i=1;i&amp;#x3C;=m;i++){
    cin&gt;&gt;op&gt;&gt;x;
    if(op==1){
      cin&gt;&gt;y&gt;&gt;k;
      T.change(x,k);
      T.change(y+1,-k); //差分
    }
    else cout&amp;#x3C;&amp;#x3C;T.query(x)+a[x]&amp;#x3C;&amp;#x3C;&quot;\n&quot;;
  }
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>C3 并查集</title><link>https://ehnotgod.site/blog/c3-%E5%B9%B6%E6%9F%A5%E9%9B%86</link><guid isPermaLink="true">https://ehnotgod.site/blog/c3-%E5%B9%B6%E6%9F%A5%E9%9B%86</guid><description>并查集模板：维护集合的合并与查询。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P3367&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;如题，现在有一个并查集，你需要完成合并和查询操作。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行包含两个整数 $$N,M$$ ,表示共有 $$N$$ 个元素和 $$M$$ 个操作。&lt;/p&gt;
&lt;p&gt;接下来 $$M$$ 行，每行包含三个整数 $$Z_i,X_i,Y_i$$ 。&lt;/p&gt;
&lt;p&gt;当 $$Z_i=1$$ 时，将 $$X_i$$ 与 $$Y_i$$ 所在的集合合并。&lt;/p&gt;
&lt;p&gt;当 $$Z_i=2$$ 时，输出 $$X_i$$ 与 $$Y_i$$ 是否在同一集合内，是的输出
&lt;code&gt;Y&lt;/code&gt; ；否则输出 &lt;code&gt;N&lt;/code&gt; 。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;对于每一个 $$Z_i=2$$ 的操作，都有一行输出，每行包含一个大写字母，为 &lt;code&gt;Y&lt;/code&gt; 或者 &lt;code&gt;N&lt;/code&gt; 。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;4 7
2 1 2
1 1 2
2 1 2
1 3 4
2 1 4
1 2 3
2 1 4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;N
Y
N
Y
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于 $$100%$$ 的数据，$$1\le N\le 2\times 10^5$$，$$1\le M\le 10^6$$，$$1 \le X_i, Y_i \le N$$，$$Z_i \in { 1, 2 }$$。&lt;/p&gt;
&lt;h3&gt;算法解析&lt;/h3&gt;
&lt;p&gt;一般我们使用“合并”即可，按秩合并不太需要&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/C3-1.png&quot; alt=&quot;image-20250417165010912&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/C3-2.png&quot; alt=&quot;image-20250417165040782&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/C3-3.png&quot; alt=&quot;image-20250417165059357&quot;&gt;&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;n, m = map(int, input().split())
pa = [i for i in range(n + 1)]
def find(x):
    if pa[x] == x:
        return x
    pa[x] = find(pa[x])
    return pa[x]
def union(x, y):
    pa[find(x)] = find(y)

for _ in range(m):
    z, x, y = map(int, input().split())
    if z == 1:
        union(x, y)
    else:
        if find(x) == find(y):
            print(&quot;Y&quot;)
        else:
            print(&quot;N&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;//并查集 路径压缩
#include &amp;#x3C;iostream&gt;
#include &amp;#x3C;cstring&gt;
#include &amp;#x3C;algorithm&gt;
using namespace std;

const int N=10005;
int n,m,x,y,z;
int pa[N];

int find(int x){
  if(pa[x]==x) return x;
  return pa[x]=find(pa[x]);
}
void unset(int x,int y){
  pa[find(x)]=find(y);
}
int main(){
  cin&gt;&gt;n&gt;&gt;m;
  for(int i=1;i&amp;#x3C;=n;i++) pa[i]=i;
  while(m --){
    cin&gt;&gt;z&gt;&gt;x&gt;&gt;y;
    if(z==1) unset(x,y);
    else{
      if(find(x)==find(y)) puts(&quot;Y&quot;);
      else puts(&quot;N&quot;);
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>C4 线段树</title><link>https://ehnotgod.site/blog/c4-%E7%BA%BF%E6%AE%B5%E6%A0%91</link><guid isPermaLink="true">https://ehnotgod.site/blog/c4-%E7%BA%BF%E6%AE%B5%E6%A0%91</guid><description>线段树模板：区间修改 + 区间查询（加法）。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P3372&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;如题，已知一个数列 $${a_i}$$，你需要进行下面两种操作：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;将某区间每一个数加上 $$k$$。&lt;/li&gt;
&lt;li&gt;求出某区间每一个数的和。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行包含两个整数 $$n, m$$，分别表示该数列数字的个数和操作的总个数。&lt;/p&gt;
&lt;p&gt;第二行包含 $$n$$ 个用空格分隔的整数 $$a_i$$，其中第 $$i$$ 个数字表示数列第 $$i$$ 项的初始值。&lt;/p&gt;
&lt;p&gt;接下来 $$m$$ 行每行包含 $$3$$ 或 $$4$$ 个整数，表示一个操作，具体如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;1 x y k&lt;/code&gt;：将区间 $$[x, y]$$ 内每个数加上 $$k$$。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;2 x y&lt;/code&gt;：输出区间 $$[x, y]$$ 内每个数的和。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输出包含若干行整数，即为所有操作 2 的结果。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;5 5
1 5 4 2 3
2 2 4
1 2 3 2
2 3 4
1 1 5 1
2 1 4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;11
8
20
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于 $$100%$$ 的数据：$$1 \le n, m \le {10}^5$$，$$a_i,k$$ 为正数，且任意时刻数列的和不超过 $$2\times 10^{18}$$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;【样例解释】&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/2251.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3&gt;算法解析&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;images/C4-2.png&quot; alt=&quot;image-20250417171455028&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/C4-3.png&quot; alt=&quot;image-20250417171654545&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/C4-4.png&quot; alt=&quot;image-20250417171714474&quot;&gt;&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class Node:
    def __init__(self, l, r, he, add):
        self.l = l
        self.r = r
        self.he = he
        self.add = add
N = 100005
tr = [Node(0, 0, 0, 0) for i in range(N * 4)]
def pushdown(p):
    lc = 2 * p
    rc = 2 * p + 1
    if tr[p].add:
        tr[lc].he += tr[p].add * (tr[lc].r - tr[lc].l + 1)
        tr[rc].he += tr[p].add * (tr[rc].r - tr[rc].l + 1)
        tr[lc].add += tr[p].add
        tr[rc].add += tr[p].add
        tr[p].add = 0
def pushup(p):
    tr[p].he = tr[p * 2].he + tr[p * 2 + 1].he
def build(p, l, r):
    tr[p] = Node(l, r, w[l], 0)
    if l == r:
        return
    m = (l + r) // 2
    build(p * 2, l, m)
    build(p * 2 + 1, m + 1, r)
    pushup(p)
def update(p, x, y, k):
    if x &amp;#x3C;= tr[p].l and tr[p].r &amp;#x3C;= y:
        tr[p].he += (tr[p].r - tr[p].l + 1) * k
        tr[p].add += k
        return
    m = (tr[p].l + tr[p].r) // 2
    pushdown(p)
    if x &amp;#x3C;= m:
        update(p * 2, x, y, k)
    if y &gt; m:
        update(p * 2 + 1, x, y, k)
    pushup(p)
def query(p, x, y):
    if x &amp;#x3C;= tr[p].l and tr[p].r &amp;#x3C;= y:
        return tr[p].he
    m = (tr[p].l + tr[p].r) // 2
    pushdown(p)
    he = 0
    if x &amp;#x3C;= m:
        he += query(p * 2, x, y)
    if y &gt; m:
        he += query(p * 2 + 1, x, y)
    return he
n, m = map(int, input().split())
w = list(map(int, input().split()))
w.insert(0, 0)
build(1, 1, n)
for i in range(m):
    ru = list(map(int, input().split()))
    if ru[0] == 1:
        update(1, ru[1], ru[2], ru[3])
    else:
        print(query(1, ru[1], ru[2]))
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;// 结构体版
#include &amp;#x3C;cstring&gt;
#include &amp;#x3C;iostream&gt;
#include &amp;#x3C;algorithm&gt;
using namespace std;

#define N 100005
#define LL long long
#define lc u&amp;#x3C;&amp;#x3C;1
#define rc u&amp;#x3C;&amp;#x3C;1|1
LL w[N];
struct Tree{ //线段树
  LL l,r,sum,add;
}tr[N*4];

void pushup(LL u){ //上传
  tr[u].sum=tr[lc].sum+tr[rc].sum;
}
void pushdown(LL u){ //下传
  if(tr[u].add){
    tr[lc].sum+=tr[u].add*(tr[lc].r-tr[lc].l+1),
    tr[rc].sum+=tr[u].add*(tr[rc].r-tr[rc].l+1),
    tr[lc].add+=tr[u].add,
    tr[rc].add+=tr[u].add,
    tr[u].add=0;
  }
}
void build(LL u,LL l,LL r){ //建树
  tr[u]={l,r,w[l],0};
  if(l==r) return;
  LL m=l+r&gt;&gt;1;
  build(lc,l,m);
  build(rc,m+1,r);
  pushup(u);
}
void change(LL u,LL l,LL r,LL k){ //区修
  if(l&amp;#x3C;=tr[u].l&amp;#x26;&amp;#x26;tr[u].r&amp;#x3C;=r){
    tr[u].sum+=(tr[u].r-tr[u].l+1)*k;
    tr[u].add+=k;
    return;
  }
  LL m=tr[u].l+tr[u].r&gt;&gt;1;
  pushdown(u);
  if(l&amp;#x3C;=m) change(lc,l,r,k);
  if(r&gt;m) change(rc,l,r,k);
  pushup(u);
}
LL query(LL u,LL l,LL r){ //区查
  if(l&amp;#x3C;=tr[u].l &amp;#x26;&amp;#x26; tr[u].r&amp;#x3C;=r) return tr[u].sum;
  LL m=tr[u].l+tr[u].r&gt;&gt;1;
  pushdown(u);
  LL sum=0;
  if(l&amp;#x3C;=m) sum+=query(lc,l,r);
  if(r&gt;m) sum+=query(rc,l,r);
  return sum;
}
int main(){
  LL n,m,op,x,y,k;
  cin&gt;&gt;n&gt;&gt;m;
  for(int i=1; i&amp;#x3C;=n; i ++) cin&gt;&gt;w[i];

  build(1,1,n);
  while(m--){
    cin&gt;&gt;op&gt;&gt;x&gt;&gt;y;
    if(op==2)cout&amp;#x3C;&amp;#x3C;query(1,x,y)&amp;#x3C;&amp;#x3C;endl;
    else cin&gt;&gt;k,change(1,x,y,k);
  }
  return 0;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>C5 加乘线段树</title><link>https://ehnotgod.site/blog/c5-%E5%8A%A0%E4%B9%98%E7%BA%BF%E6%AE%B5%E6%A0%91</link><guid isPermaLink="true">https://ehnotgod.site/blog/c5-%E5%8A%A0%E4%B9%98%E7%BA%BF%E6%AE%B5%E6%A0%91</guid><description>线段树进阶：区间加法与乘法混合懒标记。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P3373&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;如题，已知一个数列 $$a$$，你需要进行下面三种操作：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;将某区间每一个数乘上 $$x$$；&lt;/li&gt;
&lt;li&gt;将某区间每一个数加上 $$x$$；&lt;/li&gt;
&lt;li&gt;求出某区间每一个数的和。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行包含三个整数 $$n,q,m$$，分别表示该数列数字的个数、操作的总个数和模数。&lt;/p&gt;
&lt;p&gt;第二行包含 $$n$$ 个用空格分隔的整数，其中第 $$i$$ 个数字表示数列第 $$i$$ 项的初始值 $$a_i$$。&lt;/p&gt;
&lt;p&gt;接下来 $$q$$ 行每行包含若干个整数，表示一个操作，具体如下：&lt;/p&gt;
&lt;p&gt;操作 $$1$$： 格式：&lt;code&gt;1 x y k&lt;/code&gt;  含义：将区间 $$[x,y]$$ 内每个数乘上 $$k$$。&lt;/p&gt;
&lt;p&gt;操作 $$2$$： 格式：&lt;code&gt;2 x y k&lt;/code&gt;  含义：将区间 $$[x,y]$$ 内每个数加上 $$k$$。&lt;/p&gt;
&lt;p&gt;操作 $$3$$： 格式：&lt;code&gt;3 x y&lt;/code&gt;  含义：输出区间 $$[x,y]$$ 内每个数的和对 $$m$$ 取模所得的结果。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输出包含若干行整数，即为所有操作 $$3$$ 的结果。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;5 5 38
1 5 4 2 3
2 1 4 1
3 2 5
1 2 4 2
2 3 5 5
3 1 4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;17
2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;【数据范围】对于 $$100%$$ 的数据：$$1 \le n \le 10^5$$，$$1 \le q \le 10^5,1\le a_i,k\le 10^4$$。&lt;/p&gt;
&lt;h3&gt;算法解析&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;images/C5-1.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3&gt;py代码实现&lt;/h3&gt;
&lt;p&gt;有两种实现：第一种是class的，跟C++格式一样，但是常数比较大；第二种是直接全局数组接管的，快多了。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第一种：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import sys
input = sys.stdin.readline

class Tree:
    def __init__(self):
        self.l = self.r = self.sum = self.add = 0
        self.mul = 1

class SegmentTree:
    def __init__(self, w, mod):
        self.n, self.w, self.mod = len(w) - 1, w, mod
        self.tr = [Tree() for _ in range(self.n * 4 + 5)]
        self.build(1, 1, self.n)

    def lc(self, u): return u &amp;#x3C;&amp;#x3C; 1
    def rc(self, u): return u &amp;#x3C;&amp;#x3C; 1 | 1
    def length(self, u): return self.tr[u].r - self.tr[u].l + 1

    def pushup(self, u):
        self.tr[u].sum = (self.tr[self.lc(u)].sum + self.tr[self.rc(u)].sum) % self.mod

    def apply(self, u, mulv, addv):
        mulv %= self.mod; addv %= self.mod
        self.tr[u].sum = (self.tr[u].sum * mulv + addv * self.length(u)) % self.mod
        self.tr[u].mul = self.tr[u].mul * mulv % self.mod
        self.tr[u].add = (self.tr[u].add * mulv + addv) % self.mod

    def pushdown(self, u):
        if self.tr[u].mul != 1 or self.tr[u].add != 0:
            self.apply(self.lc(u), self.tr[u].mul, self.tr[u].add)
            self.apply(self.rc(u), self.tr[u].mul, self.tr[u].add)
            self.tr[u].mul, self.tr[u].add = 1, 0

    def build(self, u, l, r):
        self.tr[u].l, self.tr[u].r = l, r
        if l == r:
            self.tr[u].sum = self.w[l] % self.mod
            return
        mid = (l + r) &gt;&gt; 1
        self.build(self.lc(u), l, mid)
        self.build(self.rc(u), mid + 1, r)
        self.pushup(u)

    def change(self, u, l, r, k):  # 区间加
        if l &amp;#x3C;= self.tr[u].l and self.tr[u].r &amp;#x3C;= r:
            self.apply(u, 1, k); return
        mid = (self.tr[u].l + self.tr[u].r) &gt;&gt; 1
        self.pushdown(u)
        if l &amp;#x3C;= mid: self.change(self.lc(u), l, r, k)
        if r &gt; mid: self.change(self.rc(u), l, r, k)
        self.pushup(u)

    def change2(self, u, l, r, k):  # 区间乘
        if l &amp;#x3C;= self.tr[u].l and self.tr[u].r &amp;#x3C;= r:
            self.apply(u, k, 0); return
        mid = (self.tr[u].l + self.tr[u].r) &gt;&gt; 1
        self.pushdown(u)
        if l &amp;#x3C;= mid: self.change2(self.lc(u), l, r, k)
        if r &gt; mid: self.change2(self.rc(u), l, r, k)
        self.pushup(u)

    def query(self, u, l, r):
        if l &amp;#x3C;= self.tr[u].l and self.tr[u].r &amp;#x3C;= r: return self.tr[u].sum
        mid = (self.tr[u].l + self.tr[u].r) &gt;&gt; 1
        self.pushdown(u)
        ans = 0
        if l &amp;#x3C;= mid: ans += self.query(self.lc(u), l, r)
        if r &gt; mid: ans += self.query(self.rc(u), l, r)
        return ans % self.mod

n, m, mod = map(int, input().split())
w = [0] + list(map(int, input().split()))
seg = SegmentTree(w, mod)

for _ in range(m):
    op = list(map(int, input().split()))
    if op[0] == 1: seg.change2(1, op[1], op[2], op[3])
    elif op[0] == 2: seg.change(1, op[1], op[2], op[3])
    else: print(seg.query(1, op[1], op[2]))
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import sys
input = sys.stdin.readline

n, m, mod = map(int, input().split())
w = [0] + list(map(int, input().split()))
N = 4 * n + 5
L = [0] * N
R = [0] * N
SUM = [0] * N
ADD = [0] * N
MUL = [1] * N

def length(u):
    return R[u] - L[u] + 1

def pushup(u):
    SUM[u] = (SUM[u &amp;#x3C;&amp;#x3C; 1] + SUM[u &amp;#x3C;&amp;#x3C; 1 | 1]) % mod

def apply(u, mulv, addv):
    mulv %= mod; addv %= mod
    SUM[u] = (SUM[u] * mulv + addv * length(u)) % mod
    MUL[u] = MUL[u] * mulv % mod
    ADD[u] = (ADD[u] * mulv + addv) % mod

def pushdown(u):
    if MUL[u] == 1 and ADD[u] == 0: return
    apply(u &amp;#x3C;&amp;#x3C; 1, MUL[u], ADD[u])
    apply(u &amp;#x3C;&amp;#x3C; 1 | 1, MUL[u], ADD[u])
    MUL[u] = 1; ADD[u] = 0

def build(u, l, r):
    L[u] = l; R[u] = r
    if l == r:
        SUM[u] = w[l] % mod
        return
    mid = (l + r) &gt;&gt; 1
    build(u &amp;#x3C;&amp;#x3C; 1, l, mid); build(u &amp;#x3C;&amp;#x3C; 1 | 1, mid + 1, r)
    pushup(u)

def change(u, l, r, k):
    if l &amp;#x3C;= L[u] and R[u] &amp;#x3C;= r:
        apply(u, 1, k)
        return
    pushdown(u); mid = (L[u] + R[u]) &gt;&gt; 1
    if l &amp;#x3C;= mid: change(u &amp;#x3C;&amp;#x3C; 1, l, r, k)
    if r &gt; mid: change(u &amp;#x3C;&amp;#x3C; 1 | 1, l, r, k)
    pushup(u)

def change2(u, l, r, k):
    if l &amp;#x3C;= L[u] and R[u] &amp;#x3C;= r:
        apply(u, k, 0)
        return
    pushdown(u); mid = (L[u] + R[u]) &gt;&gt; 1
    if l &amp;#x3C;= mid: change2(u &amp;#x3C;&amp;#x3C; 1, l, r, k)
    if r &gt; mid: change2(u &amp;#x3C;&amp;#x3C; 1 | 1, l, r, k)
    pushup(u)

def query(u, l, r):
    if l &amp;#x3C;= L[u] and R[u] &amp;#x3C;= r: return SUM[u]
    pushdown(u); mid = (L[u] + R[u]) &gt;&gt; 1; ans = 0
    if l &amp;#x3C;= mid: ans += query(u &amp;#x3C;&amp;#x3C; 1, l, r)
    if r &gt; mid: ans += query(u &amp;#x3C;&amp;#x3C; 1 | 1, l, r)
    return ans % mod

build(1, 1, n)

for _ in range(m):
    op = list(map(int, input().split()))
    if op[0] == 1: change2(1, op[1], op[2], op[3])
    elif op[0] == 2: change(1, op[1], op[2], op[3])
    else: print(query(1, op[1], op[2]))
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;// 洛谷P3373
#include &amp;#x3C;cstring&gt;
#include &amp;#x3C;iostream&gt;
#include &amp;#x3C;algorithm&gt;
using namespace std;

#define N 100005
#define LL long long
#define int long long
#define lc u&amp;#x3C;&amp;#x3C;1
#define rc u&amp;#x3C;&amp;#x3C;1|1
LL w[N];
LL n,m,op,x,y,k,mod;
struct Tree{ //线段树
  LL l,r,sum,add,mul;
}tr[N*4];

inline LL len(int u) { return tr[u].r - tr[u].l + 1; }

void pushup(int u) {
  tr[u].sum = (tr[lc].sum + tr[rc].sum) % mod;
}

void apply_mul_add_to_node(int u, LL mulv, LL addv) {
  mulv %= mod; if (mulv &amp;#x3C; 0) mulv += mod;
  addv %= mod; if (addv &amp;#x3C; 0) addv += mod;
  tr[u].sum = ( (tr[u].sum * mulv) % mod + (addv * (len(u) % mod)) % mod ) % mod;
  tr[u].mul = (tr[u].mul * mulv) % mod;
  tr[u].add = (tr[u].add * mulv + addv) % mod;
}

void pushdown(int u) {
  if (tr[u].mul != 1 || tr[u].add != 0) {
    apply_mul_add_to_node(lc, tr[u].mul, tr[u].add);
    apply_mul_add_to_node(rc, tr[u].mul, tr[u].add);
    tr[u].mul = 1;
    tr[u].add = 0;
  }
}
void build(LL u,LL l,LL r){ //建树
  tr[u]={l,r,w[l],0,1};
  if(l==r) return;
  LL m=l+r&gt;&gt;1;
  build(lc,l,m);
  build(rc,m+1,r);
  pushup(u);
}
void change(LL u,LL l,LL r,LL k){ //区修
  if(l&amp;#x3C;=tr[u].l&amp;#x26;&amp;#x26;tr[u].r&amp;#x3C;=r){
    apply_mul_add_to_node(u, 1, k);
    return;
  }
  LL m=tr[u].l+tr[u].r&gt;&gt;1;
  pushdown(u);
  if(l&amp;#x3C;=m) change(lc,l,r,k);
  if(r&gt;m) change(rc,l,r,k);
  pushup(u);
}
void change2(LL u,LL l,LL r,LL k){ //区修
  if(l&amp;#x3C;=tr[u].l&amp;#x26;&amp;#x26;tr[u].r&amp;#x3C;=r){
    apply_mul_add_to_node(u, k, 0);
    return;
  }
  LL m=tr[u].l+tr[u].r&gt;&gt;1;
  pushdown(u);
  if(l&amp;#x3C;=m) change2(lc,l,r,k);
  if(r&gt;m) change2(rc,l,r,k);
  pushup(u);
}
LL query(LL u,LL l,LL r){ //区查
  if(l&amp;#x3C;=tr[u].l &amp;#x26;&amp;#x26; tr[u].r&amp;#x3C;=r) return tr[u].sum % mod;
  LL m=tr[u].l+tr[u].r&gt;&gt;1;
  pushdown(u);
  LL sum=0;
  if(l&amp;#x3C;=m) {
    sum+=query(lc,l,r);
    sum %= mod;
  }
  if(r&gt;m) {
    sum+=query(rc,l,r);
    sum %= mod;
  }
  return sum;
}
signed main(){
  cin&gt;&gt;n&gt;&gt;m&gt;&gt;mod;
  for(int i=1; i&amp;#x3C;=n; i ++) cin&gt;&gt;w[i];
  build(1,1,n);
  while(m--){
    cin&gt;&gt;op&gt;&gt;x&gt;&gt;y;
    if(op==3)cout&amp;#x3C;&amp;#x3C;query(1,x,y)&amp;#x3C;&amp;#x3C;endl;
    else if (op == 2){
      cin&gt;&gt;k;change(1,x,y,k);
    }
    else{
      cin&gt;&gt;k;change2(1,x,y,k);
    }
  }
  return 0;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>C6 最大子段和线段树</title><link>https://ehnotgod.site/blog/c6-%E6%9C%80%E5%A4%A7%E5%AD%90%E6%AE%B5%E5%92%8C%E7%BA%BF%E6%AE%B5%E6%A0%91</link><guid isPermaLink="true">https://ehnotgod.site/blog/c6-%E6%9C%80%E5%A4%A7%E5%AD%90%E6%AE%B5%E5%92%8C%E7%BA%BF%E6%AE%B5%E6%A0%91</guid><description>线段树进阶：维护区间最大子段和。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P4513&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在小新家附近有一条“公园路”，路的一边从南到北依次排着 $$n$$ 个公园，小白早就看花了眼，自己也不清楚该去哪些公园玩了。&lt;/p&gt;
&lt;p&gt;一开始，小白就根据公园的风景给每个公园打了分。小新为了省事，每次遛狗的时候都会事先规定一个范围，小白只可以选择第 $$a$$ 个和第 $$b$$ 个公园之间（包括 $$a, b$$ 两个公园）选择连续的一些公园玩。小白当然希望选出的公园的分数总和尽量高咯。同时，由于一些公园的景观会有所改变，所以，小白的打分也可能会有一些变化。&lt;/p&gt;
&lt;p&gt;那么，就请你来帮小白选择公园吧。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行，两个整数 $$n$$ 和 $$m$$，分别表示表示公园的数量和操作（遛狗或者改变打分）总数。&lt;/p&gt;
&lt;p&gt;接下来 $$n$$ 行，每行一个整数，依次给出小白开始时对公园的打分。&lt;/p&gt;
&lt;p&gt;接下来 $$m$$ 行，每行三个整数。其中第一个整数 $$k$$ 为 $$1$$ 或 $$2$$。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$$k=1$$ 表示，小新要带小白出去玩，接下来的两个整数 $$a$$ 和 $$b$$ 给出了选择公园的范围 $$(1 \le a,b \le n)$$。测试数据可能会出现 $$a &gt; b$$ 的情况，需要进行交换；&lt;/li&gt;
&lt;li&gt;$$k=2$$ 表示，小白改变了对某个公园的打分，接下来的两个整数 $$p$$ 和 $$s$$，表示小白对第 $$p$$ 个公园的打分变成了 $$s(1\le |s|\le 1000)$$。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;小白每出去玩一次，都对应输出一行，只包含一个整数，表示小白可以选出的公园得分和的最大值。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;5 3
1
2
-3
4
5
1 2 3
2 2 -1
1 2 3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2
-1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于 $$100%$$ 的数据，$$1 \le n \le 5 \times 10^5$$，$$1 \le m \le 10^5$$，所有打分都是绝对值不超过 $$1000$$ 的整数。&lt;/p&gt;
&lt;h3&gt;算法解析&lt;/h3&gt;
&lt;p&gt;我们知道，求一段序列的最大子段和是O（n）的，但是这样是显然会超时的。&lt;/p&gt;
&lt;p&gt;我们需要一个数据结构来支持修改和计算的操作，对于这种修改一个而查询区间的问题，考虑使用线段树。&lt;/p&gt;
&lt;p&gt;在线段树中，除了左端点，右端点，左儿子指针，右儿子指针之外，新开4个域——max，maxl，maxr，sum，其中sum为该区间的和，max为该区间上的最大子段和，maxl为必须包含左端点的最大子段和，maxr为必须包含右端点的最大子段和。&lt;/p&gt;
&lt;p&gt;可以用线段树来统计了注意求得的最大子段和中至少包含1个元素，所以出现了样例那样的输出负值。&lt;/p&gt;
&lt;p&gt;修改时：&lt;/p&gt;
&lt;p&gt;1、若左儿子的maxr和右儿子的maxl都为负，就从中取较大的为该节点的max（防止一个都不取），反之取二者中正的（都正就都取）。&lt;/p&gt;
&lt;p&gt;2、将该节点的max用左右儿子的max更新。&lt;/p&gt;
&lt;p&gt;3、该节点的maxl为左儿子的maxl与左儿子sum和右儿子maxl和的最大值。&lt;/p&gt;
&lt;p&gt;4、该节点的maxr为右儿子的maxr与右儿子sum和左儿子maxr和的最大值。&lt;/p&gt;
&lt;p&gt;5、该节点的sum为左右儿子的sum和。&lt;/p&gt;
&lt;p&gt;查询时：&lt;/p&gt;
&lt;p&gt;1、如果查询区间覆盖这一节点，将该节点信息返回。&lt;/p&gt;
&lt;p&gt;2、如果只与一个儿子有交集，就返回在那个儿子中查找到的信息。&lt;/p&gt;
&lt;p&gt;3、如果与两个儿子都有交集，就先分别计算出两个儿子的信息，然后按修改的方式将两个信息合并，然后返回。&lt;/p&gt;
&lt;p&gt;4、最后返回的max值即为答案。&lt;/p&gt;
&lt;h3&gt;py代码实现&lt;/h3&gt;
&lt;p&gt;略，再用py写这种题纯属想不开&lt;/p&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;//洛谷P4513

#include&amp;#x3C;bits/stdc++.h&gt;
#define int long long
using namespace std;
#define endl &quot;\n&quot;
#define range(i, a, b) for (int i = (a); i &amp;#x3C; (b); ++i)

#define lc 2*p
#define rc 2*p+1
#define N 500005

int w[N];

struct node {
    int l, r, sum, ansl, ansr, ans;
} tr[N * 4];
void merge(int p){
    tr[p].sum = tr[lc].sum + tr[rc].sum;
    tr[p].ans = max(tr[lc].ans, max(tr[rc].ans, tr[rc].ansl + tr[lc].ansr));
    tr[p].ansl = max(tr[lc].sum + tr[rc].ansl, tr[lc].ansl);
    tr[p].ansr = max(tr[rc].sum + tr[lc].ansr, tr[rc].ansr);
}
void build(int p, int l, int r) {
    tr[p].l = l; tr[p].r = r;
    if (l == r) {
        tr[p].sum = w[l];
        tr[p].ansl = tr[p].ansr = tr[p].ans = w[l];
        return;
    }
    int m = (l + r) / 2;
    build(lc, l, m);
    build(rc, m + 1, r);
    merge(p);
}
void update(int p, int x, int k) {
    if (tr[p].l == x &amp;#x26;&amp;#x26; tr[p].r == x) {
        tr[p].sum = k;
        tr[p].ansl = k;
        tr[p].ansr = k;
        tr[p].ans = k;
        return;
    }
    int m = (tr[p].l + tr[p].r) / 2;
    if (x &amp;#x3C;= m) update(lc, x, k);
    else update(rc, x, k);
    merge(p);
}

node query(int p, int x, int y) {
    if (x &amp;#x3C;= tr[p].l &amp;#x26;&amp;#x26; tr[p].r &amp;#x3C;= y) return tr[p];
    int m = (tr[p].l + tr[p].r) / 2;
    if (y &amp;#x3C;= m){
        return query(lc, x, y);
    } 
    else if (x &gt; m) {
        return query(rc, x, y);
    }
    else{
        node t, a = query(lc, x, y), b = query(rc, x, y);
        t.sum = a.sum + b.sum;
        t.ansl = max(a.ansl, a.sum + b.ansl);
        t.ansr = max(b.ansr, b.sum + a.ansr);
        t.ans = max({a.ans, b.ans, a.ansr + b.ansl});
        return t;
    }
}
signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int n, m;
    cin &gt;&gt; n &gt;&gt; m;
    range(i, 0, n){
        cin &gt;&gt; w[i + 1];
    }
    build(1, 1, n);
    range(i, 0, m){
        int op; cin &gt;&gt; op;
        int x, y, k;
        if (op == 2){
            cin &gt;&gt; x &gt;&gt; k;
            update(1, x, k);
        }
        else{
            cin &gt;&gt; x &gt;&gt; y;
            if (x &gt; y){
                cout &amp;#x3C;&amp;#x3C; query(1, y, x).ans &amp;#x3C;&amp;#x3C; endl;
            }
            else{
                cout &amp;#x3C;&amp;#x3C; query(1, x, y).ans &amp;#x3C;&amp;#x3C; endl;
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>C7 普通平衡树</title><link>https://ehnotgod.site/blog/c7-%E6%99%AE%E9%80%9A%E5%B9%B3%E8%A1%A1%E6%A0%91</link><guid isPermaLink="true">https://ehnotgod.site/blog/c7-%E6%99%AE%E9%80%9A%E5%B9%B3%E8%A1%A1%E6%A0%91</guid><description>平衡树（Treap/Splay）模板：动态维护有序集合。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P3369&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;您需要动态地维护一个可重集合 $$M$$，并且提供以下操作：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;向 $$M$$ 中插入一个数 $$x$$。&lt;/li&gt;
&lt;li&gt;从 $$M$$ 中删除一个数 $$x$$（若有多个相同的数，应只删除一个）。&lt;/li&gt;
&lt;li&gt;查询 $$M$$ 中有多少个数比 $$x$$ 小，并且将得到的答案加一。&lt;/li&gt;
&lt;li&gt;查询如果将 $$M$$ 从小到大排列后，排名位于第 $$x$$ 位的数。&lt;/li&gt;
&lt;li&gt;查询 $$M$$ 中 $$x$$ 的前驱（前驱定义为小于 $$x$$，且最大的数）。&lt;/li&gt;
&lt;li&gt;查询 $$M$$ 中 $$x$$ 的后继（后继定义为大于 $$x$$，且最小的数）。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;对于操作 3,5,6，&lt;strong&gt;不保证&lt;/strong&gt;当前可重集中存在数 $$x$$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行为 $$n$$，表示操作的个数,下面 $$n$$ 行每行有两个数 $$\text{opt}$$ 和 $$x$$，$$\text{opt}$$ 表示操作的序号（$$ 1 \leq \text{opt} \leq 6 $$）&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;对于操作 $$3,4,5,6$$ 每行输出一个数，表示对应答案。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;10
1 106465
4 1
1 317721
1 460929
1 644985
1 84185
1 89851
6 81968
1 492737
5 493598
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;106465
84185
492737
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;【数据范围】
对于 $$100%$$ 的数据，$$1\le n \le 10^5$$，$$|x| \le 10^7$$&lt;/p&gt;
&lt;h3&gt;算法解析&lt;/h3&gt;
&lt;p&gt;过难，略&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import sys

class Node:
    def __init__(self):
        self.ch = [0, 0]  # 左右孩子
        self.fa = 0  # 父节点
        self.v = 0  # 节点值
        self.cnt = 0  # 值出现次数
        self.siz = 0  # 子树大小

    def init(self, p, v1):
        self.fa = p
        self.v = v1
        self.cnt = self.siz = 1


def ls(x):
    return tr[x].ch[0]


def rs(x):
    return tr[x].ch[1]


def pushup(x):
    tr[x].siz = tr[ls(x)].siz + tr[rs(x)].siz + tr[x].cnt


def rotate(x):
    y = tr[x].fa
    z = tr[y].fa
    k = 1 if tr[y].ch[1] == x else 0
    tr[z].ch[1 if tr[z].ch[1] == y else 0] = x
    tr[x].fa = z
    tr[y].ch[k] = tr[x].ch[k ^ 1]
    tr[tr[x].ch[k ^ 1]].fa = y
    tr[x].ch[k ^ 1] = y
    tr[y].fa = x
    pushup(y)
    pushup(x)


def splay(x, k):
    while tr[x].fa != k:
        y = tr[x].fa
        z = tr[y].fa
        if z != k:
            if (ls(y) == x) ^ (ls(z) == y):
                rotate(x)
            else:
                rotate(y)
        rotate(x)
    if k == 0:
        global root
        root = x


def insert(v):
    global root, tot
    x = root
    p = 0
    while x != 0 and tr[x].v != v:
        p = x
        x = tr[x].ch[1 if v &gt; tr[x].v else 0]
    if x != 0:
        tr[x].cnt += 1
    else:
        tot += 1
        x = tot
        tr[p].ch[1 if v &gt; tr[p].v else 0] = x
        tr[x].init(p, v)
    splay(x, 0)


def find(v):
    global root
    x = root
    while tr[x].ch[1 if v &gt; tr[x].v else 0] != 0 and v != tr[x].v:
        x = tr[x].ch[1 if v &gt; tr[x].v else 0]
    splay(x, 0)


def getpre(v):
    global root
    find(v)
    x = root
    if tr[x].v &amp;#x3C; v:
        return x
    x = ls(x)
    while rs(x) != 0:
        x = rs(x)
    splay(x, 0)
    return x


def getsuc(v):
    global root
    find(v)
    x = root
    if tr[x].v &gt; v:
        return x
    x = rs(x)
    while ls(x) != 0:
        x = ls(x)
    splay(x, 0)
    return x


def del_(v):
    pre = getpre(v)
    suc = getsuc(v)
    splay(pre, 0)
    splay(suc, pre)
    del_node = tr[suc].ch[0]
    if tr[del_node].cnt &gt; 1:
        tr[del_node].cnt -= 1
        splay(del_node, 0)
    else:
        tr[suc].ch[0] = 0
        splay(suc, 0)


def getrank(v):
    insert(v)
    res = tr[ls(root)].siz
    del_(v)
    return res


def getval(k):
    global root
    x = root
    while True:
        if k &amp;#x3C;= tr[ls(x)].siz:
            x = ls(x)
        elif k &amp;#x3C;= tr[ls(x)].siz + tr[x].cnt:
            break
        else:
            k -= tr[ls(x)].siz + tr[x].cnt
            x = rs(x)
    splay(x, 0)
    return tr[x].v


N = 110001
INF = (1 &amp;#x3C;&amp;#x3C; 30) + 1
tr = [Node() for _ in range(N)]
root = 0
tot = 0


insert(-INF)
insert(INF)
n = int(sys.stdin.readline())
for _ in range(n):
    op, x = map(int, sys.stdin.readline().split())
    if op == 1:
        insert(x)
    elif op == 2:
        del_(x)
    elif op == 3:
        print(getrank(x))
    elif op == 4:
        print(getval(x + 1))
    elif op == 5:
        print(tr[getpre(x)].v)
    else:
        print(tr[getsuc(x)].v)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include &amp;#x3C;iostream&gt;
using namespace std;

#define ls(x) tr[x].ch[0]
#define rs(x) tr[x].ch[1]
const int N=1100010, INF=(1&amp;#x3C;&amp;#x3C;30)+1;
struct node{
  int ch[2]; //儿
  int fa; //父
  int v;  //点权
  int cnt; //点权次数
  int siz; //子树大小
  void init(int p,int v1){
    fa=p, v=v1;
    cnt=siz=1;
  }
}tr[N];
int root,tot; //根,节点个数

void pushup(int x){ //上传
  tr[x].siz=tr[ls(x)].siz+tr[rs(x)].siz+tr[x].cnt;
}
void rotate(int x){ //旋转
  int y=tr[x].fa, z=tr[y].fa, k=tr[y].ch[1]==x; //y的右儿是x
  tr[z].ch[tr[z].ch[1]==y]=x, tr[x].fa=z; //z的儿是x,x的父是z
  tr[y].ch[k]=tr[x].ch[k^1], tr[tr[x].ch[k^1]].fa=y; //y的儿是x的异儿,x的异儿的父是y
  tr[x].ch[k^1]=y, tr[y].fa=x; //x的异儿是y,y的父是x
  pushup(y), pushup(x); //自底向上push
}
void splay(int x, int k){ //伸展
  while(tr[x].fa!=k){ //折线转xx,直线转yx
    int y=tr[x].fa, z=tr[y].fa;
    if(z!=k) (ls(y)==x)^(ls(z)==y)?rotate(x):rotate(y);
    rotate(x);
  }
  if(!k) root=x; //k=0时,x转到根
}
void insert(int v){ //插入
  int x=root, p=0;
  //x走到空节点或走到目标点结束
  while(x&amp;#x26;&amp;#x26;tr[x].v!=v) p=x,x=tr[x].ch[v&gt;tr[x].v];
  if(x) tr[x].cnt++; //目标点情况
  else{ //空节点情况
    x=++tot;
    tr[p].ch[v&gt;tr[p].v]=x;
    tr[x].init(p,v);
  }
  splay(x, 0);
}
void find(int v){ //找到v并转到根
  int x=root;
  while(tr[x].ch[v&gt;tr[x].v]&amp;#x26;&amp;#x26;v!=tr[x].v)
    x=tr[x].ch[v&gt;tr[x].v];
  splay(x, 0);
}
int getpre(int v){ //前驱
  find(v);
  int x=root;
  if(tr[x].v&amp;#x3C;v) return x;
  x=ls(x);
  while(rs(x)) x=rs(x);
  splay(x, 0);
  return x;
}
int getsuc(int v){ //后继
  find(v);
  int x=root;
  if(tr[x].v&gt;v) return x;
  x=rs(x);
  while(ls(x)) x=ls(x);
  splay(x, 0);
  return x;
}
void del(int v){ //删除
  int pre=getpre(v);
  int suc=getsuc(v);
  splay(pre,0), splay(suc,pre);
  int del=tr[suc].ch[0];
  if(tr[del].cnt&gt;1)
    tr[del].cnt--, splay(del,0);
  else
    tr[suc].ch[0]=0, splay(suc,0);
}
int getrank(int v){ //排名
  insert(v);
  int res=tr[tr[root].ch[0]].siz;
  del(v);
  return res;
}
int getval(int k){ //数值
  int x=root;
  while(true){
    if(k&amp;#x3C;=tr[ls(x)].siz) x=ls(x);
    else if(k&amp;#x3C;=tr[ls(x)].siz+tr[x].cnt) break;
    else k-=tr[ls(x)].siz+tr[x].cnt, x=rs(x);
  }
  splay(x, 0);
  return tr[x].v;
}
int main(){
  insert(-INF);insert(INF); //哨兵
  int n,op,x; scanf(&quot;%d&quot;, &amp;#x26;n);
  while(n--){
    scanf(&quot;%d%d&quot;, &amp;#x26;op, &amp;#x26;x);
    if(op==1) insert(x);
    else if(op==2) del(x);
    else if(op==3) printf(&quot;%d\n&quot;,getrank(x));
    else if(op==4) printf(&quot;%d\n&quot;,getval(x+1));
    else if(op==5) printf(&quot;%d\n&quot;,tr[getpre(x)].v);
    else printf(&quot;%d\n&quot;,tr[getsuc(x)].v);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;更好的写法：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include &amp;#x3C;bits/stdc++.h&gt;
#include &amp;#x3C;ext/pb_ds/assoc_container.hpp&gt;
#include &amp;#x3C;ext/pb_ds/tree_policy.hpp&gt;
using namespace std;
using namespace __gnu_pbds;

using pii = pair&amp;#x3C;int,int&gt;;

tree&amp;#x3C;
    pii,
    null_type,
    less&amp;#x3C;pii&gt;,
    rb_tree_tag,
    tree_order_statistics_node_update
&gt; tr;

int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n; cin &gt;&gt; n;
    int id = 0;
    const int INF = 1e9;

    while(n--){
        int op, x; cin &gt;&gt; op &gt;&gt; x;

        if(op == 1){                     // 插入 x
            tr.insert({x, ++id});
        }
        else if(op == 2){                // 删除一个 x
            auto it = tr.lower_bound({x, -INF});
            if(it != tr.end() &amp;#x26;&amp;#x26; it-&gt;first == x) tr.erase(it);
        }
        else if(op == 3){                // x 的排名
            cout &amp;#x3C;&amp;#x3C; tr.order_of_key({x, -INF}) + 1 &amp;#x3C;&amp;#x3C; &apos;\n&apos;;
        }
        else if(op == 4){                // 第 x 小
            cout &amp;#x3C;&amp;#x3C; tr.find_by_order(x - 1)-&gt;first &amp;#x3C;&amp;#x3C; &apos;\n&apos;;
        }
        else if(op == 5){                // x 的前驱
            auto it = tr.lower_bound({x, -INF});
            --it;
            cout &amp;#x3C;&amp;#x3C; it-&gt;first &amp;#x3C;&amp;#x3C; &apos;\n&apos;;
        }
        else if(op == 6){                // x 的后继
            auto it = tr.lower_bound({x, INF});
            cout &amp;#x3C;&amp;#x3C; it-&gt;first &amp;#x3C;&amp;#x3C; &apos;\n&apos;;
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>C8 可持续化线段树</title><link>https://ehnotgod.site/blog/c8-%E5%8F%AF%E6%8C%81%E7%BB%AD%E5%8C%96%E7%BA%BF%E6%AE%B5%E6%A0%91</link><guid isPermaLink="true">https://ehnotgod.site/blog/c8-%E5%8F%AF%E6%8C%81%E7%BB%AD%E5%8C%96%E7%BA%BF%E6%AE%B5%E6%A0%91</guid><description>可持久化线段树（主席树）：静态区间第 k 小。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P3834&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目背景&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这是个非常经典的可持久化权值线段树入门题——静态区间第 $$k$$ 小。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;如题，给定 $$n$$ 个整数构成的序列 $$a$$，将对于指定的闭区间 $$[l, r]$$ 查询其区间内的第 $$k$$ 小值。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行包含两个整数，分别表示序列的长度 $$n$$ 和查询的个数 $$m$$。&lt;br&gt;
第二行包含 $$n$$ 个整数，第 $$i$$ 个整数表示序列的第 $$i$$ 个元素 $$a_i$$。&lt;br&gt;
接下来 $$m$$ 行每行包含三个整数 $$ l, r, k$$ , 表示查询区间 $$[l, r]$$ 内的第 $$k$$ 小值。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;对于每次询问，输出一行一个整数表示答案。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;5 5
25957 6405 15770 26287 26465 
2 2 1
3 4 1
4 5 1
1 2 2
4 4 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;6405
15770
26287
25957
26287
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;对于 $$100%$$ 的数据，满足 $$1 \leq n,m \leq 2\times 10^5$$，$$0\le a_i \leq 10^9$$，$$1 \leq l \leq r \leq n$$，$$1 \leq k \leq r - l + 1$$。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;算法解析&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;1. 每棵树维护什么？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;普通线段树一般维护数组下标区间，而主席树这里维护的是&lt;strong&gt;值域&lt;/strong&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;struct node{
    int l,r,s;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;l&lt;/code&gt;：左儿子编号&lt;/li&gt;
&lt;li&gt;&lt;code&gt;r&lt;/code&gt;：右儿子编号&lt;/li&gt;
&lt;li&gt;&lt;code&gt;s&lt;/code&gt;：当前值域内有多少个数&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;例如：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;          [1,4] s=4
         /          \
    [1,2] s=2    [3,4] s=2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也就是说，它本质上是一棵&lt;strong&gt;统计每个值出现次数的线段树&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. &lt;code&gt;root[i]&lt;/code&gt; 是什么？&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;root[i]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;表示：&lt;/p&gt;
&lt;p&gt;前 &lt;code&gt;i&lt;/code&gt; 个数形成的值域线段树。&lt;/p&gt;
&lt;p&gt;例如：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;a = [3,1,4,2]

root[0] = []
root[1] = [3]
root[2] = [3,1]
root[3] = [3,1,4]
root[4] = [3,1,4,2]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;3. 怎么查询第 k 小？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;查询：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;query(root[l-1],root[r],1,bn,k);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;核心代码：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;int s=tr[lc(y)].s-tr[lc(x)].s;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;x = root[l-1]
y = root[r]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以 &lt;code&gt;s&lt;/code&gt; 表示：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;[l,r]&lt;/code&gt; 中有多少个数落在当前值域的左半边。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;不断对值域二分，直到找到答案。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. 为什么要有很多版本？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;每加入一个新数，就产生一个新版本：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;insert(root[i-1],root[i],1,bn,id);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也就是：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;root[0]
   ↓ 插入 a1
root[1]
   ↓ 插入 a2
root[2]
   ↓ 插入 a3
root[3]
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是不会把整棵树复制一遍。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;5. 新版本是怎么产生的？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;核心：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;void insert(int x,int &amp;#x26;y,int l,int r,int pos){
    y=++idx;
    tr[y]=tr[x];
    tr[y].s++;
    ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;x = 旧版本节点
y = 新版本节点
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最重要的是：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;y=++idx;      // 新建节点
tr[y]=tr[x];  // 复制旧节点
tr[y].s++;    // 只修改新节点
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也就是说：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;旧节点不修改，先复制一份，再修改副本。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;因此一次插入只增加：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;O(log n)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;个节点，而不是复制整棵树。&lt;/p&gt;
&lt;p&gt;注意&lt;code&gt;int &amp;#x26;y&lt;/code&gt;，它可以让父节点的lc(y)自动附到对应的子节点。&lt;/p&gt;
&lt;p&gt;总体复杂度：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;建树：O(n log n)
单次查询：O(log n)
空间：O(n log n)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import bisect

class Node:
    def __init__(self, l=0, r=0, s=0):
        self.l = l
        self.r = r
        self.s = s


n, m = map(int, input().split())
a = list(map(int, input().split()))
a = [0] + a  # 转换为1-based索引

# 离散化处理
sorted_b = sorted(a[1:])
unique_b = []
prev = None
for num in sorted_b:
    if num != prev:
        unique_b.append(num)
        prev = num
bn = len(unique_b)

# 初始化主席树
tr = [Node(0, 0, 0)]  # 空节点，索引0
idx = 1
root = [0] * (n + 1)
root[0] = 0

def insert(x, l, r, pos):
    global idx
    y = Node(tr[x].l, tr[x].r, tr[x].s + 1)
    tr.append(y)
    y_idx = idx
    idx += 1
    if l == r:
        return y_idx
    mid = (l + r) // 2
    if pos &amp;#x3C;= mid:
        new_l = insert(tr[x].l, l, mid, pos)
        y.l = new_l
    else:
        new_r = insert(tr[x].r, mid + 1, r, pos)
        y.r = new_r
    return y_idx

# 构建主席树的每个版本
for i in range(1, n + 1):
    num = a[i]
    pos = bisect.bisect_left(unique_b, num) + 1  # 转换为1-based的id
    root[i] = insert(root[i-1], 1, bn, pos)

# 查询函数
def query(x, y, l, r, k):
    if l == r:
        return l
    mid = (l + r) // 2
    left_x = tr[x].l
    left_y = tr[y].l
    s = tr[left_y].s - tr[left_x].s
    if k &amp;#x3C;= s:
        return query(left_x, left_y, l, mid, k)
    else:
        return query(tr[x].r, tr[y].r, mid + 1, r, k - s)

# 处理每个查询并输出结果
output = []
for _ in range(m):
    l, r, k = map(int, input().split())
    id = query(root[l-1], root[r], 1, bn, k)
    output.append(str(unique_b[id-1]))  # id转换为0-based索引
print(&apos;\n&apos;.join(output))
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;// 主席树 O(nlognlogn)
#include &amp;#x3C;iostream&gt;
#include &amp;#x3C;cstring&gt;
#include &amp;#x3C;algorithm&gt;
using namespace std;

#define N 200005
#define lc(x) tr[x].l
#define rc(x) tr[x].r
struct node{
  int l,r,s; //s:节点值域中有多少个数
}tr[N*20];
int root[N],idx;
int n,m,a[N],b[N];

void insert(int x,int &amp;#x26;y,int l,int r,int pos){
  y=++idx; //开点
  tr[y]=tr[x]; tr[y].s++;
  if(l==r) return;
  int m=l+r&gt;&gt;1;
  if(pos&amp;#x3C;=m) insert(lc(x),lc(y),l,m,pos);
  else insert(rc(x),rc(y),m+1,r,pos);
}
int query(int x,int y,int l,int r,int k){
  if(l==r) return l;
  int m=l+r&gt;&gt;1;
  int s=tr[lc(y)].s-tr[lc(x)].s;
  if(k&amp;#x3C;=s) return query(lc(x),lc(y),l,m,k);
  else return query(rc(x),rc(y),m+1,r,k-s);
}
int main(){
  scanf(&quot;%d%d&quot;,&amp;#x26;n,&amp;#x26;m);
  for(int i=1; i&amp;#x3C;=n; i++){
    scanf(&quot;%d&quot;,&amp;#x26;a[i]); b[i]=a[i];
  }
  sort(b+1,b+n+1);
  int bn=unique(b+1,b+n+1)-b-1; //去重后的个数

  for(int i=1; i&amp;#x3C;=n; i++){
    int id=lower_bound(b+1,b+bn+1,a[i])-b;//下标
    insert(root[i-1],root[i],1,bn,id);
  }
  while(m--){
    int l,r,k; scanf(&quot;%d%d%d&quot;,&amp;#x26;l,&amp;#x26;r,&amp;#x26;k);
    int id=query(root[l-1],root[r],1,bn,k);
    printf(&quot;%d\n&quot;,b[id]);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;查询x是第几小的话，参考如下代码：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;int query_rank(int x,int y,int l,int r,int pos){
  if(l==r) return 1;
  int m=l+r&gt;&gt;1;
  if(pos&amp;#x3C;=m) return query_rank(lc(x),lc(y),l,m,pos);
  int s=tr[lc(y)].s-tr[lc(x)].s;
  return s+query_rank(rc(x),rc(y),m+1,r,pos);
}

int pos=lower_bound(b+1,b+bn+1,v)-b;
int rk=query_rank(root[l-1],root[r],1,bn,pos);
printf(&quot;%d\n&quot;,rk);
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>C9 扫描线</title><link>https://ehnotgod.site/blog/c9-%E6%89%AB%E6%8F%8F%E7%BA%BF</link><guid isPermaLink="true">https://ehnotgod.site/blog/c9-%E6%89%AB%E6%8F%8F%E7%BA%BF</guid><description>扫描线：矩形面积并 / 周长并。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P5490&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;求 $$n$$ 个四边平行于坐标轴的矩形的面积并。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行一个正整数 $$n$$。&lt;/p&gt;
&lt;p&gt;接下来 $$n$$ 行每行四个非负整数 $$x_1, y_1, x_2, y_2$$，表示一个矩形的四个端点坐标为 $$(x_1, y_1),(x_1, y_2),(x_2, y_2),(x_2, y_1)$$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;一行一个正整数，表示 $$n$$ 个矩形的并集覆盖的总面积。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2
100 100 200 200
150 150 250 255
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;18000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于 $$100%$$ 的数据，$$1 \le n \le {10}^5$$，$$0 \le x_1 &amp;#x3C; x_2 \le {10}^9$$，$$0 \le y_1 &amp;#x3C; y_2 \le {10}^9$$。&lt;/p&gt;
&lt;h3&gt;算法解析&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;images/C9-1.png&quot; alt=&quot;image-20250826121538028&quot;&gt;&lt;/p&gt;
&lt;p&gt;核心思路是把每个矩形拆成两条水平边：下边记 &lt;code&gt;+1&lt;/code&gt; 表示矩形开始，上边记 &lt;code&gt;-1&lt;/code&gt; 表示矩形结束，然后按 &lt;code&gt;y&lt;/code&gt; 从小到大扫描。扫描到某条边时，就在线段树中给它对应的 &lt;code&gt;x&lt;/code&gt; 区间加上或减去覆盖次数；线段树的 &lt;code&gt;len[1]&lt;/code&gt; 始终表示当前所有矩形在 x 轴上的总覆盖长度，重叠部分只算一次。因此从当前高度 &lt;code&gt;L[i].y&lt;/code&gt; 到下一条边 &lt;code&gt;L[i+1].y&lt;/code&gt; 之间的面积就是 &lt;code&gt;len[1] * (L[i+1].y-L[i].y)&lt;/code&gt;。由于 x 坐标可能很大，所以先把所有 &lt;code&gt;x1、x2&lt;/code&gt; 排序去重做离散化；其中离散后的第 &lt;code&gt;i&lt;/code&gt; 段实际上表示 &lt;code&gt;[X[i],X[i+1])&lt;/code&gt;，因此原区间 &lt;code&gt;[x1,x2)&lt;/code&gt; 要在线段树中更新 &lt;code&gt;[l,r-1]&lt;/code&gt;，而节点 &lt;code&gt;[l,r]&lt;/code&gt; 的真实长度就是 &lt;code&gt;X[r+1]-X[l]&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;py代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# 扫描线 + 线段树 + 离散化

from bisect import bisect_left
import sys
input = sys.stdin.readline
N = 200005
# L[i] = [x1, x2, y, tag]
L = []
# 线段树
cnt = [0] * (N * 8)
length = [0] * (N * 8)
# X 坐标，保持 1 下标
X = [0] * (N * 2)
def pushup(u, l, r):
    if cnt[u]:
        length[u] = X[r + 1] - X[l]   # r → X[r+1]
    else:
        length[u] = length[u &amp;#x3C;&amp;#x3C; 1] + length[u &amp;#x3C;&amp;#x3C; 1 | 1]
def change(u, l, r, a, b, tag):
    if a &gt; r or b &amp;#x3C; l:
        return  # 越界
    if a &amp;#x3C;= l and r &amp;#x3C;= b:
        cnt[u] += tag
        pushup(u, l, r)
        return
    m = (l + r) &gt;&gt; 1
    change(u &amp;#x3C;&amp;#x3C; 1, l, m, a, b, tag)          # 裂开
    change(u &amp;#x3C;&amp;#x3C; 1 | 1, m + 1, r, a, b, tag)
    pushup(u, l, r)

def main():
    n = int(input())
    xs = []
    for i in range(1, n + 1):
        x1, y1, x2, y2 = map(int, input().split())
        L.append([x1, x2, y1, 1])
        L.append([x1, x2, y2, -1])
        xs.append(x1)
        xs.append(x2)

    n *= 2
    # 扫描线排序
    L.sort(key=lambda x: x[2])
    # X 坐标排序 + 去重
    xs = sorted(set(xs))
    s = len(xs)
    for i in range(s):
        X[i + 1] = xs[i]
    ans = 0
    for i in range(n - 1):
        l = bisect_left(xs, L[i][0]) + 1
        r = bisect_left(xs, L[i][1]) + 1
        # x2 → r-1
        change(1, 1, s, l, r - 1, L[i][3])
        ans += (L[i + 1][2] - L[i][2]) * length[1]
    print(ans)


if __name__ == &quot;__main__&quot;:
    main()
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;// 扫描线+线段树+离散化 1.4s
#include &amp;#x3C;iostream&gt;
#include &amp;#x3C;cstdio&gt;
#include &amp;#x3C;algorithm&gt;
using namespace std;

#define ls u&amp;#x3C;&amp;#x3C;1
#define rs u&amp;#x3C;&amp;#x3C;1|1
const int N=200005;
struct line{   //扫描线
  int x1,x2,y;
  int tag;     //入边:+1,出边:-1
  bool operator&amp;#x3C;(line &amp;#x26;t){return y&amp;#x3C;t.y;}
}L[N];
int cnt[N*8],len[N*8]; //线段树
int X[N];              //X坐标
void pushup(int u,int l, int r){
  if(cnt[u]) len[u]=X[r+1]-X[l]; //r → X[r+1]
  else len[u]=len[ls]+len[rs];
}
void change(int u,int l,int r,int a,int b,int tag){
  if(a&gt;r || b&amp;#x3C;l) return; //越界
  if(a&amp;#x3C;=l &amp;#x26;&amp;#x26; r&amp;#x3C;=b){      //覆盖
    cnt[u]+=tag;
    pushup(u,l,r);
    return;
  }
  int m=l+r&gt;&gt;1;
  change(ls,l,m,a,b,tag); //裂开
  change(rs,m+1,r,a,b,tag);
  pushup(u,l,r);
}
int main(){
  int n,x1,x2,y1,y2; scanf(&quot;%d&quot;,&amp;#x26;n);
  for(int i=1; i&amp;#x3C;=n; i++){
    scanf(&quot;%d%d%d%d&quot;,&amp;#x26;x1,&amp;#x26;y1,&amp;#x26;x2,&amp;#x26;y2);
    L[i]={x1,x2,y1,1};
    L[n+i]={x1,x2,y2,-1};
    X[i]=x1; X[n+i]=x2;         
  }
  n*=2;
  sort(L+1,L+n+1); //扫描线排序
  sort(X+1,X+n+1); //X坐标排序
  int s=unique(X+1,X+n+1)-X-1; //去重
  
  long long ans=0;
  for(int i=1; i&amp;#x3C;n; i++){
    int l=lower_bound(X+1,X+s+1,L[i].x1)-X;
    int r=lower_bound(X+1,X+s+1,L[i].x2)-X;
    change(1,1,s,l,r-1,L[i].tag); //x2 → r-1
    ans+=1ll*(L[i+1].y-L[i].y)*len[1];
  }
  printf(&quot;%lld\n&quot;,ans);
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>C10 KD树</title><link>https://ehnotgod.site/blog/c10-kd%E6%A0%91</link><guid isPermaLink="true">https://ehnotgod.site/blog/c10-kd%E6%A0%91</guid><description>KD 树：多维最近点对 / 最近邻查询。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P1429&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;给定平面上 $$n$$ 个点，找出其中的一对点的距离，使得在这 $$n$$ 个点的所有点对中，该距离为所有点对中最小的&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行：$$n$$ ，保证 $$2\le n\le 200000$$ 。&lt;/p&gt;
&lt;p&gt;接下来 $$n$$ 行：每行两个实数：$$x\ y$$ ，表示一个点的行坐标和列坐标，中间用一个空格隔开。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;仅一行，一个实数，表示最短距离，精确到小数点后面 $$4$$ 位。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入输出样例 #1&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;3
1 1
1 2
2 2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1.0000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;数据保证 $$0\le x,y\le 10^9$$&lt;/p&gt;
&lt;h3&gt;算法解析&lt;/h3&gt;
&lt;p&gt;KD-Tree 可以理解成一棵“按坐标不断切分平面”的二叉树。建树时，第一层按 x 坐标找中位数作为根，下一层按 y 坐标，再下一层又按 x，交替进行。这样每个节点既代表一个点，也代表它整棵子树中所有点所在的一个矩形区域，也就是包围盒 &lt;code&gt;L[] ~ U[]&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;查询一个点最近的已有点时，从根开始 DFS。每到一个节点，先计算查询点到当前节点的真实距离，用它更新当前最优答案 &lt;code&gt;ans&lt;/code&gt;。然后分别计算查询点到左、右子树包围盒的最小可能距离 &lt;code&gt;dl、dr&lt;/code&gt;。这个值表示：即使子树里存在最理想的点，它也不可能比这个距离更近。&lt;/p&gt;
&lt;p&gt;如果某个子树的包围盒最小距离已经 &lt;code&gt;&gt;= ans&lt;/code&gt;，说明这棵子树里的所有点都不可能成为更优答案，可以直接剪枝；否则继续递归。通常先搜索 &lt;code&gt;dl、dr&lt;/code&gt; 较小的一边，这样更容易提前找到更小的 &lt;code&gt;ans&lt;/code&gt;，从而剪掉另一边更多节点。&lt;/p&gt;
&lt;p&gt;你现在把任意查询点放进 &lt;code&gt;t[0]&lt;/code&gt;，再设 &lt;code&gt;cur=0&lt;/code&gt;，就可以直接复用原来的 &lt;code&gt;dis()&lt;/code&gt;、&lt;code&gt;dis2()&lt;/code&gt; 和 &lt;code&gt;query()&lt;/code&gt;。因此整个算法核心就是：&lt;strong&gt;KD-Tree 建树负责把点划分成区域，query 通过“点到包围盒的距离下界”进行剪枝，从而快速找到最近点。&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;py代码实现&lt;/h3&gt;
&lt;p&gt;略&lt;/p&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;// 交替建树 970ms
#include &amp;#x3C;cstdio&gt;
#include &amp;#x3C;cstring&gt;
#include &amp;#x3C;algorithm&gt;
#include &amp;#x3C;cmath&gt;
#define lc t[p].l
#define rc t[p].r
using namespace std;

const int N=200010;
double ans=2e18;
int n,K,root,cur; //K维度,root根,cur当前节点
struct KD{     //KD树节点信息
  int l,r;     //左右孩子
  double v[2]; //点的坐标值
  double L[2],U[2]; //子树区域的坐标范围
  bool operator&amp;#x3C;(const KD &amp;#x26;b)const{return v[K]&amp;#x3C;b.v[K];}
}t[N];

void pushup(int p){ //更新p子树区域的坐标范围
  for(int i=0;i&amp;#x3C;2;i++){
    t[p].L[i]=t[p].U[i]=t[p].v[i];
    if(lc)
      t[p].L[i]=min(t[p].L[i],t[lc].L[i]),
      t[p].U[i]=max(t[p].U[i],t[lc].U[i]);
    if(rc)
      t[p].L[i]=min(t[p].L[i],t[rc].L[i]),
      t[p].U[i]=max(t[p].U[i],t[rc].U[i]);
  }
}
int build(int l,int r,int k){ //交替建树
  if(l&gt;r) return 0;
  int m=(l+r)&gt;&gt;1; 
  K=k; nth_element(t+l,t+m,t+r+1); //中位数
  t[m].l=build(l,m-1,k^1);
  t[m].r=build(m+1,r,k^1);
  pushup(m);
  return m;
}
double sq(double x){return x*x;}
double dis(int p){ //当前点到p点的距离
  double s=0;
  for(int i=0;i&amp;#x3C;2;i++) 
    s+=sq(t[cur].v[i]-t[p].v[i]);
  return s;
}
double dis2(int p){ //当前点到p子树区域的最小距离
  if(!p) return 2e18; 
  double s=0;
  for(int i=0;i&amp;#x3C;2;++i)
    s+=sq(max(t[cur].v[i]-t[p].U[i],0.0))+
       sq(max(t[p].L[i]-t[cur].v[i],0.0));
  return s;
}
void query(int p){ //查询当前点的最小距离
  if(!p) return;
  if(p!=cur) ans=min(ans,dis(p));
  double dl=dis2(lc),dr=dis2(rc);
  if(dl&amp;#x3C;dr){
    if(dl&amp;#x3C;ans) query(lc);
    if(dr&amp;#x3C;ans) query(rc);
  }
  else{
    if(dr&amp;#x3C;ans) query(rc);
    if(dl&amp;#x3C;ans) query(lc);
  }
}
int main(){
  scanf(&quot;%d&quot;,&amp;#x26;n);
  for(int i=1; i&amp;#x3C;=n; i++)
    scanf(&quot;%lf%lf&quot;,&amp;#x26;t[i].v[0],&amp;#x26;t[i].v[1]);
  root=build(1,n,0);
  for(cur=1; cur&amp;#x3C;=n; cur++) query(root);
  printf(&quot;%.4lf\n&quot;,sqrt(ans));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用以下代码可以查任意点到点群的最近距离：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;double queryPoint(double x,double y){
  t[0].v[0]=x;
  t[0].v[1]=y;
  cur=0;
  ans=2e18;
  query(root);
  return sqrt(ans);
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>C11 莫队</title><link>https://ehnotgod.site/blog/c11-%E8%8E%AB%E9%98%9F</link><guid isPermaLink="true">https://ehnotgod.site/blog/c11-%E8%8E%AB%E9%98%9F</guid><description>莫队算法：离线区间查询，分块排序优化。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P2709&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;小B 有一个长为 $$n$$ 的整数序列 $$a$$，值域为 $$[1,k]$$。  他一共有 $$m$$ 个询问，每个询问给定一个区间 $$[l,r]$$，求：  $$\sum\limits_{i=1}^k c_i^2$$&lt;/p&gt;
&lt;p&gt;其中 $$c_i$$ 表示数字 $$i$$ 在 $$[l,r]$$ 中的出现次数。  小B请你帮助他回答询问。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行三个整数 $$n,m,k$$。&lt;/p&gt;
&lt;p&gt;第二行 $$n$$ 个整数，表示 小B 的序列。&lt;/p&gt;
&lt;p&gt;接下来的 $$m$$ 行，每行两个整数 $$l,r$$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输出 $$m$$ 行，每行一个整数，对应一个询问的答案。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;6 4 3
1 3 2 1 1 3
1 4
2 6
3 5
5 6
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;6
9
5
2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于 $$100%$$ 的数据，$$1\le n,m,k \le 5\times 10^4$$。&lt;/p&gt;
&lt;h3&gt;算法解析&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;images/C11-1.png&quot; alt=&quot;image-20250519115428164&quot;&gt;&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import sys
import math
from collections import defaultdict
sys.setrecursionlimit(10**7)
input = sys.stdin.readline
n, m, k = map(int, input().split())
a = [0] + list(map(int, input().split()))

B = int(math.sqrt(n))

q = []
for i in range(m):
    l, r = map(int, input().split())
    q.append((l, r, i))

def mo_key(x):
    block = x[0] // B
    return (block, x[1] if block % 2 == 0 else -x[1])

q.sort(key=mo_key)

c = defaultdict(int)
sum = 0

def add(x):
    global sum
    sum -= c[x] * c[x]
    c[x] += 1
    sum += c[x] * c[x]

def remove(x):
    global sum
    sum -= c[x] * c[x]
    c[x] -= 1
    sum += c[x] * c[x]

ans = [0] * m
l, r = 1, 0
for L, R, idx in q:
    while l &gt; L:
        l -= 1
        add(a[l])
    while r &amp;#x3C; R:
        r += 1
        add(a[r])
    while l &amp;#x3C; L:
        remove(a[l])
        l += 1
    while r &gt; R:
        remove(a[r])
        r -= 1
    ans[idx] = sum

print(&quot;\n&quot;.join(map(str, ans)))
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;// 普通莫队 O(n*sqrt(n))
#include &amp;#x3C;iostream&gt;
#include &amp;#x3C;cstring&gt;
#include &amp;#x3C;algorithm&gt;
#include &amp;#x3C;cmath&gt;
using namespace std;

const int N=50005;
int n,m,k,B,a[N];
int sum,c[N],ans[N];
struct Q{
  int l,r,id;
  //按l所在块的编号l/B和r排序
  bool operator&amp;#x3C;(Q &amp;#x26;b){
    if(l/B!=b.l/B) return l&amp;#x3C;b.l;
    if((l/B)&amp;#x26;1) return r&amp;#x3C;b.r;
    return r&gt;b.r;
  }
}q[N];

void add(int x){ //扩展一个数
  sum-=c[x]*c[x];
  c[x]++;
  sum+=c[x]*c[x];
}
void del(int x){ //删除一个数
  sum-=c[x]*c[x];
  c[x]--;
  sum+=c[x]*c[x];
}
int main(){
  scanf(&quot;%d%d%d&quot;,&amp;#x26;n,&amp;#x26;m,&amp;#x26;k);
  B=sqrt(n); //块的大小
  for(int i=1;i&amp;#x3C;=n;++i)scanf(&quot;%d&quot;,&amp;#x26;a[i]);
  for(int i=1;i&amp;#x3C;=m;++i)
    scanf(&quot;%d%d&quot;,&amp;#x26;q[i].l,&amp;#x26;q[i].r),q[i].id=i;
  sort(q+1,q+1+m); //按l/B和r排序
  for(int i=1,l=1,r=0;i&amp;#x3C;=m;++i){
    while(l&gt;q[i].l) add(a[--l]);//左扩展
    while(r&amp;#x3C;q[i].r) add(a[++r]);//右扩展
    while(l&amp;#x3C;q[i].l) del(a[l++]);//左删除
    while(r&gt;q[i].r) del(a[r--]);//右删除
    ans[q[i].id]=sum;
  }
  for(int i=1;i&amp;#x3C;=m;++i)printf(&quot;%d\n&quot;,ans[i]);
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>C12 李超线段树</title><link>https://ehnotgod.site/blog/c12-%E6%9D%8E%E8%B6%85%E7%BA%BF%E6%AE%B5%E6%A0%91</link><guid isPermaLink="true">https://ehnotgod.site/blog/c12-%E6%9D%8E%E8%B6%85%E7%BA%BF%E6%AE%B5%E6%A0%91</guid><description>李超线段树：维护一次函数最值。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P4254&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Blue Mary 开公司，会陆续收到若干金融顾问的经营方案。每个顾问给出一个等差日收益序列：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Project S P&lt;/code&gt; 表示第 $T$ 天的收益为 $f(T)=S+(T-1)\cdot P$；&lt;/li&gt;
&lt;li&gt;初始时没有方案，任意天的最大收益为 0。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;当收到若干方案后，Blue Mary 会进行若干次询问：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Query T&lt;/code&gt; 要求返回当前已知所有方案在第 $T$ 天的&lt;strong&gt;最大收益&lt;/strong&gt;（与 0 比较取较大者），并&lt;strong&gt;以百元为单位向下取整&lt;/strong&gt;输出（例如：210 或 290 都输出 &lt;code&gt;2&lt;/code&gt;）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行一个整数 $N$，表示操作总数；接下来 $N$ 行每行为一个操作，格式如题。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;10
Project 5.10200 0.65000
Project 2.76200 1.43000
Query 4
Query 2
Project 3.80200 1.17000
Query 2
Query 3
Query 1
Project 4.58200 0.91000
Project 5.36200 0.39000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;0
0
0
0
0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;说明/提示&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;对于 $100%$ 的数据，$1\le N\le 10^5$，$1\le T\le 5\times 10^4$，$0&amp;#x3C;P&amp;#x3C;100$，$|S|\le 10^5$。&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;李超线段树用来维护若干一次函数（直线）在某个横坐标上的最值。本题中每个经营方案对应一条直线 $f(T)=S+(T-1)\cdot P$，需要在某一天 $T$ 上查询所有方案取值的最大值。&lt;/p&gt;
&lt;p&gt;线段树每个结点存一条&quot;优势直线&quot;（在当前区间中点上取值最大的一条）。插入新直线时，若新直线在中点更优则与当前直线交换，再根据左右端点的比较结果向对应的儿子递归；查询时沿根到叶子的路径取所有经过结点直线在 $x$ 处的最大值。单次插入与查询均为 $O(\log V)$（$V$ 为坐标范围）。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/C12-1.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;本题为银牌难度，实现细节较多，建议先理解&quot;优势直线&quot;的维护思想。&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;p&gt;太难，略。&lt;/p&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;// 李超线段树 O(nlogn)
#include &amp;#x3C;iostream&gt;
#include &amp;#x3C;cstring&gt;
#include &amp;#x3C;algorithm&gt;
using namespace std;

#define N 50005
#define ls u&amp;#x3C;&amp;#x3C;1
#define rs u&amp;#x3C;&amp;#x3C;1|1
int n,cnt;
struct line{
  double k,b; //斜率,截距
}p[N*2];
int tr[N*4]; //线段编号

double Y(int id,int x){ //求Y值
  return p[id].k*x+p[id].b;
}
void change(int u,int l,int r,int id){ //修改
  int mid=(l+r)&gt;&gt;1;
  if(Y(id,mid)&gt;Y(tr[u],mid)) swap(id,tr[u]);
  if(Y(id,l)&gt;Y(tr[u],l)) change(ls,l,mid,id);
  if(Y(id,r)&gt;Y(tr[u],r)) change(rs,mid+1,r,id);
}
double query(int u,int l,int r,int x){ //查询
  if(l==r) return Y(tr[u],x);
  int mid=(l+r)&gt;&gt;1;
  double t=Y(tr[u],x);
  if(x&amp;#x3C;=mid) return max(t,query(ls,l,mid,x));
  else return max(t,query(rs,mid+1,r,x));
}
int main(){
  scanf(&quot;%d&quot;,&amp;#x26;n);
  for(int i=1;i&amp;#x3C;=n;i++){
    char op[10]; scanf(&quot;%s&quot;,op);
    if(op[0]==&apos;P&apos;){
      double b,k; scanf(&quot;%lf%lf&quot;,&amp;#x26;b,&amp;#x26;k);
      p[++cnt]={k,b-k};
      change(1,1,N,cnt);
    }
    else{
      int x; scanf(&quot;%d&quot;,&amp;#x26;x);
      printf(&quot;%d\n&quot;,(int)query(1,1,N,x)/100);
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>D1 拓扑排序</title><link>https://ehnotgod.site/blog/d1-%E6%8B%93%E6%89%91%E6%8E%92%E5%BA%8F</link><guid isPermaLink="true">https://ehnotgod.site/blog/d1-%E6%8B%93%E6%89%91%E6%8E%92%E5%BA%8F</guid><description>拓扑排序：有向无环图的线性排序。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/B3644&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;有个人的家族很大，辈分关系很混乱，请你帮整理一下这种关系。给出每个人的后代的信息。输出一个序列，使得每个人的后辈都比那个人后列出。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第 $$1$$ 行一个整数 $$N$$（$$1 \le N \le 100$$），表示家族的人数。接下来 $$N$$ 行，第 $$i$$ 行描述第 $$i$$ 个人的后代编号 $$a_{i,j}$$，表示 $$a_{i,j}$$ 是 $$i$$ 的后代。每行最后是 $$0$$ 表示描述完毕。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输出一个序列，使得每个人的后辈都比那个人后列出。如果有多种不同的序列，输出任意一种即可。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;5
0
4 5 1 0
1 0
5 3 0
3 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2 4 5 3 1
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;Kahn 算法：统计每个点的入度，把入度为 0 的点入队；每次取出队首加入拓扑序列，并把其后继的入度减 1，若减到 0 则入队。若队列空时还没遍历完所有点，说明图中有环。复杂度 $O(n+m)$。&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# 拓扑排序 Kahn算法 O(V+E)
from collections import deque
N = 110
n = int(input())
rd = [0] * N          # 入度
e = [[] for _ in range(N)]   # 邻接表
tp = []               # 拓扑序
def topo():
    q = deque()
    # 入度为0的点全部入队
    for i in range(1, n + 1):
        if rd[i] == 0:
            q.append(i)
    while q:
        u = q.popleft()     # 出队
        tp.append(u)        # 记录拓扑序
        for v in e[u]:
            rd[v] -= 1      # 删除 u-&gt;v 这条边
            if rd[v] == 0:
                q.append(v)
    return len(tp) == n
for i in range(1, n + 1):
    a = list(map(int, input().split()))
    for j in a:
        if j == 0:
            break
        e[i].append(j)
        rd[j] += 1
topo()
print(*tp)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;// Kahn算法 O(n)
// 拓扑排序 Kahn算法 O(V+E)
#include&amp;#x3C;bits/stdc++.h&gt;
using namespace std;

const int N=110;
int n,rd[N];
vector&amp;#x3C;int&gt; e[N],tp;

bool topo(){
  queue&amp;#x3C;int&gt; q;
  for(int i=1; i&amp;#x3C;=n; i++) if(!rd[i]) q.push(i); //入度为0的点均入队
  while(q.size()){
    int u=q.front(); q.pop(); //出队
    tp.push_back(u); //记录拓扑序
    for(auto v:e[u]) if(--rd[v]==0) q.push(v); //入队
  }
  return tp.size()==n;
}
int main(){
  cin&gt;&gt;n;
  for(int i=1,j; i&amp;#x3C;=n; i++){
    while(cin&gt;&gt;j,j){
      e[i].push_back(j);
      rd[j]++; //入度
    }
  }
  topo();
  for(int i=0; i&amp;#x3C;n; i++) cout&amp;#x3C;&amp;#x3C;tp[i]&amp;#x3C;&amp;#x3C;&quot; &quot;;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>D2 狄克斯特拉算法</title><link>https://ehnotgod.site/blog/d2-%E7%8B%84%E5%85%8B%E6%96%AF%E7%89%B9%E6%8B%89%E7%AE%97%E6%B3%95</link><guid isPermaLink="true">https://ehnotgod.site/blog/d2-%E7%8B%84%E5%85%8B%E6%96%AF%E7%89%B9%E6%8B%89%E7%AE%97%E6%B3%95</guid><description>Dijkstra 最短路：非负权，堆优化。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：&lt;/p&gt;
&lt;p&gt;https://www.luogu.com.cn/problem/P3371&lt;/p&gt;
&lt;p&gt;https://www.luogu.com.cn/problem/P4779&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;如题，给出一个有向图，请输出从某一点出发到所有点的最短路径长度。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行包含三个整数 $$n,m,s$$，分别表示点的个数、有向边的个数、出发点的编号。&lt;/p&gt;
&lt;p&gt;接下来 $$m$$ 行每行包含三个整数 $$u,v,w$$，表示一条 $$u \to v$$ 的，长度为 $$w$$ 的边。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输出一行 $$n$$ 个整数，第 $$i$$ 个表示 $$s$$ 到第 $$i$$ 个点的最短路径，若不能到达则输出 $$2^{31}-1$$。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;4 6 1
1 2 2
2 3 2
2 4 1
1 3 5
3 4 3
1 4 4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;0 2 4 3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于 $$100%$$ 的数据：$$1 \le n \le 10^4$$，$$1\le m \le 5\times 10^5$$，$$1\le u,v\le n$$，$$w\ge 0$$，$$\sum w&amp;#x3C; 2^{31}$$，保证数据随机。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;堆优化版题目：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;给定一个 $$n$$ 个点，$$m$$ 条有向边的带非负权图，请你计算从 $$s$$ 出发，到每个点的距离。&lt;/p&gt;
&lt;p&gt;数据保证你能从 $$s$$ 出发到任意点。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行为三个正整数 $$n, m, s$$。
第二行起 $$m$$ 行，每行三个非负整数 $$u_i, v_i, w_i$$，表示从 $$u_i$$ 到 $$v_i$$ 有一条权值为 $$w_i$$ 的有向边。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输出一行 $$n$$ 个空格分隔的非负整数，表示 $$s$$ 到每个点的距离。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;4 6 1
1 2 2
2 3 2
2 4 1
1 3 5
3 4 3
1 4 4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;0 2 4 3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;$$1 \leq n \leq 10^5$$；&lt;/p&gt;
&lt;p&gt;$$1 \leq m \leq 2\times 10^5$$；&lt;/p&gt;
&lt;p&gt;$$s = 1$$；&lt;/p&gt;
&lt;p&gt;$$1 \leq u_i, v_i\leq n$$；&lt;/p&gt;
&lt;p&gt;$$0 \leq w_i \leq 10 ^ 9$$,&lt;/p&gt;
&lt;p&gt;$$0 \leq \sum w_i \leq 10 ^ 9$$。&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;images/D2-1.png&quot; alt=&quot;image-20250418090828454&quot;&gt;&lt;/p&gt;
&lt;p&gt;堆优化版：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/D2-2.png&quot; alt=&quot;image-20250418091036262&quot;&gt;&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class Edge():
    def __init__(self, v, w):
        self.v = v
        self.w = w

def dijkstra(s):
    for i in range(n + 1):
        d[i] = int(2 ** 31 - 1)
    d[s] = 0
    for i in range(1, n):
        u = 0
        for j in range(1, n + 1):
            if not vis[j] and d[j] &amp;#x3C; d[u]:
                u = j
        vis[u] = 1
        for ed in e[u]:
            v = ed.v
            w = ed.w
            if d[v] &gt; d[u] + w:
                d[v] = d[u] + w

n, m, s = map(int, input().split())
e = [[] for i in range(n + 1)]
d = [0] * (n + 1)
vis = [0] * (n + 1)
for i in range(m):
    a, b, c = map(int, input().split())
    e[a].append(Edge(b, c))

dijkstra(s)
for i in range(1, n + 1):
    print(d[i], end=&quot; &quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import heapq

class Edge():
    def __init__(self, v, w):
        self.v = v
        self.w = w

def dijkstra(s):
    for i in range(n + 1):
        d[i] = int(1e20)
    d[s] = 0
    q = []
    heapq.heappush(q, [0, s])
    while len(q) &gt; 0:
        t = heapq.heappop(q)
        u = t[1]
        if vis[u]:
            continue
        vis[u] = 1
        for ed in e[u]:
            v = ed.v
            w = ed.w
            if d[v] &gt; d[u] + w:
                d[v] = d[u] + w
                heapq.heappush(q, [d[v], v])

n, m, s = map(int, input().split())
e = [[] for i in range(n + 1)]
d = [0] * (n + 1)
vis = [0] * (n + 1)
for i in range(m):
    a, b, c = map(int, input().split())
    e[a].append(Edge(b, c))

dijkstra(s)
for i in range(1, n + 1):
    print(d[i], end=&quot; &quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;//Dijkstra
#include &amp;#x3C;iostream&gt;
#include &amp;#x3C;cstring&gt;
#include &amp;#x3C;algorithm&gt;
#include &amp;#x3C;vector&gt;
#define inf 2147483647
using namespace std;

int n,m,s,a,b,c;
const int N=100010;
struct edge{int v,w;};
vector&amp;#x3C;edge&gt; e[N];
int d[N], vis[N];

void dijkstra(int s){
  for(int i=0;i&amp;#x3C;=n;i++)d[i]=inf;
  d[s]=0;
  for(int i=1;i&amp;#x3C;n;i++){//枚举次数
    int u=0;
    for(int j=1;j&amp;#x3C;=n;j++)//枚举点
      if(!vis[j]&amp;#x26;&amp;#x26;d[j]&amp;#x3C;d[u]) u=j;
    vis[u]=1; //标记u已出圈
    for(auto ed:e[u]){//枚举邻边
      int v=ed.v, w=ed.w;
      if(d[v]&gt;d[u]+w){
        d[v]=d[u]+w;
      }
    }
  }
}
int main(){
  cin&gt;&gt;n&gt;&gt;m&gt;&gt;s;
  for(int i=0; i&amp;#x3C;m; i++){
    cin&gt;&gt;a&gt;&gt;b&gt;&gt;c;
    e[a].push_back({b,c});
  }
  dijkstra(s);
  for(int i=1;i&amp;#x3C;=n;i++)
    printf(&quot;%d &quot;,d[i]);
  return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;//堆优化Dijkstra
#include &amp;#x3C;cstring&gt;
#include &amp;#x3C;iostream&gt;
#include &amp;#x3C;algorithm&gt;
#include &amp;#x3C;queue&gt;
using namespace std;

const int N=100010;
int n,m,s,a,b,c;
struct edge{int v,w;};
vector&amp;#x3C;edge&gt; e[N];
int d[N],vis[N];

void dijkstra(int s){
  memset(d,0x3f,sizeof d); d[s]=0;
  priority_queue&amp;#x3C;pair&amp;#x3C;int,int&gt;&gt; q;
  q.push({0,s});
  while(q.size()){
    auto t=q.top(); q.pop();
    int u=t.second;
    if(vis[u])continue; //再出队跳过
    vis[u]=1; //标记u已出队
    for(auto ed : e[u]){
      int v=ed.v, w=ed.w;
      if(d[v]&gt;d[u]+w){
        d[v]=d[u]+w;
        q.push({-d[v],v}); //大根堆
      }
    }
  }
}
int main(){
  cin&gt;&gt;n&gt;&gt;m&gt;&gt;s;
  for(int i=0; i&amp;#x3C;m; i++)
    cin&gt;&gt;a&gt;&gt;b&gt;&gt;c, e[a].push_back({b,c});
  dijkstra(s);
  for(int i=1;i&amp;#x3C;=n;i++) printf(&quot;%d &quot;,d[i]);
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>D3 SPFA算法</title><link>https://ehnotgod.site/blog/d3-spfa%E7%AE%97%E6%B3%95</link><guid isPermaLink="true">https://ehnotgod.site/blog/d3-spfa%E7%AE%97%E6%B3%95</guid><description>SPFA 最短路：队列优化的 Bellman-Ford。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P3385&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;给定一个 $$n$$ 个点的有向图，请求出图中是否存在&lt;strong&gt;从顶点 $$1$$ 出发能到达&lt;/strong&gt;的负环。&lt;/p&gt;
&lt;p&gt;负环的定义是：一条边权之和为负数的回路。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输入的第一行是一个整数 $$T$$，表示测试数据的组数。对于每组数据的格式如下：&lt;/p&gt;
&lt;p&gt;第一行有两个整数，分别表示图的点数 $$n$$ 和接下来给出边信息的条数 $$m$$。&lt;/p&gt;
&lt;p&gt;接下来 $$m$$ 行，每行三个整数 $$u, v, w$$。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;若 $$w \geq 0$$，则表示存在一条从 $$u$$ 至 $$v$$ 边权为 $$w$$ 的边，还存在一条从 $$v$$ 至 $$u$$ 边权为 $$w$$ 的边。&lt;/li&gt;
&lt;li&gt;若 $$w &amp;#x3C; 0$$，则只表示存在一条从 $$u$$ 至 $$v$$ 边权为 $$w$$ 的边。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;对于每组数据，输出一行一个字符串，若所求负环存在，则输出 &lt;code&gt;YES&lt;/code&gt;，否则输出 &lt;code&gt;NO&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2
3 4
1 2 2
1 3 4
2 3 1
3 1 -3
3 3
1 2 3
2 3 4
3 1 -8
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;NO
YES
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于全部的测试点，保证：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$$1 \leq n \leq 2 \times 10^3$$，$$1 \leq m \leq 3 \times 10^3$$。&lt;/li&gt;
&lt;li&gt;$$1 \leq u, v \leq n$$，$$-10^4 \leq w \leq 10^4$$。&lt;/li&gt;
&lt;li&gt;$$1 \leq T \leq 10$$。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;SPFA 是队列优化的 Bellman-Ford，适合含负权边的图。维护队列，松弛成功的点入队；同时记录最短路经过的边数 $cnt[v]$，若 $cnt[v]\ge n$，说明从起点可达一条负环。也可用入队次数判断：某点入队超过 $n$ 次即有负环。最坏复杂度 $O(nm)$。&lt;/p&gt;
&lt;h3&gt;py代码实现：&lt;/h3&gt;
&lt;p&gt;略&lt;/p&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include &amp;#x3C;bits/stdc++.h&gt;
using namespace std;
const int N = 2010;
int n, m;
int d[N], vis[N], cnt[N];
struct Edge {
    int v, w;
};
vector&amp;#x3C;Edge&gt; g[N];
bool spfa() { // 判负环
    memset(d, 0x3f, sizeof d); d[1] = 0;
    memset(vis, 0, sizeof vis);
    memset(cnt, 0, sizeof cnt);
    queue&amp;#x3C;int&gt; q; 
    q.push(1); 
    vis[1] = 1; // 在队中
    while (!q.empty()) {
        int u = q.front(); q.pop(); 
        vis[u] = 0;
        for (auto &amp;#x26;e : g[u]) {
            int v = e.v, w = e.w;
            if (d[v] &gt; d[u] + w) { // 松弛
                d[v] = d[u] + w;
                cnt[v] = cnt[u] + 1; // 边数
                if (cnt[v] &gt;= n) return true; // 有负环
                if (!vis[v]) {
                    q.push(v);
                    vis[v] = 1;
                }
            }
        }
    }
    return false;
}

int main() {
    int T; 
    scanf(&quot;%d&quot;, &amp;#x26;T);
    while (T--) {
        scanf(&quot;%d%d&quot;, &amp;#x26;n, &amp;#x26;m);
        for (int i = 1; i &amp;#x3C;= n; i++) g[i].clear();
        for (int i = 1; i &amp;#x3C;= m; i++) {
            int u, v, w;
            scanf(&quot;%d%d%d&quot;, &amp;#x26;u, &amp;#x26;v, &amp;#x26;w);
            g[u].push_back({v, w});
            if (w &gt;= 0) g[v].push_back({u, w});
        }
        puts(spfa() ? &quot;YES&quot; : &quot;NO&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>D4 最短路 Floyd 算法</title><link>https://ehnotgod.site/blog/d4-%E6%9C%80%E7%9F%AD%E8%B7%AF-floyd-%E7%AE%97%E6%B3%95</link><guid isPermaLink="true">https://ehnotgod.site/blog/d4-%E6%9C%80%E7%9F%AD%E8%B7%AF-floyd-%E7%AE%97%E6%B3%95</guid><description>Floyd 最短路：O(n³) 全源最短路与传递闭包。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/B3647&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;给出一张由 $n$ 个点 $m$ 条边组成的无向连通图。&lt;/p&gt;
&lt;p&gt;求出所有点对 $(i,j)$ 之间的最短路径。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行为两个整数 $n,m$，分别代表点的个数和边的条数。&lt;/p&gt;
&lt;p&gt;接下来 $m$ 行，每行三个整数 $u,v,w$，代表 $u,v$ 之间存在一条边权为 $w$ 的边。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输出 $n$ 行每行 $n$ 个整数。&lt;/p&gt;
&lt;p&gt;第 $i$ 行的第 $j$ 个整数代表从 $i$ 到 $j$ 的最短路径。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;4 4
1 2 1
2 3 1
3 4 1
4 1 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;0 1 2 1
1 0 1 2
2 1 0 1
1 2 1 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于 $100%$ 的数据，$n \le 100$，$m \le 4500$，任意一条边的权值 $w$ 是正整数且 $1 \leqslant w \leqslant 1000$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;数据中可能存在重边。&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;images/D4-1.png&quot; alt=&quot;image-20250418091227375&quot;&gt;&lt;/p&gt;
&lt;p&gt;Floyd 是动态规划求全源最短路：$d[k][i][j]$ 表示只允许经过前 $k$ 个中间点时 $i$ 到 $j$ 的最短路，滚动后即为 $d[i][j]=\min(d[i][j],d[i][k]+d[k][j])$。三重循环 $O(n^3)$，可处理负权边但不能有负环；也用于求传递闭包。注意 $k$ 在最外层。&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def floyd():
    for k in range(1, n + 1):
        for i in range(1, n + 1):
            for j in range(1, n + 1):
                d[i][j] = min(d[i][j], d[i][k] + d[k][j])


n, m = map(int, input().split())
d = [[int(1e20) for __ in range(n + 1)] for _ in range(n + 1)]
for i in range(1, n + 1):
    d[i][i] = 0
for i in range(m):
    a, b, c = map(int, input().split())
    d[a][b] = min(d[a][b], c)
    floyd()
for i in range(1, n + 1):
    print(*d[i][1:])
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include &amp;#x3C;iostream&gt;
#include &amp;#x3C;cstring&gt;
#include &amp;#x3C;algorithm&gt;
using namespace std;
const int N=210,M=20010;
int n,m,a,b,c;
int d[N][N];

void floyd(){
  for(int k=1; k&amp;#x3C;=n; k++)
    for(int i=1; i&amp;#x3C;=n; i++)
      for(int j=1; j&amp;#x3C;=n; j++)
        d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
}
int main(){
  cin&gt;&gt;n&gt;&gt;m;
  memset(d,0x3f,sizeof d);
  for(int i=1; i&amp;#x3C;=n; i++)d[i][i]=0;
  for(int i=0; i&amp;#x3C;m; i++){
    cin&gt;&gt;a&gt;&gt;b&gt;&gt;c;
    d[a][b]=min(d[a][b],c); //重边
  }
  floyd();
  for(int i=1;i&amp;#x3C;=n;i++){
    for(int j=1;j&amp;#x3C;=n;j++)
      printf(&quot;%d &quot;,d[i][j]);
    puts(&quot;&quot;);
  }
  return 0;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>D5 全源 Johnson 算法</title><link>https://ehnotgod.site/blog/d5-%E5%85%A8%E6%BA%90-johnson-%E7%AE%97%E6%B3%95</link><guid isPermaLink="true">https://ehnotgod.site/blog/d5-%E5%85%A8%E6%BA%90-johnson-%E7%AE%97%E6%B3%95</guid><description>Johnson 全源最短路：重赋权 + 多次 Dijkstra。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P5905&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;给定一个包含 $$n$$ 个结点和 $$m$$ 条带权边的有向图，求所有点对间的最短路径长度，一条路径的长度定义为这条路径上所有边的权值和。&lt;/p&gt;
&lt;p&gt;注意：边权&lt;strong&gt;可能&lt;/strong&gt;为负，且图中&lt;strong&gt;可能&lt;/strong&gt;存在重边和自环；&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第 $$1$$ 行：$$2$$ 个整数 $$n,m$$，表示给定有向图的结点数量和有向边数量。&lt;/p&gt;
&lt;p&gt;接下来 $$m$$ 行：每行 $$3$$ 个整数 $$u,v,w$$，表示有一条权值为 $$w$$ 的有向边从编号为 $$u$$ 的结点连向编号为 $$v$$ 的结点。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;若图中存在负环，输出仅一行 $$-1$$。&lt;/p&gt;
&lt;p&gt;若图中不存在负环：&lt;/p&gt;
&lt;p&gt;输出 $$n$$ 行：令 $$dis_{i,j}$$ 为从 $$i$$ 到 $$j$$ 的最短路，在第 $$i$$ 行输出 $$\sum\limits_{j=1}^n j\times dis_{i,j}$$，注意这个结果可能超过 int 存储范围。&lt;/p&gt;
&lt;p&gt;如果不存在从 $$i$$ 到 $$j$$ 的路径，则 $$dis_{i,j}=10^9$$；如果 $$i=j$$，则 $$dis_{i,j}=0$$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入输出样例 #1&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;5 7
1 2 4
1 4 10
2 3 7
4 5 3
4 2 -2
3 4 -3
5 3 4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;128
1000000072
999999978
1000000026
1000000014
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;输入输出样例 #2&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输入 #2&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;5 5
1 2 4
3 4 9
3 4 -3
4 5 3
5 3 -2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #2&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于 $$100%$$ 的数据，$$1\leq n\leq 3\times 10^3,\ \ 1\leq m\leq 6\times 10^3,\ \ 1\leq u,v\leq n,\ \ -3\times 10^5\leq w\leq 3\times 10^5$$。&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;Johnson 解决含负权边的全源最短路：先加一个超级源点向所有点连权为 0 的边，用 SPFA 求出势能 $h[v]$（同时判负环）；再把每条边权重改为 $w&apos;=w+h[u]-h[v]$（非负），这样 $n$ 次 Dijkstra 即可求全源最短路，最后还原 $dis&apos;=dis-h[u]+h[v]$。复杂度 $O(nm+n\cdot m\log n)$。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/D5-1.png&quot; alt=&quot;image-20250418091441310&quot;&gt;&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import heapq

class Edge():
    def __init__(self, v, w):
        self.v = v
        self.w = w

def spfa():
    global h, vis, cnt
    q = []; l = 0
    h = [int(1e20) for i in range(n + 1)]
    vis = [False for i in range(n + 1)]
    h[0] = 0; vis[0] = True; q.append(0)
    while len(q) &gt; l:
        u = q[l]; l += 1; vis[u] = False
        for ed in e[u]:
            v = ed.v; w = ed.w
            if h[v] &gt; h[u] + w:
                h[v] = h[u] + w
                cnt[v] = cnt[u] + 1
                if cnt[v] &gt; n:
                    print(-1);exit()
                if not vis[v]:
                    q.append(v)
                    vis[v] = True

def dijkstra(s):
    global h, vis, cnt
    for i in range(n + 1):
        d[i] = int(1e9)
    vis = [False for i in range(n + 1)]
    d[s] = 0
    q = []
    heapq.heappush(q, [0, s])
    while len(q) &gt; 0:
        t = heapq.heappop(q)
        u = t[1]
        if vis[u]:
            continue
        vis[u] = 1
        for ed in e[u]:
            v = ed.v
            w = ed.w
            if d[v] &gt; d[u] + w:
                d[v] = d[u] + w
                heapq.heappush(q, [d[v], v])



n, m = map(int, input().split())
e = [[] for i in range(n + 1)]
h = [0] * (n + 1)
d = [0] * (n + 1)
vis = [0] * (n + 1)
cnt = [0] * (n + 1)
for i in range(m):
    a, b, c = map(int, input().split())
    e[a].append(Edge(b, c))
for i in range(1, n + 1):
    e[0].append(Edge(i, 0))
spfa()
for u in range(1, n + 1):
    for ed in e[u]:
        ed.w += h[u] - h[ed.v]
for i in range(1, n + 1):
    dijkstra(i)
    ans = 0
    for j in range(1, n + 1):
        if d[j] == int(1e9):
            ans += j * int(1e9)
        else:
            ans += j * (d[j] + h[j] - h[i])
    print(ans)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include&amp;#x3C;algorithm&gt;
#include&amp;#x3C;cstring&gt;
#include&amp;#x3C;iostream&gt;
#include&amp;#x3C;queue&gt;
#define N 30010
#define INF 1000000000
using namespace std;

int n,m,a,b,c;
struct edge{int v,w;};
vector&amp;#x3C;edge&gt; e[N];
int vis[N],cnt[N];
long long h[N],d[N];

void spfa(){
    queue&amp;#x3C;int&gt;q;
    memset(h,63,sizeof h);
    memset(vis,false,sizeof vis);
    h[0]=0,vis[0]=1;q.push(0);
    while(q.size()){
        int u=q.front(); q.pop();vis[u]=0;
        for(auto ed : e[u]){
            int v=ed.v,w=ed.w;
            if(h[v]&gt;h[u]+w){
                h[v]=h[u]+w;
        cnt[v]=cnt[u]+1;
        if(cnt[v]&gt;n){
          printf(&quot;-1\n&quot;);exit(0);
        }
                if(!vis[v])q.push(v),vis[v]=1;
            }
        }
    }
}
void dijkstra(int s){
    priority_queue&amp;#x3C;pair&amp;#x3C;long long,int&gt;&gt;q;
    for(int i=1;i&amp;#x3C;=n;i++)d[i]=INF;
    memset(vis,false,sizeof vis);
    d[s]=0; q.push({0,s});
    while(q.size()){
        int u=q.top().second;q.pop();
        if(vis[u])continue;
        vis[u]=1;
        for(auto ed : e[u]){
            int v=ed.v,w=ed.w;
            if(d[v]&gt;d[u]+w){
                d[v]=d[u]+w;
                if(!vis[v]) q.push({-d[v],v});
            }
        }
    }
}
int main(){
  cin&gt;&gt;n&gt;&gt;m;
  for(int i=0;i&amp;#x3C;m;i++)
    cin&gt;&gt;a&gt;&gt;b&gt;&gt;c, e[a].push_back({b,c});
    for(int i=1;i&amp;#x3C;=n;i++)
      e[0].push_back({i,0});//加虚拟边

    spfa();
    for(int u=1;u&amp;#x3C;=n;u++)
      for(auto &amp;#x26;ed:e[u])
        ed.w+=h[u]-h[ed.v];//构造新边
    for(int i=1;i&amp;#x3C;=n;i++){
        dijkstra(i);
        long long ans=0;
        for(int j=1;j&amp;#x3C;=n;j++){
            if(d[j]==INF) ans+=(long long)j*INF;
            else ans+=(long long)j*(d[j]+h[j]-h[i]);
        }
        printf(&quot;%lld\n&quot;,ans);
    }
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>D6 最小生成树-Prim与Kruscal</title><link>https://ehnotgod.site/blog/d6-%E6%9C%80%E5%B0%8F%E7%94%9F%E6%88%90%E6%A0%91-prim%E4%B8%8Ekruscal</link><guid isPermaLink="true">https://ehnotgod.site/blog/d6-%E6%9C%80%E5%B0%8F%E7%94%9F%E6%88%90%E6%A0%91-prim%E4%B8%8Ekruscal</guid><description>最小生成树：Prim（加点）与 Kruscal（加边）。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P3366&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;如题，给出一个无向图，求出最小生成树，如果该图不连通，则输出 &lt;code&gt;orz&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行包含两个整数 $$N,M$$，表示该图共有 $$N$$ 个结点和 $$M$$ 条无向边。&lt;/p&gt;
&lt;p&gt;接下来 $$M$$ 行每行包含三个整数 $$X_i,Y_i,Z_i$$，表示有一条长度为 $$Z_i$$ 的无向边连接结点 $$X_i,Y_i$$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;如果该图连通，则输出一个整数表示最小生成树的各边的长度之和。如果该图不连通则输出 &lt;code&gt;orz&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入输出样例 #1&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;4 5
1 2 2
1 3 2
1 4 3
2 3 4
3 4 3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;7
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于 $$100%$$ 的数据：$$1\le N\le 5000$$，$$1\le M\le 2\times 10^5$$，$$1\le Z_i \le 10^4$$。&lt;/p&gt;
&lt;p&gt;样例解释：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/2259.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;所以最小生成树的总边权为 $$2+2+3=7$$。&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;Prim算法（$$O(n^2)$$）：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/D6-2.png&quot; alt=&quot;image-20250418091937605&quot;&gt;&lt;/p&gt;
&lt;p&gt;Kruscal算法（$$O(mlogm)$$）：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/D6-3.png&quot; alt=&quot;image-20250418092200766&quot;&gt;&lt;/p&gt;
&lt;p&gt;Kruskal 求最小生成树：把所有边按边权升序排序，依次尝试加入，若边的两端点当前不连通（并查集不同集合）则加入，直到连成 $n-1$ 条边；若最终边数不足 $n-1$ 说明图不连通（输出 orz）。复杂度 $O(m\log m)$。Prim 则从一点出发，每次用堆取当前到集合的最小边加点，适合稠密图。&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;
N = 5010
d = [0] * N
vis = [0] * N
class Edge():
    def __init__(self, v, w):
        self.v = v
        self.w = w
e = [[] for i in range(N)]

def prim(s):
    global ans, cnt
    for i in range(n + 1):
        d[i] = int(1e9)
    d[s] = 0
    for i in range(1, n + 1):
        u = 0
        for j in range(1, n + 1):
            if not vis[j] and d[j] &amp;#x3C; d[u]:
                u = j
        vis[u] = 1
        ans += d[u]
        if d[u] != 1e9:
            cnt += 1
        for ed in e[u]:
            v = ed.v
            w = ed.w
            if d[v] &gt; w:
                d[v] = w
    return cnt == n
n, m = map(int, input().split())
ans, cnt = 0, 0
for i in range(m):
    a, b, c = map(int, input().split())
    e[a].append(Edge(b, c))
    e[b].append(Edge(a, c))
if not prim(1):
    print(&quot;orz&quot;)
else:
    print(ans)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Kruskal 版（Python）&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class Edge():
    def __init__(self, u, v, w):
        self.u = u
        self.v = v
        self.w = w

def find(x):
    if fa[x] == x:
        return x
    fa[x] = find(fa[x])
    return fa[x]
def union(x, y):
    fa[find(x)] = find(y)
def kruskal():
    global ans, cnt
    e.sort(key=lambda k:k.w)
    for i in range(m):
        x = find(e[i].u)
        y = find(e[i].v)
        if x != y:
            union(x, y)
            ans += e[i].w
            cnt += 1
    return cnt == n - 1

n, m = map(int, input().split())
fa = [i for i in range(n + 1)]
ans, cnt = 0, 0
e = []
for i in range(m):
    u, v, w = map(int, input().split())
    e.append(Edge(u, v, w))
if not kruskal():
    print(&quot;orz&quot;)
else:
    print(ans)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;// Luogu P3366 【模板】最小生成树
#include &amp;#x3C;iostream&gt;
#include &amp;#x3C;cstring&gt;
#include &amp;#x3C;algorithm&gt;
#include &amp;#x3C;vector&gt;
#define inf 1e9
using namespace std;

int n,m,a,b,c,ans,cnt;
const int N=5010;
struct edge{int v,w;};
vector&amp;#x3C;edge&gt; e[N];
int d[N], vis[N];

bool prim(int s){
  for(int i=0;i&amp;#x3C;=n;i++)d[i]=inf;
  d[s]=0;
  for(int i=1;i&amp;#x3C;=n;i++){
    int u=0;
    for(int j=1;j&amp;#x3C;=n;j++)
      if(!vis[j]&amp;#x26;&amp;#x26;d[j]&amp;#x3C;d[u]) u=j;
    vis[u]=1; //标记u已出圈
    ans+=d[u];
    if(d[u]!=inf) cnt++;
    for(auto ed : e[u]){
      int v=ed.v, w=ed.w;
      if(d[v]&gt;w) d[v]=w;
    }
  }
  return cnt==n;
}
int main(){
  cin&gt;&gt;n&gt;&gt;m;
  for(int i=0; i&amp;#x3C;m; i++){
    cin&gt;&gt;a&gt;&gt;b&gt;&gt;c;
    e[a].push_back({b,c});
    e[b].push_back({a,c});
  }
  if(!prim(1))puts(&quot;orz&quot;);
  else printf(&quot;%d\n&quot;,ans);
  return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Kruskal 版（C++）&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;// Luogu P3366 【模板】最小生成树
#include &amp;#x3C;iostream&gt;
#include &amp;#x3C;cstring&gt;
#include &amp;#x3C;algorithm&gt;
using namespace std;

const int N=200006;
int n, m;
struct edge{
  int u,v,w;
  bool operator&amp;#x3C;(const edge &amp;#x26;t)const
  {return w &amp;#x3C; t.w;}
}e[N];
int fa[N],ans,cnt;

int find(int x){
  if(fa[x]==x) return x;
  return fa[x]=find(fa[x]);
}
bool kruskal(){
  sort(e,e+m);
  for(int i=1;i&amp;#x3C;=n;i++)fa[i]=i;
  for(int i=0; i&amp;#x3C;m; i++){
    int x=find(e[i].u);
    int y=find(e[i].v);
    if(x!=y){
      fa[x]=y;
      ans+=e[i].w;
      cnt++;
    }
  }
  return cnt==n-1;
}
int main(){
  cin&gt;&gt;n&gt;&gt;m;
  for(int i=0; i&amp;#x3C;m; i++)
    cin&gt;&gt;e[i].u&gt;&gt;e[i].v&gt;&gt;e[i].w;
  if(!kruskal()) puts(&quot;orz&quot;);
  else printf(&quot;%d\n&quot;,ans);
  return 0;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>D7 倍增算法（LCA）</title><link>https://ehnotgod.site/blog/d7-%E5%80%8D%E5%A2%9E%E7%AE%97%E6%B3%95lca</link><guid isPermaLink="true">https://ehnotgod.site/blog/d7-%E5%80%8D%E5%A2%9E%E7%AE%97%E6%B3%95lca</guid><description>倍增法求 LCA：树上最近公共祖先。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P3379&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;如题，给定一棵有根多叉树，请求出指定两个点之间最近的公共祖先。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行包含三个正整数 $N,M,S$，分别表示树的结点个数、询问的个数和树根结点的序号。&lt;/p&gt;
&lt;p&gt;接下来 $N-1$ 行每行包含两个正整数 $x, y$，表示 $x$ 结点和 $y$ 结点之间有一条直接连接的边（数据保证可以构成树）。&lt;/p&gt;
&lt;p&gt;接下来 $M$ 行每行包含两个正整数 $a, b$，表示询问 $a$ 结点和 $b$ 结点的最近公共祖先。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输出包含 $M$ 行，每行包含一个正整数，依次为每一个询问的结果。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;5 5 4
3 1
2 4
5 1
1 4
2 4
3 2
3 5
1 2
4 5
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;4
4
1
4
4

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于 $100%$ 的数据，$1 \leq N,M\leq 5\times10^5$，$1 \leq x, y,a ,b \leq N$，&lt;strong&gt;不保证&lt;/strong&gt; $a \neq b$。&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;images/D7-1.png&quot; alt=&quot;image-20250727163659755&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/D7-2.png&quot; alt=&quot;image-20250727163753894&quot;&gt;&lt;/p&gt;
&lt;p&gt;倍增法求 LCA：先用 DFS 预处理每个点的深度 $dep[u]$ 和 $2^i$ 级祖先 $f[u][i]$（$f[u][i]=f[f[u][i-1]][i-1]$）。查询时先把较深的点跳到与另一个同深度，再一起向上跳，最后跳到 LCA 的下一个点。单次查询 $O(\log n)$，预处理 $O(n\log n)$。&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import sys

sys.setrecursionlimit(10 ** 7)
input = sys.stdin.readline

# 最大节点数和最大二进制位数（因为 2^20 &gt; 10^6，足够应对 N ≤ 5e5）
N = 500_005
LOG = 20

# 邻接表存储树的边
edges = [[] for _ in range(N)]

# f[u][i] 表示节点 u 的第 2^i 级祖先
# dep[u] 存储节点 u 的深度（根节点深度为 1）
f = [[0] * (LOG + 1) for _ in range(N)]
dep = [0] * N


def dfs(u: int, parent: int) -&gt; None:
    f[u][0] = parent
    dep[u] = dep[parent] + 1
    for i in range(1, LOG + 1):
        f[u][i] = f[f[u][i - 1]][i - 1]
    for v in edges[u]:
        if v == parent:
            continue
        dfs(v, u)


def lca(u: int, v: int) -&gt; int:
    if dep[u] &amp;#x3C; dep[v]:
        u, v = v, u
    diff = dep[u] - dep[v]
    for i in range(LOG + 1):
        if diff &amp;#x26; (1 &amp;#x3C;&amp;#x3C; i):
            u = f[u][i]
    if u == v:
        return u
    for i in reversed(range(LOG + 1)):
        if f[u][i] != f[v][i]:
            u = f[u][i]
            v = f[v][i]
    return f[u][0]
n, m, s = map(int, input().split())
for _ in range(n - 1):
    a, b = map(int, input().split())
    edges[a].append(b)
    edges[b].append(a)
dfs(s, 0)
for _ in range(m):
    u, v = map(int, input().split())
    print(lca(u, v))
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;// 倍增法 O(nlogn)
#include&amp;#x3C;bits/stdc++.h&gt;
using namespace std;

const int N=500005;
int n,m,s;
vector&amp;#x3C;int&gt; e[N];
int f[N][22],dep[N];

void dfs(int u,int fa){
  f[u][0]=fa; dep[u]=dep[fa]+1;
  for(int i=1;i&amp;#x3C;=20;i++) //u的2,4,8...祖先
    f[u][i]=f[f[u][i-1]][i-1];
  for(int v:e[u])
    if(v!=fa) dfs(v,u);
}
int lca(int u,int v){
  if(dep[u]&amp;#x3C;dep[v]) swap(u,v);
  for(int i=20;~i;i--) //u先大步后小步向上跳，直到与v同层
    if(dep[f[u][i]]&gt;=dep[v]) u=f[u][i];
  if(u==v) return v;
  for(int i=20;~i;i--) //u,v一起向上跳，直到lca的下面
    if(f[u][i]!=f[v][i]) u=f[u][i],v=f[v][i];
  return f[u][0];
}
int main(){
  scanf(&quot;%d%d%d&quot;,&amp;#x26;n,&amp;#x26;m,&amp;#x26;s);
  for(int i=1,a,b; i&amp;#x3C;n; i++){
    scanf(&quot;%d%d&quot;,&amp;#x26;a,&amp;#x26;b);
    e[a].push_back(b);
    e[b].push_back(a);
  }
  dfs(s,0);
  for(int i=0,a,b;i&amp;#x3C;m;i++){
    scanf(&quot;%d%d&quot;,&amp;#x26;a,&amp;#x26;b);
    printf(&quot;%d\n&quot;,lca(a,b));
  }
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>D8 树链剖分（最近公共祖先 LCA）</title><link>https://ehnotgod.site/blog/d8-%E6%A0%91%E9%93%BE%E5%89%96%E5%88%86%E6%9C%80%E8%BF%91%E5%85%AC%E5%85%B1%E7%A5%96%E5%85%88-lca</link><guid isPermaLink="true">https://ehnotgod.site/blog/d8-%E6%A0%91%E9%93%BE%E5%89%96%E5%88%86%E6%9C%80%E8%BF%91%E5%85%AC%E5%85%B1%E7%A5%96%E5%85%88-lca</guid><description>树链剖分：重链剖分求 LCA 与路径操作。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P3379&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;如题，给定一棵有根多叉树，请求出指定两个点直接最近的公共祖先。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行包含三个正整数 $$N,M,S$$，分别表示树的结点个数、询问的个数和树根结点的序号。&lt;/p&gt;
&lt;p&gt;接下来 $$N-1$$ 行每行包含两个正整数 $$x, y$$，表示 $$x$$ 结点和 $$y$$ 结点之间有一条直接连接的边（数据保证可以构成树）。&lt;/p&gt;
&lt;p&gt;接下来 $$M$$ 行每行包含两个正整数 $$a, b$$，表示询问 $$a$$ 结点和 $$b$$ 结点的最近公共祖先。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输出包含 $$M$$ 行，每行包含一个正整数，依次为每一个询问的结果。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入输出样例 #1&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;5 5 4
3 1
2 4
5 1
1 4
2 4
3 2
3 5
1 2
4 5
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;4
4
1
4
4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于 $$100%$$ 的数据，$$1 \leq N,M\leq 500000$$，$$1 \leq x, y,a ,b \leq N$$，&lt;strong&gt;不保证&lt;/strong&gt; $$a \neq b$$。&lt;/p&gt;
&lt;p&gt;样例说明：&lt;/p&gt;
&lt;p&gt;该树结构如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/2282.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;第一次询问：$$2, 4$$ 的最近公共祖先，故为 $$4$$。&lt;/p&gt;
&lt;p&gt;第二次询问：$$3, 2$$ 的最近公共祖先，故为 $$4$$。&lt;/p&gt;
&lt;p&gt;第三次询问：$$3, 5$$ 的最近公共祖先，故为 $$1$$。&lt;/p&gt;
&lt;p&gt;第四次询问：$$1, 2$$ 的最近公共祖先，故为 $$4$$。&lt;/p&gt;
&lt;p&gt;第五次询问：$$4, 5$$ 的最近公共祖先，故为 $$4$$。&lt;/p&gt;
&lt;p&gt;故输出依次为 $$4, 4, 1, 4, 4$$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解析：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/D8-2.png&quot; alt=&quot;image-20250418092432087&quot;&gt;&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;树链剖分求 LCA：先做两遍 DFS，求出每个点的重儿子、深度、链顶 $top$。查询时比较两点的链顶，深度深的往上跳到其链顶的父节点，直到两点在同一条链上，深度浅者即为 LCA。单次查询 $O(\log n)$。&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import sys
sys.setrecursionlimit(int(1e7))
N = 500010
fa = [0] * N
son = [0] * N
dep = [0] * N
siz = [0] * N
top = [0] * N
e = [[] for i in range(N)]
def dfs1(u, f):
    fa[u] = f
    siz[u] = 1
    dep[u] = dep[f] + 1
    for v in e[u]:
        if v == f:
            continue
        dfs1(v, u)
        siz[u] += siz[v]
        if siz[son[u]] &amp;#x3C; siz[v]:
            son[u] = v
def dfs2(u, t):
    top[u] = t
    if not son[u]:
        return
    dfs2(son[u], t)
    for v in e[u]:
        if v == fa[u] or v == son[u]:
            continue
        dfs2(v, v)
def lca(u, v):
    while top[u] != top[v]:
        if dep[top[u]] &amp;#x3C; dep[top[v]]:
            u, v = v, u
        u = fa[top[u]]
    if dep[u] &amp;#x3C; dep[v]:
        return u
    else:
        return v
n, m, s = map(int, input().split())
for i in range(1, n):
    a, b = map(int, input().split())
    e[a].append(b)
    e[b].append(a)
dfs1(s, 0)
dfs2(s, s)
for i in range(m):
    a, b = map(int, input().split())
    print(lca(a, b))
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;// 树链剖分 O(mlogn)
#include &amp;#x3C;iostream&gt;
#include &amp;#x3C;cstring&gt;
#include &amp;#x3C;algorithm&gt;
#include &amp;#x3C;vector&gt;
using namespace std;

const int N=500010;
int n,m,s,a,b;
vector&amp;#x3C;int&gt; e[N];
int fa[N],son[N],dep[N],siz[N],top[N];

void dfs1(int u,int f){ //搞fa,son,dep
  fa[u]=f;siz[u]=1;dep[u]=dep[f]+1;
  for(int v:e[u]){
    if(v==f) continue;
    dfs1(v,u);
    siz[u]+=siz[v];
    if(siz[son[u]]&amp;#x3C;siz[v])son[u]=v;
  }
}
void dfs2(int u,int t){ //搞top
  top[u]=t;  //记录链头
  if(!son[u]) return; //无重儿子
  dfs2(son[u],t);     //搜重儿子
  for(int v:e[u]){
    if(v==fa[u]||v==son[u])continue;
    dfs2(v,v); //搜轻儿子
  }
}
int lca(int u,int v){
  while(top[u]!=top[v]){
    if(dep[top[u]]&amp;#x3C;dep[top[v]])swap(u,v);
    u=fa[top[u]];
  }
  return dep[u]&amp;#x3C;dep[v]?u:v;
}
int main(){
  scanf(&quot;%d%d%d&quot;,&amp;#x26;n,&amp;#x26;m,&amp;#x26;s);
  for(int i=1; i&amp;#x3C;n; i++){
    scanf(&quot;%d%d&quot;,&amp;#x26;a,&amp;#x26;b);
    e[a].push_back(b);
    e[b].push_back(a);
  }
  dfs1(s,0);
  dfs2(s,s);
  while(m--){
    scanf(&quot;%d%d&quot;,&amp;#x26;a,&amp;#x26;b);
    printf(&quot;%d\n&quot;,lca(a,b));
  }
  return 0;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>D9 重链剖分+线段树</title><link>https://ehnotgod.site/blog/d9-%E9%87%8D%E9%93%BE%E5%89%96%E5%88%86%E7%BA%BF%E6%AE%B5%E6%A0%91</link><guid isPermaLink="true">https://ehnotgod.site/blog/d9-%E9%87%8D%E9%93%BE%E5%89%96%E5%88%86%E7%BA%BF%E6%AE%B5%E6%A0%91</guid><description>树链剖分 + 线段树：树上路径修改与查询。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P3384&lt;/p&gt;
&lt;p&gt;P3384 【模板】重链剖分/树链剖分&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;如题，已知一棵包含 $$N$$ 个结点的树（连通且无环），每个节点上包含一个数值，需要支持以下操作：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;1 x y z&lt;/code&gt;，表示将树从 $$x$$ 到 $$y$$ 结点最短路径上所有节点的值都加上 $$z$$。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;2 x y&lt;/code&gt;，表示求树从 $$x$$ 到 $$y$$ 结点最短路径上所有节点的值之和。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;3 x z&lt;/code&gt;，表示将以 $$x$$ 为根节点的子树内所有节点值都加上 $$z$$。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;4 x&lt;/code&gt;，表示求以 $$x$$ 为根节点的子树内所有节点值之和。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行包含 $$4$$ 个正整数 $$N,M,R,P$$，分别表示树的结点个数、操作个数、根节点序号和取模数（&lt;strong&gt;即所有的输出结果均对此取模&lt;/strong&gt;）。&lt;/p&gt;
&lt;p&gt;接下来一行包含 $$N$$ 个非负整数，分别依次表示各个节点上初始的数值。&lt;/p&gt;
&lt;p&gt;接下来 $$N-1$$ 行每行包含两个整数 $$x,y$$，表示点 $$x$$ 和点 $$y$$ 之间连有一条边（保证无环且连通）。&lt;/p&gt;
&lt;p&gt;接下来 $$M$$ 行每行包含若干个正整数，每行表示一个操作。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输出包含若干行，分别依次表示每个操作 $$2$$ 或操作 $$4$$ 所得的结果（&lt;strong&gt;对 $$P$$ 取模&lt;/strong&gt;）。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;5 5 2 24
7 3 7 8 0 
1 2
1 5
3 1
4 1
3 4 2
3 2 2
4 5
1 5 1 3
2 1 3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2
21
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;【数据规模】&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;对于 $$100%$$ 的数据： $$1\le N \leq {10}^5$$，$$1\le M \leq {10}^5$$，$$1\le R\le N$$，$$1\le P \le 2^{30}$$。所有输入的数均在 &lt;code&gt;int&lt;/code&gt; 范围内。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;【样例说明】&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;树的结构如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/2319.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;各个操作如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/2320.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;故输出应依次为 $$2$$ 和 $$21$$。&lt;/p&gt;
&lt;p&gt;树链剖分部分和上面一样，多了一个“同链打标记”，可以使用线段树维护。总时间复杂度：$${O(mlog^2n)}$$&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;重链剖分的核心思想是&lt;strong&gt;把树拍平成一个序列&lt;/strong&gt;，用 dfs 序把树上的路径/子树操作变成线段树上的区间操作。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第一遍 DFS（dfs1）&lt;/strong&gt;，自底向上求出每个点的：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$fa[u]$：父节点&lt;/li&gt;
&lt;li&gt;$dep[u]$：深度&lt;/li&gt;
&lt;li&gt;$siz[u]$：子树大小&lt;/li&gt;
&lt;li&gt;$son[u]$：&lt;strong&gt;重儿子&lt;/strong&gt;，即子树最大的那个儿子（若有多个取其一）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;第二遍 DFS（dfs2）&lt;/strong&gt;，按&quot;&lt;strong&gt;先走重儿子&lt;/strong&gt;&quot;的顺序给每个点分配 dfs 序 $dfn[u]$，并记录 $top[u]$（所在重链的链顶）。因为每次优先进入重儿子，所以&lt;strong&gt;同一条重链上的点在 $dfn$ 中是连续的一段区间&lt;/strong&gt;——这正是&quot;一条链 = 一段区间&quot;的关键。&lt;/p&gt;
&lt;p&gt;剖分完成后：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;子树操作&lt;/strong&gt;：以 $x$ 为根的子树，其所有点在 $dfn$ 中恰好是连续区间 $[dfn[x],\ dfn[x]+siz[x]-1]$，直接对线段树做一次区间加/区间和即可，$O(\log n)$。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;路径操作&lt;/strong&gt;（$x\to y$）：不断把 $x,y$ 中链顶更深的那个&quot;整条链&quot;一起处理：
&lt;ol&gt;
&lt;li&gt;若 $top[x]\neq top[y]$，比较 $dep[top[x]]$ 与 $dep[top[y]]$，取更深的一方（假设是 $x$），对区间 $[dfn[top[x]], dfn[x]]$ 做线段树区间操作，然后 $x=fa[top[x]]$（跳到链顶的父节点）；&lt;/li&gt;
&lt;li&gt;重复直到 $x,y$ 位于同一条重链；&lt;/li&gt;
&lt;li&gt;最后对 $[dfn[x],dfn[y]]$（深度小的在前）做一次区间操作。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;因为每次跳链都会让链顶深度至少减半，最多跳 $O(\log n)$ 条链，每次线段树操作 $O(\log n)$，所以单次路径操作复杂度 $O(\log^2 n)$。线段树只需支持区间加与区间求和即可（本题是纯加法，不需要处理懒标记下传顺序；加乘混合时才要特殊处理）。&lt;/p&gt;
&lt;h3&gt;py代码实现&lt;/h3&gt;
&lt;p&gt;略&lt;/p&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;// 树链剖分 O(mlognlogn)
#include&amp;#x3C;bits/stdc++.h&gt;
using namespace std;

#define int long long
const int N=100010;
int n,m,root,P,w[N],a,b,c,t;
vector&amp;#x3C;int&gt; e[N];
int fa[N],dep[N],siz[N],son[N];
int top[N],id[N],nw[N],cnt; //树链

#define lc u&amp;#x3C;&amp;#x3C;1
#define rc u&amp;#x3C;&amp;#x3C;1|1
struct tree{
  int l,r; 
  int sum,add;
}tr[N*4]; //线段树

void dfs1(int u,int f){ //搞fa,dep,siz,son
  fa[u]=f,dep[u]=dep[f]+1,siz[u]=1;
  for(int v:e[u]){
    if(v==f) continue;
    dfs1(v,u);
    siz[u]+=siz[v];
    if(siz[son[u]]&amp;#x3C;siz[v]) son[u]=v; 
  }
}
void dfs2(int u,int tp){ //搞top,id,nw
  top[u]=tp,id[u]=++cnt,nw[cnt]=w[u];
  if(son[u]) dfs2(son[u],tp);
  for(int v:e[u]){
    if(v==fa[u]||v==son[u])continue;
    dfs2(v,v);
  }
}

void pushup(int u){
  tr[u].sum=tr[lc].sum+tr[rc].sum;
}
void pushdown(int u){
  if(tr[u].add){
    tr[lc].sum+=tr[u].add*(tr[lc].r-tr[lc].l+1);
    tr[rc].sum+=tr[u].add*(tr[rc].r-tr[rc].l+1);
    tr[lc].add+=tr[u].add;
    tr[rc].add+=tr[u].add;
    tr[u].add=0;
  }
}
void build(int u,int l,int r){ //构建线段树
  tr[u]={l,r,nw[l],0};
  if(l==r) return;
  int mid=l+r&gt;&gt;1;
  build(lc,l,mid);
  build(rc,mid+1,r);
  pushup(u);
}
void change(int u,int x,int y,int k){ //线段树修改
  if(x&gt;tr[u].r||y&amp;#x3C;tr[u].l) return;
  if(x&amp;#x3C;=tr[u].l&amp;#x26;&amp;#x26;tr[u].r&amp;#x3C;=y){
    tr[u].sum+=k*(tr[u].r-tr[u].l+1);
    tr[u].add+=k;
    return;
  }
  pushdown(u);
  change(lc,x,y,k);
  change(rc,x,y,k);
  pushup(u);
}
void change_path(int u,int v,int k){ //修改路径
  while(top[u]!=top[v]){
    if(dep[top[u]]&amp;#x3C;dep[top[v]]) swap(u,v);
    change(1,id[top[u]],id[u],k);
    u=fa[top[u]];
  }
  if(dep[u]&amp;#x3C;dep[v]) swap(u,v);
  change(1,id[v],id[u],k); //最后一段
}
void change_tree(int u,int k){ //修改子树
  change(1,id[u],id[u]+siz[u]-1,k);
}
int query(int u,int x,int y){ //线段树查询
  if(x&gt;tr[u].r||y&amp;#x3C;tr[u].l) return 0;
  if(x&amp;#x3C;=tr[u].l&amp;#x26;&amp;#x26;tr[u].r&amp;#x3C;=y) return tr[u].sum;
  pushdown(u);
  return query(lc,x,y)+query(rc,x,y);
}
int query_path(int u,int v){ //查询路径
  int res=0;
  while(top[u]!=top[v]){
    if(dep[top[u]]&amp;#x3C;dep[top[v]]) swap(u,v);
    res+=query(1,id[top[u]],id[u]);
    u=fa[top[u]];
  }
  if(dep[u]&amp;#x3C;dep[v]) swap(u,v);
  res+=query(1,id[v],id[u]); //最后一段
  return res;
}
int query_tree(int u){ //查询子树
  return query(1,id[u],id[u]+siz[u]-1);
}
signed main(){
  ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
  cin&gt;&gt;n&gt;&gt;m&gt;&gt;root&gt;&gt;P;
  for(int i=1; i&amp;#x3C;=n; i++) cin&gt;&gt;w[i];
  for(int i=0; i&amp;#x3C;n-1; i++){
    cin&gt;&gt;a&gt;&gt;b;
    e[a].push_back(b); e[b].push_back(a);
  }
  dfs1(root,0); dfs2(root,root); //把树拆成链
  build(1,1,n);  //用链建线段树
  while(m--){
    cin&gt;&gt;t&gt;&gt;a;
    if(t==1) cin&gt;&gt;b&gt;&gt;c,change_path(a,b,c);
    else if(t==3) cin&gt;&gt;c,change_tree(a,c);
    else if(t==2) cin&gt;&gt;b,cout&amp;#x3C;&amp;#x3C;query_path(a,b)%P&amp;#x3C;&amp;#x3C;&quot;\n&quot;;
    else cout&amp;#x3C;&amp;#x3C;query_tree(a)%P&amp;#x3C;&amp;#x3C;&quot;\n&quot;;
  }
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>D10 树上距离</title><link>https://ehnotgod.site/blog/d10-%E6%A0%91%E4%B8%8A%E8%B7%9D%E7%A6%BB</link><guid isPermaLink="true">https://ehnotgod.site/blog/d10-%E6%A0%91%E4%B8%8A%E8%B7%9D%E7%A6%BB</guid><description>树上前缀和/差分：求树上两点距离与路径和。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://loj.ac/p/10130&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/D10-1.png&quot; alt=&quot;image-20250418092643205&quot;&gt;&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;树上两点距离 $dis(u,v)=dep[u]+dep[v]-2\cdot dep[lca(u,v)]$。先用 DFS 预处理深度和倍增祖先（或树链剖分），再用 LCA 求距离。本题为 LOJ 10130（树上距离板子）。&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;## https://loj.ac/p/10130
# 相比树链剖分板子，只在有标注“#距离”处修改了
import sys
sys.setrecursionlimit(int(1e7))
N = 100010
fa = [0] * N
son = [0] * N
dep = [0] * N
siz = [0] * N
top = [0] * N
# 距离
dis = [0] * N
e = [[] for i in range(N)]
def dfs1(u, f):
    fa[u] = f
    siz[u] = 1
    dep[u] = dep[f] + 1
    for v in e[u]:
        if v == f:
            continue
        # 距离
        dis[v] = dis[u] + 1
        dfs1(v, u)
        siz[u] += siz[v]
        if siz[son[u]] &amp;#x3C; siz[v]:
            son[u] = v
def dfs2(u, t):
    top[u] = t
    if not son[u]:
        return
    dfs2(son[u], t)
    for v in e[u]:
        if v == fa[u] or v == son[u]:
            continue
        dfs2(v, v)
def lca(u, v):
    while top[u] != top[v]:
        if dep[top[u]] &amp;#x3C; dep[top[v]]:
            u, v = v, u
        u = fa[top[u]]
    if dep[u] &amp;#x3C; dep[v]:
        return u
    else:
        return v
n = int(input())
for i in range(1, n):
    a, b = map(int, input().split())
    e[a].append(b)
    e[b].append(a)
dfs1(1, 0)
dfs2(1, 1)
m = int(input())
for i in range(m):
    a, b = map(int, input().split())
    # 距离
    d = dis[a] + dis[b] - dis[lca(a, b)] * 2
    print(d)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include &amp;#x3C;iostream&gt;
#include &amp;#x3C;cstring&gt;
#include &amp;#x3C;algorithm&gt;
#include &amp;#x3C;vector&gt;
using namespace std;
const int N=100010;
int n,m,a,b,c;
vector&amp;#x3C;int&gt; e[N];
int dep[N],fa[N],son[N],sz[N],dis[N];
int top[N];

void dfs1(int u,int father){
  fa[u]=father,dep[u]=dep[father]+1,sz[u]=1;
  for(int v:e[u]){
    if(v==father) continue;
    dis[v]=dis[u]+1;
    dfs1(v,u);
    sz[u]+=sz[v];
    if(sz[son[u]]&amp;#x3C;sz[v])son[u]=v;
  }
}
void dfs2(int u,int t){
  top[u]=t;
  if(!son[u]) return;
  dfs2(son[u],t);
  for(int v:e[u]){
    if(v==fa[u]||v==son[u])continue;
    dfs2(v,v);
  }
}
int lca(int x,int y){
  while(top[x]!=top[y]){
    if(dep[top[x]]&amp;#x3C;dep[top[y]])swap(x,y);
    x=fa[top[x]];
  }
  return dep[x]&amp;#x3C;dep[y]?x:y;
}
int main(){
  scanf(&quot;%d&quot;,&amp;#x26;n);
  for(int i=1; i&amp;#x3C;n; i++){
    scanf(&quot;%d%d&quot;,&amp;#x26;a,&amp;#x26;b);
    e[a].push_back(b);
    e[b].push_back(a);
  }
  dfs1(1,0);
  dfs2(1,1);
  scanf(&quot;%d&quot;,&amp;#x26;m);
  while(m--){
    scanf(&quot;%d%d&quot;,&amp;#x26;a,&amp;#x26;b);
    int d=dis[a]+dis[b]-dis[lca(a,b)]*2;
    printf(&quot;%d\n&quot;,d);
  }
  return 0;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>D11 强连通分量Tarjan算法</title><link>https://ehnotgod.site/blog/d11-%E5%BC%BA%E8%BF%9E%E9%80%9A%E5%88%86%E9%87%8Ftarjan%E7%AE%97%E6%B3%95</link><guid isPermaLink="true">https://ehnotgod.site/blog/d11-%E5%BC%BA%E8%BF%9E%E9%80%9A%E5%88%86%E9%87%8Ftarjan%E7%AE%97%E6%B3%95</guid><description>Tarjan 强连通分量：有向图 SCC。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P2863&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;有一个 $$n$$ 个点，$$m$$ 条边的有向图，请求出这个图点数大于 $$1$$ 的强连通分量个数。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行为两个整数 $$n$$ 和 $$m$$。&lt;/p&gt;
&lt;p&gt;第二行至 $$m+1$$ 行，每一行有两个整数 $$a$$ 和 $$b$$，表示有一条从 $$a$$ 到 $$b$$ 的有向边。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;仅一行，表示点数大于 $$1$$ 的强连通分量个数。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;5 4
2 4
3 5
1 2
4 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于全部的测试点，保证 $$2\le n \le 10^4$$，$$2\le m\le 5\times 10^4$$，$$1 \leq a, b \leq n$$。&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;images/D11-1.png&quot; alt=&quot;image-20250817142047464&quot;&gt;&lt;/p&gt;
&lt;p&gt;Tarjan 强连通分量：DFS 时给每个点记录 $dfn$（访问序）和 $low$（子树内能回溯到的最早 $dfn$），用栈维护当前 SCC 候选。当 $low[u]==dfn[u]$ 时，$u$ 是 SCC 的根，把栈顶到 $u$ 的节点全部弹出作为一个 SCC。复杂度 $O(n+m)$。整个过程其实很纯粹，你品，你细品。&lt;/p&gt;
&lt;h3&gt;py代码实现&lt;/h3&gt;
&lt;p&gt;略，涉及到dfs的都不适合写python。&lt;/p&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;// Tarjan算法 O(n+m)
#include&amp;#x3C;bits/stdc++.h&gt;
using namespace std;

const int N=10010;
int n,m,a,b,ans;
vector&amp;#x3C;int&gt; e[N]; 
int dfn[N],low[N],tim,stk[N],ins[N],top,scc[N],siz[N],cnt;

void tarjan(int x){
  dfn[x]=low[x]=++tim; //时间戳 追溯值
  stk[++top]=x,ins[x]=1;
  for(int y:e[x]){
    if(!dfn[y]){ //若y尚未访问
      tarjan(y);
      low[x]=min(low[x],low[y]); //因y是儿子
    }
    else if(ins[y]) //若y已访问且在栈中
      low[x]=min(low[x],dfn[y]); //因y是祖先或左子树点
  }

  if(dfn[x]==low[x]){ //若x是SCC的根
    ++cnt;
    while(1){
      int y=stk[top--]; ins[y]=0;
      scc[y]=cnt; //SCC的编号
      ++siz[cnt]; //SCC的大小
      if(y==x) break;
    }
  }
}
int main(){
  ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
  cin&gt;&gt;n&gt;&gt;m;
  while(m--)
    cin&gt;&gt;a&gt;&gt;b, e[a].push_back(b);
  for(int i=1;i&amp;#x3C;=n;i++) //可能不连通
    if(!dfn[i]) tarjan(i);
  for(int i=1;i&amp;#x3C;=cnt;i++)
     if(siz[i]&gt;1) ans++;
  cout&amp;#x3C;&amp;#x3C;ans;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>D12 Tarjan SCC缩点</title><link>https://ehnotgod.site/blog/d12-tarjan-scc%E7%BC%A9%E7%82%B9</link><guid isPermaLink="true">https://ehnotgod.site/blog/d12-tarjan-scc%E7%BC%A9%E7%82%B9</guid><description>SCC 缩点：把强连通分量缩成 DAG。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P3387&lt;/p&gt;
&lt;p&gt;P3387 【模板】缩点&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;给定一个 $$n$$ 个点 $$m$$ 条边有向图，每个点有一个权值，求一条路径，使路径经过的点权值之和最大。你只需要求出这个权值和。&lt;/p&gt;
&lt;p&gt;允许多次经过一条边或者一个点，但是，重复经过的点，权值只计算一次。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行两个正整数 $$n,m$$。&lt;/p&gt;
&lt;p&gt;第二行 $$n$$ 个整数，其中第 $$i$$ 个数 $$a_i$$ 表示点 $$i$$ 的点权。&lt;/p&gt;
&lt;p&gt;第三至 $$m+2$$ 行，每行两个整数 $$u,v$$，表示一条 $$u\rightarrow v$$ 的有向边。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;共一行，最大的点权之和。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2 2
1 1
1 2
2 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于 $$100%$$ 的数据，$$1\le n \le 10^4$$，$$1\le m \le 10^5$$，$$0\le a_i\le 10^3$$。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/D12-1.png&quot; alt=&quot;image-20250817142544858&quot;&gt;&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;先把有向图缩点成 DAG：对每个 SCC 合并为一个新点，权值为内部点权之和；缩点后的图是有向无环图，可对其做拓扑排序 DP，求路径点权和最大的一条路径。&lt;/p&gt;
&lt;h3&gt;py代码实现&lt;/h3&gt;
&lt;p&gt;略，涉及到dfs的都不适合写python。&lt;/p&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include&amp;#x3C;bits/stdc++.h&gt;
using namespace std;

const int N=100010;
int n,m,a,b;
vector&amp;#x3C;int&gt; e[N],ne[N]; 
int dfn[N],low[N],tim,stk[N],top,scc[N],cnt;
int w[N],nw[N],d[N];

void tarjan(int x){
  dfn[x]=low[x]=++tim; 
  stk[++top]=x;
  for(int y : e[x]){
    if(!dfn[y]){
      tarjan(y);
      low[x]=min(low[x],low[y]); 
    }
    else if(!scc[y])
      low[x]=min(low[x],dfn[y]);
  }
  if(dfn[x]==low[x]){
    ++cnt;
    while(1){
      int y=stk[top--];
      scc[y]=cnt;
      if(y==x) break;
    }
  }
}
int main(){
  cin&gt;&gt;n&gt;&gt;m;
  for(int i=1;i&amp;#x3C;=n;i++) cin&gt;&gt;w[i];
  for(int i=1;i&amp;#x3C;=m;i++){
    cin&gt;&gt;a&gt;&gt;b;
    e[a].push_back(b);
  }
  
  for(int i=1;i&amp;#x3C;=n;i++) //缩点
    if(!dfn[i]) tarjan(i); 
  for(int x=1;x&amp;#x3C;=n;x++){ //建拓扑图
    nw[scc[x]]+=w[x];
    for(int y:e[x])
      if(scc[x]!=scc[y]) ne[scc[x]].push_back(scc[y]);
  } 
  for(int x=cnt;x;x--){ //求最长路
    if(d[x]==0) d[x]=nw[x]; //起点
    for(int y:ne[x])
      d[y]=max(d[y],d[x]+nw[y]);
  } 
  int ans=0;
  for(int i=1;i&amp;#x3C;=cnt;i++) ans=max(ans,d[i]);
  cout&amp;#x3C;&amp;#x3C;ans;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>D13 Tarjan割点</title><link>https://ehnotgod.site/blog/d13-tarjan%E5%89%B2%E7%82%B9</link><guid isPermaLink="true">https://ehnotgod.site/blog/d13-tarjan%E5%89%B2%E7%82%B9</guid><description>Tarjan 割点：无向图割点判定。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P3388&lt;/p&gt;
&lt;p&gt;P3388 【模板】割点（割顶）&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;给出一个 $$n$$ 个点，$$m$$ 条边的无向图，求图的割点。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行输入两个正整数 $$n,m$$。&lt;/p&gt;
&lt;p&gt;下面 $$m$$ 行每行输入两个正整数 $$x,y$$ 表示 $$x$$ 到 $$y$$ 有一条边。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行输出割点个数。&lt;/p&gt;
&lt;p&gt;第二行按照节点编号从小到大输出节点，用空格隔开。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;6 7
1 2
1 3
1 4
2 5
3 5
4 5
5 6
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1 
5
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于全部数据，$$1\leq n \le 2\times 10^4$$，$$1\leq m \le 1 \times 10^5$$。&lt;/p&gt;
&lt;p&gt;点的编号均大于 $$0$$ 小于等于 $$n$$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tarjan 图不一定连通。&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;images/D13-1.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Tarjan 判割点：DFS 记录 $dfn$ 和 $low$。对根节点，若有两个及以上子树则是割点；对非根节点 $u$，若存在子节点 $v$ 满足 $low[v] \ge dfn[u]$，则 $u$ 是割点。复杂度 $O(n+m)$。&lt;/p&gt;
&lt;h3&gt;py代码实现&lt;/h3&gt;
&lt;p&gt;略，涉及到dfs的都不适合写python。&lt;/p&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include&amp;#x3C;bits/stdc++.h&gt;
using namespace std;

const int N=20010;
int n,m,a,b;
vector&amp;#x3C;int&gt; e[N];
int dfn[N],low[N],tim,cut[N],root;

void tarjan(int x){
  dfn[x]=low[x]=++tim;
  int son=0; //x的儿子个数
  for(int y:e[x]){
    if(!dfn[y]){ //若y未访问
      tarjan(y);
      low[x]=min(low[x],low[y]); 
      if(low[y]&gt;=dfn[x]){
        son++;
        if(x!=root||son&gt;1) cut[x]=1;
      }
    }
    else //若y已访问
      low[x]=min(low[x],dfn[y]); //注:dfn不能换成low
  }
}
int main(){
  cin&gt;&gt;n&gt;&gt;m;
  while(m --){
    cin&gt;&gt;a&gt;&gt;b;
    e[a].push_back(b),
    e[b].push_back(a);
  }
  for(root=1;root&amp;#x3C;=n;root++) if(!dfn[root]) tarjan(root);
  
  int ans=0;
  for(int i=1;i&amp;#x3C;=n;i++) if(cut[i]) ans++;
  cout&amp;#x3C;&amp;#x3C;ans&amp;#x3C;&amp;#x3C;&quot;\n&quot;;
  for(int i=1;i&amp;#x3C;=n;i++) if(cut[i]) cout&amp;#x3C;&amp;#x3C;i&amp;#x3C;&amp;#x3C;&quot; &quot;;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>D14 Tarjan割边</title><link>https://ehnotgod.site/blog/d14-tarjan%E5%89%B2%E8%BE%B9</link><guid isPermaLink="true">https://ehnotgod.site/blog/d14-tarjan%E5%89%B2%E8%BE%B9</guid><description>Tarjan 割边（桥）：无向图桥判定。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P1656&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A 国派出将军 uim，对 B 国进行战略性措施，以解救涂炭的生灵。&lt;/p&gt;
&lt;p&gt;B 国有 $$n$$ 个城市，这些城市以铁路相连。任意两个城市都可以通过铁路直接或者间接到达。&lt;/p&gt;
&lt;p&gt;uim 发现有些铁路被毁坏之后，某两个城市无法互相通过铁路到达。这样的铁路就被称为 key road。&lt;/p&gt;
&lt;p&gt;uim 为了尽快使该国的物流系统瘫痪，希望炸毁铁路，以达到存在某两个城市无法互相通过铁路到达的效果。&lt;/p&gt;
&lt;p&gt;然而，只有一发炮弹（A 国国会不给钱了）。所以，他能轰炸哪一条铁路呢？&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行 $$n,m\ (1 \leq n\leq 150$$，$$1 \leq m \leq 5000)$$，分别表示有 $$n$$ 个城市，总共 $$m$$ 条铁路。&lt;/p&gt;
&lt;p&gt;以下 $$m$$ 行，每行两个整数 $$a, b$$，表示城市 $$a$$ 和城市 $$b$$ 之间有铁路直接连接。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输出有若干行。&lt;/p&gt;
&lt;p&gt;每行包含两个数字 $$a,b$$，其中 $$a&amp;#x3C;b$$，表示 $$\lang a,b\rang$$ 是 key road。&lt;/p&gt;
&lt;p&gt;请注意：输出时，所有的数对 $$\lang a,b\rang$$ 必须按照 $$a$$ 从小到大排序输出；如果 $$a$$ 相同，则根据 $$b$$ 从小到大排序。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;6 6
1 2
2 3
2 4
3 5
4 5
5 6
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1 2
5 6
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;images/D14-1.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Tarjan 判割边（桥）：DFS 记录 $dfn$ 和 $low$。对边 $(u,v)$，若 $low[v] &gt; dfn[u]$（$v$ 的子树无法回到 $u$ 或 $u$ 以上），则该边是桥（key road）。注意用边的编号而非端点判断重边，输出时按字典序。复杂度 $O(n+m)$。&lt;/p&gt;
&lt;h3&gt;py代码实现&lt;/h3&gt;
&lt;p&gt;略，涉及到dfs的都不适合写python。&lt;/p&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include&amp;#x3C;bits/stdc++.h&gt;
using namespace std;

const int N=210,M=10010;
int n,m,a,b;
int h[N],to[M],ne[M],idx=1; //从2,3开始配对
void add(int a,int b){
  to[++idx]=b;ne[idx]=h[a];h[a]=idx;
}
int dfn[N],low[N],tim,cnt;
struct bridge{
  int x,y;
  bool operator&amp;#x3C;(const bridge &amp;#x26;t)const{
    if(x==t.x) return y&amp;#x3C;t.y;
    return x&amp;#x3C;t.x;
  }
}bri[M]; //割边

void tarjan(int x,int ine){
  dfn[x]=low[x]=++tim;
  for(int i=h[x];i;i=ne[i]){
    int y=to[i];
    if(!dfn[y]){ //若y未访问
      tarjan(y,i);
      low[x]=min(low[x],low[y]);
      if(low[y]&gt;dfn[x]) bri[cnt++]={x,y};
    }
    else if(i!=(ine^1)) //若y已访问且不是反边
      low[x]=min(low[x],dfn[y]);
  }
}
int main(){
  cin&gt;&gt;n&gt;&gt;m;
  while(m--){
    cin&gt;&gt;a&gt;&gt;b;
    add(a,b),add(b,a);
  }
  for(int i=1;i&amp;#x3C;=n;i++) if(!dfn[i])tarjan(i,0);
  sort(bri,bri+cnt);
  for(int i=0;i&amp;#x3C;cnt;i++)
    cout&amp;#x3C;&amp;#x3C;bri[i].x&amp;#x3C;&amp;#x3C;&quot; &quot;&amp;#x3C;&amp;#x3C;bri[i].y&amp;#x3C;&amp;#x3C;&quot;\n&quot;;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>D15 Tarjan eDCC缩点</title><link>https://ehnotgod.site/blog/d15-tarjan-edcc%E7%BC%A9%E7%82%B9</link><guid isPermaLink="true">https://ehnotgod.site/blog/d15-tarjan-edcc%E7%BC%A9%E7%82%B9</guid><description>边双连通分量缩点（eDCC）。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P8436&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;对于一个 $$n$$ 个节点 $$m$$ 条无向边的图，请输出其边双连通分量的个数，并且输出每个边双连通分量。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行，两个整数 $$n$$ 和 $$m$$。&lt;/p&gt;
&lt;p&gt;接下来 $$m$$ 行，每行两个整数 $$u, v$$，表示一条无向边。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;不保证图为简单图，图中可能有重边和自环。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行一个整数 $$x$$ 表示边双连通分量的个数。&lt;/p&gt;
&lt;p&gt;接下来的 $$x$$ 行，每行第一个数 $$a$$ 表示该分量结点个数，然后 $$a$$ 个数，描述一个边双连通分量。&lt;/p&gt;
&lt;p&gt;你可以以任意顺序输出边双连通分量与边双连通分量内的结点。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;7 8
1 3
2 4
3 5
2 5
6 4
2 5
6 3
2 7
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;3
1 1
5 2 5 3 6 4
1 7
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;样例1解释：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/0bzdfzeq.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;相同颜色的点为同一个连通分量。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;数据范围：&lt;/strong&gt;
对于 $$100%$$ 的数据，$$1 \le n \le 5 \times10 ^5$$，$$1 \le m \le 2 \times 10^6$$。&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;images/D15-1.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;边双连通分量（eDCC）：删去图中所有桥之后，每个连通块就是一个 eDCC。先用 Tarjan 找出所有桥，再 DFS（或并查集）把不经过桥的点合并为同一分量。本题输出所有边双连通分量。复杂度 $O(n+m)$。&lt;/p&gt;
&lt;h3&gt;py代码实现&lt;/h3&gt;
&lt;p&gt;略，涉及到dfs的都不适合写python。&lt;/p&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include&amp;#x3C;bits/stdc++.h&gt;
using namespace std;

const int N=500010,M=4000010;
int n,m,a,b;
int h[N],to[M],ne[M],idx=1;
void add(int a,int b){
  to[++idx]=b,ne[idx]=h[a],h[a]=idx;
}
int dfn[N],low[N],tim,stk[N],top,dcc[N],siz[N],cnt,bri[M];
vector&amp;#x3C;int&gt; d[N];

void tarjan(int x,int ine){
  dfn[x]=low[x]=++tim;stk[++top]=x;
  for(int i=h[x];i;i=ne[i]){
    int y=to[i];
    if(!dfn[y]){
      tarjan(y,i);
      low[x]=min(low[x],low[y]);
      if(low[y]&amp;#x3C;dfn[x]) bri[i]=bri[i^1]=1;
    }
    else if(i!=(ine^1)) low[x]=min(low[x],dfn[y]);
  }
  
  if(dfn[x]==low[x]){
    ++cnt;
    while(1){
      int y=stk[top--];
      // dcc[y]=cnt;
      siz[cnt]++;
      d[cnt].push_back(y);
      if(x==y) break;
    }
  }
}
int main(){
  ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
  cin&gt;&gt;n&gt;&gt;m;
  while(m--){
    cin&gt;&gt;a&gt;&gt;b;add(a,b);add(b,a);
  }
  for(int i=1;i&amp;#x3C;=n;i++)if(!dfn[i])tarjan(i,0);
  cout&amp;#x3C;&amp;#x3C;cnt&amp;#x3C;&amp;#x3C;&quot;\n&quot;;
  for(int i=1;i&amp;#x3C;=cnt;i++){
    cout&amp;#x3C;&amp;#x3C;siz[i]&amp;#x3C;&amp;#x3C;&quot; &quot;;
    for(int j:d[i]) cout&amp;#x3C;&amp;#x3C;j&amp;#x3C;&amp;#x3C;&quot; &quot;;
    cout&amp;#x3C;&amp;#x3C;&quot;\n&quot;;
  }
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>D16 Tarjan vDCC缩点</title><link>https://ehnotgod.site/blog/d16-tarjan-vdcc%E7%BC%A9%E7%82%B9</link><guid isPermaLink="true">https://ehnotgod.site/blog/d16-tarjan-vdcc%E7%BC%A9%E7%82%B9</guid><description>点双连通分量缩点（vDCC）。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P8435&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;对于一个 $$n$$ 个节点 $$m$$ 条无向边的图，请输出其点双连通分量的个数，并且输出每个点双连通分量。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行，两个整数 $$n$$ 和 $$m$$。&lt;/p&gt;
&lt;p&gt;接下来 $$m$$ 行，每行两个整数 $$u, v$$，表示一条无向边。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行一个整数 $$x$$ 表示点双连通分量的个数。&lt;/p&gt;
&lt;p&gt;接下来的 $$x$$ 行，每行第一个数 $$a$$ 表示该分量结点个数，然后 $$a$$ 个数，描述一个点双连通分量。&lt;/p&gt;
&lt;p&gt;你可以以任意顺序输出点双连通分量与点双连通分量内的结点。&lt;/p&gt;
&lt;p&gt;输入 #4&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;7 8
1 3
2 4
3 5
2 5
6 4
2 5
6 3
2 7
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #4&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;3
2 7 2
5 5 2 4 6 3
2 3 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输入 #5&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1 1
1 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #5&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1
1 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;说明/提示&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;样例四解释：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/huvwgbuo.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;相同颜色的点为同一个分量里的结点。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;温馨提示：请认真考虑孤立点与自环（样例五）的情况。&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;数据范围：&lt;/strong&gt;
对于 $$100%$$ 的数据，$$1 \le n \le 5 \times10 ^5$$，$$1 \le m \le 2 \times 10^6$$。&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;images/D16-1.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;点双连通分量（vDCC）：Tarjan 时用栈维护，当发现割点 $u$ 且子节点 $v$ 满足 $low[v] \ge dfn[u]$ 时，从栈中弹出直到 $v$，连同 $u$ 构成一个 vDCC（注意 $u$ 可能属于多个 vDCC，vDCC 之间通过割点相连）。复杂度 $O(n+m)$。&lt;/p&gt;
&lt;h3&gt;py代码实现&lt;/h3&gt;
&lt;p&gt;略，涉及到dfs的都不适合写python。&lt;/p&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include&amp;#x3C;bits/stdc++.h&gt;
using namespace std;

const int N=500010;
int n,m,a,b;
vector&amp;#x3C;int&gt; e[N],ne[N],dcc[N];
int dfn[N],low[N],tot,stk[N],top,cut[N],root,cnt;

void tarjan(int x){
  if(x==root&amp;#x26;&amp;#x26;!e[x].size()){ //孤立点
        dcc[++cnt].push_back(x);
        return;
    }  
  dfn[x]=low[x]=++tot; stk[++top]=x;
  int son=0;
  for(int y:e[x]){
    if(!dfn[y]){ //若y未访问
      tarjan(y);
      low[x]=min(low[x],low[y]); 
      if(low[y]&gt;=dfn[x]){
        son++;
        if(x!=root||son&gt;1)cut[x]=1; //割点
        
        ++cnt;
        while(1){
          int z=stk[top--];
          dcc[cnt].push_back(z);
          if(z==y) break; //让x留在栈中
        }
        dcc[cnt].push_back(x); //vDCC
      }
    }
    else //若y已访问
      low[x]=min(low[x],dfn[y]);
  }
}
int main(){
  ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
  cin&gt;&gt;n&gt;&gt;m;
  while(m--){
    cin&gt;&gt;a&gt;&gt;b;
    if(a==b) continue; //忽略自环
    e[a].push_back(b),
    e[b].push_back(a);
  }
  for(root=1;root&amp;#x3C;=n;root++)if(!dfn[root])tarjan(root);
  cout&amp;#x3C;&amp;#x3C;cnt&amp;#x3C;&amp;#x3C;&quot;\n&quot;;
  for(int i=1;i&amp;#x3C;=cnt;i++){
    cout&amp;#x3C;&amp;#x3C;dcc[i].size()&amp;#x3C;&amp;#x3C;&quot; &quot;;
    for(int j:dcc[i])cout&amp;#x3C;&amp;#x3C;j&amp;#x3C;&amp;#x3C;&quot; &quot;;
    cout&amp;#x3C;&amp;#x3C;&quot;\n&quot;;
  }
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>D17 Dinic最大流</title><link>https://ehnotgod.site/blog/d17-dinic%E6%9C%80%E5%A4%A7%E6%B5%81</link><guid isPermaLink="true">https://ehnotgod.site/blog/d17-dinic%E6%9C%80%E5%A4%A7%E6%B5%81</guid><description>Dinic 最大流：分层图 + 阻塞流。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P3376&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;如题，给出一个网络图，以及其源点和汇点，求出其网络最大流。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行包含四个正整数 $$n,m,s,t$$，分别表示点的个数、有向边的个数、源点序号、汇点序号。&lt;/p&gt;
&lt;p&gt;接下来 $$m$$ 行每行包含三个正整数 $$u_i,v_i,w_i$$，表示第 $$i$$ 条有向边从 $$u_i$$ 出发，到达 $$v_i$$，边权为 $$w_i$$（即该边最大流量为 $$w_i$$）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;一行，包含一个正整数，即为该网络的最大流。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;4 5 4 3
4 2 30
4 3 20
2 3 20
2 1 30
1 3 30
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;50
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;对于 $$100%$$ 的数据，保证 $$1 \leq n\leq200$$，$$1 \leq m\leq 5000$$，$$0 \leq w\lt 2^{31}$$。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;理论复杂度：$$O(n^2m)$$，实际挺快的&lt;/p&gt;
&lt;p&gt;二分图上的Dinic更快。&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;Dinic 最大流：每次用 BFS 建分层图（按到源点距离分层），再在当前分层图上用 DFS 找阻塞流（一次可增广多条路径，配合当前弧优化），反复执行直到 BFS 无法到达汇点。复杂度 $O(n^2m)$，实际表现很快。&lt;/p&gt;
&lt;p&gt;太难了，不讲具体了。&lt;/p&gt;
&lt;h3&gt;py代码实现&lt;/h3&gt;
&lt;p&gt;略，涉及到dfs的都不适合写python。&lt;/p&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;// Luogu P3376 【模板】网络最大流
#include &amp;#x3C;iostream&gt;
#include &amp;#x3C;cstring&gt;
#include &amp;#x3C;algorithm&gt;
#include &amp;#x3C;queue&gt;
#define LL long long
#define N 10010
#define M 200010
using namespace std;

int n,m,S,T;
struct edge{LL v,c,ne;}e[M];
int h[N],idx=1; //从2,3开始配对
int d[N],cur[N];

void add(int a,int b,int c){
  e[++idx]={b,c,h[a]};
  h[a]=idx;
}
bool bfs(){ //对点分层，找增广路
  memset(d,0,sizeof d);
  queue&amp;#x3C;int&gt;q; 
  q.push(S); d[S]=1;
  while(q.size()){
    int u=q.front(); q.pop();
    for(int i=h[u];i;i=e[i].ne){
      int v=e[i].v;
      if(d[v]==0 &amp;#x26;&amp;#x26; e[i].c){
        d[v]=d[u]+1;
        q.push(v);
        if(v==T)return true;
      }
    }
  }
  return false;
}
LL dfs(int u, LL mf){ //多路增广
  if(u==T) return mf;
  LL sum=0;
  for(int i=cur[u];i;i=e[i].ne){
    cur[u]=i; //当前弧优化
    int v=e[i].v;
    if(d[v]==d[u]+1 &amp;#x26;&amp;#x26; e[i].c){
      LL f=dfs(v,min(mf,e[i].c));
      e[i].c-=f; 
      e[i^1].c+=f; //更新残留网
      sum+=f; //累加u的流出流量
      mf-=f;  //减少u的剩余流量
      if(mf==0)break;//余量优化
    }
  }
  if(sum==0) d[u]=0; //残枝优化
  return sum;
}
LL dinic(){ //累加可行流
  LL flow=0;
  while(bfs()){
    memcpy(cur, h, sizeof h);
    flow+=dfs(S,1e9);
  }
  return flow;
}
int main(){
  int a,b,c;
  scanf(&quot;%d%d%d%d&quot;,&amp;#x26;n,&amp;#x26;m,&amp;#x26;S,&amp;#x26;T);
  while(m -- ){
    scanf(&quot;%d%d%d&quot;,&amp;#x26;a,&amp;#x26;b,&amp;#x26;c);
    add(a,b,c); add(b,a,0);
  }
  printf(&quot;%lld\n&quot;,dinic());
  return 0;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>D18 最小费用最大流EK</title><link>https://ehnotgod.site/blog/d18-%E6%9C%80%E5%B0%8F%E8%B4%B9%E7%94%A8%E6%9C%80%E5%A4%A7%E6%B5%81ek</link><guid isPermaLink="true">https://ehnotgod.site/blog/d18-%E6%9C%80%E5%B0%8F%E8%B4%B9%E7%94%A8%E6%9C%80%E5%A4%A7%E6%B5%81ek</guid><description>最小费用最大流：EK + SPFA 增广。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P3381&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;给出一个包含 $$n$$ 个点和 $$m$$ 条边的有向图（下面称其为网络） $$G=(V,E)$$，该网络上所有点分别编号为 $$1 \sim n$$，所有边分别编号为 $$1\sim m$$，其中该网络的源点为 $$s$$，汇点为 $$t$$，网络上的每条边 $$(u,v)$$ 都有一个流量限制 $$w(u,v)$$ 和单位流量的费用 $$c(u,v)$$。&lt;/p&gt;
&lt;p&gt;你需要给每条边 $$(u,v)$$ 确定一个流量 $$f(u,v)$$，要求：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;$$0 \leq f(u,v) \leq w(u,v)$$（每条边的流量不超过其流量限制）；&lt;/li&gt;
&lt;li&gt;$$\forall p \in {V \setminus {s,t}}$$，$$\sum_{(i,p) \in E}f(i,p)=\sum_{(p,i)\in E}f(p,i)$$（除了源点和汇点外，其他各点流入的流量和流出的流量相等）；&lt;/li&gt;
&lt;li&gt;$$\sum_{(s,i)\in E}f(s,i)=\sum_{(i,t)\in E}f(i,t)$$（源点流出的流量等于汇点流入的流量）。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;定义网络 $$G$$ 的流量 $$F(G)=\sum_{(s,i)\in E}f(s,i)$$，网络 $$G$$ 的费用 $$C(G)=\sum_{(i,j)\in E} f(i,j) \times c(i,j)$$。&lt;/p&gt;
&lt;p&gt;你需要求出该网络的&lt;strong&gt;最小费用最大流&lt;/strong&gt;，即在 $$F(G)$$ 最大的前提下，使 $$C(G)$$ 最小。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输入第一行包含四个整数 $$n,m,s,t$$，分别代表该网络的点数 $$n$$，网络的边数 $$m$$，源点编号 $$s$$，汇点编号 $$t$$。&lt;/p&gt;
&lt;p&gt;接下来 $$m$$ 行，每行四个整数 $$u_i,v_i,w_i,c_i$$，分别代表第 $$i$$ 条边的起点，终点，流量限制，单位流量费用。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输出两个整数，分别为该网络的最大流 $$F(G)$$，以及在 $$F(G)$$ 最大的前提下，该网络的最小费用 $$C(G)$$。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;4 5 4 3
4 2 30 2
4 3 20 3
2 3 20 1
2 1 30 9
1 3 40 5
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;50 280
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于 $$100%$$ 的数据，$$1 \leq n \leq 5\times 10^3$$，$$1 \leq m \leq 5 \times 10^4$$，$$1 \leq s,t \leq n$$，$$u_i \neq v_i$$，$$0 \leq w_i,c_i \leq 10^3$$，且该网络的最大流和最小费用 $$\leq 2^{31}-1$$。&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;images/D18-1.png&quot; alt=&quot;image-20251101160808199&quot;&gt;&lt;/p&gt;
&lt;p&gt;最小费用最大流：在 EK 框架下，每次用 SPFA 在残量网络上找费用最小的增广路，沿最短路增广并累加费用，直到找不到增广路。因为残量网络中存在负费用反向边，用 SPFA 而非 Dijkstra。复杂度 $O(F\cdot nm)$。&lt;/p&gt;
&lt;h3&gt;py代码实现&lt;/h3&gt;
&lt;p&gt;略，涉及到dfs的都不适合写python。&lt;/p&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;// Luogu P3381 【模板】最小费用最大流
#include &amp;#x3C;iostream&gt;
#include &amp;#x3C;cstring&gt;
#include &amp;#x3C;algorithm&gt;
#include &amp;#x3C;queue&gt;
using namespace std;

const int N=5010,M=100010,INF=1e8;
int n,m,S,T;
struct edge{int v,c,w,ne;}e[M];
int h[N],idx=1;//从2,3开始配对
int d[N],mf[N],pre[N],vis[N];
int flow,cost;

void add(int a,int b,int c,int d){
  e[++idx]={b,c,d,h[a]};
  h[a]=idx;
}
bool spfa(){
  memset(d,0x3f,sizeof d);
  memset(mf,0,sizeof mf);
  queue&amp;#x3C;int&gt; q; q.push(S);
  d[S]=0, mf[S]=INF, vis[S]=1;
  while(q.size()){
    int u=q.front(); q.pop();
    vis[u]=0;
    for(int i=h[u];i;i=e[i].ne){
      int v=e[i].v,c=e[i].c,w=e[i].w;
      if(d[v]&gt;d[u]+w &amp;#x26;&amp;#x26; c){
        d[v]=d[u]+w; //最短路
        pre[v]=i;
        mf[v]=min(mf[u],c);
        if(!vis[v]){
          q.push(v); vis[v]=1;
        }
      }
    }
  }
  return mf[T]&gt;0;
}
void EK(){
  while(spfa()){
    for(int v=T;v!=S;){
      int i=pre[v];
      e[i].c-=mf[T];
      e[i^1].c+=mf[T];
      v=e[i^1].v;
    }
    flow+=mf[T]; //累加可行流
    cost+=mf[T]*d[T];//累加费用   
  }
}
int main(){
  scanf(&quot;%d%d%d%d&quot;,&amp;#x26;n,&amp;#x26;m,&amp;#x26;S,&amp;#x26;T);
  int a,b,c,d;
  while(m --){
    scanf(&quot;%d%d%d%d&quot;,&amp;#x26;a,&amp;#x26;b,&amp;#x26;c,&amp;#x26;d);
    add(a,b,c,d);
    add(b,a,0,-d);
  }
  EK();
  printf(&quot;%d %d\n&quot;,flow,cost);
  return 0;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>D19 二分图最大匹配</title><link>https://ehnotgod.site/blog/d19-%E4%BA%8C%E5%88%86%E5%9B%BE%E6%9C%80%E5%A4%A7%E5%8C%B9%E9%85%8D</link><guid isPermaLink="true">https://ehnotgod.site/blog/d19-%E4%BA%8C%E5%88%86%E5%9B%BE%E6%9C%80%E5%A4%A7%E5%8C%B9%E9%85%8D</guid><description>二分图最大匹配：匈牙利算法。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P3386&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;给定一个二分图，其左部点的个数为 $$n$$，右部点的个数为 $$m$$，边数为 $$e$$，求其最大匹配的边数。&lt;/p&gt;
&lt;p&gt;左部点从 $$1$$ 至 $$n$$ 编号，右部点从 $$1$$ 至 $$m$$ 编号。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输入的第一行是三个整数，分别代表 $$n$$，$$m$$ 和 $$e$$。&lt;/p&gt;
&lt;p&gt;接下来 $$e$$ 行，每行两个整数 $$u, v$$，表示存在一条连接左部点 $$u$$ 和右部点 $$v$$ 的边。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输出一行一个整数，代表二分图最大匹配的边数。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1 1 1
1 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;匈牙利算法求二分图最大匹配：对每个左部点尝试匹配一个右部点，若该右部点已被匹配，则递归尝试让它的原配重新寻找新的右部点（即寻找增广路），成功则匹配数 +1。复杂度 $O(ne)$。&lt;/p&gt;
&lt;p&gt;Dinic算法的复杂度少一个根号n。&lt;/p&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;//
#include &amp;#x3C;iostream&gt;
#include &amp;#x3C;cstring&gt;
#include &amp;#x3C;algorithm&gt;
#include &amp;#x3C;queue&gt;
#define N 1010
#define M 2000010
using namespace std;

int n,m,k,S,T;
struct edge{int v,c,ne;}e[M];
int h[N],idx=1; //从2,3开始配对
int d[N],cur[N];

void add(int a,int b,int c){
  e[++idx]={b,c,h[a]};
  h[a]=idx;
}
bool bfs(){ //对点分层，找增广路
  memset(d,0,sizeof d);
  queue&amp;#x3C;int&gt;q; 
  q.push(S); d[S]=1;
  while(q.size()){
    int u=q.front(); q.pop();
    for(int i=h[u];i;i=e[i].ne){
      int v=e[i].v;
      if(d[v]==0 &amp;#x26;&amp;#x26; e[i].c){
        d[v]=d[u]+1;
        q.push(v);
        if(v==T)return true;
      }
    }
  }
  return false;
}
int dfs(int u, int mf){ //多路增广
  if(u==T) return mf;
  int sum=0;
  for(int i=cur[u];i;i=e[i].ne){
    cur[u]=i; //当前弧优化
    int v=e[i].v;
    if(d[v]==d[u]+1 &amp;#x26;&amp;#x26; e[i].c){
      int f=dfs(v,min(mf,e[i].c));
      e[i].c-=f; 
      e[i^1].c+=f; //更新残留网
      sum+=f; //累加u的流出流量
      mf-=f;  //减少u的剩余流量
      if(mf==0)break;//余量优化
    }
  }
  if(sum==0) d[u]=0; //残枝优化
  return sum;
}
int dinic(){ //累加可行流
  int flow=0;
  while(bfs()){
    memcpy(cur, h, sizeof h);
    flow+=dfs(S,1e9);
  }
  return flow;
}
int main(){
  int a,b,c;
  scanf(&quot;%d%d%d&quot;,&amp;#x26;n,&amp;#x26;m,&amp;#x26;k);
  while(k--){
    scanf(&quot;%d%d&quot;,&amp;#x26;a,&amp;#x26;b);
    add(a,b+n,1);add(b+n,a,0);
  }
  S=0;T=n+m+1;
  for(int i=1;i&amp;#x3C;=n;i++)
    add(S,i,1),add(i,S,0);
  for(int i=1;i&amp;#x3C;=m;i++)
    add(i+n,T,1),add(T,i+n,0); 
  printf(&quot;%lld\n&quot;,dinic());
  return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;dinic:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;//
#include&amp;#x3C;bits/stdc++.h&gt;
#define N 1010
#define M 2000010
using namespace std;

int n,m,k,S,T;
struct edge{int v,c,ne;}e[M];
int h[N],idx=1; //从2,3开始配对
int d[N],cur[N];

void add(int a,int b,int c){
  e[++idx]={b,c,h[a]};
  h[a]=idx;
}
bool bfs(){ //对点分层，找增广路
  memset(d,0,sizeof d);
  queue&amp;#x3C;int&gt;q; 
  q.push(S); d[S]=1;
  while(q.size()){
    int u=q.front(); q.pop();
    for(int i=h[u];i;i=e[i].ne){
      int v=e[i].v;
      if(d[v]==0 &amp;#x26;&amp;#x26; e[i].c){
        d[v]=d[u]+1;
        q.push(v);
        if(v==T)return true;
      }
    }
  }
  return false;
}
int dfs(int u, int mf){ //多路增广
  if(u==T) return mf;
  int sum=0;
  for(int i=cur[u];i;i=e[i].ne){
    cur[u]=i; //当前弧优化
    int v=e[i].v;
    if(d[v]==d[u]+1 &amp;#x26;&amp;#x26; e[i].c){
      int f=dfs(v,min(mf,e[i].c));
      e[i].c-=f; 
      e[i^1].c+=f; //更新残留网
      sum+=f; //累加u的流出流量
      mf-=f;  //减少u的剩余流量
      if(mf==0)break;//余量优化
    }
  }
  if(sum==0) d[u]=0; //残枝优化
  return sum;
}
int dinic(){ //累加可行流
  int flow=0;
  while(bfs()){
    memcpy(cur, h, sizeof h);
    flow+=dfs(S,1e9);
  }
  return flow;
}
int main(){
  int a,b,c;
  scanf(&quot;%d%d%d&quot;,&amp;#x26;n,&amp;#x26;m,&amp;#x26;k);
  while(k--){
    scanf(&quot;%d%d&quot;,&amp;#x26;a,&amp;#x26;b);
    add(a,b+n,1);add(b+n,a,0);
  }
  S=0;T=n+m+1;
  for(int i=1;i&amp;#x3C;=n;i++)
    add(S,i,1),add(i,S,0);
  for(int i=1;i&amp;#x3C;=m;i++)
    add(i+n,T,1),add(T,i+n,0); 
  printf(&quot;%lld\n&quot;,dinic());
  return 0;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>D20 树的直径</title><link>https://ehnotgod.site/blog/d20-%E6%A0%91%E7%9A%84%E7%9B%B4%E5%BE%84</link><guid isPermaLink="true">https://ehnotgod.site/blog/d20-%E6%A0%91%E7%9A%84%E7%9B%B4%E5%BE%84</guid><description>树的直径：两次 DFS/BFS 求最远点对。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/B4016&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;给定一棵 $$n$$ 个结点的树，树没有边权。请求出树的直径是多少，即树上最长的不重复经过一个点的路径长度是多少。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行输入一个正整数 $$n$$，表示结点个数。&lt;/p&gt;
&lt;p&gt;第二行开始，往下一共 $$n-1$$ 行，每一行两个正整数 $$(u,v)$$，表示一条边。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输出一行，表示树的直径是多少。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;5
1 2
2 4
4 5
2 3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;数据保证，$$1 \leq n \leq 10^5$$。&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;images/D20-1.png&quot; alt=&quot;image-20250816214118195&quot;&gt;&lt;/p&gt;
&lt;p&gt;树的直径即树上最长路径。方法：任选一点出发 DFS/BFS 找到最远点 $p$，再从 $p$ 出发 DFS/BFS 找到最远点 $q$，则 $p$ 到 $q$ 的路径就是直径（原理：从任意点出发的最远点必是直径的一个端点）。无负边权时也可用树形 DP 求。复杂度 $O(n)$。&lt;/p&gt;
&lt;h3&gt;py代码实现&lt;/h3&gt;
&lt;p&gt;略，涉及到dfs的都不适合写python。&lt;/p&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;// 树的直径 正边权 两次DFS O(n)
#include&amp;#x3C;bits/stdc++.h&gt;
using namespace std;

const int N=100005;
int n,rt,d[N];
vector&amp;#x3C;pair&amp;#x3C;int,int&gt;&gt; e[N];

void dfs(int u,int fa){
  if(d[rt]&amp;#x3C;d[u]) rt=u; //记录最远点
  for(auto [v,w]:e[u]){
    if(v==fa) continue;
    d[v]=d[u]+w; //d[v]从根走到v的距离
    dfs(v,u);
  }
}
int main(){
  cin&gt;&gt;n;
  for(int i=1,x,y;i&amp;#x3C;n;i++){
    cin&gt;&gt;x&gt;&gt;y;
    e[x].emplace_back(y,1);
    e[y].emplace_back(x,1);
  }
  dfs(1,0);  //找出离1最远的点rt
  d[rt]=0;
  dfs(rt,0); //找出离rt最远的点
  cout&amp;#x3C;&amp;#x3C;d[rt];
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;// 树的直径 正负边权 树形DP O(n)
#include&amp;#x3C;bits/stdc++.h&gt;
using namespace std;

const int N=100005;
int n,mxd,d[N]; //d[u]从u点向下走的最长距离
vector&amp;#x3C;pair&amp;#x3C;int,int&gt;&gt; e[N];

void dfs(int u,int fa){
  for(auto [v,w]:e[u]){
    if(v==fa) continue;
    dfs(v,u);
    mxd=max(mxd,d[u]+w+d[v]); //拼凑直径
    d[u]=max(d[u],d[v]+w);    //更新d[u]
  }
}
int main(){
  cin&gt;&gt;n;
  for(int i=1,x,y;i&amp;#x3C;n;i++){
    cin&gt;&gt;x&gt;&gt;y;
    e[x].emplace_back(y,1);
    e[y].emplace_back(x,1);
  }
  dfs(1,0);
  cout&amp;#x3C;&amp;#x3C;mxd;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>D21 树的重心</title><link>https://ehnotgod.site/blog/d21-%E6%A0%91%E7%9A%84%E9%87%8D%E5%BF%83</link><guid isPermaLink="true">https://ehnotgod.site/blog/d21-%E6%A0%91%E7%9A%84%E9%87%8D%E5%BF%83</guid><description>树的重心：删去后最大子树最小。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P1364&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;给定一个 $n$ 个节点的&lt;strong&gt;无根树&lt;/strong&gt;，节点按照 $1 \sim n$ 编号，并且无&lt;strong&gt;自环&lt;/strong&gt;和&lt;strong&gt;重边&lt;/strong&gt;。求它的重心，如果有多个重心，按照编号从小到大的顺序依次输出。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行：一个整数 $n$，含义见题目描述。&lt;br&gt;
接下来 $n-1$ 行，每行两个整数 $u,v$，代表 $u$ 和 $v$ 之间有一条连边。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;仅一行，按照编号从小到大输出重心。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;6
1 2
2 3
2 5
3 4
3 6
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2 3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输入 #2&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;8
1 2
2 3
3 4
4 5
5 6
6 7
7 8

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #2&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;4 5
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于 $100%$ 的数据，$2 \leq n \leq 5 \times 10^4$。&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;树的重心：删去某点后，剩余连通块中最大者最小。DFS 求每个点的子树大小 $siz[u]$，则删去 $u$ 后最大子树为 $\max(儿子们的 siz,\ n-siz[u])$（$n-siz[u]$ 是上方部分），取所有点的最小值即重心对应的值。复杂度 $O(n)$。&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import sys
sys.setrecursionlimit(100000)
input = sys.stdin.readline
n = int(input())
e = [[] for i in range(n + 1)]
for i in range(n - 1):
    u, v = map(int, input().split())
    e[u].append(v); e[v].append(u)
siz = [0] * (n + 1)
f = [0] * (n + 1)
cnt = int(1e9)
def dfs(u, p):
    global cnt
    siz[u] = 1
    for v in e[u]:
        if v != p:
            dfs(v, u)
            siz[u] += siz[v]
            f[u] = max(f[u], siz[v])
    f[u] = max(f[u], n - siz[u])
    cnt = min(cnt, f[u])
dfs(1, 0)
for i in range(1, n + 1):
    if f[i] == cnt:
        print(i, end = &quot; &quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;// 树的重心 树形DP O(n)
#include&amp;#x3C;bits/stdc++.h&gt;
using namespace std;

const int N=50010;
int n,siz[N],f[N],cnt=1e9;
vector&amp;#x3C;int&gt; e[N],g;

void dfs(int u,int fa){
  siz[u]=1;
  for(auto v:e[u]){
    if(v==fa) continue;
    dfs(v,u);
    f[u]=max(f[u],siz[v]); //u的最大子树
    siz[u]+=siz[v];
  }
  f[u]=max(f[u],n-siz[u]); //删除u后的最大连通块
  cnt=min(cnt,f[u]);       //最大块最小化
}
int main(){
  scanf(&quot;%d&quot;,&amp;#x26;n);
  for(int i=1,a,b;i&amp;#x3C;n;i++){
    scanf(&quot;%d%d&quot;,&amp;#x26;a,&amp;#x26;b);
    e[a].push_back(b);
    e[b].push_back(a);
  }
  dfs(1,0);
  for(int i=1;i&amp;#x3C;=n;i++) if(f[i]==cnt) g.push_back(i);
  for(int v:g) printf(&quot;%d &quot;,v);
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>D22 Kruscal重构树</title><link>https://ehnotgod.site/blog/d22-kruscal%E9%87%8D%E6%9E%84%E6%A0%91</link><guid isPermaLink="true">https://ehnotgod.site/blog/d22-kruscal%E9%87%8D%E6%9E%84%E6%A0%91</guid><description>Kruscal 重构树：最小瓶颈 / 连通性离线问题。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/U201076&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;给定一张 $$N$$ 个点的无向图，记为 $$1\cdots N$$ 。&lt;/p&gt;
&lt;p&gt;图中有 $$M$$ 条边，第 $$j$$ 条边的长度为 $$d_j$$ 。&lt;/p&gt;
&lt;p&gt;现在有 $$K$$ 个询问，每个询问的格式为 $$A\ \  B$$，表示询问从 $$A$$ 走到 $$B$$ 的所有路径中，最长边的最小值为多少。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行为三个整数 $$N, M, K$$ 。&lt;/p&gt;
&lt;p&gt;第二行到第 $$M+1$$ 行，每行三个整数 $$X, Y, D$$ ，表示从 $$X$$ 与 $$Y$$ 之间有一条长度为 $$D$$ 的边。&lt;/p&gt;
&lt;p&gt;第 $$M+2$$ 行到第 $$M+K+1$$ 行，每行两个整数 $$A, B$$ ，意义如上。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;共 $$K$$ 行。&lt;/p&gt;
&lt;p&gt;对于每个询问，输出最长边的最小值。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;6 6 8
1 2 5
2 3 4
3 4 3
1 4 8
2 5 7
4 6 2
1 2
1 3
1 4
2 3
2 4
5 1
6 2
6 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;5
5
5
4
4
7
4
5
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;$$1 \leq N \leq 15000$$&lt;/p&gt;
&lt;p&gt;$$1 \leq M \leq 30000$$&lt;/p&gt;
&lt;p&gt;$$1 \leq D_j \leq 10^9$$&lt;/p&gt;
&lt;p&gt;$$1 \leq K \leq 15000$$&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;Kruskal 重构树：按边权从小到大做 Kruskal，每次合并两个连通块时新建一个权值为该边权的虚点作为两者的父节点。这样得到一棵二叉重构树，其中任意两点的 LCA 权值就是这两点间所有路径中最大边权的最小值（最小瓶颈路）。查询用 LCA 即可。&lt;/p&gt;
&lt;p&gt;具体算法流程：&lt;/p&gt;
&lt;p&gt;对于此题来说，将边权从小到大排序
用并查集维护两点的联通性，若祖先不相同，那么新建一个节点，权值为边权。左右儿子分别为两个点
这样建出来的树，我们称之为Kruskal重构树&lt;/p&gt;
&lt;p&gt;它有许多美妙的性质&lt;/p&gt;
&lt;p&gt;是一颗二叉树
两点的LCA的点权为原图中最大值最小的路径上的最大值
任意点的权值大于左右儿子的权值，是一个大根堆
对于此题的样例来说，建出来的图大概长这样&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/D22-1.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3&gt;py代码实现&lt;/h3&gt;
&lt;p&gt;略&lt;/p&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;/*EHnotgod————
..............#######.....#.....#..............
..............#...........#.....#..............
..............#######.....#######..............
..............#...........#.....#..............
..............#######.....#.....#..............
*/
#include &amp;#x3C;bits/stdc++.h&gt;
using namespace std;
#define endl &quot;\n&quot;
#define int long long
#define range(i, a, b) for (int i = (a); i &amp;#x3C; (b); ++i)
#define def(name, ...) auto name = [&amp;#x26;](__VA_ARGS__)

const int N=200006;
int n, m, q;
struct edge{
  int u,v,w;
  bool operator&amp;#x3C;(const edge &amp;#x26;t)const
  {return w &amp;#x3C; t.w;}
}edge[N];
int fa2[N],ans,cnt;
vector&amp;#x3C;int&gt; value(N, 0);
vector&amp;#x3C;vector&amp;#x3C;int&gt;&gt; e(N, vector&amp;#x3C;int&gt;());
int find(int x){
  if(fa2[x]==x) return x;
  return fa2[x]=find(fa2[x]);
}
void kruskal(){
  sort(edge,edge+m);
  cnt = n;
  for(int i=1;i&amp;#x3C;N;i++)fa2[i]=i;
  for(int i=0; i&amp;#x3C;m; i++){
    int x = find(edge[i].u);
    int y = find(edge[i].v);
    if(x!=y){
        cnt++;
        fa2[x]=cnt;
        fa2[y]=cnt;
        value[cnt] = edge[i].w;

        e[x].push_back(cnt);
        e[y].push_back(cnt);
        e[cnt].push_back(x);
        e[cnt].push_back(y);
    }
  }
}
int f[N][22],dep[N];

void dfs(int u,int fa){
  f[u][0]=fa; dep[u]=dep[fa]+1;
  for(int i=1;i&amp;#x3C;=20;i++) //u的2,4,8...祖先
    f[u][i]=f[f[u][i-1]][i-1];
  for(int v:e[u])
    if(v!=fa) dfs(v,u);
}
int lca(int u,int v){
  if(dep[u]&amp;#x3C;dep[v]) swap(u,v);
  for(int i=20;~i;i--) //u先大步后小步向上跳，直到与v同层
    if(dep[f[u][i]]&gt;=dep[v]) u=f[u][i];
  if(u==v) return v;
  for(int i=20;~i;i--) //u,v一起向上跳，直到lca的下面
    if(f[u][i]!=f[v][i]) u=f[u][i],v=f[v][i];
  return f[u][0];
}
// vector&amp;#x3C;int&gt; a(n, 0);
// vector&amp;#x3C;vector&amp;#x3C;int&gt;&gt; dp(n + 1, vector&amp;#x3C;int&gt;(n + 1, 0));
void solve() {
	cin &gt;&gt; n &gt;&gt; m &gt;&gt; q;
	for (int i = 0; i &amp;#x3C; m; i++){
		cin &gt;&gt; edge[i].u &gt;&gt; edge[i].v &gt;&gt; edge[i].w;
	}
    kruskal();

    dfs(cnt, 0);
    
    while (q--){
        int u, v; cin &gt;&gt; u &gt;&gt; v;
        int uv = lca(u, v);
        cout &amp;#x3C;&amp;#x3C; value[uv] &amp;#x3C;&amp;#x3C; endl;
    }
}

signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
	int t;
	t = 1;
	while (t--) {
		solve();
	}
	return 0;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>D23 2-SAT</title><link>https://ehnotgod.site/blog/d23-2-sat</link><guid isPermaLink="true">https://ehnotgod.site/blog/d23-2-sat</guid><description>2-SAT：变量拆点 + Tarjan 强连通判定与构造。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P4782&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;有 $n$ 个布尔变量 $x_1\sim x_n$，另有 $m$ 个需要满足的条件，每个条件的形式都是 「$x_i$ 为 &lt;code&gt;true&lt;/code&gt; / &lt;code&gt;false&lt;/code&gt; 或 $x_j$ 为 &lt;code&gt;true&lt;/code&gt; / &lt;code&gt;false&lt;/code&gt;」。比如 「$x_1$ 为真或 $x_3$ 为假」、「$x_7$ 为假或 $x_2$ 为假」。&lt;/p&gt;
&lt;p&gt;2-SAT 问题的目标是给每个变量赋值使得所有条件得到满足。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行两个整数 $n$ 和 $m$，意义如题面所述。&lt;/p&gt;
&lt;p&gt;接下来 $m$ 行每行 $4$ 个整数 $i$, $a$, $j$, $b$，表示 「$x_i$ 为 $a$ 或 $x_j$ 为 $b$」($a, b\in {0,1}$)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;如无解，输出 &lt;code&gt;IMPOSSIBLE&lt;/code&gt;；&lt;/p&gt;
&lt;p&gt;否则输出 &lt;code&gt;POSSIBLE&lt;/code&gt;，下一行 $n$ 个整数 $x_1\sim x_n$（$x_i\in{0,1}$），表示构造出的解。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;3 1
1 1 3 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;POSSIBLE
0 0 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;说明/提示&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;$1\leq n, m\leq 10^6$ , 前 $3$ 个点卡小错误，后面 $5$ 个点卡效率。&lt;/p&gt;
&lt;p&gt;由于数据随机生成，可能会含有 10 0 10 0 之类的坑，但按照最常规写法的写的标程没有出错，各个数据点卡什么的提示在标程里。&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;2-SAT 把每个变量 $x_i$ 拆成两个点：$i$ 表示 $x_i$ 为真、$i+n$ 表示 $x_i$ 为假。条件「$x_i$ 为 $a$ 或 $x_j$ 为 $b$」等价于两条蕴含边：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$\neg x_i^a \rightarrow x_j^b$（若 $x_i$ 不满足 $a$，则 $x_j$ 必须满足 $b$）&lt;/li&gt;
&lt;li&gt;$\neg x_j^b \rightarrow x_i^a$（对称）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;建图后用 Tarjan 求强连通分量。若某个变量 $x_i$ 的两种取值（$i$ 与 $i+n$）在同一个 SCC 中，则矛盾无解，输出 &lt;code&gt;IMPOSSIBLE&lt;/code&gt;；否则有解：对每个变量取 $scc$ 编号较大的那个取值（Tarjan 求出的 $scc$ 编号是拓扑序的逆序，取大者即取拓扑序靠后的可行值）。复杂度 $O(n+m)$。&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import sys
sys.setrecursionlimit(10**6)
input = sys.stdin.readline

def tarjan(sz, edges):
    g = [[] for _ in range(sz)]
    for u, v in edges:
        g[u].append(v)
    dfn = [0] * sz; low = [0] * sz; scc = [0] * sz
    stk = []; in_stk = [False] * sz
    time = 0; cnt = 0
    def dfs(u):
        nonlocal time, cnt
        time += 1
        dfn[u] = low[u] = time
        stk.append(u); in_stk[u] = True
        for v in g[u]:
            if not dfn[v]:
                dfs(v)
                low[u] = min(low[u], low[v])
            elif in_stk[v]:
                low[u] = min(low[u], dfn[v])
        if low[u] == dfn[u]:
            cnt += 1
            while True:
                x = stk.pop(); in_stk[x] = False
                scc[x] = cnt
                if x == u: break
    for i in range(sz):
        if not dfn[i]:
            dfs(i)
    return scc

n, m = map(int, input().split())
edges = []
for _ in range(m):
    i, a, j, b = map(int, input().split())
    xi = i if a == 1 else i + n      # xi 为 a 的结点
    xj = j if b == 1 else j + n      # xj 为 b 的结点
    xi_neg = i + n if a == 1 else i  # xi 不满足 a
    xj_neg = j + n if b == 1 else j  # xj 不满足 b
    edges.append((xi_neg, xj))  # ¬xi_a -&gt; xj_b
    edges.append((xj_neg, xi))  # ¬xj_b -&gt; xi_a

scc = tarjan(2 * n + 1, edges)
ans = []
for i in range(1, n + 1):
    if scc[i] == scc[i + n]:
        print(&quot;IMPOSSIBLE&quot;)
        sys.exit()
    ans.append(&apos;1&apos; if scc[i] &gt; scc[i + n] else &apos;0&apos;)
print(&quot;POSSIBLE&quot;)
print(&apos; &apos;.join(ans))
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;// 2-SAT+tarjan O(n+m)
#include&amp;#x3C;bits/stdc++.h&gt;
using namespace std;

const int N=2000005;
int n,m;
int hd[N],to[N],ne[N],idx;
int dfn[N],low[N],tim,stk[N],top,scc[N],cnt;

void add(int a,int b){
  to[++idx]=b,ne[idx]=hd[a],hd[a]=idx;
}
void tarjan(int x){
  dfn[x]=low[x]=++tim;
  stk[++top]=x;
  for(int i=hd[x];i;i=ne[i]){
    int y=to[i];
    if(!dfn[y]){
      tarjan(y);
      low[x]=min(low[x],low[y]);
    }
    else if(!scc[y]) low[x]=min(low[x],dfn[y]);
  }
  if(low[x]==dfn[x]){
    ++cnt;
    for(int y=-1;y!=x;) scc[y=stk[top--]]=cnt;
  }
}
int main(){
  scanf(&quot;%d%d&quot;,&amp;#x26;n,&amp;#x26;m);
  for(int i,a,j,b;m--;){
    scanf(&quot;%d%d%d%d&quot;,&amp;#x26;i,&amp;#x26;a,&amp;#x26;j,&amp;#x26;b);
    add(i+!a*n,j+b*n); //xi拆成i和i+n
    add(j+!b*n,i+a*n);
  }
  for(int i=1;i&amp;#x3C;=2*n;i++)if(!dfn[i]) tarjan(i);
  for(int i=1;i&amp;#x3C;=n;i++)if(scc[i]==scc[i+n]){
    puts(&quot;IMPOSSIBLE&quot;);
    return 0;
  }
  puts(&quot;POSSIBLE&quot;);
  for(int i=1;i&amp;#x3C;=n;i++)printf(&quot;%d &quot;,scc[i]&gt;scc[i+n]);
  return 0;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>D24 2-SAT-前缀优化建图</title><link>https://ehnotgod.site/blog/d24-2-sat-%E5%89%8D%E7%BC%80%E4%BC%98%E5%8C%96%E5%BB%BA%E5%9B%BE</link><guid isPermaLink="true">https://ehnotgod.site/blog/d24-2-sat-%E5%89%8D%E7%BC%80%E4%BC%98%E5%8C%96%E5%BB%BA%E5%9B%BE</guid><description>2-SAT 前缀优化建图：每部分恰选一个 + 边至少一端选。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P6378&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;$n$ 个点 $m$ 条边的无向图被分成 $k$ 个部分。每个部分包含一些点。&lt;/p&gt;
&lt;p&gt;请选择一些关键点，使得每个部分&lt;strong&gt;恰&lt;/strong&gt;有一个关键点，且每条边&lt;strong&gt;至少&lt;/strong&gt;有一个端点是关键点。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行三个整数 $n,m,k$。&lt;/p&gt;
&lt;p&gt;接下来 $m$ 行，每行两个整数 $a,b$，表示有一条 $a,b$ 间的边。&lt;/p&gt;
&lt;p&gt;接下来 $k$ 行，每行第一个整数为 $w$，表示这个部分有 $w$ 个点；接下来 $w$ 个整数，为在这个部分中的点的编号。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;若可能选出请输出 &lt;code&gt;TAK&lt;/code&gt;，否则输出 &lt;code&gt;NIE&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;6 5 2
1 2
3 1
1 4
5 2
6 2
3 3 4 2
3 1 6 5
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;TAK
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;说明/提示&lt;/strong&gt;&lt;/p&gt;
&lt;h4&gt;数据规模与约定&lt;/h4&gt;
&lt;p&gt;对于全部的测试点，保证 $1\le k,w\le n\le 10^6$，$\sum w=n$，$1\le a,b\le n$，$0\le m\le 10^6$。&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;images/D24-1.png&quot; alt=&quot;2-SAT 前缀优化建图示意&quot;&gt;&lt;/p&gt;
&lt;p&gt;2-SAT 建模：每个点选/不选拆成两个点。对边 $(a,b)$，要求至少一端选，得到两条蕴含边：不选 $a$ 则选 $b$、不选 $b$ 则选 $a$。&lt;/p&gt;
&lt;p&gt;难点在「每个部分恰选一个」。若两两连边是 $O(w^2)$，需用&lt;strong&gt;前缀优化&lt;/strong&gt;：对某部分的点 $p_1,\dots,p_w$ 引入前缀节点 $pre[i]$（表示前 $i$ 个点至少选一个），连边：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$p_i$ 选 $\rightarrow pre[i]$ 选&lt;/li&gt;
&lt;li&gt;$pre[i-1]$ 选 $\rightarrow pre[i]$ 选（前缀传递）&lt;/li&gt;
&lt;li&gt;$p_i$ 选 $\rightarrow pre[i-1]$ 不选（选 $p_i$ 则前 $i-1$ 个都不能选，保证恰一个）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;总点数 $4n$（选/不选/前缀选/前缀不选），边数 $O(n+m)$。建图后 Tarjan 判 SCC：若某个点选与不选在同一 SCC 则输出 &lt;code&gt;NIE&lt;/code&gt;，否则 &lt;code&gt;TAK&lt;/code&gt;。数据较大（$n\le 10^6$），Python 版仅用于理解，竞赛建议用 C++。&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import sys
sys.setrecursionlimit(10**7)
input = sys.stdin.readline

n, m, k = map(int, input().split())
N = 4 * n + 1
g = [[] for _ in range(N)]

def x0(x): return x            # 选
def x1(x): return x + n        # 不选
def p0(x): return x + 2 * n    # 前缀选
def p1(x): return x + 3 * n    # 前缀不选

def add(a, b):
    g[a].append(b)

for _ in range(m):
    x, y = map(int, input().split())
    add(x1(x), x0(y))  # 不选x 则选y
    add(x1(y), x0(x))  # 不选y 则选x

for _ in range(k):
    wl = list(map(int, input().split()))
    w = wl[0]
    p = None
    for j, x in enumerate(wl[1:], 1):
        add(x0(x), p0(x))
        add(p1(x), x1(x))
        if j != 1:
            add(p0(p), p0(x))
            add(p1(x), p1(p))
            add(p0(p), x1(x))
            add(x0(x), p1(p))
        p = x

# Tarjan 求 SCC
dfn = [0] * N; low = [0] * N; scc = [0] * N
stk = []; inst = [False] * N
tim = 0; cnt = 0

def dfs(u):
    global tim, cnt
    tim += 1
    dfn[u] = low[u] = tim
    stk.append(u); inst[u] = True
    for v in g[u]:
        if not dfn[v]:
            dfs(v)
            low[u] = min(low[u], low[v])
        elif inst[v]:
            low[u] = min(low[u], dfn[v])
    if low[u] == dfn[u]:
        cnt += 1
        while True:
            y = stk.pop(); inst[y] = False
            scc[y] = cnt
            if y == u:
                break

for i in range(1, N):
    if not dfn[i]:
        dfs(i)

for i in range(1, n + 1):
    if scc[x0(i)] == scc[x1(i)]:
        print(&quot;NIE&quot;)
        sys.exit()
print(&quot;TAK&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include&amp;#x3C;bits/stdc++.h&gt;
using namespace std;

#define x0(x) x     //点
#define x1(x) x+n   //反点
#define p0(x) x+2*n //前缀点
#define p1(x) x+3*n //前缀反点
#define N 8000010
int n,m,k,w;
int dfn[N],low[N],scc[N],stk[N],tim,top,cnt;
int head[N],to[N],ne[N],idx;

void add(int a,int b){
  to[++idx]=b;
  ne[idx]=head[a];
  head[a]=idx;
}
void tarjan(int x){
  dfn[x]=low[x]=++tim;
  stk[++top]=x;
  for(int i=head[x];i;i=ne[i]){
    int y=to[i];
    if(!dfn[y]){ //若y尚未访问
      tarjan(y);
      low[x]=min(low[x],low[y]);
    }
    else if(!scc[y]) //若y已访问且未处理
      low[x]=min(low[x],dfn[y]);
  }
  
  if(low[x]==dfn[x]){ //若x是SCC的根
    ++cnt;
    for(int y=-1;y!=x;)
      scc[y=stk[top--]]=cnt;
  }
}
int main(){
  scanf(&quot;%d%d%d&quot;,&amp;#x26;n,&amp;#x26;m,&amp;#x26;k);
  for(int i=1,x,y;i&amp;#x3C;=m;++i){
    scanf(&quot;%d%d&quot;,&amp;#x26;x,&amp;#x26;y);
    add(x1(x),x0(y)); //不选x 则选y
    add(x1(y),x0(x)); //不选y 则选x
  }
  for(int i=1;i&amp;#x3C;=k;++i){ //前缀优化
    scanf(&quot;%d&quot;,&amp;#x26;w);
    for(int j=1,x,p;j&amp;#x3C;=w;++j){
      scanf(&quot;%d&quot;,&amp;#x26;x);
      add(x0(x),p0(x)); //向下连
      add(p1(x),x1(x)); //向下连
      if(j!=1){
        add(p0(p),p0(x)); //向右连
        add(p1(x),p1(p)); //向左连
        add(p0(p),x1(x)); //向右下连
        add(x0(x),p1(p)); //向左下连
      }
      p=x; //记录前一个x
    }
  }
  for(int i=1;i&amp;#x3C;=4*n;++i)if(!dfn[i])tarjan(i);
  for(int i=1;i&amp;#x3C;=n;++i)
    if(scc[i]==scc[i+n]) return puts(&quot;NIE&quot;),0;
  puts(&quot;TAK&quot;);
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>E1 最长上升子序列（二分优化）</title><link>https://ehnotgod.site/blog/e1-%E6%9C%80%E9%95%BF%E4%B8%8A%E5%8D%87%E5%AD%90%E5%BA%8F%E5%88%97%E4%BA%8C%E5%88%86%E4%BC%98%E5%8C%96</link><guid isPermaLink="true">https://ehnotgod.site/blog/e1-%E6%9C%80%E9%95%BF%E4%B8%8A%E5%8D%87%E5%AD%90%E5%BA%8F%E5%88%97%E4%BA%8C%E5%88%86%E4%BC%98%E5%8C%96</guid><description>LIS 最长上升子序列：二分 + 贪心 O(n log n)。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/B3637&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;给出一个由 $$n(n\le 5000)$$ 个不超过 $$10^6$$ 的正整数组成的序列。请输出这个序列的&lt;strong&gt;最长上升子序列&lt;/strong&gt;的长度。&lt;/p&gt;
&lt;p&gt;最长上升子序列是指，从原序列中&lt;strong&gt;按顺序&lt;/strong&gt;取出一些数字排在一起，这些数字是&lt;strong&gt;逐渐增大&lt;/strong&gt;的。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行，一个整数 $$n$$，表示序列长度。&lt;/p&gt;
&lt;p&gt;第二行有 $$n$$ 个整数，表示这个序列。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;一个整数表示答案。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入输出样例 #1&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;6
1 2 4 1 3 4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;4
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;LIS 最长上升子序列：$dp[i]$ 表示长度为 $i$ 的上升子序列的最小末尾值。遍历每个数 $x$，用二分在 $dp$ 中找第一个 $\ge x$ 的位置替换（若 $x$ 大于所有则追加），$dp$ 的长度就是 LIS 长度。严格上升用 $lower_bound$，非严格用 $upper_bound$。复杂度 $O(n\log n)$。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/E1-1.png&quot; alt=&quot;image-20250418140921987&quot;&gt;&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import bisect
n = int(input())
b = [0] * (n + 10)
a = list(map(int, input().split()))
b[0] = int(-2e9)
len = 0
for i in range(n):
    if b[len] &amp;#x3C; a[i]:
        len += 1
        b[len] = a[i]
    else:
        b[bisect.bisect_left(b, a[i], 1, len)] = a[i]
print(len)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include &amp;#x3C;iostream&gt;
#include &amp;#x3C;cstring&gt;
#include &amp;#x3C;algorithm&gt;
using namespace std;

const int N=100010;
int n, a[N];
int len, b[N]; //记录上升子序列

int main(){
  scanf(&quot;%d&quot;, &amp;#x26;n);
  for(int i=0; i&amp;#x3C;n; i++) scanf(&quot;%d&quot;, &amp;#x26;a[i]);

  b[0]=-2e9;                              //哨兵
  for(int i=0; i&amp;#x3C;n; i++)
    if(b[len]&amp;#x3C;a[i]) b[++len]=a[i];        //新数大于队尾数，则插入队尾
    else *lower_bound(b,b+len,a[i])=a[i]; //替换第一个大于等于a[i]的数(贪心)

  printf(&quot;%d\n&quot;, len);
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>E2 最长公共子序列</title><link>https://ehnotgod.site/blog/e2-%E6%9C%80%E9%95%BF%E5%85%AC%E5%85%B1%E5%AD%90%E5%BA%8F%E5%88%97</link><guid isPermaLink="true">https://ehnotgod.site/blog/e2-%E6%9C%80%E9%95%BF%E5%85%AC%E5%85%B1%E5%AD%90%E5%BA%8F%E5%88%97</guid><description>LCS 最长公共子序列：二维 DP。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;给出一个长度为$$n(n\le 5000)$$ 的字符串序列。请输出这个序列的&lt;strong&gt;最长公共子序列&lt;/strong&gt;及其长度。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输入两个字符串（代码里忽略了这个）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输出分为两行，&lt;/p&gt;
&lt;p&gt;第一行输出一个整数表示最长公共子序列的长度，&lt;/p&gt;
&lt;p&gt;第二行输出这个最长公共子序列。&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;最长公共子序列：$f[i][j]$ 表示 $a$ 前 $i$ 个字符与 $b$ 前 $j$ 个字符的 LCS 长度。若 $a[i]==b[j]$，$f[i][j]=f[i-1][j-1]+1$；否则 $f[i][j]=\max(f[i-1][j],f[i][j-1])$。用 $p$ 数组记录转移方向，回溯即可还原出最长公共子序列。复杂度 $O(n^2)$。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/E2-1.png&quot; alt=&quot;image-20250418141328421&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;p数组记录转移方向，用来还原子序列。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/E2-2.png&quot; alt=&quot;image-20250418141439935&quot;&gt;&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;a = &quot;ADABBC&quot;
b = &quot;DBPCA&quot;
f = []
p = []
m = 0
n = 0

def LCS():
    global m, n, f, p
    m = len(a)
    n = len(b)
    f = [[0] * (n + 1) for _ in range(m + 1)]
    p = [[0] * (n + 1) for _ in range(m + 1)]
    for i in range(1, m + 1):
        for j in range(1, n + 1):
            if a[i-1] == b[j-1]:
                f[i][j] = f[i-1][j-1] + 1
                p[i][j] = 1  # 左上方
            else:
                if f[i][j-1] &gt; f[i-1][j]:
                    f[i][j] = f[i][j-1]
                    p[i][j] = 2  # 左边
                else:
                    f[i][j] = f[i-1][j]
                    p[i][j] = 3  # 上边
    print(f[m][n])

def getLCS():
    global m, n, p, a
    i = m
    j = n
    k = f[m][n]
    s = [&apos;&apos;] * k
    while i &gt; 0 and j &gt; 0:
        if p[i][j] == 1:
            s[k-1] = a[i-1]
            i -= 1
            j -= 1
            k -= 1
        elif p[i][j] == 2:
            j -= 1
        else:
            i -= 1
    print(&apos;&apos;.join(s))

if __name__ == &quot;__main__&quot;:
    LCS()
    getLCS()
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include &amp;#x3C;cstring&gt;
#include &amp;#x3C;iostream&gt;

char a[200] = &quot;ADABBC&quot;;
char b[200] = &quot;DBPCA&quot;;
int f[200][200];
int p[200][200];
int m, n;
void LCS() {
    int i, j;
    m = strlen(a);
    n = strlen(b);
    for (i = 1; i &amp;#x3C;= m; i++) {
        for (j = 1; j &amp;#x3C;= n; j++) {
            if (a[i-1] == b[j-1]) {
                f[i][j] = f[i-1][j-1] + 1;
                p[i][j] = 1; // 左上方
            } else if (f[i][j-1] &gt; f[i-1][j]) {
                f[i][j] = f[i][j-1];
                p[i][j] = 2; // 左边
            } else {
                f[i][j] = f[i-1][j];
                p[i][j] = 3; // 上边
            }
        }
    }
    cout &amp;#x3C;&amp;#x3C; f[m][n] &amp;#x3C;&amp;#x3C; &apos;\n&apos;;
}

void getLCS() {
    int i, j, k;
    char s[200];
    i = m;
    j = n;
    k = f[m][n];
    while (i &gt; 0 &amp;#x26;&amp;#x26; j &gt; 0) {
        if (p[i][j] == 1) {
            s[k-1] = a[i-1];
            i--;
            j--;
            k--;
        } else if (p[i][j] == 2) {
            j--;
        } else {
            i--;
        }
    }
    for (i = 0; i &amp;#x3C; f[m][n]; i++) {
        cout &amp;#x3C;&amp;#x3C; s[i];
    }
}
int main() {
    LCS();
    getLCS();
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>E3 最长公共子串</title><link>https://ehnotgod.site/blog/e3-%E6%9C%80%E9%95%BF%E5%85%AC%E5%85%B1%E5%AD%90%E4%B8%B2</link><guid isPermaLink="true">https://ehnotgod.site/blog/e3-%E6%9C%80%E9%95%BF%E5%85%AC%E5%85%B1%E5%AD%90%E4%B8%B2</guid><description>最长公共子串：DP / 后缀自动机。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;给出一个长度为$$n(n\le 5000)$$ 的字符串序列。请输出这个序列的&lt;strong&gt;最长公共子串&lt;/strong&gt;及其长度。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输入两个字符串（代码里忽略了这个）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输出一个整数表示最长公共子串的长度，&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;最长公共子串：$f[i][j]$ 表示以 $a[i]$、$b[j]$ 结尾的最长公共子串长度。若 $a[i]==b[j]$，$f[i][j]=f[i-1][j-1]+1$；否则 $f[i][j]=0$（子串要求连续，一旦不等就断开）。答案取所有 $f[i][j]$ 的最大值。复杂度 $O(n^2)$。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/E3-1.png&quot; alt=&quot;image-20250418142032244&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/E3-2.png&quot; alt=&quot;image-20250418142022435&quot;&gt;&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;a = &quot;BCCABCCB&quot;
b = &quot;AACCAB&quot;
m = len(a)
n = len(b)
f = [[0] * (n + 1) for _ in range(m + 1)]
ans = 0
for i in range(1, m + 1):
    for j in range(1, n + 1):
        if a[i - 1] == b[j - 1]:
            f[i][j] = f[i - 1][j - 1] + 1
        else:
            f[i][j] = 0
        ans = max(ans, f[i][j])
print(ans)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include&amp;#x3C;iostream&gt;
#include&amp;#x3C;cstring&gt;
using namespace std;

char a[200]=&quot;BCCABCCB&quot;;
char b[200]=&quot;AACCAB&quot;;
int f[201][201];

int main(){
  int ans=0;
  for(int i=1; i&amp;#x3C;=strlen(a); i++){
    for(int j=1; j&amp;#x3C;=strlen(b); j++){
      if(a[i-1]==b[j-1]) f[i][j]=f[i-1][j-1]+1;
      else f[i][j]=0;
      ans=max(ans,f[i][j]);
    }
  }
  printf(&quot;ans=%d\n&quot;,ans);
  return 0;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>E4 01背包</title><link>https://ehnotgod.site/blog/e4-01%E8%83%8C%E5%8C%85</link><guid isPermaLink="true">https://ehnotgod.site/blog/e4-01%E8%83%8C%E5%8C%85</guid><description>01 背包：每件物品选或不选，滚动数组优化。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P1048&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;有 $$N$$ 件物品和一个容量为 $$M$$ 的背包。第 $$i$$ 件物品的重量是 $$W_i$$，价值是 $$D_i$$。求解将哪些物品装入背包可使这些物品的重量总和不超过背包容量，且价值总和最大。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行：物品个数 $$N$$ 和背包大小 $$M$$。&lt;/p&gt;
&lt;p&gt;第二行至第 $$N+1$$ 行：第 $$i$$ 个物品的重量 $$W_i$$ 和价值 $$D_i$$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输出一行最大价值。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入输出样例 #1&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;4 6
1 4
2 6
3 12
2 7
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;23
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;images/E4-1.png&quot; alt=&quot;image-20250418142142298&quot;&gt;&lt;/p&gt;
&lt;p&gt;01 背包：$f[j]$ 表示容量 $j$ 能装下的最大价值。对每个物品，容量&lt;strong&gt;从大到小&lt;/strong&gt;遍历（$j$ 逆序），$f[j]=\max(f[j], f[j-w]+v)$，逆序保证每个物品最多取一次（避免重复使用）。复杂度 $O(nm)$，空间可压缩为一维。&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;N = 3410
M = 13000
n, m = map(int, input().split())
v = [0] * N
w = [0] * N
f = [0] * M
for i in range(n):
    v[i + 1], w[i + 1] = map(int, input().split())
for i in range(1, n + 1):
    for j in range(m, 0, -1):
        if j &gt;= v[i]:
            f[j] = max(f[j], f[j - v[i]] + w[i])
        else:
            break
print(f[m])
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;// 逆序枚举，优化空间#include &amp;#x3C;iostream&gt;
#include &amp;#x3C;cstring&gt;
#include &amp;#x3C;algorithm&gt;
using namespace std;

const int N=3410,M=13000;
int n, m;
int v[N],w[N],f[M];

int main(){
  scanf(&quot;%d%d&quot;,&amp;#x26;n,&amp;#x26;m);
  for(int i=1; i&amp;#x3C;=n; i++)
    scanf(&quot;%d%d&quot;,&amp;#x26;v[i],&amp;#x26;w[i]);  //费用，价值

  for(int i=1; i&amp;#x3C;=n; i++)       //枚举物品
    for(int j=m; j&gt;=v[i]; j--)  //枚举体积
      f[j]=max(f[j],f[j-v[i]]+w[i]);

  printf(&quot;%d\n&quot;,f[m]);
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>E5 完全背包</title><link>https://ehnotgod.site/blog/e5-%E5%AE%8C%E5%85%A8%E8%83%8C%E5%8C%85</link><guid isPermaLink="true">https://ehnotgod.site/blog/e5-%E5%AE%8C%E5%85%A8%E8%83%8C%E5%8C%85</guid><description>完全背包：物品可无限选取，正序递推。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P1616&lt;/p&gt;
&lt;p&gt;题目不码了，跟上题一样，只不过是把只能拿一件物品改为可以拿任意件。&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;完全背包与 01 背包的区别是每种物品可以取任意多件。用一维数组 $f[j]$ 表示容量 $j$ 的最大价值，容量&lt;strong&gt;正序遍历&lt;/strong&gt;（$j$ 从小到大），这样 $f[j-w]$ 可能已经是本物品更新过的值，从而支持无限取用。$f[j]=\max(f[j], f[j-w]+v)$，复杂度 $O(nm)$。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/E5-1.png&quot; alt=&quot;image-20250418142653554&quot;&gt;&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;N = 1010
n, m = map(int, input().split())
v = [0] * N
w = [0] * N
f = [0] * N
for i in range(n):
    v[i + 1], w[i + 1] = map(int, input().split())
for i in range(1, n + 1):
    for j in range(v[i], m + 1):
        f[j] = max(f[j], f[j - v[i]] + w[i])
print(f[m])
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;// 优化决策+优化空间
#include &amp;#x3C;iostream&gt;
#include &amp;#x3C;cstring&gt;
#include &amp;#x3C;algorithm&gt;
using namespace std;

const int N=1010;
int n, m;
int v[N],w[N],f[N];

int main(){
  scanf(&quot;%d%d&quot;,&amp;#x26;n,&amp;#x26;m);
  for(int i=1; i&amp;#x3C;=n; i++)
    scanf(&quot;%d%d&quot;,&amp;#x26;v[i],&amp;#x26;w[i]);  //费用，价值

  for(int i=1; i&amp;#x3C;=n; i++)       //枚举物品
    for(int j=v[i]; j&amp;#x3C;=m; j++)  //枚举体积
      f[j]=max(f[j],f[j-v[i]]+w[i]);

  printf(&quot;%d\n&quot;,f[m]);
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>E6 多重背包</title><link>https://ehnotgod.site/blog/e6-%E5%A4%9A%E9%87%8D%E8%83%8C%E5%8C%85</link><guid isPermaLink="true">https://ehnotgod.site/blog/e6-%E5%A4%9A%E9%87%8D%E8%83%8C%E5%8C%85</guid><description>多重背包：物品有数量限制（二进制拆分）。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P1776&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;小 FF 对洞穴里的宝物进行了整理，他发现每样宝物都有一件或者多件。他粗略估算了下每样宝物的价值，之后开始了宝物筛选工作：小 FF 有一个最大载重为 $$W$$ 的采集车，洞穴里总共有 $$n$$ 种宝物，每种宝物的价值为 $$v_i$$，重量为 $$w_i$$，每种宝物有 $$m_i$$ 件。小 FF 希望在采集车不超载的前提下，选择一些宝物装进采集车，使得它们的价值和最大。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行为两个整数 $$n$$ 和 $$W$$，分别表示宝物种数和采集车的最大载重。&lt;/p&gt;
&lt;p&gt;接下来 $$n$$ 行每行三个整数 $$v_i,w_i,m_i$$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输出仅一个整数，表示在采集车不超载的情况下收集的宝物的最大价值。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入输出样例 #1&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;4 20
3 9 3
5 9 1
9 4 2
8 1 3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;47
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于 $$100%$$ 的数据，$$n\leq \sum m_i \leq 10^5$$，$$0\le W\leq 4\times 10^4$$，$$1\leq n\le 100$$。&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;images/E6-1.png&quot; alt=&quot;image-20250418142914118&quot;&gt;&lt;/p&gt;
&lt;p&gt;多重背包每种物品有 $m_i$ 件。用&lt;strong&gt;二进制拆分&lt;/strong&gt;：把 $m_i$ 拆成 $1,2,4,\dots,2^k$ 加余数等 $O(\log m_i)$ 组，每组当作一个“01 背包物品”（重量/价值乘对应系数），从而转化为 01 背包。复杂度 $O(nm\log m)$。&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;n, m = map(int, input().split())

v = [0] * 100005
w = [0] * 100005
f = [0] * 100005

cnt = 0
for _ in range(n):
    b, a, s = map(int, input().split())
    j = 1
    while j &amp;#x3C;= s:
        cnt += 1
        v[cnt] = j * a
        w[cnt] = j * b
        s -= j
        j &amp;#x3C;&amp;#x3C;= 1
    if s &gt; 0:
        cnt += 1
        v[cnt] = s * a
        w[cnt] = s * b

for i in range(1, cnt + 1):
    for j in range(m, v[i] - 1, -1):
        f[j] = max(f[j], f[j - v[i]] + w[i])

print(f[m])
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;// 二进制分组优化
#include&amp;#x3C;iostream&gt;
using namespace std;

const int N=100005;
int n,m,a,b,s;
int v[N],w[N];
int f[N];

int main(){
  cin&gt;&gt;n&gt;&gt;m;

  int cnt=0;
  for(int i=1;i&amp;#x3C;=n;i++){
    cin&gt;&gt;b&gt;&gt;a&gt;&gt;s;
    for(int j=1;j&amp;#x3C;=s;j&amp;#x3C;&amp;#x3C;=1){
      v[++cnt]=j*a; w[cnt]=j*b;
      s-=j;
    }
    if(s) v[++cnt]=s*a, w[cnt]=s*b;
  }

  for(int i=1;i&amp;#x3C;=cnt;i++)
    for(int j=m;j&gt;=v[i];j--)
      f[j]=max(f[j],f[j-v[i]]+w[i]);
  cout&amp;#x3C;&amp;#x3C;f[m];
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>E7 滑动窗口</title><link>https://ehnotgod.site/blog/e7-%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3</link><guid isPermaLink="true">https://ehnotgod.site/blog/e7-%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3</guid><description>滑动窗口：双端队列（单调队列）维护区间最值。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P1886&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;有一个长为 $n$ 的序列 $a$，以及一个大小为 $k$ 的窗口。现在这个窗口从左边开始向右滑动，每次滑动一个单位，求出每次滑动后窗口中的最小值和最大值。&lt;/p&gt;
&lt;p&gt;例如，对于序列 $[1,3,-1,-3,5,3,6,7]$ 以及 $k = 3$，有如下过程：&lt;/p&gt;
&lt;p&gt;$$\def\arraystretch{1.2}
\begin{array}{|c|c|c|}\hline
\textsf{窗口位置} &amp;#x26; \textsf{最小值} &amp;#x26; \textsf{最大值} \ \hline
\verb![1   3  -1] -3   5   3   6   7 ! &amp;#x26; -1 &amp;#x26; 3 \ \hline
\verb! 1  [3  -1  -3]  5   3   6   7 ! &amp;#x26; -3 &amp;#x26; 3 \ \hline
\verb! 1   3 [-1  -3   5]  3   6   7 ! &amp;#x26; -3 &amp;#x26; 5 \ \hline
\verb! 1   3  -1 [-3   5   3]  6   7 ! &amp;#x26; -3 &amp;#x26; 5 \ \hline
\verb! 1   3  -1  -3  [5   3   6]  7 ! &amp;#x26; 3 &amp;#x26; 6 \ \hline
\verb! 1   3  -1  -3   5  [3   6   7]! &amp;#x26; 3 &amp;#x26; 7 \ \hline
\end{array}
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输入一共有两行，第一行有两个正整数 $n,k$；&lt;br&gt;
第二行有 $n$ 个整数，表示序列 $a$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输出共两行，第一行为每次窗口滑动的最小值；&lt;br&gt;
第二行为每次窗口滑动的最大值。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;8 3
1 3 -1 -3 5 3 6 7
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-1 -3 -3 -3 3 3
3 3 5 5 6 7
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;单调队列维护窗口内最值：用双端队列存下标，保证队列内元素对应值单调。每次新元素入队前，把队尾比它小（求最大值）/大（求最小值）的元素弹出；同时把滑出窗口的下标从队头弹出。队头即当前窗口最值。复杂度 $O(n)$。&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import sys
from collections import deque

input = sys.stdin.readline

n, k = map(int, input().split())
a = [0] * (n + 1)  # Using 1-based indexing to match the C++ code
a[1:] = list(map(int, input().split()))

# Maintain window minimum
q = deque()
for i in range(1, n + 1):
    while len(q) &gt; 0 and a[q[-1]] &gt;= a[i]:
        q.pop()
    q.append(i)
    while q[0] &amp;#x3C; i - k + 1:
        q.popleft()
    if i &gt;= k:
        print(a[q[0]], end=&apos; &apos;)
print()

# Maintain window maximum
q = deque()
for i in range(1, n + 1):
    while len(q) &gt; 0 and a[q[-1]] &amp;#x3C;= a[i]:
        q.pop()
    q.append(i)
    while q[0] &amp;#x3C; i - k + 1:
        q.popleft()
    if i &gt;= k:
        print(a[q[0]], end=&apos; &apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include &amp;#x3C;iostream&gt;
#include &amp;#x3C;deque&gt;
using namespace std;

const int N=1000010;
int a[N];
deque&amp;#x3C;int&gt; q;

int main(){
  int n, k; scanf(&quot;%d%d&quot;, &amp;#x26;n, &amp;#x26;k);
  for(int i=1; i&amp;#x3C;=n; i++) scanf(&quot;%d&quot;, &amp;#x26;a[i]);

  // 维护窗口最小值
  q.clear();                              //清空队列
  for(int i=1; i&amp;#x3C;=n; i++){                //枚举序列
    while(!q.empty() &amp;#x26;&amp;#x26; a[q.back()]&gt;=a[i]) q.pop_back(); //队尾出队(队列不空且新元素更优)
    q.push_back(i);                       //队尾入队(存储下标 方便判断队头出队)
    while(q.front()&amp;#x3C;i-k+1) q.pop_front(); //队头出队(队头元素滑出窗口)
    if(i&gt;=k) printf(&quot;%d &quot;,a[q.front()]);  //使用最值
  }
  puts(&quot;&quot;);

  // 维护窗口最大值
  q.clear();
  for(int i=1; i&amp;#x3C;=n; i++){
    while(!q.empty() &amp;#x26;&amp;#x26; a[q.back()]&amp;#x3C;=a[i]) q.pop_back();
    q.push_back(i);
    while(q.front()&amp;#x3C;i-k+1) q.pop_front();
    if(i&gt;=k) printf(&quot;%d &quot;,a[q.front()]);
  }
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>E8 混合背包</title><link>https://ehnotgod.site/blog/e8-%E6%B7%B7%E5%90%88%E8%83%8C%E5%8C%85</link><guid isPermaLink="true">https://ehnotgod.site/blog/e8-%E6%B7%B7%E5%90%88%E8%83%8C%E5%8C%85</guid><description>混合背包：01 / 完全 / 多重背包组合。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/T627139&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;有 $N$ 种物品和一个容量是 $V$ 的背包。&lt;/p&gt;
&lt;p&gt;物品一共有三类：&lt;/p&gt;
&lt;p&gt;第一类物品只能用 $1$ 次（$01$背包）；&lt;/p&gt;
&lt;p&gt;第二类物品可以用无限次（完全背包）；&lt;/p&gt;
&lt;p&gt;第三类物品最多只能用 $s_i$ 次（多重背包）；&lt;/p&gt;
&lt;p&gt;每种体积是 $v_i$，价值是 $w_i$。&lt;/p&gt;
&lt;p&gt;求解将哪些物品装入背包，可使物品体积总和不超过背包容量，且价值总和最大。&lt;/p&gt;
&lt;p&gt;输出最大价值。&lt;/p&gt;
&lt;p&gt;不允许复制多重背包的代码。重新写&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行两个整数，$N，V$，用空格隔开，分别表示物品种数和背包容积。&lt;/p&gt;
&lt;p&gt;接下来有 $N$ 行，每行三个整数 $v_i,w_i,s_i$，用空格隔开，分别表示第 $i$ 种物品的体积、价值和数量。&lt;/p&gt;
&lt;p&gt;$s_i=−1$ 表示第 $i$ 种物品只能用$1$次；$s_i=0$ 表示第 $i$ 种物品可以用无限次；$si\gt 0$ 表示第 i 种物品可以使用 $s_i$ 次。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输出一个整数，表示最大价值。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;4 5
1 2 -1
2 4 1
3 4 0
4 5 2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;8
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;混合背包包含 01 / 完全 / 多重背包：对每件物品按类型处理——01 背包容量逆序、完全背包正序、多重背包用二进制拆分转 01 背包。本题中 $s=-1$ 表示只能取 1 件（01），$s=0$ 表示无限件（完全），$s&gt;0$ 表示有 $s$ 件（多重）。复杂度 $O(nm\log s)$。&lt;/p&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include &amp;#x3C;iostream&gt;
using namespace std;

int f[1010];

int main(){
  int n, m, v, w, s;
  scanf(&quot;%d %d&quot;, &amp;#x26;n, &amp;#x26;m);
  for(int i=1; i&amp;#x3C;=n; i++){  //枚举物品种类
    scanf(&quot;%d%d%d&quot;,&amp;#x26;v,&amp;#x26;w,&amp;#x26;s);
    if(s!=0){               //01背包或多重背包
      if(s==-1) s=1;                
      int num=min(s,m/v);
      for(int k=1; num&gt;0; k&amp;#x3C;&amp;#x3C;=1){
        if(k&gt;num) k=num;
        num-=k;
        for(int j=m; j&gt;=v*k; j--)
          f[j]=max(f[j],f[j-v*k]+w*k);
      }
    }
    else{                   //完全背包
      for(int j=v; j&amp;#x3C;=m; j++)
        f[j]=max(f[j],f[j-v]+w);
    }
  }
  printf(&quot;%d\n&quot;, f[m]);
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>E9 二维背包</title><link>https://ehnotgod.site/blog/e9-%E4%BA%8C%E7%BB%B4%E8%83%8C%E5%8C%85</link><guid isPermaLink="true">https://ehnotgod.site/blog/e9-%E4%BA%8C%E7%BB%B4%E8%83%8C%E5%8C%85</guid><description>二维费用背包：体积与重量两个限制。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/T426845&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;有 N
件物品和一个容量是 V
的背包，背包能承受的最大重量是 M
。&lt;/p&gt;
&lt;p&gt;每件物品只能用一次。体积是 vi
，重量是 mi
，价值是 wi
。&lt;/p&gt;
&lt;p&gt;求解将哪些物品装入背包，可使物品总体积不超过背包容量，总重量不超过背包可承受的最大重量，且价值总和最大。
输出最大价值。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行三个整数，N,V,M
，用空格隔开，分别表示物品件数、背包容积和背包可承受的最大重量。&lt;/p&gt;
&lt;p&gt;接下来有 N
行，每行三个整数 vi,mi,wi
，用空格隔开，分别表示第 i
件物品的体积、重量和价值。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输出一个整数，表示最大价值。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;4 5 6
1 2 3
2 4 4
3 4 5
4 5 6
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;8
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;数据范围&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;0&amp;#x3C;N≤1000&lt;/p&gt;
&lt;p&gt;0&amp;#x3C;V,M≤100&lt;/p&gt;
&lt;p&gt;0&amp;#x3C;vi,mi≤100&lt;/p&gt;
&lt;p&gt;0&amp;#x3C;wi≤1000&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;二维费用背包：每个物品有两个代价（体积 $v$ 和重量 $w$）。状态 $f[j][k]$ 表示体积 $\le j$ 且重量 $\le k$ 的最大价值。转移 $f[j][k]=\max(f[j][k], f[j-v][k-w]+val)$，两个维度都逆序（01 背包）。复杂度 $O(n\cdot V\cdot W)$。&lt;/p&gt;
&lt;h3&gt;py代码实现&lt;/h3&gt;
&lt;p&gt;略&lt;/p&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;//二维费用 01背包
#include &amp;#x3C;iostream&gt;
using namespace std;

int f[110][110];
// f[j,k]:前i个物品，体积≤j，重量≤k 的最大价值

int main(){
  int n, V, W;    //物品 容量 承重
  int v, w, val;  //体积 重量 价值
  cin&gt;&gt;n&gt;&gt;V&gt;&gt;W;
  for(int i=1; i&amp;#x3C;=n; i++){  //物品 
    cin&gt;&gt;v&gt;&gt;w&gt;&gt;val;
    for(int j=V; j&gt;=v; j--) //体积
    for(int k=W; k&gt;=w; k--) //重量
      f[j][k]=max(f[j][k],f[j-v][k-w]+val);
  }
  cout&amp;#x3C;&amp;#x3C;f[V][W];
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>E10 分组背包</title><link>https://ehnotgod.site/blog/e10-%E5%88%86%E7%BB%84%E8%83%8C%E5%8C%85</link><guid isPermaLink="true">https://ehnotgod.site/blog/e10-%E5%88%86%E7%BB%84%E8%83%8C%E5%8C%85</guid><description>分组背包：每组最多选一个。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P1757&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;自 $01$ 背包问世之后，小 A 对此深感兴趣。一天，小 A 去远游，却发现他的背包不同于 $01$ 背包，他的物品大致可分为 $k$ 组，每组中的物品相互冲突，现在，他想知道最大的利用价值是多少。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;两个数 $m,n$，表示一共有 $n$ 件物品，背包能承受的最大重量为 $m$。&lt;/p&gt;
&lt;p&gt;接下来 $n$ 行，每行 $3$ 个数 $a_i,b_i,c_i$，表示物品的重量，利用价值，所属组数。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;一个数，最大的利用价值。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;45 3
10 10 1
10 5 1
50 400 2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;10
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;$0 \leq m \leq 1000$，$1 \leq n \leq 1000$，$1\leq k\leq 100$，$a_i, b_i, c_i$ 在 &lt;code&gt;int&lt;/code&gt; 范围内。&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;分组背包：每组内最多选一个物品。状态 $f[i][j]$ 表示前 $i$ 组、容量 $j$ 的最大价值。对每组枚举选组内哪个物品（或不选）：$f[i][j]=\max(f[i-1][j],\ \max_k f[i-1][j-v[k]]+w[k])$。可压缩为一维，容量逆序。复杂度 $O(n\cdot V\cdot s)$。&lt;/p&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;// 分组背包 朴素算法
#include&amp;#x3C;iostream&gt;
#include&amp;#x3C;cstring&gt;
using namespace std;

const int N=110;
int v[N][N],w[N][N],s[N];
// v[i,j]:第i组第j个物品的体积 s[i]:第i组物品的个数
int f[N][N];
// f[i,j]:前i组物品，能放入容量为j的背包的最大值

int main(){    
  int n,V; cin&gt;&gt;n&gt;&gt;V;
  for(int i=1;i&amp;#x3C;=n;i++){
    cin&gt;&gt;s[i];
    for(int j=1;j&amp;#x3C;=s[i];j++) cin&gt;&gt;v[i][j]&gt;&gt;w[i][j];
  }
  
  for(int i=1;i&amp;#x3C;=n;i++)     //物品组
  for(int j=1;j&amp;#x3C;=V;j++)     //体积
  for(int k=0;k&amp;#x3C;=s[i];k++)  //同组内的物品只能选一个
    if(j&gt;=v[i][k]) f[i][j]=max(f[i][j],f[i-1][j-v[i][k]]+w[i][k]);                 

  cout&amp;#x3C;&amp;#x3C;f[n][V];
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>E11 树形DP</title><link>https://ehnotgod.site/blog/e11-%E6%A0%91%E5%BD%A2dp</link><guid isPermaLink="true">https://ehnotgod.site/blog/e11-%E6%A0%91%E5%BD%A2dp</guid><description>树形 DP：以子树为状态的树上动态规划。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P1352&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;某大学有 $n$ 个职员，编号为 $1\ldots n$。&lt;/p&gt;
&lt;p&gt;他们之间有从属关系，也就是说他们的关系就像一棵以校长为根的树，父结点就是子结点的直接上司。&lt;/p&gt;
&lt;p&gt;现在有个周年庆宴会，宴会每邀请来一个职员都会增加一定的快乐指数 $r_i$，但是呢，如果某个职员的直接上司来参加舞会了，那么这个职员就无论如何也不肯来参加舞会了。&lt;/p&gt;
&lt;p&gt;所以，请你编程计算，邀请哪些职员可以使快乐指数最大，求最大的快乐指数。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输入的第一行是一个整数 $n$。&lt;/p&gt;
&lt;p&gt;第 $2$ 到第 $(n + 1)$ 行，每行一个整数，第 $(i+1)$ 行的整数表示 $i$ 号职员的快乐指数 $r_i$。&lt;/p&gt;
&lt;p&gt;第 $(n + 2)$ 到第 $2n$ 行，每行输入一对整数 $l, k$，代表 $k$ 是 $l$ 的直接上司。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输出一行一个整数代表最大的快乐指数。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;7
1
1
1
1
1
1
1
1 3
2 3
6 4
7 4
4 5
3 5

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;5

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于 $100%$ 的数据，保证 $1\leq n \leq 6 \times 10^3$，$-128 \leq r_i\leq 127$，$1 \leq l, k \leq n$，且给出的关系一定是一棵树。&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;树形 DP：以子树为状态做树上 DP。$f[u][0/1]$ 分别表示 $u$ 不选 / 选时子树的最大价值：若 $u$ 不选，儿子可任选（取 $\max$）；若 $u$ 选，儿子都不能选。先 DFS 自底向上合并子树的答案。本题为“没有上司的舞会”：上司选了下属不能选。复杂度 $O(n)$。&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import sys
sys.setrecursionlimit(int(1e7))
N = 6010
head = [0] * N
to = [0] * N
ne = [0] * N
idx = 0
def add(a, b):
    global idx
    idx += 1
    to[idx] = b; ne[idx] = head[a]; head[a] = idx
w = [0] * N
fa = [0] * N
f = [[0 for i in range(2)] for j in range(N)]
def dfs(u):
    f[u][1] = w[u]
    i = head[u]
    while i != 0:
        v = to[i]
        dfs(v)
        f[u][0] += max(f[v][0], f[v][1])
        f[u][1] += f[v][0]
        i = ne[i]
n = int(input())
for i in range(1, n + 1):
    w[i] = int(input())
for i in range(n - 1):
    a, b = map(int, input().split())
    add(b, a)
    fa[a] = True
root = 1
while fa[root]:
    root += 1
dfs(root)
print(max(f[root][0], f[root][1]))
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include &amp;#x3C;iostream&gt;
#include &amp;#x3C;cstring&gt;
#include &amp;#x3C;algorithm&gt;
using namespace std;

const int N=6010;
int head[N],to[N],ne[N],idx;
void add(int a,int b){
  to[++idx]=b,ne[idx]=head[a],head[a]=idx;
}
int n,w[N],fa[N];
int f[N][2]; //0不选,1选

void dfs(int u){
  f[u][1]=w[u];
  for(int i=head[u];i;i=ne[i]){
    int v=to[i];
    dfs(v);
    f[u][0]+=max(f[v][0],f[v][1]);
    f[u][1]+=f[v][0];
  }
}
int main(){
  cin&gt;&gt;n;
  for(int i=1;i&amp;#x3C;=n;i++) cin&gt;&gt;w[i];
  for(int i=0,a,b;i&amp;#x3C;n-1;i++){
    cin&gt;&gt;a&gt;&gt;b;
    add(b,a);
    fa[a]=true;
  }
  int root=1;
  while(fa[root]) root++;
  dfs(root);
  cout&amp;#x3C;&amp;#x3C;max(f[root][0],f[root][1]);
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>E12 树上背包</title><link>https://ehnotgod.site/blog/e12-%E6%A0%91%E4%B8%8A%E8%83%8C%E5%8C%85</link><guid isPermaLink="true">https://ehnotgod.site/blog/e12-%E6%A0%91%E4%B8%8A%E8%83%8C%E5%8C%85</guid><description>树上背包：在树上做依赖背包（树形 DP）。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P2014&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在大学里每个学生，为了达到一定的学分，必须从很多课程里选择一些课程来学习，在课程里有些课程必须在某些课程之前学习，如高等数学总是在其它课程之前学习。现在有 $N$ 门功课，每门课有若干学分，分别记作 $s_1,s_2,\cdots,s_N$，每门课有一门或没有直接先修课（若课程 $a$ 是课程 $b$ 的先修课即只有学完了课程 $a$，才能学习课程 $b$）。一个学生要从这些课程里选择 $M$ 门课程学习，问他能获得的最大学分是多少？&lt;/p&gt;
&lt;p&gt;题目保证课程安排无冲突。（即不会有 $a$ 是 $b$ 的先修课，$b$ 也是 $a$ 的先修课这类情况存在。）&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行有两个整数 $N$，$M$ 用空格隔开 $(1 \leq N \leq 300$ , $1 \leq M \leq 300)$。&lt;/p&gt;
&lt;p&gt;接下来的 $N$ 行，第 $i+1$ 行包含两个整数 $k_i$ 和 $s_i$，$k_i$ 表示第 $i$ 门课的直接先修课，$s_i$ 表示第 $i$ 门课的学分。若 $k_i=0$ 表示没有直接先修课 $(0 \leq {k_i} \leq N$,$1 \leq {s_i} \leq 20)$。&lt;/p&gt;
&lt;p&gt;数据保证至少存在一个 $k_i=0$，即至少一门课无先修课。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;只有一行，选 $M$ 门课程的最大学分。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;7 4
2 2
0 1
0 4
2 1
7 1
7 6
2 2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;13
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;树上背包：在树上做依赖背包（如选课，选子课程必须先选父课程）。$f[u][j]$ 表示 $u$ 子树内选 $j$ 门课（含 $u$）的最大价值。DFS 处理完儿子后，用子树大小约束做背包合并：$f[u][j]=\max(f[u][j], f[u][j-k]+f[v][k])$。复杂度 $O(n^2)$。&lt;/p&gt;
&lt;h3&gt;py代码实现&lt;/h3&gt;
&lt;p&gt;略&lt;/p&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;// 树上背包 O(n^2)
#include &amp;#x3C;iostream&gt;
#include &amp;#x3C;cstring&gt;
#include &amp;#x3C;algorithm&gt;
#include &amp;#x3C;vector&gt;
using namespace std;

const int N=305;
vector&amp;#x3C;int&gt; e[N];
int n,m,w[N],f[N][N],siz[N];

void dfs(int u){
  f[u][1]=w[u];siz[u]=1;
  for(int v:e[u]){
    dfs(v);
    siz[u]+=siz[v];
    for(int j=min(m+1,siz[u]);j;j--) //课程
      for(int k=0;k&amp;#x3C;=min(j-1,siz[v]);k++) //决策      
        f[u][j]=max(f[u][j],f[u][j-k]+f[v][k]);
  }
}
int main(){
  scanf(&quot;%d%d&quot;,&amp;#x26;n,&amp;#x26;m);
  for(int i=1,k; i&amp;#x3C;=n; i++){
    scanf(&quot;%d%d&quot;,&amp;#x26;k,&amp;#x26;w[i]);
    e[k].push_back(i);
  }
  dfs(0); //虚拟根节点0
  printf(&quot;%d&quot;,f[0][m+1]);
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>E13 背包方案数</title><link>https://ehnotgod.site/blog/e13-%E8%83%8C%E5%8C%85%E6%96%B9%E6%A1%88%E6%95%B0</link><guid isPermaLink="true">https://ehnotgod.site/blog/e13-%E8%83%8C%E5%8C%85%E6%96%B9%E6%A1%88%E6%95%B0</guid><description>背包方案数：求达到容量限制的方案数。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/U224067&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;有 $N$ 件物品和一个容量是 $V$ 的背包。每件物品只能使用一次。&lt;/p&gt;
&lt;p&gt;第 $i$ 件物品的体积是 $v_i$，价值是 $w_i$。&lt;/p&gt;
&lt;p&gt;求解将哪些物品装入背包，可使这些物品的总体积不超过背包容量，且总价值最大。&lt;/p&gt;
&lt;p&gt;输出 最优选法的方案数。注意答案可能很大，请输出答案模 $10^9+7$ 的结果。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行两个整数，$N$，$V$，用空格隔开，分别表示物品数量和背包容积。&lt;/p&gt;
&lt;p&gt;接下来有 $N$ 行，每行两个整数 $v_i$,$w_i$，用空格隔开，分别表示第 $i$ 件物品的体积和价值。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输出一个整数，表示 方案数 模 $10^9+7$ 的结果。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;4 5
1 2
2 4
3 4
4 6
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;背包方案数：$f[j]$ 存容量 $j$ 的最优价值，$c[j]$ 存达到该最优价值的方案数。01 背包转移时：若 $f[j-v]+w &gt; f[j]$，方案数 $c[j]=c[j-v]$（继承）；若相等，$c[j]+=c[j-v]$。最后答案 $c[m]$（取模）。&lt;/p&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;// 不超背包容量的方案数
#include&amp;#x3C;iostream&gt;
#include&amp;#x3C;cstring&gt;
using namespace std;

const int N=1010, mod=1e9+7;
int f[N],c[N];
// f[i]表示背包容量为i时最优选法的总价值
// c[i]表示背包容量为i时最优选法的方案数

int main(){
  int n, m, v, w;   
  scanf(&quot;%d%d&quot;, &amp;#x26;n, &amp;#x26;m);    
  for(int i=0;i&amp;#x3C;=m;i++) c[i]=1;    
  
  for(int i=1; i&amp;#x3C;=n; i++){    //枚举物品
    scanf(&quot;%d%d&quot;,&amp;#x26;v,&amp;#x26;w);
    for(int j=m; j&gt;=v; j--){  //枚举体积
      if(f[j-v]+w&gt;f[j]){      //装新物品总价值更大
        f[j]=f[j-v]+w;
        c[j]=c[j-v];
      }
      else if(f[j-v]+w==f[j]) //装新物品总价值相等
        c[j]=(c[j]+c[j-v])%mod;     
    }
  }
  printf(&quot;%d\n&quot;,c[m]);
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>E14 背包具体方案</title><link>https://ehnotgod.site/blog/e14-%E8%83%8C%E5%8C%85%E5%85%B7%E4%BD%93%E6%96%B9%E6%A1%88</link><guid isPermaLink="true">https://ehnotgod.site/blog/e14-%E8%83%8C%E5%8C%85%E5%85%B7%E4%BD%93%E6%96%B9%E6%A1%88</guid><description>背包具体方案：回溯输出选择的物品。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/U224067&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;有 $n$ 件物品和一个容量是 $m$ 的背包。每件物品只能使用一次。&lt;/p&gt;
&lt;p&gt;第 $i$ 件物品的体积是 $w_i$，价值是 $v_i$。&lt;/p&gt;
&lt;p&gt;求解将哪些物品装入背包，可使这些物品的总体积不超过背包容量，且总价值最大。&lt;/p&gt;
&lt;p&gt;如果有多种方案能使得选取的物品达到最大价值，输出任何一种即可。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行两个整数，$n$，$m$，用空格隔开，分别表示物品数量和背包容积。&lt;/p&gt;
&lt;p&gt;接下来有 $n$ 行，每行两个整数 $w_i,v_i$，用空格隔开，分别表示第 $i$ 件物品的体积和价值。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输出两行，第一行包含一个整数，表示最优解中取走了多少件物品。&lt;/p&gt;
&lt;p&gt;第二行包含若干个用空格隔开的整数，表示最优解中所选物品的编号序列。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;4 5
1 2
2 4
3 4
4 6
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2
1 4
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;对于 $100%$ 的数据，有 $1 \le n,m \le 1000,0 \le v_i,w_i \le 1000$.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;背包具体方案：用二维 $f[i][j]$ 记录从第 $i$ 个物品开始、容量 $j$ 的最优价值（逆序枚举物品，便于正向回溯），同时用 $p[i][j]$ 记录转移的列位置。回溯时若 $p[i][j]==j-v[i]$ 说明取了第 $i$ 个物品，输出并跳到 $(i+1, j-v[i])$；否则跳到 $(i+1, j)$。&lt;/p&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include&amp;#x3C;bits/stdc++.h&gt;
using namespace std;

const int N=1005;
int n,m,v[N],w[N];
int f[N][N],path[N];

int main(){
  cin&gt;&gt;n&gt;&gt;m;
  for(int i=1;i&amp;#x3C;=n;i++)cin&gt;&gt;v[i]&gt;&gt;w[i];
  
  for(int i=n;i&gt;=1;i--){
    for(int j=0;j&amp;#x3C;=m;j++){
      f[i][j]=f[i+1][j];
      if(j&gt;=v[i])f[i][j]=max(f[i][j],f[i+1][j-v[i]]+w[i]);
    }
  }
  
  int j=m,cnt=0;
  for(int i=1;i&amp;#x3C;=n;i++){
    if(j&gt;=v[i]&amp;#x26;&amp;#x26;f[i][j]==f[i+1][j-v[i]]+w[i]){
      path[++cnt]=i;
      j-=v[i];
    }
  }
  cout&amp;#x3C;&amp;#x3C;cnt&amp;#x3C;&amp;#x3C;endl;
  for(int i=1;i&amp;#x3C;=cnt;i++) cout&amp;#x3C;&amp;#x3C;path[i]&amp;#x3C;&amp;#x3C;&quot; &quot;;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>E15 线性DP K笔买卖</title><link>https://ehnotgod.site/blog/e15-%E7%BA%BF%E6%80%A7dp-k%E7%AC%94%E4%B9%B0%E5%8D%96</link><guid isPermaLink="true">https://ehnotgod.site/blog/e15-%E7%BA%BF%E6%80%A7dp-k%E7%AC%94%E4%B9%B0%E5%8D%96</guid><description>线性 DP：最多 k 笔股票买卖的最大收益。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;https://www.luogu.com.cn/problem/U281153&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;给你一支股票，给出这个股票的N天的价格，Ai表示这个股票在第i天的价格
每一天都可以去买卖这支股票，但是你只能买卖K次（在买股票前需要先全部卖出然后重新买进，被视为一次）&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行包含整数 N 和 K，表示数组的长度以及你可以完成的最大交易笔数。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;第二行包含 N 个不超过 10000 的正整数，表示完整的数组。

1≤N≤1e5，1≤k≤100
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输出一个整数，表示最大利润。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;3 2
2 4 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输入 #2&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;6 2
3 2 6 5 0 3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #2&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;7
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;最多 $k$ 笔股票交易：$f[i][j][0/1]$ 表示前 $i$ 天、已完成 $j$ 笔交易、当前不持有/持有股票的最大收益。不持有：$f[i][j][0]=\max(f[i-1][j][0], f[i-1][j][1]+w[i])$（卖出）；持有：$f[i][j][1]=\max(f[i-1][j][1], f[i-1][j-1][0]-w[i])$（买入，开新一笔交易）。答案 $f[n][k][0]$。复杂度 $O(nk)$。&lt;/p&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include&amp;#x3C;iostream&gt;
#include&amp;#x3C;cstring&gt;
using namespace std;

const int N=100010, M=110;
int w[N], f[N][M][2];

int main(){
  int n, k; cin &gt;&gt; n &gt;&gt; k;
  for(int i=1; i&amp;#x3C;=n; i++) cin &gt;&gt; w[i];
  
  for(int j=0; j&amp;#x3C;=k; j++) f[0][j][1]=-1e6;

  for(int i=1; i&amp;#x3C;=n; i++)
  for(int j=1; j&amp;#x3C;=k; j++){
    f[i][j][0]=max(f[i-1][j][0], f[i-1][j][1]+w[i]);
    f[i][j][1]=max(f[i-1][j][1], f[i-1][j-1][0]-w[i]);
  }
  
  cout &amp;#x3C;&amp;#x3C; f[n][k][0];
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>E16 TSP-状压DP</title><link>https://ehnotgod.site/blog/e16-tsp-%E7%8A%B6%E5%8E%8Bdp</link><guid isPermaLink="true">https://ehnotgod.site/blog/e16-tsp-%E7%8A%B6%E5%8E%8Bdp</guid><description>状压 DP：旅行商问题（TSP）。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P1171&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;某乡有 $n$ 个村庄，有一个售货员，他要到各个村庄去售货，各村庄之间的路程 $s_{i,j}$ 是已知的，且 $A$ 村到 $B$ 村与 $B$ 村到 $A$ 村的路大多不同。为了提高效率，他从商店出发到每个村庄一次，然后返回商店所在的村，假设商店所在的村庄为 $1$，他不知道选择什么样的路线才能使所走的路程最短。请你帮他选择一条最短的路。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行是一个整数，表示村庄数 $n$。&lt;br&gt;
接下来 $n$ 行，每行 $n$ 个整数，第 $i$ 行的第 $j$ 个整数表示 $i$ 到 $j$ 的单向路径的距离 $s_{i,j}$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;一行一个整数表示最短的路程。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;3
0 2 1
1 0 2
2 1 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对全部的测试数据，保证 $2 \leq n \leq 20$，$1 \leq s_{i,j} &amp;#x3C; 10^3$。&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;TSP 状压 DP：$dp[mask][j]$ 表示已访问集合为 $mask$、当前在城市 $j$ 的最小路程。转移：从任意 $k\in mask$（$k\ne j$）走来，$dp[mask][j]=\min(dp[mask \oplus 2^j][k]+s[k][j])$。最后回到起点，$ans=\min_j(dp[2^n-1][j]+s[j][0])$。复杂度 $O(2^n\cdot n^2)$。&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;n = int(input())
s = []
for i in range(n):
    s.append(list(map(int, input().split())))

dp = [[1145145 ** 5 for i in range(n)] for _ in range(2 ** n)]
dp[2 ** 0][0] = 0
for i in range(2 ** n):
    for j in range(n):
        if i &amp;#x26; 2 ** j:
            for k in range(n):
                if i &amp;#x26; 2 ** k and k != j:
                    dp[i][j] = min(dp[i][j], dp[i ^ 2 ** j][k] + s[k][j])
# 为了回到原点
ans = min(dp[2 ** n - 1][j] + s[j][0] for j in range(1, n))
print(ans)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include &amp;#x3C;bits/stdc++.h&gt;
using namespace std;
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n;
    cin &gt;&gt; n;
    vector&amp;#x3C;vector&amp;#x3C;int&gt;&gt; s(n, vector&amp;#x3C;int&gt;(n));
    for (int i = 0; i &amp;#x3C; n; i++) {
        for (int j = 0; j &amp;#x3C; n; j++) {
            cin &gt;&gt; s[i][j];
        }
    }
    const int INF = 1e9;  // 比较大的数即可
    vector&amp;#x3C;vector&amp;#x3C;int&gt;&gt; dp(1 &amp;#x3C;&amp;#x3C; n, vector&amp;#x3C;int&gt;(n, INF));
    dp[1][0] = 0; // 只访问了点0，最后停在0
    for (int mask = 0; mask &amp;#x3C; (1 &amp;#x3C;&amp;#x3C; n); mask++) {
        for (int j = 0; j &amp;#x3C; n; j++) {
            if (mask &amp;#x26; (1 &amp;#x3C;&amp;#x3C; j)) { // j在集合中
                for (int k = 0; k &amp;#x3C; n; k++) {
                    if ((mask &amp;#x26; (1 &amp;#x3C;&amp;#x3C; k)) &amp;#x26;&amp;#x26; k != j) {
                        dp[mask][j] = min(dp[mask][j],
                                          dp[mask ^ (1 &amp;#x3C;&amp;#x3C; j)][k] + s[k][j]);
                    }
                }
            }
        }
    }
    int ans = INF;
    for (int j = 1; j &amp;#x3C; n; j++) {
        ans = min(ans, dp[(1 &amp;#x3C;&amp;#x3C; n) - 1][j] + s[j][0]);
    }
    cout &amp;#x3C;&amp;#x3C; ans &amp;#x3C;&amp;#x3C; &quot;\n&quot;;
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>E17 区间DP-环形石子</title><link>https://ehnotgod.site/blog/e17-%E5%8C%BA%E9%97%B4dp-%E7%8E%AF%E5%BD%A2%E7%9F%B3%E5%AD%90</link><guid isPermaLink="true">https://ehnotgod.site/blog/e17-%E5%8C%BA%E9%97%B4dp-%E7%8E%AF%E5%BD%A2%E7%9F%B3%E5%AD%90</guid><description>区间 DP：环形石子合并，破环成链。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P1880&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在一个圆形操场的四周摆放 $N$ 堆石子，现要将石子有次序地合并成一堆，规定每次只能选相邻的 $2$ 堆合并成新的一堆，并将新的一堆的石子数，记为该次合并的得分。&lt;/p&gt;
&lt;p&gt;试设计出一个算法，计算出将 $N$ 堆石子合并成 $1$ 堆的最小得分和最大得分。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;数据的第 $1$ 行是正整数 $N$，表示有 $N$ 堆石子。&lt;/p&gt;
&lt;p&gt;第 $2$ 行有 $N$ 个整数，第 $i$ 个整数 $a_i$ 表示第 $i$ 堆石子的个数。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输出共 $2$ 行，第 $1$ 行为最小得分，第 $2$ 行为最大得分。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;4
4 5 9 4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;43
54
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;$1\leq N\leq 100$，$0\leq a_i\leq 20$。&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;环形石子合并：把环复制成 2 倍长度（$a[i+n]=a[i]$）破环成链。$f[i][j]$ / $g[i][j]$ 表示把 $i..j$ 合并成一堆的最小 / 最大得分，枚举分割点 $k$：$f[i][j]=\min(f[i][k]+f[k+1][j]+s[j]-s[i-1])$。答案取长度为 $n$ 的所有区间的最值。复杂度 $O(n^3)$。&lt;/p&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include &amp;#x3C;iostream&gt;
#include &amp;#x3C;cstring&gt;
#include &amp;#x3C;algorithm&gt;
using namespace std;

const int N=210;
int n, a[N], s[N];
int f[N][N];  //f[i][j]表示把从i到j合并成一堆的得分最小值 
int g[N][N];  //g[i][j]表示把从i到j合并成一堆的得分最大值 

int main(){
  memset(f,0x3f,sizeof f); memset(g,-0x3f,sizeof g);
  scanf(&quot;%d&quot;,&amp;#x26;n);
  for(int i=1; i&amp;#x3C;=n; i++)scanf(&quot;%d&quot;,&amp;#x26;a[i]), a[i+n]=a[i];
  for(int i=1; i&amp;#x3C;=2*n; i++)s[i]=s[i-1]+a[i], g[i][i]=0, f[i][i]=0;
  
  int minv=1e9, maxv=-1e9;
  for(int len=2; len&amp;#x3C;=n; len++){            //区间长度 
    for(int i=1,j; (j=i+len-1)&amp;#x3C;=2*n; i++){  //区间起点
      for(int k=i; k&amp;#x3C;j; k++){               //区间分割点 
        f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+s[j]-s[i-1]);
        g[i][j]=max(g[i][j],g[i][k]+g[k+1][j]+s[j]-s[i-1]); 
      }
      minv=min(minv,f[i][i+n-1]); //f[1,n]...f[n,2n-1] 
      maxv=max(maxv,g[i][i+n-1]); //g[1,n]...g[n,2n-1]      
    }
  }
  printf(&quot;%d\n%d\n&quot;,minv,maxv);
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>E18 数位DP</title><link>https://ehnotgod.site/blog/e18-%E6%95%B0%E4%BD%8Ddp</link><guid isPermaLink="true">https://ehnotgod.site/blog/e18-%E6%95%B0%E4%BD%8Ddp</guid><description>数位 DP：按位枚举统计满足条件的数字。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：http://ybt.ssoier.cn:8088/problem_show.php?pid=1586&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;科协里最近很流行数字游戏。某人命名了一种不降数，这种数字必须满足从左到右各位数字成小于等于的关系，如 123，446。现在大家决定玩一个游戏，指定一个整数闭区间 [a,b]，问这个区间内有多少个不降数。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;有多组测试数据。每组只含两个数字 a,b，意义如题目描述。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;每行给出一个测试数据的答案，即 [a,b] 之间有多少不降数。&lt;/p&gt;
&lt;p&gt;#输入1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1 9
1 19
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;#输出1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;9
18
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于全部数据，$$1≤a≤b≤2^{31}−1$$。&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;数位 DP（不降数）：把数字按位处理，$f[i][j]$ 表示共 $i$ 位且最高位为 $j$ 的不降数个数（先预处理）。求 $\le n$ 的不降数个数时，从高位到低位枚举，用 $last$ 记录上一位，逐位累加符合条件的数，并处理贴住 $n$ 的边界情况；区间 $[l,r]$ 用 $f(r)-f(l-1)$。核心是“最高位 + 位数”的状态设计。复杂度 $O(位数\times 10\times 10)$。&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;N = 12
a = [0] * N   # 把整数的每一位数字抠出来，存入数组
f = [[0] * N for _ in range(N)]  # f[i][j] 表示一共有 i 位且最高位数字是 j 的不降数个数

def init():   # 预处理不降数的个数
    for j in range(10):
        f[1][j] = 1  # 一位数
    for i in range(2, N):        # 阶段：枚举位数
        for j in range(10):      # 状态：枚举最高位
            for k in range(j, 10):  # 决策：枚举次高位
                f[i][j] += f[i - 1][k]

def dp(n):
    if not n:
        return 1   # 特判，n == 0 返回 1
    cnt = 0
    while n:
        a[cnt + 1] = n % 10
        n //= 10
        cnt += 1
    res, last = 0, 0   # last 表示上一位数字
    for i in range(cnt, 0, -1):  # 从高位到低位枚举
        now = a[i]               # now 表示当前位数字
        for j in range(last, now):   # 枚举当前位可填入的数字
            res += f[i][j]       # 累加答案
        if now &amp;#x3C; last:
            break                # 若小于上一位，则 break
        last = now               # 更新 last
        if i == 1:
            res += 1             # 特判，走到 a1 的情况
    return res

init()  # 预处理不降数的个数
while True:
    try:
        l, r = map(int, input().split())
        print(dp(r) - dp(l - 1))
    except EOFError:
        break
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include &amp;#x3C;iostream&gt;
#include &amp;#x3C;cstring&gt;
#include &amp;#x3C;algorithm&gt;
using namespace std;

const int N=12;
int a[N];     //把整数的每一位数字抠出来，存入数组 
int f[N][N];  //f[i][j]表示一共有i位，且最高位数字是j的不降数的个数 

void init(){  //预处理不降数的个数  
  for(int i=0; i&amp;#x3C;=9; i++) f[1][i]=1;  //一位数
  for(int i=2; i&amp;#x3C;N; i++)        //阶段：枚举位数 
    for(int j=0; j&amp;#x3C;=9; j++)     //状态：枚举最高位 
      for(int k=j; k&amp;#x3C;=9; k++)   //决策：枚举次高位 
        f[i][j]+=f[i-1][k];
}
int dp(int n){
  if(!n) return 1;              //特判，n==0返回1 
  int cnt=0;
  while(n) a[++cnt]=n%10, n/=10;//把每一位抠出来存入数组a      
  
  int res=0, last=0;            //last表示上一位数字
  for(int i=cnt; i&gt;=1; --i){    //从高位到低位枚举 
    int now=a[i];               //now表示当前位数字           
    for(int j=last; j&amp;#x3C;now; j++) //枚举当前位可填入的数字  
      res += f[i][j];           //累加答案
    if(now&amp;#x3C;last) break;         //若小，则break                          
    last=now;                   //更新last
    if(i==1) res++;             //特判，走到a1的情况 
  } 
  return res;
}
int main(){
  init();     //预处理不降数的个数 
  int l,r;
  while(cin&gt;&gt;l&gt;&gt;r) cout&amp;#x3C;&amp;#x3C;dp(r)-dp(l-1)&amp;#x3C;&amp;#x3C;endl;
  return 0;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>E19 Windy数</title><link>https://ehnotgod.site/blog/e19-windy%E6%95%B0</link><guid isPermaLink="true">https://ehnotgod.site/blog/e19-windy%E6%95%B0</guid><description>数位 DP：相邻数字差不小于 2 的数。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P2657&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;不含前导零且相邻两个数字之差至少为 $2$ 的正整数被称为 windy 数。windy 想知道，在 $A$ 和 $B$ 之间，包括 $A$ 和 $B$，总共有多少个 windy 数？&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输入只有一行两个整数，分别表示 $A$ 和 $B$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输出一行一个整数表示答案。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1 10
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;9
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输入 #2&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;25 50
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #2&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;20
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;说明/提示&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;对于全部的测试点，保证 $1\le A\le B\le 2\times 10^9$。&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;Windy 数（相邻数字差 $\ge 2$）数位 DP：预处理 $f[i][j]$ 表示共 $i$ 位、最高位为 $j$ 的 Windy 数个数（$f[i][j]=\sum f[i-1][k]$，其中 $|j-k|\ge 2$）。求 $\le x$ 的个数时按位枚举，$last$ 记录上一位保证相邻差 $\ge 2$，同时处理前导零（位数不足的单独统计）。区间 $[l,r]$ 用 $f(r)-f(l-1)$。复杂度 $O(位数\times 10\times 10)$。&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;f = [[0] * 20 for i in range(20)]
def init():
    for i in range(10):
        f[1][i] = 1
    for i in range(2, 20):
        for j in range(10):
            for k in range(10):
                if abs(j - k) &gt;= 2:
                    f[i][j] += f[i - 1][k]
def dp(x):
    if x == 0:
        return 0
    xlis = []
    while x &gt; 0:
        xlis.append(x % 10)
        x = x // 10
    m = len(xlis)
    res = 0
    last = -2
    for i in range(m - 1, -1, -1):
        new = xlis[i]
        if i == m - 1:
            for j in range(1, new):
                if abs(j - last) &gt;= 2:
                    res += f[i + 1][j]
        else:
            for j in range(new):
                if abs(j - last) &gt;= 2:
                    res += f[i + 1][j]
        if abs(new - last) &amp;#x3C; 2:
            break
        last = new
        if i == 0:
            res += 1
    for i in range(m - 1):
        for j in range(1, 10):
            res += f[i + 1][j]
    return res
init()
a, b = map(int, input().split())
print(dp(b) - dp(a - 1))
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include &amp;#x3C;bits/stdc++.h&gt;
using namespace std;

const int N = 20;
int a[N];     //把整数的每一位数字抠出来，存入数组 
int f[N][10]; //f[i][j]表示一共有i位，且最高位数字为j的Windy数的个数 

void init(){  //预处理Windy数的个数 
  for(int i=0; i&amp;#x3C;=9; i++) f[1][i]=1;  //一位数 
  for(int i=2; i&amp;#x3C;N; i++)        //阶段：枚举位数 
    for(int j=0; j&amp;#x3C;=9; j++)     //状态：枚举第i位 
      for(int k=0; k&amp;#x3C;=9; k++)   //决策：枚举第i-1位 
        if(abs(k-j)&gt;=2) f[i][j]+=f[i-1][k];    
}
int dp(int x){
  if(!x) return 0;                //特判，x==0返回0 
  int cnt = 0; 
  while(x) a[++cnt]=x%10, x/=10;  //把每一位抠出来存入数组

  int res=0, last=-2;             //last表示上一位数字 
  for(int i=cnt; i&gt;=1; --i){      //从高位到低位枚举 
    int now = a[i];               //now表示当前位数字 
    for(int j=(i==cnt); j&amp;#x3C;now; ++j)     //最高位从1开始 
      if(abs(j-last)&gt;=2) res+=f[i][j];  //累加答案 
       
    if(abs(now-last)&amp;#x3C;2) break;  //不满足定义，break 
    last = now;                 //更新last 
    if(i==1) res++;             //特判，走到a1的情况 
  }
  
  for(int i=1; i&amp;#x3C;cnt; i++)      //答案小于cnt位的 
    for(int j=1; j&amp;#x3C;=9; j++) 
      res += f[i][j]; 
  return res; 
}
int main(){
  init();   //预处理Windy数的个数 
  int l,r;
  cin&gt;&gt;l&gt;&gt;r;
  cout&amp;#x3C;&amp;#x3C;dp(r)-dp(l-1);
  return 0;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>E20 单调队列优化dp</title><link>https://ehnotgod.site/blog/e20-%E5%8D%95%E8%B0%83%E9%98%9F%E5%88%97%E4%BC%98%E5%8C%96dp</link><guid isPermaLink="true">https://ehnotgod.site/blog/e20-%E5%8D%95%E8%B0%83%E9%98%9F%E5%88%97%E4%BC%98%E5%8C%96dp</guid><description>单调队列优化 DP：维护滑动窗口最值转移。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P1725&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;小河可以看作一列格子依次编号为 $0$ 到 $n$，琪露诺只能从编号小的格子移动到编号大的格子。当她在格子 $i$ 时，她只移动到区间 $[i+L,\ i+R]$ 中的任意一格。每一个格子都有一个冰冻指数 $a_i$，当琪露诺停留在那一格时就可以得到那一格的冰冻指数。琪露诺希望在到达对岸时获取最大的冰冻指数。&lt;/p&gt;
&lt;p&gt;开始时，琪露诺在编号 $0$ 的格子上，只要她下一步的位置编号大于 $n$ 就算到达对岸。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行三个正整数 $n,L,R$。&lt;/p&gt;
&lt;p&gt;第二行共 $n+1$ 个整数，第 $i$ 个数表示编号为 $i-1$ 的格子的冰冻指数 $a_{i-1}$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;一个整数，表示最大冰冻指数。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;5 2 3
0 12 3 11 7 -2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;11
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;说明/提示&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;对于 $60%$ 的数据，$1\le n\le 10^4$；对于 $100%$ 的数据，$1\le n\le 2\times 10^5$，$1\le L\le R\le n$，$-1000\le a_i\le 1000$。数据保证最终答案不超过 $2^{31}-1$。&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;单调队列优化 DP：当转移式是 $f[i]=\max/\min(f[j])+w$ 且 $j$ 落在长度固定的滑动窗口内时，用单调队列维护候选 $j$ 的最值。本题从 $[i-m, i-1]$ 转移：队头弹出滑出窗口的下标，队尾保持 $f$ 单调，$f[i]=f[q[h]]+a[i]$，最后取能到达末尾的最小值。复杂度 $O(n)$。&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from collections import deque
n, m = map(int, input().split())
a = [0] + list(map(int, input().split()))

q = deque()
f = [0] * (n + 1)
ans = float(&quot;inf&quot;)
for i in range(1, n + 1):
    while q and q[0] &amp;#x3C; i - m:
        q.popleft()               # 弹出滑出窗口的下标
    while q and f[q[-1]] &gt;= f[i - 1]:
        q.pop()                   # 队尾保持 f 单调
    q.append(i - 1)
    f[i] = f[q[0]] + a[i]         # 队头即窗口内 f 最小值
    if i &gt;= n - m + 1:
        ans = min(ans, f[i])
print(ans)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;// 单调队列+DP O(n)
#include&amp;#x3C;bits/stdc++.h&gt;
using namespace std;

const int N=200010;
int n,m,a[N];
int q[N],f[N];

int main(){
  cin&gt;&gt;n&gt;&gt;m;
  for(int i=1; i&amp;#x3C;=n; i++) cin&gt;&gt;a[i];
  
  int ans=2e9;
  for(int i=1,h=1,t=0; i&amp;#x3C;=n; i++){
    while(h&amp;#x3C;=t &amp;#x26;&amp;#x26; q[h]&amp;#x3C;i-m) h++;
    while(h&amp;#x3C;=t &amp;#x26;&amp;#x26; f[q[t]]&gt;=f[i-1]) t--;
    q[++t]=i-1;
    f[i]=f[q[h]]+a[i];
    if(i&gt;=n-m+1) ans=min(ans,f[i]);
  }
  cout&amp;#x3C;&amp;#x3C;ans;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>E21 斜率优化DP</title><link>https://ehnotgod.site/blog/e21-%E6%96%9C%E7%8E%87%E4%BC%98%E5%8C%96dp</link><guid isPermaLink="true">https://ehnotgod.site/blog/e21-%E6%96%9C%E7%8E%87%E4%BC%98%E5%8C%96dp</guid><description>斜率优化 DP：凸包维护转移斜率。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P3195&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;P 教授要去看奥运，但他舍不下他的玩具，于是决定把所有的玩具运到北京。他有编号为 $1\sim n$ 的 $n$ 件玩具，第 $i$ 件玩具经过压缩后的一维长度为 $c_i$。为了方便整理，P 教授要求在一个一维容器中的玩具编号是连续的。同时如果一个一维容器中有多个玩具，那么两件玩具之间要加入一个单位长度的填充物。形式地说，如果将第 $i$ 件玩具到第 $j$ 个玩具放到一个容器中，那么容器的长度将为 $j-i+\sum_{k=i}^{j} c_k$。&lt;/p&gt;
&lt;p&gt;制作容器的费用与容器的长度有关，如果容器长度为 $x$，其制作费用为 $(x-L)^2$，其中 $L$ 是一个常量。P 教授不关心容器的数目，但他希望所有容器的总费用最小。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行有两个整数，用一个空格隔开，分别代表 $n$ 和 $L$。&lt;/p&gt;
&lt;p&gt;第 $2$ 到第 $n+1$ 行，每行一个整数，第 $(i+1)$ 行的整数代表第 $i$ 件玩具的长度 $c_i$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输出一行一个整数，代表所有容器的总费用最小是多少。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;5 4
3
4
2
1
4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;说明/提示&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;对于全部的测试点，$1\le n\le 5\times 10^4$，$1\le L\le 10^7$，$1\le c_i\le 10^7$。&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;斜率优化 DP：当转移式中含 $f[j]+a[i]\cdot b[j]$ 这类项时，把候选 $j$ 看作平面上的点 $(dx,dy)$，用凸包维护最优斜率。用单调队列使相邻点斜率单调（下凸包），每次用当前斜率弹出队头不优的点，队头即最优转移。本题为玩具装箱（HNOI2008），复杂度 $O(n)$。&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import sys
input = sys.stdin.readline

while True:
    line = input()
    if not line:
        break
    n, m = map(int, line.split())
    s = [0] + list(map(int, input().split()))
    for i in range(1, n + 1):
        s[i] += s[i - 1]

    def dy(i, j):
        return f[i] + s[i] * s[i] - f[j] - s[j] * s[j]

    def dx(i, j):
        return s[i] - s[j]

    f = [0] * (n + 1)
    q = [0] * (n + 1)
    h, t = 1, 0
    for i in range(1, n + 1):
        while h &amp;#x3C; t and dy(i - 1, q[t]) * dx(q[t], q[t - 1]) &amp;#x3C;= dx(i - 1, q[t]) * dy(q[t], q[t - 1]):
            t -= 1
        t += 1
        q[t] = i - 1
        while h &amp;#x3C; t and dy(q[h + 1], q[h]) &amp;#x3C;= dx(q[h + 1], q[h]) * 2 * s[i]:
            h += 1
        j = q[h]
        f[i] = f[j] + (s[i] - s[j]) * (s[i] - s[j]) + m
    print(f[n])
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include &amp;#x3C;iostream&gt;
#include &amp;#x3C;cstring&gt;
#include &amp;#x3C;algorithm&gt;
using namespace std;

typedef long long LL;
const int N = 500010;
int n,m,q[N];
LL s[N],f[N];

LL dy(int i,int j){return f[i]+s[i]*s[i]-f[j]-s[j]*s[j];}
LL dx(int i,int j){return s[i]-s[j];}
int main(){
  while(~scanf(&quot;%d%d&quot;,&amp;#x26;n,&amp;#x26;m)){
    for(int i=1;i&amp;#x3C;=n;i++)scanf(&quot;%lld&quot;,&amp;#x26;s[i]),s[i]+=s[i-1];

    int h=1,t=0;
    for(int i=1;i&amp;#x3C;=n;i++){
      while(h&amp;#x3C;t &amp;#x26;&amp;#x26; dy(i-1,q[t])*dx(q[t],q[t-1])
                 &amp;#x3C;=dx(i-1,q[t])*dy(q[t],q[t-1])) t--;
      q[++t]=i-1;      
      while(h&amp;#x3C;t &amp;#x26;&amp;#x26; dy(q[h+1],q[h])
                 &amp;#x3C;=dx(q[h+1],q[h])*2*s[i]) h++;
      int j=q[h];
      f[i]=f[j]+(s[i]-s[j])*(s[i]-s[j])+m;
    }
    printf(&quot;%lld\n&quot;,f[n]);
  }
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>F1 KMP算法</title><link>https://ehnotgod.site/blog/f1-kmp%E7%AE%97%E6%B3%95</link><guid isPermaLink="true">https://ehnotgod.site/blog/f1-kmp%E7%AE%97%E6%B3%95</guid><description>KMP 字符串匹配：next 数组加速匹配。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P3375&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;给出两个字符串 $s_1$ 和 $s_2$，若 $s_1$ 的区间 $[l,r]$ 子串与 $s_2$ 完全相同，则称 $s_2$ 在 $s_1$ 中出现了，其出现位置为 $l$。现在请你求出 $s_2$ 在 $s_1$ 中所有出现的位置。&lt;/p&gt;
&lt;p&gt;定义一个字符串 $s$ 的 border 为 $s$ 的一个非 $s$ 本身的子串 $t$，满足 $t$ 既是 $s$ 的前缀，又是 $s$ 的后缀。对于 $s_2$，你还需要求出对于其每个前缀 $s_2$ 的最长 border 的长度。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行为一个字符串，即为 $s_1$。第二行为一个字符串，即为 $s_2$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;首先输出若干行，每行一个整数，按从小到大的顺序输出 $s_2$ 在 $s_1$ 中出现的位置。最后一行输出 $|s_2|$ 个整数，第 $i$ 个整数表示 $s_2$ 的长度为 $i$ 的前缀的最长 border 长度。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ABABABC
ABA
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1
3
0 0 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;说明/提示&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;对于全部的测试点，$1\le |s_1|,|s_2|\le 10^6$，$s_1,s_2$ 中均只含大写英文字母。&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;KMP 字符串匹配：预处理模式串的 $nxt$ 数组，$nxt[i]$ 表示 $P$ 的前缀 $i$ 的最长 border 长度。匹配时主串指针不回退，失配时 $j=nxt[j]$ 跳跃，$j==n$ 即匹配成功并输出位置 $i-n+1$。最后按题目要求输出 $nxt[1..n]$。复杂度 $O(n+m)$。&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;N = 1000010
S = list(input().strip())
S = [&quot;&quot;] + S
P = list(input().strip())
P = [&quot;&quot;] + P
nxt = [0] * N
m = len(S) - 1
n = len(P) - 1
S.append(&quot;&quot;)
P.append(&quot;&quot;)
nxt[1] = 0
j = 0
for i in range(2, n + 1):
    while j and P[i] != P[j + 1]:
        j = nxt[j]
    if P[i] == P[j + 1]:
        j += 1
    nxt[i] = j
j = 0
for i in range(1, m + 1):
    while j and S[i] != P[j + 1]:
        j = nxt[j]
    if S[i] == P[j + 1]:
        j += 1
    if j == n:
        print(i - n + 1)
print(*nxt[1:n + 1])
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include &amp;#x3C;cstring&gt;
#include &amp;#x3C;iostream&gt;
#include &amp;#x3C;algorithm&gt;
using namespace std;

const int N=1000010;
int m,n;
char S[N],P[N];
int nxt[N];

int main(){
  cin&gt;&gt;S+1&gt;&gt;P+1;
  m=strlen(S+1),n=strlen(P+1);

  nxt[1]=0;
  for(int i=2,j=0;i&amp;#x3C;=n;i++){
    while(j &amp;#x26;&amp;#x26; P[i]!=P[j+1]) j=nxt[j];
    if(P[i]==P[j+1]) j++;
    nxt[i]=j;
  }

  for(int i=1,j=0;i&amp;#x3C;=m;i++){
    while(j &amp;#x26;&amp;#x26; S[i]!=P[j+1]) j=nxt[j];
    if(S[i]==P[j+1]) j++;
    if(j==n) printf(&quot;%d\n&quot;,i-n+1);
  }

  for(int i=1;i&amp;#x3C;=n;i++)printf(&quot;%d &quot;,nxt[i]);
  return 0;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>F2 马拉车算法-最长回文子串</title><link>https://ehnotgod.site/blog/f2-%E9%A9%AC%E6%8B%89%E8%BD%A6%E7%AE%97%E6%B3%95-%E6%9C%80%E9%95%BF%E5%9B%9E%E6%96%87%E5%AD%90%E4%B8%B2</link><guid isPermaLink="true">https://ehnotgod.site/blog/f2-%E9%A9%AC%E6%8B%89%E8%BD%A6%E7%AE%97%E6%B3%95-%E6%9C%80%E9%95%BF%E5%9B%9E%E6%96%87%E5%AD%90%E4%B8%B2</guid><description>Manacher 马拉车：O(n) 求最长回文子串。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P3805&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;给出一个只由小写英文字符 $a,b,c,\dots,y,z$ 组成的字符串 $S$，求 $S$ 中最长回文串的长度。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;一行小写英文字符组成的字符串 $S$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;一个整数表示答案。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;aaa
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;说明/提示&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;对于 $100%$ 的数据，$1\le |S|\le 1.1\times 10^7$。&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;Manacher（马拉车）O(n) 求最长回文子串：先在字符间插入 &lt;code&gt;#&lt;/code&gt;（首尾加哨兵 &lt;code&gt;$&lt;/code&gt;）把奇偶回文统一为奇回文，再维护当前最右回文边界 $[l,r]$ 与对应中心。$d[i]$ 为以 $i$ 为中心的回文半径，初始化用对称位置 $d[l+r-i]$ 取 min，再向两边扩展比较，最后更新 $[l,r]$。最长回文子串长度为 $\max d[i]-1$。复杂度 $O(n)$。&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;s = input().strip()
# 改造串：插入 # 统一奇偶回文
t = [&apos;$&apos;, &apos;#&apos;]
for c in s:
    t.append(c)
    t.append(&apos;#&apos;)
n = len(t)
d = [0] * n
l, r = 1, 1
ans = 0
for i in range(1, n):
    if i &amp;#x3C;= r:
        d[i] = min(d[l + r - i], r - i + 1)
    while i - d[i] &gt;= 0 and i + d[i] &amp;#x3C; n and t[i - d[i]] == t[i + d[i]]:
        d[i] += 1
    if i + d[i] - 1 &gt; r:
        l = i - d[i] + 1
        r = i + d[i] - 1
    ans = max(ans, d[i])
print(ans - 1)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include &amp;#x3C;iostream&gt;
#include &amp;#x3C;cstring&gt;
#include &amp;#x3C;algorithm&gt;
using namespace std;

const int N=3e7;
char a[N],s[N];
int d[N]; //回文半径函数 

void get_d(char*s,int n){
  d[1]=1;
    for(int i=2,l,r=1;i&amp;#x3C;=n;i++){
        if(i&amp;#x3C;=r)d[i]=min(d[r-i+l],r-i+1);
        while(s[i-d[i]]==s[i+d[i]])d[i]++;
        if(i+d[i]-1&gt;r)l=i-d[i]+1,r=i+d[i]-1;
        // printf(&quot;i=%d d=%d [%d %d]\n&quot;,i,d[i],l,r);
    }  
}
int main(){
  //改造串
  scanf(&quot;%s&quot;,a+1);
  int n=strlen(a+1),k=0;
  s[0]=&apos;$&apos;,s[++k]=&apos;#&apos;;        
  for(int i=1;i&amp;#x3C;=n;i++) 
    s[++k]=a[i],s[++k]=&apos;#&apos;;
  n=k;
  
  get_d(s,n);//计算d函数
  int ans=0;
  for(int i=1;i&amp;#x3C;=n;i++)
    ans=max(ans,d[i]);
  printf(&quot;%d\n&quot;,ans-1);
  return 0;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>F3 Trie字典树</title><link>https://ehnotgod.site/blog/f3-trie%E5%AD%97%E5%85%B8%E6%A0%91</link><guid isPermaLink="true">https://ehnotgod.site/blog/f3-trie%E5%AD%97%E5%85%B8%E6%A0%91</guid><description>Trie 字典树：多模式串前缀匹配。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P8306&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;给定 $n$ 个模式串 $s_1\sim s_n$ 和 $q$ 次询问，每次询问给定一个文本串 $t$，请回答 $t$ 在 $s_1\sim s_n$ 中作为完整字符串出现的次数。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行是两个整数，分别表示模式串的个数 $n$ 和询问的个数 $q$。&lt;/p&gt;
&lt;p&gt;接下来 $n$ 行，每行一个字符串，表示一个模式串。接下来 $q$ 行，每行一个字符串，表示一次询问。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;对于每次询问，输出一行一个整数表示答案。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;3 3
fusufusu
fusu
fusu
fusu
anguei
kkksc
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2
1
0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;说明/提示&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;保证输入字符串的总长度不超过 $3\times 10^6$，只含大小写字母和数字，且不含空串。&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;Trie 字典树（前缀树）：每个节点有 26 个儿子指针（小写字母），插入时沿字符链走，不存在则新建节点，末尾节点 $cnt$ 加一。查询时沿询问串走到底，返回末尾节点的 $cnt$ 即该串作为完整串出现的次数；中途断链则返回 0。插入、查询均 $O(|s|)$。&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;N = 100010
ch = [[0 for _ in range(26)] for _ in range(N)]
cnt = [0] * N
idx = 0

def insert(s):
    global idx
    p = 0
    for c in s:
        j = ord(c) - ord(&apos;a&apos;)
        if not ch[p][j]:
            idx += 1
            ch[p][j] = idx
        p = ch[p][j]
    cnt[p] += 1

def query(s):
    p = 0
    for c in s:
        j = ord(c) - ord(&apos;a&apos;)
        if not ch[p][j]:
            return 0
        p = ch[p][j]
    return cnt[p]

n, q = map(int, input().split())
for _ in range(n):
    s = input().strip()
    insert(s)
for _ in range(q):
    s = input().strip()
    print(query(s))
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;// O(n)#include &amp;#x3C;iostream&gt;
#include &amp;#x3C;cstring&gt;
#include &amp;#x3C;algorithm&gt;
using namespace std;

const int N=100010;
int n;
char s[N];
int ch[N][26],cnt[N],idx;

void insert(char *s){
  int p=0;
  for(int i=0; s[i]; i ++){
    int j=s[i]-&apos;a&apos;;//字母映射
    if(!ch[p][j])ch[p][j]=++idx;
    p=ch[p][j];
  }
  cnt[p]++;//插入次数
}
int query(char *s){
  int p=0;
  for(int i=0; s[i]; i ++){
    int j=s[i]-&apos;a&apos;;
    if(!ch[p][j]) return 0;
    p=ch[p][j];
  }
  return cnt[p];
}
int main(){
  scanf(&quot;%d&quot;,&amp;#x26;n);
  while(n--){
    char op;
    scanf(&quot;%s%s&quot;,&amp;#x26;op,s);
    if(op==&apos;I&apos;)insert(s);
    else printf(&quot;%d\n&quot;,query(s));
  }
  return 0;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>F4 最大异或对</title><link>https://ehnotgod.site/blog/f4-%E6%9C%80%E5%A4%A7%E5%BC%82%E6%88%96%E5%AF%B9</link><guid isPermaLink="true">https://ehnotgod.site/blog/f4-%E6%9C%80%E5%A4%A7%E5%BC%82%E6%88%96%E5%AF%B9</guid><description>Trie + 贪心：求最大异或对。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P10471&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;给定 $n$ 个整数 $a_1\sim a_n$，任选两个数 $a_i, a_j\ (i\ne j)$，求它们的按位异或 $a_i\oplus a_j$ 的最大值。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行一个整数 $n$。&lt;/p&gt;
&lt;p&gt;第二行 $n$ 个整数 $a_1\sim a_n$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;一个整数，表示最大异或值。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;说明/提示&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;数据保证 $1\le n\le 10^5$，$0\le a_i&amp;#x3C;2^{31}$。&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;01 Trie + 贪心：把每个数从高位到低位插入 01 Trie（31 位）。查询一个数 $x$ 的最大异或时，从高位开始尽量走与当前位相反的边（$1-j$），使异或结果当前位为 1，累加 $2^i$；无相反边则走相同边。对每个数查询取最大值。插入、查询均 $O(31)$，总复杂度 $O(31n)$。&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;ch = [[0, 0]]  # 初始化Trie树，根节点为0

def insert(x):
    p = 0
    for i in range(30, -1, -1):
        j = (x &gt;&gt; i) &amp;#x26; 1
        if ch[p][j] == 0:
            ch.append([0, 0])  # 创建新节点
            ch[p][j] = len(ch) - 1  # 更新指针到新节点
        p = ch[p][j]

def query(x):
    p = 0
    res = 0
    for i in range(30, -1, -1):
        j = (x &gt;&gt; i) &amp;#x26; 1
        opposite = 1 - j
        if ch[p][opposite]:
            res += (1 &amp;#x3C;&amp;#x3C; i)
            p = ch[p][opposite]
        else:
            p = ch[p][j]
    return res

n = int(input())
a = list(map(int, input().split()))
for num in a:
    insert(num)
ans = 0
for num in a:
    ans = max(ans, query(num))
print(ans)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;// 01Trie 最大异或对
#include &amp;#x3C;iostream&gt;
using namespace std;

const int N=100010;
int n, a[N];
int ch[N*31][2],cnt;

void insert(int x){
  int p=0;
  for(int i=30; i&gt;=0; i--){
    int j=x&gt;&gt;i&amp;#x26;1; //取出第i位
    if(!ch[p][j])ch[p][j]=++cnt;
    p=ch[p][j];
  }
}
int query(int x){
  int p=0,res=0;
  for(int i=30; i&gt;=0; i--){
    int j=x&gt;&gt;i&amp;#x26;1;
    if(ch[p][!j]){
      res += 1&amp;#x3C;&amp;#x3C;i; //累加位权
      p=ch[p][!j];
    }
    else p=ch[p][j];
  }
  return res;
}
int main(){
  cin&gt;&gt;n;
  for(int i=1; i&amp;#x3C;=n; i++)
    cin&gt;&gt;a[i],insert(a[i]);
  int ans=0;
  for(int i=1; i&amp;#x3C;=n; i++)
    ans=max(ans,query(a[i]));
  cout&amp;#x3C;&amp;#x3C;ans;
  return 0;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>G1 快速幂</title><link>https://ehnotgod.site/blog/g1-%E5%BF%AB%E9%80%9F%E5%B9%82</link><guid isPermaLink="true">https://ehnotgod.site/blog/g1-%E5%BF%AB%E9%80%9F%E5%B9%82</guid><description>快速幂：二分求 a^b mod p。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P1226&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;给你三个整数 $$a,b,p$$，求 $$a^b \bmod p$$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输入只有一行三个整数，分别代表 $$a,b,p$$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输出一行一个字符串 &lt;code&gt;a^b mod p=s&lt;/code&gt;，其中 $$a,b,p$$ 分别为题目给定的值， $$s$$ 为运算结果。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2 10 9
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2^10 mod 9=7
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于 $$100%$$ 的数据，保证 $$0\le a,b &amp;#x3C; 2^{31}$$，$$a+b&gt;0$$，$$2 \leq p \lt 2^{31}$$。&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;快速幂：把指数 $b$ 按二进制拆分，$a^b$ 由 $a^{2^i}$ 的乘积组成。从低位到高位检查 $b$ 的每一位，当前位为 1 就乘上 $a^{2^i}$，同时 $a$ 每次自乘平方（$a^{2^i}=(a^{2^{i-1}})^2$）。每次运算对 $p$ 取模。复杂度 $O(\log b)$。&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;a, b, p = map(int, input().split())
ans = pow(a, b, p)
s = &quot;{0}^{1} mod {2}={3}&quot;.format(a, b, p, ans)
print(s)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include &amp;#x3C;iostream&gt;
using namespace std;

typedef long long LL;
int a,b,p;

int qpow(int a,int b,int p){ //快速幂
  int s=1;
  while(b){
    if(b&amp;#x26;1) s=(LL)s*a%p;
    a=(LL)a*a%p;
    b&gt;&gt;=1;
  }
  return s;
}
int main(){
  cin&gt;&gt;a&gt;&gt;b&gt;&gt;p;
  int s=qpow(a,b,p);
  printf(&quot;%d^%d mod %d=%d\n&quot;,a,b,p,s);
  return 0;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>G2 高精度快速幂</title><link>https://ehnotgod.site/blog/g2-%E9%AB%98%E7%B2%BE%E5%BA%A6%E5%BF%AB%E9%80%9F%E5%B9%82</link><guid isPermaLink="true">https://ehnotgod.site/blog/g2-%E9%AB%98%E7%B2%BE%E5%BA%A6%E5%BF%AB%E9%80%9F%E5%B9%82</guid><description>高精度 + 快速幂：求超大整数幂。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P1045&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;形如 $$2^{P}-1$$ 的素数称为麦森数，这时 $$P$$ 一定也是个素数。但反过来不一定，即如果 $$P$$ 是个素数，$$2^{P}-1$$ 不一定也是素数。到 1998 年底，人们已找到了 37 个麦森数。最大的一个是 $$P=3021377$$，它有 909526 位。麦森数有许多重要应用，它与完全数密切相关。&lt;/p&gt;
&lt;p&gt;任务：输入 $$P(1000&amp;#x3C;P&amp;#x3C;3100000)$$，计算 $$2^{P}-1$$ 的位数和最后 $$500$$ 位数字（用十进制高精度数表示）&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;文件中只包含一个整数 $$P(1000&amp;#x3C;P&amp;#x3C;3100000)$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行：十进制高精度数 $$2^{P}-1$$ 的位数。&lt;/p&gt;
&lt;p&gt;第 $$2\sim 11$$ 行：十进制高精度数 $$2^{P}-1$$ 的最后 $$500$$ 位数字。（每行输出 $$50$$ 位，共输出 $$10$$ 行，不足 $$500$$ 位时高位补 $$0$$）&lt;/p&gt;
&lt;p&gt;不必验证 $$2^{P}-1$$ 与 $$P$$ 是否为素数。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1279

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;386
00000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000
00000000000000104079321946643990819252403273640855
38615262247266704805319112350403608059673360298012
23944173232418484242161395428100779138356624832346
49081399066056773207629241295093892203457731833496
61583550472959420547689811211693677147548478866962
50138443826029173234888531116082853841658502825560
46662248318909188018470682222031405210266984354887
32958028878050869736186900714720710555703168729087
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;麦森数：位数由 $\lfloor P\log_{10}2\rfloor+1$ 计算。后 500 位用高精度数组 + 快速幂：乘法按位逐位计算并进位，幂次按二进制拆分，最后个位减 1（$2^P-1$）。输出分 10 行，每行 50 位。&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import sys
N = 500
def mul(a, b):  # 高精度乘法
    t = [0] * (N * 2)
    for i in range(N):
        for j in range(N):
            t[i + j] += a[i] * b[j]
            t[i + j + 1] += t[i + j] // 10
            t[i + j] %= 10
    return t
def quickpow(p):  # 快速幂
    a = [0] * N
    res = [0] * N
    a[0] = 2
    res[0] = 1
    while p:
        if p &amp;#x26; 1:
            res = mul(res, a)
        a = mul(a, a)
        p &gt;&gt;= 1
    res[0] -= 1  # 个位修正
    return res
def main():
    p = int(sys.stdin.readline())
    # 输出位数
    print(int(p * __import__(&apos;math&apos;).log10(2)) + 1)

    res = quickpow(p)
    for i in range(10):
        start = N - 1 - i * 50
        line_digits = res[start - 49:start + 1]
        print(&apos;&apos;.join(str(d) for d in reversed(line_digits)))
if __name__ == &apos;__main__&apos;:
    main()
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include &amp;#x3C;iostream&gt;
#include &amp;#x3C;cstring&gt;
#include &amp;#x3C;algorithm&gt;
#include &amp;#x3C;vector&gt;
#include &amp;#x3C;cmath&gt;
using namespace std;

const int N=500;
typedef vector&amp;#x3C;int&gt; VI;
VI a(N),res(N);
int p;

VI mul(VI &amp;#x26;a, VI &amp;#x26;b){//高精度
  VI t(N*2);
  for(int i=0; i&amp;#x3C;N; i++)
    for(int j=0; j&amp;#x3C;N; j++){
      t[i+j] += a[i]*b[j];
      t[i+j+1] += t[i+j]/10;
      t[i+j] %= 10;
    }
  return t;
}
void quickpow(int p){//快速幂
  res[0]=1, a[0]=2;
  while(p){
    if(p &amp;#x26; 1) res = mul(res,a);
    a = mul(a,a);
    p &gt;&gt;= 1;
  }
  res[0]--; //个位修正
}
int main(){
  cin &gt;&gt; p;
  printf(&quot;%d\n&quot;,int(p*log10(2))+1);
  quickpow(p);
  for(int i=0, k=499; i&amp;#x3C;10; i++){
    for(int j=0; j&amp;#x3C;50; j++, k--)
      printf(&quot;%d&quot;,res[k]);
    puts(&quot;&quot;);
  }
  return 0;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>G3 矩阵快速幂</title><link>https://ehnotgod.site/blog/g3-%E7%9F%A9%E9%98%B5%E5%BF%AB%E9%80%9F%E5%B9%82</link><guid isPermaLink="true">https://ehnotgod.site/blog/g3-%E7%9F%A9%E9%98%B5%E5%BF%AB%E9%80%9F%E5%B9%82</guid><description>矩阵快速幂：加速线性递推。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P3390&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;给定 $$n\times n$$ 的矩阵 $$A$$，求 $$A^k$$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行两个整数 $$n,k$$。&lt;br&gt;
接下来 $$n$$ 行，每行 $$n$$ 个整数，第 $$i$$ 行的第 $$j$$ 的数表示 $$A_{i,j}$$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输出 $$A^k$$&lt;/p&gt;
&lt;p&gt;共 $$n$$ 行，每行 $$n$$ 个数，第 $$i$$ 行第 $$j$$ 个数表示 $$(A^k)_{i,j}$$，每个元素对 $$10^9+7$$ 取模。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2 1
1 1
1 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1 1
1 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输入 #2&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;3 5
1 2 3
4 5 6
7 8 9
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #2&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;121824 149688 177552
275886 338985 402084
429948 528282 626616
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于 $$100%$$ 的数据，$$1\le n \le 100$$，$$0 \le k \le 10^{12}$$，$$|A_{i,j}| \le 1000$$。&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;矩阵快速幂：把标量快速幂推广到矩阵。初始为单位矩阵，转移矩阵自乘平方，指数 $k$ 按二进制拆分，当前位为 1 则乘上对应矩阵。矩阵乘法三重循环 $O(n^3)$，总复杂度 $O(n^3\log k)$，模 $10^9+7$。&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import sys
MOD = 10**9 + 7
def mat_mult(X, Y, n):  # 矩阵乘法：返回 X * Y
    Z = [[0] * n for _ in range(n)]
    for i in range(n):
        for k in range(n):
            if X[i][k]:  # 跳过零元，加快运算
                xik = X[i][k]
                for j in range(n):
                    Z[i][j] = (Z[i][j] + xik * Y[k][j]) % MOD
    return Z

def mat_pow(A, exp, n):  # 快速幂：A^exp
    # 初始化为单位矩阵
    res = [[1 if i == j else 0 for j in range(n)] for i in range(n)]
    base = A
    while exp:
        if exp &amp;#x26; 1:
            res = mat_mult(res, base, n)
        base = mat_mult(base, base, n)
        exp &gt;&gt;= 1
    return res
n, k = map(int, input().split())
# 读取矩阵，转换为 0-index 列表
A = []
for i in range(n):
    A.append(list(map(int, input().split())))
# 计算 A^k 模 MOD
result = mat_pow(A, k, n)
# 输出结果矩阵
out = []
for row in result:
    out.append(&apos; &apos;.join(str(v) for v in row))
sys.stdout.write(&apos;\n&apos;.join(out))
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include &amp;#x3C;iostream&gt;
#include &amp;#x3C;cstring&gt;
#include &amp;#x3C;algorithm&gt;
using namespace std;

typedef long long LL;
const int mod=1000000007;
struct matrix{
    LL c[101][101];
    matrix(){memset(c, 0, sizeof c);}
} A, res;
LL n, k;

matrix operator*(matrix &amp;#x26;x, matrix &amp;#x26;y){ //矩阵乘法
    matrix t; //临时矩阵
    for(int i=1; i&amp;#x3C;=n; i++)
      for(int j=1; j&amp;#x3C;=n; j++)
        for(int k=1; k&amp;#x3C;=n; k++)
          t.c[i][j]=(t.c[i][j]+x.c[i][k]*y.c[k][j])%mod;
    return t;
}
void quickpow(LL k){ //快速幂
    for(int i=1; i&amp;#x3C;=n; i++) res.c[i][i]=1; //单位矩阵
    while(k){
        if(k &amp;#x26; 1) res = res*A;
        A = A*A;
        k &gt;&gt;= 1;
    }
}
int main(){
    scanf(&quot;%d%lld&quot;,&amp;#x26;n,&amp;#x26;k);
    for(int i=1; i&amp;#x3C;=n; i++)
        for(int j=1; j&amp;#x3C;=n; j++)
            scanf(&quot;%d&quot;,&amp;#x26;A.c[i][j]);
    quickpow(k);
    for(int i=1; i&amp;#x3C;=n; i++){
        for(int j=1; j&amp;#x3C;=n; j++)
            printf(&quot;%d &quot;,res.c[i][j]);
        puts(&quot;&quot;);
    }
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>G4 gcd及lcm问题</title><link>https://ehnotgod.site/blog/g4-gcd%E5%8F%8Alcm%E9%97%AE%E9%A2%98</link><guid isPermaLink="true">https://ehnotgod.site/blog/g4-gcd%E5%8F%8Alcm%E9%97%AE%E9%A2%98</guid><description>gcd / lcm：欧几里得算法及相关性质。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P1029&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输入两个正整数 $x_0, y_0$，求出满足下列条件的 $P, Q$ 的个数：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;$P,Q$ 是正整数。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;要求 $P, Q$ 以 $x_0$ 为最大公约数，以 $y_0$ 为最小公倍数。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;试求：满足条件的所有可能的 $P, Q$ 的个数。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;一行两个正整数 $x_0, y_0$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;一行一个数，表示求出满足条件的 $P, Q$ 的个数。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;3 60

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;4

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于 $100%$ 的数据，$2 \le x_0, y_0 \le {10}^5$。&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;利用性质 $\gcd(p,q)\cdot\operatorname{lcm}(p,q)=p\cdot q$：令 $t=x\cdot y$，枚举 $i$ 满足 $i\mid t$ 且 $\gcd(i,t/i)=x$，则 $(i,t/i)$ 为一组解，对称计数加 2；$x=y$ 时去掉重复的一次。复杂度 $O(\sqrt{t})$。&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# P1029 [NOIP 2001 普及组] 最大公约数和最小公倍数问题
import math
x, y = map(int, input().split())
t = x * y
ans = 0
for i in range(1, int(t ** 0.5) + 1):
    if t % i == 0 and math.gcd(t // i, i) == x:
        ans += 2
if x == y:
    ans -= 1
print(ans)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include &amp;#x3C;iostream&gt;
#include &amp;#x3C;cstring&gt;
#include &amp;#x3C;algorithm&gt;
using namespace std;

typedef long long LL;
LL x,y,ans;

LL gcd(LL a, LL b){
  return b==0 ? a : gcd(b,a%b);
}
int main(){
    cin&gt;&gt;x&gt;&gt;y;
    LL t = x*y;
    for(LL i=1; i*i&amp;#x3C;=t; i++)
        if(t%i==0 &amp;#x26;&amp;#x26; gcd(i,t/i)==x)
          ans += 2;
    if(x==y) ans--;
    cout &amp;#x3C;&amp;#x3C; ans;
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>G5 线性筛质数</title><link>https://ehnotgod.site/blog/g5-%E7%BA%BF%E6%80%A7%E7%AD%9B%E8%B4%A8%E6%95%B0</link><guid isPermaLink="true">https://ehnotgod.site/blog/g5-%E7%BA%BF%E6%80%A7%E7%AD%9B%E8%B4%A8%E6%95%B0</guid><description>线性筛（欧拉筛）：O(n) 筛质数。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P3383&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;本题已更新，从判断素数改为了查询第 $k$ 小的素数。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;如题，给定一个范围 $n$，有 $q$ 个询问，每次输出第 $k$ 小的素数。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行包含两个正整数 $n,q$，分别表示查询的范围和查询的个数。&lt;/p&gt;
&lt;p&gt;接下来 $q$ 行每行一个正整数 $k$，表示查询第 $k$ 小的素数。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输出 $q$ 行，每行一个正整数表示答案。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;100 5
1
2
3
4
5
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2
3
5
7
11
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;说明/提示&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;【数据范围】&lt;br&gt;
对于 $100%$ 的数据，$n = 10^8$，$1 \le q \le 10^6$，保证查询的素数不大于 $n$。&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;欧拉筛（线性筛）：每个合数只被其最小质因子筛掉一次。枚举 $i$，若未被标记则是质数加入表；再枚举已有质数 $p$，标记 $i\cdot p$，当 $i%p==0$ 时 break（保证每个合数只被最小质因子筛）。$O(n)$ 得质数表后直接回答第 $k$ 小质数。&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;n, q = map(int, input().split())

# 使用欧拉筛（线性筛）来找出所有素数
vis = [True] * (n + 1)
prim = []
for i in range(2, n + 1):
    if vis[i]:
        prim.append(i)
    for p in prim:
        if i * p &gt; n:
            break
        vis[i * p] = False
        if i % p == 0:
            break

for _ in range(q):
    k = int(input())
    print(prim[k - 1])
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include &amp;#x3C;iostream&gt;
#include &amp;#x3C;cstring&gt;
#include &amp;#x3C;algorithm&gt;
using namespace std;

const int N = 100000010;
int vis[N];  //划掉合数
int prim[N]; //记录质数
int cnt; //质数个数

void get_prim(int n){ //线性筛法
  for(int i=2; i&amp;#x3C;=n; i++){
    if(!vis[i]) prim[++cnt] = i;
    for(int j=1; 1ll*i*prim[j]&amp;#x3C;=n; j++){
      vis[i*prim[j]] = 1;
      if(i%prim[j] == 0) break;
    }
  }
}
int main(){
    int n, q, k;
    scanf(&quot;%d %d&quot;, &amp;#x26;n, &amp;#x26;q);
    get_prim(n);
    while(q--){
        scanf(&quot;%d&quot;, &amp;#x26;k);
        printf(&quot;%d\n&quot;, prim[k]);
    }
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>G6 欧拉函数</title><link>https://ehnotgod.site/blog/g6-%E6%AC%A7%E6%8B%89%E5%87%BD%E6%95%B0</link><guid isPermaLink="true">https://ehnotgod.site/blog/g6-%E6%AC%A7%E6%8B%89%E5%87%BD%E6%95%B0</guid><description>欧拉函数：1~n 中与 n 互质的数个数。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/U629802&lt;/p&gt;
&lt;p&gt;欧拉函数就是小于 $x$ 的正整数中与 $x$ 互质的数的个数，一般用 $φ(x)$ 表示。特殊的，$φ(1)=1$。&lt;/p&gt;
&lt;p&gt;现在需要你求出 $1-n$ 的欧拉函数。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输入一行一个整数 $n$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输出一行 $n$ 个整数表示 $1-n$ 的欧拉函数，用空格分隔。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;10
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1 1 2 2 4 2 6 4 6 4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于 $100%$ 的数据，保证 $1≤n≤2×10^6$。&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;线性筛求欧拉函数：$\varphi(1)=1$；质数 $p$ 的 $\varphi(p)=p-1$；对合数 $m=i\cdot p$：若 $p\mid i$ 则 $\varphi(m)=p\cdot\varphi(i)$，否则 $\varphi(m)=(p-1)\cdot\varphi(i)$。用数组 $\varphi$ 记录并输出。复杂度 $O(n)$。&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import sys

def get_phi(n):
    phi = [0] * (n + 1)
    vis = [False] * (n + 1)
    primes = []
    phi[1] = 1
    for i in range(2, n + 1):
        if not vis[i]:
            primes.append(i)
            phi[i] = i - 1
        for p in primes:
            m = i * p
            if m &gt; n:
                break
            vis[m] = True
            if i % p == 0:
                # p | i
                phi[m] = p * phi[i]
                break
            else:
                phi[m] = (p - 1) * phi[i]
    return phi

data = sys.stdin.read().split()
n = int(data[0])
phi = get_phi(n)
# 输出 1..n
out = &apos; &apos;.join(str(phi[i]) for i in range(1, n + 1))
sys.stdout.write(out)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include &amp;#x3C;iostream&gt;
using namespace std;

const int N = 1000010;
int p[N], vis[N], cnt;
int phi[N];

void get_phi(int n){//筛法求欧拉函数
  phi[1] = 1;
  for(int i=2; i&amp;#x3C;=n; i++){
    if(!vis[i]){
      p[cnt++] = i;
      phi[i] = i-1;
    }
    for(int j=0; i*p[j]&amp;#x3C;=n; j++){
      int m = i*p[j];
      vis[m] = 1;
      if(i%p[j] == 0){
        phi[m] = p[j]*phi[i];
        break;
      }
      else
        phi[m]=(p[j]-1)*phi[i];
    }
  }
}
int main(){
  int n;
  cin &gt;&gt; n;
  get_phi(n);
  for(int i=1; i&amp;#x3C;=n; i++)
    printf(&quot;%d &quot;, phi[i]);
  return 0;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>G7 筛法求因数个数</title><link>https://ehnotgod.site/blog/g7-%E7%AD%9B%E6%B3%95%E6%B1%82%E5%9B%A0%E6%95%B0%E4%B8%AA%E6%95%B0</link><guid isPermaLink="true">https://ehnotgod.site/blog/g7-%E7%AD%9B%E6%B3%95%E6%B1%82%E5%9B%A0%E6%95%B0%E4%B8%AA%E6%95%B0</guid><description>线性筛求每个数的因数个数。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;给定一个正整数 $n$，请输出 $1\sim n$ 中每个数的约数个数 $d(i)$。&lt;/p&gt;
&lt;p&gt;例如 $n=6$ 时：$d(1)=1,d(2)=2,d(3)=2,d(4)=3,d(5)=2,d(6)=4$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输入一行一个正整数 $n$（$1\le n\le 10^6$）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输出一行 $n$ 个整数，第 $i$ 个整数表示 $d(i)$，相邻整数用一个空格隔开。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;6
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1 2 2 3 2 4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;说明/提示&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;$d(i)$ 表示 $i$ 的正因数个数，如 $d(6)=4$（因数为 $1,2,3,6$）。&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;线性筛求约数个数：额外维护 $a[i]$ 表示 $i$ 的最小质因子次数。质数 $d=2$；合数 $m=i\cdot p$：若 $p\mid i$ 则 $a[m]=a[i]+1,\ d[m]=d[i]/a[m]\cdot(a[m]+1)$，否则 $a[m]=1,\ d[m]=2d[i]$。复杂度 $O(n)$。&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;n = int(input())
p = [0] * (n + 1)
vis = [0] * (n + 1)
a = [0] * (n + 1)
d = [0] * (n + 1)
cnt = 0
d[1] = 1
for i in range(2, n + 1):
    if not vis[i]:
        cnt += 1; p[cnt] = i
        a[i] = 1; d[i] = 2
    for j in range(1, n + 1):
        if i * p[j] &gt; n:
            break
        m = i * p[j]
        vis[m] = 1
        if i % p[j] == 0:
            a[m] = a[i] + 1
            d[m] = d[i] // a[m] * (a[m] + 1)
            break
        else:
            a[m] = 1
            d[m] = d[i] * 2
for i in range(1, n + 1):
    print(d[i], end=&quot; &quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include &amp;#x3C;iostream&gt;
using namespace std;

const int N = 1000010;
int p[N], vis[N], cnt;
int a[N]; //a[i]记录i的最小质因子的次数
int d[N]; //d[i]记录i的约数个数

void get_d(int n){ //筛法求约数个数
  d[1] = 1;
  for(int i=2; i&amp;#x3C;=n; i++){
    if(!vis[i]){
      p[++cnt] = i;
      a[i] = 1;
      d[i] = 2;
    }
    for(int j=1; i*p[j]&amp;#x3C;=n; j++){
      int m = i*p[j];
      vis[m] = 1;
      if(i%p[j] == 0){
        a[m] = a[i]+1;
        d[m] = d[i]/a[m]*(a[m]+1);
        break;
      } 
      else{
        a[m] = 1;
        d[m] = d[i]*2;
      }
    }
  }
}
int main(){
  int n;
  cin &gt;&gt; n;
  get_d(n);
  for(int i=1; i&amp;#x3C;=n; i++)
    printf(&quot;%d\n&quot;, d[i]);
  return 0;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>G8 莫比乌斯函数</title><link>https://ehnotgod.site/blog/g8-%E8%8E%AB%E6%AF%94%E4%B9%8C%E6%96%AF%E5%87%BD%E6%95%B0</link><guid isPermaLink="true">https://ehnotgod.site/blog/g8-%E8%8E%AB%E6%AF%94%E4%B9%8C%E6%96%AF%E5%87%BD%E6%95%B0</guid><description>莫比乌斯函数：线性筛求 μ(n)。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/T320132&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;给定一个整数n，请依次输出μ(1)、μ(2)、...、μ(n)。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;读入一个n&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输出n行，第i行输出μ(i)的值&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1
-1
-1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;说明/提示&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;n&amp;#x3C;=1e7&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;线性筛求莫比乌斯函数：$\mu(1)=1$；质数 $\mu(p)=-1$；合数 $m=i\cdot p$：若 $p\mid i$ 则 $\mu(m)=0$（含平方因子），否则 $\mu(m)=-\mu(i)$。复杂度 $O(n)$，是莫比乌斯反演的基础。&lt;/p&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include &amp;#x3C;iostream&gt;
using namespace std;

const int N = 1000010;
int p[N], vis[N], cnt;
int mu[N];

void get_mu(int n){//筛法求莫比乌斯函数
  mu[1] = 1;
  for(int i=2; i&amp;#x3C;=n; i++){
    if(!vis[i]){
      p[++cnt] = i;
      mu[i] = -1;
    }
    for(int j=1; i*p[j]&amp;#x3C;=n; j++){
      int m = i*p[j]; 
      vis[m] = 1;
      if(i%p[j] == 0){
        mu[m] = 0;
        break;
      } 
      else
        mu[m] = -mu[i];
    }
  }
}
int main(){
  int n;
  cin &gt;&gt; n;
  get_mu(n);
  for(int i=1; i&amp;#x3C;=n; i++)
    printf(&quot;%d\n&quot;,mu[i]);
  return 0;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>G9 拓展欧拉定理-超大幂次取余</title><link>https://ehnotgod.site/blog/g9-%E6%8B%93%E5%B1%95%E6%AC%A7%E6%8B%89%E5%AE%9A%E7%90%86-%E8%B6%85%E5%A4%A7%E5%B9%82%E6%AC%A1%E5%8F%96%E4%BD%99</link><guid isPermaLink="true">https://ehnotgod.site/blog/g9-%E6%8B%93%E5%B1%95%E6%AC%A7%E6%8B%89%E5%AE%9A%E7%90%86-%E8%B6%85%E5%A4%A7%E5%B9%82%E6%AC%A1%E5%8F%96%E4%BD%99</guid><description>扩展欧拉定理：超大幂次取模降幂。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;本页为算法笔记。&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;扩展欧拉定理：当 $b\ge\varphi(m)$ 时 $a^b\equiv a^{b\bmod\varphi(m)+\varphi(m)}\pmod m$（$a$ 与 $m$ 互质时指数取 $b\bmod\varphi(m)$）。用于超大幂次取模降幂：先求 $\varphi(m)$，再把指数降下来快速幂。&lt;/p&gt;
&lt;p&gt;没啥好说的，这个只需要记住公式就行了。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/G9-1.png&quot; alt=&quot;image-20250808145039890&quot;&gt;&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>G10 拓展欧几里得-不定方程</title><link>https://ehnotgod.site/blog/g10-%E6%8B%93%E5%B1%95%E6%AC%A7%E5%87%A0%E9%87%8C%E5%BE%97-%E4%B8%8D%E5%AE%9A%E6%96%B9%E7%A8%8B</link><guid isPermaLink="true">https://ehnotgod.site/blog/g10-%E6%8B%93%E5%B1%95%E6%AC%A7%E5%87%A0%E9%87%8C%E5%BE%97-%E4%B8%8D%E5%AE%9A%E6%96%B9%E7%A8%8B</guid><description>扩展欧几里得：求解 ax+by=gcd(a,b)。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;给定 $a,b,c$，求解不定方程 $ax+by=c$ 的一组整数解。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;说明/提示&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;本页为算法笔记。&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;扩展欧几里得：递归求 $ax+by=\gcd(a,b)$ 的一组特解 $(x_0,y_0)$（$b=0$ 时 $x=1,y=0$）。若 $c$ 能被 $\gcd(a,b)$ 整除，则 $x=c/d\cdot x_0,\ y=c/d\cdot y_0$ 为解，否则无解。复杂度 $O(\log)$。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/G10-1.png&quot; alt=&quot;image-20250808145533135&quot;&gt;&lt;/p&gt;
&lt;p&gt;时间复杂度：$${O(logn)}$$&lt;/p&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include &amp;#x3C;iostream&gt;
#include &amp;#x3C;cstring&gt;
#include &amp;#x3C;algorithm&gt;
using namespace std;
    
int exgcd(int a,int b,int &amp;#x26;x,int &amp;#x26;y){
  if(b == 0) {x=1, y=0; return a;}
  int x1, y1, d;
  d = exgcd(b, a%b, x1, y1);
  x = y1, y = x1-a/b*y1;
  return d;
}
int main(){
  int a, b, c, x, y;
  cin &gt;&gt; a &gt;&gt; b &gt;&gt; c;
  int d = exgcd(a,b,x,y);
  if(c%d == 0) 
    printf(&quot;%d %d&quot;,c/d*x,c/d*y);
  else puts(&quot;none&quot;);
  return 0;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>G11 拓展欧几里得-乘法逆元</title><link>https://ehnotgod.site/blog/g11-%E6%8B%93%E5%B1%95%E6%AC%A7%E5%87%A0%E9%87%8C%E5%BE%97-%E4%B9%98%E6%B3%95%E9%80%86%E5%85%83</link><guid isPermaLink="true">https://ehnotgod.site/blog/g11-%E6%8B%93%E5%B1%95%E6%AC%A7%E5%87%A0%E9%87%8C%E5%BE%97-%E4%B9%98%E6%B3%95%E9%80%86%E5%85%83</guid><description>扩展欧几里得求乘法逆元。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;给定 $a,b,m$，求解同余方程 $ax\equiv b\pmod m$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;说明/提示&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;本页为算法笔记。&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;exgcd 求逆元：$ax\equiv b\pmod m$ 即 $ax+my=b$。用扩展欧几里得求 $ax+my=\gcd(a,m)$ 的特解，若 $b$ 能被 $\gcd$ 整除则 $x=b/d\cdot x_0\bmod m$ 为解，否则无解。当 $b=1$ 时即求 $a$ 的乘法逆元。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/G11-1.png&quot; alt=&quot;image-20250808150258351&quot;&gt;&lt;/p&gt;
&lt;p&gt;时间复杂度：$${O(logn)}$$&lt;/p&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include &amp;#x3C;iostream&gt;
#include &amp;#x3C;cstring&gt;
#include &amp;#x3C;algorithm&gt;
using namespace std;
    
int exgcd(int a,int b,int &amp;#x26;x,int &amp;#x26;y){
  if(b == 0){x = 1, y = 0; return a;}
  int x1, y1, d;
  d = exgcd(b, a%b, x1, y1);
  x = y1, y = x1-a/b*y1;
  return d;
}
int main(){
  int a, b, m, x, y;
  scanf(&quot;%d%d%d&quot;, &amp;#x26;a, &amp;#x26;b, &amp;#x26;m);
  int d = exgcd(a, m, x, y);
  if(b%d == 0) 
    printf(&quot;%d&quot;, 1ll*x*b/d%m);
  else puts(&quot;none&quot;);
  return 0;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>G12 扩展中国剩余定理</title><link>https://ehnotgod.site/blog/g12-%E6%89%A9%E5%B1%95%E4%B8%AD%E5%9B%BD%E5%89%A9%E4%BD%99%E5%AE%9A%E7%90%86</link><guid isPermaLink="true">https://ehnotgod.site/blog/g12-%E6%89%A9%E5%B1%95%E4%B8%AD%E5%9B%BD%E5%89%A9%E4%BD%99%E5%AE%9A%E7%90%86</guid><description>扩展中国剩余定理：模数不互质的同余方程组。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P4777&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;给定 $n$ 组非负整数 $a_i, b_i$ ，求解关于 $x$ 的方程组的最小非负整数解。
$$\begin{cases}x\equiv b_1\pmod{a_1}\x\equiv b_2\pmod{a_2}\\dots\x\equiv b_n\pmod{a_n}\end{cases}$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输入第一行包含整数 $n$。&lt;/p&gt;
&lt;p&gt;接下来 $n$ 行，每行两个非负整数 $a_i, b_i$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输出一行，为满足条件的最小非负整数 $x$。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;3
11 6
25 9
33 17

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;809
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;说明/提示&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;对于 $100 %$ 的数据，$1 \le n \le {10}^5$，$1 \le a_i \le {10}^{12}$，$0\leq b_i \leq 10^{12}$，保证所有 $a_i$ 的最小公倍数不超过 ${10}^{18}$。&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;images/G12-1.png&quot; alt=&quot;image-20250808181543528&quot;&gt;&lt;/p&gt;
&lt;p&gt;扩展中国剩余定理（EXCRT）：模数不互质时两两合并。对 $x\equiv r_1\pmod{m_1}$ 与 $x\equiv r_2\pmod{m_2}$，解 $m_1p+m_2q=\gcd$，若 $(r_2-r_1)%d\ne0$ 无解；否则得特解并合并为 $x\equiv r&apos;\pmod{\operatorname{lcm}(m_1,m_2)}$。逐对合并到只剩一个同余式。&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def exgcd(a, b):
    if b == 0:
        return a, 1, 0
    d, x1, y1 = exgcd(b, a % b)
    x = y1
    y = x1 - a // b * y1
    return d, x, y

def EXCRT(m, r):
    m1 = m[1]
    r1 = r[1]
    for i in range(2, n + 1):
        m2 = m[i]
        r2 = r[i]
        d, p, q = exgcd(m1, m2)
        # 不可整除则无解
        if (r2 - r1) % d != 0:
            return -1
        # 求一个特解并取模
        p = p * ((r2 - r1) // d)
        p = p % (m2 // d)
        r1 = m1 * p + r1
        m1 = m1 * (m2 // d)
    return r1 % m1

if __name__ == &quot;__main__&quot;:
    n = int(input().strip())
    m = [0] * (n + 1)
    r = [0] * (n + 1)
    for i in range(1, n + 1):
        mi, ri = map(int, input().split())
        m[i] = mi
        r[i] = ri
    print(EXCRT(m, r))
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include &amp;#x3C;iostream&gt;
#include &amp;#x3C;cstring&gt;
#include &amp;#x3C;algorithm&gt;
using namespace std;

typedef __int128 LL;
const int N = 100005;
LL n, m[N], r[N];

LL exgcd(LL a,LL b,LL &amp;#x26;x,LL &amp;#x26;y){
  if(b==0){x=1, y=0; return a;}
  LL d, x1, y1;
  d = exgcd(b, a%b, x1, y1);
  x = y1, y = x1-a/b*y1;
  return d;
}
LL EXCRT(LL m[], LL r[]){
  LL m1, m2, r1, r2, p, q;
  m1 = m[1], r1 = r[1];
  for(int i=2; i&amp;#x3C;=n; i++){
    m2 = m[i], r2 = r[i];
    LL d = exgcd(m1,m2,p,q);
    if((r2-r1)%d){return -1;}
    p=p*(r2-r1)/d; //特解
    p=(p%(m2/d)+m2/d)%(m2/d);
    r1 = m1*p+r1;
    m1 = m1*m2/d;
  }
  return (r1%m1+m1)%m1;
}
int main(){
  scanf(&quot;%lld&quot;, &amp;#x26;n);
  for(int i = 1; i &amp;#x3C;= n; ++i)
    scanf(&quot;%lld%lld&quot;, m+i, r+i);
  printf(&quot;%lld\n&quot;,EXCRT(m,r));
  return 0;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>G13 扩展BSGS算法</title><link>https://ehnotgod.site/blog/g13-%E6%89%A9%E5%B1%95bsgs%E7%AE%97%E6%B3%95</link><guid isPermaLink="true">https://ehnotgod.site/blog/g13-%E6%89%A9%E5%B1%95bsgs%E7%AE%97%E6%B3%95</guid><description>扩展 BSGS：模数不互质时的离散对数。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P4195&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;给定 $a,p,b$，求满足 $a^x≡b \pmod p$ 的最小自然数 $x$ 。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;每个测试文件中包含若干组测试数据，保证 $\sum \sqrt p\le 5\times 10^6$。&lt;/p&gt;
&lt;p&gt;每组数据中，每行包含 $3$ 个正整数 $a,p,b$ 。&lt;/p&gt;
&lt;p&gt;当 $a=p=b=0$ 时，表示测试数据读入结束。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;对于每组数据，输出一行。&lt;/p&gt;
&lt;p&gt;如果无解，输出 &lt;code&gt;No Solution&lt;/code&gt;，否则输出最小自然数解。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;5 58 33
2 4 3
0 0 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;9
No Solution
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于 $100%$ 的数据，$1\le a,p,b≤10^9$ 或 $a=p=b=0$。&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;扩展 BSGS：模数不互质时，先不断除去 $\gcd(a,p)$（每除一次指数加 1），化为 $A\cdot a^{x-k}\equiv b&apos;\pmod{p&apos;}$ 且互质，再套用标准 BSGS 分块：$m=\lceil\sqrt p\rceil$，baby step 存 $b&apos;\cdot a^j$，giant step 查 $a^{im}$，拼接得 $x$。无解返回 -1。&lt;/p&gt;
&lt;p&gt;时间复杂度：$${O(\sqrt{p})}$$&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import math

def gcd(a, b):
    return a if b == 0 else gcd(b, a % b)

def exbsgs(a, b, p):
    a %= p
    b %= p
    if b == 1 or p == 1:
        return 0  # x = 0

    # 处理 gcd 除掉公共因子
    k = 0
    A = 1 % p
    while True:
        d = gcd(a, p)
        if d == 1:
            break
        if b % d != 0:
            return -1
        k += 1
        b //= d
        p //= d
        A = (A * (a // d)) % p
        if A == b:
            return k

    # baby-step giant-step
    m = math.isqrt(p)
    if m * m &amp;#x3C; p:
        m += 1

    t = b % p
    table = {t: 0}
    for j in range(1, m):
        t = (t * a) % p
        table[t] = j

    mi = pow(a, m, p)  # a^m % p
    t = A % p
    for i in range(1, m + 1):
        t = (t * mi) % p
        if t in table:
            return i * m - table[t] + k

    return -1

# 使用 input() 逐个 token 读取（可处理不同换行/空格的输入格式）
def tokens():
    while True:
        try:
            for tok in input().split():
                yield tok
        except EOFError:
            return

if __name__ == &quot;__main__&quot;:
    it = tokens()
    while True:
        try:
            a = int(next(it))
            p = int(next(it))
            b = int(next(it))
        except StopIteration:
            break
        if a == 0:
            break
        res = exbsgs(a, b, p)
        if res == -1:
            print(&quot;No Solution&quot;)
        else:
            print(res)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include &amp;#x3C;iostream&gt;
#include &amp;#x3C;cstring&gt;
#include &amp;#x3C;algorithm&gt;
#include &amp;#x3C;cmath&gt;
#include &amp;#x3C;unordered_map&gt;
using namespace std;

typedef long long LL;

LL gcd(LL a, LL b){
  return b==0?a:gcd(b,a%b);
}
LL exbsgs(LL a, LL b, LL p){
  a %= p; b %= p;
  if(b==1||p==1)return 0;//x=0

  LL d, k=0, A=1;
  while(true){
    d = gcd(a,p);
    if(d==1) break;
    if(b%d) return -1; //无解
    k++; b/=d; p/=d;
    A = A*(a/d)%p; //求a^k/D
    if(A==b) return k;
  }

  LL m=ceil(sqrt(p));
  LL t = b;
  unordered_map&amp;#x3C;int,int&gt; hash;
  hash[b] = 0;
  for(int j = 1; j &amp;#x3C; m; j++){
    t = t*a%p; //求b*a^j
    hash[t] = j;
  }
  LL mi = 1;
  for(int i = 1; i &amp;#x3C;= m; i++)
    mi = mi*a%p; //求a^m
  t = A;
  for(int i=1; i &amp;#x3C;= m; i++){
    t = t*mi%p; //求(a^m)^i
    if(hash.count(t))
      return i*m-hash[t]+k;
  }
  return -1; //无解
}
int main(){
  LL a, p, b;
  while((scanf(&quot;%lld%lld%lld&quot;,&amp;#x26;a,&amp;#x26;p,&amp;#x26;b)!=EOF)&amp;#x26;&amp;#x26;a){
    LL res = exbsgs(a, b, p);
    if(res == -1) puts(&quot;No Solution&quot;);
    else printf(&quot;%lld\n&quot;,res);
  }
  return 0;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>G14 高斯消元法</title><link>https://ehnotgod.site/blog/g14-%E9%AB%98%E6%96%AF%E6%B6%88%E5%85%83%E6%B3%95</link><guid isPermaLink="true">https://ehnotgod.site/blog/g14-%E9%AB%98%E6%96%AF%E6%B6%88%E5%85%83%E6%B3%95</guid><description>高斯消元：解线性方程组。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P3389&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;给定一个线性方程组，对其求解。&lt;/p&gt;
&lt;p&gt;$$ \begin{cases} a_{1, 1} x_1 + a_{1, 2} x_2 + \cdots + a_{1, n} x_n = b_1 \ a_{2, 1} x_1 + a_{2, 2} x_2 + \cdots + a_{2, n} x_n = b_2 \ \cdots \ a_{n,1} x_1 + a_{n, 2} x_2 + \cdots + a_{n, n} x_n = b_n \end{cases}$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行，一个正整数 $$n$$。&lt;/p&gt;
&lt;p&gt;第二至 $$n+1$$ 行，每行 $$n+1$$ 个整数，为 $$ a_1, a_2, \dots ,a_n$$ 和 $$b$$，代表一组方程。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;共 $$n$$ 行，每行一个数，第 $$i$$ 行为 $$x_i$$（四舍五入保留 $$2$$ 位小数）。&lt;/p&gt;
&lt;p&gt;如果不存在唯一解或无解，在第一行输出 &lt;code&gt;No Solution&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;输入&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;3
1 3 4 5
1 4 7 3
9 3 2 2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-0.97
5.18
-2.39
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;说明/提示&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;数据范围：$$1 \leq n \leq 100, \left | a_i \right| \leq {10}^4 , \left |b \right| \leq {10}^4 $$。保证数据若有解则所有解均满足 $$|x_i|\le 10^3$$，且 $$x_i\pm 10^{-6}$$ 和 $$x_i$$ 四舍五入后的结果相同（即不会因为较小的精度误差导致四舍五入后的结果不同）。&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;高斯消元：逐列选主元（当前列绝对值最大），与当前行交换，主元行归一化，再用行变换消去下方行的当前列；回代求各变量。若某列找不到非零主元且常数项非零则无解（No Solution），否则唯一解。复杂度 $O(n^3)$。&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;
def gauss():
    c, r = 0, 0
    for c in range(n):
        t = r
        for i in range(r, n):
            if abs(a[i][c]) &gt; abs(a[t][c]):
                t = i
        if abs(a[t][c]) &amp;#x3C; 1e-6:
            continue
        for i in range(c, n + 1):
            a[t][i], a[r][i] = a[r][i], a[t][i]
        for i in range(n, c - 1, -1):
            a[r][i] /= a[r][c]
        for i in range(r + 1, n):
            if abs(a[i][c]) &gt; 1e-6:
                for j in range(n, c - 1, -1):
                    a[i][j] -= a[i][c] * a[r][j]
        r += 1
    if r &amp;#x3C; n:
        for i in range(r, n):
            if abs(a[i][n]) &gt; 1e-6:
                return 2
        return 1
    for i in range(n - 1, -1, -1):
        for j in range(i + 1, n):
            a[i][n] -= a[i][j] * a[j][n]
    return 0

n = int(input())
a = []
for i in range(n):
    a.append(list(map(float, input().split())))
t = gauss()
if t:
    print(&quot;No Solution&quot;)
else:
    for i in range(n):
        print(&quot;{:.2f}&quot;.format(a[i][n]))
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include &amp;#x3C;iostream&gt;
#include &amp;#x3C;algorithm&gt;
#include &amp;#x3C;cmath&gt;
using namespace std;

const int N=110;
const double eps=1e-6;
int n;
double a[N][N]; //增广矩阵

int gauss(){
  int c,r;//当前列，当前行
  for(c=r=0; c&amp;#x3C;n; c++){
    //1.找到c列的最大行t
    int t=r;
    for(int i=r; i&amp;#x3C;n; i++)
      if(fabs(a[i][c])&gt;fabs(a[t][c])) t=i;
    if(fabs(a[t][c])&amp;#x3C;eps) continue; //c列已0化

    //2.把最大行换到上面
    for(int i=c; i&amp;#x3C;n+1; i++)swap(a[t][i],a[r][i]);

    //3.把当前行r的第一个数，变成1
    for(int i=n; i&gt;=c; i--)a[r][i]/=a[r][c];

    //4.把当前列c下面的所有数，全部消成0
    for(int i=r+1; i&amp;#x3C;n; i++)
      if(fabs(a[i][c])&gt;eps)
        for(int j=n; j&gt;=c; j--)
          a[i][j]-=a[i][c]*a[r][j];
    r++; //这一行的工作做完，换下一行
  }
  if(r&amp;#x3C;n){ //说明已经提前变成梯形矩阵
    for(int i=r; i&amp;#x3C;n; i++)
      if(fabs(a[i][n])&gt;eps) //a[i][n]==bi
        return 2; //左边=0，右边≠0,无解
    return 1; //0==0，无穷多解
  }
  //5.唯一解，从下往上回代，得到方程的解
  for(int i=n-1; i&gt;=0; i--)
    for(int j=i+1; j&amp;#x3C;n; j++)
      a[i][n]-=a[i][j]*a[j][n];
  return 0;
}
int main(){
  cin &gt;&gt; n;
  for(int i=0; i&amp;#x3C;n; i++)
    for(int j=0; j&amp;#x3C;=n; j++)
      cin &gt;&gt; a[i][j];
  int t=gauss();
  if(t) puts(&quot;No Solution&quot;);
  else for(int i=0; i&amp;#x3C;n; i++)
        printf(&quot;%.2lf\n&quot;,a[i][n]);
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>G15 矩阵求逆-高斯约旦消元法</title><link>https://ehnotgod.site/blog/g15-%E7%9F%A9%E9%98%B5%E6%B1%82%E9%80%86-%E9%AB%98%E6%96%AF%E7%BA%A6%E6%97%A6%E6%B6%88%E5%85%83%E6%B3%95</link><guid isPermaLink="true">https://ehnotgod.site/blog/g15-%E7%9F%A9%E9%98%B5%E6%B1%82%E9%80%86-%E9%AB%98%E6%96%AF%E7%BA%A6%E6%97%A6%E6%B6%88%E5%85%83%E6%B3%95</guid><description>高斯-约旦消元求矩阵逆。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P4783&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;求一个 $N\times N$ 的矩阵的逆矩阵。答案对 ${10}^9+7$ 取模。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行有一个整数 $N$，代表矩阵的大小；&lt;/p&gt;
&lt;p&gt;接下来 $N$ 行，每行 $N$ 个整数，其中第 $i$ 行第 $j$ 列的数代表矩阵中的元素 $a_{i j}$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;若矩阵可逆，则输出 $N$ 行，每行 $N$ 个整数，其中第 $i$ 行第 $j$ 列的数代表逆矩阵中的元素 $b_{i j}$，答案对 ${10}^9+7$ 取模；&lt;/p&gt;
&lt;p&gt;否则只输出一行 &lt;code&gt;No Solution&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;3
1 2 8
2 5 6
5 1 2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;718750005 718750005 968750007
171875001 671875005 296875002
117187501 867187506 429687503
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输入 #2&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;3
3 2 4
7 2 9
2 4 3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #2&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;No Solution
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对 $100 %$ 的数据有 $N\le 400$，所有 $0 \le a_{i j} &amp;#x3C; {10}^9 + 7$。&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;高斯-约旦消元求逆：在 $A$ 右侧拼单位矩阵成 $[A\mid I]$，逐列选非零主元（$\bmod$ 下用逆元），把主元行归一化并消去&lt;strong&gt;其他所有行&lt;/strong&gt;的当前列，使左侧变为单位矩阵，右侧即 $A^{-1}$。主元为 0 则不可逆。复杂度 $O(n^3)$。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/G15-1.png&quot; alt=&quot;image-20251113091739495&quot;&gt;&lt;/p&gt;
&lt;p&gt;时间复杂度：$$O(n^3)$$&lt;/p&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include&amp;#x3C;iostream&gt;
#include&amp;#x3C;cstdio&gt;
#include&amp;#x3C;cmath&gt;
#define LL long long
using namespace std;

const int N=405,P=1e9+7;
int n;
LL a[N][N&amp;#x3C;&amp;#x3C;1];

LL quickpow(LL a, LL b){
  LL ans = 1;
  while(b){
    if(b &amp;#x26; 1) ans = ans*a%P;
    a = a*a%P;
    b &gt;&gt;= 1;
  }
  return ans;
}
bool Gauss_Jordan(){    
  for(int i=1;i&amp;#x3C;=n;++i){ //枚举主元的行列
    int r = i;
    for(int k=i; k&amp;#x3C;=n; ++k) //找非0行
      if(a[k][i]) {r=k; break;}
    if(r!=i) swap(a[r],a[i]); //换行
    if(!a[i][i]) return 0;  
    
    int x=quickpow(a[i][i],P-2); //求逆元
    for(int k=1; k&amp;#x3C;=n; ++k){ //对角化
      if(k == i) continue;
      int t=a[k][i]*x%P;
      for(int j=i; j&amp;#x3C;=2*n; ++j) 
        a[k][j]=((a[k][j]-t*a[i][j])%P+P)%P;
    } 
    for(int j=1; j&amp;#x3C;=2*n; ++j) //除以主元
      a[i][j]=(a[i][j]*x%P);
  }
  return 1;
}
int main(){
  scanf(&quot;%d&quot;,&amp;#x26;n);
  for(int i=1; i&amp;#x3C;=n; ++i)
    for(int j=1; j&amp;#x3C;=n; ++j)
      scanf(&quot;%lld&quot;,&amp;#x26;a[i][j]),a[i][i+n]=1;
  if(Gauss_Jordan())
    for(int i=1; i&amp;#x3C;=n; ++i){
      for(int j=n+1; j&amp;#x3C;=2*n; ++j) 
        printf(&quot;%lld &quot;,a[i][j]);
      puts(&quot;&quot;);
    }
  else puts(&quot;No Solution&quot;);
  return 0;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>G16 求组合数-线性逆推</title><link>https://ehnotgod.site/blog/g16-%E6%B1%82%E7%BB%84%E5%90%88%E6%95%B0-%E7%BA%BF%E6%80%A7%E9%80%86%E6%8E%A8</link><guid isPermaLink="true">https://ehnotgod.site/blog/g16-%E6%B1%82%E7%BB%84%E5%90%88%E6%95%B0-%E7%BA%BF%E6%80%A7%E9%80%86%E6%8E%A8</guid><description>组合数：线性递推求逆元计算 C(n,k)。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;求组合数 $C(n,k)$（模 $10^9+7$）。&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;线性逆推求组合数：预处理阶乘 $fac$ 与逆阶乘 $inv$。$fac[n]$ 用快速幂求逆得 $inv[n]$，再倒推 $inv[i-1]=inv[i]\cdot i$。则 $C(n,k)=fac[n]\cdot inv[k]\cdot inv[n-k]\bmod p$。查询 $O(1)$，预处理 $O(N)$。&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# 逆推法（阶乘 + 快速幂 + 模逆）
MOD = 10**9 + 7
N = 10**6  # 最大 n

# 预处理阶乘和逆阶乘
fac = [1] * (N + 1)
inv = [1] * (N + 1)

# 计算阶乘
for i in range(1, N + 1):
    fac[i] = fac[i - 1] * i % MOD

# 快速幂
def qpow(a, b):
    res = 1
    while b:
        if b &amp;#x26; 1:
            res = res * a % MOD
        a = a * a % MOD
        b &gt;&gt;= 1
    return res

# 计算逆阶乘
inv[N] = qpow(fac[N], MOD - 2)
for i in range(N, 0, -1):
    inv[i - 1] = inv[i] * i % MOD

# 组合数函数
def C(n, k):
    if k &amp;#x3C; 0 or k &gt; n:
        return 0
    return fac[n] * inv[k] % MOD * inv[n - k] % MOD

# 使用：
print(C(10, 3))  # 输出 120
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>G17 求组合数-卢卡斯</title><link>https://ehnotgod.site/blog/g17-%E6%B1%82%E7%BB%84%E5%90%88%E6%95%B0-%E5%8D%A2%E5%8D%A1%E6%96%AF</link><guid isPermaLink="true">https://ehnotgod.site/blog/g17-%E6%B1%82%E7%BB%84%E5%90%88%E6%95%B0-%E5%8D%A2%E5%8D%A1%E6%96%AF</guid><description>Lucas 定理：大组合数取模。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P3807&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;给定整数 $n, m, p$ 的值，求出 $C_{n + m}^n \bmod p$ 的值。&lt;/p&gt;
&lt;p&gt;输入数据保证 $p$ 为质数。&lt;/p&gt;
&lt;p&gt;注: $C$ 表示组合数。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行一个整数 $T$，表示数据组数。&lt;/p&gt;
&lt;p&gt;对于每组数据:&lt;/p&gt;
&lt;p&gt;一行，三个整数 $n, m, p$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;对于每组数据，输出一行，一个整数，表示所求的值。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2
1 2 5
2 1 5
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;3
3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;说明/提示&lt;/p&gt;
&lt;p&gt;对于 $100%$ 的数据，$1 \leq n, m, p \leq 10^5$，$1 \leq T \leq 10$。&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;Lucas 定理：$C(n,k)\equiv C(n/p,k/p)\cdot C(n%p,k%p)\pmod p$（$p$ 为素数）。把 $n,k$ 按 $p$ 进制拆分逐位求组合数相乘。复杂度 $O(p\log_p n)$，适合 $p$ 较小而 $n,k$ 很大的情况。本篇为公式截图笔记，代码待补充。&lt;/p&gt;
&lt;h3&gt;c++代码&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include &amp;#x3C;iostream&gt;
using namespace std;

typedef long long LL;
const int N = 100010;
LL f[N], g[N];

LL qpow(LL a, int b, int p){
  LL res = 1;
  while(b){
    if(b &amp;#x26; 1) res=res*a%p;
    a = a*a%p;
    b &gt;&gt;= 1;
  }
  return res;
}
void init(int p){
  f[0] = g[0] = 1;
  for(int i=1; i&amp;#x3C;=p; i++){
    f[i] = f[i-1]*i%p;
    g[i] = g[i-1]*qpow(i,p-2,p)%p;
  }   
}
LL getC(int n, int m, int p){
  return f[n]*g[m]*g[n-m]%p;
}
int lucas(LL n, LL m, int p){
  if(m==0) return 1;
  return lucas(n/p,m/p,p)*getC(n%p,m%p,p)%p;
}
int main(){
  int q, n, m, p;
  cin &gt;&gt; q;
  while(q--){
    cin &gt;&gt; n &gt;&gt; m &gt;&gt; p;
    init(p);
    printf(&quot;%d\n&quot;,lucas(n+m,n,p));
  }
  return 0;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>G18 卡特兰数</title><link>https://ehnotgod.site/blog/g18-%E5%8D%A1%E7%89%B9%E5%85%B0%E6%95%B0</link><guid isPermaLink="true">https://ehnotgod.site/blog/g18-%E5%8D%A1%E7%89%B9%E5%85%B0%E6%95%B0</guid><description>卡特兰数：入栈出栈 / 括号匹配等计数。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P1044&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/5qxy9fz2.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;宁宁考虑的是这样一个问题：一个操作数序列，$1,2,\ldots ,n$（图示为 1 到 3 的情况），栈 A 的深度大于 $n$。&lt;/p&gt;
&lt;p&gt;现在可以进行两种操作，&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;将一个数，从操作数序列的头端移到栈的头端（对应数据结构栈的 push 操作）&lt;/li&gt;
&lt;li&gt;将一个数，从栈的头端移到输出序列的尾端（对应数据结构栈的 pop 操作）&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;使用这两种操作，由一个操作数序列就可以得到一系列的输出序列，下图所示为由 &lt;code&gt;1 2 3&lt;/code&gt; 生成序列 &lt;code&gt;2 3 1&lt;/code&gt; 的过程。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/8uwv2pa2.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;（原始状态如上图所示）&lt;/p&gt;
&lt;p&gt;你的程序将对给定的 $n$，计算并输出由操作数序列 $1,2,\ldots,n$ 经过操作可能得到的输出序列的总数。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输入文件只含一个整数 $n$（$1 \leq n \leq 18$）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输出文件只有一行，即可能输出序列的总数目。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;3

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;5

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;卡特兰数：$Cat_n=\frac{1}{n+1}C(2n,n)=\sum_{i=0}^{n-1}Cat_i\cdot Cat_{n-1-i}$，$Cat_0=1$。计数对象包括：$n$ 对括号合法序列、$n$ 个元素入栈出栈序列、$n$ 个节点二叉树形态、凸多边形三角剖分数等。递推式 $Cat_{n+1}=\frac{4n+2}{n+2}Cat_n$。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/G18-1.png&quot; alt=&quot;image-20250808223834012&quot;&gt;&lt;/p&gt;
&lt;h3&gt;C++代码&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include &amp;#x3C;iostream&gt;
using namespace std;

int n;
long long f[20];

int main(){
  cin &gt;&gt; n;
  f[0] = 1;
  for(int i = 1; i &amp;#x3C;= n; i++) 
    f[i] = f[i-1]*(4*i-2)/(i+1);
  cout &amp;#x3C;&amp;#x3C; f[n] &amp;#x3C;&amp;#x3C; endl;
  return 0;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>G19 整除分块</title><link>https://ehnotgod.site/blog/g19-%E6%95%B4%E9%99%A4%E5%88%86%E5%9D%97</link><guid isPermaLink="true">https://ehnotgod.site/blog/g19-%E6%95%B4%E9%99%A4%E5%88%86%E5%9D%97</guid><description>整除分块：对 ⌊n/i⌋ 相同的区间合并计算。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P2261&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;给出正整数 $n$ 和 $k$，请计算&lt;/p&gt;
&lt;p&gt;$$G(n, k) = \sum_{i = 1}^n k \bmod i$$&lt;/p&gt;
&lt;p&gt;其中 $k\bmod i$ 表示 $k$ 除以 $i$ 的余数。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输入只有一行两个整数，分别表示 $n$ 和 $k$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输出一行一个整数表示答案。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;10 5

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;29
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于 $100%$ 的数据，保证 $1 \leq n, k \leq 10^9$。&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;整除分块：$\lfloor n/i\rfloor$ 的取值只有 $O(\sqrt n)$ 段，段 $[l,r]$ 内值相同，其中 $r=\lfloor n/\lfloor n/l\rfloor\rfloor$。对形如 $\sum_{i=1}^n f(i)\cdot\lfloor n/i\rfloor$ 的求和，可按段累加 $f$ 的区间和。复杂度 $O(\sqrt n)$。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/G19-1.png&quot; alt=&quot;image-20250808224555369&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/G19-2.png&quot; alt=&quot;image-20250808224637154&quot;&gt;&lt;/p&gt;
&lt;h3&gt;c++代码&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include&amp;#x3C;cstdio&gt;
#include&amp;#x3C;algorithm&gt;
using namespace std;

typedef long long LL;

int main(){
  LL n, k, l, r, res;
  scanf(&quot;%lld%lld&quot;, &amp;#x26;n, &amp;#x26;k);
  res = n*k;
  for(l=1; l&amp;#x3C;=n; l=r+1){
    if(k/l == 0) break;
    r = min(k/(k/l),n); 
    res -= (k/l)*(r-l+1)*(l+r)/2;
  }
  printf(&quot;%lld&quot;, res);
  return 0;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>G20 NTT-多项式乘法</title><link>https://ehnotgod.site/blog/g20-ntt-%E5%A4%9A%E9%A1%B9%E5%BC%8F%E4%B9%98%E6%B3%95</link><guid isPermaLink="true">https://ehnotgod.site/blog/g20-ntt-%E5%A4%9A%E9%A1%B9%E5%BC%8F%E4%B9%98%E6%B3%95</guid><description>NTT：数论变换做多项式乘法。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P3803&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;给定一个 $n$ 次多项式 $F(x)$，和一个 $m$ 次多项式 $G(x)$。&lt;/p&gt;
&lt;p&gt;请求出 $F(x)$ 和 $G(x)$ 的乘积。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行两个整数 $n,m$。&lt;/p&gt;
&lt;p&gt;接下来一行 $n+1$ 个整数，从低到高表示 $F(x)$ 的系数。&lt;/p&gt;
&lt;p&gt;接下来一行 $m+1$ 个整数，从低到高表示 $G(x)$ 的系数。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;一行 $n+m+1$ 个整数，从低到高表示 $F(x) \cdot G(x)$ 的系数。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1 2
1 2
1 2 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1 4 5 2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;保证输入中的系数均为大于等于 $0$ 且小于等于 $9$ 的整数。&lt;/p&gt;
&lt;p&gt;对于 $100%$ 的数据：$0 \le n, m \leq {10}^6$。&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;NTT 数论变换：用模素数 $P=998244353$ 的原根 $g=3$ 替代复数单位根做 FFT，避免浮点误差，结果取模。实现与 FFT 相同（位逆序 + 蝶形），逆变换时用 $g$ 的逆元。$O(n\log n)$。&lt;/p&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;// 迭代版 1.5s
#include &amp;#x3C;iostream&gt;
#include &amp;#x3C;cstring&gt;
#include &amp;#x3C;algorithm&gt;
#include &amp;#x3C;cmath&gt;
#define LL long long
using namespace std;
const int N=4e6;
const int g=3,P=998244353;
int n,m,R[N],gi,ni;
LL A[N],B[N];

LL qpow(LL a,LL b){
  LL ans=1;
  for(;b; a=a*a%P,b&gt;&gt;=1)
    if(b&amp;#x26;1) ans=ans*a%P;
  return ans;
}
void NTT(LL A[],int n,int op){
  for(int i=0; i&amp;#x3C;n; ++i)
    R[i]=R[i/2]/2+((i&amp;#x26;1)?n/2:0);      
  for(int i=0; i&amp;#x3C;n; ++i)
    if(i&amp;#x3C;R[i]) swap(A[i],A[R[i]]);
  for(int i=2; i&amp;#x3C;=n; i&amp;#x3C;&amp;#x3C;=1){
    LL g1=qpow(op==1?g:gi,(P-1)/i);
    for(int j=0; j&amp;#x3C;n; j+=i){
      LL gk=1;
      for(int k=j; k&amp;#x3C;j+i/2; ++k){
        LL x=A[k], y=gk*A[k+i/2]%P;
        A[k]=(x+y)%P;A[k+i/2]=(x-y+P)%P;
        gk=gk*g1%P;
      }
    }
  }
}
int main(){
  scanf(&quot;%d%d&quot;,&amp;#x26;n,&amp;#x26;m);
  for(int i=0;i&amp;#x3C;=n;++i)scanf(&quot;%lld&quot;,A+i);
  for(int i=0;i&amp;#x3C;=m;++i)scanf(&quot;%lld&quot;,B+i);
  for(m=n+m,n=1; n&amp;#x3C;=m; n&amp;#x3C;&amp;#x3C;=1);
  gi=qpow(g,P-2); ni=qpow(n,P-2);
  NTT(A,n,1); NTT(B,n,1);
  for(int i=0;i&amp;#x3C;n;++i)A[i]=A[i]*B[i]%P;
  NTT(A,n,-1);
  for(int i=0;i&amp;#x3C;=m;++i)
    printf(&quot;%d &quot;,A[i]*ni%P);
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>G21 第一类斯特林数</title><link>https://ehnotgod.site/blog/g21-%E7%AC%AC%E4%B8%80%E7%B1%BB%E6%96%AF%E7%89%B9%E6%9E%97%E6%95%B0</link><guid isPermaLink="true">https://ehnotgod.site/blog/g21-%E7%AC%AC%E4%B8%80%E7%B1%BB%E6%96%AF%E7%89%B9%E6%9E%97%E6%95%B0</guid><description>第一类斯特林数：圆排列计数。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;小 Z 是一个很有名的建筑师，有一天他接到了一个很奇怪的任务：在数轴上建 $n$ 个建筑，每个建筑的高度是 $1$ 到 $n$ 之间的一个整数。&lt;/p&gt;
&lt;p&gt;小 Z 有很严重的强迫症，他不喜欢有两个建筑的高度相同。另外小 Z 觉得如果从最左边（所有建筑都在右边）看能看到 $A$ 个建筑，从最右边（所有建筑都在左边）看能看到 $B$ 个建筑，这样的建筑群有着独特的美感。现在，小 Z 想知道满足上述所有条件的建筑方案有多少种？&lt;/p&gt;
&lt;p&gt;如果建筑 $i$ 的左(右)边没有任何建筑比它高，则建筑 $i$ 可以从左(右)边看到。两种方案不同，当且仅当存在某个建筑在两种方案下的高度不同。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行一个整数 $T$，代表 $T$ 组数据。
接下来 $T$ 行，每行三个整数 $n,A,B$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;对于每组数据输出一行答案 $\text{mod } 10^9+7$。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2
3 2 2
3 1 2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2
1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于 $100 %$ 的数据 ：$1 \leq n \leq 50000, \ 1 \leq A, B \leq 100, \ 1 \leq T \leq 200000$。&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;第一类斯特林数 $s(n,k)$ 表示把 $n$ 个不同元素分成 $k$ 个非空圆排列的方案数。递推式：$s(n,k)=s(n-1,k-1)+(n-1)s(n-1,k)$——新元素单独成一个圆排列，或插入已有 $n-1$ 个元素的任意一个圆排列中（$n-1$ 种插入位置）。边界 $s(0,0)=1,\ s(n,0)=0\ (n&gt;0)$。&lt;/p&gt;
&lt;p&gt;打表找到规律后直接使用。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/G21-1.png&quot; alt=&quot;image-20250808230305575&quot;&gt;&lt;/p&gt;
&lt;h3&gt;C++代码：&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include &amp;#x3C;iostream&gt;
#include &amp;#x3C;cstring&gt;
#include &amp;#x3C;algorithm&gt;
using namespace std;
const int N=50010, M=210, P=1e9+7;
long long S[N][M], C[M][M];

void init(){
  S[0][0]=1;
  for(int i=1; i&amp;#x3C;N; i++)
    for(int j=1; j&amp;#x3C;M; j++)
      S[i][j]=(S[i-1][j-1]+(i-1)*S[i-1][j])%P;
  for(int i=0; i&amp;#x3C;M; i++) C[i][0]=1;
  for(int i=1; i&amp;#x3C;M; i++)
    for(int j=1; j&amp;#x3C;=i; j++)
      C[i][j]=(C[i-1][j-1]+C[i-1][j])%P;  
}
int main(){
  init();
  int T, n, a, b, ans;
  scanf(&quot;%d&quot;, &amp;#x26;T);
  while(T--){
    scanf(&quot;%d%d%d&quot;, &amp;#x26;n, &amp;#x26;a, &amp;#x26;b);
    ans=S[n-1][a+b-2]*C[a+b-2][a-1]%P;
    printf(&quot;%d\n&quot;, ans);
  }
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>G22 第二类斯特林数</title><link>https://ehnotgod.site/blog/g22-%E7%AC%AC%E4%BA%8C%E7%B1%BB%E6%96%AF%E7%89%B9%E6%9E%97%E6%95%B0</link><guid isPermaLink="true">https://ehnotgod.site/blog/g22-%E7%AC%AC%E4%BA%8C%E7%B1%BB%E6%96%AF%E7%89%B9%E6%9E%97%E6%95%B0</guid><description>第二类斯特林数：集合划分计数。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;为了自己的安全，小猪们又建造了一个新砖房。但是现在问题出现了，怎样把三个小猪分配到两个房子里呢？第三只小猪是三只小猪中最聪明的一只，为了不浪费任何一个房子，它总共考虑了三种方案，如下图&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/6862.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;“但是将来怎么办呢？”第三只小猪知道将来随着成员的增多，它们将会盖更多的房子。它想知道给定了房子和猪的数目后，房子的分配方案有多少，但这个问题对于它来说，很明显有点难了，你能帮小猪解决这个问题吗？&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输入数据仅有一行，包含两个整数 $n$ 和 $m$，分别表示小猪的数目和房间数（$1\leq n, m \leq 50$）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输出数据应为一个整数，表示将 $n$ 只小猪安置在 $m$ 个房间且没有房间空闲的方案数。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;4 2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;7
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输入 #2&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;6 7
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #2&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;0
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;第二类斯特林数 $S(n,k)$ 表示把 $n$ 个不同元素分成 $k$ 个非空集合的方案数。递推式：$S(n,k)=S(n-1,k-1)+kS(n-1,k)$——新元素单独成一个集合，或放入已有 $k$ 个集合中的任意一个。边界 $S(0,0)=1,\ S(n,0)=0\ (n&gt;0)$。与第一类斯特林数的区别：集合无顺序，圆排列有顺序。&lt;/p&gt;
&lt;p&gt;打表找到规律后直接使用。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/G22-1.png&quot; alt=&quot;第二类斯特林数打表图&quot;&gt;&lt;/p&gt;
&lt;h3&gt;C++代码：&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include&amp;#x3C;iostream&gt;
#include&amp;#x3C;cmath&gt;
using namespace std;
const int N = 55;
int S[N][N][100],L[N][N];

void add(int x, int y){
  L[x][y]=max(L[x-1][y-1],L[x-1][y]);        
  for(int i=0; i&amp;#x3C;L[x][y]; i++){  
    S[x][y][i]+=S[x-1][y-1][i]+y*S[x-1][y][i];   
    S[x][y][i+1]+=S[x][y][i]/10; 
    S[x][y][i]%=10;
  }
  while(S[x][y][L[x][y]]){
    S[x][y][L[x][y]+1]+=S[x][y][L[x][y]]/10;
    S[x][y][L[x][y]]%=10;
    L[x][y]++;
  }
}
int main(){
  int n, m;
  cin&gt;&gt;n&gt;&gt;m;
  S[0][0][0]=1; L[0][0]=1;
  for(int i=1; i&amp;#x3C;=n; i++)
    for(int j=1; j&amp;#x3C;=i; j++)
      add(i,j);   
  if(!L[n][m]) cout&amp;#x3C;&amp;#x3C;0;     
  for(int i=L[n][m]-1; i&gt;=0; i--)
    cout&amp;#x3C;&amp;#x3C;S[n][m][i];
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>G23 三角剖分</title><link>https://ehnotgod.site/blog/g23-%E4%B8%89%E8%A7%92%E5%89%96%E5%88%86</link><guid isPermaLink="true">https://ehnotgod.site/blog/g23-%E4%B8%89%E8%A7%92%E5%89%96%E5%88%86</guid><description>计算几何：多边形三角剖分。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;多边形三角剖分：把 $n$ 边形用不相交的对角线分成 $n-2$ 个三角形。&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;多边形三角剖分：把 $n$ 边形用不相交的对角线分成 $n-2$ 个三角形。凸多边形的剖分方案数与卡特兰数相关；最优化剖分（如面积/周长和最小）可用区间 DP：$dp[l][r]=\min(dp[l][k]+dp[k][r]+cost(l,k,r))$。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/G23-1.png&quot; alt=&quot;image-20250930175859818&quot;&gt;&lt;/p&gt;
&lt;h3&gt;C++代码&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include &amp;#x3C;iostream&gt;
#include &amp;#x3C;cstring&gt;
#include &amp;#x3C;algorithm&gt;
#include &amp;#x3C;cmath&gt;
#define x first
#define y second
using namespace std;

typedef pair&amp;#x3C;double,double&gt; Point;
const double eps=1e-8;
const double PI=acos(-1.0);
double R;
Point p[4],o; //顶点和圆心

Point operator+(Point a,Point b){ //向量+
  return Point(a.x+b.x,a.y+b.y);
}
Point operator-(Point a,Point b){ //向量-
  return Point(a.x-b.x,a.y-b.y);
}
Point operator*(Point a,double t){ //数乘
  return Point(a.x*t,a.y*t);
}
Point operator/(Point a,double t){ //数除
  return Point(a.x/t,a.y/t);
}
double operator*(Point a,Point b){ //叉积
  return a.x*b.y-a.y*b.x;
}
double operator&amp;#x26;(Point a,Point b){ //点积
  return a.x*b.x+a.y*b.y;
}
double len(Point a){ //模长
  return sqrt(a&amp;#x26;a);
}
double dis(Point a,Point b){ //距离
  return len(b-a);
}
Point getNode(Point a,Point u,Point b,Point v){ //直线交点
  double t=(a-b)*v/(v*u);
  return a+u*t;
}
Point rotate(Point a,double b){ //逆转角
  return Point(a.x*cos(b)-a.y*sin(b),a.x*sin(b)+a.y*cos(b));
}
bool onSegment(Point p,Point a,Point b){ //p在线段ab上
  return fabs((a-p)*(b-p))&amp;#x3C;eps &amp;#x26;&amp;#x26; ((a-p)&amp;#x26;(b-p))&amp;#x3C;=0;
}
Point norm(Point a){ //单位向量
  return a/len(a);
}
double getDP2(Point a,Point b,Point&amp;#x26; pa,Point&amp;#x26; pb){
  Point e=getNode(a,b-a,o,rotate(b-a,PI/2)); //垂足
  double d=dis(o,e);
  if(!onSegment(e,a,b)) d=min(dis(o,a),dis(o,b));
  if(R&amp;#x3C;=d) return d; //线段在圆外
  double len=sqrt(R*R-dis(o,e)*dis(o,e));
  pa=e+norm(a-b)*len;
  pb=e+norm(b-a)*len; //pa,pb:线段与圆的两交点
  return d;           //d:圆心到线段的最近距离
}
double sector(Point a,Point b){ //扇形面积
  double angle=acos((a&amp;#x26;b)/len(a)/len(b)); //[0,Pi]
  if(a*b&amp;#x3C;0) angle=-angle;
  return R*R*angle/2;
}
double getArea(Point a,Point b){ //面积的交
  if(fabs(a*b)&amp;#x3C;eps) return 0; //共线
  double da=dis(o,a),db=dis(o,b);
  if(R&gt;=da &amp;#x26;&amp;#x26; R&gt;=db) return a*b/2; //ab在圆内
  Point pa,pb;
  double d=getDP2(a,b,pa,pb); //d:圆心到线段的最近距离
  if(R&amp;#x3C;=d) return sector(a,b); //ab在圆外
  if(R&gt;=da) return a*pb/2+sector(pb,b); //a在圆内
  if(R&gt;=db) return sector(a,pa)+pa*b/2; //b在圆内
  return sector(a,pa)+pa*pb/2+sector(pb,b); //ab是割线
}
int main(){
  while(scanf(&quot;%lf%lf%lf%lf%lf%lf%lf%lf%lf&quot;,
  &amp;#x26;p[0].x,&amp;#x26;p[0].y,&amp;#x26;p[1].x,&amp;#x26;p[1].y,&amp;#x26;p[2].x,&amp;#x26;p[2].y,&amp;#x26;o.x,&amp;#x26;o.y,&amp;#x26;R)!=-1){
    for(int i=0;i&amp;#x3C;3;i++) p[i].x-=o.x,p[i].y-=o.y; //三角形顶点平移
    o=Point(0,0); //圆心平移到原点
    double res=0;
    for(int i=0;i&amp;#x3C;3;i++) res+=getArea(p[i],p[(i+1)%3]);
    printf(&quot;%.2lf\n&quot;,fabs(res)); //点可能顺时针
  }
  return 0;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>G24 凸包算法</title><link>https://ehnotgod.site/blog/g24-%E5%87%B8%E5%8C%85%E7%AE%97%E6%B3%95</link><guid isPermaLink="true">https://ehnotgod.site/blog/g24-%E5%87%B8%E5%8C%85%E7%AE%97%E6%B3%95</guid><description>凸包：Andrew / Graham 扫描求点集凸包。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P2742&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;农夫约翰想要建造一个围栏用来围住他的奶牛，可是他资金匮乏。他建造的围栏必须包括他的奶牛喜欢吃草的所有地点。对于给出的这些地点的坐标，计算最短的能够围住这些点的围栏的长度。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输入数据的第一行是一个整数。表示农夫约翰想要围住的放牧点的数目 $$n$$。&lt;/p&gt;
&lt;p&gt;第 $$2$$ 到第 $$(n + 1)$$ 行，每行两个实数，第 $$(i + 1)$$ 行的实数 $$x_i, y_i$$ 分别代表第 $$i$$ 个放牧点的横纵坐标。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输出输出一行一个四舍五入保留两位小数的实数，代表围栏的长度。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入输出样例 #1&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;4
4 8
4 12
5 9.3
7 8
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;12.00
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于 $$100%$$ 的数据，保证 $$1 \leq n \leq 10^5$$，$$-10^6 \leq x_i, y_i \leq 10^6$$。小数点后最多有 $$2$$ 位数字。&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;Andrew 凸包：按 $(x,y)$ 排序后，先从左到右构造下凸包，再从右到左构造上凸包，用叉积判断转向（$\le0$ 弹出栈顶）。最后栈中即凸包顶点，相邻点距离求和得周长。$O(n\log n)$。&lt;/p&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include &amp;#x3C;iostream&gt;
#include &amp;#x3C;cstring&gt;
#include &amp;#x3C;algorithm&gt;
#include &amp;#x3C;cmath&gt;
using namespace std;

const int N=100010;
struct Point{double x,y;} p[N],s[N];
int n,top;

double cross(Point a,Point b,Point c){ //叉积
  return (b.x-a.x)*(c.y-a.y)-(b.y-a.y)*(c.x-a.x);
}
double dis(Point a,Point b){ //距离
  return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
bool cmp(Point a, Point b){ //比较
  return a.x!=b.x ? a.x&amp;#x3C;b.x : a.y&amp;#x3C;b.y;
}
double Andrew(){
  sort(p+1,p+n+1,cmp); //排序
  for(int i=1; i&amp;#x3C;=n; i++){ //下凸包
    while(top&gt;1&amp;#x26;&amp;#x26;cross(s[top-1],s[top],p[i])&amp;#x3C;=0)top--;
    s[++top]=p[i];
  }
  int t=top;
  for(int i=n-1; i&gt;=1; i--){ //上凸包
    while(top&gt;t&amp;#x26;&amp;#x26;cross(s[top-1],s[top],p[i])&amp;#x3C;=0)top--;
    s[++top]=p[i];
  }
  double res=0; //周长
  for(int i=1; i&amp;#x3C;top; i++) res+=dis(s[i],s[i+1]);
  return res;
}
int main(){
  scanf(&quot;%d&quot;,&amp;#x26;n);
  for(int i=1;i&amp;#x3C;=n;i++)scanf(&quot;%lf%lf&quot;,&amp;#x26;p[i].x,&amp;#x26;p[i].y);
  printf(&quot;%.2lf\n&quot;, Andrew());
  return 0;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>G25 旋转卡壳</title><link>https://ehnotgod.site/blog/g25-%E6%97%8B%E8%BD%AC%E5%8D%A1%E5%A3%B3</link><guid isPermaLink="true">https://ehnotgod.site/blog/g25-%E6%97%8B%E8%BD%AC%E5%8D%A1%E5%A3%B3</guid><description>旋转卡壳：求凸包直径 / 最远点对。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P1452&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;给定平面上 $$n$$ 个点，求凸包直径。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行一个正整数 $$n$$。&lt;br&gt;
接下来 $$n$$ 行，每行两个整数 $$x,y$$，表示一个点的坐标。保证所有点的坐标两两不同。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输出一行一个整数，表示答案的平方。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入输出样例 #1&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;4
0 0
0 1
1 1
1 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于 $$100%$$ 的数据，$$2\le n \le 50000$$，$$|x|,|y| \le 10^4$$。&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;旋转卡壳：先求凸包，再用双指针枚举凸包上的对踵点对：固定一个点，另一个沿凸包顺时针移动，用面积单调性判断卡壳位置，更新最大距离平方。$O(n)$，用于求凸包直径、最远点对。&lt;/p&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include &amp;#x3C;iostream&gt;
#include &amp;#x3C;cstring&gt;
#include &amp;#x3C;algorithm&gt;
using namespace std;

#define N 50010
#define x first
#define y second
#define Point pair&amp;#x3C;int,int&gt;
Point p[N],s[N];
int n;

int cross(Point a,Point b,Point c){ //叉积
  return (b.x-a.x)*(c.y-a.y)-(b.y-a.y)*(c.x-a.x);
}
int dis(Point a, Point b){ //距离平方
  return (a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y);
}
void Andrew(){
  sort(p+1,p+n+1);
  int t=0;
  for(int i=1; i&amp;#x3C;=n; i++){ //下凸包
    while(t&gt;1&amp;#x26;&amp;#x26;cross(s[t-1],s[t],p[i])&amp;#x3C;=0)t--;
    s[++t]=p[i];
  }
  int k=t;
  for(int i=n-1; i&gt;=1; i--){ //上凸包
    while(t&gt;k&amp;#x26;&amp;#x26;cross(s[t-1],s[t],p[i])&amp;#x3C;=0)t--;
    s[++t]=p[i];
  }
  n=t-1; //n为凸包上的点数
}
int rotating_calipers(){ //旋转卡壳
  int res=0;
  for(int i=1,j=2; i&amp;#x3C;=n; i++){
    while(cross(s[i],s[i+1],s[j])&amp;#x3C;cross(s[i],s[i+1],s[j+1]))j=j%n+1;
    res=max(res,max(dis(s[i],s[j]),dis(s[i+1],s[j])));
  }
  return res;
}
int main(){
  scanf(&quot;%d&quot;,&amp;#x26;n);
  for(int i=1; i&amp;#x3C;=n; i++) scanf(&quot;%d%d&quot;,&amp;#x26;p[i].x,&amp;#x26;p[i].y);
  Andrew();
  printf(&quot;%d\n&quot;,rotating_calipers());
  return 0;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>G26 自适应辛普森积分</title><link>https://ehnotgod.site/blog/g26-%E8%87%AA%E9%80%82%E5%BA%94%E8%BE%9B%E6%99%AE%E6%A3%AE%E7%A7%AF%E5%88%86</link><guid isPermaLink="true">https://ehnotgod.site/blog/g26-%E8%87%AA%E9%80%82%E5%BA%94%E8%BE%9B%E6%99%AE%E6%A3%AE%E7%A7%AF%E5%88%86</guid><description>自适应辛普森积分：数值积分求面积。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P4525&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;试计算积分&lt;/p&gt;
&lt;p&gt;$$\displaystyle{\int_L^R\frac{cx+d}{ax+b}\mathrm{d}x}$$&lt;/p&gt;
&lt;p&gt;结果保留至小数点后 $$6$$ 位。&lt;/p&gt;
&lt;p&gt;数据保证计算过程中分母不为 $$0$$ 且积分能够收敛。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;一行，包含 $$6$$ 个实数 $$a,b,c,d,L,R$$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;一行，积分值，保留至小数点后 $$6$$ 位。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1 2 3 4 5 6
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2.732937
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;$$a,b,c,d\in[-10,10]$$，$$-100\le L&amp;#x3C;R\le 100$$ 且 $$R-L\ge1$$。&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;images/G26-1.png&quot; alt=&quot;image-20251010102351335&quot;&gt;&lt;/p&gt;
&lt;p&gt;自适应辛普森积分：用辛普森公式 $\int_l^r f\approx\frac{r-l}{6}(f(l)+4f(m)+f(r))$ 近似积分。递归比较区间整体与左右两半辛普森值的差，误差小于 $\epsilon$ 时停止，否则递归细分。适用于无法解析积分的一般函数。&lt;/p&gt;
&lt;p&gt;时间复杂度：$${O(log(n / eps))}$$&lt;/p&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include &amp;#x3C;iostream&gt;
#include &amp;#x3C;cstring&gt;
#include &amp;#x3C;algorithm&gt;
#include &amp;#x3C;cmath&gt;
using namespace std;

const double eps=1e-10;
double a,b,c,d,l,r;

double f(double x){ //积分函数
  return (c*x+d)/(a*x+b);
}
double simpson(double l,double r){//辛普森公式
  return (r-l)*(f(l)+f(r)+4*f((l+r)/2))/6;
}
double asr(double l,double r,double ans){//自适应
  auto m=(l+r)/2,a=simpson(l,m),b=simpson(m,r);
  if(fabs(a+b-ans)&amp;#x3C;eps) return ans;
  return asr(l,m,a)+asr(m,r,b);
}
int main(){
  scanf(&quot;%lf%lf%lf%lf%lf%lf&quot;,&amp;#x26;a,&amp;#x26;b,&amp;#x26;c,&amp;#x26;d,&amp;#x26;l,&amp;#x26;r);
  printf(&quot;%.6lf&quot;,asr(l,r,simpson(l,r)));
  return 0;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>G27 有向图博弈-SG函数</title><link>https://ehnotgod.site/blog/g27-%E6%9C%89%E5%90%91%E5%9B%BE%E5%8D%9A%E5%BC%88-sg%E5%87%BD%E6%95%B0</link><guid isPermaLink="true">https://ehnotgod.site/blog/g27-%E6%9C%89%E5%90%91%E5%9B%BE%E5%8D%9A%E5%BC%88-sg%E5%87%BD%E6%95%B0</guid><description>SG 函数：有向图博弈的胜负判定。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://loj.ac/p/10243&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;给定一个有 N 个节点的有向无环图，图中某些节点上有棋子，两名玩家交替移动棋子。&lt;/p&gt;
&lt;p&gt;玩家每一步可将任意一颗棋子沿一条有向边移动到另一个点，无法移动者输掉游戏。&lt;/p&gt;
&lt;p&gt;对于给定的图和棋子初始位置，双方都会采取最优的行动，询问先手必胜还是先手必败。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行，三个整数 N , M, K，N 表示图中节点总数，M 表示图中边的条数，K 表示棋子的个数。&lt;/p&gt;
&lt;p&gt;接下来 M 行，每行两个整数 X, Y 表示有一条边从 X 出发指向 Y。&lt;/p&gt;
&lt;p&gt;接下来一行，K 个空格间隔的整数，表示初始时，棋子所在的节点编号。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;若先手胜，输出 &lt;code&gt;win&lt;/code&gt;，否则输出 &lt;code&gt;lose&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;SG 函数：$sg(u)=\operatorname{mex}{sg(v)\mid v$ 为 $u$ 的后继$}$，$sg=0$ 为必败态。记忆化搜索求每个点 $sg$。多枚棋子时各棋子 $sg$ 值异或，非 0 先手胜（win），为 0 后手胜（lose）。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/G27-1.png&quot; alt=&quot;image-20251010103151069&quot;&gt;&lt;/p&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include &amp;#x3C;cstdio&gt;
#include &amp;#x3C;cstring&gt;
#include &amp;#x3C;set&gt;
using namespace std;

const int N=2005,M=10005;
int n,m,k,a,b,x;
int h[N],to[M],ne[M],tot; //邻接表
int f[N];

void add(int a,int b){
  to[++tot]=b,ne[tot]=h[a],h[a]=tot;
}
int sg(int u){
  // 记忆化搜索
  if(f[u]!=-1) return f[u]; 
  // 把子节点的sg值插入集合
  set&amp;#x3C;int&gt; S;
  for(int i=h[u];i;i=ne[i]) 
    S.insert(sg(to[i]));
  // mex运算求当前节点的sg值并记忆
  for(int i=0; ;i++) 
    if(!S.count(i)) return f[u]=i;
}
int main(){
  scanf(&quot;%d%d%d&quot;,&amp;#x26;n,&amp;#x26;m,&amp;#x26;k);
  for(int i=0;i&amp;#x3C;m;i++)
    scanf(&quot;%d%d&quot;,&amp;#x26;a,&amp;#x26;b), add(a,b);
  memset(f,-1,sizeof f); 
  int res=0;
  for(int i=0;i&amp;#x3C;k;i++)
    scanf(&quot;%d&quot;,&amp;#x26;x),res^=sg(x);
  if(res) puts(&quot;win&quot;);
  else puts(&quot;lose&quot;);
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>G28 线性基-max</title><link>https://ehnotgod.site/blog/g28-%E7%BA%BF%E6%80%A7%E5%9F%BA-max</link><guid isPermaLink="true">https://ehnotgod.site/blog/g28-%E7%BA%BF%E6%80%A7%E5%9F%BA-max</guid><description>线性基：求最大异或和。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P3812&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;给定 $$n$$ 个整数（数字可能重复），求在这些数中选取任意个，使得他们的异或和最大。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行一个数 $$n$$，表示元素个数&lt;/p&gt;
&lt;p&gt;接下来一行 $$n$$ 个数&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;仅一行，表示答案。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2
1 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输入 #2&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;4
1 5 9 4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #2&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;13
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;$$ 1 \leq n \leq 50, 0 \leq S_i &amp;#x3C; 2 ^ {50} $$&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;线性基求最大异或和：把每个数从高位到低位插入线性基，若当前位已有基向量则异或消去，否则放入。构造后用贪心：从高位到低位，若异或该基向量能使结果变大则异或，累加得最大异或和。复杂度 $O(63n)$。&lt;/p&gt;
&lt;p&gt;看这个原始 $x$ 最后能不能成功插入线性基。&lt;/p&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;// 线性基 O(63*n)
#include &amp;#x3C;iostream&gt;
#include &amp;#x3C;cstring&gt;
#include &amp;#x3C;algorithm&gt;
using namespace std;

typedef long long LL;
int n;
LL p[64];

void insert(LL x){ //贪心法
  for(int i=63;i&gt;=0;--i){
    if(x&gt;&gt;i&amp;#x26;1){  //x第i位为1
      if(!p[i]){ //不存在则加入
        p[i]=x; 
        break;
      }
      x^=p[i];   //存在则异或
    }
  }
}
int main(){
  scanf(&quot;%d&quot;,&amp;#x26;n);
  for(int i=0;i&amp;#x3C;n;i++){
    LL x; scanf(&quot;%lld&quot;,&amp;#x26;x);
    insert(x);
  }
  LL ans=0;
  for(int i=63;i&gt;=0;i--) ans=max(ans,ans^p[i]);
  printf(&quot;%lld\n&quot;,ans);
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>G29 拉格朗日插值法</title><link>https://ehnotgod.site/blog/g29-%E6%8B%89%E6%A0%BC%E6%9C%97%E6%97%A5%E6%8F%92%E5%80%BC%E6%B3%95</link><guid isPermaLink="true">https://ehnotgod.site/blog/g29-%E6%8B%89%E6%A0%BC%E6%9C%97%E6%97%A5%E6%8F%92%E5%80%BC%E6%B3%95</guid><description>拉格朗日插值：求多项式在任意点的值。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;题目链接：https://www.luogu.com.cn/problem/P4781&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;对于 $$n$$ 个点 $$(x_i,y_i)$$，如果满足 $$\forall i\neq j, x_i\neq x_j$$，那么经过这 $$n$$ 个点可以唯一地确定一个 $$n-1$$ 次多项式 $$y = f(x)$$。&lt;/p&gt;
&lt;p&gt;现在，给定这样 $$n$$ 个点，请你确定这个 $$n-1$$ 次多项式，并求出 $$f(k) \bmod 998244353$$ 的值。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一行两个整数 $$n,k$$。&lt;/p&gt;
&lt;p&gt;接下来 $$n$$ 行，第 $$i$$ 行两个整数 $$x_i,y_i$$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;一行一个整数，表示 $$f(k) \bmod 998244353$$ 的值。&lt;/p&gt;
&lt;p&gt;输入 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;3 100
1 4
2 9
3 16
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;10201
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输入 #2&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;3 100
1 1
2 2
3 3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出 #2&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;100
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;p&gt;$$1 \le n \leq 2\times 10^3$$，$$1 \le x_i,y_i,k &amp;#x3C; 998244353$$，$$x_i$$ 两两不同。&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;时间复杂度：$${O(n^2)}$$&lt;/p&gt;
&lt;p&gt;拉格朗日插值：$f(k)=\sum_{i=1}^n y_i\prod_{j\ne i}\frac{k-x_j}{x_i-x_j}$。对每个 $i$，分子累乘 $(k-x_j)$、分母累乘 $(x_i-x_j)$，分母用逆元（费马小定理）处理模意义除法。&lt;/p&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;// 拉格朗日插值法 O(n^2)
#include &amp;#x3C;iostream&gt;
#include &amp;#x3C;cstring&gt;
#include &amp;#x3C;algorithm&gt;
using namespace std;

#define LL long long
const LL mod=998244353;
LL n,k,ans;
LL x[2005],y[2005];

LL ksm(LL a,LL b){
  LL s=1;
  while(b){
    if(b&amp;#x26;1)s=s*a%mod;
    a=a*a%mod;
    b&gt;&gt;=1;
  }
  return s;
}
int main(){
  cin&gt;&gt;n&gt;&gt;k;
  for(int i=1;i&amp;#x3C;=n;i++)cin&gt;&gt;x[i]&gt;&gt;y[i];
  for(int i=1;i&amp;#x3C;=n;i++){
    LL a=y[i],b=1;
    for(int j=1;j&amp;#x3C;=n;j++){
      if(i==j) continue;
      a=a*(k-x[j])%mod;
      b=b*(x[i]-x[j])%mod;
    }
    ans=(ans+a*ksm(b,mod-2)%mod)%mod;
  }
  cout&amp;#x3C;&amp;#x3C;(ans+mod)%mod;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>G30 超级gcd</title><link>https://ehnotgod.site/blog/g30-%E8%B6%85%E7%BA%A7gcd</link><guid isPermaLink="true">https://ehnotgod.site/blog/g30-%E8%B6%85%E7%BA%A7gcd</guid><description>超级 gcd：求多个数的最大公约数。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;题目情境&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/G30-1.png&quot; alt=&quot;image-20250808230643738&quot;&gt;&lt;/p&gt;
&lt;h3&gt;算法解析：&lt;/h3&gt;
&lt;p&gt;超级 gcd：指数巨大，用扩展欧拉定理降幂。递归：$\gcd(a^b,c^d)=\gcd(g^{\min(b,d)}\cdot\ldots)$，逐步约去公共因子（$g=\gcd(a,c)$），指数超出 $\varphi$ 时取 $\bmod\varphi+\varphi$，快速幂相乘。详见代码。&lt;/p&gt;
&lt;h3&gt;Python代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import sys
import threading
import math

MOD = 998244353
PHI = MOD - 1  # 欧拉函数 phi(998244353)

def qmi(a, k):
    res = 1
    a %= MOD
    while k:
        if k &amp;#x26; 1:
            res = res * a % MOD
        a = a * a % MOD
        k &gt;&gt;= 1
    return res

def dfs(a, b, c, d):
    g = math.gcd(a, c)
    if g == 1:
        return 1
    minv = min(b, d)
    if b &amp;#x3C;= d:
        exp = b if b &amp;#x3C; PHI else b % PHI + PHI
        return qmi(g, exp) * dfs(a // g, b, g, d - b) % MOD
    else:
        exp = d if d &amp;#x3C; PHI else d % PHI + PHI
        return qmi(g, exp) * dfs(g, b - d, c // g, d) % MOD

def main():
    T = int(sys.stdin.readline())
    for _ in range(T):
        a, b, c, d = map(int, sys.stdin.readline().split())
        print(dfs(a, b, c, d))

threading.Thread(target=main).start()
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include &quot;bits/stdc++.h&quot;
using namespace std;
using ll = long long;
using lll = __int128;
#define all(x) (x).begin(),(x).end()
const ll p = 998244353;
ll ksm(ll x, lll Y)
{
	ll r = 1;
	x %= p;
	if (!Y) return 1;
	if (!x) return 0;
	ll y = Y % (p - 1);
	while (y)
	{
		if (y &amp;#x26; 1) r = r * x % p;
		x = x * x % p; y &gt;&gt;= 1;
	}
	return r;
}
ll f(ll a, lll b, ll c, lll d)
{
	ll x = gcd(a, c);
	if (x == 1 || b == 0 || d == 0) return 1;
	lll ka = 0, kc = 0;
	while (a % x == 0) a /= x, ++ka;
	while (c % x == 0) c /= x, ++kc;
	ka *= b, kc *= d;
	if (ka &gt; kc) swap(a, c), swap(b, d), swap(ka, kc);
	return ksm(x, ka) * f(a, b, x, kc - ka) % p;
}
int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	int T;
	cin &gt;&gt; T;
	while (T--)
	{
		ll a, b, c, d;
		cin &gt;&gt; a &gt;&gt; b &gt;&gt; c &gt;&gt; d;
		cout &amp;#x3C;&amp;#x3C; f(a, b, c, d) &amp;#x3C;&amp;#x3C; &apos;\n&apos;;
	}
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>k-means算法</title><link>https://ehnotgod.site/blog/k-means%E7%AE%97%E6%B3%95</link><guid isPermaLink="true">https://ehnotgod.site/blog/k-means%E7%AE%97%E6%B3%95</guid><description>k-means 聚类：无监督学习，迭代更新簇中心。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;k-means 算法&lt;/h1&gt;
&lt;h2&gt;一、什么是 k-means？&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;k-means&lt;/strong&gt; 是一种&lt;strong&gt;无监督学习&lt;/strong&gt;中的&lt;strong&gt;聚类&lt;/strong&gt;（Clustering）算法。它的目标很简单：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;把一堆没有标签的数据，按照&quot;相似程度&quot;自动分成 $k$ 组（簇）。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;比如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;给一堆用户画像，分成几类人群&lt;/li&gt;
&lt;li&gt;把图片里的颜色聚成几种主色调&lt;/li&gt;
&lt;li&gt;把文章分成几个主题&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;我们不需要提前告诉机器&quot;每类长什么样&quot;，机器自己找规律，这就是&quot;无监督&quot;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;二、算法步骤&lt;/h2&gt;
&lt;p&gt;假设数据是二维平面上的点，$k=3$：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;初始化&lt;/strong&gt;：随机挑 $k$ 个点作为初始中心 $C_1, C_2, \dots, C_k$&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;分配&lt;/strong&gt;：对每个点 $x$，计算它到每个中心的距离（通常用欧氏距离），把它分给最近的中心&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;更新&lt;/strong&gt;：对每个簇，重新计算所有点的均值，作为新的中心&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;迭代&lt;/strong&gt;：重复步骤 2、3，直到中心位置几乎不变（或达到最大迭代次数）&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;距离公式（欧氏距离）：&lt;/p&gt;
&lt;p&gt;$$
d(x, C) = \sqrt{(x_1 - C_1)^2 + (x_2 - C_2)^2}
$$&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2&gt;三、Python 代码实现&lt;/h2&gt;
&lt;p&gt;用一个简单的 Python 例子，自己动手实现一遍（不依赖 sklearn）：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import numpy as np
import matplotlib.pyplot as plt

def kmeans(X, k, max_iter=100):
    # 1. 随机初始化 k 个中心
    centroids = X[np.random.choice(len(X), k, replace=False)]

    for _ in range(max_iter):
        # 2. 分配：每个点归到最近的中心
        dists = np.linalg.norm(X[:, None] - centroids, axis=2)
        labels = np.argmin(dists, axis=1)

        # 3. 更新：用簇内均值作为新中心
        new_centroids = np.array([
            X[labels == i].mean(axis=0) for i in range(k)
        ])

        # 4. 中心不变则提前结束
        if np.allclose(centroids, new_centroids):
            break
        centroids = new_centroids

    return labels, centroids

# 造一点模拟数据（3 个&quot;团&quot;）
np.random.seed(0)
X = np.vstack([
    np.random.randn(50, 2) + [0, 0],
    np.random.randn(50, 2) + [5, 5],
    np.random.randn(50, 2) + [0, 8],
])

labels, centroids = kmeans(X, k=3)

# 画图
plt.scatter(X[:, 0], X[:, 1], c=labels, cmap=&quot;viridis&quot;)
plt.scatter(centroids[:, 0], centroids[:, 1], c=&quot;red&quot;, marker=&quot;x&quot;, s=200)
plt.title(&quot;k-means 聚类结果&quot;)
plt.show()
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;四、特点&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;优点：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;思想简单，实现容易，速度快&lt;/li&gt;
&lt;li&gt;适合大数据集&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;缺点：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;需要&lt;strong&gt;事先指定 k&lt;/strong&gt;（分几类）&lt;/li&gt;
&lt;li&gt;对初始中心敏感，不同初始值可能得到不同结果&lt;/li&gt;
&lt;li&gt;对&lt;strong&gt;异常点&lt;/strong&gt;敏感&lt;/li&gt;
&lt;li&gt;只能发现&quot;球形&quot;簇，对形状复杂的簇效果差&lt;/li&gt;
&lt;li&gt;容易陷入局部最优&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;五、怎么选 k？&lt;/h2&gt;
&lt;p&gt;常用方法：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;肘部法则（Elbow Method）&lt;/strong&gt;：画出&quot;k 和误差（SSE）&quot;的曲线，找拐点。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;误差：每个点到所属中心距离的平方和&lt;/li&gt;
&lt;li&gt;k 增大，误差一定下降，但到某个点后下降变缓，这个&quot;拐点&quot;就是合适的 k&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;轮廓系数（Silhouette Coefficient）&lt;/strong&gt;：衡量簇内紧凑、簇间分离的程度，越大越好。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>torch语法学习1</title><link>https://ehnotgod.site/blog/torch%E8%AF%AD%E6%B3%95%E5%AD%A6%E4%B9%A01</link><guid isPermaLink="true">https://ehnotgod.site/blog/torch%E8%AF%AD%E6%B3%95%E5%AD%A6%E4%B9%A01</guid><description>PyTorch 语法笔记：优化器、梯度与常用 API。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;建议和Dezero相关内容一起食用，效果更佳，更能理解。
eg. optimizer = optim.Adam(model.parameters(), lr=0.001)
为什么传入的是model.parameters()？以及梯度的清理optimizer.zero_grad()的理由是啥？为啥这么调用&lt;/p&gt;
&lt;h2&gt;1. 张量&lt;/h2&gt;
&lt;h3&gt;1.1 基础&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import torch

# 设置数据类型和设备
dtype = torch.float  # 张量数据类型为浮点型
device = torch.device(&quot;cpu&quot;)  # 本次计算在 CPU 上进行

# 创建并打印两个随机张量 a 和 b
a = torch.randn(2, 3, device=device, dtype=dtype)  # 创建一个 2x3 的随机张量
b = torch.randn(2, 3, device=device, dtype=dtype)  # 创建另一个 2x3 的随机张量

print(&quot;张量 a:&quot;)
print(a)

print(&quot;张量 b:&quot;)
print(b)

# 逐元素相乘并输出结果
print(&quot;a 和 b 的逐元素乘积:&quot;)
print(a * b)

# 输出张量 a 所有元素的总和
print(&quot;张量 a 所有元素的总和:&quot;)
print(a.sum())

# 输出张量 a 中第 2 行第 3 列的元素（注意索引从 0 开始）
print(&quot;张量 a 第 2 行第 3 列的元素:&quot;)
print(a[1, 2])

# 输出张量 a 中的最大值
print(&quot;张量 a 中的最大值:&quot;)
print(a.max())
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是别用&lt;code&gt;max(a)&lt;/code&gt;哈，这玩意儿不是torch的。
广播机制应该和numpy的是一样的：从右往左看：相等可以；有 1 可以；缺维度可以；其他不行。&lt;/p&gt;
&lt;p&gt;numpy转tensor:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import numpy as np
numpy_array = np.array([[1, 2], [3, 4]])
tensor_from_numpy = torch.from_numpy(numpy_array)
print(tensor_from_numpy)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;数组转tensor:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import torch
tensor = torch.tensor([1, 2, 3])
print(tensor)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;tensor转numpy:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;x.numpy()
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;1.2 自动微分&lt;/h3&gt;
&lt;p&gt;一旦定义了计算图，可以通过 .backward() 方法来计算梯度。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# 创建一个需要梯度的张量
tensor_requires_grad = torch.tensor([1.0], requires_grad=True)
# 进行一些操作
tensor_result = tensor_requires_grad * 2
# 计算梯度
tensor_result.backward()
print(tensor_requires_grad.grad)  # 输出梯度
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果你不希望某些张量的梯度被计算（例如，当你不需要反向传播时），可以使用 torch.no_grad() 或设置 requires_grad=False。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;with torch.no_grad():
    y = x * 2
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;1.3 更多操作&lt;/h3&gt;
&lt;p&gt;属性：
.shape	获取张量的形状	tensor.shape
.size()	获取张量的形状	tensor.size()
.dtype	获取张量的数据类型	tensor.dtype
.device	查看张量所在的设备 (CPU/GPU)	tensor.device
.dim()	获取张量的维度数	tensor.dim()
.requires_grad	是否启用梯度计算	tensor.requires_grad
.numel()	获取张量中的元素总数	tensor.numel()
.is_cuda	检查张量是否在 GPU 上	tensor.is_cuda
.T	获取张量的转置（适用于 2D 张量）	tensor.T
.item()	获取单元素张量的值	tensor.item()
.is_contiguous()	检查张量是否连续存储	tensor.is_contiguous()
操作：
+, -, *, /	元素级加法、减法、乘法、除法。	z = x + y
torch.matmul(x, y)	矩阵乘法。	z = torch.matmul(x, y)
torch.dot(x, y)	向量点积（仅适用于 1D 张量）。	z = torch.dot(x, y)
torch.sum(x)	求和。	z = torch.sum(x)
torch.mean(x)	求均值。	z = torch.mean(x)
torch.max(x)	求最大值。	z = torch.max(x)
torch.min(x)	求最小值。	z = torch.min(x)
torch.argmax(x, dim)	返回最大值的索引（指定维度）。	z = torch.argmax(x, dim=1)
torch.softmax(x, dim)	计算 softmax（指定维度）。	z = torch.softmax(x, dim=1)&lt;/p&gt;
&lt;p&gt;x.view(shape)	改变张量的形状（不改变数据）。	z = x.view(3, 4)
x.reshape(shape)	类似于 view，但更灵活。	z = x.reshape(3, 4)
x.t()	转置矩阵。	z = x.t()
x.unsqueeze(dim)	在指定维度添加一个维度。	z = x.unsqueeze(0)
x.squeeze(dim)	去掉指定维度为 1 的维度。	z = x.squeeze(0)
torch.cat((x, y), dim)	按指定维度连接多个张量。	z = torch.cat((x, y), dim=1)&lt;/p&gt;
&lt;h2&gt;2. 神经网络&lt;/h2&gt;
&lt;h3&gt;2.1 网络构建&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import torch.nn as nn
import torch.optim as optim
# 定义一个简单的全连接神经网络
class SimpleNN(nn.Module):
    def __init__(self):
        super(SimpleNN, self).__init__()
        self.fc1 = nn.Linear(2, 2)  # 输入层到隐藏层
        self.fc2 = nn.Linear(2, 1)  # 隐藏层到输出层
    
    def forward(self, x):
        x = torch.relu(self.fc1(x))  # ReLU 激活函数
        x = self.fc2(x)
        return x
# 创建网络实例
model = SimpleNN()
# 打印模型结构
print(model)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.2 流程&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# 随机输入
x = torch.randn(1, 2)
# 前向传播
output = model(x)
print(output)
# 定义损失函数（例如均方误差 MSE）
criterion = nn.MSELoss()
# 假设目标值为 1
target = torch.randn(1, 1)
# 计算损失
loss = criterion(output, target)
# 定义优化器（使用 Adam 优化器）
optimizer = optim.Adam(model.parameters(), lr=0.001)
# 训练步骤
optimizer.zero_grad()  # 清空梯度
loss.backward()  # 反向传播
optimizer.step()  # 更新参数
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.3 完整&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import torch
import torch.nn as nn
import torch.optim as optim

# 1. 定义一个简单的神经网络模型
class SimpleNN(nn.Module):
    def __init__(self):
        super(SimpleNN, self).__init__()
        self.fc1 = nn.Linear(2, 2)  # 输入层到隐藏层
        self.fc2 = nn.Linear(2, 1)  # 隐藏层到输出层
    
    def forward(self, x):
        x = torch.relu(self.fc1(x))  # ReLU 激活函数
        x = self.fc2(x)
        return x

# 2. 创建模型实例
model = SimpleNN()

# 3. 定义损失函数和优化器
criterion = nn.MSELoss()  # 均方误差损失函数
optimizer = optim.Adam(model.parameters(), lr=0.001)  # Adam 优化器

# 4. 假设我们有训练数据 X 和 Y
X = torch.randn(10, 2)  # 10 个样本，2 个特征
Y = torch.randn(10, 1)  # 10 个目标值

# 5. 训练循环
for epoch in range(100):  # 训练 100 轮
    optimizer.zero_grad()  # 清空之前的梯度
    output = model(X)  # 前向传播
    loss = criterion(output, Y)  # 计算损失
    loss.backward()  # 反向传播
    optimizer.step()  # 更新参数
    
    # 每 10 轮输出一次损失
    if (epoch+1) % 10 == 0:
        print(f&apos;Epoch [{epoch+1}/100], Loss: {loss.item():.4f}&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.4 更多函数&lt;/h3&gt;
&lt;p&gt;模型定义基础：
nn.Module	所有神经网络模块的基类	class Net(nn.Module): ...
nn.Sequential	顺序容器，按顺序执行各层	nn.Sequential(conv, relu, pool)
nn.ModuleList	将子模块存储在列表中	self.layers = nn.ModuleList([...])
nn.Parameter	创建可学习的参数张量	self.w = nn.Parameter(torch.randn(n))
卷积层：
nn.Conv1d	一维卷积（文本、音频）	nn.Conv1d(3, 64, 3)
nn.Conv2d	二维卷积（图像）	nn.Conv2d(3, 64, 3, padding=1)
nn.Conv3d	三维卷积（视频）	nn.Conv3d(3, 64, 3)
nn.ConvTranspose2d	转置卷积（解码器、上采样）	nn.ConvTranspose2d(64, 3, 2, stride=2)
池化层：
nn.MaxPool2d	二维最大池化	nn.MaxPool2d(2, 2)
nn.AvgPool2d	二维平均池化	nn.AvgPool2d(2, 2)
nn.AdaptiveAvgPool2d	自适应平均池化（固定输出尺寸）	nn.AdaptiveAvgPool2d((1, 1))
nn.AdaptiveMaxPool2d	自适应最大池化（固定输出尺寸）	nn.AdaptiveMaxPool2d((1, 1))
线性层：
nn.ReLU	ReLU 激活函数，f(x) = max(0, x)	nn.ReLU()
nn.GELU	高斯误差线性单元（Transformer 默认）	nn.GELU()
nn.SiLU	Swish 激活函数	nn.SiLU()
nn.Tanh	双曲正切	nn.Tanh()
nn.Sigmoid	Sigmoid 激活函数	nn.Sigmoid()
nn.Softmax	Softmax 激活函数	nn.Softmax(dim=1)
nn.LogSoftmax	Log Softmax（数值稳定）	nn.LogSoftmax(dim=1)
nn.LeakyReLU	LeakyReLU，允许负值有小幅梯度	nn.LeakyReLU(0.01)
nn.ELU	指数线性单元	nn.ELU()
归一化层：
nn.BatchNorm2d	二维批归一化（卷积网络常用）	nn.BatchNorm2d(64)
nn.LayerNorm	层归一化（Transformer 常用）	nn.LayerNorm(512)
nn.GroupNorm	组归一化（ResNet 等常用）	nn.GroupNorm(4, 64)
nn.InstanceNorm2d	实例归一化（风格迁移常用）	nn.InstanceNorm2d(64)
Transformer 层：
nn.Transformer	完整 Transformer 模型	nn.Transformer(d_model=512, nhead=8)
nn.TransformerEncoder	Transformer 编码器	nn.TransformerEncoder(layer, num_layers=6)
nn.TransformerDecoder	Transformer 解码器	nn.TransformerDecoder(layer, num_layers=6)
nn.TransformerEncoderLayer	Transformer 编码器层	nn.TransformerEncoderLayer(512, 8)
nn.MultiheadAttention	多头注意力机制	nn.MultiheadAttention(512, 8)
嵌入层：
nn.Embedding	嵌入层（词汇映射到向量）	nn.Embedding(10000, 256)
nn.EmbeddingBag	嵌入袋（聚合多个嵌入）	nn.EmbeddingBag(10000, 256)
Dropout 层：
nn.Dropout	随机丢弃（防止过拟合）	nn.Dropout(0.5)
nn.Dropout2d	2D Dropout（按特征图丢弃）	nn.Dropout2d(0.5)
损失函数：
nn.CrossEntropyLoss	交叉熵损失（多分类）	nn.CrossEntropyLoss()
nn.MSELoss	均方误差损失（回归）	nn.MSELoss()
nn.L1Loss	L1 损失（MAE）	nn.L1Loss()
nn.BCEWithLogitsLoss	带 Sigmoid 的二元交叉熵	nn.BCEWithLogitsLoss()
nn.HuberLoss	Huber 损失（鲁棒回归）	nn.HuberLoss()
nn.NLLLoss	负对数似然损失	nn.NLLLoss()&lt;/p&gt;
&lt;h3&gt;2.5 更大的示例&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# 导入PyTorch库
import torch
import torch.nn as nn

# 定义输入层大小、隐藏层大小、输出层大小和批量大小
n_in, n_h, n_out, batch_size = 10, 5, 1, 10

# 创建虚拟输入数据和目标数据
x = torch.randn(batch_size, n_in)  # 随机生成输入数据
y = torch.tensor([[1.0], [0.0], [0.0], 
                 [1.0], [1.0], [1.0], [0.0], [0.0], [1.0], [1.0]])  # 目标输出数据

# 创建顺序模型，包含线性层、ReLU激活函数和Sigmoid激活函数
model = nn.Sequential(
   nn.Linear(n_in, n_h),  # 输入层到隐藏层的线性变换
   nn.ReLU(),            # 隐藏层的ReLU激活函数
   nn.Linear(n_h, n_out),  # 隐藏层到输出层的线性变换
   nn.Sigmoid()           # 输出层的Sigmoid激活函数
)

# 定义均方误差损失函数和随机梯度下降优化器
criterion = torch.nn.MSELoss()
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)  # 学习率为0.01

# 执行梯度下降算法进行模型训练
for epoch in range(50):  # 迭代50次
   y_pred = model(x)  # 前向传播，计算预测值
   loss = criterion(y_pred, y)  # 计算损失
   print(&apos;epoch: &apos;, epoch, &apos;loss: &apos;, loss.item())  # 打印损失值

   optimizer.zero_grad()  # 清零梯度
   loss.backward()  # 反向传播，计算梯度
   optimizer.step()  # 更新模型参数
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;3. 数据处理与预加载&lt;/h2&gt;
&lt;h3&gt;3.1 Dataset&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import torch
from torch.utils.data import Dataset

# 自定义数据集类
class MyDataset(Dataset):
    def __init__(self, X_data, Y_data):
        &quot;&quot;&quot;
        初始化数据集，X_data 和 Y_data 是两个列表或数组
        X_data: 输入特征
        Y_data: 目标标签
        &quot;&quot;&quot;
        self.X_data = X_data
        self.Y_data = Y_data

    def __len__(self):
        &quot;&quot;&quot;返回数据集的大小&quot;&quot;&quot;
        return len(self.X_data)

    def __getitem__(self, idx):
        &quot;&quot;&quot;返回指定索引的数据&quot;&quot;&quot;
        x = torch.tensor(self.X_data[idx], dtype=torch.float32)  # 转换为 Tensor
        y = torch.tensor(self.Y_data[idx], dtype=torch.float32)
        return x, y

# 示例数据
X_data = [[1, 2], [3, 4], [5, 6], [7, 8]]  # 输入特征
Y_data = [1, 0, 1, 0]  # 目标标签

# 创建数据集实例
dataset = MyDataset(X_data, Y_data)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.2 Dataloader&lt;/h3&gt;
&lt;p&gt;DataLoader 是 PyTorch 提供的一个重要工具，用于从 Dataset 中按批次（batch）加载数据。
DataLoader 允许我们批量读取数据并进行多线程加载，从而提高训练效率。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from torch.utils.data import DataLoader

# 创建 DataLoader 实例，batch_size 设置每次加载的样本数量
dataloader = DataLoader(dataset, batch_size=2, shuffle=True)

# 打印加载的数据
for epoch in range(1):
    for batch_idx, (inputs, labels) in enumerate(dataloader):
        print(f&apos;Batch {batch_idx + 1}:&apos;)
        print(f&apos;Inputs: {inputs}&apos;)
        print(f&apos;Labels: {labels}&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.3 预处理&lt;/h3&gt;
&lt;p&gt;transforms.Compose()：将多个变换操作组合在一起。
transforms.Resize()：调整图像大小。
transforms.ToTensor()：将图像转换为 PyTorch 张量，值会被归一化到 [0, 1] 范围。
transforms.Normalize()：标准化图像数据，通常使用预训练模型时需要进行标准化处理。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import torchvision.datasets as datasets
import torchvision.transforms as transforms

# 定义预处理操作
transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0.5,), (0.5,))  # 对灰度图像进行标准化
])

# 下载并加载 MNIST 数据集
train_dataset = datasets.MNIST(root=&apos;./data&apos;, train=True, download=True, transform=transform)
test_dataset = datasets.MNIST(root=&apos;./data&apos;, train=False, download=True, transform=transform)

# 创建 DataLoader
train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=64, shuffle=False)

# 迭代训练数据
for inputs, labels in train_loader:
    print(inputs.shape)  # 每个批次的输入数据形状
    print(labels.shape)  # 每个批次的标签形状
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;4. 一些例子&lt;/h2&gt;
&lt;h3&gt;4.1 卷积神经网络&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torchvision import datasets, transforms


# 1. 数据加载与预处理
transform = transforms.Compose([
    transforms.ToTensor(),  # 转为张量
    transforms.Normalize((0.5,), (0.5,))  # 归一化到 [-1, 1]
])

# 加载 MNIST 数据集
train_dataset = datasets.MNIST(root=&apos;./data&apos;, train=True, transform=transform, download=True)
test_dataset = datasets.MNIST(root=&apos;./data&apos;, train=False, transform=transform, download=True)

train_loader = torch.utils.data.DataLoader(dataset=train_dataset, batch_size=64, shuffle=True)
test_loader = torch.utils.data.DataLoader(dataset=test_dataset, batch_size=64, shuffle=False)

# 2. 定义 CNN 模型
class SimpleCNN(nn.Module):
    def __init__(self):
        super(SimpleCNN, self).__init__()
        # 定义卷积层
        self.conv1 = nn.Conv2d(1, 32, kernel_size=3, stride=1, padding=1)  # 输入1通道，输出32通道
        self.conv2 = nn.Conv2d(32, 64, kernel_size=3, stride=1, padding=1)  # 输入32通道，输出64通道
        # 定义全连接层
        self.fc1 = nn.Linear(64 * 7 * 7, 128)  # 展平后输入到全连接层
        self.fc2 = nn.Linear(128, 10)  # 10 个类别

    def forward(self, x):
        x = F.relu(self.conv1(x))  # 第一层卷积 + ReLU
        x = F.max_pool2d(x, 2)     # 最大池化
        x = F.relu(self.conv2(x))  # 第二层卷积 + ReLU
        x = F.max_pool2d(x, 2)     # 最大池化
        x = x.view(-1, 64 * 7 * 7) # 展平
        x = F.relu(self.fc1(x))    # 全连接层 + ReLU
        x = self.fc2(x)            # 最后一层输出
        return x

# 创建模型实例
model = SimpleCNN()

# 3. 定义损失函数与优化器
criterion = nn.CrossEntropyLoss()  # 多分类交叉熵损失
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9)

# 4. 模型训练
num_epochs = 5
model.train()  # 设置模型为训练模式

for epoch in range(num_epochs):
    total_loss = 0
    for images, labels in train_loader:
        outputs = model(images)  # 前向传播
        loss = criterion(outputs, labels)  # 计算损失

        optimizer.zero_grad()  # 清空梯度
        loss.backward()  # 反向传播
        optimizer.step()  # 更新参数

        total_loss += loss.item()

    print(f&quot;Epoch [{epoch+1}/{num_epochs}], Loss: {total_loss / len(train_loader):.4f}&quot;)

# 5. 模型测试
model.eval()  # 设置模型为评估模式
correct = 0
total = 0

with torch.no_grad():  # 关闭梯度计算
    for images, labels in test_loader:
        outputs = model(images)
        _, predicted = torch.max(outputs, 1)
        total += labels.size(0)
        correct += (predicted == labels).sum().item()

accuracy = 100 * correct / total
print(f&quot;Test Accuracy: {accuracy:.2f}%&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4.2 循环神经网络&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import torch
import torch.nn as nn
import torch.optim as optim
import matplotlib.pyplot as plt
import numpy as np

# 数据集：字符序列预测（Hello -&gt; Elloh）
char_set = list(&quot;hello&quot;)
char_to_idx = {c: i for i, c in enumerate(char_set)}
idx_to_char = {i: c for i, c in enumerate(char_set)}

# 数据准备
input_str = &quot;hello&quot;
target_str = &quot;elloh&quot;
input_data = [char_to_idx[c] for c in input_str]
target_data = [char_to_idx[c] for c in target_str]

# 转换为独热编码
input_one_hot = np.eye(len(char_set))[input_data]

# 转换为 PyTorch Tensor
inputs = torch.tensor(input_one_hot, dtype=torch.float32)
targets = torch.tensor(target_data, dtype=torch.long)

# 模型超参数
input_size = len(char_set)
hidden_size = 8
output_size = len(char_set)
num_epochs = 200
learning_rate = 0.1

# 定义 RNN 模型
class RNNModel(nn.Module):
    def __init__(self, input_size, hidden_size, output_size):
        super(RNNModel, self).__init__()
        self.rnn = nn.RNN(input_size, hidden_size, batch_first=True)
        self.fc = nn.Linear(hidden_size, output_size)

    def forward(self, x, hidden):
        out, hidden = self.rnn(x, hidden)
        out = self.fc(out)  # 应用全连接层
        return out, hidden

model = RNNModel(input_size, hidden_size, output_size)

# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=learning_rate)

# 训练 RNN
losses = []
hidden = None  # 初始隐藏状态为 None
for epoch in range(num_epochs):
    optimizer.zero_grad()

    # 前向传播
    outputs, hidden = model(inputs.unsqueeze(0), hidden)
    hidden = hidden.detach()  # 防止梯度爆炸

    # 计算损失
    loss = criterion(outputs.view(-1, output_size), targets)
    loss.backward()
    optimizer.step()
    losses.append(loss.item())

    if (epoch + 1) % 20 == 0:
        print(f&quot;Epoch [{epoch + 1}/{num_epochs}], Loss: {loss.item():.4f}&quot;)

# 测试 RNN
with torch.no_grad():
    test_hidden = None
    test_output, _ = model(inputs.unsqueeze(0), test_hidden)
    predicted = torch.argmax(test_output, dim=2).squeeze().numpy()

    print(&quot;Input sequence: &quot;, &apos;&apos;.join([idx_to_char[i] for i in input_data]))
    print(&quot;Predicted sequence: &quot;, &apos;&apos;.join([idx_to_char[i] for i in predicted]))

# 可视化损失
plt.plot(losses, label=&quot;Training Loss&quot;)
plt.xlabel(&quot;Epoch&quot;)
plt.ylabel(&quot;Loss&quot;)
plt.title(&quot;RNN Training Loss Over Epochs&quot;)
plt.legend()
plt.show()
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>自动微分与反向传播</title><link>https://ehnotgod.site/blog/%E8%87%AA%E5%8A%A8%E5%BE%AE%E5%88%86%E4%B8%8E%E5%8F%8D%E5%90%91%E4%BC%A0%E6%92%AD</link><guid isPermaLink="true">https://ehnotgod.site/blog/%E8%87%AA%E5%8A%A8%E5%BE%AE%E5%88%86%E4%B8%8E%E5%8F%8D%E5%90%91%E4%BC%A0%E6%92%AD</guid><description>自动微分与反向传播：从零实现 Variable 与梯度计算。</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;1. 变量与函数&lt;/h3&gt;
&lt;p&gt;我们先从最核心的部分开始——变量类（Variable）。
还记得 PyTorch 和 TensorFlow 吗？它们的底层核心都是 Tensor。在我们的实现中，也会有类似的概念，只不过底层我们用的是 numpy.ndarray。所有的运算都基于它来完成。
那问题来了：既然 NumPy 已经能做广播、求和、矩阵乘法这些操作，为什么还要自己再封装一层“变量类”呢？
确实，用 NumPy 我们也能写出各种计算，但它有一个致命的限制——NumPy 不会记录你的计算过程。也就是说，它不知道你是怎么一步步算出结果的，更别提反向传播或者自动求导了。
而我们定义“变量类”的意义就在这里。它不只是一个装数据的容器，而是一个能“记住”自己经历过哪些运算的智能变量。有了它，我们就能自动构建计算图，实现一系列自动化的操作，比如梯度计算、参数更新等等。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import numpy as np

class Variable:
    def __init__(self, data):
        self.data = data
        
data = np.array(1.0)
x = Variable(data)
print(x.data)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;接下来，我们来看一看函数这一块。
先抛个问题：什么是函数？
比如$$f(x)=x^2$$这算不算函数？当然算！这是一个最基础的形式——从一个变量映射到另一个变量。
不过，现实中我们经常会遇到更复杂的函数。比如 sum()，它的输入是多个值，输出却是一个值，这就属于多对一的函数。而且这类函数通常还涉及到数据维度的变化，稍微麻烦一点，我们后面再慢慢讲。现在，我们先从简单的单变量函数入手。
前面我们说过，定义“变量类”的目的，是为了能记录计算过程。那同理，如果我们想要整个计算图完整、可追踪，函数的计算过程也得被“记住”。这意味着什么？——函数也要被实例化！
不过，函数有很多种：平方、加法、乘法、求和、指数……每一种都有自己的逻辑。如果一个个单独写，那代码会一团糟。
因此我们需要定义一个函数的父类（Function），然后让各种具体运算（eg. Add、Mul、Square ）去继承它：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import numpy as np

class Variable:
    def __init__(self, data):
        self.data = data

class Function:
    def __call__(self, input):
        x = input.data
        y = self.forward(x)
        output = Variable(y)
        return output
    
    def forward(self,x):
        raise NotImplementedError
                
class Square(Function):
    def forward(self, x):
        return x ** 2

def square(x):
    return Square()(x)

if __name__ == &quot;__main__&quot;:
    import numpy as np
    x = Variable(np.array(6))
    y = square(x)
    print(y.data)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;解释一下：&lt;strong&gt;call&lt;/strong&gt; 是一个特殊方法，允许一个对象像函数一样被调用。怎么说呢？等价于隐藏了.&lt;strong&gt;call&lt;/strong&gt;()函数，然后使用y = square(x)的时候，先调用了Square里从Function那里继承来的__call__，再利用call内部的forward执行自己的forward。
有一个需要注意的是，我们说过，ndarray执行的是计算功能，所以，定义函数时候的forward的输入不是Variable类型，而是ndarray类型。而执行函数的时候还是输入Variable类型。
还有就是，定义square(x)函数的主要目的是符合我们的习惯，这样可以不用定义两个括号。&lt;/p&gt;
&lt;h3&gt;2. 纯链式正向传播与反向传播&lt;/h3&gt;
&lt;p&gt;对于正向传播，其实我们只需要这么做就行了：square(square(square(x)))不就得了，这样就能很自然地一层一层执行下去......当然，也不一定是square函数，这里只是举个例子，其实换成其他的也行，比如add(exp(x)+exp(y))。
然后我们来研究一下反向传播。
首先，先提出一个概念，叫做计算图，我们之前提到过，它可以长这样：
&lt;img src=&quot;https://ehnotgod.site/_astro/1-1.DIgNhEMi_Z2hl19a.webp&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;假如我们拥有变量y，并且要对x求导的话，由链式法则，有：$$\begin{aligned} \frac{\mathrm{d}y}{\mathrm{d}x}=\frac{\mathrm{d}y}{\mathrm{d}y}\frac{\mathrm{d}y}{\mathrm{d}b}\frac{\mathrm{d}b}{\mathrm{d}a}\frac{\mathrm{d}a}{\mathrm{d}x} \end{aligned}$$，即：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ehnotgod.site/_astro/1-2.kJiBG568_IBAlE.webp&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;我们把$$\begin{aligned} \frac{\mathrm{d}?}{\mathrm{d}?} \end{aligned}$$叫做梯度，在Variable类里添加grad这个属性，类型是ndarray，这一点我相信大家并不会陌生。
首先明确一件事情，我们需要知道什么才能计算梯度$$\begin{aligned} \frac{\mathrm{d}y}{\mathrm{d}x} \end{aligned}$$？我们知道前向过程的输入的变量。举个例子,$$y = x ^ 2$$知道$$x = 5$$之后，就能知道$$\begin{aligned} \frac{\mathrm{d}y}{\mathrm{d}x}=2x=10 \end{aligned}$$了。所以我们需要在Fuction类中添加input属性加以记录就行，然后此次算出来乘起来就行，Fuction的每个函数添加一个backword方法计算。
接下来就是，怎样记录这个计算图？从刚刚的反向传播的步骤我们不难知道这是一个类似链表一样的东西，把变量和函数都视作节点，记住它们的前驱就行了！所以现在你可以回答Fuction类中input属性是什么了，没错，是Variable类型。同理，我们需要记住变量的前驱，在Variable类型里添加creator属性就行，同理，这个属性的类型是函数类型。
我们还有一件事，假如我们顺利地处理出了所有的梯度，但是我们最后要求的可不只是每个的梯度，还有它们梯度的乘积，所以为了把“后面的变量的梯度”传递到前面，我们再给Fuction类添加一个output属性，这样后面的grad就能自然的在反向的时候向前推动了。
对于y = square(exp(square(x)))我们能轻松实现这一过程：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import numpy as np

class Variable:
    def __init__(self, data):
        self.data = data
        self.grad = None
        self.creator = None

    def set_creator(self, func):
        self.creator = func

    def backward(self):
        if self.grad is None:
            self.grad = np.ones_like(self.data)

        funcs = [self.creator]
        while funcs:
            f = funcs.pop()
            x, y = f.input, f.output
            x.grad = f.backward(y.grad)

            if x.creator is not None:
                funcs.append(x.creator)

def as_array(x):
    if np.isscalar(x):
        return np.array(x)
    return x

class Function:
    def __call__(self, input):
        x = input.data
        y = self.forward(x)
        output = Variable(as_array(y))
        output.set_creator(self)
        self.input = input
        self.output = output
        return output

    def forward(self, x):
        raise NotImplementedError()

    def backward(self, gy):
        raise NotImplementedError()


class Square(Function):
    def forward(self, x):
        y = x ** 2
        return y

    def backward(self, gy):
        x = self.input.data
        gx = 2 * x * gy
        return gx


class Exp(Function):
    def forward(self, x):
        y = np.exp(x)
        return y

    def backward(self, gy):
        x = self.input.data
        gx = np.exp(x) * gy
        return gx


def square(x):
    return Square()(x)


def exp(x):
    return Exp()(x)


x = Variable(np.array(0.5))
y = square(exp(square(x)))
y.backward()
print(x.grad)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. 可变数参数&lt;/h3&gt;
&lt;p&gt;前面我们研究的是链式的计算图，接下来我们研究一下多链式图（即“相对链式”，输入之后就是输出的那种图，但是输入和输出参数的数量会变，即“可变的长参数”，无需考虑拓扑序的那种）。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;前向过程：
我们来解决一下上面没有解决的问题，也就是add函数，它接受两个输入，但是只返回一个输出！这种函数是存在的。那么，有没有一个输入两个输出的呢？答案是，这也存在。比如把一个矩阵分成两个小矩阵的函数，甚至分成几个相同的矩阵也不是不行。也存在多对多的情况，比如MLP！所以说，我们需要把Function的input和output处理成inputs和outputs。
其实这不难理解，无非就是添加了一个循环而已。不过需要注意的是，我们为了形式上的统一，把单变量也按照列表管理处理，也就是说，内部的流动不存在单个的形式，都有一定的维度，但是输入和输出允许单个，方便我们的习惯。&lt;/li&gt;
&lt;li&gt;反向过程：
我们的backword函数里，需要进行调整，局部的函数也需要进行调整。
gys是函数是输出层的所有的变量的梯度，利用它们可以“计算”出函数输入层的梯度（这里的“计算”指的是融合，实际上局部梯度只需要函数本身和输入层就够了）。同样道理的，假如gxs是输出维度只有一层就给它套个壳（之前规范过），然后把输入层和输入层的梯度对应上就行了。对应部分的判断主要是应对这种情况：y=add(x, x)。那为啥不初始化为0，直接执行递增呢？设置为None可以节约空间啊！还有一件事，就是，由于py底层问题，此处最好不要使用自加函数哈，会不稳定。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import numpy as np


class Variable:
    def __init__(self, data):
        if data is not None:
            if not isinstance(data, np.ndarray):
                raise TypeError(&apos;{} is not supported&apos;.format(type(data)))

        self.data = data
        self.grad = None
        self.creator = None

    def set_creator(self, func):
        self.creator = func

    def cleargrad(self):
        self.grad = None

    def backward(self):
        if self.grad is None:
            self.grad = np.ones_like(self.data)

        funcs = [self.creator]
        while funcs:
            f = funcs.pop()
            gys = [output.grad for output in f.outputs]
            gxs = f.backward(*gys)
            if not isinstance(gxs, tuple):
                gxs = (gxs,)

            for x, gx in zip(f.inputs, gxs):
                if x.grad is None:
                    x.grad = gx
                else:
                    x.grad = x.grad + gx

                if x.creator is not None:
                    funcs.append(x.creator)


def as_array(x):
    if np.isscalar(x):
        return np.array(x)
    return x


class Function:
    def __call__(self, *inputs):
        xs = [x.data for x in inputs]
        ys = self.forward(*xs)
        if not isinstance(ys, tuple):
            ys = (ys,)
        outputs = [Variable(as_array(y)) for y in ys]

        for output in outputs:
            output.set_creator(self)
        self.inputs = inputs
        self.outputs = outputs
        return outputs if len(outputs) &gt; 1 else outputs[0]

    def forward(self, xs):
        raise NotImplementedError()

    def backward(self, gys):
        raise NotImplementedError()


class Add(Function):
    def forward(self, x0, x1):
        y = x0 + x1
        return y

    def backward(self, gy):
        return gy, gy


def add(x0, x1):
    return Add()(x0, x1)

x = Variable(np.array(3.0))
y = add(add(x, x), x)
y.backward()
print(x.grad)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4. True计算图&lt;/h3&gt;
&lt;p&gt;其实上一节的假设不太好，我们这里直接引入复杂的计算图吧。计算图的本质上是一个DAG（有向无环图），对于一个DAG，给定起点，一定可以进行拓扑排序。拓扑排序的做法有很多种，比如kahn算法抑或是染色法，我们这里使用Kahn算法，但是不完全是Kahn，个人感觉只是使用了Kahn 算法中的“层次”概念。不了解的自己去搜或者尝试证明以下的方法。
假如我需要处理下面这个图，我们之前的方法可能会y→D→c→C→a→A→x...这样是错的，因为处理a一直到x的时候，b和B都没处理，显然和计算顺序不符。
&lt;img src=&quot;https://ehnotgod.site/_astro/1-4.BN6ty61q_Z1fsmLk.webp&quot; alt=&quot;image&quot;&gt;
所以我们使用这个方法：
我们给每个Function还有Variable添加一个generation参数，然后我们在正向传播的时候，假如现在是函数，那么它的输出变量的generation为函数的generation+1。有人可能会问，直接赋值不会受到其他函数的干扰吗？要记住，一个变量只能由一个函数生成！所以不会干扰。函数的generation为它的所有输出层变量的generation的max。具体怎么挑下一个要处理的函数呢？取最大的generation的处理就行了。
理论上，只需要每个Function有 generation就行了，不过为了方便，我们给变量赋值上generation可以利用计算图的完整性，方便执行。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import numpy as np


class Variable:
    def __init__(self, data):
        if data is not None:
            if not isinstance(data, np.ndarray):
                raise TypeError(&apos;{} is not supported&apos;.format(type(data)))

        self.data = data
        self.grad = None
        self.creator = None
        self.generation = 0

    def set_creator(self, func):
        self.creator = func
        self.generation = func.generation + 1

    def cleargrad(self):
        self.grad = None

    def backward(self):
        if self.grad is None:
            self.grad = np.ones_like(self.data)

        funcs = []
        seen_set = set()

        def add_func(f):
            if f not in seen_set:
                funcs.append(f)
                seen_set.add(f)
                funcs.sort(key=lambda x: x.generation)

        add_func(self.creator)

        while funcs:
            f = funcs.pop()
            gys = [output.grad for output in f.outputs]
            gxs = f.backward(*gys)
            if not isinstance(gxs, tuple):
                gxs = (gxs,)

            for x, gx in zip(f.inputs, gxs):
                if x.grad is None:
                    x.grad = gx
                else:
                    x.grad = x.grad + gx

                if x.creator is not None:
                    add_func(x.creator)
def as_array(x):
    ......

class Function:
    def __call__(self, *inputs):
        xs = [x.data for x in inputs]
        ys = self.forward(*xs)
        if not isinstance(ys, tuple):
            ys = (ys,)
        outputs = [Variable(as_array(y)) for y in ys]

        self.generation = max([x.generation for x in inputs])
        for output in outputs:
            output.set_creator(self)
        self.inputs = inputs
        self.outputs = outputs
        return outputs if len(outputs) &gt; 1 else outputs[0]
......
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;seen_set的意思是，目前添加过哪些函数，就不能二次添加了。每次添加函数之后进行一轮sort，看似复杂度爆炸，实则模型里的函数数目其实比变量少多了，这点复杂度不需要节约，加个优先队列反而需要占用更多空间。&lt;/p&gt;
&lt;h3&gt;5. 一些必要的优化&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;内存管理和循环引用&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Python使用两种方式管理内存，一种是引用计数，另外一种是分代垃圾回收。引用计数的原理很简单，当它被另一个对象引用的时候，引用计数+1，停止引用时，引用计数-1。引用计数为0时，Python解释器会回收该对象。这很智能，但是它不能处理循环引用，所以单纯设置None无法解决这个内存问题。不过分代垃圾回收机制可以解决这个问题，具体原理很复杂这里不多说了。他会在内存不足的时候自动调用，也可以gc.collect()主动调用。
在 Python 中，我们可以使用 weakref.ref 函数来创建弱引用。弱引用是在不增加引用计数的情况下引用另一个对象的功能。我们需要破除右侧的循环，可以给outputs打上weakref，就可以自动破除了。weakref的引用方式是后面加个&quot;()&quot;。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;while funcs:
    f = funcs.pop()
    gys = [output().grad for output in f.outputs]  # output is weakref
    ......
        if x.creator is not None:
            add_func(x.creator)
    ......
    class Function:
    def __call__(self, *inputs):
        ......
        self.inputs = inputs
        self.outputs = [weakref.ref(output) for output in outputs] # output is weakref
        return outputs if len(outputs) &gt; 1 else outputs[0]
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;自动消除不必要的内存&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;一、在反向传播的时候，中间用过的梯度，在很多情况下，可以不要了。&lt;/li&gt;
&lt;li&gt;二、我们知道，pytorch里，有个功能是model.train()，还有功能是model.eval()。这两个的区别之一就是后者不需要保留中间层的输入变量。
对于第一点，我们可以在backword函数处设置一个开关，自主选择是否需要中间的梯度：
对于第二点，可以使用@contextlib.contextmanager修饰器，它可以让你用 生成器函数 来实现 with 语句的行为，实现“在局部时间”内的控制操作。&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;添加一些同步化的优化
有一点需要明确，虽然我们使用了numpy的ndarray作为计算底层，那么我们为何不整一个numpy-plus呢？简单来说，我们可以添加一些函数让它更加易用！
numpy里有个东西叫做x.shape，这里我们实现一下：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;@property
def shape(self):
    return self.data.shape
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;dtype，ndim，size同理，len可以重载__len__实现，print可以重载__repr__实现，这里不赘述了，包括运算符的重载。
有个需要注意的是，由于函数是实例化的，它可以存东西。这一点很重要，后续的矩阵运算也依赖维度的信息存储。比如幂函数：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class Pow(Function):
    def __init__(self, c):
        self.c = c
    def forward(self, x):
        y = x ** self.c
        return y
    def backward(self, gy):
        x = self.inputs[0].data
        c = self.c
        gx = c * x ** (c - 1) * gy
        return gx
def pow(x, c):
    return Pow(c)(x)
Variable.__pow__ = pow
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;原书有绘制计算图以及求高阶导数等步骤，在这里我们将掠过，这部分内容不需要。&lt;/p&gt;
&lt;h3&gt;6. 可变长参数&lt;/h3&gt;
&lt;p&gt;前面我们研究过“可变数参数”，这是啥意思呢？通俗的说就是，输入和输出的变量数目不一样，比如add，会把两个变量聚合到一个变量上面去。这里我们讲的可变长参数的主要是，变量内部的维度。比如sum函数，它可以把一个1*n长度的变量变成一个标量，对应的，它的正向传播和反向传播也得写清楚，还有更难的，比如矩阵呢？这个在接下来我们会一步一步讲。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;reshape函数
这个函数比较容易理解，所以我打算把它放在最开始去讲。
回忆一下，我们之前讲幂次函数的时候，我们说过，要记录一个东西是吧，也就是幂次本身。我们的reshape函数同理，要记录一下原来的维度（x_shape）以及改变后的维度(shape)。numpy里自带了reshape函数，所以计算过程直接使用就行。对于反向过程，我们直接reshape回去就行，梯度数值不需要进行处理。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class Reshape(Function):
    def __init__(self, shape):
        self.shape = shape

    def forward(self, x):
        self.x_shape = x.shape
        y = x.reshape(self.shape)
        return y

    def backward(self, gy):
        return reshape(gy, self.x_shape)
        
def reshape(x, shape):
    if x.shape == shape:
        return as_variable(x)
    return Reshape(shape)(x)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用方法比较反人类：y = F.reshape(x,(6,))，而我们平时可能使用惯了y = x.reshape(2, 3)这样，所以我们可以修改一下Variable类，去实现这个便捷设计（略）。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;矩阵乘法&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;求导：
设定：
令$$Y = A B$$其中$$A \in \mathbb{R}^{m\times n}$$，$$B \in \mathbb{R}^{n\times p}$$，$$Y \in \mathbb{R}^{m\times p}$$上游标量损失为L，并记梯度矩阵$$G = \frac{\partial L}{\partial Y} \in \mathbb{R}^{m\times p}.$$。
则$$\boxed{\frac{\partial L}{\partial A} = G,B^{\mathsf{T}}}$$，$$\boxed{\frac{\partial L}{\partial B} = A^{\mathsf{T}},G}$$。
推导：
每个元素：
$$Y_{ij} = \sum_{k} A_{ik} B_{kj}.$$
用链式法则：
$$\frac{\partial L}{\partial A_{uv}} = \sum_{i,j} \frac{\partial L}{\partial Y_{ij}} \frac{\partial Y_{ij}}{\partial A_{uv}}
= \sum_{i,j} G_{ij},\delta_{iu},B_{vj}
= \sum_{j} G_{uj} B_{vj},$$
这就是矩阵形式
$$\frac{\partial L}{\partial A} = G B^{\mathsf{T}}.$$
对 B 同理：
$$\frac{\partial L}{\partial B_{uv}} = \sum_{i,j} \frac{\partial L}{\partial Y_{ij}} \frac{\partial Y_{ij}}{\partial B_{uv}}
= \sum_{i,j} G_{ij},A_{iu},\delta_{jv}
= \sum_{i} A_{iu} G_{iv},$$
因此矩阵形式为
$$\frac{\partial L}{\partial B} = A^{\mathsf{T}} G.$$
故矩阵乘法就很简单了：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class MatMul(Function):
    def forward(self, x, W):
        y = x.dot(W)
        return y

    def backward(self, gy):
        x, W = self.inputs
        gx = matmul(gy, W.T)
        gW = matmul(x.T, gy)
        return gx, gW


def matmul(x, W):
    return MatMul()(x, W)
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item></channel></rss>