【王道机试】第十二章 动态规划

本文深入探讨了动态规划的概念及其在解决最优解问题中的应用,通过多个实例,如楼梯上楼、最大连续子序列和、最大子矩阵、最长递增子序列、背包问题等,详细展示了动态规划的解题思路和状态转移方程。每个例子都包含代码实现,帮助读者更好地理解和掌握动态规划的运用。
摘要由CSDN通过智能技术生成

动态规划通常用于求解最优解问题,将带求解问题分解成若干子问题,先求解子问题,然后从这些子问题的解得到原问题的解。经分解得到的子问题往往不是互相独立的
动态规划的做法是将已解决子问题的答案保存下来,在需要子问题答案的时候便可以直接获得,而不需要重复计算。
dp,yyds!

动态规划最重要的是找出状态转移方程

12.1 递推求解

例题12.1 N阶楼梯上楼问题

提交网址

#include <iostream>
using namespace std;

int const MAXN = 91;
long long dp[MAXN];

int main(){
	dp[1] = 1;
	dp[2] = 2;
	for(int i=3; i<MAXN; i++){
		dp[i] = dp[i - 1] + dp[i - 2];
	} 
	int n;
	while(scanf("%d", &n) != EOF){
		printf("%lld\n", dp[n]);
	}
	return 0;
}

习题12.1 吃糖果

提交网址 代码和上一题一模一样,就不放了。

12.2 最大连续子序列和

例题12.2 最大序列和

提交网址

#include <iostream>
using namespace std;

const int MAXN = 1000000;

long long arr[MAXN];
long long dp[MAXN]; 

long long MaxSubsequence(int n){
	long long maximum = dp[0] = arr[0];
	for(int i=1; i<n; i++){
		dp[i] = max(arr[i], arr[i] + dp[i - 1]);
		maximum = max(maximum, dp[i]);
	}
	return maximum;
}

int main(){
	int n;
	while(scanf("%d", &n) != EOF){
		for(int i=0; i<n; i++){
			scanf("%lld", &arr[i]);
		}
		long long answer = MaxSubsequence(n);
		printf("%lld\n", answer);
	}
	return 0;
}

例题12.3 最大子矩阵

提交网址

#include <iostream>
using namespace std;

const int MAXN = 100;
int matrix[MAXN][MAXN];
int total[MAXN][MAXN];
int arr[MAXN];
int dp[MAXN];

int SubSequence(int n){
	int maximum = dp[0] = arr[0];
	for(int i=1; i<n; i++){
		dp[i] = max(arr[i], arr[i] + dp[i - 1]);
		maximum = max(maximum, dp[i]);
	}
	return maximum;
} 

int MaxSubmatrix(int n){
	int maximum = -127;
	for(int i=0; i<n; i++){
		for(int j=i; j<n; j++){
			for(int k=0; k<n; k++){
				if(i == 0){
					arr[k] = total[j][k];
				}else{
					arr[k] = total[j][k] - total[i-1][k];
				}
			}
			maximum = max(maximum, SubSequence(n));
		}
	}
	return maximum;
}

int main(){
	int n;
	while(scanf("%d", &n) != EOF){
		for(int i=0; i<n; i++){
			for(int j=0; j<n; j++){
				scanf("%d", &matrix[i][j]);
				//填写辅助矩阵 
				if(i == 0){
					total[i][j] = matrix[i][j];
				}else{
					total[i][j] = total[i - 1][j] + matrix[i][j];
				}
			}
		}
		int answer = MaxSubmatrix(n);
		printf("%d\n", answer);
	}
	return 0;
}

习题12.2 最大连续子序列

提交网址

#include <iostream>
using namespace std;

const int MAXN = 10000;
int dp[MAXN], first[MAXN], arr[MAXN];
int num;

void SubSequence(int n){
	int maximum = dp[0] = first[0] = arr[0];
	num = 0;
	for(int i=1; i<n; i++){
		if(dp[i - 1] < 0){
			dp[i] = first[i] = arr[i];
		}else{
			dp[i] = arr[i] + dp[i - 1];
			first[i] = first[i - 1];
		}
		if(maximum < dp[i]){
			maximum = dp[i];
			num = i;
		}
	}
}

