[题解](更新中)(A-G)Atcoder Educational DP Contest
Atcoder Educational DP Contest
\(\textbf{A. Frog 1}\)
对于一块石头\(i(3 \le i \le N)\),\(i-1\)和\(i-2\)均能到达。
用\(f[i]\)表示跳到第\(i\)个石头用的最小体力消耗:
时间复杂度\(O(n)\)。
点击查看代码
#include<bits/stdc++.h> using namespace std; int n,h[200010],dp[200010]; int main(){ cin>>n; for(int i=1;i<=n;i++) cin>>h[i]; dp[2]=abs(h[2]-h[1]); for(int i=3;i<=n;i++){ dp[i]=min(abs(h[i-1]-h[i])+dp[i-1],abs(h[i-2]-h[i])+dp[i-2]); } cout<<dp[n]; return 0; }
\(\textbf{B. Frog 2}\)
与上一题类似,不过这一题每个石头都和前\(K\)个相关。
时间复杂度\(O(nk)\)。
点击查看代码
#include<bits/stdc++.h> using namespace std; int n,k; int h[100010],dp[100010]; int main(){ cin>>n>>k; for(int i=1;i<=n;i++) cin>>h[i]; for(int i=2;i<=k;i++){ int minn=1e9+7; for(int j=1;j<i;j++){ minn=min(minn,dp[j]+abs(h[j]-h[i])); } dp[i]=minn; } for(int i=k+1;i<=n;i++){ int minn=1e9+7; for(int j=1;j<=k;j++){ minn=min(minn,dp[i-j]+abs(h[i-j]-h[i])); } dp[i]=minn; } cout<<dp[n]; return 0; }
\(\textbf{C. Vacation}\)
用\(f[i][j]\)表示进行到第\(i\)天,这一天做了事件\(j\)的最大快乐值。枚举\(i,j\),对于每一个\(j\)再枚举昨天的事件\(k(k\neq j)\),求最大即可。
时间复杂度\(O(n)\)。
点击查看代码
#include<bits/stdc++.h> using namespace std; int n,a[100010][3]; int dp[100010][3]; int main(){ cin>>n; for(int i=1;i<=n;i++){ for(int j=0;j<3;j++){ cin>>a[i][j]; } } for(int i=0;i<3;i++) dp[1][i]=a[1][i]; for(int i=2;i<=n;i++){ for(int j=0;j<3;j++){ for(int k=0;k<3;k++){ if(k==j) continue; dp[i][j]=max(dp[i][j],dp[i-1][k]+a[i][j]); } } } cout<<max(max(dp[n][0],dp[n][1]),dp[n][2]); return 0; }
\(\textbf{D. Knapsack 1}\)
01背包问题,注意开long long
。
时间复杂度\(O(nw)\)。
点击查看代码
#include<bits/stdc++.h> using namespace std; int n,m,w[110],v[110]; long long dp[100010]; int main(){ cin>>n>>m; for(int i=1;i<=n;i++) cin>>w[i]>>v[i]; for(int i=1;i<=n;i++){ for(int j=m;j>=w[i];j--){ dp[j]=max(dp[j],dp[j-w[i]]+v[i]); } } cout<<dp[m]; return 0; }
\(\textbf{E. Knapsack 2}\)
与D相似,但是我们发现数据范围上有差异:
- 上一题\(1 \leq W \leq 10^5,1 \leq v_i \leq 10^9\)
- 这一题\(1 \leq W \leq 10^9,1 \leq v_i \leq 10^3\)
所以如果用\(O(nw)\)会超时,怎么利用物品价值范围小这一特点优化呢?
我们用\(f[i][j]\)表示选前\(i\)个物品,总价值恰好为\(j\)的背包最小容量。
如果仔细想想,就能发现这转化成了\(n\)个物品,背包容量为\(1\sim N*v(10^5)\),把价值当作每个物品的体积,求最小价值(这里就是原条件的体积了)的01背包正好装满问题。我们知道,正好装满的背包问题和普通背包问题就差在一个初始化,因为我们求的是最小值,所以把\(f\)初始化为极大值就可以啦~
最后从大到小枚举每个价值\(i\),如果\(f[n][i]\leq m\),则直接输出\(i\)即可。
代码把第\(1\)维滚掉了。
时间复杂度\(O(n^2v)\)。
点击查看代码
#include<bits/stdc++.h> using namespace std; int n,m,w[110],v[110]; int dp[100010]; int main(){ cin>>n>>m; for(int i=1;i<=n;i++){ cin>>w[i]>>v[i]; } memset(dp,0x3f,sizeof dp); dp[0]=0; for(int i=1;i<=n;i++){ for(int j=100000;j>=v[i];j--){ dp[j]=min(dp[j],dp[j-v[i]]+w[i]); } } for(int i=100000;i>=1;i--){ if(dp[i]<=m){ cout<<i; return 0; } } return 0; }
\(\textbf{F. LCS}\)
最长公共子序列模板题,动态规划的线性模型一文中已经有详解了,所以就不再赘述了。
时间复杂度\(O(n^2)\)(\(n\)是字符串长度)。
点击查看代码
#include<bits/stdc++.h> using namespace std; string a,b; int n,m,f[3010][3010]; char d[3010][3010]; int main(){ cin>>a>>b; n=a.size(),m=b.size(); a=' '+a,b=' '+b; for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ if(a[i]==b[j]){ d[i][j]='+'; f[i][j]=f[i-1][j-1]+1; }else{ if(f[i-1][j]>f[i][j-1]){ d[i][j]='U'; f[i][j]=f[i-1][j]; }else{ d[i][j]='L'; f[i][j]=f[i][j-1]; } } } } string ans=""; for(int x=n,y=m;x>0&&y>0;){ if(d[x][y]=='+'){ ans+=a[x]; x--,y--; }else if(d[x][y]=='U'){ x--; }else{ y--; } } reverse(ans.begin(),ans.end()); cout<<ans; return 0; }
\(\textbf{G. Longest Path}\)
记搜即可,最后搜出的点数\(-1\)就是边数。
点击查看代码
#include<bits/stdc++.h> using namespace std; int n,m; vector<int> p[100010]; bool b[100010]; int mem[100010]; int dfs(int pos){ //记忆化 if(mem[pos]) return mem[pos]; int len=p[pos].size(); int maxx=0; for(int i=0;i<len;i++){ maxx=max(maxx,dfs(p[pos][i])); } return mem[pos]=maxx+1; } int main(){ cin>>n>>m; for(int i=1;i<=m;i++){ int x,y; cin>>x>>y; p[y].push_back(x); b[x]=1; } int maxx=-1; for(int i=1;i<=n;i++){ if(!b[i]){ maxx=max(maxx,dfs(i)); } } cout<<maxx-1;//边=点-1 return 0; }
\(\textbf{T. Permutation}\)
据说这种dp叫插入dp。我们用\(f[i][j]\)表示前\(i\)个以\(1\sim i\)的排列来填充,第\(i\)位正好是\(j\)的方案数。我们填第\(i\)位时,可以把前面所有\(\geq j\)的值都\(+1\),这样填进去的\(j\)正好弥补了空缺,让前\(i\)位成为一个排列。所以得到状态转移方程:
\(f[i][j]= \begin{cases} \sum_{k=1}^{j-1}\ f[i-1][k] & s[i]='<'\\ \sum_{k=j}^{i-1}\ f[i-1][k] & s[i]='>' \end{cases} \)
最终答案即为\(\sum_{i=1}^{n}f[n][i]\)。但是如果这样\(O(n^3)\)会超时,前缀和优化成\(O(n^2)\)即可通过。
点击查看代码
#include<bits/stdc++.h> #define mod 1000000007 #define int long long using namespace std; int n,f[3010][3010],sum[3010][3010]; string s; signed main(){ cin>>n>>s; s=" "+s; sum[1][1]=1; for(int i=2;i<=n;i++){ for(int j=1;j<=i;j++){ if(s[i]=='<'){ f[i][j]=(sum[i-1][j-1])%mod; }else{ f[i][j]=(sum[i-1][i-1]-sum[i-1][j-1]+mod)%mod; } sum[i][j]=(sum[i][j-1]+f[i][j])%mod; } } cout<<sum[n][n]; return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效