动态规划的实质是将较大问题问题分解为较小的同类子问题。与分治法和贪心法不同的是,动态规划法利用最优子结构,自底向上从子问题的最优解逐步构造出整个问题的最优解。
设计一个动态规划算法,通常可以按一下4个步骤进行:
(1)刻画最优解的结构特性;
(2)递归定义最优解值;
(3)自底向上计算最优解值;
(4)根据计算得到的信息构造一个最优解。
一个最优化多步决策问题是否适合用动态规划方法求解有两个要素:最优子结构核重叠子问题。
虽然动态规划法也是基于分解思想的,但由于子问题往往是重叠的,为了避免重复计算,动态规划算法采用字自底向上的方式进行计算,并且保存已求解的子问题的最优解值。当这些子最优解值被重复引用时无需重新计算,因而节省大量计算时间。
下面是我的题解,记录于此。
1.斐波拉契数列
http://acm.njupt.edu.cn/acmhome/problemdetail.do?&method=showdetail&id=1003
先从斐波拉契数来窥探动态规划问题。既然斐波那契数就由之前的两数相加得到,那么循环中我们记录它的前两个数,自底向上求到斐波拉契数。
1 #include<iostream> 2 using namespace std; 3 4 int main() 5 { 6 int n,i; 7 cin>>n; 8 if(n==0||n==1) 9 cout<<n<<endl; 10 else 11 { 12 int f1=0,f2=1,f3; 13 for(i=2;i<=n;i++) 14 { 15 f3=f1+f2; 16 f1=f2; 17 f2=f3; 18 } 19 cout<<f3<<endl; 20 } 21 return 0; 22 }
2.最长递减子序列
http://acm.njupt.edu.cn/acmhome/problemdetail.do?&method=showdetail&id=1161
当前每个数的最长子序列依赖于它的子序列,因此我们自后向前记录每个数的最长子序列,最后输出最大值即可。
1 #include<iostream> 2 using namespace std; 3 4 int main() 5 { 6 int n,*a,*count,i,j,max; 7 cin>>n; 8 a=new int[n]; 9 count=new int[n]; 10 for(i=0;i<n;i++) 11 { 12 cin>>a[i]; 13 count[i]=1; 14 } 15 for(i=n-1;i>=0;i--) 16 { 17 max=0; 18 for(j=i+1;j<n;j++) 19 if(a[j]<a[i]&&max<count[j]) 20 max=count[j]; 21 count[i]+=max; 22 } 23 for(i=0;i<n;i++) 24 { 25 if(max<count[i]) 26 max=count[i]; 27 } 28 cout<<max<<endl; 29 delete []a; 30 delete []count; 31 return 0; 32 }
3.最少硬币问题
http://acm.njupt.edu.cn/acmhome/problemdetail.do?&method=showdetail&id=1221
最开始考虑贪心法,WA。换DP,AC!看来还是没有深刻区分两类问题。其中,num数组记录要使用当前种类硬币的个数,count数组记录需要找的钱数的最少硬币个数。这里18块由13块跟硬币5组成,13块由8块跟硬币5组成,8块由3块跟硬币5组成,3块由1块跟硬币2组成,1块由硬币1得到。反过来也就是从1块钱开始,自底向上记录需要找的钱数的最少硬币个数。
1 #include<iostream> 2 using namespace std; 3 4 int main() 5 { 6 int n,*T,*Coins,i,j,m,*count,*num; 7 cin>>n; 8 T=new int[n]; 9 Coins=new int[n]; 10 for(i=0;i<n;i++) 11 cin>>T[i]>>Coins[i]; 12 cin>>m; 13 count=new int[m+1]; 14 num=new int[m+1]; 15 for(i=1;i<=m;i++) 16 count[i]=0xfffffff; 17 count[0]=0; 18 for(i=0;i<n;i++) 19 { 20 for(j=0;j<=m;j++) 21 num[j]=0; 22 for(j=0;j<=m-T[i];j++) 23 { 24 if(num[j]<Coins[i]&&count[j]+1<count[j+T[i]]) 25 { 26 count[j+T[i]]=count[j]+1; 27 num[j+T[i]]=num[j]+1; 28 } 29 } 30 } 31 if(count[m]!=0xfffffff) 32 cout<<count[m]<<endl; 33 else 34 cout<<"-1"<<endl; 35 delete []T; 36 delete []Coins; 37 delete []count; 38 delete []num; 39 return 0; 40 }
4.数字三角形
http://acm.njupt.edu.cn/acmhome/problemdetail.do?&method=showdetail&id=1989
1 #include<iostream> 2 using namespace std; 3 4 inline int max(int a,int b) 5 { 6 return a>b?a:b; 7 } 8 const int MAX=101; 9 10 int main() 11 { 12 int t,n,i,j; 13 int a[MAX][MAX]; 14 int p[MAX][MAX]; 15 cin>>t; 16 while(t--) 17 { 18 cin>>n; 19 for(i=0;i<n;i++) 20 for(j=0;j<=i;j++) 21 cin>>a[i][j]; 22 for(i=0;i<n;i++) 23 p[n-1][i]=a[n-1][i]; 24 for(i=n-2;i>=0;i--) 25 { 26 for(j=0;j<=i;j++) 27 { 28 p[i][j]=a[i][j]+max(p[i+1][j],p[i+1][j+1]); 29 } 30 } 31 cout<<p[0][0]<<endl; 32 } 33 return 0; 34 }
5.吃苹果
http://acm.njupt.edu.cn/acmhome/problemdetail.do?&method=showdetail&id=1060
动态方程p[i][j]=max(p[i-1][j]+p[i-1][j-1])+(有苹果+1)。
1 #include<iostream> 2 using namespace std; 3 #define MAXT 1001 4 #define MAXK 31 5 inline int max(int a,int b) 6 { 7 return a>b?a:b; 8 } 9 int main() 10 { 11 int t,k,i,j; 12 cin>>t>>k; 13 int a[MAXT]; 14 int p[MAXT][MAXK]; 15 for(i=1;i<=t;i++) 16 cin>>a[i]; 17 p[0][0]=0; 18 for(i=1;i<=t;i++) 19 { 20 p[i][0]=p[i-1][0]+a[i]%2; 21 for(j=1;j<=k;j++) 22 { 23 p[i][j]=max(p[i-1][j-1],p[i-1][j])+(j%2==(a[i]-1)?1:0); 24 } 25 } 26 int max=0; 27 for(j=1;j<=k;j++) 28 if(p[t][j]>max) max=p[t][j]; 29 cout<<max<<endl; 30 return 0; 31 }
6.最长公共子序列
动态方程c[i][j]=c[i-1][j-1] s1[i]=s2[j];
c[i][j]=max(c[i][j-1],c[i-1][j]) s1[i]≠s2[j];
1 #include<iostream> 2 #include<string> 3 using namespace std; 4 5 void LCS(const string s1,const string s2) 6 { 7 int i,j; 8 int len1=s1.length(); 9 int len2=s2.length(); 10 int **c=new int*[len1+2]; 11 for(i=0;i<=len1;i++) 12 c[i]=new int[len2+2]; 13 for(i=0;i<=len1;i++) 14 c[i][0]=0; 15 for(i=0;i<=len2;i++) 16 c[0][i]=0; 17 for(i=1;i<=len1;i++) 18 { 19 for(j=1;j<=len2;j++) 20 { 21 if(s1[i-1]==s2[j-1]) 22 c[i][j]=c[i-1][j-1]+1; 23 else if(c[i-1][j]>=c[i][j-1]) 24 c[i][j]=c[i-1][j]; 25 else 26 c[i][j]=c[i][j-1]; 27 } 28 } 29 /******* c矩阵 ********* 30 for(i=0;i<=len1;i++) 31 { 32 for(j=0;j<=len2;j++) 33 cout<<c[i][j]<<" "; 34 cout<<endl; 35 } 36 ************************/ 37 cout<<c[len1][len2]<<endl; 38 for(i=0;i<=len1;i++) 39 delete []c[i]; 40 delete []c; 41 } 42 43 int main() 44 { 45 string s1,s2; 46 while(cin>>s1>>s2) 47 { 48 LCS(s1,s2); 49 } 50 return 0; 51 }
7.待续
...
通过这几题的练习,应该可以解决一些简单的DP问题了。