Codeforces DP训练

本文精选了多个经典算法题目,涵盖了动态规划、状态压缩、矩阵乘法等高级技巧,深入解析了各种复杂问题的解决方案,包括序列分析、树结构处理、概率计算等多个方面。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

813D:

题意:给出长度为nnn的序列,从中找出222个子序列,满足每个子序列相邻两数之间要么相差111,要么同余于777,求这两个子序列的最长长度和。
题解:DP优化主要考虑状态的减少和转移的加快,这个题f[i][j]f[i][j]f[i][j]表示分别以iiijjj为结尾的子序列最长长度和的状态不能减少,考虑优化转移。防止一个位置被重复选择,要强制限制i&lt;ji&lt;ji<j,那么固定iiif[i][j]f[i][j]f[i][j]实际上是从前面的某个f[i][k]f[i][k]f[i][k]转移过来,所以可以通过维护mx1[p]mx1[p]mx1[p]mx2[p]mx2[p]mx2[p]表示a[k]%7=pa[k]\%7=pa[k]%7=p的最大f[i][k]f[i][k]f[i][k]a[k]=pa[k]=pa[k]=p的最大f[i][k]f[i][k]f[i][k],即可实现O(1)O(1)O(1)转移。

796E:

题意:有nnn道题目,有两个人分别会做某些题目,有ppp次偷看机会,每次可以偷看某个人最多连续kkk道题目,求最多偷看几道题目。
题解:f[i][j][x][y]f[i][j][x][y]f[i][j][x][y]表示前iii道题目,用了jjj次偷看机会,第一个人还可以看xxx道,第二个人还可以看yyy道即可。

534F:

题意:一个只有黑白格子的矩形,给出每行每列的连续的黑色格子段数,还原出这个矩形。
题解:比较简单的一道题目,但是还是卡了卡。首先状态压缩DP很好想,就一列一列填就行了,但是这个做法TLE了,原因是状态数太多,一个状态能被很多个状态转移到,所以考虑倒着来,做记忆化搜索就行了,因为这样找到一个合法的就会结束了。

946G:

题意:给出nnn个数,问最少把多少个数改成任意整数后,使得之后的nnn个数能通过去掉一个数,成为一个严格上升序列。
题解:套路都忘光了……如果最后要求的是一个不降序列,问题就简单了,那么我们就通过每个位置−i-ii实现这一转化。然后对于删数这个操作,实际上是使被删数后面的数由−i-ii变成−(i−1)-(i-1)(i1),用两个DP数组分别维护删了数和没删数的最长不降序列即可。

856C:

题意:给出nnn个数,把这nnn个数拼起来成为一个数,求n!n!n!种方法中有多少是111111的倍数。
题解:首先有性质:一个数奇数位数上的和与偶数位数上的和的差的绝对值为111111的倍数,这个数即为111111的倍数。对于每个数,它原来的奇数位和偶数位可能会在最后的答案中倒过来,易得若nnn个中有mmm个奇数位数的数,那么有⌊m2⌋\lfloor {m\over 2}\rfloor2m个奇数的贡献是要倒过来的,偶数位数的数的贡献则可以任意,那么分别对奇数位数的数和偶数位数的数DP一下,f[i][j][k]f[i][j][k]f[i][j][k]表示前iii个有jjj个贡献反过来,余数为kkk的方案数即可,最后把偶数插进奇数中。

178F2:

题意:给出nnn个字符串,选出其中恰好kkk个组成一个集合,定义集合的权值为字符串两两之间最长公共前缀之和,求权值最大值。
题解:建出字典树的虚树,在上面DP即可。

946F:

题意:定义F(x)F(x)F(x)表示一个010101串,F(0)=0F(0)=0F(0)=0F(1)=1F(1)=1F(1)=1F(x)=F(x−1)+F(x−2)(x&gt;1)F(x)=F(x-1)+F(x-2)(x&gt;1)F(x)=F(x1)+F(x2)(x>1)+++表示字符串的拼接,给出一个010101串,求其在F(x)F(x)F(x)的所有子序列中的出现次数之和。
题解:字符串匹配可以考虑区间DP,f[i][l][r]f[i][l][r]f[i][l][r]表示s[l−r]s[l-r]s[lr]F(i)F(i)F(i)的所有子序列中的出现次数之和,分情况讨论转移即可。

