dp练习
P2848 [USACO16DEC] Cow Checklist G
感谢张田让我看到这么多 blue dp。
这道题的题目很糊。大概就是说有 个 , 个 ,每个点都在同一平面坐标系第一象限内。让你访问所有的点,访问顺序满足:
-
开始是 点,结束是 点;
-
两个序列分别按字典序访问。
每次走过的权值是两点欧几里得距离的平方。
那么我们可以这样设状态:
设 表示 走过 个而 走过 个,现在处在 。
同理。
那么每个 都是从 状态而来,每个 都是从 状态而来,那么就有转移:
初始是 ,结束是 。
然后就做出来了。
P2875 [USACO07FEB] The Cow Lexicon S
原本以为要 hash,结果发现不用。
考虑位置 的答案可以从哪里来,找一个位置 ,分成 和 ,其中 的答案可以拆成字典内的和噪音组成。
那么考虑对于每个字典内的字符串,找到主串中最小覆盖这个字符串的子串,也就是满足该字符串是主串这个子串的子序列的子串。
令匹配到的位置是 , 中有 个不匹配的位置,那么有转移:
P2883 [USACO07MAR] Cow Traffic S
考虑一条边被经过的次数是什么。
按照乘法原理,是以它左边节点为终点的路径数乘以右边节点为起点的路径数。
那么,怎么计算以任意一点为终点的路径个数呢?
考虑这个 DAG,对它进行拓扑排序,那么两点的转移显而易见:。
然后就做出来了。
那另一部分呢?
考虑建一个反图,再做一次上面的操作就对了。
答案就是 。
P2885 [USACO07NOV] Telephone Wire G
数据过水、
因为每个电线杆的高度都极其的小,那么我们直接设 表示第 个电线杆,并且这个电线杆高度为 。
那么转移就很显然了:
P2899 [USACO08JAN] Cell Phone Network G
很好的树形 dp。
我们考虑一个节点可能的信号接收状态,无非就是三种:
-
父亲是信号塔
-
自己是信号塔
-
自己的儿子之一是信号塔
那转移就很好写了:
void dfs(int u,int f) { dp[1][u]=1;//自己是发射塔 int mi=0; for(int v:e[u]) { if(v==f) continue; dfs(v,u); dp[1][u]+=min(min(dp[0][v],dp[1][v]),dp[2][v]); dp[2][u]+=min(dp[0][v],dp[1][v]);//父亲是发射塔,那儿子的父亲就不是发射塔了 if((dp[1][mi]-min(dp[0][mi],dp[1][mi]))>(dp[1][v]-min(dp[0][v],dp[1][v]))) mi=v;//最小权值的儿子 } for(int v:e[u]) { if(v==f||v==mi) continue; dp[0][u]+=min(dp[1][v],dp[0][v]);//儿子是发射塔 } dp[0][u]+=dp[1][mi]; }
P2893 [USACO08FEB] Making the Grade G
有个小结论?
结论就是修改完的海拔可以是之前已经出现过的。
考虑一个很朴素的 dp, 表示在第 个点,修改完高度为 的最小答案。
可以在枚举 的时候做一个最小值的前缀和,能优化一个循环。
复杂度 ,其中 是值域。但是值域范围是 ,这样做只有 分。
那我们用上面的结论,把海拔离散化了,每次都找存在的海拔高度就好了。时间复杂度 。并且可以滚掉一维优化空间。
P2905 [USACO08OPEN] Crisis on the Farm G
这就是那种,dp 超级简单,然后回溯特别奇妙的 dp。
单的只输出 ans1 就是黄绿题,加上字典序最小的方案输出就是蓝紫。。。
具体地,我们设 表示第 次移动时,已经向左移动 ,向上移动 的最大拯救个数。
那我们可以预处理出每个牛塔和每个草垛之间的移动距离。并且,为了避免负数,可以把零点(起始状态)确定为 ,因为 也就是最多支持横向或竖向的 次移动。
那么每次转移,就可以从上一次移动 的四个方向而来,并且这个方案能拯救的个数应该加上先前预处理的 。
这样 dp 就写出来了。
考虑怎么找字典序最小的路径,这里正序找和逆序找都可以。我的想法,是先逆序,找出每个状态的前一个状态,记录转移方向。然后在这之前,用计算出来的 ans1 标记路径终点,这样就能做到答案正确,并且状态覆盖完全了。
找答案的过程是正序的。从状态 开始,按照字典序较小的走,因为标记过的路径一定能到达终点并且答案最小,那么我们可以直接按照字典序顺序依次递推,递推到 即可。
for(int i=1;i<=61;i++) for(int j=1;j<=61;j++) if(ans==dp[k][i][j]) pth[k][i][j]='A'; cout<<ans<<"\n"; for(int i=k-1;i>=0;i--) { for(int j=1;j<=61;j++) { for(int o=1;o<=61;o++) { for(int d=1;d<=4;d++) if(dp[i][j][o]+cnt[j+dx[d]][o+dy[d]]==dp[i+1][j+dx[d]][o+dy[d]]&&pth[i+1][j+dx[d]][o+dy[d]]<'Z') pth[i][j][o]=cc[d]; } } } int i=31,j=31; for(int o=0;o<k;o++) { cout<<pth[o][i][j]; if(pth[o][i][j]=='E') i--; else if(pth[o][i][j]=='W') i++; else if(pth[o][i][j]=='S') j++; else if(pth[o][i][j]=='N') j--; }
P2915 [USACO08NOV] Mixed Up Cows G
把状压 dp 好好复习了一下。忘得差不多了都。
首先,观察数据范围,,那不用说,直接上状压。
通过观察暴力 dfs 的剪枝优化,我们可以知道一个序列如果已经,满足条件,那么下一个加入进序列的数只需要满足和当前序列最后一个数的差大于 即可。
那如果我们现在序列的状态一定,并且已知最后一位的数是哪个,那接下来加入的数就可以转移了:
设 表示当前状态 下,序列的最后一位是 ,那通过填表,我们可以得到转移:
这里用 同样借指了一下 。
然后答案就是 。
P3052 [USACO12MAR] Cows in a Skyscraper G
同样是状压,甚至觉得比前者难。。。
考虑你现在已经选了状态 乘坐电梯,那如果分了 组,那我们可以认为前 组已经满了,要加进一个数,它会仍然留在第 组,也可能会进入 组。
那可以设 表示现在选取状态为 时的最小划分次数, 表示此时没有填满的那个电梯的最小体积。
那转移就很简单了,你现在要再加入一个数,那 的状态就可以分类讨论一下,如果 ,那当前状态答案 ,否则不变,和原来存的答案取个最小值即可。
P2943 [USACO09MAR] Cleaning Up G
加上优化可过。
考虑把每只奶牛分成各自的组,那答案是 。显然,要想更优,答案一定要小于 。所以当前选取的集合元素个数要小于 ,然后转移就行了。时间复杂度 。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!