The Art of the Induction III

\[\Huge \mathrm{The\ Art\ of\ the\ Induction\ III} \]

$\ $

引言

在开始之前,请大家先问自己几个问题——请试着思考:我完全知道自己是如何做题的吗?做题是靠“注意力”多一点,“见过的套路”多一点,还是兼而有之?“注意力”是能够训练的吗,“套路”是能记录,能逐一背诵,乃至最终穷尽的吗?我是为一道题或一个解法的“巧妙”而兴奋还是叹息?

那么,大家有没有问过别人,“为什么能、如何才能注意到这一点”,而他的回答让你满意了吗?

有没有畅想,梦想,或者幻想过:如果有一天,这世界上没有灵感,没有注意力。或者它虽然存在,却被某套体系框定了。这套体系可操作,可言说,也即可复用和可传授。只要由这套体系引导我的思绪,就能注意到那些应该被注意到的?

我打心眼里不相信这套体系完美地存在,我更不打算给出这套体系。我的天赋很差,也许我是你读到的博客作者中 OI 水平最差劲的一个——然而我还期望自己为这个社区作出一点贡献。我的贡献应该是,也仅仅是指出这个事实:

思维体系不是无法逼近的。

我除了指出存在性,还尝试着迈出了一步,但这不过是抛砖引玉。我寄希望于看到这里的你,还有一切广大的后来者,我们一起来完善这套体系。如果连我这种天赋的人都能迈出一步,那此事就大概率能做下去。虽然人与人思维方式有些许差异,但是我们将达成一些共识。

下面给出我的私人体系。它会提出一些通用的方法,然后围绕动态规划、数据结构、图论、数学、交互与构造等方面展开,每个方面都会先给出一般性方法,再具体分析。我特别推荐 Alex-Wei 的博客 作为补充阅读,它对各类算法及其基本应用的介绍极为详实,本文就不再赘述。例题在本博客下 Exemplary Examples 中,它将详细展示这套体系的动态运作,期待大家结合阅读。

祝大家前途光明。

第一章 总体方法

§1.1 超级方法

一、元素

元素,主体,对象。

例如一棵树,树上的一个点,一条边,一条路径,一个子树,一个连通块,都可以是元素。以点为主体时,边就是元素间的联系。以路径为主体时,点和边就是元素的组成部分。

所有题目都建立在元素之上,否则就无法叙述题面。然而题面所关注的元素,不一定是我们选用的主体。如果在原题框架下转换主体,甚至作出跳跃性的等价变换(例如括号序列转折线图),就需要将原题面翻译到新元素上去。

自然理解题面时所需关注的元素,对答案作出贡献的元素,边界元素(例如树的叶子、网格图边框上的节点),数据范围小(或者说特别简单、具备某种特殊性质)的元素等,我们往往留心,称之为关键元素,或者主体。

元素的对称。例如在 01 序列中,从以 0 为主体,转换成以 1 为主体,就好比推箱子的问题,可以把箱子的移动转化成空格的移动。
单位值主体。把每单位值想象成一个小球,小球去往何处,就是给那里做了贡献。

二、限制

限制,性质,关系。

只要一道题目谈到了元素,那么它必然对其施加定义。可以认为所有的定义都是限制。

例如当谈及某集合的 mex 值 \(v\) 时,相当于施加了“\(1,2,...,v-1\) 都在集合中出现”“\(v\) 不出现”的限制。要叙述此限制,所选的关键元素就是“集合内的全体元素”。然而,它实际只施加在集合内元素的值上,也即主体的某个方面上。若要求元素具备某种性质,则必然引入了限制。

对于同一个限制,只要更换主体,就能得到等价表述。

按照题面自然叙述限制时,限制所施加的主体往往不令人满意,主体可能太大,太抽象,例如“图是连通的”。一般来说,这就需要改为选取原主体的组成部分来描述限制,例如“图中任取两点,都存在一条路径”。

  1. 单点限制。就是拿出一个元素来,看上面的限制。限制一般作用在某个方面上。
  2. 双元限制。拿出两个元素来,它们会存在某种关系,就看对这种关系的限制。这两个元素通常在某些方面较为接近,形象说就是“相邻”。双元限制和图论中的“边”较为类似。当元素之间的限制过于紧密,将这几个元素合并处理可能更方便。
  3. 多元限制。

对主体做了转化,就必须把限制和所求答案翻译到新主体的语境下。然而限制的表述方法不单单由主体而决定。以集合 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 序列,就会显得直观。

  1. 存在型限制与全局型限制的互相转化,用取反。
  2. 最值型限制与存在-全局型限制的互相转化,按照值的顺序枚举,有时可以二分。
  3. 存在型限制到最值型限制的转化,就是找出“最可能符合条件的元素”,观察它究竟是否符合。

再看这个例子,两个串 \(a,b\) 的 lcp。如果把限制加在前缀上,就是“最长的相同的前缀”。先关注第 \(i\) 个前缀是否相同,再找出最长的。把前者写成 01 序列,形如 11111100000000。需找出最后一个“一”,或对称地,第一个“零”的位置。若求“第一个零的位置”,可利用限制本身的内容来简化限制——“第一个零”意味着前面都是一,换句话说,无需以前缀为主体,只需找出第一个 \(a_i\neq b_i\)\(i\) 就可以了。

请问如何判断一个 01 串是否形如 11111100000000。很多简易的限制只涉及到数量关系。以单个字符为主体,取双元限制,取相邻,取数量关系。限制实为:10 子串出现次数恰好为一。

  1. 分步检测。给多项限制钦定“检测顺序”,“前 \(i\) 个限制都满足”的元素,才送去检测第 \(i+1\) 个限制。好处在于:或者满足前几个限制的元素数目少允许暴力枚举,或者此类元素具备良好性质(有时,良好性质与所加限制甚至不在同一方面),故能简化判定过程。有时,由于元素满足了前几个限制,后一个限制本身也可做修改。
    7.1 要构造方案,使之同时满足多个限制。可先强令其满足某一个限制,在此基础上,做调整使之满足第二个限制,以此类推。
    特别地,限制可能不是常数个。例如是单点限制/双元限制,可依次加入元素(和对应限制),实时调整。换句话说,维护由当前元素构成的“子图”,只考虑当前元素之间的关系,以及限制。
  2. 用数量关系描述限制。或者构造势能量。相通之处在于,采用极少量的信息,描述了复杂状态的合法性。
  3. 必要性探路。限制加在全部单点元素上,不妨先只注意:关键元素受到限制会造成什么后果。限制加在全部双元 \((i,j)\) 上,不妨先只注意:双元之间关系匪浅的那些对元素(例如某些轴上相等或相邻,即 \(j=i,j=i+1\) 时)所受的限制,会造成什么后果。
    9.1 只考虑在题面中确定的元素(题目信息)涉及到的限制,不考虑只涉及“允许自定义元素”的。

三、轴

轴,顺序。

前文提到,元素具备多个方面,每个方面都可能存在性质。许多情况下,这些方面就是轴,如值域轴,位置轴,时间轴,种类轴等。因为对于两个元素,在这些方面可以比较“大小”,这就产生了偏序,而轴就是一种使得偏序都成立的全序。

举例来说,一棵树,取主体为节点,选取方面为“在树上位置”,定义大小比较为“无祖先关系不可比,反之祖先为小”。任意 dfs 序就是一种“使得偏序都成立的全序”。因此 dfs 序可以是个轴。更进一步地,树上启发式合并,必然是先合出儿子再合其祖先,这也会使得偏序都成立,故也可看作一种轴。