958C3:

题意:给出nnn个数,把他划分成恰好kkk段,求每段的和%p\%p%p之和的最小值。
题解:暴力DP:f[i][j]f[i][j]f[i][j]表示前iii个数划分成了jjj段,f[i][j]=min⁡{f[k][j−1]+(sum[i]−sum[k]+p)%p}f[i][j]=\min\{f[k][j-1]+(sum[i]-sum[k]+p)\%p\}f[i][j]=min{f[k][j1]+(sum[i]sum[k]+p)%p},可以按照sum[i]sum[i]sum[i]sum[k]sum[k]sum[k]的大小关系分类讨论,用数据结构优化,但是还不够,可以对于每个余数维护最小值,再用数据结构维护,那么lognlognlogn变为logplogplogp,可以通过。

875E:

题意:有两个快递员AAABBB,他们的初始坐标为s1s1s1s2s2s2,有nnn个需要送的地点坐标为a1...na_{1...n}a1...n,按照派送优先顺序编号。求两个快递员派送过程中相隔最大距离的最小值。
题解:二分答案,考虑如何检验。从前面开始不会搞,倒过来搞。这样我们不需要知道谁在送货,只需要维护当其中一人在aia_iai时,另外一个人的区间即可。

643E:

题意:一开始有只有根节点,支持两种操作:1、插入一个节点,以当前某节点为父亲。2、询问以某个节点为根的子树,若每条边有121\over221的概率断掉,期望的最大深度是多少(在误差范围内即可)。
题解:首先很重要的一点,若深度太大,那么概率就会小的可以忽略不计,所以实际有用的层数不会太多,这应该是概率DP中不错的套路。那么直接f[i][j]f[i][j]f[i][j]表示以iii为根节点,最大深度为jjj的概率即可。

888F:

题意:给出111nnn的点两两之间的边是否可以连,求连成一棵树且不存在两条边(ui,vi)(u_i,v_i)(ui,vi)(uj,vj)(u_j,v_j)(uj,vj)满足ui&lt;uj&lt;vi&lt;vju_i&lt;u_j&lt;v_i&lt;v_jui<uj<vi<vj的方案数。
题解:直接f[i][j]f[i][j]f[i][j]表示把iiijjj连起来,且iiijjj必须有边相连方案数,g[i][j]g[i][j]g[i][j]表示把iiijjj连起来方案数,互相转移即可。

1034C:

题意:给定一棵树,每个点有一个点权aia_iai。整棵树是第111级划分。定义第iii级划分是将第i−1i−1i1级划分中的每个区域划分成至少两个新的区域,并且所有区域都是一个连通块,每个点在每一级中只属于一个区域,在同一级划分内每个区域内的点的点权和相等。一种划分方案包含它划分的每一级。两种划分方案不同当且仅当它们划分的级数不同,或者存在一个点在某一级中它们在两种划分方案中属于不同区域。
题解:感觉这个题跟之前的某个把树划分成若干个点数相同的块有点类似,策略是一样的,若每块的点权和是kkk,那么当某棵子树权值和达到kkk时就砍掉这棵子树,所以对于每个kkk都只有一种方案划分。f[i]f[i]f[i]表示子树权值和s[x]%(s[1]/i)=0s[x]\%(s[1]/i)=0s[x]%(s[1]/i)=0xxx有多少个,然后再用ans[i]ans[i]ans[i]表示最后分成iii块的方案数即可。关键是如何求f[i]f[i]f[i]。暴力是O(n2)O(n^2)O(n2)的,考虑求出对于每棵子树最小合法的iii,这样有且仅有任意正整数倍的iii都是合法的,那么这个iii显然会s[1]/gcd⁡(s[1],s[x])s[1]/\gcd(s[1],s[x])s[1]/gcd(s[1],s[x])(这题比较难啊)。

708E:

