动态规划题目选讲
\(\text{动态规划题目选讲}\)
\(\text{zph}\)
\(\text{长沙 2025.5.7}\)
based on 2025 年湖南省队集训《dp 专题》。
前 \(16\) 题是正式的讲课内容,本文会持续更新。
引入
动态规划(dp)是算法竞赛的一个热门考点。本文选取了约 \(10\) 道 dp 好题,旨在帮助大家掌握常见的 trick,同时启发大家思考,拓宽思维。
为了达到更好的学习效果,在此进行如下约定:
- 当你知道关于题目的信息后,题目难度和学习效果可能会降低,这一点相信大家深有体会。故本文中不保证题目按任何逻辑顺序排列。
- 每个题目的解法会分步给出,请各位阅读每一步后,在前文基础上进行至少 \(10\) 分钟的独立思考。
- 本文侧重点在于 dp 的状态设计和转移优化,具体的代码实现细节将略过。
方法论
- dp 状态的设计在于简洁。你需要对状态进行充分的描述(避免后效性),在此基础上,你应该用尽可能少的维度描述状态。为了减少维度,你需要对问题的性质进行挖掘,合并同类的状态,或剪去无用的状态。通俗的说,我们只关心我们需要关心的事物。
- 如何优化转移?一种方法是剪去无用的转移,另一种方法是将转移写成便于维护的形式,有时需要套用已有的模型求解。
举个例子,对于经典的 \(01\) 背包问题:
设计状态 \(dp(i,j)\) 表示,前 \(i\) 个物品用了 \(j\) 的体积所能达到的最大价值。状态设计的原因是,我们只关心用了多少的体积(即 \(j\))和最多装了多少价值(即 \(dp\) 值),这些体积具体装了啥,我们并不关心。
考虑经典的转移优化,我们发现 \(dp(i+1,j)\) 只依赖于 \(dp(i,0),dp(i,1),\cdots,dp(i,j)\),这是一个非常好看的形式,因为若在一轮转移中对 \(j\) 倒序处理,我们可以把整个数组压成一维。
扩展阅读资料
https://www.luogu.com/article/ki71nw88
https://www.cnblogs.com/Mackerel-Pike/p/16395436.html
目录
此处列出本文中的所有题目,各位可以提前思考。
- Heat Stroke (https://qoj.ac/problem/8811)
- 棋盘染色 (https://loj.ac/p/6385)
- 麻将*
- Paired Roads (https://codeforces.com/gym/104922/problem/I)
- Dance (https://qoj.ac/problem/6372)
- Sequence Transformation (https://codeforces.com/problemset/problem/280/E)
- Famous in Russia* (https://atcoder.jp/contests/xmascon20/tasks/xmascon20_f )
- Investors (https://qoj.ac/problem/5507)
- Complexity (https://atcoder.jp/contests/agc033/tasks/agc033_d)
- Array Covering (https://codeforces.com/problemset/problem/720/F)
- City United (https://qoj.ac/problem/7303)
- Desant 3 (https://loj.ac/p/4134)
- Broken Dream (https://acm.hdu.edu.cn/showproblem.php?pid=7374)
- Random IS (https://atcoder.jp/contests/arc108/tasks/arc108_e)
- 芒果冰加了空气 (https://qoj.ac/contest/1081/problem/5357)
- Zigzag (https://atcoder.jp/contests/agc017/tasks/agc017_f)
*:一些题目没有中文或英文题面,在此列出:
-
给一套有三种花色(万筒条,下文中记为
mps),每种花色有 \(n\) 种点数,每个点数 \(4\) 张,共 \(12n\) 张的麻将,给 \(13\) 张起手牌,剩下的 \(12n-13\) 张牌均匀打乱,即 \((12n-13)!\) 种可能等概率随机出现,问期望的最早胡牌巡目数,对 \(998244353\) 取模(不考虑七对),\(n \le 40\)。 -
你经营了一家会员制餐厅,今天有 \(N\) 个客户,每个客户想吃的菜需要 \(A_1,A_2,\cdots,A_N\) 的时间制作,每个客户吃菜的时间为 \(B_1,B_2,\cdots,B_N\)。
你的餐厅还蛮大的,所以多个客户可以同时吃菜。
对于序列 \(A,B\),和一个正整数 \(k(1 \le k \le N)\),定义 \(f(A,B,k)\) 为:以任意顺序服务任意 \(k\) 个客户,所需要的最少时间。需要的时间被定义为从开始制作第一个客户想吃的菜,到最后一个客户吃完,所经过的时间
给定 \(N,V,B\),对于所有 \(k\),求满足 \(1 \le A_i \le V\) 的总共 \(V^{N}\) 种 \(A\) 的 \(f(A,B,k)\) 之和,对 \(998244353\) 取模。
\(1 \le N \le 30,1 \le V \le 20, 1 \le B_i \le NV\)。
A. Heat Stroke
Step 1:
令 \(N,L\) 同阶。
对于道路 \(i\),考察这条道路上所有人的决策,令 < 表示往左走,> 表示往右走,x 表示离开,则决策一定形如:<<...<< >>...>> xx...xx 或者 >>...>> <<...<< xx...xx。
x 是一段后缀很好理解,不然会有一个 x 的时刻,左右至少有一个没满员,与题意矛盾。
<<...<< >>...>> 这种结构也很好理解,考虑 exchange arguments:如果有决策形如 <<><>,则交换第二个和第三个,变成 <<<>>,交换后你会发现左右两边被塞满的时刻不会变晚。
Step 2:
有了这个性质后,我们就可以 dp 了,令 \(dp(i,<,>,\text{x},0/1)\) 表示,考虑了前 \(i\) 条道路,第 \(i\) 条道路上 < > x 分别有对应的个数,< > 哪个在前面,且从前 \(i-1\) 条道路上飞走时左右两边均已塞满,从第 \(i\) 条道路上飞走时左边已塞满。
你可以理解为,在决策第 \(i\) 条道路时,需要满足第 \(i-1\) 条道路对右边的要求,和第 \(i\) 条道路对左边的要求。
进一步的,\(<,>,\text{x}\) 只需要记录其中之二,因为另一个能通过总数唯一确定。故状态数降到了 \(O(N^2)\)。
Step 3:
考虑转移,转移需要保证第 \(i-1\) 条道路的人飞走之前,右边会被塞满,所以新的 \(<\) 就被确定了,故只需要枚举一维即可转移(注意特判 \(\text{x}=0\) 的情况)。
这样就得到了时间复杂度 \(O(N^3)\) 的做法。
Step 4:
把这个做法实现出来,你会惊奇的发现转移是对一个二维数组的一条斜线取 max(如果选了 \(<,>,\text{x}\) 中不同的东西保留,这里可能会是横线或者竖线),仔细分析你写的每一句话,你还会发现左右端点都递增,可以单调队列维护。
复杂度 \(O(N^2)\)。
B. 棋盘染色
Step 1:
\(m\) 很小,考虑对 \(m\) 这一维状压。
一个思想是进行轮廓线 dp,也就是说你可以在一行中一个一个填,以替代枚举一行中总共 \(2^m\) 种情况。
Step 2:
你需要记录轮廓线上的如下信息:
- 每个位置是黑色和白色。
- 同色之间的联通情况(贝尔数)。
Step 3:
你可以采用如下剪枝策略:
- 一个显然的剪枝是钦定相邻两个同色位置在同一个连通块,加上后搜出来状态数只有 \(3 \times 10^4\) 左右。
- 还有一个剪枝就是如果存在 \((a,b,c,d)\) 满足 \(a \lt b \lt c \lt d\),\(a,c\) 同色不同块,\(b,d\) 同色不同块,也可以剪枝。
转移的时候,可以对于每一种轮廓线,预处理出在 \(m\) 个位置放哪种颜色会转移到哪种轮廓线,可以建出一个自动机,直接在这个自动机上进行 dp 就行,复杂度视搜出来的状态数而定。
注意特殊处理轮廓线上的拐点。
C. 麻将
Step 1:
先考虑如何判断是否胡牌。
不妨假设只有一种花色,令其为 m 。
令 \(dp(i,j,k,0/1)\) 表示,考虑前 \(i\) 种点数 1m 到 im,组成了 \(j\) 组 (i-1)m,im 的两面搭子(为了凑成 (i-1)m,im,(i+1)m),有 \(k\) 个 im 的孤张(为了凑成 im,(i+1)m,(i+2)m),是否有对子。在此情况下所组成的最大面子数。
Step 2:
转移枚举:新的点数是否自己组雀头,是否自己组成刻字,和前面连上的顺子数,和前面连上的两面搭子数,孤张张数。注意到 \(j,k \le 2\),因为三个同样的顺子可以组成三个刻子,是等价的。
对于多种花色,令 1p = (n+2)m,1s=(2n+3)m 即可(中间用一个空位隔开)。
Step 3:
对于上面的 dp 建立自动机,一个状态用 \(3 \times 3 \times 2 = 18\) 个数表示,一个状态有 \(5\) 个后继状态,对应后面加入的一张牌的张数 \(\in [0,4]\)。
进行一个基本的期望意义的转化:对于所有 \(i\),求按点数从小到大依次选 \(i\) 张胡不了的方案数 \(cnt(i)\),答案就是 \(\dfrac{\sum_{i} cnt(i)i!(12n-13-i)!}{(12n-13)!}\)。
Step 4:
令 \(f(x,len,u)\) 表示考虑点数 \([1,x]\),一共选了 \(len\) 张牌,走到自动机上的点 \(u\) 的方案数。对于 (n+1)m,(2n+2)m 需要特殊处理,钦定必须有 \(0\) 张即可。
复杂度 \(O(Kn^2)\)。\(K\) 是自动机点数,正常实现的话可能在 \(1000\) 左右,跑一个最小化自动机大概在 \(200\) 多。
注:如果你记不得如何写最小化自动机,可以尝试如下乱搞算法:
- 在自动机上直接删去起点到不了的状态,和到不了终点的状态(适用于 B 题)。
- 在自动机上暴力跑若干步,如果发现两个状态的所有长度的路径条数全部相同,可以合并(适用于 C 题)。
- 枚举任意两个状态,合并后进行对拍(适用于 C 题)。
第一条显然正确,第二条和第三条未经任何验证,只是一些非常符合直觉的东西。
D. Paired Roads
Step 1:
能感受到凸性。
感性理解:我们会优先选择权值大的东西。
Step 2:
考虑树上 dp,\(dp(u,0/1,0/1)\) 表示 \(u\) 的子树里面选,\(u\) 到 \(u\) 的儿子的边选了奇数还是偶数条(这对应了 \(u\) 到 \(u\) 父亲的边是否需要选),是否把 \(c_u\) 加到了代价里面。这个 dp 是线性的,套一层 wqs 二分问题就能解决。
Step 3:
构造方案比较麻烦,但是一个问题是凸的,大概率它的子问题也是凸的。
对于一个凸包,一条直线切上去一定会切一个区间。对每个 dp 状态记录最优值,和可能取到的最小的数量和最大的数量(记录双关键字)。
Step 4:
还原方案时,考虑一个一个把子树拆下去(dp 的逆过程),拆下去的部分有一个可以取到的区间,剩余的部分也有一个可以取到的区间,在里面任取两个数,只要加起来符合条件,就可以往下递归。
复杂度 \(O(n \log A)\)。
E. Dance
Step 1:
将所有点预先往左边平移 \(d\),则问题等价于,所有点留在原位或向右移动 \(2d\) 个长度,为方便起见下文中用 \(d\) 表示 \(2d\)。
考虑将 \(x\) 轴上的所有点排列成一个矩阵:
1 d+1 2d+1 ....
2 d+2 2d+2 ....
. . . ....
. . . ....
. . . ....
d 2d 3d ....
你能做的操作是,把一些点往右挪一个位置,每一行的末尾需要特殊处理。
Step 2:
观察到这个矩形一定有一维 \(\le O(\sqrt{n})\),可以分 \(d\) 小和 \(d\) 大讨论。
对于 \(d\) 小,可以设 \(dp(i,mask,0/1)\) 表示,当前在数轴上从左往后扫了前 \(i\) 个位置,前 \(i\) 个位置中的所有点均已被覆盖,\([i+1,i+d]\) 中 \(mask\) 的位置被移动后的点占据,最后一个 \(0/1\) 表示是否有一个未确定右端点的区间跨过 \(i\)。
换句话说,\(dp(*,*,0)+a\) 可以转移到 \(dp(*,*,1)\),表示新开一个区间;\(dp(i,*,1)+b\) 可以转移到 \(dp(i+1,*,1)\),表示将当前区间延长 \(1\);\(dp(*,*,1)\) 可以转移到 \(dp(*,*,0)\),表示结束一个区间,
Step 3:
对于 \(d\) 大,设 \(dp(x,y,mask,0/1)\) 表示,按 \(1,d+1,2d+1,...,2,d+2,2d+2,...\) 的顺序扫描到了 \((x,y)\),当前轮廓线上(第 \(x\) 行的前 \(y\) 个,和第 \(x-1\) 行中的第 \(y+1\) 列后面的)被区间覆盖的状态是 \(mask\),\((x,y)\) 上的点是否被移到了右边。
注意特殊处理每一行每一列的最后一个位置。
F. Sequence Transformation
Step 1:
回忆 Slope Trick,这个东西实际上是在维护实数轴上的分段函数。
Step 2:
令 \(f_i(x)\) 表示,填了 \([1,i]\),\(y_i\) 填的是 \(x\) 的最小代价,转移:\(f_i(x)=(x-x_i)^2+\min_{y \in [x-b,x-a]} f_{i-1}(y)\)。
\(f_1(x)\) 的图像很简单,就是抛物线 \(y=(x-x_1)^2\),\(f_2(x)\) 的图像可以看做将 \(f_1(x)\) 的图像平移,从最低点切开,将右边的部分再平移,中间用一个平台相连,然后整体加上 \((x-x_2)^2\)。
\(f_i(x)\) 的图像我们可以看做 \(O(i)\) 段开口向上的抛物线拼接成的下凸函数(可归纳证明),利用类似 slope trick 的方法分段维护转移,暴力的复杂度是 \(O(n^2)\),用平衡树可以做到 \(O(n \log n)\)。
G. Famous in Russia
Step 1:
先考虑选好 \(k\) 个人的情况:
假设有两个人,制作时间是 \(a_1,a_2\),吃饭时间是 \(b_1,b_2\),且 \(b_1 \lt b_2\)。如果先服务 \(1\) 再服务 \(2\),时间为 \(\max\{a_1+b_1,a_1+a_2+b_2\}\),如果先服务 \(2\) 再服务 \(1\),时间为 \(\max\{a_2+b_2,a_1+a_2+b_1\}\),由于 \(b_1 \lt b_2\),所以 \(a_1+a_2+b_1,a_2+b_2 \lt a_1+a_2+b_2\),所以先服务 \(2\) 更优,即按 \(b\) 降序排列的顺序服务是最优的。
Step 2:
反过来考虑这个过程,你先假设所有人吃完之后同时离店,也就是说,你有 \(k\) 条线段,一开始长这样:

你可以做的是,将一些红的段往右边挪,使得任意两个红段不交,你需要最小化移动后整个图形的宽度。贪心的,你肯定是先满足 \(1\),再满足 \(2\),然后满足 \(3\)。
形式化的刻画这个贪心:一开始有一个变量 \(t=0\),依次考虑 \(i=1,2,\cdots,k\),执行 \(t \leftarrow \max\{t,b_i\}\) 和 \(t \leftarrow t+a_i\)。
Step 3:
考虑还没确定具体是哪 \(k\) 个人的情况,依然可以贪心的考虑:
维护优先队列 \(Q\) 存储服务每个人所需的时间代价,显然小的元素在堆顶。另外维护变量 \(ans\) 表示答案,初始为 \(0\)。将时间轴在 \(b_1,b_2,\cdots,b_n\) 处分段,然后依次考虑 \(i=1,2,\cdots,n\):
- 若 \(ans+Q.top \le b_i\),因为在这一轮结束后 \(ans\) 一定会对 \(b_i\) 取 \(\max\),这个人不选白不选。将 \(ans\) 加上 \(Q.top\),然后移除堆顶。
- 否则,\(ans\) 加上 \(Q\) 里的任何元素都会 \(\gt b_i\),贪心的将 \(ans-b_i\) 这段时间花到 \(Q.top\) 上,将 \(Q.top\) 减去 \((ans-b_i)\),将 \(ans\) 改为 \(b_i\),然后把 \(a_i\) 加入 \(Q\),接着考虑 \(i+1\)。
- 在弹出 \(k\) 个数的那一刻,\(ans\) 的值就是选 \(k\) 个人的答案。
Step 4:
对这个贪心做 dp,我们惊奇的发现,只需要记录 \(dp(i,k,popmax,inqmin,ans)\) 表示前 \(i\) 个人,选了 \(k\) 个人,目前在 \(Q\) 里面的钦定被选上的最大值是 \(popmax\),钦定没被选上的最小值是 \(inqmin\),当前的答案是 \(ans\) 的方案数。转移是容易刻画的。
Step 5:
- \(ans'=\max\{ans,b_{i+1}\}\)
- 如果 \(ans \le b_{i+1}\),则说明该选的全部选了,\(popmax'=0\),否则 \(popmax'=\min\{popmax,ans-b_{i+1}\}\),这个 \(\min\) 的含义是,如果 \(Q\) 里只有 \(popmax\) 一个元素,则它会变为 \((ans-b_{i+1})\),否则不影响 \(popmax\) 的取值。
- \(inqmin'=inqmin-ans'+ans\)
- 枚举 \(a_{i+1}\) 的取值,根据和 \(popmax',inqmin'\) 的关系看能否转移到 \(dp(i+1,k,popmax',\min\{a_{i+1},inqmin'\},ans')\) 和 \(dp(i+1,k+1,\max\{popmax',a_{i+1}\},inqmin',ans'+a_{i+1})\)。
- 处理“弹出 \(k\) 个数的那一刻”,也是简单的,如果 \(popmax\) 存在(为了不数重)且 \(ans \le b_{i+1}\)(确实这一轮能把该选的都选了),就把 dp 状态上的值乘上 \(ans\) 再乘上 \(V^{n-i+1}\) 加到答案里。注意特判 \(i=n+1\)。
dp 的五维分别是 \(O(n),O(n),O(V),O(V),O(nV)\) 级别,转移复杂度是 \(O(V)\),故复杂度 \(O(n^3V^4)\),显然状态数不满,且转移常数不大,std 里只优化了取模意义下的加法,能在 \(1s\) 左右跑完。
H. Investors
Step 1:
容易发现题意等价于把整个序列分 \(k\) 段,最小化每一段的逆序对数之和。
Step 2:
直接应用决策单调性:考虑 \(a \lt b \lt c \lt d\),通过讨论逆序对的位置,计算对不等式两边的贡献,可以证明 \(w(a,c)+w(b,d) \le w(a,d)+w(b,c)\)。
I. Complexity
Step 1:
最朴素的 dp:\(dp(x_1,y_1,x_2,y_2)\) 表示左上角 \((x_1,y_1)\),右下角 \((x_2,y_2)\) 的子矩形的 complexity。
容易发现答案不超过 \(\log n\),每次劈成相等的两半就行。
Step 2:
考虑另一个 dp:\(dp(c,x_1,y_1,x_2)\),表示对于一个 complexity 不超过 \(c\),左上角为 \((x_1,y_1)\),右下角的 \(x\) 坐标为 \(x_2\) 的子矩形,右下角的 \(y\) 坐标最大是多少。
Step 3:
考虑转移,竖着劈是简单的,直接把 \(dp(c,x_1,dp(c,x_1,y_1,x_2)+1,x_2)\) 的值赋给 \(dp(c+1,x_1,y_1,x_2)\) 即可。
横着劈要枚举劈在什么位置,确定 \(c,x_1,y_1\) 后,这个决策点和 \(x_2\) 是有单调性的,感性理解就是,分界点需要保证两边比较“均衡”。
如果还不理解,那就闭上眼睛想(
复杂度 \(O(n^3 \log^2 n)\)。
J. Array Covering
Step 1:
感觉是上凸的,感性理解就是选择的区间权值不断递减,故考虑 wqs 二分,求出将整个序列覆盖的最大代价即可。
Step 2:
令 wqs 二分的附加权值为 \(d\),\(s_i\) 为前缀和。
先把所有贡献为正的区间选上,这些区间 \([l,r]\) 满足 \(s_r-s_{l-1}+d \gt 0\),可以用权值线段树求所有贡献和,并用线段树二分对每个 \(r\) 找到一个最小的 \(l\)。
Step 3:
有些位置没有被正区间覆盖,令这些位置为 \(p_1,p_2,\cdots,p_m\),令 \(dp(i)\) 表示覆盖前 \(i\) 个位置的最小代价。
一个观察是不存在两个负区间包含,原因显然,也就是将负区间按左端点从小到大排序,任意相邻两个区间中间没有空隙。
转移 \(dp(j)+\max_{k=p_i}^{p_{i+1}-1} s_k-\min_{l=p_{j-1}}^{p_j-1}s_l +d \rightarrow dp(i)\)。
将上下界适当放宽:\(dp(j)+\max_{k=p_i}^{n} s_k-\min_{0}^{p_j-1}s_l +d \rightarrow dp(i)\),容易发现不改变答案。
这个 \(j\) 一定取 \(dp(j)+\min_{l=0}^{p_j-1}s_l\) 最小的 \(j\),预处理前后缀 min 和 max 即可。
K. City United
Step 1:
求 \(\text{mod} \space 2\) 很奇怪,考虑先在模意义下进行同义替换。令 \(S\) 为点集的一个子集,\(f(S)\) 为 \(S\) 的导出子图的连通块个数。对于 \(f(S)=1\) 的 \(S\) 对答案有 \(1\) 的贡献,其余没有贡献,则答案可以写作 \(\dfrac{\sum 2^{f(S)} \bmod 4}{2}\)。
Step 2:
再对于新的式子找组合意义:\(2^{f(S)}\) 表示每个联通块黑白染色的方案数。
Step 3:
对应到原图上就是:用 \(0,1,2\) 三种颜色对顶点染色,其中 \(0\) 表示 \(S\) 中没有这个,\(1,2\) 对应一个联通块的颜色。
要求没有边连接 \(1,2\),可以用状压 dp 解决:\(dp(i,mask)\) 表示染了 \([1,i]\) 中的点,\([i-k+1,i]\) 中的点的染色状态是 \(S\),枚举下一个的颜色转移即可,实现的好可以做到 \(O(n3^k)\),其中 \(k=13\)。
L. Desant 3
Step 1:
本题的核心思想是”消去两个相同的状态“。
Step 2:
考虑对 \(m\) 个操作逐层搜索,一个状态记录一个三进制串,\(0/1\) 表示确定当前位是什么,\(2\) 表示还没确定(\(01\) 均可,你可以看做是一种压缩存储)。
Step 3:
依次考虑所有操作:
- 情况一:\(a_u=a_v=2\),你会发现,若 \(a_u \neq a_v\),则操作之后 \(a_u=0,a_v=1\),因为只需要求模 \(2\) 的结果,两个相同的串可以抵消,故只需要考虑 \(a_u=a_v\) 的两种情况。
- 情况二:\(a_u,a_v\) 恰好一个为 \(2\)。例如 \(a_u=1,a_v=2\),若 \(a_v=1\),则操作之后 \(a_u=1,a_v=1\);若 \(a_v=0\),则操作之后 \(a_u=0,a_v=1\)。故操作之后可以看成一个 \(a_u=2,a_v=1\) 的状态。另外三种情况同理。
- 情况三:\(a_u,a_v\) 都不为 \(2\)。直接做一遍这个操作就行。
由于每产生 \(2\) 个分支,\(2\) 的个数减少 \(2\),故复杂度是 \(O(2^{n/2}m)\)。
M. Broken Dream
Step 1:
按照 Kruskal 的过程,将边按 \(w_i\) 从小到大排序,令 \(f(i,S),g(i,S)\) 分别表示在加入前 \(i\) 条边的情况下,点集 \(S\) 是一个极大连通块的概率和期望。加入边 \((u,v,w,p)\) 的时候,枚举一个包含 \(u\) 但不包含 \(v\) 的集合 \(S\),和一个包含 \(v\) 但不包含 \(u\) 的集合 \(T\),转移到 \(S|T\)。
Step 2:
此时有一个问题,如果之前有一条边,一端在 \(S\) 里一端在 \(T\) 里,我们想让这条边不出现,所以需要乘上这些边的 \((1-p)\) 的积。如果能快速维护出每一对 \(S,T\) 之间的连边,问题就解决了。
Step 3:
考虑维护 \(E(R)\) 表示有一端在集合 \(R\) 里面的边的集合,我们需要支持快速查询 \(E(S)\&E(T)\) 里面所有边的 \((1-p)\) 的积。用四毛子,将边每 \(B\) 个分成一块,每一块预处理 \(2^B\) 种出现的可能带来的贡献,计算的时候把 \(S\) 中连出去的边集,and 上 \(T\) 中连出去的边集,对于每一块 \(O(1)\) 查一下表,算出乘积。
复杂度 \(O(3^nm^2/B+2^BB)\),取 \(B=m/3\) 可以通过。
N. Random IS
Step 1:
令 \(a_0=0,a_{N+1}=N+1\),假设第一步选择了 \(mid\),能观察到 \([0,mid]\) 和 \([mid,N+1]\) 两段是独立的。
Step 2:
令 \(dp(l,r)\) 表示区间 \([l,r]\) 的答案,转移 \(dp(l,r)=\frac{\sum_{k=l+1}^{r-1} [a_l \lt a_k \lt a_r](dp(l,k)+dp(k,r)-1)}{\sum_{k=l+1}^{r-1} [a_l \lt a_k \lt a_r]}\)。
转移是二维偏序的形式,对每个左端点和右端点开一个 BIT 维护,注意特判合法 \(k\) 数量为 \(0\) 的情况。
复杂度 \(O(n^2 \log n)\)。
O. 芒果冰加了空气
Step 1:
考虑将两颗点分树合并:我们会在两个根中选择一个根,并将剩下的那个放进当前根唯一对应子树,递归执行合并过程。
Step 2:
我们要做的实质上是,将两个树根之间的节点归并,剩下的部分都是确定的。
Step 3:
观察到转移系数只和树根在原树中的深度有关,令 \(dp(i,d)\) 表示,考虑 \(i\) 的子树,树根的深度为 \(d\)。转移类似树上背包合并,复杂度 \(O(n^2)\)。
P. Zigzag
Step 1:
考虑轮廓线 dp,令 \(dp(i,j,S)\) 表示,考虑到第 \(i\) 条路径的第 \(j\) 个位置,轮廓线的状态是 \(S\)。本题的关键在于如何优秀的刻画轮廓线 \(S\)。
Step 2:
不妨用 \(S\) 的前 \(j\) 位记录“新轮廓线”的状态,后 \(n-j\) 位记录“旧轮廓线”的状态。
Step 3:
转移的时候,唯一需要处理的细节是:当旧轮廓线往左,新轮廓线往右时,需要把旧轮廓线的第一个 \(1\) 变成 \(0\)(对应到原图上就是,往右走之后限制变松了,建议在纸上画画或者闭上眼睛想)。

浙公网安备 33010602011771号