有多个轴时,就需决定哪个轴放在外面。大致来说,依最外轴的顺序添加限制、考虑问题。例如二元限制有方向时(限制 \((i,j)\)\((j,i)\) 不同,\(a_i<a_j\) 就是这样的限制),常常把“限制所涉及的方面”(\(a_i<a_j\) 中,位置和值域)放在最外面。所谓“强制在线”,就意味着题目指定了最外轴是谁,并钦定最外轴仅允许扫描线。然而,主席树等预处理式数据结构,允许我们预先扫描其它轴,之后再来处理“强制在线”的查询。

  1. 扫描线。从小到大枚举 \(t\),并实时维护“在外部轴时刻 \(t\),内部轴上坐标为 \(i\) 的元素(此处可能是抽象元素,也即不是原题面所叙述的元素)的值(额外轴)是什么”。若有两个内部轴,就是维护二维平面。注意总会有一个额外轴以“被维护信息”的形式存在。
  2. 最外轴翻转。加入变删除,删除变加入。如果终止状态确定,常用这个办法。有时还会要求“寻找答案来源”,例如求复合函数 \(f(g(h(x)))\) 最大值取到的位置(许多题目的操作过程,可被抽象成复合函数)。正序轴是由内而外,逐步复合。倒序轴则是先问 \(f(u)\) 最大值取到的 \(u\) 再问 \(g(v)=u\)\(v\),最后问 \(h(x)=v\)\(x\)。尤其在覆盖问题中常见。
  3. 转 01 序列。尤其是限制只涉及“元素相对大小关系”的题目,可把值域轴放外面做扫描线,设大于当前值的元素为一,反之为零。

有时轴并非常被关注的方面,此时就要回到本义,轴是考虑问题的顺序。例如题面中提到了多种操作,经过尝试证明了:操作顺序任意调整,不劣。这相当于“操作顺序”轴由人自定义。常见办法就是放弃以“操作顺序”为最外轴。例如,改成以“操作种类”为最外轴,先将类型一的全部操作进行完毕,再进行操作二,以此类推,实例见 T-174。

  1. 自定义。我们通常屈从于题目叙述的顺序,或者题目限制所加的顺序,因为这样才容易考虑问题。如果发现某个轴的顺序允许自定义,一般就不用它作为最外轴,就是使用它,也会让另一轴随该轴单调。

四、描述法

描述,解。