题意:(最好结合图理解)每天除上下两排,最左边和最右边的每个方块都有概率消失,求最后这个图形连成一块的概率。
题解:比较简单的DP是这样的:f[i][l][r]f[i][l][r]f[i][l][r]表示到第iii行,剩下了[l,r][l,r][l,r]的方块的概率,转移可以用前缀和优化做到O(1)O(1)O(1),但是这样光是状态就是n3n^3n3的,没有前途。我们最后要求的是∑l=1m∑r=lmf[n][l][r]\sum_{l=1}^m\sum_{r=l}^m f[n][l][r]l=1mr=lmf[n][l][r],考虑直接DP某个前缀和,不求fff,那么这样就可以做到O(nm)O(nm)O(nm),也是个不错的优化方法吧。

886E:

题意:求nnn的排列有多少满足:从左到右扫求最大值,当最大值不再变化kkk次后的最大值不为nnn
题解:计数题还是不会啊,也不太会思考,只能多做了。f[i]f[i]f[i]表示iii个数,最大数放在最后的合法方案数。转移就考虑次大数放的位置即可。

908G:

题意:定义S(x)S(x)S(x)xxx的各个位数字从小到大排形成的数,前导000忽略,如S(321)=123S(321)=123S(321)=123S(1002)=12S(1002)=12S(1002)=12,求∑i=1nS(i)\sum_{i=1}^nS(i)i=1nS(i)
题解:考虑计算每个数字的贡献,比如3×10n3\times 10^n3×10n,我们这样计算:在第nnn位填1、2、31、2、3123的时候分别加上10n10^n10n的贡献,那么每个数字的贡献就是最后大于等于它的数字个数,如果有kkk个,那么这个贡献就是10k−1910^k-1\over 9910k1,也就是kkk111,那么直接数位DP就行了。

367E:

题意:求选出nnn个区间[l1,r1]....[ln,rn][l_1,r_1]....[l_n,r_n][l1,r1]....[ln,rn],使得所有1&lt;=li&lt;=ri&lt;=m1&lt;=l_i&lt;=r_i&lt;=m1<=li<=ri<=m,且区间之间没有包含关系,至少有一个区间左端点为xxx的方案数。
题解:f[i][j][k]f[i][j][k]f[i][j][k]表示到了第iii个数,左端点有jjj个,右端点有kkk个的方案数,那么一个数可以分四种情况转移。对于这种二元组的题,不一定要一组一组取,还要考虑一个一个取。

436D:

题意:无限长的数轴,上面放着nnn个布丁,相邻两个布丁会黏在一起,移动任意一块另外一块也会移动,每次你可以向左或向右移动一块布丁,这块布丁会一直运动到撞到一块布丁为止,然后他们就黏在一起了,数轴上有mmm个特殊点,你可以做无数次操作,求最多能覆盖多少个特殊点。
题解:f[i]f[i]f[i]表示前iii个布丁最多覆盖点数,g[i]g[i]g[i]表示部分靠到iii的情况下最多覆盖点数,直接DP即可。

1055E:

题意:给出一个序列和一些线段,要求恰好选出mmm条线段覆盖序列,求被覆盖数从小到大排序后第kkk个的最小值。
题解:简单二分DP。

58E:

题意:给一个算式,要求加上尽量少的数字使等式成立。
题解:直接DP可以,但是写成dfs要更简洁一点,以后要注意什么时候写dfs。

954H:

题意:给一棵树,第iii层的每个点儿子数都相同,求长度为kkk的简单路径数。
题解:比较简单的计数题,预处理出fi,jf_{i,j}fi,j表示到第iii层的一个点长度为jjj的方案数,然后枚举起点计数即可。

382E:

题意:求满足111号点度数不超过222,其它点度数不超过333、最大匹配数为kkk的无根树数目。
题解:把111看做根节点,就是求最大匹配为kkk的二叉树个数,fi,j,0/1f_{i,j,0/1}fi,j,0/1表示大小为iii,最大匹配为jjj,根是否被匹配的方案数即可。

938F:

