[题解](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\)个石头用的最小体力消耗:

\[f[i]=min(abs(h[i]-h[i-1])+f[i-1],abs(h[i]-h[i-2])+f[i-2])\qquad i\ge 3 \]

时间复杂度\(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[i]\leq m\),则直接输出\(i\)即可。

时间复杂度\(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;
}
posted @ 2024-04-14 21:37  Sinktank  阅读(12)  评论(0编辑  收藏  举报
★CLICK FOR MORE INFO★ TOP-BOTTOM-THEME
Copyright © 2023 ~ 2024 Sinktank - 1328312655@qq.com
Illustration from 稲葉曇『リレイアウター/Relayouter/中继输出者』,by ぬくぬくにぎりめし.