问题所求的答案背后,往往存在“解”。例如求某些元素的最大值,答案是值,“解”是取到最大值的元素。如果“解”极为复杂,就需引入描述法。例如下题(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. “逐步”描述法。答案权值跟多个“时刻”(时间轴,是选择并扫描的最外轴)的状态都有关系,但状态间具有可递推性的问题。每次只需在上个状态的基础上,额外记录部分信息得到新状态。可以不断用已记的信息,对尚未处理的信息作出限制(为使得已知信息“此刻”是“这样”的,未知信息必须满足何种条件)。
  3. 钦定一对一。包括但不限于判定性计数中的“原序列-操作序列”问题。“钦定字典序最小”。原序列-操作序列本来是多对多的。一般是描述“原序列-某种能生成原序列的操作序列”。若这种方式不可行,就必须提前操作序列的描述顺序,也即描述“操作序列-使得该操作序列合法的所有原序列”。但对同一原序列,有多个操作序列符合条件,这就算重了。法一,统计每个原序列会被多少操作序列计算,这个数对各种原序列均相同,只需让答案除以该值。法二,钦定多对一,即钦定一个原序列只对应一个“真正合法”操作序列,一般是“在所有合法操作序列中字典序最小的”。字典序可自定义。另一个例子是树上连通块计数,在最浅点统计答案。

§1.2 一般方法

  1. 对元素的操作
    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. 问题的转化
    2.1 变现。将能够处理的情况处理掉,剩余的情况就具备性质。
    2.2 化归。将当前问题转化为已经解决的子问题。特别地,依题意扫描时序并调整状态时,有时会存在某时刻,问题被调成已解决问题,或者容易解耦。则此时刻就是“里程碑”。
    2.3 上下界。只考虑在题面中确定的元素(题目信息)涉及到的限制,不考虑只涉及“自定义元素”的,据此估计上下界。并不一定取到,但有助于思考。
    2.4 无解。判断无解性,可用于变现。
  3. 多个本质相同问题
    这类问题可视作函数,各个问题的不同只在于传入的参数。
    3.1 简化单个问题的计算方法。
    3.2 递推。常常需要找到:某一轴上相邻(或者许多轴上同时相邻)的两个问题,比对这两个问题的差异,用其它问题(可能与原问题本质相同,或本质不同),或者 \(O(1)\) 计算来弥补。
    3.3 合并连续段。某一轴上相邻(或者许多轴上同时相邻)的多个问题,合并统一计算。有时这些问题的答案甚至都会相同。
    3.4 选择贡献主体。把二元关系“问题-答案来源元素”换成“答案来源元素-问题”,就是由元素贡献问题,而不是由问题访问元素。选取最合适的主体做贡献,可能不是题目所叙述的。
    3.5 改变求和顺序。可以把“枚举位置在外层”的变量,当作常量从内层提到外面。
  4. 调整法
    调整法的目的是减少合法解,或最优解的可能性,了解这种解的性质,优化描述法,或者直接解题。先尽量做小调整,从“只修改一个位置,或只把值修改一个单位”开始尝试,如果没有得到好的结论,就加大调整力度。
    4.1 最优化问题。先拿出一种合法方案(未必最优)。对一次正确的调整,有两个限制。第一个是要保持方案合法性,第二个是在最优性判定上,要不劣于(甚至优于)调整前的状态。
    4.2 判定性问题。可能存在多个方案最优,或者原问题没有“最优化”只有“合法性”。这就需要自定义“最优方案”,一般是“按某个字典序最小”。
    4.3 元调整。将一次调整看成“删去若干元素并添加(等量)若干元素”,调整也能作为“解”被描述。用“放生”描述法描述“调整”本身,甚至对“调整”做调整,或者贪心。

§1.3 特殊方法

  1. 排列
    1.1 关心元素的相对大小时,扫描值域轴,转化为(多层)01 序列。
    1.2 建立置换环。常把环的个数用于势能分析。
    1.3 关注(值域轴或位置轴上的)前后缀元素是否连续出现。
    1.4 关注从某元素开始的最长上升子序列,还有等差序列等。
    1.5 排列所包含的信息,和其逆排列相同。所以考察排列相当于考察其逆排列。
    1.6 刻画集合 \(s\) 内部元素连续的方法:\(|s|=\max-\min+1\)
    1.7 排列计数的一般技巧,本质上有六种方法:
    法一,扫描位置轴,微调(逐步描述)策略是值域分裂。
    法二,扫描位置轴,微调策略是位置分裂。
    法三,扫描位置轴,微调策略是位置交换。
    法四,扫描值域轴,微调策略是位置分裂。
    法五,扫描值域轴,微调策略是值域分裂。
    法六,扫描值域轴,微调策略是值域交换。
  2. 类括号序列
    2.1 记录当前多余的 ( 数,即在 \(i\) 前面决定与后面匹配的位置数量,“允许操作没有结束”。
    2.2 考虑与 \(l\) 配对的元素 \(k\) 的位置,如 (...)...。注意,这里如果有多个和 \(l\) 配对的元素,选取最后一个,才能顺利分割子问题。
    2.3 将 ( 视为 +1,) 视为 -1(视情况,所赋权值可能不同),关注序列前缀和,可绘制折线图,限制通常加在最值上。
    2.4 合法括号序列,转化为树的结构。
  3. 直方图
    每一列长度不同,希望顺序考虑这些列(例如希望先考虑长的列再考虑短的)。
    3.1 如果 \(a_i\) 任意重排不影响答案,更换到值域轴上做。
    3.2 建立笛卡尔树。
    3.3 扫描高度轴,逐行处理。待处理部分是若干区间,处理后,因为一些立柱到顶,有些区间分裂。
  4. 网格图上的矩形
    这类问题通常需要选择合适的描述顺序,利用递推和解耦(拆分函数),并充分利用单调性(也即两轴的本质同一)。
    4.1 依次枚举左下角、边长(正方形计数)。
    4.2 依次枚举左下角、左上角、宽(矩形计数)。有时在枚举前两者后,建立笛卡尔树以处理。
    4.3 依次枚举上底所在行、下底所在行、两条竖边所在列。

第二章 动态规划

§2.1 一般方法

一、初步的状态

此处主要讨论生成式 dp,也就是含有“解”的 dp,无论求最优解,求合法解,抑或是求合法解数目。非生成式 dp 更接近数学推式子,还有直接递推的思维模式,一般来说,题面叙述到的变量本身,就已告知了状态,如 T-122。但要特别注意:非生成式 dp 中,若其他状态包含与当前状态相似的转移,可直接重复利用。

生成式 dp 中,子问题的意义在于确定“解在此部分的形态”。dp 和贪心的区别是什么?dp 会把与全局最优解有关的“解的部分”都记录下来。贪心只把使子问题达到最优的“解的部分”记录下来(若对子问题本身存在多解,只需记录“对外界”的最优解)。然而,不一定记录解本身,而是记录,“外界”若要调用“子问题的解”(在此基础上做微调,或以“子问题的解”权值计算更大结构的权值),所需的信息(微调的“接口”等)。

首要是定义状态,但定义状态时必须参照转移。状态定义时需要考虑五个要点:

  1. 描述法,描述法只跟答案计算过程,还有所选主体有关。
  2. 轴,即被扫描的最外层轴,和被(逐步描述法)微调维护的内层轴。若已知解,扫描此最外轴一般能求出解的权值(例如 dp 套 dp 或者 dp 套贪心,内层套的就是,权值或称“答案”的求解办法),但也有反例。
  3. 逐步转移(一般而言都是逐步)还是断点转移。
  4. 信息推后确定,尽量只在信息影响状态转移时才确定它;信息提前确定,该信息对未来转移至关重要,但若现在不确定并记录它,未来就没机会了;信息释放,某些信息不涉足未来转移,可与 dp 解耦,无需记录在状态,而是直接贡献给答案,或贡献给 dp 数组内的值,注意有许多信息可以在 dp 开始前就解耦。断点转逐步,通过增加状态和“当前操作未结束”来实现。逐步转断点,通过减少状态和释放信息来实现。
  5. 先粗糙再扩充,无法记录信息或完成转移时才扩充状态。此处还要介绍一个针对交互题非生成式 dp 的通法(这类题目,题面就已给出转移):先设计一个不足够的“状态一”,据此转移一次。为描述某个“状态一”转移一次后得到的状态,需要引入“状态二”。循环往复直到“状态二”对转移封闭,设计完毕。

信息推后确定,改成信息提前确定或信息释放,需要抓住这一点:信息推后确定时,大概是,现在记录下来一些“接口”“选项”“限制”供未来元素选择或遵从。提前确定或释放的策略则是,在“接口”“选项”“限制”建立的同时,确定某个未来元素(可能是抽象元素,例如,\(a,b,c\) 三个具体元素中的最大者)某个方面的值。要求是:对于任意情形,只要这个方面等于你所确定的值,就必能把接口接上,或满足当前限制。这样一来,只需记录“你所确定的值”(能做信息释放时甚至无需记录),而无需记录“接口”“选项”“限制”本身。

二、初步的转移

非常重要的一个概念是转移 dag。只需按 dag 的某一拓扑序,快速描述所有边,就能转移。描述边的方法一般有三种:

  1. 做贡献,即在点 \(i\) 处,处理一切以该点为起点的边。
  2. 受贡献,即在点 \(i\) 处,处理一切以该点为终点的边。特别地,可以扫描线(枚举 \(i\))微调维护各边的“当前权值”。
  3. 以边为主体,将多条(一般是边权 \(f_s+t\) 相同,或附加边权 \(t\),甚至附加边权来源相同)的边合并处理。

三、状态与转移的初步优化

打表看有效状态与有效值域是首要操作。

  1. 改换描述法。比如错解不优。选主体,轻限制。优先涵盖所有解,暂且忽略“解必须合法”“解必须优秀”。例如要选择子序列 \(a\) 使 \(\max a\) 最大。原本描述“子序列-子序列最大元素”,现在只需描述“子序列-子序列内任一元素”。
  2. 做对称,状态与答案互换。要求答案很小(例如答案是 01),且相同答案对应的状态是规整的(如一段区间。尤其是状态-答案具备单调性时,两个轴基本等价)。还能据此根号分治,见 T-102-6。
  3. 记忆化和下标换元。如定义 \(g(x)\) 为一切合法状态,则令新 dp 数组 \(dp_{x}\) 代替原来的 \(f_{g(x)}\)。很多时候,实际有值的状态不多。
  4. 对状态做合并,删除,或者等价替换。
    观察无用状态。除了删除值无效的状态(计数中值为零,最优化中值为无穷),在最优化问题中还有“只需记录某种前缀最大值”。例如原状态 \(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.。

普适型优化:

  1. 整体 dp。扫描线一个轴,数据结构(类滚动数组地)维护 dp 数组。这一步可能会用到单调性等性质。
  2. 用矩阵乘法表示转移。这就可以带修。一些 dp 数组衍生量,例如统计答案量(如果直接贡献答案的状态有很多),只需给矩阵扩维成 [f,ans]
  3. cdq 分治。适用于一切半在线形式 \(f_i=\sum_{j<i}(f_j+w(j,i)),f_i=\max_{j<i}(f_j+w(j,i))\)

性质驱动型优化:

  1. 决策单调性。
    决策单调性是指,对任意 \(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)\) 难以直接求,只能类似莫队来递推时,整体分治就发挥作用。
  2. 斜率优化。\(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)\)
  3. 凸优化。
    凸函数支持两种操作:闵和(与另一凸函数卷积),还有加上另一凸函数。对于前者,新函数的定义域 \([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\) 个”,二分套二分即可。
  4. 四边形不等式。
    \(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}]\)
  5. 区间查询。以母数组的多个区间为子数组,问子数组对应的 dp 值。可以运用猫树分治等数据结构题的处理办法,或者 4.2 的矩阵。
  6. 倒求贡献系数。初始化位置被修改,但转移图不变。倒序建立转移图,因为终点不变,即可 \(O(1)\) 应答一个起点的贡献。另有:查询多个 dp 结果之和,但转移完全相同。不妨统一赋初值,统一转移。
  7. 最短路优化。dp 的转移图不是 dag 时常采用。有时(无论是否 dag),从本层转移到下一层的方式固定,可改分层图为单层图,直接跑最短路。
  8. 拉格朗日插值。注意到 \(f_x=\sum a_i x^i\),因为 \(i>y\)\(a_i\) 一定为零,所以只需求 \(x=0,1,2,...,y\) 对应的 \(f_x\),然后拉插。
  9. 生成函数处理卷积。注意和拉插区分。定义多项式 \(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

  1. 扫描最外轴“树轴”。以“子树”为子问题,定义 \(f_x\) 表示考虑 \(x\) 的子树的答案,子树的根是关键元素,起到并联儿子子树的重要作用。重点关注如何合并子树、上传信息。往往用另一维状态存储包含当前点的连通块,或链的信息,看它是否应向父亲延伸,即信息推后确定,“最后一个操作没有结束”。
  2. 扫描“dfs 序”轴。例如“选择一个连通块,必须包含根”的问题,则若选择一个点就必须选择其父亲。按 dfs 序依次考虑,每次可选择当前点,或不选。不选择当前点,就相当于跳过当前子树(转移是 \(f_i\to f_{i+\mathrm{siz}_i}\))。
  3. 扫描值域轴。例如 T-089。
  4. 扫描深度轴。

又及,容易证明树上背包复杂度是 \(n\) 的“维数乘二”次方,“维数”是指与背包容量等地位的状态维数。
参见数据结构、图论章节的“树”部分。

  1. 长链剖分优化树形 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

  1. 选定最外轴后,内层轴用状压维护,状压能够存储的信息极多,因此大多数状压 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)\)
  2. 状压 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

普适型方法:

  1. 期望就是加权平均数,尤其当大分母(所有情况数目)确定时,可直接转化为计数。
  2. 扫描值域轴,拆 \(E(X)=\sum P(X\ge k)\)
  3. 改主体算贡献。例如计算全局符合条件元素的期望数目,改为算各个元素符合条件的概率之和。
  4. 把每次均匀随机改为随机排列并依次操作,遇到不能操作的就跳过。然后可以拆贡献到点上(“本点在多少个排列中被操作”)。
  5. min-max 容斥。
  6. 转移图是 dag 的问题,可按拓扑序直接求解。反之通过高斯消元求解。特别地,如果转移图“类似但不确切是”线性结构、树形结构时,采用主元法,也即假设已经求得了若干关键元素的期望,用它们加上常数,去表示其余元素的期望,把一切转移环打破,然后正常地扫描一个轴来求解。转移到一个关键元素,就得到一个方程。最后解方程算出所有关键元素的实际期望。

