CF DP 2300~3000 板刷
Astatinear
·
2024-11-17 15:50:37
·
个人记录
我把荆棘当作铺满鲜花的原野,人间便没有什么能将我折磨。
他们说,击败我即是终结,而我必将归来将之改写。
1. CF1421E Swedish Heros
\color{green}\text{Difficulty : 2700}
首先有一个 \mathcal{O}(n^3) 的区间 DP,类似二叉树的状态转移。
考虑通过对小数据打表找规律。
主要是去观察序列上哪些点最终符号为 + ,哪些点符号为 - 。
惊讶的发现 num_{+}+n\equiv 2\pmod 3 ,且符号序列中必然有相邻的两个符号相同。
直接暴力 DP,dp_{i,(0/1/2),(0/1),(0/1)} 表示前 i 个数,+ 号模 3 的个数,当前位置的符号是 -/+ ,是否已存在相邻两个符号相同的情况。
暴力转移,复杂度 \mathcal{O}(n) 。
\texttt{Code}
2. CF1174E Ehab and the Expected GCD Problem
\color{green}\text{Difficulty : 2500}
首先考虑最优排列 p ,即满足 f(p)=f_{\max }(n) 的那些排列,有什么性质。
定义 n 在二进制的最高位为 k 。则 p_1 只有可能等于 2^k 或者 2^{k-1}\times3 且 2^{k-1}\times 3\le n 。(显然不可能是 2^{k-1}\times 5 因为 2^{k+1}<2^{k-1}\times 5 )
考虑将 p_1=2^k,p_1=2^{k-1}\times 3 分开计数。下面以 p_1=2^k 为例:
定义 dp_{i,j} 表示前 i 个数的最大公因数为 2^j 的方案数。下面考虑转移。
如果 i-1 位的最大公因数就已经是 2^j ,那么转移就是 dp_{i,j}=dp_{i-1,j}\times (\lfloor\frac{n}{2^j}\rfloor-i+1) 。比较好理解,因为这一位只需要是 2^j 的倍数且与前面不重即可。
如果 i-1 位的最大公因数是 2^{j+1} ,到了这一位才衰减为 2^j ,这一位显然是 2^j 的倍数,却不是 2^{j+1} 的倍数,转移即 dp_{i,j}=dp_{i-1,j+1}\times (\lfloor\frac{n}{2^j}\rfloor-\lfloor\frac{n}{2^{j+1}}\rfloor)
初始值即 dp_{1,k}=1 ,答案就是 dp_{n,0} ,暴力转移即可,时空复杂度均为 \mathcal{O}(n\log n) 。
对于 p_1=2^{k-1}\times 3 的情况,多记一维 0/1 表示 3 的幂次,同上进行转移,不过需要多讨论一个从 2^j\times 3\to 2^j 这种衰减情况,应该是比较容易的。
\texttt{Code}
3. CF115D Unambiguous Arithmetic Expression
\color{green}\text{Difficulty : 2600}
我是一个拼命优化,交了 60+ 发准备 \mathcal{O}(n^3) 美梦过 2000 的巨型 SB,大家不要学习我。
鲜花
当然,这个暴力 \mathcal{O}(n^3) 的区间 DP 应该是比较容易的,这里就不细讲了,讲一讲 \mathcal{O}(n^2) 怎么去做。
一段数字显然可以看成一个数字,缩下来之后,考虑一下左右括号的组成。
首先,右括号必然出现在数字后面,显然你符号后面是不可能出现右括号的。并且数字后面的右括号可以是任意个,只要和前面的左括号能匹配上。
其次,左括号也必然不会出现在数字后面,你总不可能数字和符号/数字之间凭空出现一个左括号。故左括号必然出现在符号后面,且每个符号后面应该必须出现刚好一个左括号,不多不少。
我们按照上面两种限制去填左右括号,且只需要满足最终填出来的括号刚好匹配,然后再算方案书貌似就是答案。
但是我们忽略了一种情况,就是 *,/ 前面不能出现非数字,即 *,/ 前面不能出现左括号,而在上述的两个情况中我们并没有考虑到,所以需要特判掉原数组中 *,/ 之前出现了非数字的情况,直接输出 0 。
特判掉之后,根据上面的两条限制,我们直接 DP,定义 dp_{i,j} 表示前 i 位,填的若干个括号,匹配掉能匹配的括号之后,不存在失配的右括号,且左括号还剩下 j 个。转移比较简单:
初始值 dp_{0,0}=1 ,答案就是 dp_{n,0} 。
\texttt{Code}
4. CF1481E Sorting Books
\color{green}\text{Difficulty : 2500}
以前应该是做过的,但是我为啥没有提交记录,那就再来自己做一遍。
又要看题解了,真的要哭了啊,为什么会想到去转化它呢。。。
一个很重要的转化是将最小变化数 转化为 n 减去最大不变数 。这也同样启示我们一定要学会向题目的反方向考虑。
定义 dp_i 表示 i\to n 中间这一段变为整齐的可以达到的最大不变数是多少,考虑转移(为什么要反过来定义,因为你是移到最后一位,当然更重要的是里面的转移促使你必须这么定义):
如果我们选择改变第 i 位的位置,有: dp_i=dp_{i+1} 。
如果我们选择不改变第 i 位的位置,那么显然我们也不会改变所有和 i 颜色相同的点的位置,有 dp_i=\sum_{j=i}^n[a_j=a_i]
不改变第 i 位的情况,其实在这种情况下,我们忽略了另外一种转移。你虽然不改变所有和 i 不同的颜色,但他并不意味着其他颜色必须要改,但是第二种转移并没要考虑到。假设所有和 i 颜色相同的位置,最左边的位置是 l ,最右边的位置是 r 。发现对于 [l,r] 内的除开和 i 相同的所有位置,在和 i 颜色相同的位置都不改变的情况下,这些位置都是要改变的。但是对于 [1,l-1],[r+1,n] 这里面的位置,其实他们改不改对我们来说并没有所谓。故在 i=l 的情况下,有 dp_i=dp_{r+1}+cnt ,其中 cnt 表示和 i 相同的颜色的所有出现次数。
将三种转移取 \max 即可。
答案即 dp_1 。
边转移边打桶,时间复杂度应该是 \mathcal{O}(n) 。
\texttt{Code}
5. CF316D3 PE Lesson
\color{green}\text{Difficulty : 2400}
首先有一个比较重要的东西,就是说,我们只关心 a_i=1 和 a_i=2 的点的个数,至于是什么顺序对答案是没有影响的,因为你可以选择任意两个交换。
说实话,最开始真没想到这个东东。
考虑 \forall i\in[1,n],a_i=1 怎么做。
定义 dp_i 表示有 i 个 1 的方案数是多少,显然有 dp_i=dp_{i-1}+dp_{i-2}\times (i-1) ,分别表示 i 与自己交换和 i 找到剩下 i-1 中的一个交换的方案数。
然后考虑 \exist a_i=2 的时候怎么做。
假设最终的排列第 i 个是原数组的 p_i 。考虑将 i\to p_i 连一条有向边,那么原图就会构成若干个环,且每个环上 a_i=1 的点最多只有两个。
假设 a_i=1 的点有 \text{cnt1} 个,a_i=2 的点有 \text{cnt2} 个,考虑先求出 dp_{\text{cnt1}} ,然后在这些 a_i=1 的中间插入 a_i=2 的点。
先考虑插入一个 a_i=2 怎么插入,发现直接在 dp 中的若干个环上的任意一条边上断开,然后插入是一种方案,自己连向自己也是一种方案,此时方案数就是 dp_{\text{cnt1}}\times (cnt1+1) 。
那么对于 \text{cnt2} 个 a_i=2 ,方案数就是 dp_\text{cnt1}\times\text{cnt1}\times(\text{cnt1}+1)\times....\times (\text{cnt1}+\text{cnt2}=n)
总时空复杂度 \mathcal{O}(n) ,然后就做完了。
\texttt{Code}
6. CF1430G Yet Another DAG Problem
\color{green}\text{Difficulty : 2600}
这个东西有点像分层图,每次将一个集合的 a 定义为当前的 \max+1 且保证合法。
具体来说,定义 in_y=\sum_{(x,y,w)}w,out_x=\sum_{(x,y,w)}w 。
那么答案就是 \sum_{i=1}^n a_i\times (out_i-in_i) 。
考虑定义 dp_{i,j} 表示现在已经钦定出了 i 子集内所有节点的 a_i ,最大值为 j ,所可以得到的当前最大答案是多少。
考虑枚举 i 的子集 k ,只有满足 \forall(x,y,w)\in E,y\in k ,有 x\notin i ,才有转移 dp_{i,j}=\min\{dp_{i-k,j-1}+j\times (\sum_{l\in i}out_l-in_l))\}
应该是比较好理解的,输出方案直接打个 \text{pre} 记录前驱最优状态即可。
此时稍微预处理一些东西,时空复杂度 \mathcal{O}(n\times 3^n) ,依然不太可过。
可以考虑去把 j 这一维省掉,每次转移的时候写成 \sum_{i=1}^n(out_i-in_i)-\sum_{l\in i}(out_l-in_l) ,应该就可以直接省掉这一维。
此时时间复杂度 \mathcal{O}(3^n) ,可以接受,因为枚举子集并不会枚举满,实际跑了 \text{1999ms} 非常极限。
但是疑似是我写的太劣了,貌似同样的解法就我这么极限。
\texttt{Code}
7. CF232E Quick Tortoise
\color{green}\text{Difficulty : 3000}
首先这个 q 非常的大,所以我们需要考虑离线下来。
最开始感觉就是排序之后将 x_1 从下到上指针移上去,这样只需要 DP 转移记录一行的到达性,使用 \text{bitset} 优化,空间复杂度 \mathcal{O}(\frac{n^3}{8}) ,时间复杂度 \mathcal{O}(\frac{n^4}{32}) ,看上去已经开的下了,但是时间复杂度仍然非常难受。
考虑离线利器 \text{CDQ} 分治。
考虑按照 x 坐标进行分治,假设当前分治区间为 [l,r] ,且有 \text{mid}=\lfloor\frac{l+r}{2}\rfloor 。
对于 x_1\le \text{mid},x_2\ge \text{mid} 的点对,他们为了到达 (x_2,y_2) 必然会经过 \text{mid} 这一行。
考虑定义 dp_{i,j,k} 表示 (i,j) 能否到达 (\text{mid},k) ,定义 f_{i,j,k} 表示 (\text{mid},k) 能否到达 (i,j) 。
每层分治暴力转移,并且采用 \text{bitset} 优化时间复杂度应该是 \mathcal{O}(\frac{n\times m^2\times\log n}{32}) 。
对于满足 x_1\le \text{mid},x_2\ge \text{mid} 的点对 (x_1,y_1,x_2,y_2) ,能到达的条件就是 \exist k\in [1,m],dp_{x_1,y_2,k}=\text{true},f_{x_2,y_2,k}=\text{true} 。
对于剩下的点对,如果 x_2<\text{mid} 那就进入 [l,\text{mid}] 这个递归区间,否则进入 [\text{mid}+1,r] 这个递归区间。
然后就结束啦,时间复杂度瓶颈在于 DP 转移的 \mathcal{O}(\frac{n\times m^2\times\log n}{32}) ,求答案的时间复杂度是 \mathcal{O}(\frac{n\times (m+q)}{32}) ,空间复杂度 \mathcal{O}(\frac{n\times m^2}{8}) ,都是可以接受的。
感觉难点不在 DP 上,而是你对于离线的处理。
\texttt{Code}
8. CF762D Maximum path
\color{green}\text{Difficulty : 2300}
起初是没什么思路的,但是我们回忆一下序列 DP 的常见类型,发现这个题影响序列从左向右进行 DP 的罪魁祸首是向左走这种情况。
但是向左走总感觉是在远离目标点,那能不能有些限制呢?
手玩一些小数据发现,每种路径总存在一种方案,只会最多连续向左走 1 步。(最开始不敢乱想,直接猜的是最多 3 步,但是其实都能做,无伤大雅。)
定义 dp_{i,1/2/3} 表示走到 (1/2/3,i) 的路径最大权值和是多少。考虑转移:
如果 i 并不会想左走到 i-1 ,那么就是从 dp_{i-1,1/2/3} 直接走过来找最大值即可。
如果 i 会向左走到 i-1 ,那么就是 dp_{i-2,1/2/3} 走过来找最大值即可。
注意一下里面两种转移的走法和细节即可。
\texttt{Code}
9. CF1784D Wooden Spoon
\color{green}\text{Difficulty : 2400}
感觉题目中的组合意义奇怪转移跟今天的考试非常像,正好我提前一天看了这道题,赢!
首先有两种考虑方法:
考虑自底向上,需要记录一直连败的叶子节点编号,以及当前的赢家和轮次,时空复杂度至少在 \mathcal{O}(2^n\times n^2) ,不太能接受。
考虑自顶向下,只需要记录当前的赢家,且钦定他在从上到下的过程中一直失败即可,时空复杂度至少是 \mathcal{O}(2^n\times n) 。
观察到只有“自顶向下”对我们来说才是有前途的。为了方便叙述,我们将题目中的编号小赢直接钦定为编号大赢,然后输出反过来就行了。
故我们定义 dp_{i,j} 表示从上至下第 i 层,当前的赢家是 j 且其输掉了前面的 i-1 场比赛的方案数,考虑怎么转移:
考虑顺推,即 dp_{i,j}\to dp_{i+1,k},k< j ,想一下系数是多少。发现实际上在 i+1 层 j 子树无论是什么方案都能赢,可以随便排列,实际上就是 dp_{i+1,k}=\sum_{j=k+1}^{2^n}dp_{i,j}\times C_{j-2^{n-i-1}-1}^{2^{n-i-1}-1}\times (2^{n-i-1})!\times 2 ,其中 C_{j-2^{n-i-1}-1}^{2^{n-i-1}-1}\times (2^{n-i-1})!\times 2 表示找出所有的 2^{n-i-1}-1 个放在 j 子树下的编号,将其任意排列,并且由于 j,k 可以交换,还要 \times 2 。
发现转移系数与 k 无关,直接前缀和优化即可。
总时间复杂度 \mathcal{O}(2^n\times n) 。
对于组合数系数的相关疑惑和相关代码可以参考这篇题解。
10. CF1279F New Year and Handle Change
\color{green}\text{Difficulty : 2800}
草了,找半天给我找来一道限制为最多 k 个的题。
首先如果没有这个限制,显然可以直接暴力 DP,从 [i-1,i-k-1] 中间转移过来。
要去掉这个限制显然是一眼 \text{wqs} 二分。
假设 f_x 表示最多 x 个的最优答案。发现 (x,f_x) 是一个下凸壳,直接套 \text{wqs} 二分硬做,二分里面暴力 \mathcal{O}(n) DP 就可以了。
时间复杂度 \mathcal{O}(n\log n) 。
我不会 \text{wqs} 二分的写法,我只是概念神,所以咕咕咕了。
11. CF1144G Two Merged Sequences
\color{green}\text{Difficulty : 2400}
神 TM \text{Div 3} 的 G 题评分 2400 。
完啦,被 \text{Div 3 G} 创死了。不是,LIS 这么经典的状态我为什么没想到啊。
首先有一个比较显然的 \mathcal{O}(n^2) 的可行性 DP,即 dp_{i,j,0/1} 表示将前 i 个数分成 LIS 和 LDS 的 (0:LIS,1:LDS) 最后一位是 i ,(0:LDS,1:LIS) 的最后一位是 j 。转移应该是一个比较简单的 \mathcal{O}(1) 转移,这里就不细写了。
发现这个可行性 DP 状态非常的冗余,考虑模仿最长上升子序列单 \log 的做法,将状态定义为最后一位的最值。
具体来说,定义 dp_{i,0/1} 表示将前 i 个数分成 LIS 和 LDS,i 作为 (0:LIS,1:LDS) 的最后一位,(0:LDS 最后一位的最大值是多少,1:LIS 最后一位的最小值是多少。) 这样就非常好的使用了我们最长上升子序列的惯用套路,并且可以知道可行性。
考虑如何转移:
先看怎么转移 dp_{i,0} 。
如果 a_i>a_{i-1} ,显然有 dp_{i,0}=dp_{i-1,0} 。
如果 a_i>dp_{i-1,1} ,显然有 dp_{i,0}=a_{i-1} 。
将两者取 \max 即可。
\texttt{Code}
12. CF1845E Boxes and Balls
\color{green}\text{Difficulty : 2500}
日常计数题最开始一点思路都没有。题解区怎么全都说是些经典套路啊啊啊啊啊。
又是经典的看完题解恍然大悟。
感觉正着做直接去 DP 的话是非常容易算重的,故考虑去看哪些数组 b 是合法的。
考虑将原数组 a 中所有为 1 的位置拿出来,假设为 a_i ,将 b 数组 1 的位置拿出来,假设为 b_i ,考虑去看哪些 b_i 合法。
注:后面所说的 a,b 都是这些为 1 的位置所构成的数组,a_i,b_i 就是第 i 个为 1 的位置,且保证两者是排好序的。
后面有个 2|\sum 是因为你可以不停的交换两个 1 偶数次,但是数组本质上是不会变的。
故有一个很智慧的 DP,即定义 dp_{i,j,k} 表示考虑完了前 i 个位置,b 数组中有 j 个 1 ,k=\sum_{l=1}^j|b_l-a_l| 的方案数。
转移是非常简单的,直接 dp_{i,j,k}\to dp_{i+1,j+1,k+|i+1-a_{l+1}|} ,时间复杂度 \mathcal{O}(n^3) ,看了看,仍然失之毫厘。
有一个很套路的东西是,去计算每个位置 i% 要被 a_l\to b_l 这样的移动经过多少次,换句话说,定义 f_{b,i} 表示 b 方案中,所有在 i 之前的 1 的位置,比原方案中在 i 之前的 1 的位置多多少个。(这里可以是负数。)故最终操作次数就是 \sum_{i=1}^n |f_{b,i}| 。
观察到 |f_{b,i}-f_{b,i+1}|\le 1 ,故其 \max|f_{b,i}| 在最大的情况下,每次最多 +1 ,增长途中其至少会构成一个等差数列,既然如此,\max|f_{b,i}|\le \sqrt{k} ,因为等差数列的求和公式。
考虑换 DP 状态,定义 dp_{i,j,k} 表示表示考虑完了前 i 个位置,f_{b,i}=j,\sum_{l=1}^i |f_{b,i}|=k ,每次转移同上,复杂度 \mathcal{O}(1) ,但是 DP 总状态只有 \mathcal{n^2\times \sqrt{k}} ,故总复杂度 \mathcal{O}(n^2\times \sqrt{k}) ,观察到是可以通过的。
\texttt{Code}
13. CF1615F LEGOndary Grandmaster
\color{green}\text{Difficulty : 2800}
故意找了一道和上一题比较类似的。但是依然不会。
为啥我感觉一点也不类似捏。。
”我们发现将两个相邻的且相同的数取反就是直接将原数组的所有奇数的位置取反,然后每次操作看成交换相邻两个不同的数,最后看是否和目标数组的奇数位取反相等。“然后就转化成了上一个题的操作。
不是哥们,你是神仙吗,怎么看出来的啊。。什么阴间转化啊。。
然后转化成上一道 CF1845E 的操作之后,我们仍然是类似的进行 DP,但是我们发现并不需要去记录 k 这一维,只需要在转移的时候将相应增加的操作次数记录下来即可,可以把每个 DP 当作一个 \text{pair} ,一个储存当前操作总数,一个储存方案数,这样直接仿照上一题进行转移即可。
这里由于并没有操作次数上限,所以 \max|f_{b,i}| 并不是 \sqrt{n} 级别的,而是 n 级别的,故总时间复杂度 \mathcal{O}(n^2) ,具体细节见代码。
\texttt{Code}
14. CF1221G Graph And Numbers
\color{green}\text{Difficulty : 2900}
感觉是比较弱智的题目,但是我最开始对于折半的答案合并并没有想的太清楚,所以还得练。
首先是比较显然的容斥,答案应该就是 g_{\varnothing}-g_{\{0\}}-g_{\{1\}}-g_{\{2\}}+g_{\{0,1\}}+g_{\{0,2\}}+g_{\{1,2\}}-g_{\{0,1,2\}} 。(g_S 表示 S 集合内部的边权都不出现的方案数。)
然后为了方便计算,先把 m=0 直接给特判掉,然后一个一个看怎么求解。
华丽结束,总复杂度应该是 \mathcal{O}(2^{\frac{n}{2}}\times n) ,又是完成了 6 道题目的一天。
\texttt{Code}
15. CF1743F Intersection and Union
\color{green}\text{Difficulty : 2300}
VP 遇到的一道题,感觉挺有价值的,而且也让我认识到了一种全新的数据结构优化 DP。
首先你肯定是想到统计对于一个权值有多少种方案会出现在最终集合里面,然后加起来就可以了。
然后有一个 \mathcal{O}(n^2) 的算法是,你考虑枚举权值 x\in [1,3\times 10^5] ,然后定义 dp_{i,0/1} 表示进行完前面 i 次操作,他是否在集合里面的方案数,最终答案就是对于每个权值所求得的 dp_{n,1} 之和。
稍微写一下这个柿子:
\begin{aligned}
&l_i\le x \le r_i\\
&dp_{i,0}=dp_{i-1,0}+dp_{i-1,1}\\
&dp_{i,1}=dp_{i-1,0}\times 2+dp_{i-1,1}\times 2\\
&\text{Other Wise}\\
&dp_{i,0}=dp_{i-1,0}\times 3+dp_{i-1,1}\\
&dp_{i,1}=dp_{i-1,1}\times 2\\
\end{aligned}
观察到上述转移非常像矩阵的形式:
\begin{aligned}
&l_i\le x\le r_i\\
&\begin{pmatrix}
dp_{i,0}& dp_{i,1}\\
\end{pmatrix}=
\begin{pmatrix}
dp_{i-1,0}& dp_{i-1,1}\\
\end{pmatrix}\times
\begin{pmatrix}
1&2\\
1&2\\
\end{pmatrix}
\\
&\text{Otherwise}\\
&\begin{pmatrix}
dp_{i,0}& dp_{i,1}\\
\end{pmatrix}=
\begin{pmatrix}
dp_{i-1,0}& dp_{i-1,1}\\
\end{pmatrix}\times
\begin{pmatrix}
3&0\\
1&2\\
\end{pmatrix}
\end{aligned}
你考虑对于每个 i 从 1\to n 枚举,对于所有的 x 同时执行以上过程,即开一颗线段树,每个叶子节点储存一下 dp_{x,i,0},dp_{x,i,1} 他们两个构成的矩阵,然后每次修改就是区间 [1,l_i-1],[l_i,r_i],[r_i+1,3\times 10^5] 分别乘上对应的转移矩阵即可。
时间复杂度 \mathcal{O}(V\log n) ,但是因为有矩阵的常数,可能很容易被卡常。
经过自己的实现,确实需要主意矩阵乘法的时候尽量用 \text{*=} 不要直接用 \text{*} 因为后者会有很多的结构体赋值以及额外的空间开销导致常数爆炸。
这个旨在告诉你,对于一个二维甚至高维的 DP,你每次转移如果可以写成矩阵形式,并且是一整段一整段的乘上这个转移矩阵,那么你就可以直接把 DP 放进线段树上,然后区间乘矩阵。
\texttt{Code}
16. CF1767E Algebra Flash
\color{green}\text{Difficulty : 2500}
题意是比较清楚的。
而你要从起点到达终点的一个充分必要条件是:
故你考虑对于起点和终点的颜色连一个自环,然后将相邻两个点分别对应的颜色连一条边。
故这个题就转化成了一个一般图最小带权点覆盖的问题。
而我们知道最小带权点覆盖 = 点权和 - 最大带权独立集。(注意这个东西在一般图上也是存在的 )
故我们只需要知道这个图的最大带权独立集是什么。
首先有一个很 \texttt{naive} 的状压是定义 dp_S 表示只选择 S 内部的点可以构成的最大独立集是多少。
另外,我们定义点 x 的邻域的点构成的集合是 N(x) 。
转移的话本质上我们只需要考虑这个集合中的任意一个点就行了,为了方便我们后面的优化,考虑 \text{highbit}(S) ,假设是 u 。
显然有:dp_S=\max(dp_{S-u},dp_{S-N(u)-u}+w_u(u\not \in N(u))) 。(显然如果是自环的话你不能把这个点当作最大独立集上的点)
时间复杂度 \mathcal{O}(2^n) 。
但是这里 n\le 40 显然不可过啊。
于是我们考虑一个奇技淫巧,也是这个算法的关键所在。
首先先把这个 \text{dp} 搬到记忆化搜索上面。由于我们使用的是 \text{highbit}(S) ,故这个 S 在搜素过程中逐渐减小。故我们考虑只对 s < 2^{\frac{n}{2}} 这部分进行记忆化,而剩下的部分直接爆搜。
时间复杂度我们简单的证明一下。
对于搜索的前 \frac{n}{2} 层,每向下递归一次,最多只会变成两个,故前半是 \mathcal{O}(2^{\frac{n}{2}}) 。
对于后面的 \frac{n}{2} 层,显然都会被记忆化到,无论你前面有多少种状态,加起来仍然只有 2^{\frac{n}{2}} 级别。(就是在记忆化和非记忆化分层的哪些点只有 2^{\frac{n}{2}} 而他们分别向外扩展也只会扩展成 2^{\frac{n}{2}} 级别,有点像 \text{Meet in the middle} 那种感觉)
故总时间复杂度 \mathcal{O}(2^{\frac{n}{2}}) 。
本题代码和更多细节见此。
17. CF1739E Cleaning Robots
\color{green}\text{Difficulty : 2400}
如此唐氏的 DP 我居然想了这么久我是不是废了。
我们先思考一下什么情况下会崩溃。
观察到对于一个脏格子 (i,j) 如果 (3-i,j-1) 是脏的,那么如果扫地机移动到了 (i,j-1) ,它就会崩溃。
这指向我们去维护一下下一行上下两个格子的状态,即他们是否被清理。
故我们定义 dp_{i,0/1,0/1,0/1} 表示当前停留在 (j,i) 上,且 (3-j,i) 如果是脏的,已被清理,第 i+1 列上下两个格子是否被清理的状态。
你考虑直接枚举 i-1 的 0/1 状态判断合法直接转移即可,细节见代码。
个人认为有一篇比较好的解释。
\texttt{Code}
18. CF1743E FTL
\color{green}\text{Difficulty : 2400}
有一个比较显然的 \mathcal{O}(h\times t_1\times t_2) 做法,即你定义一个状态表示当前敌方飞船的血量,飞船 1/2 分别的充能时间。
额这个状态及其的冗余啊。而且看了样例我才知道这个充能是可以两个飞船同时充能的。
定义 dp_i 表示进行完上次攻击之后,敌方飞船剩下的血量为 i 所需要的最少时间。
先看只需要蓄力一个的暴力转移,即:
\begin{aligned}
dp_{i-(p_1-s)}=\min(dp_i+t_1)\\
dp_{i-(p_2-s)}=\min(dp_i+t_2)\\
\end{aligned}
考虑怎么对于同时蓄力的进行转移。观察到你可以把两个同时打出,看作打出一个的同时另外一个正好蓄好了力,正好可以打出去。
故你考虑枚举 p_i 打出去了 j 次,且第 j 次 p_{3-i} 会同时打出,然后算出 p_{3-i} 在这段时间内会有单独打出去多少次,毕竟不能干等着对吧。
首先显然要满足 t_i \times j \ge t_{3-i} 不然不会同时打出一次。
然后你看在这期间 p_{3-i} 会被打出去多少次。稍微算一下应该比较容易,懒得打 \LaTeX 了。
时间复杂度 \mathcal{O}(h^2) 。
MD,读错题了,浪费了我一个小时。
\texttt{Code}
19. CF407D Largest Submatrix 3
\color{green}\text{Difficulty : 2700}
考试题,感觉自己的做法挺不错,搬过来。
题解非常清晰明了,这里懒得讲了,讲一下自己的巨恶心做法。
为了将这个做法优化到 \mathcal{O}(n^3) 我也是煞费苦心了。
说实话这个做法真的挺难有语言描述的。
首先你考虑维护一个 \text{num}_{i,l,r} ,表示对于第 i 行,找到最小的 j\ge i ,存在一个数 k_1,k_2\in[l,r] ,满足 a_{i,k_1}=a_{j,k_2} 。
接着维护一个 \text{nxt}_{i,j} ,表示找到一个最大的 k 使得 a_{i,[j,k]} 中的数互不相同。
有了 \text{nxt}_{i,j} ,我们考虑再维护一个 b_{i,j,k} ,表示找到最大的 l\ge i ,使得对于 \forall x\in[i,l],\text{nxt}_{x,j}\ge k ,如果没有满足的,就是 0 。
有了这三个数组之后,答案应该是非常好维护的。
我们考虑枚举这个长方形的的最上面一行,假设是第 i 行的 [l,r] 这个区间。
答案应该是 \min(\min_{j=i}^{\text{num}_{i,l,r}}\text{num}_{j,l,r}-i+1,b_{i,l,r-l+1})\times (r-l+1) 。
感觉还是比较好理解的。至于里面的那一坨 \min ,它有一些性质,你可以考虑打一个后缀最小值,具体细节可以看代码。
里面唯一的难点在于 \text{num} 的求法。
观察到 \text{num}_{i,l,r}=\min(\text{num}_{i,l+1,r},\text{num}_{i,l,r-1}) ,除了这两个产生的贡献,剩下的就只有第 l 和第 r 列对 a_{i,l},a_{i,r} 产生的贡献了。
其实还有一堆细节,可以考虑直接看代码。
20. CF1988F Heartbeat
\color{green}\text{Difficulty : 3000}
好题,以前写过题解,直接搬进来。
\begin{aligned}
&f_{i, j, k}, g_{i, j, k} \to (i \text{ permutation}, j \text{ premax or sufmax},k (a[l] > a[l - 1])) \\
&\text{Initialize : } f_{1,1,0}=g_{1,1,0}=1\\
&\text{Transfer for f,g}\\
&f_{i,j,k} = f_{i-1,j-1,k-1}+f_{i-1,j,k}\times(k+1)+f_{i-1,j,k-1}\times (i-k-1)\\
&g_{i,j,k} = g_{i-1,j-1,k}+g_{i-1,j,k}\times k+g_{i-1,j,k-1}\times (i-k)\\
&\text{Now you know f, g, a, b and c}\\
&n= \forall \texttt{interger} \in [1,N],\text{ans}_n=C_{N-1}^{n-1}\times \sum^n_{i = 1,} \sum^{i-1}_{j=0 \text{ for premax,}} \sum^{n-i}_{k=0\text{ for sufmax,}} \sum^{i-2}_{l=0 \text{ for }f_k,} \sum^{n-i-1}_{m=0 \text{ for } g_k}f_{i-1,j,l} \times g_{n-i,k,m} \times a_{j+1} \times b_{k+1} \times {c_{l+m+[i\not=1]}}\\
&\text{Try to find ans of } \mathcal{O}(N^3)\\
&\text{Define }F_{i,k}=\sum ^{n}_{j=0} f_{i,j,k}\times a_{j+1},G_{i,k}=\sum ^{n}_{j=0} g_{i,j,k}\times b_{j+1}\\
&\text{ans}_n=C_{N-1}^{n-1}\times\sum^n_{i=1,}\sum^{i-2}_{l=0 \text{ for }f_k,} \sum^{n-i-1}_{m=0 \text{ for } g_k} F_{i-1,l} \times G_{n-i,m}\times {c_{l+m+[i\not=1]}}\\
&\text{Define } H_{i,m}=\sum^{i-1}_{l=0} F_{i,l}\times c_{m+l+[i\not=0]}\\
&\text{ans}_n=C_{N-1}^{n-1}\times\sum^{n}_{i=1} \sum^{n-i-1}_{m=0 \text{ for } g_k} G_{n-i,m}\times H_{i-1,m}
\end{aligned}
21. CF53E Dead Ends
\color{green}\text{Difficulty : 2500}
显然,定义 dp_{i,j} 表示当前的这棵树包含了 i 集合里面的点,其叶子集合为 j 的方案数。
考虑顺推,直接枚举一个不在 i 里面的点,找到一条边,使得这个点在这条边上且另一个点在 i 集合里面,然后叶子节点增加这个点,如果另一个点在 j 集合里,那就把另一个点从叶子节点里面剔除。
但是这样显然是会算重的,就是说你一课树可能会被计算多次。
手玩一下,发现对于每一个 dp_{i,j} ,转移完之后,会将其对应的树重复计算其叶子个数次,换句话说,每次转移玩直接除以一个 |j| 即可完成去重。
然后就过了。。时间复杂度应该是 \mathcal{4^n\times(n+m)} 。
\texttt{Code}
22. CF813D Two Melodies
\color{green}\text{Difficulty : 2600}
好久没有自己做出来一道题了,老泪纵横啊。
定义 dp_{i,j} 表示两个无重复的 \text{Melody} 序列,一个以 a_i 结尾,另一个的末尾是 j 。
先考虑怎么暴力转移:
如果要将 i 自己加入第一个序列,考虑枚举 k\in[1,i-1] \land (|a_k-a_i|=1\lor a_k\equiv a_i\pmod 7) ,有 dp_{i,j}=\max\{dp_{k,j}+1\} 。
如果要将 k 自己加入第二个序列,考虑枚举 k\in[1,i-1]\land l\in[1,V] \land (|l-a_i|=1\lor l\equiv a_i\pmod 7) ,有 dp_{i,a_k}=\max\{dp_{k,l}+1\} 。
此时时间复杂度应该是 \mathcal{O}(n^2\times V) ,可以离散化做到 \mathcal{O}(n^3) 。
优化是非常简单的,考虑维护四个前缀 \max 数组,将括号中的通过”或者“连接起来的四个条件分别维护对应的最优情况。以第一个转移为例,具体来说,发现转移中可以预处理出满足条件的所有 \max\{dp_{k,j}\} ,从而我们只需要枚举一维 j 。第二种转移同理。
此时总时空复杂度均为 \mathcal{O}(n^2) ,因为数组开太多了甚至要开 \text{short} 才能过,还有一堆初始值的细节一定要注意。
\texttt{Code}
23. CF1497E2 Square-free division (hard version)
\color{green}\text{Difficulty : 2500}
可以把这个选择 k 个数定义为任意整数看作选这些数的时候不需要满足立方限制,先考虑暴力 DP。
定义 dp_{i,j} 表示前 i 个数,操作 j 次之后,连续字段的最小数量是多少。
考虑转移:
枚举 k\in [0,i-1] ,转移就是 dp_{i,j}=\min\{dp_{k,j-\text{cost}(k+1,i)}+1\} ,其中 \text{cost}(l,r) 表示将 [l,r] 看作一个字段最少需要操作多少次。
难点显然在于 \text{cost}(l,r) 怎么快速求。
这就牵扯到怎么看两个数的乘积是完全平方数了。考虑对于所有的 a_i ,找到最大的 x ,满足 x^2\mid a_i ,然后令 a_i=\frac{a_i}{x^2} ,那么要使两个数 i,j 的乘积是平方数的充要条件就是 a_i=a_j 。(这一步可以通过质数筛求得每个数的最小质因子来完成)
故进一步的,\text{cost}(l,r) 其实就是 r-l+1-\text{num}(l,r) ,\text{num}(l,r) 表示 [l,r] 这个区间中不同的数的个数。显然,因为每个子段只能保留互不相同的数,不然乘积就会出现完全平方数。
倒着枚举 k ,打个桶记录一下,应该就可以求出 \text{cost}(k+1,i) 。
此时时间复杂度应该是 \mathcal{O}(n^2\times k) 。
考虑怎么优化:
观察到,\forall j\in[0,k],i\in[2,n],dp_{i-1,j}\le dp_{i,j} ,根据定义,这个应该是比较显然的。
发现所有能转移的 \text{cost}(k+1,i) 必然满足 \le K (这个 K 是题目里面输入的。)
并且,对于 \text{cost}(k+1,i)=x\in[0,K] ,x 显然只有 K+1 种。
对于这 K+1 种 x ,每种 x 的转移显然只需要找到最左边的 k 进行转移,正确性根据第一个性质即可得到。
发现此时每个 dp_{i,j} 的有效转移数量最多只有 K+1 个,复杂度瓶颈在于怎么找到每种 x 对应的满足条件的最左边的 k 。
发现可以维护 K+1 个双指针,对应每种 x 当前 k 最左边的位置,每次从 i\to i+1 的时候,去判断当前的指针是否合法,不合法就一直往右跳就行了,跳的中途打桶维护即可。
此时时间复杂度 \mathcal{O}(n\times k^2) ,是可以通过的。
\texttt{Code}
24. CF1681F Unique Occurrences
\color{green}\text{Difficulty : 2300}
首先,这个题应该可以直接暴力 \text{LCT} 或者是虚数统计方案直接暴力弄过去。
貌似是一道线段树分治的基础题,咳咳,这不重要。
我们考虑一些更简单的方式,比如树形 DP。
首先先考虑这个方案是怎么来的,本质上应该是去算每一种颜色的贡献。对于每一种颜色的贡献,可以看作是把这种颜色的边删掉,然后求出剩下的连通块大小,然后对于每条删掉的边,答案加上两个端点分别对应的连通块大小的乘积。
考虑如何快速计算这个过程。。
定义一个点的点权是其到其父亲节点的点的边权。
定义 dp_i 表示从 i 到 i 的子树中,不经过与 i 相同点权的点可以到达多少个点。
转移是比较容易的,考虑先将 dp_i 定义为其子树大小,然后减掉不能到达的点。用栈维护当前与 i 点权相同最近一次出现的且比它深度小的点的编号,假设是 y ,那么 dp_y=dp_y-\text{siz}_i 。
考虑如何通过 dp 数组来统计答案。对于每一个 dp_i ,依然用栈维护当前 i 点权相同最近一次出现的且比它深度小的点的编号 y ,那么答案就加上 dp_i\times dp_y 。
感觉是比较好理解的。看不懂我在写什么的,代码应该也是能看懂的。。
\texttt{Code}
25. CF1521D Nastia Plays with a Tree
\color{green}\text{Difficulty : 2500}
额,感觉是比较简单的树形 \texttt{DP} 。
转化一下题意,就是将一棵树删成若干条链的操作次数。
定义 dp_{x,0/1} 表示将 x 的子树操作为一条链,x 是否是链的端点的最小操作次数。
转移应该是比较简单的,如下:
void dfs(int x, int last)
{
int sum[3] = {0, 0, 0}, minn = INT_MAX, min2 = INT_MAX, son = 0;
for (int i = fst[x]; i; i = arr[i].nxt)
{
int j = arr[i].tar;
if(j == last) continue;
dfs(j, x);
dp[x][0] += dp[j][1] + 1;
if(dp[j][0] - dp[j][1] < minn) min2 = minn, minn = dp[j][0] - dp[j][1];
else if(dp[j][0] - dp[j][1] < min2) min2 = dp[j][0] - dp[j][1];
sum[0] += dp[j][0] + 1, sum[1] += dp[j][1] + 1;
++son;
}
if(son) dp[x][0] = min(dp[x][0], sum[1] + minn - 1);
if(son >= 2) dp[x][1] = sum[1] + min2 + minn - 2;
dp[x][1] = min(dp[x][0], dp[x][1]);
}
然后输出方案就可以恶心到吐了。。
\texttt{Code}
26. CF1442E Black, White and Grey Tree
\color{green}\text{Difficulty : 3000}
非常的妙啊,一道循序渐进的题。
先考虑链的情况,假设这个链没有灰色,你考虑将一段相同的黑色或者白色合并在一起,那么这条链就变成了黑白相间的,假设长度是 \text{len} ,你可以发现这条链的操作下限是 \lceil\frac{\text{len}}{2}\rceil+1 。
感觉是比较好理解的。
然后考虑迁移到一棵树上。如果这棵树只有黑白,那么显然就是这棵树对应的链就是在黑白相同合并之后的直径。
而你现在就是要将灰色染成黑白然后求合并之后的直径的最小值。
考虑 DP,类似求树的直径,设 dp_{x,0/1} 表示 x 染成白/黑,子树中某个点离它最大的距离,f_{x,0/1} 表示 染成 x 白/黑 经过 x 的直径答案。
类似直径的转移。比较容易。
答案就是 \max (\min(f_{x,0/1})) 。
\texttt{Code}
27. CF512D Fox And Travelling
\color{green}\text{Difficulty : 2900}
首先,在环中的点一定不会被遍历。
用类似拓扑排序的过程可以把环上的点全部扔掉,剩下的点会构成若干个有根树和无根树,其中有根树的根是树中唯一与环中的点相连的点。
显然对于有根树,直接跑树上背包。
对于无根树,以树中每个点为根做一次有根树的树上背包,这样会发现每种选择 i 个点的方案会被算 s - i 次,其中 s 为这棵无根树的大小,那么除掉即可。
然后合并即可。
\texttt{Code}
28. CF111D Petya and Coloring
\color{green}\text{Difficulty : 2300}
这题有个 DP 的标签仅仅只是因为它有个第二类斯特林数???然后这么SB的题我还做了半天???
考虑寻找这个题满足题目要求的性质。
本质上来说就是,第一列和最后一列的颜色数量相同,且第二列到倒数第二列中间的颜色必须既在第一列,又在最后一列出现过。
故考虑枚举第一列和最后一列的颜色数量 i ,然后再枚举两者颜色集合交集的大小 j 。
考虑如何计算答案。
先预处理一个数组 dp_{i,j} 表示将 i 个球,放入 j 个盒子中,球不同,盒子相同,且无空盒的方案数。
其实是第二类斯特林数,但是我不知道,现推了一遍,转移柿子是很简单且好理解的:dp_{i,j}=dp_{i-1,j-1}+dp_{i-1,j}\times j 。
首先对于第一列和最后一列,假设这 i 种颜色已经确定,看怎么填的方案数就是 (dp_{n,i}\times i!)^2 。(因为我们的 dp 定义盒子是没有顺序的。)
然后再看这 i 种颜色和 j 种两边都出现的颜色被选出来的方案数:C_{k}^{j}\times C_{k-j}^{2\times(i-j)}\times C_{2\times (i-j)}^{i-j} 。(感觉比较好理解。)
最后中间的 m-2 的填颜色方案数量就是 j^{n\times (m-2)} 。(把这 j 种颜色随便填即可。)
故总答案就是 \sum_{i=1}^{\min(n,k)}\sum_{j=0}^{i}(dp_{n,i}\times i!)^2\times C_{k}^{j}\times C_{k-j}^{2\times(i-j)}\times C_{2\times (i-j)}^{i-j} \times j^{n\times (m-2)} 。
\texttt{Code}
29. CF906C Party
\color{green}\text{Difficulty : 2400}
日常做题遇到状压板子题,甚至分这么高。
考虑先把答案是 0 的情况特判掉,即输入的图两两之间都有边的情况,因为状压遇到这个会死。
然后很板的,定义 dp_i 表示 i 这个子集,两两都是直接朋友所需要的最小操作次数。
考虑顺推,即在知道 dp_i% 的情况下递推,找到 j\in i ,dp_{i|N(j)}=\min \{dp_i+1\} ,N(j) 表示 j 的邻域。
初始值,对于每个点 i ,有 dp_{N(i)}=1 。
时间复杂度 \mathcal{O}(2^n\times n) ,输出方案的话,打一个前驱数组记录从哪一个状态转移过来即可,
\texttt{Code}
30. CF1793E Velepin and Marketing
\color{green}\text{Difficulty : 2600}
先观察一下在最优方案下,每个书如何分配的读者。
显然要将读者按照 a_i 从小到大排序,可以发现开心的读者必然是一个前缀。、
考虑一下方案是怎么构成的,发现只有以下两种情况:
将开心的读者分成若干个组,且每个组都能保证这些读者是开心的。
将所有开心的读者分在一组,外加一些不开心的读者来保证这个组的读者数量从而保证读者开心。
对于没有分配在这些组内的读者,一人占一个组。
容易发现,如果不是以上两种情况,那么显然可以通过调整得到这种情况。
故考虑定义 dp_i 表示保证前 i 个读者开心最多 可以分成多少个组。
假设当前有 k 本书,那么能有 i 个读者开心的条件就是:
容易发现,随着 k 的增多,\max\{i\} 在减小,随便写个指针或者二分就可以做到 \mathcal{O}(n\log n) 。
瓶颈在于排序。
\texttt{Code}
31. CF808G Anthem of Berland
\color{green}\text{Difficulty : 2300}
最开始想了一个 \mathcal{O}(26\times n\times m) 的唐氏做法想半天还不觉得哪里有问题。。
其实是转移冗余了。
看到这个 n\times m\le 10^7 ,你考虑直接定义 dp_{i,j} 表 s 填好前 i 位,最后 j 位对应 t 的前 j 位的当前最大匹配次数。
然后发现这个东西多多少少跟 \text{Border} 有点关系,考虑先把 t 求一遍 \text{KMP} ,得到每个 [1....i] 的最长 \text{Border} 数组,假设是 \text{pre}_i 。
考虑顺推转移,即知道 dp_{i,j} 的情况下往后转移。
首先你可以直接失配,即 dp_{i+1,0}=\max \{dp_{i,j}\}
考虑向后匹配一位,即当 s_{i+1}=t_{j+1} 或者 s_{i+1} 是问号的时候,即 dp_{i+1,j+1}=\max\{dp_{i,j}\}
最后找到 \max\{dp_{n,j}+[j=m]\} 即可。
你可能会认为,你中途失配,即 dp_{i+1,0}=\max\{dp_{i,j}\} 这一步,他不一定会直接失配到 0 ,而是可以跳 \text{pre} 跳到中间满足 s_{i+1}=t_{j+1} 的位置,其实这个是没有必要的,你应该会在第二种转移中考虑到这种情况。
说实话不太好解释,总之就是这种情况会被剩下的情况考虑到,这样就不用去枚举失配的时候下一位填什么了,直接失配到 0 就可以了。
时间复杂度 \mathcal{O}(n\times m) 。
\texttt{Code}