int main(){
	int n;
	while(scanf("%d", &n) != EOF){
		if(n == 0) break;
		bool flag = false;
		for(int i=0; i<n; i++){
			scanf("%d", &arr[i]);
			if(arr[i] >= 0){
				flag = true;
			}
		}
		if(!flag) printf("0 %d %d\n", arr[0], arr[n - 1]);
		else{
			SubSequence(n);
			printf("%d %d %d\n", dp[num], first[num], arr[num]);
		}
	}
	return 0;
} 

12.3 最长递增子序列(LIS)

例题12.4 拦截导弹

提交网址

#include <iostream>
using namespace std;

const int MAXN = 25;
int height[MAXN], dp[MAXN];

int main(){
	int n;
	while(scanf("%d", &n) != EOF){
		for(int i=0; i<n; i++){
			scanf("%d", &height[i]);
		}
		int answer = 0;
		for(int i=0; i<n; i++){
			dp[i] = 1;
			for(int j=0; j<i; j++){
				if(height[i] <= height[j]){
					dp[i] = max(dp[i], dp[j] + 1);
				}
			}
			answer = max(answer, dp[i]);
		}
		printf("%d\n", answer);
	}
	return 0;
} 

例题12.5 最长上升子序列和

提交网址

#include <iostream>
using namespace std;

const int MAXN = 1000;
int a[MAXN], dp[MAXN];

int main(){
	int n;
	while(scanf("%d", &n) != EOF){
		for(int i=0; i<n; i++){
			scanf("%d", &a[i]);
		}
		int answer = 0;
		for(int i=0; i<n; i++){
			dp[i] = a[i];
			for(int j=0; j<i; j++){
				if(a[j] < a[i]){
					dp[i] = max(dp[i], dp[j] + a[i]);
				}
			}
			answer = max(answer, dp[i]);
		}
		printf("%d\n", answer);
	}
	return 0;
} 

习题12.3 合唱队形

提交网址 其实也很简单,从前往后和从后往前两次dp,相加即可。

#include <iostream>
using namespace std;

const int MAXN = 100;
int arr[MAXN], dp1[MAXN], dp2[MAXN], sum[MAXN];

int main(){
	int n;
	while(scanf("%d", &n) != EOF){
		for(int i=0; i<n; i++){
			scanf("%d", &arr[i]);
		}
		for(int i=0; i<n; i++){
			dp1[i] = 1;
			for(int j=0; j<i; j++){
				if(arr[j] < arr[i]){
					dp1[i] = max(dp1[i], dp1[j] + 1);
				}
			}
		}
		for(int i=n-1; i>=0; i--){
			dp2[i] = 1;
			for(int j=n-1; j>i; j--){
				if(arr[j] < arr[i]){
					dp2[i] = max(dp2[i], dp2[j] + 1);
				}
			}
		}
		int mmax = 0;
		for(int i=0; i<n; i++){
			sum[i] = dp1[i] + dp2[i];
			mmax = max(mmax, sum[i]);
		}
		printf("%d\n", n - mmax + 1);
	}
	return 0;
} 

12.4 最长公共子序列(LCS)

例题12.6 Common Subsequence

#include <iostream>
#include <string>
using namespace std;

int main(){
	string s1, s2;
	while(cin >> s1 >> s2){
		int len1 = s1.length();
		int len2 = s2.length();
		int dp[len1+1][len2+1];
		for(int i=0; i<=len1; i++){
			dp[i][0] = 0;
		}
		for(int i=0; i<=len2; i++){
			dp[0][i] = 0;
		}
		for(int i=1; i<=len1; i++){
			for(int j=1; j<=len2; j++){
				if(s1[i-1] == s2[j-1]){
					dp[i][j] = dp[i-1][j-1] + 1;
				}else{
					dp[i][j] = max(dp[i-1][j], dp[i][j-1]);
				}
			}
		}
		printf("%d\n", dp[len1][len2]);
	}
	return 0;
}

习题12.4 Coincidence

提交网址 和上一题代码一模一样。

12.5 背包问题

背包问题是动态规划中一个非常常见并且在机试中重点考查的问题。背包问题的变体多且复杂。
本节主要讨论0-1背包、完全背包、多重背包三类背包问题。

之前上算法设计与分析课,碰到的背包问题一般就用到深搜dfs了。只能说万事皆可dp!但是dp确实很不好想上去啊。。。