性质驱动型方法:

  1. 高次期望的拆分。应用了期望的线性性。\(E((x+1)^2)=E(x^2)+2E(x)+1\)。用于转移时,就要用两个状态分别记录 \(E(x^2),E(x)\)
  2. 对于求期望步数的题目,求转移附加边权时,常常要将“选到就意味着升级的元素”视作“障碍元素”,与其余“障碍元素”混在一起,计算“选到障碍元素的期望步数”,如 CF1392H。
  3. 例如 \(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,且要求输出实数,就可以尝试了。

五、其他

  1. 主动算重。对几个相同的元素,给它们钦定“标号大小”顺序,使之不同。最后除以钦定顺序的方案数即可。
  2. 基环树(森林)的计数技巧,有三种:
    2.1 先钦定一些环,然后把环缩成点,并用 prufer 序列,或 Cayley 定理完成常规森林计数。
    2.2 逐步添点,维护“由前 \(i\) 个点构成的虚基环树森林”形态。
    2.3 给边定向,指向父亲,环上会有两种方向,对一个连通块除以二即可。从小到大加入点,加入一个点时要把它可达的点全部加入,如果可达的点中有编号比“当前时刻”更大的,就什么也不做。也即扫描 \(i\) 的过程中,要么添加一个包含 \(i\) 且编号都小于等于 \(i\) 的环,要么添加一条以 \(i\) 为结尾且编号都小于等于 \(i\) 的链,这链一直接到已有环上/已有的树上,要么添加一个 ρ 形态结构,要么等待机会,什么都不做,任由后面的点把 \(i\) 添进去。
  3. 二分图的计数技巧。
    3.1 一个连通块仅两种黑白染色方法。
    3.2 扫描右部点做 dp,状压或枚举左部点涉及的全部信息。
  4. 实数轴上的相对大小问题。有一个扩倍的经典思路,就是把每个单位长度分成 \(n\) 份,然后 \(O(n!)\) 枚举各元素小数部分的相对大小关系(这部分可改为 dp 确定)。

第三章 数据结构

§3.1 一般方法

一、代数结构与信息的性质(选择性阅读)

这一部分远非严谨的数学证明,甚至在定义上也可能有小错误,请多包涵。然而需要强调的核心思想是:要关心所研究对象满足的代数结构类型,确定其性质,从而选用最合适的数据结构来维护。本节相当于“元素”在数据结构语境下的一个显影。

一个代数结构是一个集合,连同定义在该集合上的若干运算,这些运算满足特定的公理。例如对于集合 \(R\) 内的元素,可以定义若干双目运算“加法”“乘法”等等。一般来说,这个“集合”可能有两种现实意义,分别是:“集合”由所维护的信息组成;“集合”由某种操作本身组成。

  1. 半群。
    定义:满足结合律的运算和对应元素 \((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 交换半群。
    定义:半群基础上,满足交换律。
    意义:交换律使得合并时,不必按左右顺序。定义在操作上时,交换律是莫队的基础。
  2. 群。
    定义:幺半群基础上,每个元素有它对应的逆元,满足 \(b+b^{-1}=e\)
    意义:如果定义在信息上,是可减性,允许差分;如果定义在操作上,是可逆性,操作存在逆元,就允许撤销操作,用于莫队等算法。
    2.1 循环群。
    定义:群内集合可以由生成元(用原根来理解)构造,集合 \(R=\{ng,n\in \mathbb{Z}\}\)。循环群可以通过“在一个环上转圈”来理解。
    意义:例如取模意义下的指数运算。循环群要求 \(0g=e\)。有限循环群还要求存在正整数 \(x\) 使得 \(xg=e\)(无限循环群可以视作环特别大以至直线)。
    接下来是定义在同一集合上,且有两种运算的代数结构:
  3. 格。
    定义:两个定义集合一样的半格,设运算分别为 \(\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)\) 的性质能够成立。
  4. 非交换半环/分配幺半群。
    定义:两个定义集合一样的幺半群,设运算分别为加法和乘法,则乘法对加法满足分配律 \(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。

二、轴在数据结构中的应用

本节相当于“轴”在数据结构语境下的显影。

  1. 扫描线。假设扫到 \(t\),一般实时维护“在外部轴时刻 \(t\),内部轴上坐标为 \(i\) 的元素的值是什么”。若有两个内部轴,就维护二维平面。所谓“强制在线”,就意味着题目指定了最外轴是谁,并钦定最外轴仅允许直接扫描线。
  2. 区间查询。每次查询涉及到最外轴上的一个区间。如果信息具备可减性,可以尝试差分。
    法一,坚持扫描线,视情况可做线段树历史和。
    法二,类猫树分治。
    法三,令若干个轴地位平等,使用莫队。
    法四,分块。
  3. 规避删除,加入,或者修改操作。
    大部分修改操作都可以视为先删除再加入。将一个大修改解耦成若干“先删除后加入”的“单位修改”。
    3.1 对最外层线段树分治,回滚莫队,或者分块并定期重构(后两者本质相同)。
    3.2 最外轴翻转。加入变删除,删除变加入。如果终止状态确定,常用这个办法。
  4. cdq 分治。需要满足:可将贡献解耦并视为“初始元素、修改和查询间的连边”,一切贡献从最外轴的小端向大端进行。
  5. 转 01 序列。尤其是限制只涉及“元素相对大小关系”的题目,可把值域轴放外面做扫描线,设大于当前值的元素为一,反之为零。
  6. 在答案值域轴上直接二分。常常用数量关系来刻画“本区间内是否存在答案”。搭配线段树上二分更佳。
    6.1 整体二分。需要满足:可二分答案,可将贡献解耦并视为“初始元素、修改和查询间的连边”,贡献依任意顺序可加。
  7. 查询轴。放在内层维护各个查询“当前的答案”,碰到 \(l_j=i\) 则插入一个查询,碰到 \(r_j=i\) 则问答案。

三、其他一般方法

普适型方法:

  1. 数据结构“三步走”:预处理。修改。查询。
    千万不要忽视查询,查询时能处理的事情很多,预处理或修改时难做就放弃,因为不是每一时刻都得保证能 \(O(1)\)\(O(\log)\) 知道任一元素的答案。
    预处理方法有折半、分块、分治等。询问复杂度过低往往是预处理造成的。
  2. 寻找一个熟知的问题,比较这两个问题的强弱,可得复杂度下界,也可借鉴已知问题的思路。
  3. 合并元素、更改主体,统一贡献、统一修改。
    缩连续段,尤其是具备单调性的时候。因为单调性暗示着两个轴地位一致。一般而言,这时候的连续段,就不只是在一个维度上连续,而是同时在多个维度上。
  4. 只考虑关键元素。关键元素的数目可能并不多。如果元素没有用了,就把它删除,或与其他元素合并起来。

性质驱动型方法:

  1. 数量表示信息。尤其是带有“复杂结构判定”的题目,尝试用某种数量(类似于哈希值)来替代区间信息,转化为判定对应区间的这个值。好就好在:第一点,复杂结构压成的数量,往往具备可加性,合并数值远快于合并结构。第二点,异或哈希还具备可减性,允许迅速对消。
  2. 每个元素具备 \(k\) 种阶段,最外轴扫描过程中,阶段只会增加不会回退。每次暴力找出待更新的元素,设单次找出 \(O(a)\) 则总复杂度 \(O(nka)\)
    特别地,元素的两个阶段是“存在”与“消逝”。例如元素只会合并而不会分裂。将合并得来的视为抽象元素,两个抽象元素合并时,就视为旧元素消逝而新元素开始存在。故暴力合并是正确的。有时会解耦维护,也即采用 \(k\) 个数据结构分别维护每一类元素。
  3. 割补法。问题形如,在二维平面上查询一个区域内的信息。将大区域分割开来,由于经过了解耦,若干小区域的求法常常完全不同(可以离线,可以在线,可以改主体,可以递推。每一类小区域的预算都和总预算相同)。
    首要是分析,信息是否具备可加性、可减性、可重算性。可减性意味着允许差分,可重算性则允许小区域互相重叠。
    在二维平面上,只要信息满足可加性,\(O(\log)\) 就足够处理(一定注意:直线、矩形未必与轴平行):
    在线(带修)求两条平行直线之间范围内的信息;
    在线(带修)求某条直线上某个点 \((x,kx+b)\) 左上方的信息;
    离线求“确定了小于等于三条边的半矩形”内的信息(扫描线)。
  4. 查询时有临时修改。但不是真正在数据结构上修改。若忽略修改查出最大值,改后可能不再是了。如果修改涉及的元素很少,同时维护最大值、次大值等即可。
  5. 查询涉及“等于某值的全体元素”。思考这个值是否一定为最值。或者说思考只关注最值元素能否回答查询。
  6. “区间”“点对”。
    6.1 既可认为是一维的,也可以为它建立两个轴(\(l,r\) 的两个轴,画在二维图上考虑,一般用分治或者分块处理)。
    6.2 思考特殊性质:区间互不包含如何做;区间形成树形结构怎么做。有时题目所示的区间暗中满足了几项性质。

§3.2 分治数据结构

  1. 多 tag 问题。一个操作序列,可被压缩成顺序确定的“操作种数”次操作,其先后顺序需要自定义,例如有加有乘的情况,就把操作序列压缩成先乘 \(a\) 再加 \(b\)。确定顺序后,只需考虑合并 tag,例如有两种操作时,就相当于“任意元素先与 \(a\) 做操作一,再与 \(b\) 做操作二,再与 \(a'\) 操作一,再与 \(b'\) 操作二的结果,等于先后与 \(a'',b''\) 做操作一,二的结果,求这个 \(a'',b''\)”。
    将 tag 视作代数结构,则 tag 之间能做乘法(只有结合律,一般不满足交换律),tag 对信息的乘法运算(或者说影响)满足分配律。
    一般来说,tag 可被视作“特殊定义乘法”的矩阵,此时,信息也需变成对应类型的矩阵。矩阵乘法在半环上定义,因此许多 tag 合并规则可以统一为矩阵运算。
    涉及空节点(空 tag)的 pushup(与 pushdown)需要特别注意,一般来说空节点被视为单位元。
  2. 类势能线段树。某特殊单点操作(或特殊元素)总计进行 \(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. 主席树。
    3.1 常选取时间轴、值域轴等作为主席树的版本轴。版本轴可以作为最外轴被独立维护。例如要同时修改主席树好几个版本的点值,可以用外层线段树套内层主席树。
    3.2 区间修改等情况里,主席树标记下传需要额外建立节点。为了空间更省,常数更小,最好标记永久化。
    3.3 多版本问题除了主席树,还可以建立版本继承关系树。
  4. 历史最值与历史和。
    法一,每节点维护一个 max,+ 矩阵 [a,b,0],其中 \(a\) 表示当前的值,\(b\) 表示到当前的历史最值。用 tag 维护操作,给每个节点放置 3*3 标记矩阵。但无法标记永久化,因为矩阵乘法有顺序。
    法二,给节点维护时间戳,代表“最后一次对当前节点打标记的时刻”。标记永久化时,可以利用时间戳,使子节点给祖先做值上的贡献,假性“同步版本”。
  5. 单边递归线段树。线段树子节点 pushup 时,需要用到某一儿子一段前缀的信息。用法:尝试用区间前缀最大值序列描述区间答案。
  6. 线段树分治。将所有“删除”变为撤销上一步。可以维护“操作栈”,发挥作用时入栈,消除影响时从栈顶倒序消除,弹栈至空。线段树分治可以半在线,dfs 到某个时刻(叶子节点)后,依据此时的状态,对后面的时刻产生加边或删边的影响。
  7. 线段树卡空间,定期重构。
    取消动态开点的办法:离线所有询问(然后离散化),统一预建树。
  8. 暴力跳父亲。平衡树或线段树上,已知节点编号,求在树上的相对位置(类似于平衡树上求特定节点排名,或 zkw 线段树),可以从它往上跳父亲。
  9. 平衡树带交并。相当于平衡树领域的线段树合并。若合并 \(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);
  10. 线段树合并。复杂度 \(O(\min (n,m))\)。合并时,如果两棵树当前节点中,有任一个没有儿子,就特殊处理终止递归。反之则各自 pushdown,分别合并左右儿子(若某棵树只有一个儿子,pushdown 时建出另一儿子)。
  11. 线段树分裂。区间操作变为全局操作(也就是给待操作区间建立 \(O(\log)\) 个虚点,从而具备一棵完整线段树的结构)。最后线段树合并回来。线段树分裂的复杂度是一次 \(O(\log)\),而线段树合并每递归一下就减少全局上的一个节点,故其总复杂度为 \(O(n\log V)\)。分析“全局节点个数”为势能量从而计算复杂度的,在数据结构题目中很常考,如 QOJ9559 P5494。我认为线段树分裂能做的,大多用平衡树带交并也能解决。
  12. 定长分块中的线段树。涉及到相邻两块,前一块选 \(i\) 后一块选 \(j\) 要求 \(j-i<C\) 的问题,不妨把两块叠合起来(有时将某一块倒序放置)建立一个线段树,就变成形如 \(i<j\) 的限制。具体见 T-221。
  13. 线段树维护复合函数。输入代表传进的值,输出表示经过此区间之后的答案,每个节点相当于一个函数,子节点 pushup 相当于函数的复合。
  14. 分治,cdq 分治,猫树。其核心总在于用 \(\mathrm{mid}\) 把元素划分成左右。两部分尽量解耦,但有时藕断丝连,需要先内部消化贡献和需求,再向对面请求。
    14.1 猫树问题需要经过适当预处理,使得一个查询只访问一个节点就得到解决。允许考虑“与当前猫树节点有关的‘贡献点对’”,可能是指“被节点区间包含”,还可能含有“被当前节点某儿子区间包含的‘贡献点对’”。
    14.2 类似决策单调性分治的办法,可广泛用于“\(f_i\) 有单调性,同时求多个 \(f_i\)”的问题。
  15. trie。
    15.1 求一个数的前驱和后缀,本要用 set。用压位 trie,时间复杂度降至 \(O(\log_w(n))\)。取 \(w=64\),即每个节点有 64 个子树。
    15.2 01 trie 全局加一。只需从低位到高位建立 trie,全局加一时,每次将当前节点的左右子树互换,并递归进“一”子树即可,共计 \(O(\log)\) 次交换。

§3.3 根号数据结构

一、分块

  1. 善用预处理。
  2. 查询的 \(O(\sqrt n)\) 预算可能用于:
    每块的末尾记录某值,每次从最近块的末尾递推到精确位置。
    枚举 \(i\) 所在块(\(i\) 属于某整块)或者 \(i\) 具体值(散块)。
  3. 卡空间。做贡献,一块一块做。就只需保存当前的块的预处理和标记,空间复杂度下降为 \(O(n)\)
  4. 主体是 \((l,r)\) 点对。画在二维图上考虑。类似割补法,分情况处理。\(l,r\) 均属于整块;\(l\) 属于散块,\(r\) 属于整块;\(l,r\) 均属于散块。见 T-102-4。特别是散块对散块可以用归并的办法。
  5. 定长分块。“一段长 \(\le c\) 的区间”的问题。每隔定值 \(c\) 分段或设置关键点,这种区间最多跨过一个关键点。
  6. 块内-块外。本质是先处理块内关系,处理完后一致对外。
  7. 值域分块代替二分。扫描值域轴,一个块一个块地跳。当跳不过当前块(预算余量不足块内总价值)时,改为在块内一个一个跳,直到答案位置。
  8. 倍增分块。它适用于“对块 \([B^k,B^{k+1})\) 内元素的特殊操作,元素必然增加 \(B^k\),故暴力进行特殊操作能够接受”的情形。顺带一提,把特殊操作拿出来分析,往往发现它进行的次数不太多,比如老哥或者根号量级。

二、莫队

  1. 带老哥莫队的优化。回滚莫队,值域分块,二次离线。
    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)\) 回答。
  2. 带权莫队。将 \(l=x\) 的查询次数记录到 \(a_x\) 处,从左往右扫计数器对 \(a_i\) 累计求和,每当计数器大于 \(\sqrt q\) 就新开一个块;每当本块长度大于 \(\sqrt n\) 也要新开一块。如果存在某个 \(a_x>>\sqrt q\),那么对 \(l=x\) 的查询们特殊处理,扫一遍真正的“只删除莫队”(无需撤销上一步)即可。
  3. 高维莫队。比如三维状态 \(x_1,x_2,x_3\),排序时用第一关键字是 \(x_1\) 所在块,第二关键字是 \(x_2\) 所在块,第三关键字是 \(x_3\)。注意要将大修改拆成单次只加入或删除一个元素的“单位修改”。

三、根号分治

常按节点度数,出现次数,连通块大小等根号分治。

  1. 问题涉及两元素间的关系,但是直接维护“任两个元素间的关系”是不可取的。可用根号分治把关系变成单向(从小型元素到大型元素)的。
  2. 如果某些元素的和是一个不大的定值(\(10^12,10^5\) 分别代表 \(O(\sqrt n),O(n\sqrt n)\) 的复杂度),不妨根号分治。有两个对称的结论:
    2.1 大于 \(\sqrt n\) 的元素仅 \(O(\sqrt n)\) 个。
    2.2 所有元素只有 \(O(\sqrt n)\) 种本质不同的出现次数。
  3. 平衡复杂度。用若干种不同角度的暴力做法,分别处理自身适宜的数据范围。

§3.4 树

  1. 树上的主体,常见的就只有点,边,子树,连通块,树链(路径)这五种。
    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\) 更加奇怪,就可能需要引入“合法子图”的概念,或者对于度数不同的节点赋予不同的权值了。
  2. 轴。
    如果把“树轴”放外面,最常见的是合并子树信息(可以线段树合并),按 dfs 序扫描,按深度扫描,不常见的有按节点编号扫描等。
    如果把“树轴”放里面,常见的有即时维护虚树,重链剖分并即时维护线段树,dfs 序拍平子树变成区间等。
  3. 树上路径问题。
    3.1 树上差分。
    3.1.1 普通的链求和,差分转化为三个到根链求和。有时可建立主席树维护“节点 \(i\) 的到根链”。
    3.1.2 虚树相关问题,有时把虚树拆成若干条到根链,以到根链为主体。有时要考虑不同到根链间的关系和贡献。例如:求一棵子树内是否有某种颜色,可以将所有有此颜色的点按 dfs 序排序,每点加一,每相邻两点的 lca 减一,答案即为子树内标记和。
    3.2 一些对称(做贡献-受贡献层面):单点改链求和,变成子树修改单点查询。类似地,链加单点查询,变成单点加子树求和。及二者的逆转化。链改可视为到根链改,从而对称成子树操作。
    3.3 离线把路径挂到 \(u,v,\mathrm{lca}\) 上。然后把“树轴”放外面。
    3.4 树链剖分,或者点分治,见下。
  4. 树链剖分。
    重剖几乎总是把每个点轻儿子,或轻儿子子树的信息合并上来直接挂在本点,然后用线段树去处理重儿子,及父亲方向的信息。
    4.1 重剖维护树上 ddp。
    4.2 带修邻域查询问题,经典重剖解决。注意“k-邻域”“连通块”是差不多的。
    4.3 子树邻域查询,提示长剖。
  5. 点分治。
    分治是在分治中点考虑“所有跨过该中点,且被当前分治区间包含的区间”。点分治是在分治重心考虑“所有包含该重心,且被当前分治树包含的连通块,或链”。用于解决树上路径或连通块问题。
  6. 其他。
    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.1 有两种对称的分块平衡方法,分别对应两种倍增。一种是以“步数”为状态,以“位置”为答案,处理“从 \(x\) 开始,跳 \(\sqrt n,2\sqrt n,...\) 步到哪里;从 \(x\) 开始,跳 \(1,2,...,\sqrt{n}-1\) 到哪里”。另一种是以“位置”为状态,以“步数”为答案,处理“从 \(x\) 开始,跳多少步跳出当前块;跳出当前块后第一个跳到的是哪里”。
  2. 启发式合并允许“陪跑”。枚举小一边的同时,可以枚举大一边的等量元素。
  3. baka's trick。如果双指针不便维护删除,可以维护 \(\mathrm{mid}\) 分别到 \(l,l+1,...,\mathrm{mid}\) 和到 \(r\) 各自的值,每一次增加 \(r\) 直接操作;增加 \(l\) 可以直接调用 \(l+1\) 即可;如果 \(l>\mathrm{mid}\)\(\mathrm{mid}=r\) 然后重构即可。
  4. 十字链表。可以维护元素邻接相对位置。在元素邻接相对位置变化不大时可以处理,见 SNOI2024 矩阵。
  5. 并查集。
    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.1 原图是不是:树,仙人掌,dag,竞赛图,平面图,广义串并联图,弦图等。
    1.2 缩点处理后,一个分量内部是否具备良好性质。考察一个分量内部的性质,可以利用子图添点法。
    子图添点法本质上是通过调整法找多元限制。
    1.2.1 耳分解-子图添点法。例如在某道题中:对一个点双,若是个环显然合法。添一个耳,发现这个耳只能是一条边,而不是一条路径。同时,不允许两个耳“相交”。因此,一个点双形如一个“大环”上多连了一些互不相交的边。
    1.2.2 同胚-子图添点法。常见的一些特殊图的定义是用“不存在子图同胚于图 \(G'\)”来给出的。“同胚”的意思是,经过若干次缩二度点,可以使得两图完全相同。例如广义串并联图:不存在 \(K_4\);平面图:不存在 \(K_5,K_{3,3}\)
    例如在某道题中的操作流程:假设“不准与之同胚”的图为 \(G'\),从三元环开始尝试,三元环本身是不够的。添点可得,合法点双不包含同胚于 \(K_4\) 的子图,这意味着每个点双都是广义串并联图。
  2. 尝试缩点。建立点双缩点树,圆方树,或强连通分量缩点 dag。
    2.1 关注连通性,尤其是涉及到删点时,注意割点-圆方树。
  3. 建立生成树。
  4. 扩展版广义串并联图方法。不断缩合、删除点或边,达到简化图的目的,原图不拘泥于无向图,缩合删点方式自定义。拓扑排序,还有广义串并联图方法,都是例子。
    4.1 当元素的度数小,小到足以看出它没有用,或者用途能够被一眼看出时,就应该缩合或者删除。
    4.2 让一个点、一条边承担一个原子图的责任。例如令一个缩合而成的点权存储“它表示的原子图的信息”,边权亦然。
    4.3 \(m-n\) 很小的一类图,经过广义串并联图方法处理后,点和边数都会极小。另外补充,也可建立生成树,将非树边及相关端点都拿出来,建立虚树之类的。
  5. 采用数量关系(点数,边数,度数)来考察图的状态。
  6. 尝试归纳。按照某种顺序逐渐添点和边,或者逐渐删点和边,递归成规模恰好小一的子问题。

§4.2 具体方法

一、元素取值限制问题

  1. 对非 01 元素取值的限制。
    1.1 限制呈变量加和形,求合法解或字典序最小解,差分约束。
    1.2 限制呈变量加和形,给变量赋值有代价,最小化总代价。切糕模型。
    1.3 拆点 2-sat。对元素 \(x\)\(O(V)\) 个点表示 \([x\le a]\) 的 01 性(值域大?离散化;证明只有很少的数字有用)。
  2. 二选一问题。
    2.1 网络流,采用集合划分模型。另外补充,“团”的限制会令人想到“最小团问题”,从而想到集合划分模型。
    2.2 网络流,给一次决策建立虚点,向两个可能的选项连边。
    2.3 转化为类 2-sat 问题。
    2.4 在选项之间连边,转化为给边定向的问题。
  3. 类 2-sat 问题。
    3.1 2-sat。注意逆否命题也要连边。
    3.2 二分图,扩展域并查集。要求是 2-sat 边无向,即若 \(a\)\(b\)\(b\)\(a\)。好处在于一个连通块仅两种黑白染色方法,可以计数。

二、特殊图

  1. 树。
    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. 环。
    2.1 找环,常常缩点,或 dfs 树,或拓扑排序求出。
    2.2 破环为链。有向图里常常体现为 \(x\to y\) 的一条边和 \(y\to x\) 的一条路径。或者两条路径。
  3. 仙人掌。
    建立圆方树。每个点双都是环。按照环上顺时针等顺序,排列一个方点的全部圆儿子,然后逐个 dfs 之类的。
  4. 竞赛图。
    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. 平面图(任意两条边可以不在节点以外的地方相交的无向图,或者“不存在五元团、三-三完全二分图”同胚子图的无向图)。
    5.1 平面图的欧拉定理。它将平面划分为 \(r\) 块,则 \(r\le n\)。设原图有 \(p\) 个连通块,则 \(n-m+r=p+1\)。故 \(m\)\(O(n)\) 量级的。
    5.2 建立对偶图。如果是有向平面图,可以面朝原图边的方向,让对偶图边从原边的左向右垂直穿过。
    5.3 网格图上连通性问题,及最短路,常转化为障碍物路径问题,及最小割。考虑怎样的两个障碍物之间能连边(有边才能讨论路径),换句话说,找出“障碍物构成的路径”中“相邻障碍物”的性质。枚举这两个障碍物的相对位置,并想象按照网格图边,从此两个障碍物的“中间”穿过(取两个障碍物的重心,连接,取连线中点,假设站在连线中点周围的某网格图点上,看能否按照近似于“垂直连线”的方向,从两个障碍物间穿过)。如果不能,这两个障碍物可以视作建立了边。
  6. 广义串并联图(不存在四元团子图,它也是一种平面图)。
    用广义串并联图方法缩任意 \(m-n=k\) 的图,可得点数 \(2k\),边数 \(3k\) 的小图。
  7. 欧拉图。
    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.1 根据(黑白)染色方法(按节点编号奇偶性,或剩余系分组),行列建点,出入度虚点(如 T-184)的套路,将元素分为若干部分,每部分就是一层。
    1.2 根据时间,某种资源量的值,或某种“特殊工具”的“当前剩余使用次数“,进行拆点,建立分层图。
    1.3 用两遍残量网络 bfs(起点终点各一遍),即可对所有 \(i\) 求出,经过 \(i\) 是否存在增广路。
    1.4 输出最大流方案。求出残量网络后,每次 dfs 模拟一条流,能流就流,将残量网络稍微还原即可。
    1.5 逐步修改原图,在上次跑完的残量网络上继续。
  2. 最小割。
    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. 模拟网络流。
    3.1 模拟费用流,详见 T-118。
    3.2 模拟最大流,或模拟最小割。描述与 \(s,t\) 分别连通的集合,不能有交。如果某一层的边数非常少,常枚举这一层的边有哪些割掉,以此为基础考虑剩余的图。
  4. 偏序集的最小链覆盖。定义:链,一个子图,任意两个点都可比(可跳过一些中间点,不是图论中的路径)。反链,一个子图,任两个点都不可比。建新图,一个点向其所有原图后代连边,求 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. 二分图。
    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.1 最短路树和最短路 dag。
    1.2 spfa 求得的解恰为差分约束字典序最大解。“字典序”限制,肯定有另外要求导致变量有界,如 \(a_i\le 0\),建虚点表示值“零”即可。要求字典序最小时,此类有界限制必然是 \(a_i\ge 0\),解法是考虑 \(0\ge b_i=-a_i\) 间的关系,直接对 \(b_i\) 求最大字典序,然后全取相反数处理。
  2. 拓扑排序。
    2.1 可以处理“\(x\)\(y\) 早”的限制。拓展应用见 T-032。
    2.2 反图也是 dag,只需关心当前访问到的点在反图中的 dfs 序,倒过来就是正图拓扑序。选一个超源点作为起点 dfs。每次 dfs(x) 时候,先访问其所有反图上的(未访问过的)儿子,回溯时 ans[x]=++timrtimr 就是“当前访问过反图多少节点”,而拓扑序/反的 dfn[x] 当然也就是这个。若可以高效维护当前点“没有访问过”的反图儿子,则此方法有效,见 CF798E。
  3. 生成树。
    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. 博弈论。
    4.1 dag 游戏。公平博弈(同一位置双方可以选择的下一步位置集合相同),能从一个状态走向另一个状态就连边,结果图是一个 dag,两人轮流操作,谁无路可走就输/赢。
    初始化时,记录所有状态的出度,出度为零的压入队列,并根据游戏是正常规则或是反常规则,分别设为必败或必胜。弹出队首。如果是必败状态,则设前驱状态为必胜状态。否则,当前状态必胜,所有前驱的出度减一,并将出度为零的前驱设为必败状态。将可以判断胜败的前驱状态压入队列。队列为空时终止。尚未判断胜败的均为平局。
    4.2 二分图博弈。给一张二分图和起点,两人轮流操作,每次操作只能选择与上一个被选的点相邻的点,不能选已选过的点,无法选的人输,问先手是否必胜。考虑二分图所有的最大匹配,若都包含了起点,则先手必胜,否则必败。
    求匹配必经点是经典问题。先跑一遍匹配,从每个左部非匹配点出发,遍历它的所有右部匹配点邻居对应的左部匹配点,则所有被遍历到的左部匹配点均为非必经点,因为从它们开始存在交错路径使得终点为非匹配点。为了不选该匹配边,顺次将匹配边换成路径上一条边即可。
    4.3 反常游戏(先手要把自己送死才算赢),先手必胜当且仅当游戏的 sg 函数不为 0 且游戏中某个独立游戏的 sg 函数值大于 1,或者游戏的 sg 函数为 0 且没有某个独立游戏的 sg 函数大于 1。
    4.4 很多题目中,先思考“我如何赢”,再思考“我如何不让别人赢”,即“如何逼平”。
  5. 优化建图。
    5.1 区间向区间连边,可以转化为区间向虚点连边,再由虚点向区间连边。
    5.2 对连出边的区间,拆成了 \(O(\log n)\) 个线段树上节点,“入”区间亦如是。让每部分各 \(O(\log n)\) 个点互相连边,呈完全图状,如 ABC414G。
    5.3 动态删点,一段区间的点向 \(i\) 连边。主席树优化建图。二维偏序建图也可扫描某一轴后转化为上述过程。
    5.4 前后缀优化建图。
    5.5 最好的优化建图就是不实际建图,例如拓扑排序,只需数据结构动态维护 ind 的变化,每次不断找出下一个 ind 等于零的入队,未必得真建图。
  6. 点边互相转换。对于“点”是一种序列,有一种转换技巧是,原来的点只保留前面几位,将最后一位作为边,表示点点之间的联系,例如 De Bruijin 序列中哈密尔顿路与欧拉路径间的转换。
  7. 将一个点双划分成两个部分,要求各自连通的问题。做双极定向。

第五章 数学

生成函数详见 T-101。

§5.1 式子

式子一般如何化简?

  1. 转换贡献主体,递推,改变求和顺序。在外层求和枚举的变量,当作常量从内层求和中提到外,内外层解耦的同时内层当作函数预处理。
    在求积式中,乘除的地位与和式中的加减相同,乘方的地位与和式中“乘上系数”的地位相同。所以在和式中常见的 \(\sum(a-b)=\sum a-\sum b,\sum ka=k\sum a\) 在求积式中均有类似物。更进一步地,可以通过 ln/exp 把求积式转化为和式。事实上可以手动写 ln 后对和式做适当的变换,然后手动 exp 回来(这就避免了对积式做操作),并不需要运用多项式。
  2. 组合数公式套用。
    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. 组合意义。
    3.1 选取元素模型。把和式看作某种“选小球过程”的方案数。
    3.2 格路计数模型。有时发现 dp 转移式与求解格路计数时等价,可以把求 dp 数组值改成求组合数学和式。
    3.3 带权方案数的权值,用组合意义弄到方案数里去。
    3.4 把多项式乘开的过程,可看作是在各多项式中选择一个单项式乘到式子里,并把得到的多个式子求和。如 T-096。
  4. 涉及 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. 绝对值。
    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 数论,变换与反演

  1. 积性函数是指,若两个数 \(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})\),用这个递推式去计算。
  2. 狄利克雷卷积,\((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. 莫比乌斯反演。
    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. 欧拉反演。
    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))\)

  1. 单位根反演。
    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\) 的因数之一,快速幂即可。
  2. 分解质因数。
    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\) 等。
  3. 其他。
    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.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. 卢卡斯定理。
    2.1 \(\binom{i}{j}\mod 2=1\) 当且仅当 \(j\)\(i\) 的二进制子集。
    不过得找个递推方法使得 \(i=x+y,w=z+x\) 的变化量不多。这相当于是把 \(x,y,z\) 互相独立地拆开,要求它们三个的变化量均不多。
  3. 零点存在定理。一个序列,最大值大于 \(0\),最小值小于它,则必存在一个点和它相邻的点正负号相反。
    尤其是序列元素连续变化时,若最大值大于等于零、最小值小于等于零,则二者之间必然有一个位置为零,所以这种情况下可以线段树二分找出零点。
  4. 切比雪夫和曼哈顿的互转。
    4.1 切转曼,是为了把两维分开做和,来规避 max 这种跟两维都有关的运算。
    4.2 曼转切,是为了把和转成 max,方便做二分之类的。
    4.3 另外补充,二维平面上的随机游走可以看作 \(x,y\) 分别等概率地 \(+1\)\(-1\)(共四种可能)。
  5. prufer 序列。
    5.1 扩展 Cayley 公式。将大小序列为 \(a_i\) 的若干有标号连通块连成树的方案数为 \(n^{k-2}\prod a_i\)
    5.2 限制带在度数上的树计数问题。尝试和 prufer 序列联系起来,因为我们知道 prufer 序列中度数为 \(i\) 的节点恰好出现 \(i-1\) 次。
  6. 线性基。
    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\) 小等。给每个数字扩展一个二进制“奇偶指示位”放在最高/最低位,就可以了。
  7. 方差。
    方差就是 \(\frac{1}{n}\min_x (a_i-x)^2\)。因此与方差有关的最值问题,常常改为以 \(x\) 为主体(首先枚举 \(x\))来考虑方差,\(x\) 不一定是所枚举集合的均值,这类似于错解不优。特别地,处理最大值时,则必须钦定 \(x\) 就是均值。
  8. 绝对众数。
    8.1 摩尔投票法,最后剩下的就是绝对众数。
    8.2 若一定有绝对众数,则将所有数排序后,中位数就是绝对众数。
    8.2.1 补充:如果要选择一个区间,使得区间和大于二分之序列总和。找原序列中和大于二分之总序列和的第一个前缀,则区间必然跨过该前缀的末端。
    8.3 若有绝对众数,随机一个序列中的位置,则有二分之一概率它就是绝对众数。
  9. 对数。取对数,乘变加。
    例如若干 \(x_{a_i} \ge k_ix_{b_i}\) 条件均需满足,取对数使用差分约束,\(\log{x_{a_i}} \ge \log{k_i}+\log{x_{b_i}}\)
    9.1 可用于大数比大小。
  10. 求前 \(k\) 大方案和。
    法一,直接 dp。
    法二,调整法和堆。把最优解放入堆中。共进行 \(k\) 次,每次弹掉当前最优解,对当前最优解进行一点调整得到次优解,后放入堆中。
    法三,二分。记录 \(c(m)\) 表示价值大于等于 \(m\) 的方案数,而 \(s(m)\) 为这些方案的价值之和。得到最终 \(m\) 之后,显然答案就是 \(s(m)-(c(m)-k)m\)
  11. \(a+b\le m\) 的问题。
    11.1 就是将所有元素分为 \(\le \frac{m}{2}\)\(>\frac{m}{2}\) 两个组别。
    11.2 正序扫描值域轴到 \(i\) 的同时,倒序扫描值域轴到 \(V-i\)
  12. 前缀操作结果。也就是 \(s_i=\mathrm{op}_{j=1}^i\ a_i\)。它很可能总计 \(\log V\) 种不同的值,且值相同的是连续段。另外补充的例子,是单目运算,一个数至多取 \(O(\log)\) 次欧拉函数会变为一。
  13. 依次加入元素,发现计数器越过 \(a\) 时立刻终止。
  14. 从一点引射线,与多边形的边相交奇数次,则点在多边形内。
  15. 01 序列。关注它的差分、前缀和是很常见的,有时还翻转偶数位。

