The Art of the Induction III
$\ $
引言
在开始之前,请大家先问自己几个问题——请试着思考:我完全知道自己是如何做题的吗?做题是靠“注意力”多一点,“见过的套路”多一点,还是兼而有之?“注意力”是能够训练的吗,“套路”是能记录,能逐一背诵,乃至最终穷尽的吗?我是为一道题或一个解法的“巧妙”而兴奋还是叹息?
那么,大家有没有问过别人,“为什么能、如何才能注意到这一点”,而他的回答让你满意了吗?
有没有畅想,梦想,或者幻想过:如果有一天,这世界上没有灵感,没有注意力。或者它虽然存在,却被某套体系框定了。这套体系可操作,可言说,也即可复用和可传授。只要由这套体系引导我的思绪,就能注意到那些应该被注意到的?
我打心眼里不相信这套体系完美地存在,我更不打算给出这套体系。我的天赋很差,也许我是你读到的博客作者中 OI 水平最差劲的一个——然而我还期望自己为这个社区作出一点贡献。我的贡献应该是,也仅仅是指出这个事实:
思维体系不是无法逼近的。
我除了指出存在性,还尝试着迈出了一步,但这不过是抛砖引玉。我寄希望于看到这里的你,还有一切广大的后来者,我们一起来完善这套体系。如果连我这种天赋的人都能迈出一步,那此事就大概率能做下去。虽然人与人思维方式有些许差异,但是我们将达成一些共识。
下面给出我的私人体系。它会提出一些通用的方法,然后围绕动态规划、数据结构、图论、数学、交互与构造等方面展开,每个方面都会先给出一般性方法,再具体分析。我特别推荐 Alex-Wei 的博客 作为补充阅读,它对各类算法及其基本应用的介绍极为详实,本文就不再赘述。例题在本博客下 Exemplary Examples 中,它将详细展示这套体系的动态运作,期待大家结合阅读。
祝大家前途光明。
第一章 总体方法
§1.1 超级方法
一、元素
元素,主体,对象。
例如一棵树,树上的一个点,一条边,一条路径,一个子树,一个连通块,都可以是元素。以点为主体时,边就是元素间的联系。以路径为主体时,点和边就是元素的组成部分。
所有题目都建立在元素之上,否则就无法叙述题面。然而题面所关注的元素,不一定是我们选用的主体。如果在原题框架下转换主体,甚至作出跳跃性的等价变换(例如括号序列转折线图),就需要将原题面翻译到新元素上去。
自然理解题面时所需关注的元素,对答案作出贡献的元素,边界元素(例如树的叶子、网格图边框上的节点),数据范围小(或者说特别简单、具备某种特殊性质)的元素等,我们往往留心,称之为关键元素,或者主体。
元素的对称。例如在 01 序列中,从以 0 为主体,转换成以 1 为主体,就好比推箱子的问题,可以把箱子的移动转化成空格的移动。
单位值主体。把每单位值想象成一个小球,小球去往何处,就是给那里做了贡献。
二、限制
限制,性质,关系。
只要一道题目谈到了元素,那么它必然对其施加定义。可以认为所有的定义都是限制。
例如当谈及某集合的 mex 值 \(v\) 时,相当于施加了“\(1,2,...,v-1\) 都在集合中出现”“\(v\) 不出现”的限制。要叙述此限制,所选的关键元素就是“集合内的全体元素”。然而,它实际只施加在集合内元素的值上,也即主体的某个方面上。若要求元素具备某种性质,则必然引入了限制。
对于同一个限制,只要更换主体,就能得到等价表述。
按照题面自然叙述限制时,限制所施加的主体往往不令人满意,主体可能太大,太抽象,例如“图是连通的”。一般来说,这就需要改为选取原主体的组成部分来描述限制,例如“图中任取两点,都存在一条路径”。
- 单点限制。就是拿出一个元素来,看上面的限制。限制一般作用在某个方面上。
- 双元限制。拿出两个元素来,它们会存在某种关系,就看对这种关系的限制。这两个元素通常在某些方面较为接近,形象说就是“相邻”。双元限制和图论中的“边”较为类似。当元素之间的限制过于紧密,将这几个元素合并处理可能更方便。
- 多元限制。
对主体做了转化,就必须把限制和所求答案翻译到新主体的语境下。然而限制的表述方法不单单由主体而决定。以集合 mex 的定义为例:
“\(1,2,...,v-1\) 都在集合中出现”且“\(v\) 不出现”。
不在集合中出现的最小 \(v\)。
“\(1,2,...,v-1\) 都在集合中出现”的最大 \(v\)。
只要对第一行限制做两次取反,就变成:不要这样的 \(v\),使得“或者 \(1,2,...,v-1\) 中有一个值没出现过,或者 \(v\) 出现过”。
关注限制的前半部分“或者 \(1,2,...,v-1\) 中有一个值没出现过”,它意味着所有大于“不在集合中出现的最小 \(v\)”的元素都不要。
结合后半部分,所求的就是不在集合中出现的最小 \(v\)。
对称主体,由考虑不在集合中出现的元素,变为考虑出现过的,那么就是“\(1,2,...,v-1\) 都在集合中出现”的最大 \(v\)。
以上这段推导,如果在值域轴上考虑,记录 \(a_i\) 表示集合内是否出现过 \(i\),将数组一字排开成 01 序列,就会显得直观。
- 存在型限制与全局型限制的互相转化,用取反。
- 最值型限制与存在-全局型限制的互相转化,按照值的顺序枚举,有时可以二分。
- 存在型限制到最值型限制的转化,就是找出“最可能符合条件的元素”,观察它究竟是否符合。
再看这个例子,两个串 \(a,b\) 的 lcp。如果把限制加在前缀上,就是“最长的相同的前缀”。先关注第 \(i\) 个前缀是否相同,再找出最长的。把前者写成 01 序列,形如 11111100000000。需找出最后一个“一”,或对称地,第一个“零”的位置。若求“第一个零的位置”,可利用限制本身的内容来简化限制——“第一个零”意味着前面都是一,换句话说,无需以前缀为主体,只需找出第一个 \(a_i\neq b_i\) 的 \(i\) 就可以了。
请问如何判断一个 01 串是否形如 11111100000000。很多简易的限制只涉及到数量关系。以单个字符为主体,取双元限制,取相邻,取数量关系。限制实为:10 子串出现次数恰好为一。
- 分步检测。给多项限制钦定“检测顺序”,“前 \(i\) 个限制都满足”的元素,才送去检测第 \(i+1\) 个限制。好处在于:或者满足前几个限制的元素数目少允许暴力枚举,或者此类元素具备良好性质(有时,良好性质与所加限制甚至不在同一方面),故能简化判定过程。有时,由于元素满足了前几个限制,后一个限制本身也可做修改。
7.1 要构造方案,使之同时满足多个限制。可先强令其满足某一个限制,在此基础上,做调整使之满足第二个限制,以此类推。
特别地,限制可能不是常数个。例如是单点限制/双元限制,可依次加入元素(和对应限制),实时调整。换句话说,维护由当前元素构成的“子图”,只考虑当前元素之间的关系,以及限制。 - 用数量关系描述限制。或者构造势能量。相通之处在于,采用极少量的信息,描述了复杂状态的合法性。
- 必要性探路。限制加在全部单点元素上,不妨先只注意:关键元素受到限制会造成什么后果。限制加在全部双元 \((i,j)\) 上,不妨先只注意:双元之间关系匪浅的那些对元素(例如某些轴上相等或相邻,即 \(j=i,j=i+1\) 时)所受的限制,会造成什么后果。
9.1 只考虑在题面中确定的元素(题目信息)涉及到的限制,不考虑只涉及“允许自定义元素”的。
三、轴
轴,顺序。
前文提到,元素具备多个方面,每个方面都可能存在性质。许多情况下,这些方面就是轴,如值域轴,位置轴,时间轴,种类轴等。因为对于两个元素,在这些方面可以比较“大小”,这就产生了偏序,而轴就是一种使得偏序都成立的全序。
举例来说,一棵树,取主体为节点,选取方面为“在树上位置”,定义大小比较为“无祖先关系不可比,反之祖先为小”。任意 dfs 序就是一种“使得偏序都成立的全序”。因此 dfs 序可以是个轴。更进一步地,树上启发式合并,必然是先合出儿子再合其祖先,这也会使得偏序都成立,故也可看作一种轴。
有多个轴时,就需决定哪个轴放在外面。大致来说,依最外轴的顺序添加限制、考虑问题。例如二元限制有方向时(限制 \((i,j)\) 与 \((j,i)\) 不同,\(a_i<a_j\) 就是这样的限制),常常把“限制所涉及的方面”(\(a_i<a_j\) 中,位置和值域)放在最外面。所谓“强制在线”,就意味着题目指定了最外轴是谁,并钦定最外轴仅允许扫描线。然而,主席树等预处理式数据结构,允许我们预先扫描其它轴,之后再来处理“强制在线”的查询。
- 扫描线。从小到大枚举 \(t\),并实时维护“在外部轴时刻 \(t\),内部轴上坐标为 \(i\) 的元素(此处可能是抽象元素,也即不是原题面所叙述的元素)的值(额外轴)是什么”。若有两个内部轴,就是维护二维平面。注意总会有一个额外轴以“被维护信息”的形式存在。
- 最外轴翻转。加入变删除,删除变加入。如果终止状态确定,常用这个办法。有时还会要求“寻找答案来源”,例如求复合函数 \(f(g(h(x)))\) 最大值取到的位置(许多题目的操作过程,可被抽象成复合函数)。正序轴是由内而外,逐步复合。倒序轴则是先问 \(f(u)\) 最大值取到的 \(u\) 再问 \(g(v)=u\) 的 \(v\),最后问 \(h(x)=v\) 的 \(x\)。尤其在覆盖问题中常见。
- 转 01 序列。尤其是限制只涉及“元素相对大小关系”的题目,可把值域轴放外面做扫描线,设大于当前值的元素为一,反之为零。
有时轴并非常被关注的方面,此时就要回到本义,轴是考虑问题的顺序。例如题面中提到了多种操作,经过尝试证明了:操作顺序任意调整,不劣。这相当于“操作顺序”轴由人自定义。常见办法就是放弃以“操作顺序”为最外轴。例如,改成以“操作种类”为最外轴,先将类型一的全部操作进行完毕,再进行操作二,以此类推,实例见 T-174。
- 自定义。我们通常屈从于题目叙述的顺序,或者题目限制所加的顺序,因为这样才容易考虑问题。如果发现某个轴的顺序允许自定义,一般就不用它作为最外轴,就是使用它,也会让另一轴随该轴单调。
四、描述法
描述,解。
问题所求的答案背后,往往存在“解”。例如求某些元素的最大值,答案是值,“解”是取到最大值的元素。如果“解”极为复杂,就需引入描述法。例如下题(T-104-3 P12074):
给定数组 \(a\),值均为零。同时给定数组 \(x\)。枚举 \(i\),选择下标 \(p_i\) 令 \(a_{p_i}\gets x_i-a_{p_i}\)。求最终 \(a\) 元素之和的最大值。
答案是值,“解”是 \(p\) 序列(按原题的叙述来说)。然而,答案的值形如 \(\sum \pm x_i\),所以描述 \(x_i\) 前系数序列也可。
描述法是,对于每个给定的解都能算出的一套信息,一套信息通常一一对应解,因此信息和解等价。描述法和选取的主体及其上的限制有关,只对统计答案服务。也就是说,一个解对答案贡献的权值,必须能通过“一套信息”计算(“是否合法”视作 01 权值)。这套信息的所有组合方式(称作可能性),应能够囊括合法解,或最优解的所有可能情况。若有优秀描述法(所描述的信息易被考虑),则只需找出一种方法(dp,贪心,或枚举等),去遍历其全部可能性。
有时存在多个解对应同一套信息。甚而,在所有可能性中,有时会存在不对应解的信息(如上面的例子),这就需要对信息加上合法性限制。总体来说,描述法只和答案计算方式、选取主体以及主体的特殊性质有关。
- “放生”描述法。若难以看出优秀描述法,通常是因为没有掌握足够的性质。此时可以先找出一个愚蠢的描述法(不加过多限制,只和主体有关,虽然可能性过多,但合法解或最优解一定被它描述),再把部分信息“放生”(假设其余信息已经确定,作为参数传入了某个函数。这个函数的目的在于依据参数,确定“放生”信息,返回最优答案),将“放生”了的信息调整或贪心到最优,观察所得到的结构(得出性质),据此修改描述法。循环往复直到足够优秀为止。关键在于将描述法划为多个基本独立的部分。这些部分的描述顺序,一般来说可任意确定,故还可“改变描述顺序”。数学推式子题,其实每个求和符号,都代表一部分描述(枚举变量,这就增添了信息)。“改变求和顺序”实质上等同于“改变描述顺序”。
- “逐步”描述法。答案权值跟多个“时刻”(时间轴,是选择并扫描的最外轴)的状态都有关系,但状态间具有可递推性的问题。每次只需在上个状态的基础上,额外记录部分信息得到新状态。可以不断用已记的信息,对尚未处理的信息作出限制(为使得已知信息“此刻”是“这样”的,未知信息必须满足何种条件)。
- 钦定一对一。包括但不限于判定性计数中的“原序列-操作序列”问题。“钦定字典序最小”。原序列-操作序列本来是多对多的。一般是描述“原序列-某种能生成原序列的操作序列”。若这种方式不可行,就必须提前操作序列的描述顺序,也即描述“操作序列-使得该操作序列合法的所有原序列”。但对同一原序列,有多个操作序列符合条件,这就算重了。法一,统计每个原序列会被多少操作序列计算,这个数对各种原序列均相同,只需让答案除以该值。法二,钦定多对一,即钦定一个原序列只对应一个“真正合法”操作序列,一般是“在所有合法操作序列中字典序最小的”。字典序可自定义。另一个例子是树上连通块计数,在最浅点统计答案。
§1.2 一般方法
- 对元素的操作
1.1 解耦。
1.1.1 两部分信息无需缠绕着确定,而是可以先确定 A 再确定 B,甚至对 AB 的确定顺序都不作要求。有的解耦需要仔细考察两部分的描述顺序,或计算顺序。反过来,如果两部分的计算顺序已被确定,或允许自定义(例如先做操作一再做操作二不劣),大概率也就需要解耦(这时就要先观察,只用操作一能得到什么)。
1.1.2 可以把计算过程的某一部分抽象出来,视作函数。
1.2 合并。
1.2.1 元素统一计算。可以把元素合并成易查询信息的二级结构,这就是分块的雏形。在多个轴上同时相邻的元素,并段。值得一提的是,贡献来源相同的元素并段。
1.2.2 一个节点只能作为中转点时(不再关键),就可与别的元素合并。若干元素之间的关系处理完了(或者这些元素之间本来没关系),能够一致对外,可合并起来。
1.3 对称。
1.3.1 01 对称。从考虑全体白元素,到考虑全体黑元素。
1.3.2 原序列-差分序列-前缀和序列对称。
1.3.3 单调轴对称。限制原本加在位置轴的序列 \(a\) 上,而另一轴 B(例如值域轴 \(a_i\) 关于位置轴 \(i\))关于位置轴单调,可以把元素转写到 \(b_i\) 中去,以之翻译限制。由于单调性,以任一轴上元素为主体,问题本质相同。由于一对单调轴的同一性,数据结构可以方便地维护对两个轴上区间的混合操作。有时可以主动制造一对单调轴 \(x,y\),常分析哪些元素是有用的。例如若对任两个元素 \((x,y),(x',y')\) 只要满足 \(x>x',y>y'\) 则后者无用,那么有用的元素就仅是关于 \(x\) 的前缀最大 \(y\) 值,有用元素构成的序列在 \(x,y\) 两个轴上单调因而 \(x,y\) 轴具备同一性。总结来说,制造单调轴的方法就是观察无用状态。
1.3.4 根据对称得到两种暴力,从而根号分治。 - 问题的转化
2.1 变现。将能够处理的情况处理掉,剩余的情况就具备性质。
2.2 化归。将当前问题转化为已经解决的子问题。特别地,依题意扫描时序并调整状态时,有时会存在某时刻,问题被调成已解决问题,或者容易解耦。则此时刻就是“里程碑”。
2.3 上下界。只考虑在题面中确定的元素(题目信息)涉及到的限制,不考虑只涉及“自定义元素”的,据此估计上下界。并不一定取到,但有助于思考。
2.4 无解。判断无解性,可用于变现。 - 多个本质相同问题
这类问题可视作函数,各个问题的不同只在于传入的参数。
3.1 简化单个问题的计算方法。
3.2 递推。常常需要找到:某一轴上相邻(或者许多轴上同时相邻)的两个问题,比对这两个问题的差异,用其它问题(可能与原问题本质相同,或本质不同),或者 \(O(1)\) 计算来弥补。
3.3 合并连续段。某一轴上相邻(或者许多轴上同时相邻)的多个问题,合并统一计算。有时这些问题的答案甚至都会相同。
3.4 选择贡献主体。把二元关系“问题-答案来源元素”换成“答案来源元素-问题”,就是由元素贡献问题,而不是由问题访问元素。选取最合适的主体做贡献,可能不是题目所叙述的。
3.5 改变求和顺序。可以把“枚举位置在外层”的变量,当作常量从内层提到外面。 - 调整法
调整法的目的是减少合法解,或最优解的可能性,了解这种解的性质,优化描述法,或者直接解题。先尽量做小调整,从“只修改一个位置,或只把值修改一个单位”开始尝试,如果没有得到好的结论,就加大调整力度。
4.1 最优化问题。先拿出一种合法方案(未必最优)。对一次正确的调整,有两个限制。第一个是要保持方案合法性,第二个是在最优性判定上,要不劣于(甚至优于)调整前的状态。
4.2 判定性问题。可能存在多个方案最优,或者原问题没有“最优化”只有“合法性”。这就需要自定义“最优方案”,一般是“按某个字典序最小”。
4.3 元调整。将一次调整看成“删去若干元素并添加(等量)若干元素”,调整也能作为“解”被描述。用“放生”描述法描述“调整”本身,甚至对“调整”做调整,或者贪心。
§1.3 特殊方法
- 排列
1.1 关心元素的相对大小时,扫描值域轴,转化为(多层)01 序列。
1.2 建立置换环。常把环的个数用于势能分析。
1.3 关注(值域轴或位置轴上的)前后缀元素是否连续出现。
1.4 关注从某元素开始的最长上升子序列,还有等差序列等。
1.5 排列所包含的信息,和其逆排列相同。所以考察排列相当于考察其逆排列。
1.6 刻画集合 \(s\) 内部元素连续的方法:\(|s|=\max-\min+1\)。
1.7 排列计数的一般技巧,本质上有六种方法:
法一,扫描位置轴,微调(逐步描述)策略是值域分裂。
法二,扫描位置轴,微调策略是位置分裂。
法三,扫描位置轴,微调策略是位置交换。
法四,扫描值域轴,微调策略是位置分裂。
法五,扫描值域轴,微调策略是值域分裂。
法六,扫描值域轴,微调策略是值域交换。 - 类括号序列
2.1 记录当前多余的(数,即在 \(i\) 前面决定与后面匹配的位置数量,“允许操作没有结束”。
2.2 考虑与 \(l\) 配对的元素 \(k\) 的位置,如(...)...。注意,这里如果有多个和 \(l\) 配对的元素,选取最后一个,才能顺利分割子问题。
2.3 将(视为 +1,)视为 -1(视情况,所赋权值可能不同),关注序列前缀和,可绘制折线图,限制通常加在最值上。
2.4 合法括号序列,转化为树的结构。 - 直方图
每一列长度不同,希望顺序考虑这些列(例如希望先考虑长的列再考虑短的)。
3.1 如果 \(a_i\) 任意重排不影响答案,更换到值域轴上做。
3.2 建立笛卡尔树。
3.3 扫描高度轴,逐行处理。待处理部分是若干区间,处理后,因为一些立柱到顶,有些区间分裂。 - 网格图上的矩形
这类问题通常需要选择合适的描述顺序,利用递推和解耦(拆分函数),并充分利用单调性(也即两轴的本质同一)。
4.1 依次枚举左下角、边长(正方形计数)。
4.2 依次枚举左下角、左上角、宽(矩形计数)。有时在枚举前两者后,建立笛卡尔树以处理。
4.3 依次枚举上底所在行、下底所在行、两条竖边所在列。
第二章 动态规划
§2.1 一般方法
一、初步的状态
此处主要讨论生成式 dp,也就是含有“解”的 dp,无论求最优解,求合法解,抑或是求合法解数目。非生成式 dp 更接近数学推式子,还有直接递推的思维模式,一般来说,题面叙述到的变量本身,就已告知了状态,如 T-122。但要特别注意:非生成式 dp 中,若其他状态包含与当前状态相似的转移,可直接重复利用。
生成式 dp 中,子问题的意义在于确定“解在此部分的形态”。dp 和贪心的区别是什么?dp 会把与全局最优解有关的“解的部分”都记录下来。贪心只把使子问题达到最优的“解的部分”记录下来(若对子问题本身存在多解,只需记录“对外界”的最优解)。然而,不一定记录解本身,而是记录,“外界”若要调用“子问题的解”(在此基础上做微调,或以“子问题的解”权值计算更大结构的权值),所需的信息(微调的“接口”等)。
首要是定义状态,但定义状态时必须参照转移。状态定义时需要考虑五个要点:
- 描述法,描述法只跟答案计算过程,还有所选主体有关。
- 轴,即被扫描的最外层轴,和被(逐步描述法)微调维护的内层轴。若已知解,扫描此最外轴一般能求出解的权值(例如 dp 套 dp 或者 dp 套贪心,内层套的就是,权值或称“答案”的求解办法),但也有反例。
- 逐步转移(一般而言都是逐步)还是断点转移。
- 信息推后确定,尽量只在信息影响状态转移时才确定它;信息提前确定,该信息对未来转移至关重要,但若现在不确定并记录它,未来就没机会了;信息释放,某些信息不涉足未来转移,可与 dp 解耦,无需记录在状态,而是直接贡献给答案,或贡献给 dp 数组内的值,注意有许多信息可以在 dp 开始前就解耦。断点转逐步,通过增加状态和“当前操作未结束”来实现。逐步转断点,通过减少状态和释放信息来实现。
- 先粗糙再扩充,无法记录信息或完成转移时才扩充状态。此处还要介绍一个针对交互题非生成式 dp 的通法(这类题目,题面就已给出转移):先设计一个不足够的“状态一”,据此转移一次。为描述某个“状态一”转移一次后得到的状态,需要引入“状态二”。循环往复直到“状态二”对转移封闭,设计完毕。
信息推后确定,改成信息提前确定或信息释放,需要抓住这一点:信息推后确定时,大概是,现在记录下来一些“接口”“选项”“限制”供未来元素选择或遵从。提前确定或释放的策略则是,在“接口”“选项”“限制”建立的同时,确定某个未来元素(可能是抽象元素,例如,\(a,b,c\) 三个具体元素中的最大者)某个方面的值。要求是:对于任意情形,只要这个方面等于你所确定的值,就必能把接口接上,或满足当前限制。这样一来,只需记录“你所确定的值”(能做信息释放时甚至无需记录),而无需记录“接口”“选项”“限制”本身。
二、初步的转移
非常重要的一个概念是转移 dag。只需按 dag 的某一拓扑序,快速描述所有边,就能转移。描述边的方法一般有三种:
- 做贡献,即在点 \(i\) 处,处理一切以该点为起点的边。
- 受贡献,即在点 \(i\) 处,处理一切以该点为终点的边。特别地,可以扫描线(枚举 \(i\))微调维护各边的“当前权值”。
- 以边为主体,将多条(一般是边权 \(f_s+t\) 相同,或附加边权 \(t\),甚至附加边权来源相同)的边合并处理。
三、状态与转移的初步优化
打表看有效状态与有效值域是首要操作。
- 改换描述法。比如错解不优。选主体,轻限制。优先涵盖所有解,暂且忽略“解必须合法”“解必须优秀”。例如要选择子序列 \(a\) 使 \(\max a\) 最大。原本描述“子序列-子序列最大元素”,现在只需描述“子序列-子序列内任一元素”。
- 做对称,状态与答案互换。要求答案很小(例如答案是 01),且相同答案对应的状态是规整的(如一段区间。尤其是状态-答案具备单调性时,两个轴基本等价)。还能据此根号分治,见 T-102-6。
- 记忆化和下标换元。如定义 \(g(x)\) 为一切合法状态,则令新 dp 数组 \(dp_{x}\) 代替原来的 \(f_{g(x)}\)。很多时候,实际有值的状态不多。
- 对状态做合并,删除,或者等价替换。
观察无用状态。除了删除值无效的状态(计数中值为零,最优化中值为无穷),在最优化问题中还有“只需记录某种前缀最大值”。例如原状态 \(f_{i,j}\),其中 \(i\) 是被扫描的最外轴,\(j\) 是“当前代价”,数组内存放“最大值”,题目要求,在代价不超过 \(C\) 的前提下,最大化“解”对应的最终值。对 \(i\) 而言,无用状态 \((i,j)\) 的标志是存在 \(k\subseteq j\),但 \(f_{i,k}\le f_{i,j}\)。这是因为状态之间存在内禀的单调性,也就是状态 A“天生应该”比 B 好。据此观察无用转移边。最优化问题中,权值相同的一条边,转移给 A 和 B。如果状态 A“天生应该”比 B 好,那么可以认为,转移给 A 的边无用。经过观察,重新选取三种方法之一,描述一切有用边。
换句话说,是否应该改为维护连续段,或者说维护原数组的差分,前缀和,前缀最大值等。改为维护前缀最大值,一般是为了制造一对单调轴,其通用方法就是观察无用状态。改为维护前缀和的其中一种情况是,答案求 \(\sum f_{n,i}\),但第二维过大打算拉插。
四、模板式的优化
首先是要观察 dp 数组沿某一维的单调性、凸性。必须要先检查 三、4.。
普适型优化:
- 整体 dp。扫描线一个轴,数据结构(类滚动数组地)维护 dp 数组。这一步可能会用到单调性等性质。
- 用矩阵乘法表示转移。这就可以带修。一些 dp 数组衍生量,例如统计答案量(如果直接贡献答案的状态有很多),只需给矩阵扩维成
[f,ans]。 - cdq 分治。适用于一切半在线形式 \(f_i=\sum_{j<i}(f_j+w(j,i)),f_i=\max_{j<i}(f_j+w(j,i))\)。
性质驱动型优化:
- 决策单调性。
决策单调性是指,对任意 \(i,j\),从 \(i\) 转移还是沿 \(j\) 转移更优,写成 01 序列,只有两段。dp 是否具备决策单调性可以通过打表看出。
任一数组与一个凸包做 min,+ 卷积,这就是二分队列做决策单调性优化的绝佳例子。“任一数组”中的下标视为决策点。
1.1 二分队列。例如 \(f_i=\min(f_j+w(j,i))\)。对任两个 \(u,v\) 都可以二分找到临界点。做法如下:计算 \(f_i\) 时,若队头的管辖区间不包含 \(i\),就弹掉,循环往复。利用队头算 \(f_i\) 后,将 \(i\) 作为方案加入到队列。求 \(i\) 与队尾的临界点,以及队尾与次队尾的临界点,若后者更靠后,弹队尾。最终 \(i\) 从队尾入队。
1.2 整体分治。若 \(f\) 与自身无关,并非半在线问题,即 \(f_i=\min(g_j+w(j,i))\)。假设已知 \([l,r]\) 的决策点在 \([L,R]\) 范围内。暴力找出 \(\mathrm{mid}\) 的决策点 \(m\)(可扫描 \([L,R]\)),那么 \([l,\mathrm{mid}-1]\) 的决策点在 \([L,m]\) 中,\([\mathrm{mid}+1,R]\) 在 \([m,R]\) 中,递归求解。分治处理的问题不如二分队列宽泛。但当 \(w(j,i)\) 难以直接求,只能类似莫队来递推时,整体分治就发挥作用。 - 斜率优化。\(f_i=\min(f_j+a_i+b_j+c_id_j)\) 其中 \(a_i,c_i\) 仅同 \(i\) 有关,\(b_j,d_j\) 仅同 \(j\) 有关。令 \(g_i=f_i+b_i\),变形:\(f_i=\min(c_id_j+g_j)+a_i\)。
2.1 做贡献。对 \(j\),从它转移到任意满足 \(c_i=x\) 的位置时,代价都为 \(d_jx+g_j\),这是关于 \(x\) 的一次函数。每次查询 \(x=c_i\) 时刻,所有函数的最大值,再加上 \(a_i\) 即可。用李超树。
2.2 受贡献。对 \(j\) 建点 \((d_j,g_j)\)(若干一次函数求最值问题,将一次函数建点 \((k,b)\) 后转化为凸包。凸包能做闵和,还能两两合并)。对 \(i\),相当于枚举 \(j\),最小化“直线 \(y=-c_ix\) 与 \(x=d_j\) 的交点,与 \((d_j,g_j)\) 的距离”。维护凸包,对直线 \(y=-c_ix+b\),\(b\) 逐渐增大时,有一瞬间它突然和凸包有交点了,此时 \(b\) 就是最短距离。交点,或者说决策点必然是某一 \((d_j,g_j)\) 满足,在它凸包上前一个点 \(u\),后一个点 \(v\),有 \(u,j\) 的斜率小于 \(-c_i\) 小于 \(v,nxt\) 的斜率。
现在一个是维护凸包,一个是求最优 \(j\)。
如果 \(d_j\) 单调,可以 \(O(n)\) 维护凸包。
进一步地如果 \(c_i\) 也单调,则最优 \(j\) 沿 \(i\) 单调,可依据斜率,指针后移求最优 \(j\)。反之直接二分。
如果 \(d_j\) 不单调。可以上 cdq,每次求 \([l,m]\) 向 \([m+1,r]\) 的贡献时,先离线求 \([l,m]\) 的凸包。
此时如果 \(c_i\) 单调,就是总复杂度 \(O(n\log n)\),反之查询时还要二分,就是 \(O(n\log^2 n)\)。 - 凸优化。
凸函数支持两种操作:闵和(与另一凸函数卷积),还有加上另一凸函数。对于前者,新函数的定义域 \([l,r]\) 是两个函数定义域区间端点对应相加后的结果,边界点 \(f(l),f(r)\) 也是对应相加得到。取出两个函数所有长度为一的段,依斜率排序所有段,就是新函数各段从左到右依次排列的结果。用 set 维护所有段的斜率值即可。每次闵和就做 set 的启发式合并。
3.1 slope trick。总体上属于一种整体 dp。例如对二维 dp,将它看成若干函数 \(f_i(x)\),要求它们都是凸函数。扫描 \(i\),在以前函数的基础上,维护当前函数。把对 dp 数组的操作视作对函数的操作。如果涉及到闵和,还需额外维护当前函数的定义域端点。但有时 \(f_i\) 的定义域已经事先约定,就需要截断“直接闵和得到的结果”。“截断”相当于丢弃斜率最大,或最小的若干段。特殊情况见 T-104-3。
3.2 wqs 二分。最小代价根据所选“物品”数量是凸的,求选择 \(x\) 个物品的最小代价。忘记“选择 \(x\) 个物品”的要求,每次决策新选物品时,给代价自动加上所二分的值 \(m\),直接 dp。另需记录,当前状态的最优解选择了几个物品。找出最优方案之后,观察它选择的物品个数,与 \(x\) 的大小关系,并继续二分。若有二维限制“这个物品选 \(x\) 个,另一个选 \(y\) 个”,二分套二分即可。 - 四边形不等式。
\(w(j,i)\) 满足交叉大于包含。
4.1 若 \(w\) 满足四边形不等式,则 \(f_i=\min(f_j+w(j,i))\) 具备决策单调性,二分队列求解。
4.2 若 \(w\) 满足四边形不等式,则 \(f_{i,k}=\min(f_{j,k-1}+w_(j,i))\) 具备凸性,wqs 二分变成 4.7.1 情形,总复杂度 \(O(n\log n\log V)\)。
4.3 若 \(w\) 满足区间包含单调性和四边形不等式。则区间 dp \(f_{i,j}=\min(f_{i,k}+f_{k+1,j}+w(i,j))\) 满足,最优转移点 \(g_{i,j}\in [g_{i,j-1},g_{i+1,j}]\)。 - 区间查询。以母数组的多个区间为子数组,问子数组对应的 dp 值。可以运用猫树分治等数据结构题的处理办法,或者 4.2 的矩阵。
- 倒求贡献系数。初始化位置被修改,但转移图不变。倒序建立转移图,因为终点不变,即可 \(O(1)\) 应答一个起点的贡献。另有:查询多个 dp 结果之和,但转移完全相同。不妨统一赋初值,统一转移。
- 最短路优化。dp 的转移图不是 dag 时常采用。有时(无论是否 dag),从本层转移到下一层的方式固定,可改分层图为单层图,直接跑最短路。
- 拉格朗日插值。注意到 \(f_x=\sum a_i x^i\),因为 \(i>y\) 时 \(a_i\) 一定为零,所以只需求 \(x=0,1,2,...,y\) 对应的 \(f_x\),然后拉插。
- 生成函数处理卷积。注意和拉插区分。定义多项式 \(F=\sum f_i x^i\)(其中 \(i>y\) 时 \(f_i\) 一定为零),多次卷积形式的转移,就被看作 \(F\) 等几个多项式间的乘法。代值计算直接数乘,能规避卷积,就可以算出 \(F(x=0,1,2,...,y)\),然后插值还原 \(F\) 多项式本身。注意拉插式子 \(\sum y_i\prod_{j\neq i} \frac{x-x_j}{x_i-x_j}\) 可以变为 \(\sum y_i(\prod_{j\neq i}\frac{1}{x_i-x_j})(\prod (x-x_j))\frac{1}{x-x_i}\)。这三个因式中,后两个用多项式除法做到 \(O(y)\),最前面的是系数乘法做到 \(O(y)\),外层循环 \(O(y)\)。总共 \(O(y^2)\) 就能还原。
§2.2 具体方法
一、树形 dp
- 扫描最外轴“树轴”。以“子树”为子问题,定义 \(f_x\) 表示考虑 \(x\) 的子树的答案,子树的根是关键元素,起到并联儿子子树的重要作用。重点关注如何合并子树、上传信息。往往用另一维状态存储包含当前点的连通块,或链的信息,看它是否应向父亲延伸,即信息推后确定,“最后一个操作没有结束”。
- 扫描“dfs 序”轴。例如“选择一个连通块,必须包含根”的问题,则若选择一个点就必须选择其父亲。按 dfs 序依次考虑,每次可选择当前点,或不选。不选择当前点,就相当于跳过当前子树(转移是 \(f_i\to f_{i+\mathrm{siz}_i}\))。
- 扫描值域轴。例如 T-089。
- 扫描深度轴。
又及,容易证明树上背包复杂度是 \(n\) 的“维数乘二”次方,“维数”是指与背包容量等地位的状态维数。
参见数据结构、图论章节的“树”部分。
- 长链剖分优化树形 dp。dp 状态形如 \(f_{x,i}\),其中第二维取值 \(i\in[d_x,\mathrm{maxd}_x]\)。合并子树 \(x,y\) 到 \(r\) 时,暴力遍历 \(\min(\mathrm{maxd}_x,\mathrm{maxd}_y)\),总复杂度线性。一般来说超出的部分,也就是说 \(i\in(\min,\max)\) 之间的那些 \(f_{r,i}\) 可以直接继承较大的一边(例如 \(f_{x,i}\))。如果不是直接继承,一般采用数据结构维护 dp 数组,就可以打 tag 了。
二、区间 dp
区间 dp 与树形 dp 本质类似,因为区间只有包含和相离的关系,类比子树,它也只有包含和相离。那么状态 \(f_{l,r}\) 表示当前区间作为“子树”子问题。在这里切忌直接描述所有儿子节点,应改为逐步(类比树形 dp 的 dfs 过程,它也是逐儿子转移),可每次转移“剩余儿子里,最靠左的儿子”。设辅助状态 \(g_{l,r}\) 表示当前区间作为某个封闭子树 \([l,t]\) 的前缀(后面还能再添儿子节点)的答案。有的状压 dp 本质上也在描述树的结构,区间 dp 与之有异曲同工之妙。
常见转移有三种,同一题目里不一定全部出现,分别是:
\(f_{l,r}\gets f_{l,k}+f_{k+1,r}\)
\(f_{l,r}\gets g_{l,r},g_{l,r}\gets g_{l,k}+f_{k+1,r}\)
\(f_{l,r}\gets f{l+1,r},f_{l,r-1}\)
举例说明。有一类区间 dp 是把每个元素划分给若干集合之一。对集合内的元素,求出其(位置轴上)最大值 \(r\) 和最小值 \(l\),构成一个“区间”。要求对任两个集合,其“区间”或者不交,或者包含。那么“子树”形如 ###...###.....##### 的一段序列,其中 ... 是“当前子树根节点”的“儿子节点所在子树”对应的区间,而 ### 则表示属于“当前子树根节点对应集合”的元素。初始化是将单个元素划成子树(“根”对应的集合获得元素)。第一种转移,将当前“子树”区间向左,或向右扩张一步,被扩张元素分配给“根”对应的集合,相当于在边缘接上一个 #。第二种是,将当前“子树”向右扩展若干步,被扩张区间作为一个整体,成为当前节点的儿子节点,也就是在右边接上一个 ... 段。
三、状压 dp
- 选定最外轴后,内层轴用状压维护,状压能够存储的信息极多,因此大多数状压 dp 的初步设计并不困难。其典型优化有三个:
1.1 FWT。尤其是半在线子集卷积 \(f_s=\sum_{t\subset s}f_tg_{s-t}\)。
1.2 半在线高维前缀和 \(f_s=\sum_{t\subset s} f_t\) 的形式。可以 cdq 分治,每次从最高位是零的,转移向最高位是一的。
1.3 动态高维前缀和,每次令 \(a_s\gets a_s+t\) 并查询高维前缀和数组 \(f_t\) 的值。根号分治,每次修改只预处理较低的一半位,每次查询就只需枚举较高的一半位,做到 \(O(n\sqrt n)\)。 - 状压 dp 有一个重要分支,即状压图问题,见 T-101-2。
总是有一维状态形如“只考虑 \(s\) 这个导出子图内部的边”。有三个基本思路,一般联合使用:
2.1 用总数减去不合法。
2.2 从子结构拼成大型结构。
2.3 枚举“子集内编号最小的节点所在连通块”涉及哪些节点。
几种具体情形:
2.4 连通 dag 计数问题中,常使用“当前所有入度为零的点”作为子结构。
2.5 点、边双连通分量,或强连通分量在最优化问题中,常用耳分解为子结构,连通分量本身为大结构,配合“信息提前确定”(提前确定当前“耳”的终点)“最后一个操作没有结束”。
2.6 点、边双连通分量,或强连通分量在计数问题中,常用连通分量本身作为子结构,而大型结构则成了缩点树,圆方树或缩点 dag。大型结构常作为“被减去的不合法”参与转移。
值得特别介绍的是状压树计数问题,注意圆方树计数也属于“树计数”的范畴。
2.7 定一个根上树形 dp。
2.8 改从值域轴扫描,每次加入编号为 \(i\) 的点到当前连通块或虚树中。尤其是与状压图结合时,\(f_{i,s}\) 表示“要求点的编号都小于等于 \(i\),\(s\) 内选边”的方案数。
2.9 如果 dp 圆方树形态(仙人掌,或点双连通子图计数),关键元素是割点。以割点为“子树的根”,用类似子树合并的思维方式,将子状态并联起来。“\(s\) 内选边,使得 \(s\) 是仙人掌,要求割点的编号都小于等于 \(i\)”“\(s\) 内选边,不要求别的,只要割点的编号都小于等于 \(i\)”。
四、期望 dp
普适型方法:
- 期望就是加权平均数,尤其当大分母(所有情况数目)确定时,可直接转化为计数。
- 扫描值域轴,拆 \(E(X)=\sum P(X\ge k)\)。
- 改主体算贡献。例如计算全局符合条件元素的期望数目,改为算各个元素符合条件的概率之和。
- 把每次均匀随机改为随机排列并依次操作,遇到不能操作的就跳过。然后可以拆贡献到点上(“本点在多少个排列中被操作”)。
- min-max 容斥。
- 转移图是 dag 的问题,可按拓扑序直接求解。反之通过高斯消元求解。特别地,如果转移图“类似但不确切是”线性结构、树形结构时,采用主元法,也即假设已经求得了若干关键元素的期望,用它们加上常数,去表示其余元素的期望,把一切转移环打破,然后正常地扫描一个轴来求解。转移到一个关键元素,就得到一个方程。最后解方程算出所有关键元素的实际期望。
性质驱动型方法:
- 高次期望的拆分。应用了期望的线性性。\(E((x+1)^2)=E(x^2)+2E(x)+1\)。用于转移时,就要用两个状态分别记录 \(E(x^2),E(x)\)。
- 对于求期望步数的题目,求转移附加边权时,常常要将“选到就意味着升级的元素”视作“障碍元素”,与其余“障碍元素”混在一起,计算“选到障碍元素的期望步数”,如 CF1392H。
- 例如 \(E_0=0,E_x=\min(E_{x-1}+1,\frac{1}{p}\sum_i (E_i+1))\),输出八位实数。无法高斯消元。输出实数提示乱搞,先按 \(E_i=i\) 赋初值,然后对每个 \(x\) 求出 min 式子中的后一项(按照 \(E_i=i\) 求的),并据此更新一次。迭代数百次之后直接输出。在期望 dp 的转移式中,只要带 max/min,且要求输出实数,就可以尝试了。
五、其他
- 主动算重。对几个相同的元素,给它们钦定“标号大小”顺序,使之不同。最后除以钦定顺序的方案数即可。
- 基环树(森林)的计数技巧,有三种:
2.1 先钦定一些环,然后把环缩成点,并用 prufer 序列,或 Cayley 定理完成常规森林计数。
2.2 逐步添点,维护“由前 \(i\) 个点构成的虚基环树森林”形态。
2.3 给边定向,指向父亲,环上会有两种方向,对一个连通块除以二即可。从小到大加入点,加入一个点时要把它可达的点全部加入,如果可达的点中有编号比“当前时刻”更大的,就什么也不做。也即扫描 \(i\) 的过程中,要么添加一个包含 \(i\) 且编号都小于等于 \(i\) 的环,要么添加一条以 \(i\) 为结尾且编号都小于等于 \(i\) 的链,这链一直接到已有环上/已有的树上,要么添加一个 ρ 形态结构,要么等待机会,什么都不做,任由后面的点把 \(i\) 添进去。 - 二分图的计数技巧。
3.1 一个连通块仅两种黑白染色方法。
3.2 扫描右部点做 dp,状压或枚举左部点涉及的全部信息。 - 实数轴上的相对大小问题。有一个扩倍的经典思路,就是把每个单位长度分成 \(n\) 份,然后 \(O(n!)\) 枚举各元素小数部分的相对大小关系(这部分可改为 dp 确定)。
第三章 数据结构
§3.1 一般方法
一、代数结构与信息的性质(选择性阅读)
这一部分远非严谨的数学证明,甚至在定义上也可能有小错误,请多包涵。然而需要强调的核心思想是:要关心所研究对象满足的代数结构类型,确定其性质,从而选用最合适的数据结构来维护。本节相当于“元素”在数据结构语境下的一个显影。
一个代数结构是一个集合,连同定义在该集合上的若干运算,这些运算满足特定的公理。例如对于集合 \(R\) 内的元素,可以定义若干双目运算“加法”“乘法”等等。一般来说,这个“集合”可能有两种现实意义,分别是:“集合”由所维护的信息组成;“集合”由某种操作本身组成。
- 半群。
定义:满足结合律的运算和对应元素 \((a+b)+c=a+(b+c)\)。还有封闭性要求运算结果仍然在集合内。
意义:可加性/可合并性。如果集合定义在信息上,那么可以按任意先后顺序合并信息,从而允许批合并一个连续段,或者应用分治数据结构,解决单答案问题等。如果集合定义在操作上,那么运算相当于操作的复合,就允许批合并操作,并把它们当作一个操作来处理,非常实用。
1.1 幺半群。
定义:半群基础上,存在单位元 \(e\) 满足 \(e+a=a\)。
意义:允许信息与“空信息”合并,从而可以放心在线段树等中,构建“空节点”。我们见到的题目里,只要代数结构是半群,一般就也是幺半群。如果不是,不妨额外引入一个单位元,就能解决问题。
1.2 幂等半群/带。
定义:半群基础上,保证幂等性 \(a+a=a\)。
意义:定义在信息上,就具备可重算性,允许重复合并,也就允许了更加野蛮的批合并求解单答案。例如 st 表。
1.2.1 交换幂等半群/半格。
定义:幂等半群基础上,满足交换律 \(a+b=b+a\)。
意义:定义在集合上的交/并,定义在实数上的 max/min,还有数论中的 gcd/lcm,都属于这样的代数结构。这种集合是天然的偏序集(集合内具备若干形如 \(a\le b\) 的关系,这个关系必须具备传递性。但是可以有若干对元素不可比):若 \(a+b=a\) 则认为 \(a\le b\),那么 \(a\le b,b\le c\to a+c=(a+b)+c=a+b+b+c=(a+b)+(b+c)=a+b=a\to a\le c\)。这类代数结构是容斥的基础,见 T-101-3。也是 FWT 的背景。有没有觉得与/或 FMT 特别像一个容斥?从而可以类比着发明“快速 gcd/lcm 卷积”等。当然,无需发明“快速 max/min 卷积”(笑)。
为什么传递性证明没有用到交换律,但是要在满足交换律的代数结构上定义偏序集呢?因为交换律的作用是让左偏序和右偏序一致(\(a\le b\to b\ge a\)),从而得到一个自然的、对称的偏序关系,这也是算法中最常见的情形。这个不必纠结。
1.3 交换半群。
定义:半群基础上,满足交换律。
意义:交换律使得合并时,不必按左右顺序。定义在操作上时,交换律是莫队的基础。 - 群。
定义:幺半群基础上,每个元素有它对应的逆元,满足 \(b+b^{-1}=e\)。
意义:如果定义在信息上,是可减性,允许差分;如果定义在操作上,是可逆性,操作存在逆元,就允许撤销操作,用于莫队等算法。
2.1 循环群。
定义:群内集合可以由生成元(用原根来理解)构造,集合 \(R=\{ng,n\in \mathbb{Z}\}\)。循环群可以通过“在一个环上转圈”来理解。
意义:例如取模意义下的指数运算。循环群要求 \(0g=e\)。有限循环群还要求存在正整数 \(x\) 使得 \(xg=e\)(无限循环群可以视作环特别大以至直线)。
接下来是定义在同一集合上,且有两种运算的代数结构: - 格。
定义:两个定义集合一样的半格,设运算分别为 \(\cap,cup\)。这两种运算内部(自己和自己)具备交换律和结合律;两种运算之间存在吸收律 \(a\cup(a\cap b)=a,a\cap(a\cup b)=a\)。两种运算各自的幂等性可以由吸收律推出:\(a=a\cup (a\cap (a\cup a))==a\cup a\),另一个亦然。
意义:格意味着,对应偏序集的偏序 dag 满足,任意两个元素具备唯一“最近公共祖先”和唯一“最近公共子孙”,这两个元素分别是 \(a\cup b,a\cap b\) 的运算结果。集合交/并,max/min,gcd/lcm 就是格。
3.1 全序对合格。
定义:全序格(偏序 dag 是一条链)一定是分配格,两个半格定义的运算之间具备分配律。格配备了“对合反序映射”,类似负数。
意义:max/min 或者二元布尔代数更精确地属于此类格。“对合”意味着类似 \(-\max(-a,-b)=\min(a,b)\) 的性质能够成立。 - 非交换半环/分配幺半群。
定义:两个定义集合一样的幺半群,设运算分别为加法和乘法,则乘法对加法满足分配律 \(a\times (b+c)=a\times b+a\times c\)。注意这里的乘法是半群里的,不一定有逆元、交换律。
意义:将“信息合并”与“操作”分别对应加法和乘法。那么“非交换半环”就是一个特别合适的,对线段树“打 tag”过程的刻画,分配律允许标记下传,而“打 tag”这一操作的结合律允许标记合并。幺半群是为了允许存在空 tag 和空信息。
4.1 热带半环
定义:max,+ 或者 min,+ 的半环。
意义:可以定义 max,+ 矩阵乘法等,广泛用于 dp 转移,可得到矩阵快速幂算法。加法对 min,max 有分配律,所以有常见变形 \(t+\max(a,b)=\max(t+a,t+b)\)。注意热带半环无法处理三操作 min,+,-,或者 min,max,+,见 T-130。
二、轴在数据结构中的应用
本节相当于“轴”在数据结构语境下的显影。
- 扫描线。假设扫到 \(t\),一般实时维护“在外部轴时刻 \(t\),内部轴上坐标为 \(i\) 的元素的值是什么”。若有两个内部轴,就维护二维平面。所谓“强制在线”,就意味着题目指定了最外轴是谁,并钦定最外轴仅允许直接扫描线。
- 区间查询。每次查询涉及到最外轴上的一个区间。如果信息具备可减性,可以尝试差分。
法一,坚持扫描线,视情况可做线段树历史和。
法二,类猫树分治。
法三,令若干个轴地位平等,使用莫队。
法四,分块。 - 规避删除,加入,或者修改操作。
大部分修改操作都可以视为先删除再加入。将一个大修改解耦成若干“先删除后加入”的“单位修改”。
3.1 对最外层线段树分治,回滚莫队,或者分块并定期重构(后两者本质相同)。
3.2 最外轴翻转。加入变删除,删除变加入。如果终止状态确定,常用这个办法。 - cdq 分治。需要满足:可将贡献解耦并视为“初始元素、修改和查询间的连边”,一切贡献从最外轴的小端向大端进行。
- 转 01 序列。尤其是限制只涉及“元素相对大小关系”的题目,可把值域轴放外面做扫描线,设大于当前值的元素为一,反之为零。
- 在答案值域轴上直接二分。常常用数量关系来刻画“本区间内是否存在答案”。搭配线段树上二分更佳。
6.1 整体二分。需要满足:可二分答案,可将贡献解耦并视为“初始元素、修改和查询间的连边”,贡献依任意顺序可加。 - 查询轴。放在内层维护各个查询“当前的答案”,碰到 \(l_j=i\) 则插入一个查询,碰到 \(r_j=i\) 则问答案。
三、其他一般方法
普适型方法:
- 数据结构“三步走”:预处理。修改。查询。
千万不要忽视查询,查询时能处理的事情很多,预处理或修改时难做就放弃,因为不是每一时刻都得保证能 \(O(1)\) 或 \(O(\log)\) 知道任一元素的答案。
预处理方法有折半、分块、分治等。询问复杂度过低往往是预处理造成的。 - 寻找一个熟知的问题,比较这两个问题的强弱,可得复杂度下界,也可借鉴已知问题的思路。
- 合并元素、更改主体,统一贡献、统一修改。
缩连续段,尤其是具备单调性的时候。因为单调性暗示着两个轴地位一致。一般而言,这时候的连续段,就不只是在一个维度上连续,而是同时在多个维度上。 - 只考虑关键元素。关键元素的数目可能并不多。如果元素没有用了,就把它删除,或与其他元素合并起来。
性质驱动型方法:
- 数量表示信息。尤其是带有“复杂结构判定”的题目,尝试用某种数量(类似于哈希值)来替代区间信息,转化为判定对应区间的这个值。好就好在:第一点,复杂结构压成的数量,往往具备可加性,合并数值远快于合并结构。第二点,异或哈希还具备可减性,允许迅速对消。
- 每个元素具备 \(k\) 种阶段,最外轴扫描过程中,阶段只会增加不会回退。每次暴力找出待更新的元素,设单次找出 \(O(a)\) 则总复杂度 \(O(nka)\)。
特别地,元素的两个阶段是“存在”与“消逝”。例如元素只会合并而不会分裂。将合并得来的视为抽象元素,两个抽象元素合并时,就视为旧元素消逝而新元素开始存在。故暴力合并是正确的。有时会解耦维护,也即采用 \(k\) 个数据结构分别维护每一类元素。 - 割补法。问题形如,在二维平面上查询一个区域内的信息。将大区域分割开来,由于经过了解耦,若干小区域的求法常常完全不同(可以离线,可以在线,可以改主体,可以递推。每一类小区域的预算都和总预算相同)。
首要是分析,信息是否具备可加性、可减性、可重算性。可减性意味着允许差分,可重算性则允许小区域互相重叠。
在二维平面上,只要信息满足可加性,\(O(\log)\) 就足够处理(一定注意:直线、矩形未必与轴平行):
在线(带修)求两条平行直线之间范围内的信息;
在线(带修)求某条直线上某个点 \((x,kx+b)\) 左上方的信息;
离线求“确定了小于等于三条边的半矩形”内的信息(扫描线)。 - 查询时有临时修改。但不是真正在数据结构上修改。若忽略修改查出最大值,改后可能不再是了。如果修改涉及的元素很少,同时维护最大值、次大值等即可。
- 查询涉及“等于某值的全体元素”。思考这个值是否一定为最值。或者说思考只关注最值元素能否回答查询。
- “区间”“点对”。
6.1 既可认为是一维的,也可以为它建立两个轴(\(l,r\) 的两个轴,画在二维图上考虑,一般用分治或者分块处理)。
6.2 思考特殊性质:区间互不包含如何做;区间形成树形结构怎么做。有时题目所示的区间暗中满足了几项性质。
§3.2 分治数据结构
- 多 tag 问题。一个操作序列,可被压缩成顺序确定的“操作种数”次操作,其先后顺序需要自定义,例如有加有乘的情况,就把操作序列压缩成先乘 \(a\) 再加 \(b\)。确定顺序后,只需考虑合并 tag,例如有两种操作时,就相当于“任意元素先与 \(a\) 做操作一,再与 \(b\) 做操作二,再与 \(a'\) 操作一,再与 \(b'\) 操作二的结果,等于先后与 \(a'',b''\) 做操作一,二的结果,求这个 \(a'',b''\)”。
将 tag 视作代数结构,则 tag 之间能做乘法(只有结合律,一般不满足交换律),tag 对信息的乘法运算(或者说影响)满足分配律。
一般来说,tag 可被视作“特殊定义乘法”的矩阵,此时,信息也需变成对应类型的矩阵。矩阵乘法在半环上定义,因此许多 tag 合并规则可以统一为矩阵运算。
涉及空节点(空 tag)的 pushup(与 pushdown)需要特别注意,一般来说空节点被视为单位元。 - 类势能线段树。某特殊单点操作(或特殊元素)总计进行 \(m\) 次。dfs 整棵线段树,对当前访问的节点,分别检索其两个儿子的子树内,是否存在“需要特殊单点操作的元素”(或存在特殊元素)。若有则递归该儿子。复杂度为 \(O(m\log\frac{n}{m})\)(\(n\) 为线段树元素总个数)。
2.1 区间最值操作。查询 \(\sum^r_l a_i\),修改 \(\max(a_i,x)\to a_i,i\in [l,r]\)。记录区间最小值和次小值。若 \(x\) 在二者之间,则打标记更改最大值,反之若 \(x\) 同时大于二者,就递归儿子。 - 主席树。
3.1 常选取时间轴、值域轴等作为主席树的版本轴。版本轴可以作为最外轴被独立维护。例如要同时修改主席树好几个版本的点值,可以用外层线段树套内层主席树。
3.2 区间修改等情况里,主席树标记下传需要额外建立节点。为了空间更省,常数更小,最好标记永久化。
3.3 多版本问题除了主席树,还可以建立版本继承关系树。 - 历史最值与历史和。
法一,每节点维护一个 max,+ 矩阵[a,b,0],其中 \(a\) 表示当前的值,\(b\) 表示到当前的历史最值。用 tag 维护操作,给每个节点放置 3*3 标记矩阵。但无法标记永久化,因为矩阵乘法有顺序。
法二,给节点维护时间戳,代表“最后一次对当前节点打标记的时刻”。标记永久化时,可以利用时间戳,使子节点给祖先做值上的贡献,假性“同步版本”。 - 单边递归线段树。线段树子节点 pushup 时,需要用到某一儿子一段前缀的信息。用法:尝试用区间前缀最大值序列描述区间答案。
- 线段树分治。将所有“删除”变为撤销上一步。可以维护“操作栈”,发挥作用时入栈,消除影响时从栈顶倒序消除,弹栈至空。线段树分治可以半在线,dfs 到某个时刻(叶子节点)后,依据此时的状态,对后面的时刻产生加边或删边的影响。
- 线段树卡空间,定期重构。
取消动态开点的办法:离线所有询问(然后离散化),统一预建树。 - 暴力跳父亲。平衡树或线段树上,已知节点编号,求在树上的相对位置(类似于平衡树上求特定节点排名,或 zkw 线段树),可以从它往上跳父亲。
- 平衡树带交并。相当于平衡树领域的线段树合并。若合并 \(u\) 和 \(v\),不妨设 \(u\) 的权值更小,是合并后的根。需要将 \(v\) 按照 \(u\) 的变量值分裂成两个子树,分别与 \(u\) 的左右儿子合并。合并平衡树的过程中,需要合并值相同的对应节点如下:
int A,B,C;split(v,t[u].v-1,A,C),split(C,t[u].v,B,C);if(B) fa[find(B)]=find(u); - 线段树合并。复杂度 \(O(\min (n,m))\)。合并时,如果两棵树当前节点中,有任一个没有儿子,就特殊处理终止递归。反之则各自 pushdown,分别合并左右儿子(若某棵树只有一个儿子,pushdown 时建出另一儿子)。
- 线段树分裂。区间操作变为全局操作(也就是给待操作区间建立 \(O(\log)\) 个虚点,从而具备一棵完整线段树的结构)。最后线段树合并回来。线段树分裂的复杂度是一次 \(O(\log)\),而线段树合并每递归一下就减少全局上的一个节点,故其总复杂度为 \(O(n\log V)\)。分析“全局节点个数”为势能量从而计算复杂度的,在数据结构题目中很常考,如 QOJ9559 P5494。我认为线段树分裂能做的,大多用平衡树带交并也能解决。
- 定长分块中的线段树。涉及到相邻两块,前一块选 \(i\) 后一块选 \(j\) 要求 \(j-i<C\) 的问题,不妨把两块叠合起来(有时将某一块倒序放置)建立一个线段树,就变成形如 \(i<j\) 的限制。具体见 T-221。
- 线段树维护复合函数。输入代表传进的值,输出表示经过此区间之后的答案,每个节点相当于一个函数,子节点 pushup 相当于函数的复合。
- 分治,cdq 分治,猫树。其核心总在于用 \(\mathrm{mid}\) 把元素划分成左右。两部分尽量解耦,但有时藕断丝连,需要先内部消化贡献和需求,再向对面请求。
14.1 猫树问题需要经过适当预处理,使得一个查询只访问一个节点就得到解决。允许考虑“与当前猫树节点有关的‘贡献点对’”,可能是指“被节点区间包含”,还可能含有“被当前节点某儿子区间包含的‘贡献点对’”。
14.2 类似决策单调性分治的办法,可广泛用于“\(f_i\) 有单调性,同时求多个 \(f_i\)”的问题。 - trie。
15.1 求一个数的前驱和后缀,本要用 set。用压位 trie,时间复杂度降至 \(O(\log_w(n))\)。取 \(w=64\),即每个节点有 64 个子树。
15.2 01 trie 全局加一。只需从低位到高位建立 trie,全局加一时,每次将当前节点的左右子树互换,并递归进“一”子树即可,共计 \(O(\log)\) 次交换。
§3.3 根号数据结构
一、分块
- 善用预处理。
- 查询的 \(O(\sqrt n)\) 预算可能用于:
每块的末尾记录某值,每次从最近块的末尾递推到精确位置。
枚举 \(i\) 所在块(\(i\) 属于某整块)或者 \(i\) 具体值(散块)。 - 卡空间。做贡献,一块一块做。就只需保存当前的块的预处理和标记,空间复杂度下降为 \(O(n)\)。
- 主体是 \((l,r)\) 点对。画在二维图上考虑。类似割补法,分情况处理。\(l,r\) 均属于整块;\(l\) 属于散块,\(r\) 属于整块;\(l,r\) 均属于散块。见 T-102-4。特别是散块对散块可以用归并的办法。
- 定长分块。“一段长 \(\le c\) 的区间”的问题。每隔定值 \(c\) 分段或设置关键点,这种区间最多跨过一个关键点。
- 块内-块外。本质是先处理块内关系,处理完后一致对外。
- 值域分块代替二分。扫描值域轴,一个块一个块地跳。当跳不过当前块(预算余量不足块内总价值)时,改为在块内一个一个跳,直到答案位置。
- 倍增分块。它适用于“对块 \([B^k,B^{k+1})\) 内元素的特殊操作,元素必然增加 \(B^k\),故暴力进行特殊操作能够接受”的情形。顺带一提,把特殊操作拿出来分析,往往发现它进行的次数不太多,比如老哥或者根号量级。
二、莫队
- 带老哥莫队的优化。回滚莫队,值域分块,二次离线。
1.1 回滚莫队。虽能规避删除操作,但(例如按查询左端点分块处理)左端点所在块,必须能够维护“撤销上一步”操作(莫队过程:同一块内,\(r\) 不停增加,但 \(l\) 保持在当前块的右端点 \(B_r\)。每次查询时,令 \(l\) 减小直到合适位置,然后查询。查询完后 \(l\) 要归位)。
如果做不到“撤销上一步”,就应该考虑 \(l\) 减小时不影响原数组的值,也就是把 \([B_r,r]\) 的状态复制一遍,在上面做正常修改(还是只加的),然后查询,最后把复制出的数组丢弃。
常见做法并非复制数组,而是通过某种方式,在不修改 \([B_r,r]\) 状态的情况下,直接算出 \(l\) 减小一后的答案。
注意回滚莫队和定期重构基本相同,关键都在于如何“添加最后 \(\sqrt n\) 个修改”。一般而言因为是每个查询添一遍,查询之间互不影响,所以这些修改的添加顺序是可以任意指定的。
并且因为是对一个块扫一遍全局,甚至可以针对当前块,个性化指定其扫描全局的方式。常见办法形如,把本块内的操作涉及元素高亮标出,特殊处理。或者据这 \(O(\sqrt n)\) 个修改涉及的关键元素进行全局分段等,对照 T-070。
线段树分治又何尝不是这样,需要能够撤销上一步操作。
1.2 值域分块。修改 \(O(1)\),查询允许 \(O(\sqrt n)\)。
1.3 二次离线。把 \(O(n\sqrt n)\) 个查询离线下来,看能否 \(O(n\sqrt n)\) 预处理 \(O(1)\) 回答。 - 带权莫队。将 \(l=x\) 的查询次数记录到 \(a_x\) 处,从左往右扫计数器对 \(a_i\) 累计求和,每当计数器大于 \(\sqrt q\) 就新开一个块;每当本块长度大于 \(\sqrt n\) 也要新开一块。如果存在某个 \(a_x>>\sqrt q\),那么对 \(l=x\) 的查询们特殊处理,扫一遍真正的“只删除莫队”(无需撤销上一步)即可。
- 高维莫队。比如三维状态 \(x_1,x_2,x_3\),排序时用第一关键字是 \(x_1\) 所在块,第二关键字是 \(x_2\) 所在块,第三关键字是 \(x_3\)。注意要将大修改拆成单次只加入或删除一个元素的“单位修改”。
三、根号分治
常按节点度数,出现次数,连通块大小等根号分治。
- 问题涉及两元素间的关系,但是直接维护“任两个元素间的关系”是不可取的。可用根号分治把关系变成单向(从小型元素到大型元素)的。
- 如果某些元素的和是一个不大的定值(\(10^12,10^5\) 分别代表 \(O(\sqrt n),O(n\sqrt n)\) 的复杂度),不妨根号分治。有两个对称的结论:
2.1 大于 \(\sqrt n\) 的元素仅 \(O(\sqrt n)\) 个。
2.2 所有元素只有 \(O(\sqrt n)\) 种本质不同的出现次数。 - 平衡复杂度。用若干种不同角度的暴力做法,分别处理自身适宜的数据范围。
§3.4 树
- 树上的主体,常见的就只有点,边,子树,连通块,树链(路径)这五种。
1.1 点减边容斥。常用于统计树上所有连通块的权值和。
1.1.1 01 权值:问合法连通块数目。用单点合法的数目之和,减去单边合法的数目之和。“单边合法”就是说边的两个端点都合法。
1.1.2 扩展权值:点集为树上连通块,但当点集为 \(S\) 时,它对答案的贡献应为 \(f_S\),不必须是 01。例如 \(f_S=3|S|-2\) 时,就需要给“单点合法”赋权值一,并给“单边合法”赋权值二。显然 \(f_S\) 是 \(|S|,|S|-1\) 的线性组合时,总有一种给“单点-单边”的赋权方法。如果 \(f_S\) 更加奇怪,就可能需要引入“合法子图”的概念,或者对于度数不同的节点赋予不同的权值了。 - 轴。
如果把“树轴”放外面,最常见的是合并子树信息(可以线段树合并),按 dfs 序扫描,按深度扫描,不常见的有按节点编号扫描等。
如果把“树轴”放里面,常见的有即时维护虚树,重链剖分并即时维护线段树,dfs 序拍平子树变成区间等。 - 树上路径问题。
3.1 树上差分。
3.1.1 普通的链求和,差分转化为三个到根链求和。有时可建立主席树维护“节点 \(i\) 的到根链”。
3.1.2 虚树相关问题,有时把虚树拆成若干条到根链,以到根链为主体。有时要考虑不同到根链间的关系和贡献。例如:求一棵子树内是否有某种颜色,可以将所有有此颜色的点按 dfs 序排序,每点加一,每相邻两点的 lca 减一,答案即为子树内标记和。
3.2 一些对称(做贡献-受贡献层面):单点改链求和,变成子树修改单点查询。类似地,链加单点查询,变成单点加子树求和。及二者的逆转化。链改可视为到根链改,从而对称成子树操作。
3.3 离线把路径挂到 \(u,v,\mathrm{lca}\) 上。然后把“树轴”放外面。
3.4 树链剖分,或者点分治,见下。 - 树链剖分。
重剖几乎总是把每个点轻儿子,或轻儿子子树的信息合并上来直接挂在本点,然后用线段树去处理重儿子,及父亲方向的信息。
4.1 重剖维护树上 ddp。
4.2 带修邻域查询问题,经典重剖解决。注意“k-邻域”“连通块”是差不多的。
4.3 子树邻域查询,提示长剖。 - 点分治。
分治是在分治中点考虑“所有跨过该中点,且被当前分治区间包含的区间”。点分治是在分治重心考虑“所有包含该重心,且被当前分治树包含的连通块,或链”。用于解决树上路径或连通块问题。 - 其他。
6.1 建立虚树,将关键点按 dfs 序排序,求出相邻元素的 lca,将所有 lca 加入到序列,并再次排序,然后将新序列相邻两元素的 lca,向后一元素连边即可。
6.2 树上区间 lca,等于区间内节点中 dfn 最小的,和最大的,二者 lca。也等于(编号)相邻两点 lca 深度最小者。
6.3 树上莫队。把括号序分块,在括号序上跑莫队。
6.4 一条到根链上的点权依次临时加上 \(k,k+1,k+2,k+3,...\)。先把所有点权减去该点深度(或者加上深度),转换为到根链临时加上 \(x\)。
§3.5 字符串
字符串题经常用 kmp 刻画 border,用 manacher 刻画回文,用 sa 去刻画子串相同的限制。
如果问题与若干个字符串的公共前缀有关,可以尝试建立字典树,转换成树上问题。
如果问题与子串或子串间的公共前缀有关,可以尝试 sa,利用 height 数组转化成直方图问题。
如果问题对字典序做出限制,一般方法是枚举 lcp 再枚举第一个不同的值。比较字典序大小也是这个思路,二分 lcp 长度,哈希判断。比较字典序还可考虑逆排列。
border 是 \(s\) 的一个相等前缀后缀。它具有如下性质:
若 \(L\) 为周期,则 \([1,n-L]\) 为 border,反之亦然。
定义 kmp 数组 \(f_i\) 表示前缀 \(i\) 的最长 border 长度。如果要求出所有 border,只需不断跳到 \(f_i\) 即可(因为 border 的 border 还是 border)。连接 \(i,f_i\),建立“border 树”,则前缀 \(i\) 的所有祖先就是这个前缀的所有 border。
若存在 border,则其最短 border 长度不超过字符串长度的一半。
若有周期 \(p,q,p+q\le |s|\),则有 \(\gcd(p,q)\) 也是周期。
\(s\) 所有长度不小于 \(\frac{|s|}{2}\) 的 border 构成等差数列。证明取最长 border 长为 \(n-p\) 和任一其它 border \(n-q\),则 \(p,q\) 为周期,\(p+q\le |s|\),故 \(\gcd(p,q)\) 为周期,这样,\(n-\gcd(p,q)\) 也是 border,故 \(\gcd(p,q)=p\),\(q\) 是 \(p\) 的倍数。容易证明所有 \(n-kp\) 都能取到。这相当于说,不停地跳 kmp,跳一段等差数列之后,得到了一个小于 \(\frac{|s|}{2}\) 的 border,对于这个东西,再跳一段等差数列,当前 border 缩减一半。故可以把 \(s\) 的所有 border 按照长度 \([1,2),[2,4),...\) 分段,每一段都是等差数列形式的。
AC 自动机。建立 fail 树。这棵树类似于把所有建立进去的字符串求了一个 border 树。常用结论:
一个单词在匹配串 \(s\) 中出现次数之和,等于它在其所有前缀中作为后缀出现的次数之和。
和字符串 \(s\) 的某个后缀的单词数量,就是 \(s\) 对应的 AC 自动机节点 \(p\) 向根的路径上,以每个点结尾的单词数量之和,常常需要预处理,存到 \(f_p\) 中。
§3.6 其他
- 倍增。对每个元素求出(最优)后继,每个元素传向它的后继,倍增加速。
1.1 有两种对称的分块平衡方法,分别对应两种倍增。一种是以“步数”为状态,以“位置”为答案,处理“从 \(x\) 开始,跳 \(\sqrt n,2\sqrt n,...\) 步到哪里;从 \(x\) 开始,跳 \(1,2,...,\sqrt{n}-1\) 到哪里”。另一种是以“位置”为状态,以“步数”为答案,处理“从 \(x\) 开始,跳多少步跳出当前块;跳出当前块后第一个跳到的是哪里”。 - 启发式合并允许“陪跑”。枚举小一边的同时,可以枚举大一边的等量元素。
- baka's trick。如果双指针不便维护删除,可以维护 \(\mathrm{mid}\) 分别到 \(l,l+1,...,\mathrm{mid}\) 和到 \(r\) 各自的值,每一次增加 \(r\) 直接操作;增加 \(l\) 可以直接调用 \(l+1\) 即可;如果 \(l>\mathrm{mid}\) 令 \(\mathrm{mid}=r\) 然后重构即可。
- 十字链表。可以维护元素邻接相对位置。在元素邻接相对位置变化不大时可以处理,见 SNOI2024 矩阵。
- 并查集。
5.1 类链表并查集。常见于染色,或覆盖问题中。多次把一修改为零,询问“某位置后面首个是一的位置”。
5.2 可撤销并查集。放弃路径压缩即可。
5.3 可持久化并查集。用主席树维护并查集数组。
5.4 带权并查集。用标记。如果要支持撤销,可以维护永久化标记数组,使得任意时刻一个点的价值,就是从它到根的标记之和。
5.5 倍增并查集。记录 \(fa_{i,j}\),使 \(j\) 指代一段连续的长为 \(2^j\) 的区间。对每一次区间覆盖问题,二进制拆分这个区间,在相应子区间大小的层级,维护并查集区间染色。最后较为暴力地由 \(j=30,29,28,...,0\) 递减向下传递并查集信息,于 \(j=0\) 层查询即可。用于解决“要求两个区间内的元素对应相等”。
5.6 种类并查集。每个点拆分成,举例说,两个点。\(a\) 表示“朋友类”,\(a+n\) 表示“敌人类”,遵从“朋友的朋友是朋友”“敌人的敌人是朋友”的规则,用以合并。
第四章 图论
§4.1 一般方法
建图的时机:当限制非常复杂、看不出轴(也就是转移顺序混乱,限制方向复杂时),可图论建模。
有两个长度相同的数组时,不妨令 \(a_i\to b_i\)。特别是只有一个数组时,不妨令 \(i\to a_i\)。
若题目给出类似“边权邻接矩阵”的矩阵,尝试建图。
- 找出题目图的特殊性质
1.1 原图是不是:树,仙人掌,dag,竞赛图,平面图,广义串并联图,弦图等。
1.2 缩点处理后,一个分量内部是否具备良好性质。考察一个分量内部的性质,可以利用子图添点法。
子图添点法本质上是通过调整法找多元限制。
1.2.1 耳分解-子图添点法。例如在某道题中:对一个点双,若是个环显然合法。添一个耳,发现这个耳只能是一条边,而不是一条路径。同时,不允许两个耳“相交”。因此,一个点双形如一个“大环”上多连了一些互不相交的边。
1.2.2 同胚-子图添点法。常见的一些特殊图的定义是用“不存在子图同胚于图 \(G'\)”来给出的。“同胚”的意思是,经过若干次缩二度点,可以使得两图完全相同。例如广义串并联图:不存在 \(K_4\);平面图:不存在 \(K_5,K_{3,3}\)。
例如在某道题中的操作流程:假设“不准与之同胚”的图为 \(G'\),从三元环开始尝试,三元环本身是不够的。添点可得,合法点双不包含同胚于 \(K_4\) 的子图,这意味着每个点双都是广义串并联图。 - 尝试缩点。建立点双缩点树,圆方树,或强连通分量缩点 dag。
2.1 关注连通性,尤其是涉及到删点时,注意割点-圆方树。 - 建立生成树。
- 扩展版广义串并联图方法。不断缩合、删除点或边,达到简化图的目的,原图不拘泥于无向图,缩合删点方式自定义。拓扑排序,还有广义串并联图方法,都是例子。
4.1 当元素的度数小,小到足以看出它没有用,或者用途能够被一眼看出时,就应该缩合或者删除。
4.2 让一个点、一条边承担一个原子图的责任。例如令一个缩合而成的点权存储“它表示的原子图的信息”,边权亦然。
4.3 \(m-n\) 很小的一类图,经过广义串并联图方法处理后,点和边数都会极小。另外补充,也可建立生成树,将非树边及相关端点都拿出来,建立虚树之类的。 - 采用数量关系(点数,边数,度数)来考察图的状态。
- 尝试归纳。按照某种顺序逐渐添点和边,或者逐渐删点和边,递归成规模恰好小一的子问题。
§4.2 具体方法
一、元素取值限制问题
- 对非 01 元素取值的限制。
1.1 限制呈变量加和形,求合法解或字典序最小解,差分约束。
1.2 限制呈变量加和形,给变量赋值有代价,最小化总代价。切糕模型。
1.3 拆点 2-sat。对元素 \(x\) 拆 \(O(V)\) 个点表示 \([x\le a]\) 的 01 性(值域大?离散化;证明只有很少的数字有用)。 - 二选一问题。
2.1 网络流,采用集合划分模型。另外补充,“团”的限制会令人想到“最小团问题”,从而想到集合划分模型。
2.2 网络流,给一次决策建立虚点,向两个可能的选项连边。
2.3 转化为类 2-sat 问题。
2.4 在选项之间连边,转化为给边定向的问题。 - 类 2-sat 问题。
3.1 2-sat。注意逆否命题也要连边。
3.2 二分图,扩展域并查集。要求是 2-sat 边无向,即若 \(a\) 推 \(b\) 则 \(b\) 推 \(a\)。好处在于一个连通块仅两种黑白染色方法,可以计数。
二、特殊图
- 树。
1.1 确定一棵有根树的信息,本质上相当于确定各个节点的父亲分别是谁,或者确定每个节点的儿子分别有谁。
1.2 无根树问题要先定根,一般不是随便定,而很可能是定在重心,直径端点,直径中点等。
1.3 直径的性质。
1.3.1 直径的性质。任一点的树上最远点是直径端点之一。两棵树合并后的直径仍在四个原直径端点内选取。
1.3.2 中点的性质。最长链次长链一样长。每个点向离它最远点的路径必过中点。不断删除一度点,最后删除的是直径中点。
1.4 重心的性质。
1.4.1 节点(可带点权 \(w_i\))深度之和最小,即 \(\sum w_i\mathrm{dep}_i\) 最小。
1.4.2 以重心为根,最大子树不超过 \(n/2\)。因此“定向”问题中,常常让一些子树成为“全入”子树,另一些为“全出”子树。
1.5 树上任两连通块的交和并都是连通块。任两条路径的交仍是路径。
1.6 树上覆盖问题,大多都是贪心题目。每次贪心地选择最深的一个应该覆盖,但尚未被覆盖的节点。在能够覆盖它的节点中,选择深度尽量浅的一个来覆盖。
1.7 树上选择 \(k\) 条路径,使它们的并集最大。长链剖分。 - 环。
2.1 找环,常常缩点,或 dfs 树,或拓扑排序求出。
2.2 破环为链。有向图里常常体现为 \(x\to y\) 的一条边和 \(y\to x\) 的一条路径。或者两条路径。 - 仙人掌。
建立圆方树。每个点双都是环。按照环上顺时针等顺序,排列一个方点的全部圆儿子,然后逐个 dfs 之类的。 - 竞赛图。
4.1 若有环,则必有三元环,可由环上的“弦”去逐步缩小环,得三元环。
4.2 竞赛图和完全图常常归纳构造。
4.3 竞赛图缩点后呈一条链。
4.3.1 兰道定理。将竞赛图依出度从小到大排序,记度数前缀和 \(s_i\),则强连通分量是一个区间,是且仅是相邻的 \(s_i=\binom{i}{2}\) 的位置。图是竞赛图当且仅当 \(\forall i,s_i\le \binom{i}{2},s_n=\binom{n}{2}\)。
4.3.2 每个强连通分量内有哈密顿回路,可先找出任一三元环后,逐步将边扩展成“一边镶嵌一点”,能扩展就扩展。
4.3.3 竞赛图存在哈密顿路径。
4.5 竞赛图上任两个度数分别为 \(d_x>d_y\) 的节点 \(x,y\),满足 \(x\) 至多两步能到 \(y\)。因 \(x\) 向外连的邻域节点,若 \(x\) 经该节点不能到 \(y\) 则 \(y\) 也向它有边,何况 \(y\to x\),故 \(d_y>d_x\) 矛盾。
4.5.1 竞赛图三元环可以枚举 \(d_x>d_y,y\to x\) 的边得到。 - 平面图(任意两条边可以不在节点以外的地方相交的无向图,或者“不存在五元团、三-三完全二分图”同胚子图的无向图)。
5.1 平面图的欧拉定理。它将平面划分为 \(r\) 块,则 \(r\le n\)。设原图有 \(p\) 个连通块,则 \(n-m+r=p+1\)。故 \(m\) 是 \(O(n)\) 量级的。
5.2 建立对偶图。如果是有向平面图,可以面朝原图边的方向,让对偶图边从原边的左向右垂直穿过。
5.3 网格图上连通性问题,及最短路,常转化为障碍物路径问题,及最小割。考虑怎样的两个障碍物之间能连边(有边才能讨论路径),换句话说,找出“障碍物构成的路径”中“相邻障碍物”的性质。枚举这两个障碍物的相对位置,并想象按照网格图边,从此两个障碍物的“中间”穿过(取两个障碍物的重心,连接,取连线中点,假设站在连线中点周围的某网格图点上,看能否按照近似于“垂直连线”的方向,从两个障碍物间穿过)。如果不能,这两个障碍物可以视作建立了边。 - 广义串并联图(不存在四元团子图,它也是一种平面图)。
用广义串并联图方法缩任意 \(m-n=k\) 的图,可得点数 \(2k\),边数 \(3k\) 的小图。 - 欧拉图。
7.1 欧拉子图计数,找出连通块内任一生成树,答案就是二的非树边次方,即 \(2^{m-n+c}\),\(c\) 是连通块个数。用自由元的办法证明。
自由元构成集合的性质(充要条件)是:第一,若知道这些元素各自的值,则唯一确定剩余所有元素的值(依据题目给定的限制推出)。第二,这些元素各自独立取值,无论怎样取,都不会因为和题目限制冲突而不合法。
若存在约束条件,不妨把“若确定 \(x\) 则确定 \(y\)”的关系化成边(间接的也连边),自由元数目就是最小点覆盖。
如果是二值问题,把“\(x\) 与 \(y\) 取值相同”的关系化成边,自由元数目就是缩点后 dag 上的点数。见 CF1889E。
如果是线性基,例如求在整数集合 \(S\) 中取子集异或成 \(a\) 的方案数,自由元数目就是线性基外的元素数目。
如果是方程组,自由元数目就是高斯消元后,总未知数数目减去有效方程数。
自由元往往是关键元素、边界元素,例如树边、网格图上的边框等。
7.2 欧拉回路。
7.2.1 欧拉回路保证了对任一连通子图,出入度是尽量平均的。目标是让某值平均,就要求我们把这个值和出入度一一对应。
7.2.2 遇到“度数奇偶性”应该有所敏感。要求每条边恰好经过一次,或者要做边定向,也要想到欧拉回路。
三、网络流与二分图
- 最大流。其建图一般是二分图或分层图。让边只在层间连接,表示一次“配对”或称“选取”,而层内的边通常至多构成一条链之类的。所以说对于网络流题目,要有意识地通过拆点构建“匹配”。
1.1 根据(黑白)染色方法(按节点编号奇偶性,或剩余系分组),行列建点,出入度虚点(如 T-184)的套路,将元素分为若干部分,每部分就是一层。
1.2 根据时间,某种资源量的值,或某种“特殊工具”的“当前剩余使用次数“,进行拆点,建立分层图。
1.3 用两遍残量网络 bfs(起点终点各一遍),即可对所有 \(i\) 求出,经过 \(i\) 是否存在增广路。
1.4 输出最大流方案。求出残量网络后,每次 dfs 模拟一条流,能流就流,将残量网络稍微还原即可。
1.5 逐步修改原图,在上次跑完的残量网络上继续。 - 最小割。
2.1 路径不通模型。每一条从源到汇的路径上,必须有一个边割掉。见 T-113-2。
还可用于解决“限制某个集合内,至少有一个元素不存在”的问题。建图目标就是把这些元素串成一条从源到汇的路径,可以拆点表示代价。但顺序不能乱,否则可能存在意想不到的路径,它们也是从源到汇故必须割,但原题可能没有该限制。为避免之,往往需要分层,要求同层任两个元素,不同时处于任一个路径内。
2.2 集合划分模型。
二值问题,每个点要么划给 \(S\),有 \(a_i\) 的代价,反之划给 \(T\),代价 \(b_i\)。两点 \(u,v\) 不在同一集合,代价 \(c_{u,v}\)。求最小代价。
最小割建图,每个点连 \(S\) 容量 \(b_i\),连 \(T\) 为 \(a_i\)。点间连 \(c_{u,v}\) 双向边。最终看残量网络,凡是容量不为零的边视作连通,则 \(S,T\) 分别所在连通块里包含的点,就是集合划分结果。
若 \(a_i,b_i\) 中有小于零的,令 \(a_i,b_i\) 均加上偏移量使之大于零,求出答案后减去偏移量即可。若 \(c_{u,v}\) 全小于零,可将 \(a,b,c\) 全都取反,然后转化为 \(a,b\) 中有负值的问题。
2.3 切糕模型。
2.4 输出最小割方案。看残量网络,凡是容量不为零的边视作联通,可求出 \(S,T\) 分别所在连通块。这两个连通块之间连的边都是在最小割上的。 - 模拟网络流。
3.1 模拟费用流,详见 T-118。
3.2 模拟最大流,或模拟最小割。描述与 \(s,t\) 分别连通的集合,不能有交。如果某一层的边数非常少,常枚举这一层的边有哪些割掉,以此为基础考虑剩余的图。 - 偏序集的最小链覆盖。定义:链,一个子图,任意两个点都可比(可跳过一些中间点,不是图论中的路径)。反链,一个子图,任两个点都不可比。建新图,一个点向其所有原图后代连边,求 dag 最小路径覆盖即可。
4.1 dag 最小路径覆盖。每个点拆成出入两点,有向边 \(u\to v\) 就是从出 \(u\) 到入 \(v\)。求最大匹配后,凡是在最大匹配上的边,就代表 \(u,v\) 处于同一链。
4.2 最长反链等于总点数减去最小链覆盖。例如要证明,如果原序列能分成两个上升子序列,则它的最长下降子序列长度至多为二。对所有 \(i<j,p_i<p_j\) 建边,则条件就是“最小链覆盖”为二。“反链”是不可比子图,这要求此子图中,不能存在任一两个元素满足 \(i<j,p_i<p_j\),“反链”也就是下降子序列,其长度至多为二。 - 二分图。
5.1 对于二分图给定度数序列后的构造问题,Havel–Hakimi(哈基米)算法。针对一类“给定度数序列的图构造”问题,基本都是先处理大度数顶点。对于任意两点都可连边的二分图,其算法应为,从左部点、右部点中各自选择当前度数最大的点,并把它们相连。这种二分图的有解判定性问题,可以使用 Gale–Ryser 定理。将 \(p,q\) 各自从大到小排序,则有解当且仅当 \(\forall i,\sum_{j=1}^i p_j\le \sum \min(q_j,i)\)。等式右边的意思是,如果所有右部点都尽量往左部点度数前 \(i\) 大的点连边,最多能连多少,如果即使这样都连不够,肯定无解。如果满足上述性质,就用哈基米算法直接构造了。
5.2 一条匹配边恰一个端点属于点覆盖集。最大独立集是最小点覆盖集的补集。最大团是补图最大独立集。都能转化为集合划分模型(每个点选入或不选入解集)。
5.3 霍尔定理。对二分图 \((A,B,E)\),最大匹配数是 \(|A|-\max_{S\subseteq A}(|S|-|N(S)|)\),\(N(S)\) 为连向 \(S\) 的邻域。
四、其他
- 最短路。
1.1 最短路树和最短路 dag。
1.2 spfa 求得的解恰为差分约束字典序最大解。“字典序”限制,肯定有另外要求导致变量有界,如 \(a_i\le 0\),建虚点表示值“零”即可。要求字典序最小时,此类有界限制必然是 \(a_i\ge 0\),解法是考虑 \(0\ge b_i=-a_i\) 间的关系,直接对 \(b_i\) 求最大字典序,然后全取相反数处理。 - 拓扑排序。
2.1 可以处理“\(x\) 比 \(y\) 早”的限制。拓展应用见 T-032。
2.2 反图也是 dag,只需关心当前访问到的点在反图中的 dfs 序,倒过来就是正图拓扑序。选一个超源点作为起点 dfs。每次dfs(x)时候,先访问其所有反图上的(未访问过的)儿子,回溯时ans[x]=++timr。timr就是“当前访问过反图多少节点”,而拓扑序/反的dfn[x]当然也就是这个。若可以高效维护当前点“没有访问过”的反图儿子,则此方法有效,见 CF798E。 - 生成树。
3.1 最小生成树。
3.1.1 一条非最小生成树上的边 \((u,v)\) 有可能在最小生成树上,等价于 \((u,v)\) 间树上路径中最大值的最小值等于边 \((u,v)\) 的值。
3.1.2 Kruskal, Prim, Boruvka 三者其一用于求出特殊图的最小生成树。
3.2 无向图 dfs 树。
3.2.1 没有横叉边。
3.2.2 每条非树边形成一个简单环。任两点 \(x,y\),二者之间的所有路径均可以通过“某一路径”异或若干简单环得到。有向图没有类似概念,但是强连通图有。具体来说,dfs 树建出后,每条非树边可能形成一个简单环。任两点 \(x,y\),二者之间的所有路径均可以通过“某一路径”加上(走这个环回到起点,然后走剩余的路径)若干简单环得到。也就是说,两者之间的路径长度 \(l'=l+\sum k_ir_i\),\(k_i\) 为任一整数,\(r_i\) 为环长。 - 博弈论。
4.1 dag 游戏。公平博弈(同一位置双方可以选择的下一步位置集合相同),能从一个状态走向另一个状态就连边,结果图是一个 dag,两人轮流操作,谁无路可走就输/赢。
初始化时,记录所有状态的出度,出度为零的压入队列,并根据游戏是正常规则或是反常规则,分别设为必败或必胜。弹出队首。如果是必败状态,则设前驱状态为必胜状态。否则,当前状态必胜,所有前驱的出度减一,并将出度为零的前驱设为必败状态。将可以判断胜败的前驱状态压入队列。队列为空时终止。尚未判断胜败的均为平局。
4.2 二分图博弈。给一张二分图和起点,两人轮流操作,每次操作只能选择与上一个被选的点相邻的点,不能选已选过的点,无法选的人输,问先手是否必胜。考虑二分图所有的最大匹配,若都包含了起点,则先手必胜,否则必败。
求匹配必经点是经典问题。先跑一遍匹配,从每个左部非匹配点出发,遍历它的所有右部匹配点邻居对应的左部匹配点,则所有被遍历到的左部匹配点均为非必经点,因为从它们开始存在交错路径使得终点为非匹配点。为了不选该匹配边,顺次将匹配边换成路径上一条边即可。
4.3 反常游戏(先手要把自己送死才算赢),先手必胜当且仅当游戏的 sg 函数不为 0 且游戏中某个独立游戏的 sg 函数值大于 1,或者游戏的 sg 函数为 0 且没有某个独立游戏的 sg 函数大于 1。
4.4 很多题目中,先思考“我如何赢”,再思考“我如何不让别人赢”,即“如何逼平”。 - 优化建图。
5.1 区间向区间连边,可以转化为区间向虚点连边,再由虚点向区间连边。
5.2 对连出边的区间,拆成了 \(O(\log n)\) 个线段树上节点,“入”区间亦如是。让每部分各 \(O(\log n)\) 个点互相连边,呈完全图状,如 ABC414G。
5.3 动态删点,一段区间的点向 \(i\) 连边。主席树优化建图。二维偏序建图也可扫描某一轴后转化为上述过程。
5.4 前后缀优化建图。
5.5 最好的优化建图就是不实际建图,例如拓扑排序,只需数据结构动态维护 ind 的变化,每次不断找出下一个 ind 等于零的入队,未必得真建图。 - 点边互相转换。对于“点”是一种序列,有一种转换技巧是,原来的点只保留前面几位,将最后一位作为边,表示点点之间的联系,例如 De Bruijin 序列中哈密尔顿路与欧拉路径间的转换。
- 将一个点双划分成两个部分,要求各自连通的问题。做双极定向。
第五章 数学
生成函数详见 T-101。
§5.1 式子
式子一般如何化简?
- 转换贡献主体,递推,改变求和顺序。在外层求和枚举的变量,当作常量从内层求和中提到外,内外层解耦的同时内层当作函数预处理。
在求积式中,乘除的地位与和式中的加减相同,乘方的地位与和式中“乘上系数”的地位相同。所以在和式中常见的 \(\sum(a-b)=\sum a-\sum b,\sum ka=k\sum a\) 在求积式中均有类似物。更进一步地,可以通过 ln/exp 把求积式转化为和式。事实上可以手动写 ln 后对和式做适当的变换,然后手动 exp 回来(这就避免了对积式做操作),并不需要运用多项式。 - 组合数公式套用。
2.1 \(\binom{n+1}{m+1}=\binom{n}{m}+\binom{n}{m+1}\)。
2.1.1 组合数的列前缀和。
2.2 \(\binom{n}{m}\binom{m}{k}=\binom{n}{k}\binom{n-k}{m-k}\)。
2.3 范德蒙德卷积。
2.4 二项式定理。
2.5 卷积技巧,把系数摊到 \(i,j,i\ \mathrm{op}\ j\) 上去,见 T-100。
2.6 若有系数在组合数前面,就把它弄到组合数里面。典例:\(t\binom{x}{t}=x\binom{x-1}{t-1}\)(或形如 \(P(t)\binom{x}{t}\),\(P(t)\) 是任意多项式)。
2.7 上大下小的组合数,视作关于底下的多项式。把原式拆成 \(\sum_i f_ix^i\),再将 \(i\) 的求和顺序提前即可。
2.8 可尝试对每个组合数,求出其对答案的贡献系数。
2.9 第二类斯特林数。
2.9.1 \(S(n,m)=\frac{1}{m!}\sum_{i=0}^m (-1)^{m-i}\binom{m}{i}i^n\),\(S(n,m)=mS(n-1,m)+S(n-1,m-1)\)。如果发现 dp 式与斯特林数递推式相似,还可以逆用递推式,把求 dp 数组值改成求通项之类的。
2.9.2 \(n^k\) 可以用第二类斯特林数做掉,\(n^k=\sum S(k,i)i!\binom{n}{i}\)。搭配带权方案数权值转方案数的组合意义来应用,不用转成“在 \(i\) 个元素中选可重 \(k\) 个”,而是“在 \(i\) 个元素中选不重 \(k\) 个”。 - 组合意义。
3.1 选取元素模型。把和式看作某种“选小球过程”的方案数。
3.2 格路计数模型。有时发现 dp 转移式与求解格路计数时等价,可以把求 dp 数组值改成求组合数学和式。
3.3 带权方案数的权值,用组合意义弄到方案数里去。
3.4 把多项式乘开的过程,可看作是在各多项式中选择一个单项式乘到式子里,并把得到的多个式子求和。如 T-096。 - 涉及 min/max 的公式。
4.1 在处理多重嵌套的 min/max 式子(仅一种运算)的时候,常把嵌套拆开,变成仅一层的。
4.1.1 min,max 混合运算属于非交换半环,可以将长式子拆除嵌套变为 \(\max(a,b,c,...\min(e,f,g,...))\)。
4.2 \(\min(a,b)=-\max(-a,-b)\)。
4.3 \(\max(t+i,t+j)=t+\max(i,j)\)。
4.4 \(\max(a,b)=\frac{a+b+|a-b|}{2}\)。
4.5 如果 max/min 里面只有两项,不妨分讨二者的关系,转化为大小比较问题。
4.6 一般希望 max/min 中一元只含一种未知数,像不等式分参。例如 \(\sum\sum \min(a_i-a_j,b_i-b_j)=\sum\sum (-a_j+b_i+\min(a_i-b_i,a_j-b_j))\)。 - 绝对值。
5.1 \(|x|=\max(x,-x)\)。
5.2 转组合意义,视作数轴上的欧几里得距离。或者画位置-值域二维图线,视作“折线图上下坡”。
5.3 处理多重嵌套绝对值式子时常把嵌套拆开而只关心(或描述)元素前的系数。
5.4 \(\sum |a_{i+1}-a_i|=(a_n-a_1)+2\sum_{a_i>a_{i+1}} (a_i-a_{i+1})\)。
§5.2 容斥
题目给定一族对元素取值的限制 \(R\)。可以自定义“严格度”,任两个限制的“严格度”存在偏序关系(具备传递性),也可能不可比。对它的定义必须非常谨慎,使得:
对于任一方案,能唯一找出它在 \(R\) 中满足的最严格的限制 \(x\)(不存在另一个它满足的限制 \(y\) 使得 \(y\) 比 \(x\) 严格)。
方案的权值,是只跟 \(x\) 有关的某个已知 \(p(x)\)。
记权值为 \(p(x)\) 的方案共 \(f(x)\) 种,则答案就是 \(f(x)p(x)\)。但发现 \(f(x)\) 极其难算。
不过“满足限制 \(x\)”的方案数 \(g(x)\) 好算。现希望给 \(x\) 配上容斥系数 \(q(x)\) 使得 \(\sum f(x)p(x)=\sum g(x)q(x)\)。
计算方法是,\(q(x)=p(x)-\sum_{y<x}w(y|x)q(y)\),其中 \(w(y|x)\) 表示任一满足限制 \(x\) 的方案,是否一定也满足限制 \(y\)(即,被 \(g(x)\) 统计的方案一定被 \(g(y)\) 统计)。
有时用 dp 处理 \(\sum_{y<x}w(y|x)q(y)\)。如果算系数的速度还不够快,就需要打表看规律了。
千万注意,因为偏序关系自定义,所以 \(w(y|x)=[y\ge x]\) 并不总是成立。
反演是指,题目给定限制族,然后自定义偏序。现在已知 \(g(x)\) 想要求出 \(f(x)\)。
一般按照偏序关系从小到大枚举 \(x\),\(f(x)=g(x)-\sum_{y<x}v(y|x)\),其中 \(v(y|x)\) 表示同时满足 \(y,x\) 两个限制的方案数有多少,计算 \(v(y|x)\) 的时候可以自选调用 \(f(y)\)(指定的偏序关系要比较合适,使得 \(v(y|x)\) 容易计算)。反演常应用于,题目询问所有 \(f(x)\) 的情况下。用反演,就不能得到那个独立于方案数的系数 \(q\) 了。
扩展上述容斥,可处理反演擅长的问题。更改 \(w(y|x)\) 的定义为“概率”(而非 01 变量),也即在满足限制 \(x\) 的条件下,同时满足限制 \(y\) 的条件概率。
集合划分容斥是一个特例。若把全集 \(U\) 恰好划分成元素 \(x_1,x_2,...\) 的权值是 \(p(x)=\prod P_i(x_i)\),方案数是 \(f(x)\),求加权和 \(\sum f(x)p(x)\)。
易求出集合至少划分成元素 \(x_1,x_2,...\) 的方案数(也就是说 \(x_i\) 内部可能还有划分)\(g(x)\),想配权值 \(q(x)=\prod Q_i(x_i)\)。
定义幂级数 \(A=\sum_i Q_i(x_i)y^{x_i},B=\sum_i P_i(x_i)y^{x_i},C=\sum_i G_i(x_i)y^{x_i}\)。
若存在某组合构造方法对应的幂级数变换 \(H\) 使得 \(g(x)=[y^U]H(C)\) 且 \(\sum w(y|x)q(y)=\prod_i [y^{x_i}]H(A)\)(这两个会同时成立,是“集合划分”的性质保证的),故 \([y^x]B=[y^x]H(A)\)。
至于为何 \(q(x)\) 也可以把多个小限制分开独立计算,最后再乘起来?这是集合划分问题的内禀属性使然。
集合划分容斥运用生成函数能够解决的问题,当然也能拿通用的 dp 方法至少给出一个暴力,具体方式就是考虑所有把一个元素 \(x_i\) 划分成若干其它元素的方案 \(x'\),它是把原来的元素划分成了 \(x'_1,x'_2,...x'_n\),则 \(Q_i(x_i)=P_i(x_i)-\sum_{x'}\prod Q_j(x'_j)\)。
题目的限制族不明显时,需要自己构造合适的族。问题只有一个目标(只有一个特殊的 \(p(x)\) 不为零),则定义限制族时,可以把问题的目标限制(没有怎样的元素/没有怎样的关系)拆成若干单点/双元小限制,然后此限制族的某个大限制就应该是,在所有可能的小限制构成的集合中,挑选一个子集,囊括其中的所有小限制。很特别地,我们常常把所有限制取反面,然后定义限制族,这是为了方便求 \(g(x)\)。
我们最后用这套理论,推导一下数论莫比乌斯函数。此场景下,限制族 \(R\) 中的每个元素都是一个数字 \(x\),用于限制“某元素应该是它的倍数”。
若 \(p(x)=[x=1]\) 那么可以推断 \(q(x)=-\sum_{y|x,y\neq x}q(y)\),从而 \(q\) 就是莫比乌斯函数。
那么为什么 \(\sum_{d|x}q(d)=[x=1]\) 呢?因为我们在此更换了主体,如果以一个顶格满足限制 \(x\) 的方案为主体,那么它总计被所有 \(d|x\) 的限制计入过,它对答案的贡献系数就是上述 \(\sum_{d|x}q(d)\),而这个东西就是 \(p(x)=[x=1]\)。
一切容斥题目在经过容斥转化后,要解决的都只剩下带权计数问题,可关注权值的组合意义,乘进方案数里。
很常见的一个优化是,找出容斥系数必定为零的方案,并将其剔除掉。
§5.3 数论,变换与反演
- 积性函数是指,若两个数 \(x,y\) 互质则 \(f(x)f(y)=f(xy)\) 的一类函数。可以通过欧拉筛求出积性函数,只有形如 \(f(p^k)\) 的地方(\(p\) 是质数)必须手动求解。对每个数 \(x\) 筛出其最小质因数 \(p\) 和它在本数中出现的次幂数 \(k\),则 \(f(x)=f(p^k)f(\frac{x}{p^k})\),用这个递推式去计算。
- 狄利克雷卷积,\((f*g)(i)=\sum_{d|i}f(d)g(i/d)\)。
2.1 单位元是函数 \(\epsilon(i)=[i=1]\) 满足 \((f*\epsilon)(i)=f(i)\)。狄利克雷卷积有交换律,结合律,对加法的分配律。
2.2 积性函数的狄利克雷卷积也是积性函数。因此其狄利克雷卷积可以线性筛。
2.3 给函数求逆与多项式求逆一致,积性函数的逆元是积性函数。
2.4 狄利克雷前缀和 \(s(i)=\sum_{d|i}f(d)\),用高维前缀和的做法去做,每一维就是一个质数即可做到 \(O(n\ln\ln n)\)。
2.5 gcd 卷积 \(h_i=sum_{\gcd(j,k)=i}f_jg_k\)。类似 FWT 做变换 \(f'_i=\sum_{i|j}f_j\),则 \(h'_i=f'_ig'_i\)。求容斥系数可得,其逆变换是 \(f_i=\sum_{i|j}\mu(j)f'_j\)。所谓快速 gcd 卷积 FGT 就是狄利克雷后缀和,IFGT 就是逆回来的狄利克雷后缀差分,类比 FWT,IFWT。 - 莫比乌斯反演。
3.1 定义莫比乌斯函数 \(\mu(x)\),若 \(x=1\),则 \(\mu(x)=1\)。反之,若 \(x\) 有任意一个质因子出现超过一次,则 \(\mu(x)=0\)。反之,若 \(x\) 有 \(k\) 个不同质因子,则 \(\mu(x)=(-1)^k\)。可以用欧拉筛求得。
3.2 几乎只有 \([x=1]=\sum_{d|x}\mu(d)\) 是重要公式。
3.3 莫反常用于处理 gcd 有关问题,可以把 \(\sum_{x,y} [\gcd(x,y)=t]\) 转化为 \(\sum_{d|(x/t),d|(y/t)}\mu(d)\)。接下来一般是改换主体,改为枚举 \(d\)(或者说把 \(d\) 的求和顺序置于更前),求合法 \(x,y\) 有多少,形如 \(\sum_d \mu(d)\sum_i\sum_j\),这里枚举的 \(i,j\) 实质上就是枚举 \(x,y\),其中 \(id=x,jd=y\)。
3.4 莫反后期常常得到形如 \(\sum_d \mu(d)G(d) \sum_i F(di)\) 的式子。随后可以改变求和顺序,枚举 \(T=di\),相当于 \(\sum_T F(T)\sum_{d|T}\mu(d)G(d)\)。\(\sum_{d|T}\mu(d)G(d)\) 一般是积性函数,可以欧拉筛求得。 - 欧拉反演。
4.1 几乎只有 \(n=\sum_{d|n}\varphi(d)\) 是重要公式。
4.2 欧拉反演的应用范围,和莫比乌斯反演多有重叠。例如求 \(\sum_{i=1}^n \gcd(i,n)\),莫反是枚举 \(\gcd(i,n)=k\) 得到 \(\sum_k\sum_i[\gcd(i,n)=k]=\sum_{k|n}\sum_{i'k\le n,i=i'k}\sum_{d|k}\mu(d)=\sum_{k|n}(\sum_{d|k}\mu(d))\frac{n}{k}\)。而欧反是,\(\sum_{i=1}^n\sum_{d|\gcd(i,n)}\varphi(d)=\sum_{d|n}\varphi(d)\frac{n}{d}\)。
4.3 实际上 \(\varphi(n)=\sum_{d|n}\mu(n)\)。
设 \(a^x\equiv 1\mod m\),则最小的 \(x\) 称为 \(\delta_m(a)\),也就是 \(a\) 模 \(m\) 的阶。显然 \(a\) 的幂次是以 \(\delta_m(a)\) 为循环节的。可以把 \(x\to ax\mod m\) 连上边,则 \(a\) 连着跳 \(\delta_m(a)\) 步之后,第一次回到 \(a\)。据此可以推出一些性质,诸如 \(\delta(a)\) 是积性函数,且 \(\delta(a^k)=\mathrm{lcm}(\delta(a),k)/k\)。注意 \(\delta_m(a)\) 存在显然代表着 \(a,m\) 互质。计算阶,可以使用 BSGS,也可注意到欧拉定理,\(\delta_m(a)|\varphi(m)\),故令 \(x\) 初值为 \(\varphi(m)\),从小到大找出 \(\varphi(m)\) 的所有质因数,每次不断让 \(x\) 试除当前质因数,若 \(x\) 不再是其倍数,就看下一个质因数;否则得 \(x'\),问还是否满足 \(a^{x'}\equiv 1\)。若发现仍旧满足,则 \(x\gets x'\),否则看下一个。
若 \(\delta_m(a)=\varphi(m)\),则 \(a\) 是 \(m\) 的原根。原根的若干次幂在模意义下取遍了所有与 \(m\) 互质的数。判定 \(a\) 是不是原根的一般方法是直接计算阶。显然只有很少的若干数 \(m\) 有原根,\(m\) 有原根当且仅当 \(m=2,4,p^k,2p^k\),\(p\) 是奇素数。若有原根,那么其缩系(只和 \(m\) 互质的数构成的剩余系)上任意两个数的乘法,等价于对应指数模 \(\phi(m)\) 下的加法,因为可以将每个数转化成 \(a^t\) 的形式。计算原根,只需从小到大枚举原根并检测,因为一个数若有原根,则其最小正原根肯定非常非常小。显然一个数原根的个数是 \(\varphi(\varphi(m))\)。
- 单位根反演。
5.1 几乎只有 \([d|x]=\frac{1}{d}\sum_i\omega_d^{ix}\) 是重要公式。
证明:若 \(d|x\) 则 \(\omega_d^{ix}=1\),加一起再除以 \(n\) 就是一;反之,是在环上跳,跳了 \(n\) 步很均匀,把这些复数加一起结果一定是零。
5.2 如果要问 \([x\equiv y\mod m]\) 的形式,这就是 \([m|(x-y)]\),故也可以如上处理。例如 \(\sum f(i)a_{i\mod 4}=\sum f(i)\sum_{j=0}^3a_j[i\equiv j\mod 4]\),然后上单位根反演。
5.3 单位根反演常运用二项式定理化式子。
5.4 单位根反演的最后一步,一般是利用同构把单位根转化成原根。对于常见模数 \(p=998244353\),显然其最小原根 \(g\) 满足 \(g^{\varphi(p)}=1\),故这个 \(g\) 就是 \(\omega_p\) 对 \(p\) 取模的结果。然而我们可能不是要 \(\omega_p\) 而是想要 \(\omega_x\),只需根据分圆的性质,你想要的是 \(\omega_p^{\frac{p-1}{x}}=g^{\frac{p-1}{x}}\),一般 \(x\) 就是 \(p-1\) 的因数之一,快速幂即可。 - 分解质因数。
6.1 可依每个数不同质因子的幂次写出限制。
6.2 按质因子大小分治,例如把“最大质因子大于 \(\sqrt V\) 的归为一类数字”等。如果值域太大无法分解,就只能使用 \(\gcd\) 去处理(写出限制/求答案等)了。
6.3 数的因数个数其实非常少,因此 \(O(\sum d_i),O(\sum d^2_i)\) 等复杂度常常可以接受(\(d_i\) 代表 \(i\) 的因数个数)见下图。

6.4 值域特别大,但是希望质因数分解。此时只能进行 \(\gcd\) 运算。可以求“相对质因数”,也就是在本组数据中,作为单位元出现的数字。每次插入一个新数字,把它和所有已有单位元求 \(\gcd\),如果有 \(\gcd\) 不为一的,就删除对应单位元,并改成插入这个 \(\gcd\),递归处理。最终得到的若干个单位元可以在本组数据中当作质数使用,把每个数分解后,可以方便地处理子集 \(\lcm\) 等。 - 其他。
7.1 裴蜀定理。\(ax\equiv b\ \mathrm{mod}\ p \to ax+py=b\) 有解的充要条件是 \(\gcd(a,p)|b\)。这是由 \(ax+by=c\) 的有解条件 \(\gcd(a,b)|c\) 推出的。
类似完全背包的问题,也可以从裴蜀定理,或同余方程的角度去思考。
7.2 预处理光速幂。根号分治。当底数为定值 \(x\) 时,可预处理 \(x^1,x^2,...,x^{\sqrt{y}-1}\) 和 \(x^{\sqrt y},x^{2\sqrt y},...\)。
7.3 除法取整。
7.3.1 形如 \(\lfloor \frac{a}{b} \rfloor\)。不妨将分子拆成 \(a=qb+r\),答案即为 \(q\)。对 \(a\) 操作时,关键可能在于维护 \(r\)。形如 \(\lfloor \frac{a-c}{b} \rfloor\),令 \(a=qb+r,c=q'b+r'\),则只需会比较 \(r,r'\) 关系即可。
7.3.2 取模和除法取整有关。
取整变取模是熟识的 \(\lfloor\frac{a}{m}\rfloor=\frac{a-(a\mod m)}{m}\)。
也可逆用,定义 \(g(a)=\lfloor\frac{a}{m}\rfloor\),则 \(a\mod m=a-mg(a)\),从而 \((a\mod m)<(b\mod m)\) 就是 \(a-mg(a)<b-mg(b)\),也即 \(\frac{a-b}{m}<g(a)-g(b)\)。得到 \([(a\mod m)<(b\mod m)]=g(a)-g(b)-g(a-b)\)。
7.4 整除分块。
§5.4 其他
- 卡特兰数。
1.1 \(H_n=\frac{4n-2}{n+1}H_{n-1}\)。
1.2 \(H_{n,m}=\binom{n+m}{n}-\binom{n+m}{n-1}\)。
1.3 \(H_n=\sum H_iH_{n-i+1}\)。 - 卢卡斯定理。
2.1 \(\binom{i}{j}\mod 2=1\) 当且仅当 \(j\) 为 \(i\) 的二进制子集。
不过得找个递推方法使得 \(i=x+y,w=z+x\) 的变化量不多。这相当于是把 \(x,y,z\) 互相独立地拆开,要求它们三个的变化量均不多。 - 零点存在定理。一个序列,最大值大于 \(0\),最小值小于它,则必存在一个点和它相邻的点正负号相反。
尤其是序列元素连续变化时,若最大值大于等于零、最小值小于等于零,则二者之间必然有一个位置为零,所以这种情况下可以线段树二分找出零点。 - 切比雪夫和曼哈顿的互转。
4.1 切转曼,是为了把两维分开做和,来规避 max 这种跟两维都有关的运算。
4.2 曼转切,是为了把和转成 max,方便做二分之类的。
4.3 另外补充,二维平面上的随机游走可以看作 \(x,y\) 分别等概率地 \(+1\) 或 \(-1\)(共四种可能)。 - prufer 序列。
5.1 扩展 Cayley 公式。将大小序列为 \(a_i\) 的若干有标号连通块连成树的方案数为 \(n^{k-2}\prod a_i\)。
5.2 限制带在度数上的树计数问题。尝试和 prufer 序列联系起来,因为我们知道 prufer 序列中度数为 \(i\) 的节点恰好出现 \(i-1\) 次。 - 线性基。
6.1 线性基按位贪心求异或 max,也可求异或第 \(k\) 小。
6.2 线性基可以 \(O(\log^2 V)\) 合并。因此可以视作代数结构。
6.3 区间修改查询。令 \(b_i=a_i\oplus a_{i+1}\),类似差分。询问 \(a_l,...,a_r\) 相当于问 \(a_l,b_l,...,b_{r-1}\) 的,线段树查询后,将 \(a_l\) 插入即可。
6.4 钦定选择奇数/偶数个,求异或成 \(a\),异或 max,异或第 \(k\) 小等。给每个数字扩展一个二进制“奇偶指示位”放在最高/最低位,就可以了。 - 方差。
方差就是 \(\frac{1}{n}\min_x (a_i-x)^2\)。因此与方差有关的最值问题,常常改为以 \(x\) 为主体(首先枚举 \(x\))来考虑方差,\(x\) 不一定是所枚举集合的均值,这类似于错解不优。特别地,处理最大值时,则必须钦定 \(x\) 就是均值。 - 绝对众数。
8.1 摩尔投票法,最后剩下的就是绝对众数。
8.2 若一定有绝对众数,则将所有数排序后,中位数就是绝对众数。
8.2.1 补充:如果要选择一个区间,使得区间和大于二分之序列总和。找原序列中和大于二分之总序列和的第一个前缀,则区间必然跨过该前缀的末端。
8.3 若有绝对众数,随机一个序列中的位置,则有二分之一概率它就是绝对众数。 - 对数。取对数,乘变加。
例如若干 \(x_{a_i} \ge k_ix_{b_i}\) 条件均需满足,取对数使用差分约束,\(\log{x_{a_i}} \ge \log{k_i}+\log{x_{b_i}}\)。
9.1 可用于大数比大小。 - 求前 \(k\) 大方案和。
法一,直接 dp。
法二,调整法和堆。把最优解放入堆中。共进行 \(k\) 次,每次弹掉当前最优解,对当前最优解进行一点调整得到次优解,后放入堆中。
法三,二分。记录 \(c(m)\) 表示价值大于等于 \(m\) 的方案数,而 \(s(m)\) 为这些方案的价值之和。得到最终 \(m\) 之后,显然答案就是 \(s(m)-(c(m)-k)m\)。 - \(a+b\le m\) 的问题。
11.1 就是将所有元素分为 \(\le \frac{m}{2}\) 和 \(>\frac{m}{2}\) 两个组别。
11.2 正序扫描值域轴到 \(i\) 的同时,倒序扫描值域轴到 \(V-i\)。 - 前缀操作结果。也就是 \(s_i=\mathrm{op}_{j=1}^i\ a_i\)。它很可能总计 \(\log V\) 种不同的值,且值相同的是连续段。另外补充的例子,是单目运算,一个数至多取 \(O(\log)\) 次欧拉函数会变为一。
- 依次加入元素,发现计数器越过 \(a\) 时立刻终止。
- 从一点引射线,与多边形的边相交奇数次,则点在多边形内。
- 01 序列。关注它的差分、前缀和是很常见的,有时还翻转偶数位。
第六章 交互与构造
§6.1 一般方法
广义上的构造是,可以输出操作序列或元素方案的一切“存在性”问题。对结构大小 \(x\),或结构某方面的性质一般有要求。常见办法有:
- 数量关系,或势能法。
- 已知变现。已能解决的情况,从待考虑序列中剔除,就只剩下具备一定性质的问题。
2.1 构造的第一步是判断无解性。可视为一种变现手段。无解性一般和某个数量有关,比如“某个数量的奇偶性合适”或者“某个数量处于一个上下界范围内”则问题有解,注意所述的条件可能并非必要。例如,若 \(n>5\) 则问题有解,不代表 \(n\le 5\) 问题一定无解,可以小数据暴搜一下。 - 归纳构造。
3.1 每次删除(或确定结构中的)一个元素。删叶子(或扩展版广义串并联图方法),删极值,按某个轴依次在原有解基础上添点。
3.2 每次加入一个元素,并维护当前的结构。 - dp 构造。
- 二级结构。
5.1 二级结构涉及询问之间关系匪浅,询问的参数差别一般很小,例如对定值 \(i,j\) 和所有 \(k\) 做询问 \((i,j,k)\)。
5.2 手动爆搜“解”是什么,也就是先枚举“解”的第一步操作,然后找出第二步操作等等。 - 分组构造。对结构大小 \(x\) 的要求是某个变量乘以分数,暗示分组构造,或多种做法拼接平衡。分组方法一般是编号剩余系。
6.1 将所有元素分为 \(k\) 组,保证至少有一组元素全选,或称成功(全部贡献给结构大小),如 T-135。
6.2 将所有元素分成 \(k\) 组,每组至少让结构大小增加一,如 T-125-2(这种常常和归纳构造结合)。 - 能操作就操作。
- 限制的分步检测。
8.1 先令解满足限制一,再调整使之满足限制二。
8.2 只考虑在题面中确定的元素(题目信息)涉及到的限制,先不考虑只涉及“自定义元素”的。 - 建立交互决策树。
暴力搜索建树。假设可能性集合为 \(S\),对应步数下界 \(f(S)\),枚举第一步的所有可能方式,根据第一步的结果将集合分为 \(P_1,P_2,...\)。如果 \(\max f(P_i)>f(S)-1\) 则这一步就不行。反之就分下去然后继续搜索。
9.1 信息论视角。需要找出隐藏信息的交互问题中,若信息的初始范围,或称可能性种数是 \(V\),则至少要 \(O(\log V)\) bit 才能把实际的隐藏信息与其他信息区分开,这就是一个下界。
§6.2 具体方法
- 交互的“初始资本”。假设用已知信息作为参数描述子问题,不仅有子问题(可能性)大小,还有“初始资本”,就是一切已知辅助信息的统称。有一种情况好,有一种情况坏。好情况比平均步数要求要好,碰到了就直接过。坏情况我们不希望碰到,如果一直碰到就不能接受。交互过程中,不断发现辅助信息,应该利用辅助信息不断贪心选择,继续碰到“好情况”概率更大的“初始资本”。升级原有“初始资本”,不做白不做。
1.1 用随机化方法找出交互的初始状态。 - 进制构造。
2.1 二进制构造。
2.1.1 逐位确定。常常是用若干次询问确定全体元素某一位的值。
2.1.2 分治确定。
2.2 进制混合。对数构造换底,有时更佳。
2.2.1 将 64 位分组,每次将当前的unsigned long long数字对三,或对二取模的结果输出即可,并令它除以三或二,规避了高精度除法。
2.2.2 通信题中。将全部信息视作 01 串从而转化为 \([0,1)\) 内的二进制小数 \(\sum 2^{-i}a_i\)。每个进制位传递的信息,相当于“该小数处于当前区间的前三分之一,或前一半”之类的。发送者与接收者可以约定,未必每次都要平均地划分区间。 - 树上构造。
3.1 删叶子归纳,如 T-134,T-175。关键在于三部分(这个过程也适用于一切扩展版广义串并联图方法):
第一,快速找出一个叶子。
第二,找出这个叶子的父亲(或者说,对于这个叶子,我们好奇的信息)。
第三,删除一个叶子后,出现新的“叶子”,加入待考虑栈中。
3.1.1 有时把某个 \(p_i\) 求出后,就把 \(i,p_i\) 合并起来,这就减小了规模。
3.2 从上向下的构造。每次找出“没有父亲是关键点”的关键点并解决它的问题(考虑如下对称,剥叶子相当于找出“没有儿子是关键点”的关键点并解决)。 - 撒谎的交互库。若询问集合 \(S\) 且回答“是”,就让 \(U-S\) 的元素的计数器都
push_back一;反之令 \(S\) 内的push_back。如果某个元素计数器如何如何,那它一定不是答案,因为交互库不能连着撒两次谎之类的。实时维护所有 \(n\) 个计数器的情况。 - 随机化。
5.1 未保证随机的数据,可以自行随机打乱,减少极端数据影响。例如交互题中,自行加上随机基底排列 \(s\),逐步求出 \(p'\),交互时调用 \(p=s_{p'}\)。第二个例子是有一些 0,1,每个都有权值,选一个子集使得 \(01\) 数量相同,最大化权值和。把元素随机打乱再 dp,记录当前 0 比 1 多几个,这一维可以只开到 \(O(\sqrt n)\)。
结语
就像野外徒步,它无法告诉你,翻过眼前这座山唯一的正确路线。
它只能说,沿着山脊,不容易遭遇洪水。
荒野里走着的人,只得紧握双手;
堪比原始哲学,它如同石矛简陋。
我愿以哲学的角度,给简陋工具几个命名。
我们如何切割世界?元素就是对象。
我们如何理解事物间的联系?限制就是关系。
我们如何安排观察的节奏?轴就是顺序。
我们如何表征我们找到的答案?描述法就是语言。
正如组诗《太阳的姓》试图指出的那样,
凡人触碰太阳的姓
我们从未松开
长着名字的石矛
一代一代传
我们就像小孩子
刚睁开湿润的眼
大声念出
它的名字——
然后你看见一潮一汐的波澜

浙公网安备 33010602011771号