动态规划
递推求解 错排公式
我们按照 n 的取值顺序将所有的错装方式数量排 列为一个数列,同样用 F[n]表示数列里第 n 个数的取值,F[n]同时代表 n 个信封 的错装方式总数,我们确定该数列的递推关系。当 n 大于 3 时,我们考虑 n 封信 全部装错的情况。将信封按顺序由 1 到 n 编号。在任意一种错装方案中,假设 n 号信封里装的是 k 号信封的信,而 n 号信封里的信则装在 m 号信封里。按照k和m的等值与否将总的错误方式分为两类。
若 k 不等于m,交换 n 号信封和m号信封的信后,n 号信封里装的恰好是对 应的信,而 m 号信封中错装 k 号信封里的信,即除 n 号信封外其余 n-1 个信封 全部错装,其错装方式等于 F[n - 1],又由于m的 n-1 个可能取值,这类错装方 式总数为(n - 1)* F[n - 1]。也可以理解为,在 n-1 个信封错装的 F[n - 1]种方式 的基础上,将 n 号信封所装的信与n - 1个信封中任意一个信封(共有 n-1 中选 择)所装的信做交换后,得到所有信封全部错装的方式数。
另一种情况,若 k 等于m,交换 n 号信封和m号信封的信后,n 号信封和m 号信封里装的恰好是对应的信,这样除它们之外剩余的 n-2 个信封全部错装,其 错装方式为 F[n - 2],又由于m的 n-1 个取值,这类错装方式总数为(n - 1)* F[n - 2]。也可以理解为,在 n - 2 个信封全部错装的基础上,交换最后两个信封中的 信(n 号信封和 1 到 n-1 号信封中任意一个,共有 n-1 种选择),使所有的信封全部 错装的方式数。
综上所述,F[n] = (n - 1) * F[n - 1] + (n - 1) * F[n - 2]。这就是有名的错排公式
主要代码
1 2 3 4 5 F=0; F=1; for(int i=3;i<=20;i++) F=(i-1)*F+(i-1)*F; //递推求得数列的每一个数字
最长递增子序列 LIS 原理
示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 using namespace std; int dp[26]; int a[26]; int main() { int k; while (cin>>k&&k) { for (int i =1;i<=k;i++) cin>>a[i]; for (int i =1;i<=k;i++) { int tmax =1; for (int j =1;j<i;j++) if (a[j]>=a[i]) tmax =max(tmax, dp[j]+1); dp[i]=tmax; } int ans =1; for (int i =1;i<=k;i++) { ans =max(ans,dp[i]); } cout<<ans<<endl; } }
示例2
思路:
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 #include<iostream> #include<cmath> using namespace std;int dp1[101 ];int dp2[101 ];int a[101 ];int main() { int N; while (cin>>N) { for (int i=1 ;i<=N;i++) cin>>a[i]; for (int i=1 ;i<=N;i++) { dp1[i]=1 ; for (int j=1 ;j<i;j++) { if (a[i]>a[j]) dp1[i]=max(dp1[i],dp1[j]+1 ); } } for (int i=N;i>=1 ;i { dp2[i]=1 ; for (int j=N;j>i;j { if (a[i]>a[j]) dp2[i]=max(dp2[i],dp2[j]+1 ); } } int ans=0 ; for (int i=1 ;i<=N;i++) { ans=max(ans,dp1[i]+dp2[i]); } cout<<N-ans+1 <<endl; } return 0 ; }
最长公共子序列(LCS) 有两个字符串 S1 和 S2,求一个最长公共子串,即求字符串 S3,它同时为 S1 和 S2 的子串,且要求它的长度最长,并确定这个长度。这个问题被我们称为 最长公共子序列问题
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #include <iostream> #include <string> #include <cmath> using namespace std;int dp[101 ][101 ];int main () { string a,b; while (cin>>a>>b) { int lena=a.length (); int lenb=b.length (); for (int i=0 ;i<=lena;i++) for (int j=0 ;j<=lenb;j++) { if (i==0 ||j==0 ) { dp[i][j]=0 ; continue ; } if (a[i-1 ]==b[j-1 ]) dp[i][j]=dp[i-1 ][j-1 ]+1 ; else dp[i][j]=max (dp[i-1 ][j],dp[i][j-1 ]); } cout<<dp[lena][lenb]<<endl; } return 0 ; }
动态规划问题分析举例 搬寝室
示例代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #include <iostream> #include <algorithm> using namespace std; const int INF = 0x7fffffff; //预定义最大的int取值为无穷 int list; //保存每个物品重量 int dp; //保存每个状态 int main() { int n, k; while (cin >> n >> k && n && k) { for (int i = 1; i <= n; i++) cin >> list; sort(list + 1, list + 1 + n); //使所有物品按照重量递增排序 for (int i = 1; i <= n; i++) dp = 0; for (int i = 1; i <= k; i++) { //递推求得每个状态 for (int j = 2 * i; j <= n; j++) { if (j > 2 * i) dp = dp; //j>2*i表明,最后两个物品可以不配对,即前j-1件物品足够配成i对 //dp可以由dp转移而来,其值先被设置为dp else dp = INF; //若j==2*i,说明最后两件物品必须配对,否则前j件物品配不成i对, //所有其状态不能由dp转移而来,dp先设置为正无穷 if( dp> (dp+(list-list)*(list-list)) ) { //若dp从dp转移而来时,其值优于之前确定的正无穷 //或者由dp转移而来的值时,更新该状态 dp = dp + (list - list) * (list - list); //更新 } } } cout << dp << endl; } return 0; }
背包问题 主要讨论0-1背包,完全背包和多重背包三类问题
0-1背包问题 采药
首先我们将这个问题抽象:有一个容量为 V 的背包,和一些物品。 这些物品分别有两个属性,体积w和价值 v,每种物品只有一个。 要求用这个背包装下价值尽可能多的物品,求该最大价值,背包可以不被装满。 因为最优解中,每个物品都有两种可能的情况,即在背包中或者不存在(背 包中有 0 个该物品或者 1 个),所以我们把这个问题称为 0-1 背包问题。在该例 中,背包的容积和物品的体积等效为总共可用的时间和采摘每个草药所需的时间。
在众多方案中求解最优解,是典型的动态规划问题。为了用动态规划来解决 该问题,我们用 dp[i][j]表示在总体积不超过 j 的情况下,前 i 个物品所能达到的 最大价值。
初始时,dp[0][j] (0<=j<=V)为 0。依据每种物品是否被放入背包,每个状态有两个状态转移的来源。
若物品 i 被放入背包,设其体积为w,价值为 v, 则 dp[i][j] = dp[i - 1][j - w] + v。即在总体积不超过 j-w 时前 i-1 件物品可组成的最大价值的基础上再加上i物品的价值v;
若物品不加入背包,则dp[i][j] = dp[i-1][j], 即此时与总体积不超过 j 的前 i-1 件物品组成的价值最大值等价。
选择它们之中 较大的值成为状态 dp[i][j]的值。
综上所述,0-1 背包的状态转移方程为:
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 #include<iostream> #include<cmath> using namespace std; struct Node{ int w; int v; }a; int dp;//dp表示前i个物品组成的总体积不大于j的最大价值和 int main() { int V,M; while(cin>>V>>M) { for(int i=1;i<=M;i++) cin>>a.v>>a.w; for(int i=1;i<=V;i++) //初始化 dp=0; for(int i=1;i<=M;i++) //循环遍历每个物品 { for(int j=V;j>=a.v;j--) { dp=max(dp,dp+a.w); } for(int j=a.v-1;j>=0;j--) dp=dp; } cout<<dp<<endl; } return 0; }
代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 #include <iostream> using namespace std;const int INF=0x7fffffff ;int max (int a,int b) { return a>b?a:b; }struct E { int w; int v; }list[101 ];int dp[1001 ];int main () { int s,n; while (cin>>s>>n){ for (int i=1 ;i<=n;i++) cin>>list[i].w>>list[i].v; for (int i=0 ;i<=s;i++){ dp[i]=0 ; } for (int i=1 ;i<=n;i++){ for (int j=s;j>=list[i].w;j--){ dp[j]=max (dp[j],dp[j-list[i].w]+list[i].v); } } cout<<dp[s]<<endl; } return 0 ; }
完全背包问题
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 #include <iostream> #include <cmath> using namespace std;const int INF=0x7fffffff ;struct E { int w; int v; }list[501 ];int dp[10001 ];int main () { int T; cin>>T; while (T--) { int s,tmp; cin>>tmp>>s; s-=tmp; int n; cin>>n; for (int i=1 ;i<=n;i++) { cin>>list[i].v>>list[i].w; } for (int i=0 ;i<=s;i++) dp[i]=INF; dp[0 ]=0 ; for (int i=1 ;i<=n;i++){ for (int j=list[i].w;j<=s;j++){ if (dp[j-list[i].w]!=INF) dp[j]=min (dp[j],dp[j-list[i].w]+list[i].v); } } if (dp[s]!=INF) cout<<dp[s]<<endl; else cout<<"impossible" <<endl; } return 0 ; }
多重背包 输入
输出
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #include <iostream> #include <cmath> using namespace std;struct E { int w; int v; }list[2001 ];int dp[101 ];int main () { int T; cin>>T; while (T--) { int s,n; cin>>s>>n; int cnt=0 ; for (int i=1 ;i<=n;i++){ int v,w,k; cin>>v>>w>>k; int c=1 ; while (k-c>0 ){ k-=c; list[++cnt].w=c*w; list[cnt].v=c*v; c*=2 ; } list[++cnt].w=w*k; list[cnt].v=v*k; } for (int i=1 ;i<=s;i++) dp[i]=0 ; for (int i=1 ;i<=cnt;i++){ for (int j=s;j>=list[i].v;j--){ dp[j]=max (dp[j],dp[j-list[i].v]+list[i].w); } } cout<<dp[s]<<endl; } return 0 ; }