紫书第9章 动态规划初步
9.1 数字三角形
9.1.2 记忆化搜索与递推
方法1:递归计算。程序如下(需注意边界处理):
int solve(int i,int j) { return a[i][j] + (i==n ?0:max(solve(i+1,j),solve(i+1,j+1)); }
用直接递归的方法计算状态转移方程,效率往往十分低下。其原因是相同的子问题被重复计算了多次。
方法2:递推计算。程序如下(需再次注意边界处理):
int i, j; for(j = 1; j <= n; j++) d[n][j] = a[n][j]; for(i = n-1; i >= 1; i——) for(j = 1; j <= i; j++) d[i][j] = a[i][j] + max(d[i+1][j],d[i+1][j+1]);
程序的时间复杂度显然是O(n2),但为什么可以这样计算呢?原因在于:i是 逆序枚举的,因此在计算d[i][j]前,它所需要的d[i+1][j]和d[i+1][j+1]一定已经计算出来了。
方法3:记忆化搜索。程序分成两部分。首先用“memset(d,-1,sizeof(d));”把d全部初始化为-1,然后编写递归函数(1):
int solve(int i, int j){ if(d[i][j] >= 0) return d[i][j]; return d[i][j] = a[i][j] + (i == n ? 0 : max(solve(i+1,j),solve(i+1,j+1)));
9.2 DAG上的动态规划
9.2.1 DAG模型
嵌套矩形问题。有n个矩形,每个矩形可以用两个整数a、b描述,表示它的长和宽。矩
形X(a,b)可以嵌套在矩形Y(c, d)中,当且仅当a<c,b<d,或者b<c,a<d(相当于把矩
形X旋转90°)。例如,(1, 5)可以嵌套在(6, 2)内,但不能嵌套在(3, 4)内。你的任务是选出尽
量多的矩形排成一行,使得除了最后一个之外,每一个矩形都可以嵌套在下一个矩形内。如
果有多解,矩形编号的字典序应尽量小。
int dp(int i) { int& ans = d[i]; if(ans>0) return ans; for(int j = 0;j<n;j++) { if(G[i][j]) ans = max(ans,dp(j)+1); } return ans; }
原题还有一个要求:如果有多个最优解,矩形编号的字典序应最小。还记得第6章中的
例题“理想路径”吗?方法与其类似。将所有d值计算出来以后,选择最大d[i]所对应的i。如
果有多个i,则选择最小的i,这样才能保证字典序最小。接下来可以选择d(i)=d(j)+1且
(i,j)∈E的任何一个j。为了让方案的字典序最小,应选择其中最小的j
void print_ans(int i) {2 printf("%d ", i); for(int j = 1; j <= n; j++) if(G[i][j] && d[i] == d[j]+1){ print_ans(j); break; } }
9.2.3 固定终点的最长路和最短路
int dp(int s) { int& ans = d[s]; if(ans>=0) return ans; for(int i = 0;i<n;i++) { if(s>=v[i]) ans = max(ans,dp(s-v[i])+1); } return ans; }
例题9-1 城市里的间谍(A Spy in the Metro, ACM/ICPC World Finals 2003,
UVa1025)
经典dp问题,和状态有关,数字又不大,可以直接dp递推。
runtime_error有可能是指针越界,要注意
#include <iostream> #include <string> #include <cstring> #include <cstdlib> #include <cstdio> #include <cmath> #include <algorithm> #include <stack> #include <queue> #include <cctype> #include <vector> #include <iterator> #include <set> #include <map> #include <sstream> using namespace std; #define mem(a,b) memset(a,b,sizeof(a)) #define pf printf #define sf scanf #define spf sprintf #define pb push_back #define debug printf("!\n") #define INF 10000 #define MAXN 5010 #define MAX(a,b) a>b?a:b #define blank pf("\n") #define LL long long #define ALL(x) x.begin(),x.end() #define INS(x) inserter(x,x.begin()) #define pqueue priority_queue int t[205],d1[60],d2[60],has_train[205][60][2],dp[205][60]; int main() { int i,j,T,M1,M2,n,kase=0; while(sf("%d",&n)==1 && n) { mem(dp,0); mem(d1,0); mem(d2,0); mem(has_train,0); mem(t,0); sf("%d",&T); for(i=0;i<n-1;i++) sf("%d",&t[i]); sf("%d",&M1); for(i=0;i<M1;i++) { sf("%d",&d1[i]); int sum_time = 0; has_train[d1[i]][0][0]=1; for(j=0;j<n-1;j++) { sum_time+=t[j]; if(sum_time+d1[i]<=T) has_train[sum_time+d1[i]][j+1][0]=1; else break; } } sf("%d",&M2); for(i=0;i<M2;i++) { sf("%d",&d2[i]); has_train[d2[i]][n-1][1]=1; int sum_time = 0; for(j=n-2;j>=0;j--) { sum_time+=t[j]; if(d2[i]+sum_time<=T) has_train[d2[i]+sum_time][j][1]=1; else break; } } for(i=0;i<n-1;i++) dp[T][i] = INF; dp[T][n-1]=0; for(i=T-1;i>=0;i--) { for(j=0;j<n;j++) { dp[i][j] = dp[i+1][j]+1; if(j<n-1 && has_train[i][j][0] && i+t[j]<=T) dp[i][j] = min(dp[i][j],dp[i+t[j]][j+1]); if(j>0 && has_train[i][j][1] && i+t[j-1]<=T) dp[i][j] = min(dp[i][j],dp[i+t[j-1]][j-1]); } } pf("Case Number %d: ",++kase); if(dp[0][0]>=INF) pf("impossible\n"); else pf("%d\n",dp[0][0]); } }
递归版,测试结果对,不过WA了
int re(int i,int j) { if(i==T) return dp[i][j]; int& ans = dp[i][j]; if(vis[i][j]) return ans; vis[i][j] = 1; ans = re(i+1,j)+1; for(int k = n-1;k>=0;k--) { if(k>0 && has_train[i][k][1] && i+t[k-1]<=T) ans = min(ans,re(i+t[k-1],k-1)); if(k<n-1 && has_train[i][k][0] && i+t[k]<=T) ans = min(ans,re(i+t[k],k+1)); } return ans; }
例题9-2 巴比伦塔(The Tower of Babylon, UVa 437)
#include <iostream> #include <string> #include <cstring> #include <cstdlib> #include <cstdio> #include <cmath> #include <algorithm> #include <stack> #include <queue> #include <cctype> #include <vector> #include <iterator> #include <set> #include <map> #include <sstream> using namespace std; #define mem(a,b) memset(a,b,sizeof(a)) #define pf printf #define sf scanf #define spf sprintf #define pb push_back #define debug printf("!\n") #define INF 10000 #define MAXN 5010 #define MAX(a,b) a>b?a:b #define blank pf("\n") #define LL long long #define ALL(x) x.begin(),x.end() #define INS(x) inserter(x,x.begin()) #define pqueue priority_queue int n; struct cube { int x,y,h; void setc(int a,int b,int c) {x=a;y=b;h=c;} }c[100]; int g[100][100],d[100]; int check(int i,int j) { if(c[i].x>c[j].x && c[i].y>c[j].y || c[i].x>c[j].y && c[i].y>c[j].x) return 1; return 0; } int dp(int i) { int& ans = d[i]; if(ans>0) return ans; ans = c[i].h; for(int j =0;j<n;j++) { if(g[i][j]) ans = max(ans,dp(j)+c[i].h); } return ans; } int main() { int i,j,kase=1; while(sf("%d",&n)==1 && n) { mem(d,0); mem(c,0); mem(g,0); for(i=0;i<n;i++) { int a,b,d; sf("%d %d %d",&a,&b,&d); c[i].setc(a,b,d); c[i+n].setc(b,d,a); c[i+2*n].setc(d,a,b); } n*=3; for(i=0;i<n;i++) { for(j=i+1;j<n;j++) { g[i][j] = check(i,j); g[j][i] = check(j,i); } } int res = 0; for(i=0;i<n;i++) res = max(res,dp(i)); printf("Case %d: maximum height = %d\n", kase++, res); } }
递推:
sort(c,c+n,cmp); for(i=0;i<n;i++) { for(j=i+1;j<n;j++) { g[i][j] = check(i,j); g[j][i] = check(j,i); } } for(i=0;i<n;i++) d[i] = c[i].h; for(i=0;i<n;i++) { int tmp = 0; for(j=i;j<n;j++) { if(g[j][i]) { d[j] = max(d[j],d[i]+c[j].h); } } } int res = 0; for(i=0;i<n;i++) { if(res<d[i]) res=d[i]; }
例题9-3 旅行(Tour, ACM/ICPC SEERC 2005, UVa1347)
注意状态的设定能使问题简单化
另外这题算法导论里也有,叫双调欧几里得旅行商问题
用的是递推的方法:http://blog.csdn.net/xiajun07061225/article/details/8092247
#include <iostream> #include <string> #include <cstring> #include <cstdlib> #include <cstdio> #include <cmath> #include <algorithm> #include <stack> #include <queue> #include <cctype> #include <vector> #include <iterator> #include <set> #include <map> #include <sstream> using namespace std; #define mem(a,b) memset(a,b,sizeof(a)) #define pf printf #define sf scanf #define spf sprintf #define pb push_back #define debug printf("!\n") #define INF 10000 #define MAXN 5010 #define MAX(a,b) a>b?a:b #define blank pf("\n") #define LL long long #define ALL(x) x.begin(),x.end() #define INS(x) inserter(x,x.begin()) #define pqueue priority_queue int n; double d[1000][1000]; struct point { int x,y; }p[1000]; double getd(int a,int b) { return sqrtf((p[a].x-p[b].x)*(p[a].x-p[b].x)+(p[a].y-p[b].y)*(p[a].y-p[b].y)); } double dp(int i,int j) { double &ans = d[i][j]; if(ans>0) return ans; if(i==n-2) { return getd(n-2,n-1)+getd(j,n-1); } ans = min(dp(i+1,j)+getd(i,i+1),dp(i+1,i)+getd(j,i+1)); return ans; } int main() { int i,j,kase=1; while(sf("%d",&n)==1 && n) { mem(d,0); mem(p,0); for(i=0;i<n;i++) sf("%d%d",&p[i].x,&p[i].y); pf("%.2lf\n",dp(1,0)+getd(1,0)); } }
递推:
分类讨论,状态递推
int main() { int i,j,kase=1; while(sf("%d",&n)==1 && n) { mem(d,0); mem(p,0); for(i=0;i<n;i++) sf("%d%d",&p[i].x,&p[i].y); d[1][0] = getd(0,1); for(i=2;i<n;i++) { for(j=0;j<=i-2;j++) { d[i][j] = d[i-1][j] + getd(i,i-1); } d[i][i-1] = INF; for(j=0;j<=i-2;j++) { d[i][i-1] = min(d[i][i-1],d[i-1][j]+getd(j,i)); } } d[n-1][n-1] = d[n-1][n-2]+getd(n-1,n-2); pf("%.2lf\n",d[n-1][n-1]); } }
例题9-4 单向TSP(Unidirectional TSP, UVa 116)
字典序最小,所以递推比较好,遍历时注意按顺序遍历
#include <iostream> #include <string> #include <cstring> #include <cstdlib> #include <cstdio> #include <cmath> #include <algorithm> #include <stack> #include <queue> #include <cctype> #include <vector> #include <iterator> #include <set> #include <map> #include <sstream> using namespace std; #define mem(a,b) memset(a,b,sizeof(a)) #define pf printf #define sf scanf #define spf sprintf #define pb push_back #define debug printf("!\n") #define INF 1<<30 #define MAXN 5010 #define MAX(a,b) a>b?a:b #define blank pf("\n") #define LL long long #define ALL(x) x.begin(),x.end() #define INS(x) inserter(x,x.begin()) #define pqueue priority_queue int n,m; int mp[15][105],d[15][105],nt[105][105]; int main() { int i,j,kase=1; while(sf("%d%d",&n,&m)==2) { for(i=0;i<n;i++) { for(j=0;j<m;j++) { sf("%d",&mp[i][j]); } } int ans = INF,first = 0; for(j=m-1;j>=0;j--) { for(i=0;i<n;i++) { if(j==m-1) d[i][j] = mp[i][j]; else { int row[3]={i-1,i,i+1}; if(i==0) row[0] = n-1; if(i==n-1) row[2] = 0; sort(row,row+3); d[i][j] = INF; for(int k = 0;k<3;k++) { int v = d[row[k]][j+1]+mp[i][j]; if(v<d[i][j]) { d[i][j] = v; nt[i][j] = row[k]; } } } if(j==0 && d[i][j]<ans) { first = i; ans = d[i][j]; } } } // for(i=0;i<n;i++) // { // for(j=0;j<m;j++) // { // pf("%lld ",d[i][j]); // } // blank; // } pf("%d",first+1); int v = nt[first][0]; for(int i = nt[first][0], j = 1; j < m; i = nt[i][j], j++) printf(" %d", i+1); blank; pf("%d\n",ans); } }
递归:
int n,m; int mp[15][105],d[15][105],nt[105][105]; int dp(int i,int j) { int& ans = d[i][j]; if(j==m-1) return mp[i][j]; if(ans>0) return d[i][j]; int row[3]={i-1,i,i+1}; if(i==0) row[0] = n-1; if(i==n-1) row[2] = 0; sort(row,row+3); ans = INF; for(int k = 0;k<3;k++) { ans = min(ans,dp(row[k],j+1)+mp[i][j]); } return ans; } int main() { int i,j,kase=1; while(sf("%d%d",&n,&m)==2) { for(i=0;i<n;i++) { for(j=0;j<m;j++) { sf("%d",&mp[i][j]); } } mem(d,0); int res =INF; for(i=0;i<n;i++) res = min(res,dp(i,0)); pf("%d\n",res); } }
例题9-5 劲歌金曲(Jin Ge Jin Qu [h]ao, Rujia Liu's Present 6, UVa 12563)
这题是01变种,因为要求时间长度,即满足最大值时的体积。所以需要给01加一个限定条件
v==weight || dp[v-weight]>=1
1可以替换成val
#include <iostream> #include <string> #include <cstring> #include <cstdlib> #include <cstdio> #include <cmath> #include <algorithm> #include <stack> #include <queue> #include <cctype> #include <vector> #include <iterator> #include <set> #include <map> #include <sstream> using namespace std; #define mem(a,b) memset(a,b,sizeof(a)) #define pf printf #define sf scanf #define spf sprintf #define pb push_back #define debug printf("!\n") #define INF 1<<30 #define MAXN 5010 #define MAX(a,b) a>b?a:b #define blank pf("\n") #define LL long long #define ALL(x) x.begin(),x.end() #define INS(x) inserter(x,x.begin()) #define pqueue priority_queue int n,t,res; int song[60],dp[100000]; void zobag(int weight) { int v; for(v=t;v>=weight;v--) { if(v==weight || dp[v-weight]>=1) { dp[v] = max(dp[v],dp[v-weight]+1); res = max(res,dp[v]); } } } int main() { int i,j,kase=1; int T; sf("%d",&T); while(T--) { sf("%d%d",&n,&t); t--; mem(dp,0); res = 0; for(i=0;i<n;i++) sf("%d",&song[i]); for(i=0;i<n;i++) { zobag(song[i]); } int x; for(i=t;i>=0;i--) { if(dp[i]==res) { x=i; break; } } pf("Case %d: %d %d\n",kase++,res+1,678+x); } }
最长上升子序列(LIS)
nlgn算法:http://blog.csdn.net/dangwenliang/article/details/5728363
#include <iostream> #include <string> #include <cstring> #include <cstdlib> #include <cstdio> #include <cmath> #include <algorithm> #include <stack> #include <queue> #include <cctype> #include <vector> #include <iterator> #include <set> #include <map> #include <sstream> using namespace std; #define mem(a,b) memset(a,b,sizeof(a)) #define pf printf #define sf scanf #define spf sprintf #define pb push_back #define debug printf("!\n") #define INF 1<<30 #define MAXN 5010 #define MAX(a,b) a>b?a:b #define blank pf("\n") #define LL long long #define ALL(x) x.begin(),x.end() #define INS(x) inserter(x,x.begin()) #define pqueue priority_queue int n; int num[100],dp[100],pre[100]; int main() { int i,j,kase=1; while(sf("%d",&n)==1 && n) { for(i=0;i<n;i++) sf("%d",&num[i]); for(i=0;i<n;i++) dp[i] = 1; int res = 0,v=0; for(i=0;i<n;i++) { for(j=0;j<i;j++) { if(num[i]>num[j]) { int t = dp[j]+1; if(t>dp[i]) { dp[i] = t; pre[i] = j; } } } } for(i=0;i<n;i++) { if(dp[i]>res) { res = dp[i]; v = i; } } pf("%d ",num[v]); for(i=1;i<res;i++) { pf("%d ",num[pre[v]]); v = pre[v]; } blank; pf("%d\n",res); } } /* 6 1 6 2 3 7 5 */
最长公共子序列(LCS)
递归:
#include <iostream> #include <string> #include <cstring> #include <cstdlib> #include <cstdio> #include <cmath> #include <algorithm> #include <stack> #include <queue> #include <cctype> #include <vector> #include <iterator> #include <set> #include <map> #include <sstream> using namespace std; #define mem(a,b) memset(a,b,sizeof(a)) #define pf printf #define sf scanf #define spf sprintf #define pb push_back #define debug printf("!\n") #define INF 1<<30 #define MAXN 5010 #define MAX(a,b) a>b?a:b #define blank pf("\n") #define LL long long #define ALL(x) x.begin(),x.end() #define INS(x) inserter(x,x.begin()) #define pqueue priority_queue int n,m,res; int a[10000],b[10000]; int dp(int i,int j) { if(i>=n || j>=m) return 0; if(a[i]==b[j]) return dp(i+1,j+1)+1; else { return max(dp(i,j+1),dp(i+1,j)); } } int main() { int i,j,kase=1; while(sf("%d%d",&n,&m)==2) { for(i=0;i<n;i++) sf("%d",&a[i]); for(i=0;i<m;i++) sf("%d",&b[i]); pf("%d\n",dp(0,0)); } } /* 6 7 1 5 2 6 8 7 2 3 5 6 9 8 4 */
递推:
判断条件要稍微注意一下,递推时可以用flag来保存数组,见http://www.cnblogs.com/xudong-bupt/archive/2013/03/15/2959039.html
#include <iostream> #include <string> #include <cstring> #include <cstdlib> #include <cstdio> #include <cmath> #include <algorithm> #include <stack> #include <queue> #include <cctype> #include <vector> #include <iterator> #include <set> #include <map> #include <sstream> using namespace std; #define mem(a,b) memset(a,b,sizeof(a)) #define pf printf #define sf scanf #define spf sprintf #define pb push_back #define debug printf("!\n") #define INF 1<<30 #define MAXN 5010 #define MAX(a,b) a>b?a:b #define blank pf("\n") #define LL long long #define ALL(x) x.begin(),x.end() #define INS(x) inserter(x,x.begin()) #define pqueue priority_queue int n,m; int a[1000],b[1000],dp[1000][1000],flag[1000][1000],res[1000]; int main() { int i,j,kase=1; while(sf("%d%d",&n,&m)==2) { for(i=0;i<n;i++) sf("%d",&a[i]); for(i=0;i<m;i++) sf("%d",&b[i]); for(i=1;i<=n;i++) { for(j=1;j<=m;j++) { if(a[i-1]==b[j-1]) { dp[i][j] = dp[i-1][j-1]+1; flag[i][j] = 1;//斜上 } else if(dp[i-1][j]>dp[i][j-1]) { dp[i][j] = dp[i-1][j]; flag[i][j] = 2;//上 } else { dp[i][j] = dp[i][j-1]; flag[i][j] = 3;//左 } } } i = n; j = m; int k = 0; while(i>0 && j>0) { if(flag[i][j]==1) { res[k++]=a[i-1]; i--; j--; } else if(flag[i][j]==2) i--; else j--; } for(i=k-1;i>=0;i--) pf("%d ",res[i]); blank; pf("%d\n",dp[n][m]); } } /* 6 7 1 5 2 6 8 7 2 3 5 6 9 8 4 4 5 1 3 7 5 3 4 6 7 5 */
例题9-6 照明系统设计(Lighting System Design, UVa 11400)
当前决策影响后面决策,所以可以用分段更新。见:http://blog.csdn.net/yanzheshi/article/details/47069189
#include <iostream> #include <string> #include <cstring> #include <cstdlib> #include <cstdio> #include <cmath> #include <algorithm> #include <stack> #include <queue> #include <cctype> #include <vector> #include <iterator> #include <set> #include <map> #include <sstream> using namespace std; #define mem(a,b) memset(a,b,sizeof(a)) #define pf printf #define sf scanf #define spf sprintf #define pb push_back #define debug printf("!\n") #define INF 1<<30 #define MAXN 5010 #define MAX(a,b) a>b?a:b #define blank pf("\n") #define LL long long #define ALL(x) x.begin(),x.end() #define INS(x) inserter(x,x.begin()) #define pqueue priority_queue int n,m; int s[1005],dp[1005]; struct light { int V,K,C,L; }lt[1005]; int cmp(const light& a,const light& b) { return a.V<b.V; } int main() { int i,j,kase=1; while(sf("%d",&n)==1 && n) { int k = 1,sum=0; for(i=0;i<n;i++) { sf("%d%d%d%d",<[i].V,<[i].K,<[i].C,<[i].L); } sort(lt,lt+n,cmp); for(i=0;i<n;i++) { sum+=lt[i].L; s[k++]=sum; } for(i=0;i<n;i++) { dp[i] = s[i+1]*lt[i].C+lt[i].K; for(j=0;j<i;j++) { dp[i] = min(dp[i],dp[j]+(s[i+1]-s[j+1])*lt[i].C+lt[i].K); } } pf("%d\n",dp[n-1]); } }
例题9-7 划分成回文串(Partitioning by Palindromes, UVa 11584)
dp[i]为i长度的最小值,dp[j]已知时,判断后面j+1-i的状态,若为回文,则dp[i]=dp[j]+1
判断回文这里给了个更简便的方法:http://blog.csdn.net/shuangde800/article/details/9669175
#include <iostream> #include <string> #include <cstring> #include <cstdlib> #include <cstdio> #include <cmath> #include <algorithm> #include <stack> #include <queue> #include <cctype> #include <vector> #include <iterator> #include <set> #include <map> #include <sstream> using namespace std; #define mem(a,b) memset(a,b,sizeof(a)) #define pf printf #define sf scanf #define spf sprintf #define pb push_back #define debug printf("!\n") #define INF 1<<30 #define MAXN 5010 #define MAX(a,b) a>b?a:b #define blank pf("\n") #define LL long long #define ALL(x) x.begin(),x.end() #define INS(x) inserter(x,x.begin()) #define pqueue priority_queue int n,m; char str[1005]; int dp[1005]; int ispara(int a,int b) { if(a==b) return 1; int mid = (a+b)>>1; int l = b-a+1,i; if(l%2==0) { for(i=0;i<l/2;i++) { if(str[mid-i]!=str[mid+i+1]) return 0; } } else { for(i=1;i<=l/2;i++) { if(str[mid-i]!=str[mid+i]) return 0; } } return 1; } int main() { int i,j,T; sf("%d",&T); while(T--) { sf("%s",str); int len = strlen(str); // pf("%d\n",ispara(0,2)); if(ispara(0,len-1)) { pf("1\n"); continue; } for(i=0;i<len;i++) { dp[i]=i+1; if(ispara(0,i)) { dp[i]=1; continue; } for(j=0;j<i;j++) { if(ispara(j+1,i)) dp[i] = min(dp[i],dp[j]+1); } } pf("%d\n",dp[len-1]); } }
例题9-8 颜色的长度(Color Length, ACM/ICPC Daejeon 2011, UVa1625)
http://blog.csdn.net/chlerry/article/details/48322275#
#include <iostream> #include <string> #include <cstring> #include <cstdlib> #include <cstdio> #include <cmath> #include <algorithm> #include <stack> #include <queue> #include <cctype> #include <vector> #include <iterator> #include <set> #include <map> #include <sstream> using namespace std; #define mem(a,b) memset(a,b,sizeof(a)) #define pf printf #define sf scanf #define spf sprintf #define pb push_back #define mp make_pair #define debug printf("!\n") #define INF 1<<30 #define MAXN 5010 #define MAX(a,b) a>b?a:b #define blank pf("\n") #define LL long long #define ALL(x) x.begin(),x.end() #define INS(x) inserter(x,x.begin()) #define pqueue priority_queue int n,m; char a[5005],b[5005]; int dp[1005][1005],res[1005][1005],sta[27],stb[27],eda[27],edb[27]; int main() { int i,j,T; sf("%d",&T); while(T--) { sf("%s%s",a,b); int al = strlen(a); int bl = strlen(b); mem(sta,-1); mem(stb,-1); mem(eda,-1); mem(edb,-1); for(i=0;i<al;i++) { if(sta[a[i]-'A']==-1) sta[a[i]-'A'] =i; eda[a[i]-'A'] = i; } for(i=0;i<bl;i++) { if(stb[b[i]-'A']==-1) stb[b[i]-'A'] =i; edb[b[i]-'A'] = i; } for(i=0;i<=al;i++) { for(j=0;j<=bl;j++) { dp[i][j] = 0; for(int k =0;k<26;k++) { if((i>sta[k] && i<eda[k]) || (i>stb[k] && i<edb[k])) dp[i][j]++; } if(i==0&&j==0) continue; else if(i==0) dp[i][j]+=dp[i][j-1]; else if(j==0) dp[i][j]+=dp[i-1][j]; else dp[i][j] += min(dp[i-1][j],dp[i][j-1]); } } pf("%d\n",dp[al-1][bl]); } }
最优矩阵链乘
递归法,分析见:
http://blog.jobbole.com/87012/
http://blog.csdn.net/simmerlee/article/details/7731594
#include <iostream> #include <string> #include <cstring> #include <cstdlib> #include <cstdio> #include <cmath> #include <algorithm> #include <stack> #include <queue> #include <cctype> #include <vector> #include <iterator> #include <set> #include <map> #include <sstream> using namespace std; #define mem(a,b) memset(a,b,sizeof(a)) #define pf printf #define sf scanf #define spf sprintf #define pb push_back #define mp make_pair #define debug printf("!\n") #define INF 1<<30 #define MAXN 5010 #define MAX(a,b) a>b?a:b #define blank pf("\n") #define LL long long #define ALL(x) x.begin(),x.end() #define INS(x) inserter(x,x.begin()) #define pqueue priority_queue int n,m; typedef pair<int,int> pa; vector<pa> p; int f[100][100]; int dp(int i,int j) { if(f[i][j]!=-1) return f[i][j]; if(i==j) return f[i][j]=0; f[i][j] = INF; for(int k=i;k<j;k++) { f[i][j] = min(f[i][j],dp(i,k)+dp(k+1,j)+p[i].first*p[k].second*p[j].second); } return f[i][j]; } int main() { int i,j,T; while(sf("%d",&n)==1) { p.clear(); for(i=0;i<n;i++) { int a,b; sf("%d%d",&a,&b); p.pb(mp(a,b)); } mem(f,-1); pf("%d\n",dp(0,n-1)); } } /* 3 2 3 3 4 4 5 6 30 35 35 15 15 5 5 10 10 20 20 25 */
递推:http://blog.csdn.net/simmerlee/article/details/7731594
上述方程有些特殊:记忆化搜索固然没问题,但如果要写成递推,无论
按照i还是j的递增或递减顺序均不正确。正确的方法是按照j-i递增的顺序递推,因为长区
间的值依赖于短区间的值。
for(r=1;r<n;i++) { for(i=1;i<=n-r+1;i++) { j = i+r; f[i][j] = f[i+1][j]+p[i-1]*p[i]*p[j]; s[i][j] = i; for(int k = i+1;k<j;k++) { int t = f[i][k]+f[k+1][j]+p[i-1]*p[k]*p[j]; if(t<f[i][j]) { f[i][j] = t; s[i][j] = k; } } } }
UVA 348
记录路径的方法:用r[i][j]保存断点,然后递归输出路径
#include <iostream> #include <string> #include <cstring> #include <cstdlib> #include <cstdio> #include <cmath> #include <algorithm> #include <stack> #include <queue> #include <cctype> #include <vector> #include <iterator> #include <set> #include <map> #include <sstream> using namespace std; #define mem(a,b) memset(a,b,sizeof(a)) #define pf printf #define sf scanf #define spf sprintf #define pb push_back #define mp make_pair #define debug printf("!\n") #define INF 1<<30 #define MAXN 5010 #define MAX(a,b) a>b?a:b #define blank pf("\n") #define LL long long #define ALL(x) x.begin(),x.end() #define INS(x) inserter(x,x.begin()) #define pqueue priority_queue int n,m; typedef pair<int,int> pa; vector<pa> p; int f[100][100],r[100][100]; int dp(int i,int j) { if(f[i][j]!=-1) return f[i][j]; r[i][j]=i; if(i==j) return f[i][j]=0; f[i][j] = INF; for(int k=i;k<j;k++) { int v = dp(i,k)+dp(k+1,j)+p[i].first*p[k].second*p[j].second; if(v<f[i][j]) { f[i][j] = v; r[i][j] = k; } } return f[i][j]; } void print(int i,int j) { if(i>j) return; if(i==j) pf("A%d",i+1); else { pf("("); print(i,r[i][j]); pf(" x "); print(r[i][j]+1,j); pf(")"); } } int main() { int i,j,T,kase=0; while(sf("%d",&n)==1 && n) { p.clear(); for(i=0;i<n;i++) { int a,b; sf("%d%d",&a,&b); p.pb(mp(a,b)); } mem(f,-1); pf("%d\n",dp(0,n-1)); pf("Case %d: ",++kase); print(0,n-1); blank; } } /* 3 2 3 3 4 4 5 6 30 35 35 15 15 5 5 10 10 20 20 25 */
例题9-9 切木棍(Cutting Sticks, UVa 10003)
这题让我更加明确了DP的使用状态
一:重叠子问题
二:最优子结构
既然是最优子结构,肯定要从最小结构入手,一直推到最后的情况
这里的最下结构就是间距为1时,所以不是用i,j枚举
#include <iostream> #include <string> #include <cstring> #include <cstdlib> #include <cstdio> #include <cmath> #include <algorithm> #include <stack> #include <queue> #include <cctype> #include <vector> #include <iterator> #include <set> #include <map> #include <sstream> using namespace std; #define mem(a,b) memset(a,b,sizeof(a)) #define pf printf #define sf scanf #define spf sprintf #define pb push_back #define mp make_pair #define debug printf("!\n") #define INF 1<<30 #define MAXN 5010 #define MAX(a,b) a>b?a:b #define blank pf("\n") #define LL long long #define ALL(x) x.begin(),x.end() #define INS(x) inserter(x,x.begin()) #define pqueue priority_queue int d[60],dp[60][60]; int main() { int L; while(sf("%d",&L)==1 && L) { int n,i,j,r,k; sf("%d",&n); for(i=1;i<=n;i++) sf("%d",&d[i]); d[0] = 0; d[n+1] = L; for(r=1;r<=n+1;r++) { for(i=0;i<=n+1;i++) { j = i+r; if(j>n+1) break; int tmp = INF; for(k=i+1;k<j;k++) { int t = dp[i][k]+dp[k][j]+d[j]-d[i]; if(t<tmp) tmp = t; } if(tmp!=INF) dp[i][j] = tmp; } } pf("The minimum cutting is %d.\n",dp[0][n+1]); } }
递推:
int re(int i,int j) { if(i==j-1) return 0; int& ans = dp[i][j]; if(ans) return ans; ans = INF; for(int k = i+1;k<j;k++) { ans = min(ans,re(i,k)+re(k,j)+ d[j]-d[i]); } return ans; }
例题9-10 括号序列(Brackets Sequence, NEERC 2001, UVa1626)
还是最小到最大,所以i逆着枚举
#include <iostream> #include <string> #include <cstring> #include <cstdlib> #include <cstdio> #include <cmath> #include <algorithm> #include <stack> #include <queue> #include <cctype> #include <vector> #include <iterator> #include <set> #include <map> #include <sstream> using namespace std; #define mem(a,b) memset(a,b,sizeof(a)) #define pf printf #define sf scanf #define spf sprintf #define pb push_back #define mp make_pair #define debug printf("!\n") #define INF 1<<30 #define MAXN 5010 #define MAX(a,b) a>b?a:b #define blank pf("\n") #define LL long long #define ALL(x) x.begin(),x.end() #define INS(x) inserter(x,x.begin()) #define pqueue priority_queue char str[250]; int dp[250][250]; int match(int i,int j) { return (str[i]=='[' && str[j]==']') || (str[i]=='(' && str[j]==')'); } void print(int i,int j) { int k; if(i>j) return; if(i==j) { if(str[i]=='(' || str[i]==')') pf("()"); else pf("[]"); return; } int ans = dp[i][j]; if(match(i,j) && ans == dp[i+1][j-1]) { pf("%c",str[i]); print(i+1,j-1); pf("%c",str[j]); return; } for(k=i;k<j;k++) { if(ans == dp[i][k]+dp[k+1][j]) { print(i,k); print(k+1,j); return; } } } int main() { int T,r,i,j,k; sf("%d",&T); while(T--) { sf("%s",str); int n = strlen(str); for(i=0;i<n;i++) { dp[i][i] = 1; } for(i=n-2;i>=0;i--) { for(j=i+1;j<n;j++) { dp[i][j] = n; if(match(i,j)) { dp[i][j] = min(dp[i][j],dp[i+1][j-1]); } for(k=i;k<j;k++) { dp[i][j] = min(dp[i][j],dp[i][k]+dp[k+1][j]); } } } pf("%d\n",dp[0][n-1]); print(0,n-1); blank; } } /* 4 ([(] ([)] ([]) (((]] */
递归:
递归如果要打印路径记得不要只返回一个值,这样边界条件的数组就不会保存
#include <iostream> #include <string> #include <cstring> #include <cstdlib> #include <cstdio> #include <cmath> #include <algorithm> #include <stack> #include <queue> #include <cctype> #include <vector> #include <iterator> #include <set> #include <map> #include <sstream> using namespace std; #define mem(a,b) memset(a,b,sizeof(a)) #define pf printf #define sf scanf #define spf sprintf #define pb push_back #define mp make_pair #define debug printf("!\n") #define INF 1<<30 #define MAXN 5010 #define MAX(a,b) a>b?a:b #define blank pf("\n") #define LL long long #define ALL(x) x.begin(),x.end() #define INS(x) inserter(x,x.begin()) #define pqueue priority_queue char str[250]; int dp[250][250]; int match(int i,int j) { return (str[i]=='[' && str[j]==']') || (str[i]=='(' && str[j]==')'); } int re(int i,int j) { int& ans = dp[i][j]; if(i==j) return ans = 1; if(ans) return ans; ans = INF; if(match(i,j)) ans = min(ans,re(i+1,j-1)); for(int k = i;k<j;k++) ans = min(ans,re(i,k)+re(k+1,j)); return ans; } void print(int i,int j) { int k; if(i>j) return; if(i==j) { if(str[i]=='(' || str[i]==')') pf("()"); else pf("[]"); return; } int ans = dp[i][j]; if(match(i,j) && ans == dp[i+1][j-1]) { pf("%c",str[i]); print(i+1,j-1); pf("%c",str[j]); return; } for(k=i;k<j;k++) { if(ans == dp[i][k]+dp[k+1][j]) { print(i,k); print(k+1,j); return; } } } int main() { int T,r,i,j,k; sf("%d",&T); while(T--) { sf("%s",str); int n = strlen(str); mem(dp,0); pf("%d\n",re(0,n-1)); print(0,n-1); blank; } } /* 4 ([(] ([)] ([]) (((]] */
例题9-11 最大面积最小的三角剖分(Minimax Triangulation, ACM/ICPC NWERC
2004, UVa1331)
9.4.2 树上的动态规划
树的最大独立集
int dp(int x,int fa) { for(int i=0;i<edge[x].size();i++) { int m = edge[x][i]; if(m!=fa) { } } } int main() { int T,r,i,j,n; while(sf("%d",&n)==1 && n) { string a,b; cin>>a; int k = 0; name.insert(mp(a,k++)); for(i=0;i<n-1;i++) { cin>>a>>b; if(!name.count(a)) name.insert(mp(a,k++)); if(!name.count(b)) name.insert(mp(b,k++)); edge[name[a]].pb(name[b]); edge[name[b]].pb(name[a]); } } }