第六章 交互与构造

§6.1 一般方法

广义上的构造是,可以输出操作序列或元素方案的一切“存在性”问题。对结构大小 \(x\),或结构某方面的性质一般有要求。常见办法有:

  1. 数量关系,或势能法。
  2. 已知变现。已能解决的情况,从待考虑序列中剔除,就只剩下具备一定性质的问题。
    2.1 构造的第一步是判断无解性。可视为一种变现手段。无解性一般和某个数量有关,比如“某个数量的奇偶性合适”或者“某个数量处于一个上下界范围内”则问题有解,注意所述的条件可能并非必要。例如,若 \(n>5\) 则问题有解,不代表 \(n\le 5\) 问题一定无解,可以小数据暴搜一下。
  3. 归纳构造。
    3.1 每次删除(或确定结构中的)一个元素。删叶子(或扩展版广义串并联图方法),删极值,按某个轴依次在原有解基础上添点。
    3.2 每次加入一个元素,并维护当前的结构。
  4. dp 构造。
  5. 二级结构。
    5.1 二级结构涉及询问之间关系匪浅,询问的参数差别一般很小,例如对定值 \(i,j\) 和所有 \(k\) 做询问 \((i,j,k)\)
    5.2 手动爆搜“解”是什么,也就是先枚举“解”的第一步操作,然后找出第二步操作等等。
  6. 分组构造。对结构大小 \(x\) 的要求是某个变量乘以分数,暗示分组构造,或多种做法拼接平衡。分组方法一般是编号剩余系。
    6.1 将所有元素分为 \(k\) 组,保证至少有一组元素全选,或称成功(全部贡献给结构大小),如 T-135。
    6.2 将所有元素分成 \(k\) 组,每组至少让结构大小增加一,如 T-125-2(这种常常和归纳构造结合)。
  7. 能操作就操作。
  8. 限制的分步检测。
    8.1 先令解满足限制一,再调整使之满足限制二。
    8.2 只考虑在题面中确定的元素(题目信息)涉及到的限制,先不考虑只涉及“自定义元素”的。
  9. 建立交互决策树。
    暴力搜索建树。假设可能性集合为 \(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.1 用随机化方法找出交互的初始状态。
  2. 进制构造。
    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. 树上构造。
    3.1 删叶子归纳,如 T-134,T-175。关键在于三部分(这个过程也适用于一切扩展版广义串并联图方法):
    第一,快速找出一个叶子。
    第二,找出这个叶子的父亲(或者说,对于这个叶子,我们好奇的信息)。
    第三,删除一个叶子后,出现新的“叶子”,加入待考虑栈中。
    3.1.1 有时把某个 \(p_i\) 求出后,就把 \(i,p_i\) 合并起来,这就减小了规模。
    3.2 从上向下的构造。每次找出“没有父亲是关键点”的关键点并解决它的问题(考虑如下对称,剥叶子相当于找出“没有儿子是关键点”的关键点并解决)。
  4. 撒谎的交互库。若询问集合 \(S\) 且回答“是”,就让 \(U-S\) 的元素的计数器都 push_back 一;反之令 \(S\) 内的 push_back。如果某个元素计数器如何如何,那它一定不是答案,因为交互库不能连着撒两次谎之类的。实时维护所有 \(n\) 个计数器的情况。
  5. 随机化。
    5.1 未保证随机的数据,可以自行随机打乱,减少极端数据影响。例如交互题中,自行加上随机基底排列 \(s\),逐步求出 \(p'\),交互时调用 \(p=s_{p'}\)。第二个例子是有一些 0,1,每个都有权值,选一个子集使得 \(01\) 数量相同,最大化权值和。把元素随机打乱再 dp,记录当前 0 比 1 多几个,这一维可以只开到 \(O(\sqrt n)\)

结语

就像野外徒步,它无法告诉你,翻过眼前这座山唯一的正确路线。
它只能说,沿着山脊,不容易遭遇洪水。
荒野里走着的人,只得紧握双手;
堪比原始哲学,它如同石矛简陋。
我愿以哲学的角度,给简陋工具几个命名。

我们如何切割世界?元素就是对象。
我们如何理解事物间的联系?限制就是关系。
我们如何安排观察的节奏?轴就是顺序。
我们如何表征我们找到的答案?描述法就是语言。

正如组诗《太阳的姓》试图指出的那样,

凡人触碰太阳的姓
我们从未松开
长着名字的石矛
一代一代传

我们就像小孩子
刚睁开湿润的眼
大声念出
它的名字——
然后你看见一潮一汐的波澜

posted @ 2026-06-17 20:49  timefinder  阅读(12)  评论(0)    收藏  举报