DP Problems
考虑到 Topcoder AC 不方便,所以 Topcoder 的都没写()
1.CF1152F2 Neko Rules the Catniverse (Large Version)
题意: 给定 \(n,k,m\),求满足条件整数序列 \(a_1, a_2, a_3, \dots, a_k\) 个数:
-
\(1 \le a_i \le n\)。
-
\(\forall i \ge 2, a_{i} \le a_{i-1} + m\)
-
\(a_i\) 互不相同。
\(n \le 10^9, k \le 12, m \le 5\)。
思路:
我们发现,三个条件中最难的是 \(a_i\) 互不相同,这意味着我们如果按照 \(a_1, a_2, \dots, a_k\) 顺序 dp 将极其困难。
所以我们考虑按照值域的顺序来 dp,这里常见的计数就是插入的方法。
我们从大到小考虑,每次假设插入当前这个数,对于一个序列,我们可以插入在末尾或者某个数前面。
考虑到这个数是当前最小的,末尾肯定可以。
而如果在某个数后面就需要后面的数满足 $\le m + $ 当前这个数。
所以我们不妨设 \(f(i,j,S)\) 表示考虑到 \([i,n]\),已经有了 \(j\) 个数,并且 \([i + 1, i + m]\) 这个范围内的情况是 \(S\),每次转移乘上 \(\text{popcount(S)} + 1\) 即可。
然后发现这个可以用矩阵加速转移。就做完了。
2.GYM100377H Petya and arrays 加强版
题意: 求满足以下条件的 \(n\) 长度整数数组:
-
每个数都属于 \(\{1,2,3,\dots,P\} / \{A\}\)。
-
连续的子数组的和不是 \(P\) 的倍数。
对 \(10^9 + 7\) 取模。
\(n \le 85, A,P \le 10^9\)。
思路:
首先第二个性质的形式显然是将数组转化成前缀和。意味着不存在 \(s_i \equiv s_j \pmod P\)。
所以我们不妨将前缀和对 \(P\) 取模。现在就是不能有相等的。
同时我们还发现要求不能有 \(s_i + A \equiv s_{i+1} \pmod P\)。
我们不妨将所有的 \(i\) 向 \(i + A \pmod p\) 连边,这样会形成若干个环。
现在变成了我们需要按顺序在这些环上选取一些数,要求有相邻的不能相邻的选。
我们不妨对每个环 dp 算出在这个环选若干个组成序列的方案数。
考虑到 \(n\) 很小,我们还是可以以插入的形式,不妨设 \(f(i,j,S)\) 表示当前在环上按顺序考虑了 \(i\) 个点,已经选了 \(j\) 个,第一个元素和 \(i-1\) 的状态是 \(S\)。
转移很好转移,同样可以矩阵快速幂优化,算答案时看一下 \(S\) 即可。
考虑到所有环都是相同的,我们只用计算 \(n\) 个环的贡献即可。这里可以用背包,最后容斥和组合数算一下即可。
(不保证正确性)
3.Topcoder10741 Colorful Maze
题意:
有一个 \(n \times m\) 迷宫,包括障碍物和空格,其中空格都有一个颜色 \(0 \sim 6\)。
每种颜色有概率 \(a_i\) 是危险的。如果进入一个危险颜色的格子两次就会死亡。
现在给定起点和终点和地图,求最优策略下活着走到终点的最大概率。
\(n,m \le 50\)
思路:
显然最优策略不是固定的,于是我们考虑设计 dp 状态。
对于这种概率 dp,更好的方式是从起点往回推。
设 \(f(i,j,mask,D)\) 表示从 \((i,j)\) 出发,已经确认了 \(mask\) 是安全的,\(D\) 是危险的(如果没有则 \(D = 7\)),求走到终点的最大概率。
我们枚举走到的下一个格子转移即可。
但是问题在于如果我们走到一个 mask 中的格子就将陷入死循环。
为此我们需要将所有相同颜色的格子所称一个大点来 dp 即可。
4.P2150 [NOI2015] 寿司晚宴
题意:
选取两个集合 \(A,B\) 满足:
-
\(A,B \sube \{2,3,\dots,n\}\)。
-
不存在 \(x,y\) 使得 \(x \in A, y \in B\) 且 \(x\) 与 \(y\) 不互质。
\(n \le 500\)。
思路:
经典题。
我们考虑将 \(A,B\) 的质因子集合记作 \(P_A, P_B\),不难发现 \(P_A \cap P_B = \empty\)。
但是质数的个数太多了,不好 dp。
我们发现,如果将质数按照 \(\le \sqrt n\) 和 \(> \sqrt n\) 分类,则每个数至多含有一个二类质数!
而枚举一下发现一类质数个数最多 \(8\) 个。
所以我们可以把每个数划到其二类质数的那一类,现在问题转化为:每一类只能一个集合选,且一类质数不冲突。
于是我们对一类质数状压即可。时间复杂度 \(O(n2^{16})\)。
5.UOJ86 mx的组合数
题意:
对于 \(i = 0, 1, \dots, p-1\),分别求 \(l \le x \le r\) 且 \(\binom{x}{n} \equiv i \pmod p\) 的个数,对 \(998244353\) 取模。
\(p \le 30000, l,r,n \le 10^{30}\)。
思路:
Lucas 定理非常厉害的应用。对于模数很小的情况 Lucas 定理会很有用。
我们考虑 Lucas 定理的等价形式:不妨设 \(x = (x_1x_2\dots x_m)_p,n = (n_1n_2\dots n_m)_p\),则:
然后明显这道题就变成了一个数位 dp 题。
我们不妨设 \(f(i,j,0/1)\) 表示当前考虑了前 \(i\) 位,组合数乘起来模 \(p\) 是 \(j\),是否贴着上界。
转移需要枚举下一位的所有选择,时间复杂度为 \(O(p^2 \log n)\)。但是显然是过不去的。
我们发现如果我们把乘法转换成原根的加法,这将是一个卷积的形式,并且模数 \(998244353\) 支持 NTT,所以用 NTT 优化即可做到 \(O(p \log p \log n)\) 了。
但是我们无法计算 0 的方案数,但是我们也只无法计算 0 的方案数,最后用总数减去即可。
注意差分的时候如果 \(l - 1 < n\) 也要减去 0 的贡献。
6.POJ1090 Chain
题意:
现在有一个长为 \(n\) 的 01 串,每次可以执行下面两种操作之一:
-
找到最右边的 1 并反转其左边的元素。
-
反转最后一位。
求最少几次变成 0。
思路:
首先我们需要观察:连续两次同种操作会抵消,所以操作一定是交替进行的。
所以只有两种可能,并且我们发现每种操作都会减少或增加一个 1,所以这其实就是格雷码!
我们知道,第 \(n\) 个的格雷码是 \(n \oplus [\frac{n}{2}]\),所以我们可以倒退出其是第几个格雷码。
注意到标准格雷码是一个环,有两种方法到起点,都要考虑。
7.Topcoder13457 BoardFolding
题意:
有一个 \(n \times m\) 的 01 矩阵,可以横着或竖着对折,要求重叠部分完全一样,求最后能折成的最小面积。
\(n,m \le 250\)(实际可以做到 \(O(nm)\))
思路:
一个很自然的想法是 \(f(a,b,c,d)\) 表示 \([a,b] \times [c,d]\) 这个矩形是否可行。
转移枚举一下对折线即可,时间复杂度 \(O(n^2m^2(n+m))\)。
我们考虑优化,通过观察,我们发现一个事情:横着对折和竖着对折互不影响。
所以我们可以分成 \(f(a,b)\) 和 \(f(c,d)\),表示只考虑一种方向的对折是否可行,最后乘起来即可。
进一步,我们可以直接变成 \(f(a)\),\(f(b)\) 表示 \([1,a]\) 是否可行和 \([b,n]\) 是否可行,然后乘起来。
用 Manacher 来转移,时间复杂度 \(O(nm)\)。
8.CF111C Petya and Spiders
题意:
有一个 \(n \times m\) 的矩阵,每个格子有一只蜘蛛,每只蜘蛛可以往上下左右移动一格或者不移动,求最多空出多少个格子。
\(n\times m\le 40\)。
思路:
根号分治得到 \(\min\{n,m\}\le 6\),所以我们可以状压 dp。
设 \(f(i,j,k)\) 表示到第 \(i\) 行的都确定了,当前第 \(i\) 行和第 \(i+1\) 行的状态是 \(j,k\)。
如果直接枚举第 \(i+1\) 行的话需要枚举 \((x,y,z)\),转移如下:
但是时间复杂度是 \(O(n2^{30})\),显然不行。
发现 \(x\) 其实不会关心转移,所以我们对 \((j,y,z)\) 记录最小的 \(x\) 即可。
时间复杂度 \(O(n2^{24})\)。
9.[AGC012E] Camel and Oases
题意:
直线上有 \(n\) 个点 \(x_1 \sim x_n\) 和一个数字 \(V\),两个点可达当且仅当其距离小于等于 \(V\)。
从一个点出发,可以到达其所有直接或间接可达的点,或者跳跃到任何一个点,但是这样的话就会使得 \(V \to [\frac{V}{2}]\)。
求每个点出发能否访问所有点。
\(n,V \le 2 \times 10^5\)。
思路:
显然 \(V\) 的取值总共有 \(k = [\log_2V]\) 个,我们就是要把这些点分成 \(V\) 组,使得每组互相可达的最小的 \(V\) 不超过分配给这个组的长度。
所以我们考虑状压 dp,不妨设 \(f(S)\) 表示如果用 \(S\) 中的长度,最长能覆盖的前缀。\(g(S)\) 则表示后缀。
我们通过枚举下一个长度和预处理可以计算出这两个值。
然后我们发现对于一个点,假设其最近不能覆盖到的是 \(l \sim r\),则我们需要找到 \(S\) 满足 \(f(S) \ge l, g(S) \le r\),这显然是充分的。
于是二维数点即可。
时间复杂度 \(O(n \log n)\)。
10.CF678E Another Sith Tournament
题意:
\(n\) 个人决斗,你知道每两个人之间彼此获胜的概率,你先选两个人决斗,赢的人继续和你再次选的人决斗(你可以根据上次结果来选),求最后 1 号获胜的最大概率。
\(n \le 18\)。
思路:
首先这是一道概率问题,我们发现,如果我们知道了当前的人是 \(i\),现在还未决斗的人的集合是 \(S\),那么我们应该有一个最优策略是的我们会选一个 \(j\) 与 \(i\) 决斗。
所以我们不妨设 \(g(i,S)\) 表示最大的概率,那么我们现在要做的就是选一个 \(j\),使得决斗后的概率最大。
明显有 \(g(i,S) = \max_{j}\{g(i, S - \{j\}), g(j, S - \{i\})\}\),则我们最后只用看最开始选哪个人即可。
时间复杂度是 \(O(n^22^n)\),关键在于什么状态会确定最优策略。
11.CF1342F Make It Ascending
题意:
将 \([n]\) 划分成 \(S_1, S_2, \dots, S_k\),设 \(f(S) = \sum_{i \in S}a_i\),要求 \(f(S_1) < f(S_2) < \dots < f(S_k)\) 且存在 \(i_j \in S_j\) 使得 \(i_1 < i_2 < \dots < i_k\)。
\(n \le 15\)。
思路:
显然是用状压 dp,我们思考一下哪些东西会影响状态:当前已经选了的元素,总共选了多少个集合,上一个的代表,上一个集合的权值。
只要知道这些就能转移,但是显然不能都存下来,我们发现题目特征是单调递增的序列,这让我们想到 LIS 的二分算法,所以我们可以记 \(f(i,j,S)\) 表示选了 \(i\) 个集合,上一个代表是 \(j\),用了 \(S\) 中的元素,上一个集合权值和最小是多少。
我们发现转移只用枚举下一个集合是什么即可。注意需要取前缀 min 来优化,时间复杂度 \(O(n^23^n)\)。
但是这道题比较卡常,需要用刷表法,当遇到不可行的状态时就不去转移。
12.P2466 [SDOI2008] Sue 的小球
题意:
一个平面上有若干个小球从开始时刻以固定的速度开始下落,给定起点,从起点出发在 \(x\) 轴上移动,移动 1 格花费 \(1\) 秒。
每次第一次到达某个横坐标时,就能获得现在这个横坐标的所有小球的纵坐标的值的和,求收集完所有小球后贡献最大是多少。
\(n \le 1000\)。
思路:
首先,这道题最开始需要观察,我们发现,如果从起点出发,向左走了一段距离后向右走,那么我们肯定要走到越过原来起点的位置,这样才会有新的收益。所以我们就能大概知道答案的样子。
但是这个计算方式也需要转化一下,我们发现,当我们走了 \(1\) 秒后,我们对答案的贡献其实是当前没有走到过的小球的速度和。
所以任何时刻收集的小球都是区间,我们考虑区间 DP,设 \(f(l,r,0/1)\) 表示走完 \([l,r]\) 区间后,最后一次是 \(l \to r\) 或者 \(r \to l\)。
如果直接枚举上一次不同方向的话就是 \(O(n^3)\) 的了,过不去。我们发现其实 \(f(l,r,0)\) 可以从 \(f(l,r - 1,0)\) 和 \(f(l,r-1,1)\) 转移就够了,这样就能涵盖所有的情况。
所以我们便得到了 \(O(n^2)\) 的做法。
13.CODE FESTIVAL 2017 qual B D-101 to 010
题意:
有一个 \(01\) 序列,每次可以将一个 \(101\) 变成 \(010\),求最多能操作几次。
\(n \le 5 \times 10^5\)。
思路:
题解直接线性 dp 什么神仙。感觉我的做法更平易近人一点。
我们首先还是观察,我们发现其实我们可以将整个段按照中间隔了超过 1 个 0 的分成若干段,每一段都是 \(1\dots101\dots101\dots101\dots1\) 这样的。
我们先对于两个 0 的 \(1011\dots1101\),我们发现让其中的一个 101 占用中间的所有 1 与两个都占一点是一样的。
所以我们可以根据这个对于每一段来 dp,设 \(f(i,0/1/2)\) 表示第 \(i\) 个 \(0\) 占用左边的 1/右边的 1/不用。
转移非常简单,于是我们直接分段也是在 \(O(n)\) 内解决这个问题。
14.ZOJ1234-Chopsticks
题意:
将 \(n\) 个数分成 \(K\) 个三元组 \(a \le b \le c\),每个三元组权值是 \((a-b)^2\),求最小权值和。
\(n \le 5000\)。
思路:
首先不难猜测最优解的所有 \(a,b\) 都是相邻的,否则我们可以通过调整使得最优解不优。
所以我们就可以考虑 \(dp\),倒序排序,设 \(f(i,j)\) 表示前 \(i\) 个选了 \(j\) 个的最小代价,如果 \(3j \le i\),就用 \(a=i,b=i-1\) 转移即可。
注意这道题卡空间,需要滚动数组。
15.[ARC078F] Mole and Abandoned Mine
题意:
给定一张无向连通图,边有边权,求最少割掉的边权总和,使得 \(1\) 到 \(n\) 只有唯一一条简单路径。
\(n \le 15\)。
思路:
\(O(n^23^n)\) 的更劣做法。
我们首先需要观察最终剩下的样子,不难发现其实是将 \([n]\) 划分成若干个集合,路径上每一个点都在一个集合中,集合之间除了路径上的边没有其他边。
这就很好 dp 了,\(f(i,S)\) 表示现在路径上最后一个点是 \(i\),已经用了 \(S\) 中的点。
我们只需要枚举下一个路径上的店和其对应集合即可。需要预处理两个点集的所有边权,这个可以 \(O(n^23^n)\) 暴力处理,但是更好的方法是算出一个点集内部的边权然后容斥,这样变成 \(O(n^22^n)\)。
于是总的复杂度是 \(O(n^23^n)\),可以用链表储存每个集合的元素,这样可以减少常数。
但是我们其实可以将选点和选集和分开,这样就是 \(O(n3^n)\) 的了。
16.CF55D Beautiful numbers
题意:
求 \([l,r]\) 中所有被其每个位置上的非零数整除的数的个数。
\(l,r \le 9 \times 10^{18}, T \le 10\)。
思路:
最朴素的想法是记录当前存在的数字集合与 \(2 \sim 9\) 每个数字的余数,但是显然过不去。
首先,我们发现,只用知道 \(5,7,8,9\) 的余数就可以推出所有数是否整除,所以我们只用记录这四个书的余数。
其次,我们发现,我们其实只需要记录当前数字集合中 \(2,3,5,7\) 的最高次幂即可,所以我们总的状态数是 \(18 \times 2520 \times 48 = 2 \times 10^6\),完全可做。
注意到是多测,所以我们记忆化搜索时不记录 lim 的记忆化。
17.Topcoder 修正表达式
题意:
现在有三个正整数 \(a,b,c\),可以执行一下操作任意多次:
-
将 \(0 \sim 9\) 中的一个插入到某个数的某个位置,单次花费 \(I\) 元。
-
将某个数的某个位置修改,单次花费 \(R\) 元。
-
删除某个数的某个位置,单次花费 \(D\) 元。
现在给定 \(a,b,c,I,D,R\),求使得 \(a+b=c\) 的最少花费,要求任意时刻都没有前导 \(0\)。
思路:
我们考虑数位 dp,设 \(f(z,carry,A,B,C,S,lim)\):
\(z = 0/1/2\) 表示当前处理哪个数。
\(carry\) 表示当前的 \(c - a - b\) 也就是进位。
\(A,B,C\) 分别表示当前应该处理到这三个数的哪一位。
\(S = 0 \sim 7\) 表示当前三个数是否有前导 0。
\(lim\) 表示当前执行的插入次数,以此来断绝后效性。
我们从 \(z = 2\) 开始,然后 \(z=1\),然后 \(z=0\) 轮流进行。
首先考虑终止状态,如果 \(z=2\) (注意这个一定要有,否则有可能出现 \(100,100,10000\) 这种最后一个 \(0\) 被忽略的情况)并且 \(A=B=C=0\) 并且 \(carry=0\) 的话就是一个可能的终止状态。
否则,我们考虑转移。
首先,如果我们保留不变,则当前的 \(z\) 这个数处理减去 1,并且需要更新前导 \(0\) 和进位,同时要确保这个数不是前导 \(0\)。
如果我们插入,我们枚举插入的这个数,依然要求没有前导 \(0\),同时更新 \(lim\),
如果是修改,我们枚举修改成什么,然后更新即可。
如果是删除,需要注意,对于 \(a,b\),我们可以直接 \(A-1\) 或 \(B-1\) 进行删除。但是假设当前我们还是前导 \(0\),我们也可以不放数,不进行任何操作,转移到下一个数即可。
最后对于 \(c\) 的情况,如果存在某个时刻 \(C=0,A \ge 0, B \ge 0\),则我们需要处理看看如果当前把 \(A,B\) 删了也是一种情况。
总之转移就是这些,代码用记忆化搜索还是相对比较好实现的。
18.CF1334F Strange Function
题意:
定义 \(f(a)\) 表示 \(a\) 的前缀最大值构成的数组,给定 \(a,b\),以及每个 \(a\) 中的数删除的代价,求最小代价使得 \(f(a) = b\)。
\(n \le 5 \times 10^5\)。
思路:
先考虑朴素的 dp,设 \(f(i)\) 表示 \(1 \sim i\) 的最小代价,\(b\) 中位置 \(t\) 可以唯一确定。则:
我们考虑维护所有下标并 lazy 更新。
我们发现 \(i \to i + 1\) 时,如果 \(p_i > 0\),则比 \(a_i\) 大的都会更新,而本身还要考虑被 \(i\) 更新。
需要后缀加,单点查询,单点修改,用 BIT 维护即可。