动态规划算法和分治法类似,其基本思想也是将待求解问题分解成若干子问题,但是经分解得到的子问题的数目只是多项式量级,而且子问题不是互相独立的。可以避免分治法中子问题被重复计算很多次。
两个要素:(1)子问题重叠性质(2)最优子结构性质
子问题重叠性质:(递归算法求解时)有些子问题被反复计算多次。动态规划法中对每一个子问题只解一次。
最优子结构性质:当一个问题的最优解包含了其子问题的最优解。
全源最短路径
Floyed算法:采用自底向上方式计算,dist[i][j]存放从结点i到j的最短路径的长度,在算法的第k步上,做出决策:从i到j的最短路径上是否包含结点k,时间复杂度O(n^3)
最长公共子序列
dp[i][j] 表示第一个序列的前i个字符和第二个序列的前j个字符的最长公共子序列的长度,动态转移方程为:
dp[i][j] = max(dp[i-1][j], dp[i][j-1],dp[i-1][j-1] + (A[i]==B[j] ? 1 : 0)),表示在这三种状态中取到最大值,
(1)第一种状态表示不录入第一个序列的第i个字符时的最长公共子序列;
(2)第二种状态表示不录入第二个序列的第j个字符时的最长公共子序列;
(3)第三种状态表示第一个序列的前i-1个字符与第二个序列前j-1个字符的公共子序列加上最后一个字符的录入状态,如果最后的一个字符相等则录入1,否则为0。
然后根据动归的状态,来判断我们要求得的序列中的字符有哪些。
#define INF 0x3f3f3f3f char a[1001], b[1001]; int dp[1001][1001], len1, len2; void lcs(int i, int j) { for(i=1; i<=len1; i++) { for(j=1; j<=len2; j++) { if(a[i-1] == b[j-1])///a[i]和b[j]相等 dp[i][j] = dp[i-1][j-1] + 1; else if(dp[i-1][j] > dp[i][j-1]) dp[i][j] = dp[i-1][j]; else dp[i][j] = dp[i][j-1]; } } } void llcs()///根据dp求得最长公共子序列 { int i, j, z = 0; char c[1001]; memset(c, 0, sizeof(c)); i = len1, j = len2; while(i!=0 && j!=0) { if(a[i-1] == b[j-1]) { c[z++] = a[--i]; j--; } else if(dp[i-1][j] < dp[i][j-1]) j--; else if(dp[i][j-1] <= dp[i-1][j]) i--; } for(i=z-1; i>=0; i--) printf("%c", c[i]); printf(" "); } int main() { while(scanf(" %s", a)!=EOF) { scanf(" %s", b); memset(dp, 0, sizeof(dp)); len1 = strlen(a); len2 = strlen(b); lcs(len1, len2); llcs(); } return 0; }
0/1背包问题
0/1背包问题可以看作是决策一个序列(x1, x2, …, xn),对任一变量xi的决策是决定xi=1还是xi=0。在对xi-1决策后,已确定了(x1, …, xi-1),在决策xi时,问题处于下列两种状态之一:
(1)背包容量不足以装入物品i,则xi=0,背包不增加价值;
(2)背包容量可以装入物品i,则xi=1,背包的价值增加了vi。
int v[maxn],w[maxn],dp[maxn];///dp[m]是背包容量为m时能装入的最大价值 int main() { int t,i,j,n,m; cin>>t; while(t--) { cin>>n>>m;///n是物品数量,m是背包容量 for(i=1;i<=n;i++) cin>>v[i];///价值 for(i=1;i<=n;i++) cin>>w[i];///体积 memset(dp,0,sizeof(dp)); for(i=1;i<=n;i++) { for(j=m;j>=w[i];j--) dp[j]=max(dp[j],dp[j-w[i]]+v[i]); } cout<<dp[m]<<endl; } }
完全背包
有n种物品和一个容量为v的背包,每种物品都有无限件可用。第i种物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大
int dp[50010]; int w[2010],v[2010];///w是物品体积,v是物品价值 int main { int i,j,t,n,m,a,b; scanf("%d",&t); while(t--) { scanf("%d%d",&n,&m);///n是物品数量,m是背包容量 for(i=1;i<=n;i++) scanf("%d"%d",&w[i],&v[i]); memset(dp,-inf,sizeof(dp)); dp[0]=0; for(i=1;i<=n;i++) { for(j=w[i];j<=m;j++) dp[j]=max(dp[j],dp[j-w[i]]+v[i]); } if(dp[m]>0) printf("%d ",dp[m]); else printf("No "); } return 0; }
与01背包相比,完全背包只是第二重循环的顺序发生了改变。保证用较小容量的最大价值去更新较大容量的最大价值,不重不漏。