题意:给一个字符串,第iii次删除长度为2i−12^{i-1}2i1的一个子串,删到不能删为止,求最后字典序最小的字符串是什么。
题解:显然先后顺序是没有影响的,考虑一位一位确定,fS,if_{S,i}fS,i表示用了集合SSS的操作,保证前i−1i-1i1位最小,第iii位最小是原串中的第几位,每次枚举用哪种操作转移即可。

917C:

题意:有nnn个石头,前xxx个石头上各有一只蝌蚪,每次最左边的蝌蚪会跳到右边某个没有蝌蚪的石头上,每次最多跳kkk,一次跳不同距离消耗不同的能量,跳到一些特殊石头上会消耗或者获得一定能量,求全部跳到后xxx个石头最小消耗能量。
题解:思维有点僵化了,这种取min⁡\minmin的也是可以矩阵乘法的,kkk较小,可以状压,fi,Sf_{i,S}fi,S表示从iii开始的kkk个石头是否有蝌蚪的状态为SSS的最小消耗,但是这样有个问题,iii不一定会转移到i+1i+1i+1,因为iii可能没有蝌蚪,此时要令形如011001100110这样的状态先转移到110011001100,这样保证最左边一定有蝌蚪再转移,这样就能够保证转移可以矩阵乘法。

755G:

题意:有nnn个不同的球,选出若干份,每份要么是相邻的两球,要么是单独一个球,允许有球不在任何一份,但不允许球同时出现在两份中。求选出111~kkk份的方案数。
题解:fi,jf_{i,j}fi,j表示前iii个球选了jjj份方案数,那么fi,j=fi−1,j−1+fi−2,j−1+fi−1,jf_{i,j}=f_{i-1,j-1}+f_{i-2,j-1}+f_{i-1,j}fi,j=fi1,j1+fi2,j1+fi1,j,考虑优化,无法矩阵乘法,多项式看上去也不行。倍增一下,f2i,j=∑k=0jfi,k×fi,j−kf_{2i,j}=\sum_{k=0}^jf_{i,k}\times f_{i,j-k}f2i,j=k=0jfi,k×fi,jk,还有中间两球组成一份,加上∑k=0j−1fi−1,k×fi−1,j−1−k\sum_{k=0}^{j-1}f_{i-1,k}\times f_{i-1,j-1-k}k=0j1fi1,k×fi1,j1k。这样的话我们发现,只需要维护fi、fi−1、fi−2f_i、f_{i-1}、f_{i-2}fifi1fi2就行了,直接上NTT,要注意kkk以后的项一定要清零!

### 推荐练习题目 对于希望提升算法竞赛技能的学习者而言,Codeforces提供了丰富的资源来帮助个人成长。根据Przemysław Dębiak的观点,在Codeforces上存在多种方式可以找到适合自己的训练题目[^1]。 一种有效的方法是从官方博客获取建议,这里经常会有经验分享以及针对不同水平选手的习题推荐。此外,Thanh Trung Nguyen提到过一篇关于如何科学地进行编程比赛训练的文章也值得阅读,其中包含了对提高解题能力有帮助的信息。 具体到寻找合适难度级别的题目方面: - 新手可以从Div.2 A/B级别开始尝试解决简单问题; - 随着技巧逐渐成熟,可逐步挑战更难一些的C/D类目下的任务; - 对于已经具备一定基础并寻求更大突破的人,则应该关注E/F甚至更高层次的比赛项目。 另外,Huang I-Wen在其FAQs中指出,除了参加正式赛事外,还可以利用平台上其他功能如Gym区内的历史赛题库来进行专项强化训练。 ```python # 示例:查询特定标签下的题目列表(Python伪代码) def get_problems_by_tag(tag_name): url = f"https://codeforces.com/problemset?tags={tag_name}" response = requests.get(url) soup = BeautifulSoup(response.text, "html.parser") problem_list = [] for row in soup.select(".datatable tr"): columns = row.find_all('td') if len(columns) > 0: title = columns[0].text.strip() difficulty = int(columns[-2].text.strip()) problem_list.append((title, difficulty)) return sorted(problem_list, key=lambda x:x[1]) print(get_problems_by_tag("dp")) # 动态规划相关题目为例 ```
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值