[题解](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;
}