Exemplary Examples I
本文和网络上大部分题解/题解集不同,着重于介绍思路“从哪里来”“如何注意到”。如果在日常练习中,你也有“为什么要这样想”的疑问,希望本文对你有所帮助。本文将会展示 The Art of the Induction 的实际应用。
作者学艺不精,而且这些题目基本都没有实现代码,因此存在错误的概率无限接近 100%。但这些错误不能被读者大人巧妙修复的概率接近 0%。还有表达不清的部分请多包涵。不同的人情况不同,我的思路仅作参考,希望不会给你带来误导。推荐结合其它题解阅读。
祝大家前途光明。
皇上四哥,你赢了。你赢了。可是弟弟告诉你,如果你不是皇上,你是赢不了的。
四哥,我输了,可你呢,却并没有赢。你咬着牙苦苦的熬着,得到了什么?得到的只是身前身后的骂名罢了。
其实,输也罢,赢也罢,到头来不过是过眼烟云,你太放不下了。
弘时啊,八叔和你阿玛斗了一辈子,最终没有赢。可是如今呢,你要肯去争,只要去争,也算了却八叔的一个心愿。
天若助我,我学周公。天若灭我,我也决不会自绝于列祖列宗。
- 从 2025 年 7 月动笔写起,题目依写题解时间排序。我想你可以从一篇篇题解中看出我的成长历程。
- 本文中标点符号的应用。引号通常标出“长难句中的一个整体”,例如主语,宾语等,就像刚才那样。括号内是补充说明(也可视为批注),就像刚才那样。
- 致谢 C202044zxy, YeahPotato, Alex_Wei, command-block 的博客。感谢艾教、徐教、李教;还有代码源的蒋老师、杜老师。
upd on 2025-10-27:从即日起,在每个题号后,增加写题解的日期。
upd on 2025-11-18:在日期后,增加了主观难度评分(大致是指思维难度,完全没有考虑代码难度),分九档 S*,S+,S,A+,A,B+,B,C,D。
upd on 2026-03-16:把整篇文章拆成若干小篇。从即日起,每大约 25000 个汉字开新一篇。
upd on 2026-04-07:从即日起,对于比较过分的跳步(未能理解为何这样想),用 (*) 注明。减少加粗的使用。题解更加简略。
T-001 CF1477C D
从结果去考虑问题。从反面来看,假设答案中存在某连续三个节点 \(A,B,C\) 使得 \(\angle ABC\) 为钝角。怎么去刻画钝角呢,把初中知识复习一下,其必要条件是三角形内的最大角,大角对大边嘛,所以 \(|AC|>|AB|,|AC|>|BC|\)。只要让这个必要条件不满足,就能防止钝角出现。依次做决策,每次往答案末尾添点,设当前末尾点 \(A\)。决策下一点时,选 \(|AB|\) 最大的 \(B\) 即可。
T-003 P3590 C
首先差分之后转四维偏序是秒杀,但是会超时。扫描右端点。看一下限制:差分后三个维度都不相等。
假如是一维问题怎么办?记一个最小值位置和一个次小值位置,满足他们两个这一维不相等不就完了。那两维呢?记一下最小值,一个与最小值第一维相同的次小值,一个与最小值第二维相同的次小值,一个与它两维都不相同的次小值。三维呢?完全一样可以维护 \(O(1)\) 个最小值,总复杂度 \(O(n)\)。但需要 \(O(n)\) 吗?不需要。那么 \(n\log n\) 怎么去做呢?
第三维作为权值线段树的权值,每个点维护“第三维等于它时的上述两维的四个值”,显然我们所维护的信息可以合并。每次查询一个前缀和一个后缀(把第三维与查询的相等的空过去)即可。
T-004 CF2101C B
我多次强调,你从答案去考虑问题嘛。你要得到答案最优所满足的必要条件嘛。答案是什么?若干个配对。配对和区间是类似的。
那么区间有什么特殊的条件呢,就是包含,相交和相离嘛。那它能有相离吗?不能。选择两个相离区间的最左和最右端点,形成一个新配对,这一个就比原来两个加起来价值要大。它能有相交吗?可以不用。选择中间两个和两边的两个,构成包含关系的两个配对,不劣。所以能调整成嵌套的结构。嵌套有什么呢?所有的左端点都比所有的右端点要小。那左端点和右端点有关系吗?没有。把左端点和右端点分开考虑。
答案还有什么?它每个配对的值是不一样的。那所有的值排个序,它一定是从 \(1\) 到 \(k\) 连续的,如果不是,可以调整,因为值可以随便减小嘛。
所以记录 \(\mathrm{pre}_i\) 表示从 \(1\) 到 \(i\),最多能够放置 \(\mathrm{pre}_i\) 个左端点,满足这些左端点分别值为 \(1\) 到 \(\mathrm{pre}_i\)。\(\mathrm{suf}_i\) 类似。求 \(\max_i\min(\mathrm{pre}_i,\mathrm{suf}_{i+1})\) 即可。
T-005 CF2072G D
又是非常简单的题目。要快速计算 \(\sum_i f(i)\),就必然是,简化 \(f(i)\) 的计算方式,或者同时计算若干个 \(f(i)\) 的值,或者用以前的 \(f(j)\) 推出 \(f(i)\),或者改变贡献顺序/改变贡献主体。
它能简化吗?能同时计算吗?能递推吗?能改变贡献顺序吗?都不能。为什么呢?因为最基本的,不同 \(p\) 进制下的位数不一样。我们要让它一样。考虑到数据范围,推出算法复杂度 \(O(\sqrt n)\),根号分治。当 \(p<\sqrt n\) 暴力,\(p>\sqrt n\),进制下恰好都是两位,化式子后整除分块,也就是同时计算,即可。
T-006 CF2070E D
极简单的题目。你分别思考一下两位玩家。先手看着很菜,而后手很强。但凡当前有 1,后手不会输。1 是谁消耗的?只有后手,所以后手要保护自己就要少消耗 1。如果可以消耗 10/01,后手会消耗 11 吗?不会。什么时候后手必须消耗 11 呢?不可能,这时候他已经赢了。所以后手只消耗 10/01。
这个时候你可以用势能去分析嘛,一个回合必然先手消耗 00,后手消耗一个 1 一个 0。所以如果 1 的个数大约超过了 0 个数的三分之一,后手必胜,反之必败。边界情况再讨论,答案随便计算即可,不提了啊。
T-007 CF1815C B
这里面的限制很奇怪,怎么去处理呢?减小限制的强度,从数量关系的角度考虑。我认为这也是类似于势能法的,同样都是去考虑一个特殊的数值的变化情况,用少量信息描述整个限制/序列的情况。
那么有什么数量关系呢?一个限制,每两个 \(x\) 中间至少有一个 \(y\),那不就是 \(y\) 的个数 \(\ge\) \(x\) 的个数减一吗?差分约束,即可求出每种数的个数。
构造方案不难,还是提一下。若 \(x\) 的出现次数比 \(y\) 要小,那限制“\(y\) 中夹 \(x\)”可能存在也可能不存在。无所谓,都可以让它满足。若 \(x,y\) 出现次数相同,则 \(x,y\) 间不可能有什么限制,所以把出现次数相同的元素合并起来处理,也就是所谓“我和你没关系,但是所有的‘他’和我们都有关系的,合并起来”,这是限制存在于两个元素之间的情况。还有与此题无关的,限制不在于“我你他”之间,而在于元素和其它什么东西之间的情况,就是“我的限制已经全被满足的和你合并起来”。然后就真的不用提了。
T-008 CF1909F2 D
F1 是极简单题。F2 依次确定每个 \(a_i=-1\) 的实际 \(a_i\) 然后根据这个 \(a\) 统计符合条件的 \(p\) 是不行的。考虑对于任意排列 \(p\),只要它满足在 \(a_i\neq -1\) 位置的限制,则必然存在一个合理的 \(a\) 将它统计。故只需关注“框架”,即依次考虑最近的两个 \(a_j,a_i\neq -1\) 的位置中间的一段区间。组合计数你肯定会。
T-009 CF2085F2 B+
一种想法是我不带脑子,每次选择我觉得“合适”的元素,然后把它移到靠近其他人的地方来。但如果你带脑子,你发现整个过程可以分两步,第一步是选出 \(k\) 个位置满足它们的颜色分别为 \(1\) 到 \(k\),第二步是将它们移到一起。
那么如果知道是哪 \(k\) 个位置,怎么去移呢?一定是把它们朝中位数位置移动。所以中位数位置很重要,枚举之。枚举了以后,中位数左右两边有关系吗?没关系嘛。拆开。显然对于每个颜色 \(i\),都只有左右两边各自离中位数最近的那个元素是重要的,分别记为 \(l_i,r_i\)。这个中位数还有一个好处,就是只需求出所需要的元素到中位数的距离之和,再减去 \(\sum^{k/2} 2i\) 即可,无需分辨当前元素是左/右边的第几个元素。
我们要两边各自选择 \(\frac{k}{2}\) 个元素,它们还互不相同,这是很困难的。有两个限制,你先满足一个,然后调整答案使另一个满足。先满足谁呢?肯定是“互不相同”的条件难满足,就先满足之,比如说各个颜色强制取左边的。然后按照 \((r_i-\mathrm{mid})-(\mathrm{mid}-l_i)\) 排序嘛,将此价值最小的 \(\frac{k}{2}\) 种颜色换成右边的。\(\mathrm{mid}\) 移动一步时这个排序后的数组只有 \(O(1)\) 的改变。因此简单维护即可,可以借助对顶堆算法。
T-011 CF2089C2 D
我多次强调,要关注对称性,减少思维的遗漏嘛。什么是对称性呢?我想到了,可能紧着一个钥匙去试所有的锁,但是没有想到与之对称地,也可以紧着一个锁去试钥匙。那么具体选择哪一个呢?第一个人肯定随便选,第二个人你算一下,发现继承上一个人的钥匙/锁的胜率是一样的。那第三个人呢?分析一下就发现跟第二个人所继承的东西是一样的,不然不优。所以顺着就想下来了,没有什么难的,就是细节多有点难打。
T-012 QOJ2559 S
我一再强调,不管简单题还是难题你都要一步步把它推出来嘛。区间呀!没想法吗?区间有什么特殊关系呢?包含嘛。如果没有包含怎么做呢?如果只有包含怎么去做呢?这些问题即使没有对应部分分也可以去想。
如果没有包含,可经典地按左端点排序(因若没有包含,依左/右端点排序后的对应区间完全相同)。我们要求什么?每次覆盖一个区间;求每个区间未被覆盖的长度大小。怎么去处理呢?如果暴力去做,可依次考虑每个区间被新覆盖的长度大小。就是要同时求多个本质相同的问题的答案嘛,那么必然是,简化单个问题的计算方式,或者同时计算若干个问题的值,或者用一个问题的答案推出另一个,或者改变贡献顺序/改变贡献主体。用排除法,对一个问题 \(O(1)\) 都不够快,故一/三方法不行。第二个能行吗?因区间差异过大,也不能。
所以改变贡献主体。就是说以“新被覆盖的段”为主体去贡献每个区间。那就要关注它对不同区间的贡献是什么样的,才能方便地处理。从函数的角度考虑,它是一个以编号为横轴的分段函数。函数的分界点在哪里?当相邻两个区间的左端点均在“新段”外,右端点在“新段”内,且右端点不同时,存在分界点;右端点均在内且不同,同时左端点均在外时,也有分界点,没有其它情况。
这是覆盖问题,一段只会覆盖一次,完全可以扫描“新段”的每一个位置(每个位置只扫描一次),计算是否存在分界点。那就是说可把“新段”依左右端点离散化后得到的单位区间分裂。这时候你发现有别的简单做法,不必计算分界点了(计算也可,本质相同)。因单位区间没有关系,拆开。那么对每个单位,它贡献到的区间编号也是一段区间(利用了排序),剩下的比较容易,就不提了。
那如果“只存在包含和相离”怎么处理呢?这里有什么性质?一个区间所包含的所有区间都处理后才可能处理它自己。现在把对两种特殊性质分别的想法统一起来。从第二种性质的思路得到,只有当某区间包含的所有区间都处理完后,才需考虑它自身。这有什么好处呢?任意时刻,所有区间无交。于是可转化到“区间无包含”的情况。
要去做这个转化,分两步。第一步是要知道何时开始考虑区间,第二步是要知道当前区间,在所有正在被考虑的区间中,排序后在哪里(因为针对第一种性质的做法,需要排序)。第二步好办,因区间不交,只需关注左端点的顺序,可在初始时按左端点排序所有区间,则新编号就是顺序。
对第一步,本质上什么时候能将区间 \([l,r]\) 加入呢?拿数学式子表示,\(\forall l_j\ge l,r_j>r\)。就是说它的 \(r\) 比“新编号比它大的,且未加入队列的区间的 \(r\)”都小,那就是后缀最小值嘛。具体地,记录 \(c_i\) 代表左端点为 \(i\) 的区间中,未加入队列的 \(r\) 最小是什么,若无这种区间则为 \(+\infty\)。则无包含他人的区间 \([l,r]\) 满足 \(c_l\) 为严格后缀最小值。每一次新覆盖的区间 \([a,b]\) 从“未加入队列的区间”集合中删去后,产生的变化就是 \(c_a\) 增大。这形成了哪些新的后缀最小值呢?单侧递归线段树可以做。不过因为每次修改比较特殊,新的后缀最小值只会在 \(a\) 前面出现。故也可用普通线段树维护 \(c\) 数组,查询下一个后缀最小值时直接进行线段树上二分。另外,无法用单位区间去贡献“未加入区间”的覆盖程度(若要贡献则失去所谓“下标也是区间”的性质),只需新建一个线段树/树状数组表示当前每一个单位区间的覆盖情况,每当加入一个新区间时直接查询其覆盖程度即可。
T-013 CF2030G2 B+
非常简单的题目。你看啊,这个问题是分两步,第一步确定一个子集,第二步是计算此集合的答案。
你可以先从暴力想过去,若枚举了这个子集,则最小点取在什么地方代价最小呢?要求这个问题,只能依次枚举最小点,每个点的代价可以通过上一个点的代价,加上“左右不包含当前点区间数目之差”递推得到。有什么特殊性质呢,就是它有点像“将一些点移到相邻”的例子(这个的最优方案是都移到中位数),这个代价关于点的函数是凹的。
好,这就够了,现在不想它。如果你对同时求多个本质相同的问题计算方法已经掌握了的话,就知道只能是改变贡献主体。那就是说枚举公共点,计算有多少符合条件的子集。那符合条件子集所对应的函数的极小值点集合必然包含公共点,换句话说 \(f'(x)=0\),而 \(f'(x)\) 就是左右不包含公共点的区间数目之差,故左右区间数目相同。但是 \(f'(x)=0\) 可能有多个。怎么去去重呢?只有钦定标号最小者计入,或者容斥。因为 \(f(x)\) 是非严格凹函数,所以显然极小值点集合是个区间,且对于每个取到极小值的公共点来说,子集内,它两侧与它不交的集合都是一样的。区间有什么呢?区间有两个端点嘛。
所以法一,钦定标号最小者计入。此区间的左端点必然是“左侧集合”最大的右端点。于是枚举“左侧集合”最大右端点位置,则只要满足“右侧集合”与“左侧集合”区间数相同,就在此处取到最小值,对一个子集只计算一次(因只一个最大右端点),没重复。
法二,点减边容斥。整数区间内整点个数总比区间长度多一。那么计算每个点,只满足“右侧集合”与“左侧集合”区间数相同的答案,求和;计算每个单位长度,只满足上述条件的答案,求和。两和相减即为答案。两种方法推式子时都需要范德蒙德卷积。
T-014 CF2029G A
又是极简单的一道题。你看到这个题就应该先分析操作性质嘛。每次对前缀/后缀操作,考虑差分嘛。差分数组是什么样的呢?要什么样是什么样嘛。后缀操作可以令其加一,前缀操作令 \(a_1\) 加一,且某位置差分数组减一。那么该操作没有带来什么性质。
所以暴力 dp 只能从左向右去做嘛。状态起码需要记录当前位置,以及求的 \(v\)。可以记录后缀操作位置在当前位置及之前有多少次。那前缀操作怎么去处理呢?需要处理吗?不需要。你从左往右 dp,前面的前缀操作和当前有关系吗?没有。我说你要充分利用当前状态嘛。于是在当前位置进行一次前缀操作转化成,我们的 \(v\) 变成 \(v-1\)。仅状态复杂度就在 \(O(nV^2)\)。
怎么去优化呢,必然是有状态无用嘛。状态无用你先考虑记忆化(其实还可考虑状态答案互换,但也不行)。假如说做出实际决策的状态不多,记忆化就能砍掉状态了,但是这里能行吗?不能,因为做出实际决策的状态很多。你又说状态无用,那就是决策无用,就是操作无用。什么操作无用呢?操作有什么,有操作位置和次数嘛。考虑操作位置,我们的主元是什么,是 \(a_i=v\)(最终值)的位置,其它位置变成什么没关系的嘛。那我操作在其它位置上有用吗?没有,可以调整成在 \(a_i=v\) 位置上操作。于是从逐步转移变成断点转移(主元法标配),若知道 \(a_i\)(初值)和 \(v\) 即可求得对应的后缀操作数。状态 \(O(nV)\),显然的树状数组优化,时间复杂度 \(O(nV\log V)\),还不够。
现在要砍什么复杂度呢,不是 \(V\),因为题目给了 \(V^2\) 的限制,看着不像要砍它。是 \(n\),\(n\) 是元素个数啊,那么元素不能有那么多,必然需要合并。怎么去做呢,其实题目有提示的啊。它的初始值是从操作给出的,为什么不是直接输入?而通过若干次操作生成的序列本身没有什么特殊性质(开头分析的),这意味着用操作给初值不是为了令序列保持特殊性质。那它能够帮我们合并元素吗?可以。初值相同的连续段可以认为是一个元素,合并后新 \(c\) 为其和嘛。而操作后值大于 \(V\) 的段无用。因此总复杂度 \(O(V^2\log V)\)。
T-015 CF1983G B+
我说你既要记得我教你的处理多个本质相同式子的方法,也不要忘了预处理嘛。怎么去处理这个 \(\sum a_{p_i}\oplus i\) 呢?同时计算多个 \(i\),或者同时计算多个询问是不可能。如果递推,也就是树上莫队,也不行。改变贡献主体,这个非常非常难维护的。那怎么去做呢?只有是这个式子本身具有特殊性质嘛。
异或呀!没想法吗?异或是位运算嘛,可以拆位。转化成求从 \(x\) 到 \(y\) 一段零,一段一,无限循环的序列每个位置异或上对应 \(a_{p_i}\) 的某一位嘛。你发现莫队还是不行的,若改变主体,有一个启发式合并做法(把经过当前点的,起点/结尾深度模 \(2^i\) 相同的路径统一计算,利用带权并查集处理),但是即使是对的也特别特别难写。那么必然要利用这个零一序列的性质嘛。
你说什么性质?周期性嘛!一点若为一,则它的 \(2^i\) 级祖先也为一。可以怎样呢?递推预处理嘛。这是树上路径问题呀!可以树剖(此处不行),也可以预处理,就是树上差分(例如树上主席树,每个节点上的版本记录从根到它的数据)这个是非常非常经典的啊。刚才(未拆时)不能差分,现在可以了。
你看啊,假如只考虑起点到 lca 的一段,显然开头是一段一。所以预处理以 \(x\) 开头,向祖先方向到根,开头是 \(2^i\) 个一,具有 \(2\times 2^i\) 为循环节的序列的答案。那 lca 处若不是以 \(2^i\) 个一开始怎么办,无法差分了。你把从 lca 开始的不完整周期直接算掉,同时找出距离它最近的完整周期开始位置即可。对于结尾到 lca 也是类似的,因为这是循环序列嘛,具有对称性。
T-016 CF2039E B
非常简单的题目,我们用势能法。势能法就是要加一个关于整个序列的状态函数,你随便看一眼就知道是逆序对数最合适嘛。每做一次操作肯定会让逆序对数增加或不变嘛。若想不变只有插入到最后一个位置。所以最后有一大段值和序列逆序对数一样的元素。插入的时候,只要不放到最后一段就能够增加逆序对数。
这是计数题嘛,要求不能重复,就是说我们希望一个最终序列对应一个操作序列。回到前面。每次插入的元素单调不降啊!你把值相同的拉出来考虑嘛,因为它们在操作序列中相邻嘛。显然,一旦把元素插到前面去,就不可能再把值和它一样的元素插到最后面了。所以最终序列里,同值的元素只存在一个单点和一个连续段(这里不考虑特殊的零元素),且所有这种元素的插入顺序是定的,就是说一一对应了。
那看上去,动态规划肯定和最后一段长度有关嘛。这时候你肯定有一个简单 dp 状态 \(f_{i,j}\) 表示当前序列长度为 \(i\),最后一段长为 \(j\)。已经可以转移了,但是这个状态都是炸的嘛。
但是我说动态规划要对问题服务嘛,你要求什么呢?长度为 \(n\) 的序列个数。跟最后一段长度有关系吗?没有,所以把 \(j\) 删掉。但是删了不能影响转移。故只需记录一个 \(i-j\) 就足够转移了(因为转移系数就是 \(i-j\))。前缀和优化即可。
T-017 C
模拟赛题,其实非常非常简单一个题,但是标程被爆了,只介绍能够爆标的优化。就是你要状压一个 lis 数组 \(c\),\(c_i\) 表示当前以 \(i\) 结尾的 lis 最大长度。转移时,显然若插入了一个值 \(a_i\) 则 \(c_{a_i}=\max_{j<a_i} c_j+1\)。
我说状态要为转移服务嘛,这个转移跟前缀最大值很有关系的,跟 \(c\) 本身有关系吗?没有嘛,所以改成记录前缀最大值,可以只记录差分数组嘛。不提了啊。
T-018 CF2018F3 A+
又是一个计数题目。共 \(k\) 个起点呀!没想法吗?随便猜一下或者打一下表,就能得到对一个原序列,合法起点应是一个区间。但是我们很难处理“恰为 \(k\) 个”,也就是合法区间极大长度为 \(k\) 的问题。我说极大性限制比存在性限制强,存在性限制比所有性限制强嘛。强的条件要转成弱的。
怎么转化成存在性呢?容斥。于是求“至少存在一个长度为 \(i\in [k,n]\) 的合法区间”即可。再看合法起点是区间有什么好处?区间是连续的嘛,故便于枚举。不妨枚举那个“至少存在”的区间,dp 计数即可。但是原序列不好计数(若好计数,可按原序列为主体,先考虑如何判定其合法性,后 dp 判定过程)。转换主体,变成求,对某操作序列,有多少通过此操作序列能够处理成功的原序列。那就要求一个原序列对应一个操作序列嘛。但是这里显然是一对多。怎么去处理呢?
一般方法只有,所有合法操作序列钦定一个字典序,对同一原序列只让字典序最小的操作序列统计它。那如何确定字典序呢?分析性质,用某简单方法表示操作序列,就能找出字典序。你看它能怎样操作呢?只能操作当前扩展到的区间 \([l,r]\) 的 \(l-1\) 或者 \(r+1\) 嘛。那不妨用 0/1 表示每一步操作了左边还是右边,代表操作序列,直接让其字典序最小。这样,肯定是想尽量操作左边。那什么时候必须操作右边呢?就是 \(\exists i>r,a_i\le i-l+1\)。
接下来是 dp(因不可能枚举操作序列,故只能一边生成操作序列,一边计算合法原序列个数,只能用 dp 计算)。其核心在于,关注操作序列的生成过程(即向左向右扩展区间),求对应原序列个数。原序列应满足什么呢?第一是操作序列合法;第二是操作序列是字典序最小的,也就是说如果不得不向右走了,则必然在右边存在一个上述的点。具体如何呢?在扩展某位置之前,不可能确定该位置的值嘛,因为未来可能导致冲突。
综上定义状态 \(f_{l,r,0/1}\) 表示,当前这个操作序列已操作了 \([l,r]\),也确定了 \([l,r]\) 内的原序列值,现在是否被强制朝右走,在已经确定的原序列部分中,共有多少种可能性。转移时,每扩展一步才定下新位置原序列的值,作为转移系数乘进去。需要满足这个值不会导致,按照我们的操作序列处理原序列失败(因要合法);且,若向右走到某点后决定,我这个操作序列现在要取消向右走的强制,也就是说此点必然是导致你当初必须向右走的最右边的诱因。首先,这个点必只有一种取值;第二,需满足更右边没有其它强制点(实不需考虑。一旦做出决定,下一步向左走之后,对更右边值的限制就加强了,使得它们不会在当初强制向右走)。而赋初值就是令外层枚举的那个区间 \(f_{L,R,0/1}=1\)。不过这个复杂度 \(O(n^4)\) 不对,主要是枚举区间看着不必要。
这是要对若干个区间求本质相同的问题,而这些问题的最终状态(就是 \(f_{1,n,0/1}\))是相同且确定的。可以通过倒求贡献系数的技巧处理,即可。
T-019 CF2034F2 B
非常非常经典的题目。按常规思路,问题相当于从 \((0,0)\) 走到 \((n,m)\) 嘛,关键位置是卷轴位置嘛,将其作为框架去断点转移,能够处理“至少经过”某些卷轴的方案数。但是我们要计算“翻倍”,需要求出恰好经过某些卷轴(不经过其它)的方案数,显然你去容斥嘛(每次转移时容斥),能够通过 F1。
但是我说你见到这种带权计数问题,就要考虑组合意义嘛。什么叫带权呢?例如求所有某某子序列长度的平方之和,又如这里关键的操作“翻倍”。前者转化成在子序列内选择两个点,有多少种选法(可进一步,改变主体,为枚举两个点,有多少子序列包含之)。那么这里关键操作“翻倍”如何去处理呢?从每一个元素的贡献去考虑嘛。对一个宝石,选择它之后又拿走了几个卷轴,则其价值翻几次倍,就是 \(2^i\)。\(2^i\) 的组合意义是什么,是子集个数嘛。那么还需要容斥吗?不需要。“至少经过”本来就意味着实际经过的卷轴是这里的超集。一个实际的经过集合,被其所有子集各自作为“至少经过”统计了一遍。故直接 dp “至少经过”,同时无需翻倍即可。
T-020 CF2013F2 A
先考虑 F1。博弈呀!没想法吗?一个是要考虑 SG 函数嘛(此处连影都没),另一个是每个人都是先想我赢,再想不让别人赢嘛。这里是第二个。我想赢,怎么去做呢,肯定是直接找距离自己最远的地方一直走就行了。而怎么阻止别人呢,肯定是朝他的方向走嘛。所以两个人策略都应该是先朝对方行走,到某点把对方的一个很远的方案堵死后,自己不再向对方行走,而是向着对方的那个方案走下去了。
首先,只有两个人之间的一条链是关键的,于是记录这条链从 \(1\) 到 \(u\) 为 \(p_1,p_2,...,p_k\)。记录 \(f_x\) 表示从 \(x\) 开始不经过任何链上的点所能到达的最远距离。你发现两个人的策略和对方的关系已经拆开了,两个人均不需要考虑对方现在在哪里。那么可以尝试对两个人分别求出些东西。对 Alice,可以求出最小的 \(l:l-1+f_{p_l}>\max_{t>l}\{f_{p_t}+m-p_t\}\),这里就是 Alice 必胜所至少需要沿链走到的位置。同理,Bob 的那个位置可以求出,看谁的更远即可。
那么 F2 怎么处理呢?首先 Alice 不动嘛,她所涉及到的关键链本质上一共只有两条 \(1\to u,1\to v\)。故分别求出按照此两条链,Alice 至少要走到哪里。然后,对 Bob,他从 \(u\to \mathrm{lca}(u,v)\),其“位置”越来越向上,具有单调性,二分一下就完了。细节耐心处理一下即可。
T-022 pjudge21655 B
强连通分量呀!我一再强调这种从零开始构造连通分量(边双/强均可)的问题,你去考虑耳分解嘛。那就是说对已知连通分量 \(S\),若有路径 \(i,j\in S,i\to j\) 则 \(S\cup(i\to j)\) 也是连通分量,这样每种方案均能考虑到嘛。
那怎么去做呢?状压 dp,每次枚举路径去转移,这个复杂度不对。我说 dp 如果转移复杂度太大,你可以从断点转移变回逐步转移嘛。怎么去逐步呢?就是最后一个操作没有结束嘛,这里就是最后一个路径没有终止嘛。而状态就要相应增加几维,这是为了记录最后一个操作的信息嘛。所以肯定至少要记 \(f_{s,x}\) 表示原有强连通集合,并上目前的路径为 \(s\) 集合,而现在到了 \(x\)。
但是如何保证当前路径的终点也在原强连通分量内呢?你总不能把当初的连通分量情况记到状态里嘛。也就是说只有“新建路径”的时刻才知道所有合适的终点有哪些,只有此刻可以把一些信息记到状态里,传到未来嘛。那如何去处理呢?只有传一些有关终点的东西嘛,就只能是终点本身嘛,就是说再加一维状态,表示当前路径钦定终点是谁。即可。
T-026 CF660E B+
首先你有两个想法。一个是改变贡献主体,枚举子序列。另一个是考虑对于一个确定的序列,我们怎样算它的子序列个数,据此往下想(关键的是 lst 数组。然不行)。这里使用第一个方法。
这里子序列会算重。我一再强调,如果算重怎么去去重呢,要么容斥,要么钦定最小计入嘛。我们用第二种方法。什么叫最小计入呢?就是原序列的,所有和给定子序列相同的子序列中,只有下标集合字典序最小的那一个,进行贡献嘛。那我们枚举了给定子序列,以及其下标集合,如何填入原序列的其它元素,保证其字典序最小呢?
这个不好做,那把答案和条件反过来,你可以从“知道原序列,枚举给定子序列,如何找出其字典序最小的下标集合”去考虑嘛。显然,依次找出当前位置后面的第一个等于子序列下一元素的位置,然后将“当前位置”放到该位置,这个贪心是正确的。那意味着什么?就是在子序列的下标集合最大值之前,每一个不在子序列内的元素,都不能等于它后面第一个在子序列内的元素的值嘛,要不然就是它而不是后面那个在下标集合内。
所以枚举子序列长度和下标集合最大值就足够,式子为 \(\sum\sum\binom{j-1}{i-1}m^{n-j+i}(m-1)^{j-i}\)。如何去优化呢?这个式子非常像二项式定理的形式,尝试变换之,使之符合二项式定理标准形式即可。
T-027 P10592 B
极简单一道题。我说你把过程分析清楚嘛。肯定是改变贡献主体,第一步枚举子序列,第二步求得到该子序列的操作方式数目。
那么假设知道结果是哪个子序列,怎样去操作呢?第一,不能操作子序列内的元素。第二,在删到只剩该子序列之前,没有变成不升的。那如何去保证第二点呢?什么叫做不降,\(\forall i,a_i\ge a_{i+1}\)。没有不降嘛,所以不断维持 \(\exists i,a_i<a_{i+1}\) 不就行了。什么叫“不断维持”?在最后一步之前都应 \(\exists\) 上述的一个 \(i\)。这就意味着最后一步肯定是将仅有的 \(i\) 删掉了,而前面的那么多步怎样删都无所谓(只需不删子序列内的)。所以只需在确定子序列过程中顺便确定一个 \(i\),把它也保送到最后就行了。
可以 dp,设状态 \(f_{i,0/1}\) 表示当前确定了下标 \(i\) 及之前的元素在不在最终子序列内,是否确定了那个只能在最后一步删掉的元素。但是这个 dp 是 \(O(n^3)\) 的。
我一再强调,你要知道你想要什么嘛,这个 dp 本质上在干什么?它其实不是在计数原子序列,而是在计数另一个子序列,满足其中只有一个元素“不正常”,而其余不升。那我们不用 dp 能去计数吗?显然那个“不正常”的元素是重要的,要区别对待。但是不能枚举之(最先确定)。那只能把它放在最后确定。
设 \(f_i\) 表示长度为 \(i\) 的不升子序列个数。那就是枚举 \(i\),在此基础上,选择一个合适的元素使得它不会变成长为 \(i+1\) 的不升子序列。正难则反,总体减去不合法。在 \(i\) 基础上任意添元素的方式为 \(f_i(n-i)\)。那有多少种可能使之变成 \(i+1\) 的不升子序列?就是 \(f_{i+1}(i+1)\)。剩余元素的删除顺序无所谓嘛,直接阶乘即可。至于 \(f_i\) 的计算方法,可以 \(O(n^2\log n)\) 树状数组优化 dp,不提了啊。
T-029 CF2101E B+
极简单一个题。但是可以把点分治能干什么,以及答案与状态互换复习一下。首先他要简单路径。你稍微想一下就知道,简单路径是很难去保证的嘛(要强行保证之,若不用科技,无多项式做法)。那只能是它不需要特别满足简单路径,那就是只需满足条件 \(2w\leq w'\),就已能满足路径简单,可以证一下(因前 \(i-1\) 条边长度之和必小于第 \(i\) 条边,故不可能有重复点)。
dp 去处理。\(f_{i,j}\) 表示从 \(i\) 开始,第一条边长度至少为 \(j\) 的路径,最长长度是多少。转移只需预处理 \(i,j\) 的距离即可。那么状态已经是炸的,如何去优化呢?每次边长翻倍,边长上限为 \(n-1\),则路径长度最多为 \(O(\log n)\),那么可以答案与状态互换。但是如何去换呢,你把 dp 想象成一个函数,那么互换就相当于反函数嘛。但是这个原 dp 数组是多个 \(j\) 对一个答案,反函数必然是一对多的,不合定义。所以答案状态互换一般要满足单调性嘛,其实只需满足答案相同的 \(j\) 是规整的,如一段区间等。
那这里怎么去处理呢,它是有单调性的嘛。所以定义 \(f_{i,j}\) 表示从 \(i\) 出发,要走出长度为 \(j\) 的路径,其首边长度最小是多少。现在如何去转移呢,就是若 \(2\mathrm{dis}(a,b)\leq f_{a,j}\),\(\mathrm{dis}(a,b)\to f_{b,j+1}\)。
现在你去想,还有什么条件没有用到嘛,就是原图是个树。若原图是任意图,我们只能去预处理 \(\mathrm{dis}\)。那么树上多点间距离问题有什么快速计算方法呢?点分治。对每个 \(j\) 做一遍,即可。
T-030 P10764 B+
所有情况之和呀!没想法吗?改变贡献顺序,枚举贡献位置 \(i\)。那 \(H-h_i\)。\(h_i\) 在所有情况下的和是极好算的嘛,所以只需算 \(\sum H\)。怎么去做呢,有两种方法。一种是直接的,一种是间接的。
法一,直接做就是考虑对 \(i\),\(H\) 是什么呢,假设 \(p_i,s_i\) 分别为前缀后缀最大值。那么 \(H=\min(p_i,s_i)\),仍不好算。我们多次强调过 \(\min(a,b)=a+b-\max(a,b)\) 嘛,变一下式子,即 \(H=p_i+s_i-\max(s_i,p_i)\)。\(p_i,s_i\) 是倒过来做一遍的区别,因此只需会求 \(\sum p_i\) 即可。那 \(\max(p_i,s_i)\) 是什么呢?是全局最大值。刚才把 \(s_i\) 化成了 \(p_i\),启发我们,要把它也化成 \(p_i\)。怎么去化呢,全局最大值就是 \(p_n\),因此只需求 \(\sum p_n\)。故本质上只需求出对每个 \(i\),\(\sum p_i\)。直接组合计算非常困难。
那只能 dp 嘛,\(f_{i,x}\) 表示只考虑前 \(i\) 个元素的取值,\(p_i=x\) 的方案数,则 \(\sum p_i=2^{n-i}\sum_x xf_{i,x}\)。状态是炸的,但是你要的是 dp 数组中的每个值吗?不是,对每个 \(i\) 只要查询一个类似于全局和的值。虽然状态是炸的,但不是每个状态都要特别地(具有独特的贡献系数)统计答案,只要转移简单,我们就能压掉 \(i\) 一维,然后线段树优化。
法二。你想,问所有情况的水深和嘛,和问水深期望没有区别。在期望中的经典结论,有 \(E(H)=\sum_t P(H\ge t)\)。那这里如何去做呢,就是要处理对 \(i\),\(\min(p_i,s_i)\ge t\) 的方案数。这个问题和你直接处理所要计算的问题是类似的,好像用期望拆贡献完全没有用啊。我们昨天说了,二项式反演为什么一般不用“至少”。这个 \(P(H\ge t)\) 不也是“至少”吗,而且也是和原问题类似的,不好去算。那一样的,正难则反(计数题,正难则反很重要),去求 \(\min(p_i,s_i)<t\)。容斥一下嘛,只需分别求 \(p_i\le t-1,s_i\le t-1\) 和二者的交集大小。此三者可以直接在值域线段树上组合维护(本质实与法一类似),不提了啊。
T-031 CF2084F A
我一再强调这种换顺序的操作可以考虑只操作相邻两项嘛,你发现在这里只操作相邻两项确是不劣的。那就是若 \(a_i>a_{i+1}\),可以将其交换。从势能去考虑,逆序对数只能减小。
这启发我们关注序列的顺序对和逆序对,你发现顺序对不可能通过操作变成逆序的。因此从数学限制的角度,起始序列的每个顺序对都是一个形如“某数必须填在某数之后”的限制。没想法吗?建立图论模型,拓扑排序嘛。但是有 \(O(n^2)\) 条边,且无法优化。
那怎样去处理呢?改变思路,先翻译硬性条件(外界元素对构造元素的限制),忽视构造中的隐性条件(构造元素间的限制)。为什么呢?构造题呀!能抓住几个限制就抓住几个(为尽力在构造之前就减小可能性),不确定存不存在的限制你就先当它不存在嘛。你要在 \(c\) 的间隙填数嘛,硬性条件就是要满足填的数不和框架冲突。故对每个不在 \(c\) 内的元素,其填入位置只能是一个区间,可以用线段树求得。那就是经典问题,扫描当前位置,将 \(l\) 与当前位置相同的加入,贪心地每次选择 \(r\) 最小的放在当前位置即可。
然后再考虑隐性条件。隐形条件限制了在 \(a\) 中满足 \(x<y,a_x<a_y\) 的一对均不在框架内的元素。那如何去刻画这两个均不在框架内的元素呢?通过其对应的区间去考虑,必有 \(l_x\leq l_y,r_x\leq r_y\)。因此,若按照贪心方法填数,基本已经满足隐性条件,只需特判 \(r\) 有多个最小时选择最小的元素填入即可。
T-032 一些拓扑排序题目 B+
T-032-1 P1954/CF1765H
法一。“最靠前”呀!有单调性,可以二分。二分有什么用呢,二分是把问题转成“判定有无解”的问题嘛。那么对 \(i\) 二分答案 \(t\),就是问 \(i\) 起飞编号不超过 \(t\) 有没有解。没想法吗?题目提示特别特别明显的,这相当于令 \(k_i=t\),然后和第一问是一模一样的啊。
那第一问如何去处理呢?从特殊性质去思考,相当于分别处理限制。若没有 \(k\) 的限制,你看剩下什么限制?要求 \(a\) 序号小于 \(b\) 的。经典的拓扑排序问题嘛,建图。若只有 \(k\) 的限制,则每次选择 \(k\) 最小的去填即可。
原题同时具备两个限制,怎么去做呢?那还是每次选择 \(k\) 最小的点记为 \(x\),但(因限制一)要先把其祖先全填完才能填 \(x\) 嘛。那如何知道其未被填过的祖先有谁呢?建反图 bfs 即可。之后,通过正图上拓扑排序,可以确定,为了填 \(x\),其未填祖先分别应该在何时填入。全部做完后,将每个点的拓扑序与对应 \(k\) 比较,即可判断是否有解。
法二。先把时光轴操作复习一下。典例很多啊,例如给定一张图,每次删一条边,删后询问某点对是否连通。正着做是删边很麻烦,时光倒流改成加边,并查集维护即可。又如各类线段树分治题目,一样规避了删除操作。再如可离线的一类版本问题,建立版本树,在其上通过 dfs 维护当前版本节点的情况。
那本题如何去做呢?还是先分别处理限制。本题最关键也最难的部分,是第二个限制 \(k\),其作用相当于要求在 \(k_i\) 时刻后,让你把 \(i\) 从待考虑的航班中删除。你不会做本题,就是因为总害怕某飞机来不及起飞就被删除了嘛。那与上面例题类似的,可以时光倒流,改成在 \(k_i\) 时刻及之前将 \(i\) 纳入考虑范围。至于第一个限制,即要求 \(a\) 序号小于 \(b\) 的,可建反图进行类拓扑排序处理。若一架飞机在考虑范围内,且反图入度被减成了零,则它当前可飞。每个时刻随便选一架合适的起飞即可,但尽量不要让 \(i\) 起飞。若不得不起飞(只有它一个可飞),则 \(i\) 的所有情况最小拓扑序就是此刻。
T-032-2 P13783
补充一道拓扑排序题目 P13783,有两种做法,只讲做法不讲思路。首先用树剖和线段树优化建隐式图(如果是显式图,会导致拓扑序与实际分配的边权值不一致,比较难处理 \([l_i,r_i]\) 的限制)转化题意为给定一个 DAG,求一个拓扑序使得 \(i\) 的序落在 \([l_i,r_i]\) 内。
法一,假如只有 \([l_i,r_i]\) 的限制,可从小到大依次分配拓扑序。扫描当前正在分配的拓扑序 \(x\),将新的 \(l\le x\) 的区间纳入考虑范围。每次对 \(r\) 最小的区间分配序号 \(x\) 即可。加入“拓扑序”的限制,每个新的 \(l\le x\) 的区间还需要满足入度被减为零才能纳入考虑范围。令 \(a_i\) 表示区间 \(i\) 的后代中 \(r_i\) 的最小值,每次改为选择 \(a_i\) 最小的即可。
法二,进行一点放缩,令 \(l_i\) 改为其祖先的 \(l_i\) 最大值加一,\(r_i\) 为其后代的 \(r_i\) 最小值减一(还需和本身取 max/min)。这样,满足了对任一节点 \(i\),其任一祖先 \(j\) 有 \(l_j<l_i,r_j<r_i\)。所以再直接贪心,就能顺便满足 \(i\) 的拓扑序比 \(j\) 大,即可。这两种做法在本题中,可以方便地使用线段树处理;在一般图上也比较容易直接计算。这些做法都能够处理上面的 P1954/CF1765H。
T-033 P13714 B+
我一再强调你从“操作何时有用”去分析嘛。这里从它的反面。怎样的操作无用呢?就是以后的操作把它的贡献全部覆盖了的时候无用。那就是说,每个操作都要有自己的贡献,能够保持到最后。那就是每个操作都要将新的若干位改得跟 \(y\) 一样。
显然,需要使用状压 dp,那状态就是 \(f_i\) 表示想要 \(i\) 这个状态的那几位和 \(y\) 一样,且保持到最后,最少代价是多少。如何去转移呢?枚举新增的跟 \(y\) 一样的集合嘛,显然新增的那几位全是一(或操作),或者全是零(与操作)。比如全是一,那我们的或操作一定要满足什么条件呢?已确定和 \(y\) 一样的零位,不能被或上一;当前新增的位,必须或上一。
那我们去预处理满足这种条件的所有操作代价最小值即可。如何预处理呢?对于一个或操作,选它后“新增”的位一定是它“一”位的子集,选它前“确定了”的“零”位一定是它“零”位的子集。那么“确定了的零”和“新增的一”集合必然无交。无交呀!只需用 \(O(3^n)\) 的预处理数组,用状态中的一记录“确定了的零”,二记录“新增的一”。高维前缀和即可。
T-034 P13663 D
首先翻译一下题目式子,这个 mex 就是深度。那么离线建树,树剖加二分,有 \(O(n\log^2 n)\) 做法,无法优化。
正解怎么去做呢?改变贡献主体。那就是问,“将 \(u\) 子树内最大深度更新为 \(x\)”这 \(a_u\) 的贡献给了谁?给了 \(u\) 子树内(依加入顺序)第一个深度为 \(x\) 的节点。深度呀!没想法吗?离线建树后,长链剖分去维护当前节点子树内第一个深度为 \(i\) 的节点,我们要每次将 \(a_u\) 的贡献加给这些节点,通过带权并查集加上即可。
T-035 CF2042E B
我一再强调,树上问题不要忘记重心和直径。
它的代价是 \(2^i\) 呀,按位贪心,依编号从大到小依次枚举点,不想把它加入到集合中。那就是在删去它后的几个连通块中考虑,有没有能够同时包含 \(n\) 种点的,若没有,则当前点无法删去。
那如何去判断呢?首先,删后分裂出来了好多个块,但只有一个能包含 \(n\) 个以上的点,故只需知道它能不能同时包含所有种类即可,那就是说任意时刻,可能合法的连通块只有一个。那么为回答“能不能”,需维护这个连通块的形状,就需要考虑成功删除的影响(此操作改变形状),而每次成功删除,是一个“子树”或一个“子树外”(也许可以随意定根大力维护,但不用)。但是我们不想要“子树外”,能做到吗?可以,只需让根取在答案集合中,那如何取呢?你看总点数 \(2n\),选择一个 \(n=2n/2\) 的连通块,没想法吗?重心呀!任意连通块不含重心,就不能包含 \(n\) 个以上的点。所以二重心之一必为答案集合的根,枚举哪一个是即可。
那知道了根,如何判断“每次删子树,能不能”呢?从反面,若不合法,则已删去部分,同时包含至少一种点的所有点。我说你要关注题目不寻常之处嘛,你看不寻常在哪里?在“同种只有两个点”嘛。那关注每种点,就只需关注两个点,树上的两个点有什么?有路径,有 lca 嘛。若新删除一个子树后,导致某种两个点均被删除,总共就两个点呀!可以分讨一下。第一种是新删子树包含两个点,想一下就知道,该子树的根,是某种的 lca 到总根上的点之一。第二种是以前删了两个点之一,现在新删了另一个点,那记录以前删了哪几个点,找出其对应点,标出即可。
T-037 CF2128F S
从结果去考虑,可假设知道边权分成什么样。因为它要用到 \(1\to n\) 的最短路,那就用分好的边权,建立从 \(1\) 开始的最短路网。
但是我想要“网”吗?不想,我想要“树”,因为最短路不能过 \(k\),最短路条数越少越好。若已知一个网,可只保留某一条到 \(n\) 且不过 \(k\) 的最短路,将其他边全部设成顶格的 \(r\) 来做调整,这样对过 \(k\) 的路径很不好,但是对最短路本身没有影响。
那关键在于那条到 \(n\) 的最短路!把它拎出来。那对它有什么限制呢?这个时候你可以去考虑删边最短路是怎样去处理的,是分别关注从 \(1,n\) 开始,原路径和新路径的第一次分叉。假设“原路径”和“过 \(k\) 且最短的路径”的那两个分叉点分别是 \(u,v\),就有 \(\mathrm{dis}_{u,k}+\mathrm{dis}_{k,v}>\mathrm{dis}_{u,v}\)。那我想要 \(\mathrm{dis}_{u,v}\) 尽量小,所以对“原路径”上的所有边,可将其换成顶格的 \(l\)。
综上,只需求出那条最短路,将其上的边定为 \(l\),其余定为 \(r\) 即可。那 dp 一下,状态只能是 \(f_i\) 代表以 \(1\to i\) 结尾的,边全部取 \(l\) 的路径中,\(\min_u \mathrm{dis}_{u,k}-\mathrm{dis}_{u,i}\) 的最大值是多少。用求最短路的方式转移一下即可。
T-039 CF2143F A
首先 \(\mathrm{lowbit}\) 的结果只有三种:\(0,1,2\),其中相邻两个相等为 \(0\),相邻两个为 \(0,2\) 或者 \(1,3\) 为 \(2\),其余的为 \(1\)。
那你看结果为谁最奇怪?为 \(2\)(因一般情况就是一,“而相邻两个相等,为零”看起来也较正常),但是不太好直接入手去处理。你再看谁第二奇怪?为 \(0\)。这个好办,把连续相同的段当成一个数就行了。
那这样处理完之后,再关注“最奇怪”的位置。怎么关注呢?把“最奇怪”的缝隙标为一,其它缝隙标成零。你怎么去关注零一串中的一呢?没有其它分布规律,看连续段。一个全一连续段就代表原序列为 020202 或 31313。但不要忘了我们把连续相同的缩到一起了,如果不缩,就是连续的只有 \(0,2/1,3\) 的段。但是所有的直接 dp 做法必须要记录“当前用了几个 \(0,1,2,3\)”,完全不能接受。
你再看谁第三奇怪?这些“第三奇怪”的缝隙,就存在于 \(0,2\) 段和 \(1,3\) 段交替处,不妨枚举一下?则得到 \(0,2/1,3\) 各自有多少段,也算清了“第三奇怪”的贡献。你想,“第三奇怪”的贡献算完了,\(0,2\) 段和 \(1,3\) 段还有关系吗?没有,分开考虑。那就是要把 \(0,2/1,3\) 各自看成整体。
但是我明显把“整体”拆成了若干段呀!其实整个问题分两步,一步是算出“整体”的贡献,一步是算出“拆”造成的损失。第一步简单 dp 计算一下,而第二步的重点在于你是把“最奇怪”缝隙拆开了,还是把“第二奇怪”拆开了。枚举一下拆了多少“最奇怪”即可。
T-040 A+
一张带权无向图 \(n\le 10^5\) 个点,\(m\le 10^5\) 条边。还有点权。每个点上有一老鼠,均可耗费边权的代价,沿边移动。若多只老鼠在同一点上,可令其中一只吃掉另一只,而需耗费点权为代价。求只剩一只老鼠所需最小代价。
我一再强调,你去把过程分析清楚,要关注重要的过程嘛。那这个题目的重点肯定在“吃”上。怎么刻画一个“吃”?谁吃了谁、在哪里吃的。如果要刻画所有的“吃”,还需要知道每一个“吃”之间的顺序。那边权的贡献怎么去计算呢?每只老鼠都必须依次赶到它涉及到的每一个“吃”的地点,一定是最短路嘛。最短路还是相对好处理的,所以赶路的过程不重要,只有“吃”的地点重要。可以把每只老鼠涉及到的(时间上的,包括起点)相邻两次“吃”地点之间连有向边,边权为最短路长度,则形成了一个专门刻画“吃”过程的,树的结构(因包括起点,树的节点数恰为 \(n\))。
那点权的贡献怎么去计算呢?就是每个点的度数减一,乘以点权。度数呀!没想法吗?点权拆到边权上。对每条边,加上它连接的两点之间的点权。最终的答案就是边权之和,减去所有点点权之和,加上根的点权。那么谁吃谁、吃的顺序几乎完全不重要,有向边基本相当于无向边,只需吃出来的结构和这棵树一样,答案基本就一样。有向边的唯一意义是确定根的位置,但在所有吃出来等于这棵树的方案中,根其实可以取到所有点。故取点权最小的点即可。
那问题转化成,\(n\) 个点的完全图,边权为两点点权和加原图最短路长度,求最小生成树。完全图、最短路、最小生成树同时出现的,我们只讲过一个典型例题。就是原图上有 \(k\) 个关键点,关键点两两之间以原图最短路长度连边,成一个新完全图,求新图最小生成树大小。做法就是,从每个关键点为零距离,开始在原图上跑 dijkstra。出队时,按照当前点的最短路来自的关键点编号,为每个点染色。出队后更新其它点的过程中,如果当前点的相邻点中,存在已经出队的点,则当前点和相邻点,染的颜色代表的两个关键点,之间的“新图边”有用(无法求出此“新图边”的权值,可用当前点、相邻点 dis 值之和,加上原图边权代替,是正确的)。显然只有 \(O(n)\) 条“新图边”有用,kruskal 即可。
那原题怎么处理“加上点权”呢?拆点。给原图每个点挂一个叶子,边权为原有点权。去掉点权,以所有叶子为关键点跑上述算法即可。
T-041 CF2124G B+
其实是非常非常简单的题目。假设 \(s_i\) 表示第 \(i\) 个前缀最小值。它只操作一次呀!暴力地有 \(n^2\) 种操作,但是肯定不能枚举,那就是有操作无用。哪些无用呢?所有 \(i\) 不是前缀最小值的操作。好,那合适的 \(i\) 一定是前缀最小值了,这个看着非常奇怪/重要,枚举一下。
现在你去考虑暴力怎么做嘛,暴力是再枚举一个 \(j\)。其实我们不想要 \(j\) 被枚举,如果答案与它关系不大(\(j\) 不重要)是最好,所以我们去待会关注答案的哪一部分和它没关。你看啊,计算答案,需要分类讨论:假设 \(\mathrm{lst,nxt}\) 表示 \(i\) 上一个/下一个前缀最小值。首先无论哪种情况,\(\forall t\in [j,n] s_j\to 0\)(这是跟 \(j\) 极其有关,但是形式很简单)。
第一种,如果 \(a_i+a_j\ge a_{\mathrm{lst}}\),其它部分的 \(s\) 变化量与 \(a_i,a_j\) 均无关。一会再来分析如何进一步处理;如果是 \(a_i+a_j<a_{\mathrm{lst}}\) 的第二种情况,就需要查询 \(i\) 后面、\(\mathrm{nxt}\) 之前第一个 \(a_k<a_i+a_j\) 的位置,则 \(\forall t \in [k,j) s_t\) 的变化量与 \(a_i,a_j\) 均无关,这部分变化量预处理就能搞定。还剩下的部分,其实也只跟 \(a_i+a_j\) 有关(不是跟 \(j\) 本身关系极大)。
那能枚举它吗?可以,因为 \(a_i+a_j\ge a_{\mathrm{lst}}\)。枚举之后,答案基本与 \(j\) 无关,那肯定是让 \(j\) 尽量大,则所谓“操作代价”最大嘛。结果恰好,仅剩的与 \(j\) 有关的,\([j,n]\) 部分的变化量也是 \(j\) 越大越好,第二种情况就解决了。第一种情况完全类似,只需查询 \(a_j\ge a_{\mathrm{lst}}-a_i\) 的最大 \(j\) 位于何处即可。
T-042 ARC164E A+
按照我看错题的版本:节点被遍历,严格相当于正常线段树上查询访问到当前点的次数。这个版本和原题其实差不多。
首先按照查询端点离散化一下,离散化数组相邻两数构成的区间看成整体,因为没有查询需要访问到里面的节点。答案跟“被访问的最深节点”有关,最深节点的儿子肯定不能被访问过,那所谓“整体”中有几个肯定是最深节点。
假如只看限制“\(d\) 最优”,通过对“整体”建立线段树,可以求出满足 \(d\) 最小的一个方案,同时求出 \(d\)。然后想要加入限制“\(c\) 最优”。你已经有了一个 \(d\) 最优的可行解,待会我们把它调整一下就行。
我要知道“最深节点被访问次数”,就要知道“每个整体被访问次数”。则对一个整体,只有包含它且至少有一端点和它的一端点重合的查询,才会访问到它。可以简单地求出其访问次数。我想让一部分“整体”不在最深处,这样才能不算其访问次数,那就是要把它往浅调整,只需让其变浅一点就行了。那就是,每个“整体”的深度不是 \(d\) 就是 \(d-1\)。
现在可以去 dp,状态肯定是类似于 \(f_{i,j}\) 表示考虑前 \(i\) 个整体,有 \(j\) 个深度为 \(d\),其余深度为 \(d-1\),最少的访问次数和是多少。但是线段树每个节点应该有两个儿子,这意味着所有深度为 \(d\) 的“整体”必须是连续的两个一起去转移的(作为同一父亲的两个儿子)。
那答案如何去统计,使统计到的方案满足 \(d\) 最优呢?谁是重要的,只有“整体”所在节点是重要的。从结果去考虑,线段树中浅层结构是不重要的,从 \(d-1\) 层开始,直接相邻两个节点连一个父亲就是最优的。那就是第 \(d-1\) 层最多 \(2^{d-1}\) 个节点。那可以更改一下状态,\(f_{i,j}\) 表示考虑前 \(i\) 个整体,共计用了 \(j\) 个 \(d-1\) 层节点(作为整体所在节点,或者两个整体的父亲),最少代价是什么,即可。
T-043 ARC163D S
我一再强调,你要从结果去考虑。什么叫“从结果”呢?最优化问题里面,就是去分析答案具有的性质,可假设已知一种合法答案,看看怎样去调整它能让它合法且更优,最优解就是调整不了的。如果是计数题里面,跟“分两步”是一样的。就是假设知道第一步的结果,然后想第二步怎么做,接下来可根据第二步的做法,改变贡献主体。有时候,题目不是分两步就做出来了,但是这样想能提供思路。
那假设知道了最终图长什么样,接下来只需求它的强连通分量个数,缩点。缩点之后有什么?一个 DAG 嘛,在竞赛图中,这个 DAG 是什么样的?是一条类似于有向链的形式,只不过每个点向后面的所有点都连边。链有什么呢?
前面有一道讲区间的题目,你可能都忘了。就是点减边容斥所用到的结论,一个区间的点个数是边个数加一。放在链上也合适,那什么是“边”呢?有的东西的作用,只有删掉才知道,因为它表示着其它元素之间的某种关系,通过“删除”去直观看出这种“关系”。把链上某条边删去,链变成两段。“有向边”就表示这两段之间的关系。什么关系呢?后一段的拓扑序均在前一段之后。因为通过“边”就能统计强连通分量个数,所以我不再愿意关心“强连通分量”,想回去关注原图中的节点(“第二步”考虑完毕,返回思考“第一步”)。“有向边”和原图节点的关系就是,把所有节点分成了两个集合,不同集合中的点间的边一定是第一个集合的点向第二个集合的点连的。
“边”把集合间的关系确定了,则集合之间无关,拆开。那观察集合内部的边,除了是竞赛图以外,没有其它要求(集合各自缩点得链,通过“边”拼在一起之后就得最终链,故无论两集合内部情形如何,“边”恒有其贡献)。题目的限制还有 \(i<j,i\to j\) 的边个数为 \(m\),那可以扫描值域,即按照标号从小到大考虑 \(i\),依次确定其属于哪个集合。则用 dp 状态 \(f_{i,j,k}\) 表示在标号前 \(i+j\) 的点中,第一个集合用了 \(i\) 个点,第二个集合用了 \(j\) 个点,集合间连边有 \(k\) 条是从小到大连的。最终答案统计即为 \(\sum_a\sum_x\sum_y \binom{\binom{n}{2}}{x}\binom{\binom{n}{2}}{y}f_{a,n-a,m-x-y}\)。
T-045 P9393 B
对于一个询问串,要操作后二进制数最小,可按位贪心。按位贪心的本质是什么?\(O(\log n)\) 次查询判定性问题:能否得到一个“保证某些位是一,其它位是谁无所谓”的结果串。判定性问题好在哪里?你知道结果。
知道结果,而且操作是覆盖呀!时光倒流,关注最后一次操作。从反面,最后的操作绝不能在结果中为一的位置是零,没有其它限制。如果操作和结果串有几位同为一,则倒回去之后,前一时刻的串在这几位上可以是“无所谓”。这类似于一个构造题目,问能不能从结果串推回一个串,使其中的一所在位置集合为询问串的子集。
分析一下,构造题著名性质“能操作就操作”是生效的。我再给你写慢一点,就是你一旦知道哪些操作合法,直接无脑操作即可,知道几个操作几个。本题是一个二进制相关问题,从子集超集的角度刻画限制。这里仅有的限制相当于“操作串的零位置,是结果串无所谓位置的子集”。高维前缀和一下,即可求出对任意结果串 \(S\),将所有已知能倒回的操作倒回完后的新结果串(即,将知道的操作操作完后的情形),记为 \(g(S)\)。每个判定性问题就是求 \(g^{+\infty}(S)\),将它和询问串比较。再 dp 一下即可。
T-046 一些组合推式子题目 B+
T-046-1 CF1264D2
非常简单的题目。首先这个问题分两步,假设第一步“确定序列”已经完成,怎样去计算答案呢?答案是一个深度。你想,谁产生了这个深度?一个形如 (((...))) 的串,其中左右括号数目相等,称之为答案串。只需所有的左括号在右括号之前,保证此限制后,左括号右括号没有关系,可以拆开。
那如何保证此限制?可以枚举串中最后一个左括号位置 \(i\),在它之前只需关注左括号个数 \(a_i\),之后只需关注右括号个数 \(b_i\)。若最优答案串的最后一个左括号确实在 \(i\),则答案深度即为 \(\min(a_i,b_i)\)。\(\min\) 里面只有两项呀!如果讨论“谁比谁大”之后,就没有关系了,可拆开。对 \(a_i\),它随 \(i\) 递增;\(b_i\) 则递减。故最优的 \(i\) 一定满足 \(a_i=b_i\)。
回到第一步,可转换主体,那就要知道满足“最优答案串的最后一个左括号在 \(i\)”的原括号串个数。这种串只需满足 \(a_i=b_i\)。当然,为了统计答案,还需知道最优答案深度是什么,可以枚举 \(a_i\)。那推一下组合式子。枚举 \(i\) 是最优答案串的最后一个左括号位置,就能得到输入串中,\(i\) 左边的左括号数和问号数;右边的右括号数和问号数,用 \(l,x\);\(r,y\) 分别表示。枚举 \(a_i\)。故原问题所求答案即 \(\sum_i\sum_{a_i}a_i\binom{x}{a_i-l}\binom{y}{a_i-r}\)。
很想范德蒙德卷积优化,但是因为有 \(a_i\) 的系数,不行。我一再强调,如果有系数在组合数前面,就要把它弄到组合数里面嘛,咱们讲过的一个经典例子就是 \(t\binom{x}{t}=x\binom{x-1}{t-1}\)(用组合意义去理解,从 \(x\) 中选择 \(t\) 个之后,再在其中挑选一个。相当于先挑选一个,再选剩下 \(t-1\) 个)。这里也一样的,令 \(t=a_i-l\),变化一下就是 \(\sum_i\sum_t(t\binom{x}{t}\binom{y}{t+l-r}+l\binom{x}{t}\binom{y}{t+l-r})\)。分别范德蒙德即可。
T-046-2 P13682
拓展一下,在 P13682 中有如下式子 \(\sum_i\frac{1}{i}\binom{n}{i-3}\)。
法一,我不会系数 \(i\) 在分母上的,但是会在分子上的,如何转化?先展开,\(\sum_i \frac{n!}{i(i-3)!(n-i+3)!}\),想让它是个“系数在分子上”的组合式子,那就引入 \((i-1)(i-2)\)。之后主要是要求 \(\sum_i(i-1)(i-2)\binom{t}{i}\)。然后呢?典型例子的转化,应用了组合意义。这个组合意义肯定也可以处理系数是任何关于 \(i\) 多项式的情形。具体而言,\((i^2-3i+2)\binom{t}{i}=i^2\binom{t}{i}-3i\binom{t}{i}+2\binom{t}{i}\)。
法二,我想把 \(\frac{1}{i}\)“吸收”进组合数里面,接着就可直接做组合数前缀和了。那本质上是把它吸收到哪一项里面了?是组合数展开式的分母 \((i-3)!\) 这一项里面。没法直接吸收,但是如果分母形如 \((i-1)!\) 就可以了。回去看这个 \((i-3)!\) 是怎么来的,是组合数的下面一项,那能不能通过恒等变换,把原组合数用其它一些组合数表示,使得新表示中所有组合数下面一项均为 \(i-1\) 呢?
这种恒等变形,一般就是使用杨辉三角 \(\binom{n+1}{m+1}=\binom{n}{m}+\binom{n}{m+1}\)。改一下,\(\binom{n}{m}=\binom{n+1}{m+1}-\binom{n}{m+1}\)。如果用右边替换左边,组合数下面一项就加一了。那只需不断进行这个过程即可,写慢一点就是 \(\binom{n}{i-3}=\binom{n+1}{i-2}-\binom{n}{i-2}=\binom{n+2}{i-1}-\binom{n+1}{i-1}-\binom{n+1}{i-1}+\binom{n}{i-1}\)。代换一下原式转化为求四项类似于 \(\sum \frac{1}{i}\binom{n}{i-1}\) 的式子的和。也就是 \(\sum \frac{n!}{i!(n-i+1)!}=\frac{1}{n+1}\sum \binom{n+1}{i}\),最后一步用组合数前缀和即可。再补充一下,刚才需要把 \(\frac{1}{i}\) 吸收进了组合数下面一项。如果有别的情况,需要吸收进组合数上下两项之差的,也可用杨辉三角去变换。
T-046-3
另有一道题,需要 \(O(1)\) 求 \(\sum\binom{s}{i}i!(n-i-1)!\),\(s,n\) 均为常数,\(s<n\)。
原式就是 \(\sum \frac{s!}{(s-i)!}(n-i-1)!\),把含有 \(i\) 的放在一起,得 \(s!\sum \frac{(n-i-1)!}{(s-i)!}\)。这有点类似于“排列数的列前缀和”的形式,没有学过,尝试变成“组合数的列前缀和”,那就要先除再乘 \(((n-i-1)-(s-i))!\)。则原式就是 \(s!(n-s-1)!\sum \binom{n-i-1}{n-s-1}\)。利用列前缀和,得到 \(s!(n-s-1)!\binom{n}{n-s}=\frac{n!}{n-s}\)。
T-049 ARC118E B
计数题目,首先思考组合/容斥/dp。这个问题分两步,第一步确定排列,第二步给排列求方案。这相当于“知道排列,求路径数”,改变贡献主体,思考“知道路径,求能使它合法的排列数”如何计算(不过“确定路径”和“求合法排列”最终要在同一个 dp 中综合处理)。但是这个不好直接算,可以容斥。容斥题,先正难则反,那就是计算“钦定 \(k\) 个位置和已知路径重复,其它位置不管”的排列数。
容斥贡献系数 \(f(k)\) 怎么求呢?我多次强调你要先想出“正好/极大为 \(k\) 个位置重复”对答案的贡献系数 \(g(k)\)。答案是“没有重复的排列数”,那只有 \(g(0)=1\),其它都是零。然后关注“钦定 \(k\) 个重复的方案中,包含了几种钦定 \(i\) 个重复点的方案”,显然对 \(i<k\),这种包含数是 \(\binom{k}{i}\)。那么 \(f(k)=g(k)-\sum_{i<k}f(i)\binom{k}{i}\)。不过,容斥系数求出来后,可以打表看一下情况,你发现实际上 \(f(i)=(-1)^i\)。这种比较简单的容斥系数,可以放到 dp 里面去处理,我们一会再说。
那如何 dp 呢?首先,\(a_i\neq -1\) 的位置并不关键,因为可直接闪避,也就是说可以通过 dp 的过程,强制让这些位置与路径不交(方法是,让与它交的路径不合法,通过让这种转移不合法实现),当然也不参与容斥的过程。这个时候不一定要思维定式断点转移(如一列一列转移),也可想想逐步转移。那如果逐步转移,状态中至少要记录路径的当前位置 \((x,y)\),以及容斥要用到的“已经钦定了几个重复”\(l\),转移就是当前位置是否钦定重复,以及向下向右迈步。但是这是排列呀!不能让同一行/同一列钦定两个重复位置。其实 \(x,y\) 在转移过程中单调,那可以直接用 0/1 状态记录本行本列是否钦定了位置。假设 \(a_i\neq -1\) 的位置共 \(m\) 个,则最终方案数应为 \(\sum f_{n+1,n+1,0,0,l}(m-l)!\)。我说容斥系数也可以放到 dp 过程中求,也就是说方案数(最终,以及计算过程中)自带系数。容斥系数只和 \(l\) 有关,那 \(l\) 变化时就应改变当前方案的系数。那如果有使 \(l+1\) 的转移,乘以系数 \((-1)\) 即可。
T-050 B+
一道和 min/max 有关的典型题目。给定长度为 \(n\le 10^6\) 的三个非严格递增数组 \(a,b,c\),求 \(\min(j+k-a_i,i+k-b_j,i+j-c_k)\)。这题有两种做法,先讲一个咱们刚讲过的(T-046)。
法一,你看式子和 \(i,j,k\) 三个下标有关系,枚举其中一个,比如说枚举 \(k\),那就要求 \(\min(k+\min(j-a_i,i-b_j),-c_k+i+j)\)。注意,我说“枚举”\(k\),也不一定是真的枚举,也可能是假设枚举,作为把 \(k\) 和其它变量分开的手段,由此入手思考问题。这里即使枚举了 \(k\),仍不好做,还需枚举 \(i+j=t\),接下来只需知道 \(\min(j-a_i,i-b_j)(i+j=t)\)。
我多次强调,要让需要比较大小(带有 min/max 或偏序关系等)的式子好看,就要想方设法使得同一下标在同一边嘛,可变形为 \(\min(t-i-a_i,t-j-b_j)\)。现只需比较这两项的大小之后,它们就无关了。那分开看,两个函数分别是随 \(i,j\) 递减的函数,但是从高中数学知识说要消元,自变量只能是一个,比如是 \(i\)。则在 \(t\) 被枚举的情况下 \(t-j-b_j\) 随 \(i\) 递增,那就是说以 \(i\) 为自变量,两个函数一个递增一个递减,显然可以二分其交点,就是最小值取得的地方。那回去解决“枚举了 \(k,t\) 两个数”的问题。令 \(d_t=\min(j-a_i,i-b_j)\),则你枚举 \(k\) 是为了求 \(\min(k+d_t,-c_k+t)\),那只需枚举 \(k+t\) 这一个值,接下来跟上面是一模一样的,不提了啊。
法一点五,代码其实和法一一样,但是想法略有不同,可能更自然一点。枚举 \(t\) 以后,要求 \(\min(j-a_i,i-b_j)(i+j=t)\)。里面只有两项呀!可以比较大小分类讨论。那就是要比较 \(j-a_i,i-b_j\) 的关系。把同一下标放一边,就是比较 \(j+b_j,i+a_i\)。此时再换元,由单调性,当 \(i\) 小时 \(j+b_j\) 相对小,故 \(j-a_i\) 小;反之 \(i-b_j\) 小。可二分求出 \(j+b_j=i+a_i\) 相等的位置 \(p\)。在该位置以左(\(i\) 较小),\(\min(...)=j-a_i=t-i-a_i\),想让它最小,因 \(i+a_i\) 单调递增,只需让 \(i=p\)。在位置右边也类似,可发现也在 \(i=p\) 位置取得最小值。
法二,有一个 min,max 的经典转化:取任意 \(t\),\(\min(a,b,c,d,...)=t-\max(t-a,t-b,t-c,t-d,...)\),一般取 \(t=a+b+c+d+...\)。这个变化的目的是什么?还是把同种下标放一边。因为原式有轮换性,只需用 \(t=i+j+k\) 即可,变成 \(i+j+k-\max{a_i+i,b_j+j,c_k+k}\)。那只需枚举 max 是哪一项取到,就转化为一维偏序问题了。

浙公网安备 33010602011771号