poj 動態規劃專題練習
http://poj.org/problem?id=2336
大意是要求一艘船將m個車運到對岸所消耗的最短時間和最小次數
定義dp[i][j]運送前i個車,當前船上有j個車所消耗的時間,非常容易得到如下ac代碼的推導
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int maxn=1508; const int INF=0x7f7f7f7f; int dp[maxn][maxn]; int w[maxn]; int main() { int i,j,cas,n,m,t; scanf("%d",&cas); while(cas--) { scanf("%d%d%d",&n,&t,&m); for(i=1;i<=m;i++) scanf("%d",&w[i]); for(j=1;j<=n;j++) dp[1][j]=w[1]+t; int sum=w[1]+t,ans=1; for(i=2;i<=m;i++) { // printf("fuck %d\n",sum); dp[i][1]=max(sum+t,w[i])+t; for(j=2;j<=n;j++) { if(w[i]>dp[i-1][j-1]-t) dp[i][j]=dp[i-1][j-1]+w[i]-w[i-1]; else dp[i][j]=dp[i-1][j-1]; } sum=INF; for(j=1;j<=n;j++) if(sum>dp[i][j]) sum=dp[i][j]; } /* for(i=1;i<=m;i++) { for(j=1;j<=n;j++) printf("%dfuck%d ",num[i][j],dp[i][j]); printf("\n"); }*/ ans=m/n; if(m%n) ans++; printf("%d %d\n",sum,ans); } return 0; }
但似乎看起來還是有些不對?這裏的狀態轉移沒有類似於dp[i][j]=max(dp[i][j],。。)這樣取最優狀態的做法,我們這樣定義狀態可能是多余的。事實上只要dp[i]就可以記錄狀態了。除了dp以外還有貪心做法:我們運送所有車輛的最短時間,就是要最後一輛車的等待時間最小:第一次運m%n個,然後每次運n個,就可以得到最優解了
poj 3162 Walking Race
這題可以概括為兩步:
1.求樹上點所能到達的最遠點,這類問題可以說和求樹上點的直徑類似。樹上任意點的最遠點顯然就是直徑的兩個端點之一
2.在一個序列中,求滿足區間內最大值最小值之差不超過m的最長區間的大小
問題1是經典的樹規,dp[u]為向下走能走到的最遠值,dp1[u]為不包括dp[u]的第一個結點所能走到的最大值,tp[u]為向上走所能走到的最大值
最遠點就是max(dp[u],tp[u]),分類討論可得tp[u]。轉移見代碼
問題2也是經典的規劃問題,用單調隊列可以維護區間最大值和最小值
我們單調隊列的做法是:開兩條單調隊列,一條維護最大值一條維護最小值。插入元素時,若插入新元素到隊尾破壞了隊列的單調性,則拋棄原有的隊尾元素直到新元素的插入不破壞隊列的單調性。每次拿出兩個隊首的元素進行判斷,若max-min>m,刪除下標靠前的隊首元素,重新判斷條件是否成立。若不成立繼續刪直到成立為止。 若max-min>m,考慮更新答案。
隊列的插入刪除手段實際上是根據所求的一種貪心--當更大更靠右的元素在隊首,之前入隊的元素可以拋棄(嘛,)
#include<cstdio> #include<cstring> #include<algorithm> #include<queue> using namespace std; const int maxn=1000008; struct fuck{ int u,v,w,next; }edge[maxn<<1]; int tol; int head[maxn]; void init() { tol=0; memset(head,-1,sizeof(head)); } void addedge(int u,int v,int w) { edge[tol].u=u; edge[tol].v=v; edge[tol].w=w; edge[tol].next=head[u]++; head[u]=tol++; } int dp[maxn],dpx[maxn],tp[maxn],dp1[maxn]; void dfs(int u,int pre) { int i; dp[u]=0; for(i=head[u];i!=-1;i=edge[i].next) { int v=edge[i].v; if(edge[i].v==pre) continue; dfs(v,u); if(dp[v]+edge[i].w>dp[u]) { dp[u]=dp[v]+edge[i].w; dpx[u]=v; } } } void dfs1(int u,int pre) { int i; dp1[u]=0; for(i=head[u];i!=-1;i=edge[i].next) { int v=edge[i].v; if(v==pre) continue; dfs1(v,u); if(v!=dpx[u]&&dp[v]+edge[i].w>dp1[u]) dp1[u]=dp[v]+edge[i].w; } } void dfs2(int u,int pre) { int i; for(i=head[u];i!=-1;i=edge[i].next) { int v=edge[i].v; if(v==pre) continue; if(dpx[u]==v) tp[v]=max(dp1[u],tp[u])+edge[i].w; else tp[v]=max(dp[u],tp[u])+edge[i].w; dfs2(v,u); } } int ans[maxn]; int mx[maxn]; int mi[maxn]; int solve(int n,int m) { int mxf=0,mxb=0; int mif=0,mib=0; int res=0; int left=1; for(int i=1;i<=n;i++) { while(mxf>mxb&&ans[mx[mxf-1]]<=ans[i]) mxf--; mx[mxf++]=i; while(mif>mib&&ans[mi[mif-1]]>=ans[i]) mif--; mi[mif++]=i; if(ans[mx[mxb]]-ans[mi[mib]]<=m) { int sum=i-left+1; if(sum>res) res=sum; // printf("%d %d\n",left,i); } else { // printf("%d\n",i); while(mib<mif&&mxb<mxf&&ans[mx[mxb]]-ans[mi[mib]]>m) { if(mx[mxb]>mi[mib]) { left=mi[mib]+1; mib++; } else { left=mx[mxb]+1; mxb++; } } } } return res; } int main() { int n,m,u,v; while(scanf("%d%d",&n,&m)==2) { init(); for(int i=1;i<n;i++) { scanf("%d%d",&u,&v); addedge(u,i+1,v); addedge(i+1,u,v); } dfs(1,-1); dfs1(1,-1); tp[1]=0; dfs2(1,-1); for(int i=1;i<=n;i++) ans[i]=max(dp[i],tp[i]); // for(int i=1;i<=n;i++) printf("%d ",ans[i]); int res=solve(n,m); printf("%d\n",res); } return 0; }
poj1947 Rebuilding Roads
經典樹規,求在一顆樹中獲得一顆節點數為p的子樹最少需要砍掉多少條邊
這題有兩個沒有說明的地方,其一是,數據中給出的子樹總是以1為根的。其二是,所求的子樹可以不包含根節點
定義dp[u][i][j]為以u為根節點的樹處理了前i個子節點並獲得一顆j節點的子樹最少需要砍掉的邊,初始dp[u][0][1]為u節點的度數,然後有以下轉移:
if(dp[u][idx-1][j-x]!=INF&&dp[v][du[v]][x]!=INF) { dp[u][idx][j]=min(dp[u][idx-1][j-x]+dp[v][du[v]][x]-1,dp[u][idx][j]); }
之後根據枚舉根節點可以這樣求得答案:
int ans=dp[1][du[1]][p]; for(int i=2;i<=n;i++) if(dp[i][du[i]][p]+1<ans) ans=dp[i][du[i]][p]+1;
dp的過程也不難理解,就是相當於將每顆子樹看成一個分組,將每顆子樹可能取的值當作分組中的物品然後跑一遍分組背包。理解分組背包的情況下不難想到
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int maxn=158; const int INF=0x7f7f7f7f; struct fuck{ int u,v,next; }edge[maxn<<1]; int tol; int head[maxn]; void init() { tol=0; memset(head,-1,sizeof(head)); } void addedge(int u,int v) { edge[tol].u=u; edge[tol].v=v; edge[tol].next=head[u]; head[u]=tol++; } int dp[maxn][maxn][maxn]; int du[maxn]; int num[maxn]; void dfs(int u,int p) { int i; dp[u][0][1]=du[u]; num[u]=1; int idx=0; for(i=head[u];i!=-1;i=edge[i].next) { int v=edge[i].v; dfs(v,p); num[u]+=num[v]; idx++; for(int x=1;x<=p;x++) dp[u][idx][x]=dp[u][idx-1][x]; for(int j=1;j<=p;j++) for(int x=1;x<j;x++) { if(dp[u][idx-1][j-x]!=INF&&dp[v][du[v]][x]!=INF) { dp[u][idx][j]=min(dp[u][idx-1][j-x]+dp[v][du[v]][x]-1, dp[u][idx][j]); } } } } int main() { int n,p,u,v; while(scanf("%d%d",&n,&p)==2) { memset(dp,INF,sizeof(dp)); init(); memset(du,0,sizeof(du)); for(int i=1;i<n;i++) { scanf("%d%d",&u,&v); addedge(u,v); du[u]++; } dfs(1,p); int ans=dp[1][du[1]][p]; for(int i=2;i<=n;i++) if(dp[i][du[i]][p]+1<ans) ans=dp[i][du[i]][p]+1; printf("%d\n",ans); } return 0; }
poj 動態規劃專題練習