1. 0-1背包

0-1背包问题描述的是:有n件物品,每件物品的重量为w[i],其价值为v[i],现在有个容量为m的背包,如何选择物品使得装入背包物品的价值最大。

0-1背包的特点是,每种物品至多只能选择一件,即在背包中该物品的数量只有0和1两种情况,这也是0-1背包名称的由来。

例题12.7 点菜问题

提交网址

#include <iostream>
using namespace std;

const int MAXN = 1001;
int dp[MAXN], v[MAXN], w[MAXN];

int main(){
	int m, n;
	while(scanf("%d%d", &m, &n) != EOF){
		for(int i=0; i<n; i++){
			scanf("%d%d", &w[i], &v[i]);
		}
		for(int i=0; i<=m; i++){
			dp[i] = 0;
		}
		for(int i=0; i<n; i++){
			for(int j=m; j>=w[i]; j--){
				dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
			}
		}
		printf("%d\n", dp[m]);
	}
	return 0;
}

习题12.5 采药

提交网址 只能说和上一题一模一样。

习题12.6 最小邮票数

提交网址

// dp[i][j]表示只用前i种邮票能凑成总值M的最少邮票数,如果凑不成为INF
// dp[0][j] = INF
// dp[i][0] = 0
/*  
    dp[i][j] = min(dp[i - 1][j], dp[i - 1][j - p[i]] + 1)
*/
#include<bits/stdc++.h>
using namespace std;
const int INF = 1e8;
int main()  
{
    int M, n;
    while (cin >> M >> n) {
        vector<int> p(n, 0);
        for (int i = 0;i < n;i++)
            cin >> p[i];
        vector<int> dp(M + 1, INF);
        dp[0] = 0;
        for (int i = 0;i < n;i++)
            for (int j = M;j >= p[i];j--)
                dp[j] = min(dp[j], dp[j - p[i]] + 1);
        cout << (dp[M] == INF ? 0 : dp[M]) << endl;
    }
    return 0;
}

2. 完全背包

完全背包问题:有n种物品,每种物品的重量为w[i],其价值为v[i],每种物品的数量均为无限个,现有容量为m的背包,如何选择物品使得装入背包物品的价值最大。

例题12.8 Piggt-Bank

#include <iostream>
using namespace std;

const int MAXN = 10000;
const int INF = INT_MAX / 10;
int dp[MAXN], v[MAXN], w[MAXN];

int main(){
	int caseNumber;
	scanf("%d", &caseNumber);
	while(caseNumber--){
		int e, f;
		scanf("%d%d", &e, &f);
		int m = f - e;
		int n;
		scanf("%d", &n);
		for(int i=0; i<n; i++){
			scanf("%d%d", &v[i], &w[i]);
		}
		for(int i=1; i<=m; i++){
			dp[i] = INF;
		}
		dp[0] = 0;
		for(int i=0; i<n; i++){
			for(int j=w[i]; j<=m; j++){
				dp[j] = min(dp[j], dp[j - w[i]] + v[i]);
			}
		}
		if(dp[m] == INF){
			printf("This is impossible.\n");
		}else{
			printf("The minimum amount of money in the piggy-bank is %d.\n", dp[m]);
		}
	}
	return 0;
}

3. 多重背包

多重背包问题:有n种物品,每种物品的重量为w[i],其价值为v[i],每种物品的数量为k[i],现在有容量为m的背包,如何选择物品使得装入背包物品的价值最大。

例题12.9 珍惜现在,感恩生活

这个代码用到了优化。书上的很多代码都用到了优化,但是优化确实也会让代码编写比较麻烦,容易出bug。可能真正机试的时候我不会用到优化吧,直接用0-1背包的方法做。
当然,如果开卷就又不一样了。。。

#include <iostream>
using namespace std;

const int MAXN = 10000;
int dp[MAXN], v[MAXN], w[MAXN], k[MAXN];
int weight[MAXN], value[MAXN];

