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;
}