PKU演習問(8/10)
このままじゃ話にならない。何とかしないと!!
No. | 問題名 | 問題の種類および解法 | 難易度 |
---|---|---|---|
2486 | Apple Tree | 動的計画法(Tree DP+ナップサック問題) | ★★★★★ |
1985 | Cow Marathon | 動的計画法(Tree DP) | ★★★☆☆ |
2264 | Advanced Fruits | 動的計画法・バックトラック | ★★★☆☆ |
2486 Apple Tree
問題概要
ノード数nの木の、各ノードにりんごがそれぞれいくつかずつ実っている。
1番のノードから出発して、k回以下、ノードを移動して、そのノードにあるりんごを全て取る。(りんごを取れるのは最初の一回のみ)
取れるりんごの最大の個数を求めよ。
n≦100,k≦200を満たす。
解法
自力で思いついたのはO(n^3k^2)のクソ解法のみ(もちろんTLE)。
ぐぐって見つけた解説を要約すると以下のような感じ。
- dp2[i][j]に、ノードiを根とする部分木からj歩でとれる最大のりんごの数
- dp[i][j]に、ノードiを根とする部分木で、最後にiまで戻ってきてj歩でとれる最大のりんごの数
をもっておく。
そして、
dp[i][j]=max{dp[i][k]+dp[子][i-k-2]}および
dp2[i][j]=max{dp[i][k]+dp2[子][i-k-2]}
の関係からテーブルを更新していくことで、O(n^2k^2)の計算量で答えが求まる。らしい。
このDPはナップサック問題的であるらしい。そんな気もするけどあんまりピンと来ないorz
ソースコード
int n,k,rng[100]; vector<vi> e; bool V[100]; int dp[101][201],dp2[101][201]; void dfs(int c) { V[c]=1; fr(it,e[c])if(!V[*it]) { dfs(*it); for(int i=k;i>=0;i--)for(int j=0;j<i;j++) { if(i>=2&&j<=i-2) { dp[c][i]=max(dp[c][i],dp[c][j]+dp[*it][i-2-j]); dp2[c][i]=max(dp2[c][i],dp2[c][j]+dp[*it][i-2-j]); } if(i>0&&j<k) dp2[c][i]=max(dp2[c][i],dp[c][j]+dp2[*it][i-1-j]); } } } int main() { while(~scanf("%d%d",&n,&k)) { rep(i,n)scanf("%d",rng+i); e.clear(); e.resize(n); rep(i,n-1) { int a,b; scanf("%d%d",&a,&b); a--; b--; e[a].pb(b); e[b].pb(a); } rep(i,n)V[i]=0; rep(i,n)rep(j,k+1)dp[i][j]=dp2[i][j]=rng[i]; dfs(0); printf("%d\n",max(dp[0][k],dp2[0][k])); } return 0; }
1985 Cow Marathon
問題概要
平面状にn個の牧場があり、m個の道でそれらがつながっている。
道は「牧場Aの特定の方向(N,E,W,S)距離dに牧場B」がある、という形式であたえられる。
このとき、最も遠い二つの牧場間の道程を求めよ。
n,m≦40000かつ、二つの牧場間の道程は一意に定まる。
解法
二つの牧場間の道程が一意に定まるので、与えられるグラフは森。
したがって以下のようなTree DPをすれば解ける。
最も遠い牧場間のペアは、(一番遠い葉)-根-(二番目に遠い葉)の場合と、
間に根を通らないのペアの場合のいずれか。
後者の場合は部分木について再帰することで求められる。
根から出ているノードが2個より少ない場合などの場合分けはしっかりやること。
ソースコード
int n,m; int e[40000][4],d[40000][4],ne[40000]; bool V[40000]; int dp[40000]; int far(int c)//根から最も遠い葉までの距離を求める { V[c]=1; if(dp[c]>=0)return dp[c]; int ret=0; rep(i,ne[c])if(!V[e[c][i]])ret=max(ret,far(e[c][i])+d[c][i]); return dp[c]=ret; } int rec(int c,int p) { V[c]=1; int ret=0,a=-1,b=-1; rep(i,ne[c])if(e[c][i]!=p)//根のそれぞれの子から1番遠い葉を調べ、上位二つを覚える { int t=far(e[c][i])+d[c][i]; if(a<t)b=a,a=t; else if(b<t)b=t; } ret=max(ret,max(a,0)+max(b,0)); rep(i,ne[c])if(e[c][i]!=p)ret=max(ret,rec(e[c][i],c));//部分木も探索 return ret; } int main() { scanf("%d%d",&n,&m); rep(i,n)ne[i]=0,V[i]=0,dp[i]=-1; rep(i,m) { int a,b,c; char dm; scanf("%d%d%d %c",&a,&b,&c,&dm); a--; b--; e[a][ne[a]]=b; d[a][ne[a]]=c; ne[a]++; e[b][ne[b]]=a; d[b][ne[b]]=c; ne[b]++; } int ans=0; rep(i,n)if(!V[i])ans=max(ans,rec(i,i)); printf("%d\n",ans); return 0; }
2264 Advanced Fruits
問題概要
二つの文字列A,Bを部分列として含む長さ最小の文字列Cを出力せよ。
そのような文字列が複数ある場合どれを出力してもよい。
A,Bの長さは100以下である。
ソースコード
int a,b; char A[101],B[101]; int dp[101][101]; int main() { while(~scanf(" %s %s",A,B)) { a=strlen(A); b=strlen(B); rep(i,a+1)rep(j,b+1)dp[i][j]=inf; rep(i,101)dp[0][i]=dp[i][0]=i; for(int i=1;i<=a;i++)for(int j=1;j<=b;j++) { dp[i][j]=min(dp[i][j],dp[i-1][j-1]+(A[i-1]!=B[j-1])+1); dp[i][j]=min(dp[i][j],min(dp[i-1][j],dp[i][j-1])+1); } char ans[201]; int len=dp[a][b]; ans[len]=0; while(a||b) { int c=dp[a][b]; if(a>0&&b>0&&c==dp[a-1][b-1]+(A[a-1]!=B[b-1])+1) { if(A[a-1]!=B[b-1])ans[--len]=A[--a],ans[--len]=B[--b]; else ans[--len]=A[--a],--b; } else if(a>0&&c==dp[a-1][b]+1)ans[--len]=A[--a]; else if(b>0&&c==dp[a][b-1]+1)ans[--len]=B[--b]; } printf("%s\n",ans); } return 0; }