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