int main(){
	int caseNumber;
	scanf("%d", &caseNumber);
	while(caseNumber--){
		int n, m;
		scanf("%d%d", &m, &n);
		int number = 0;
		for(int i=0; i<n; i++){
			scanf("%d%d%d", &w[i], &v[i], &k[i]);
			for(int j=1; j<=k[i]; j<<=1){
				value[number] = j * v[i];
				weight[number] = j * w[i];
				number++;
				k[i] -= j;
			}
			if(k[i] > 0){
				value[number] = k[i] * v[i];
				weight[number] = k[i] * w[i];
				number++;
			}
		}
		for(int i=0; i<=m; i++){
			dp[i] = 0;
		}
		for(int i=0; i<number; i++){
			for(int j=m; j>=weight[i]; j--){
				dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
			}
		}
		printf("%d", dp[m]);
	}
	return 0;
}

12.6 其它问题

例题12.10 The Triangle

#include <iostream>
using namespace std;

const int MAXN = 101;
int matrix[MAXN][MAXN];
int dp[MAXN][MAXN];

int main(){
	int n;
	scanf("%d", &n);
	for(int i=0; i<n; i++){
		for(int j=0; j<=i; j++){
			scanf("%d", &matrix[i][j]);
		}
	}
	for(int j=0; j<n; j++){
		dp[n-1][j] = matrix[n-1][j];
	}
	for(int i=n-2; i>=0; i--){
		for(int j=0; j<=i; j++){
			dp[i][j] = max(dp[i+1][j], dp[i+1][j+1]) + matrix[i][j];				
		} 
	}
	printf("%d\n", dp[0][0]);
	return 0;
}

例题12.11 Monkey Banana Problem

#include <iostream>
using namespace std;

const int MAXN = 101;
int matrix[2 * MAXN][MAXN];
int dp[2 * MAXN][MAXN];

int main(){
	int caseNumber, cas;
	scanf("%d", &caseNumber);
	for(cas=1; cas<=caseNumber; cas++){
		int n;
		scanf("%d", &n);
		for(int i=0; i<n; i++){
			for(int j=0; j<=i; j++){
				scanf("%d", &matrix[i][j]);
			}
		}
		for(int i=n; i<=2*n-2; i++){
			for(int j=0; j<=2*n-i-2; j++){
				scanf("%d", &matrix[i][j]);
			}
		}
		dp[2*n-2][0] = matrix[2*n-2][0];
		for(int i=2*n-3; i>=n-1; i--){
			for(int j=0; j<=2*n-i-2; j++){
				if(j == 0){
					dp[i][j] = dp[i+1][j] + matrix[i][j];
				}else if(j == 2*n-i-2){
					dp[i][j] = dp[i+1][j-1] + matrix[i][j];
				}else{
					dp[i][j] = max(dp[i+1][j-1], dp[i+1][j]) + matrix[i][j];
				}
			}
		}
		for(int i=n-2; i>=0; i--){
			for(int j=0; j<=i; j++){
				dp[i][j] = max(dp[i+1][j], dp[i+1][j+1]) + matrix[i][j];
			}
		}
		printf("Case %d: %d\n", cas, dp[0][0]);
	}
	return 0;
}

习题12.7 放苹果

提交网址 又是一道经典题。

#include <iostream>
using namespace std;

const int MAXN = 11;
int dp[MAXN][MAXN];

int main(){
	int m, n;
	while(scanf("%d%d", &m, &n) != EOF){
		for(int i=0; i<=m; i++){
			for(int j=1; j<=n; j++){
				if(i == 0 || j == 1){	//苹果数为0或盘子数为1 
					dp[i][j] = 1;
				}
				else if(i < j){
					dp[i][j] = dp[i][j-1];
				}else{
					dp[i][j] = dp[i-j][j] + dp[i][j-1];
				} 
			}
		}
		printf("%d\n", dp[m][n]);
	}
	return 0;
}

习题12.8 整数拆分

提交网址思路参考了讲解部分。

#include<iostream>
using namespace std;

const int MAXN = 1000001;
int dp[MAXN];

int main(){
    int n;
    dp[0] = 1;
    while(scanf("%d", &n) != EOF){
        for(int i=1; i<=n; i++){
            if(i%2 == 0){
                dp[i] = (dp[i-1] + dp[i/2]) % 1000000000;
            }else{
                dp[i] = dp[i-1] % 1000000000;
            }
        }
        printf("%d\n", dp[n]);
    }
    return 0;
}

完结!!!撒花!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值