目录
动态规划——背包问题
动态规划又叫记忆化搜索,本质上是用空间换时间
利用上一个状态的最优解去求下一个状态的解,并找出下一个状态的最优解
0-1背包
要选择的东西具有原子性,只有拿与不拿,是不可拆分的
二维dp
dp[n][v]表示在背包容量为v的限制下,装入n个物体所能获得的最大价值,我们把不同的(n,v)对叫做不同的状态,每一个dp[n][v]都是在相应状态下的最优值
for (i = 1; i <= n; i++) {//决策完第n个物体
for (j = 1; j <= weight; j++) {//当背包容量为j时
if (m[i] > j) //装不下
dp[i][j] = dp[i - 1][j];//继承上一个状态
else { //能装下
dp[i][j] = max(dp[i - 1][j - p[i]] + m[i], dp[i - 1][j]);//dp[i - 1][j - p[i]]表示装到i前面的第i-1物体时的状态
//比较装与不装的最大价值
}
}
}
printf("%d\n", dp[n][weight]);
一维dp
若剩余的容量能够装下该物品,则有两种情况,装或是不装;
若选择装,则dp[j]=dp[j-m[i]]+p[i];
若不装,则dp[j]=dpj];//继承上个状态
最终结果取两种情况的最大值:
dp[j]=max(dp[j],dp[j-m[i]]+p[i]);
(dp[j]表示容量不超过j时所放物体的最大价值)
for (int i = 0; i < n; i++) {
for (int j = weight; j >= 0; j--) {
if (j >= m[i])
dp[j] = max(dp[j], dp[j - m[i]] + p[i]);
}
}
printf("%d\n", dp[weight]);
01背包的一维数组表示,更新的时候是逆序的,从代码不难看出,对一个值的更新是基于这个值左边的值进行更新的,如果是顺序更新,那么在更新时一定会先更新左边的值,从而使得后更新的值有可能基础被更新过了,而逆序更新就可以避免这个问题。也可以这么说,因为01背包里面每个物品只能放一次,如果顺序更新,那么假设状态1已经放了一个物品了,那么基于状态1的状态2如果需要更新,那么不就变成了放两个物品了么,这不就出错了,所以应该需要逆序更新.
完全背包
完全背包和 01 背包的区别:
1.对背包容量大小枚举的顺序不同
2.01不可以放多个同种物品而完全背包可以放多个同种物品。
与一维01dp代码不同
把01背包的逆序更新换成顺序更新,把所有还可以放的状态都更新一遍,从左向右进行更新,如果前面的状态就已经放得下一个了,那么基于这个状态的另一个状态还可以再放一个,,所以就基于上个状态继续更新,因而是正序更新。
for (int i = 0; i < n; i++) {
for (int j = 0; j <=weight; j++) {
if (j >= m[i])
dp[j] = max(dp[j], dp[j - m[i]] + p[i]);
}
}
printf("%d\n", dp[weight]);
数字三角形问题:
题目链接:[USACO1.5][IOI1994]数字三角形 Number Triangles - 洛谷
题目描述
观察下面的数字金字塔。
写一个程序来查找从最高点到底部任意处结束的路径,使路径经过数字的和最大。每一步可以走到左下方的点也可以到达右下方的点。
7 3 8 8 1 0 2 7 4 4 4 5 2 6 5
在上面的样例中,从 7→3→8→7→57→3→8→7→5 的路径产生了最大
输入格式
第一个行一个正整数 rr ,表示行的数目。
后面每行为这个数字金字塔特定行包含的整数。
输出格式
单独的一行,包含那个可能得到的最大的和。
输入输出样例
输入 #1
5
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5输出 #1
30
说明/提示
【数据范围】
对于 100%100% 的数据,1≤r≤10001≤r≤1000,所有输入在 [0,100][0,100] 范围内。题目翻译来自NOCOW。
USACO Training Section 1.5
IOI1994 Day1T1
思路:
将从最高点选到最低点转换为从最低点选到最高点,用一个二维数组dp记录,dp[1][1]表示从底部选到最高点最大的和,开始遍历所有底部的数字,从底部的数字开始,选左上或选右上对应01背包问题中的拿与不拿
/*
经典数字三角形问题
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
在上面的数字三角形中寻找一条从顶部到底边的路径,使得路径上所经过的数字之和最大。
路径上的每一步只能往左下或右下走。只需求出最大和即可,不必给出具体路径
#include<stdio.h>//递归求解
#define MAX 101
using namespace std;
int D[MAX][MAX];
/*
7
3 8
8 1 0
2 7 4
4 5 2 6 5//在D二维数组中的存储
int n;
int max(int a, int b) {
return a > b ? a : (a < b ? b : a);
}
int maxsum(int i, int j) {
if (i == n) {
return D[i][j];
}
int x = maxsum(i + 1, j);
int y = maxsum(i + 1, j + 1);
return max(x, y) + D[i][j];
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= i; j++) {
scanf("%d", &D[i][j]);
}
}
printf("%d", maxsum(1, 1));
}
//出现了大量重复计算
//、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、
#include<stdio.h>//递归求解,去掉重复计算
#define MAX 101
using namespace std;
int D[MAX][MAX];
int summax[MAX][MAX];//
int n;
int max(int a, int b) {
return a > b ? a : (a < b ? b : a);
}
int maxsum(int i, int j) {
if (summax[i][j] != -1) {//
return summax[i][j];//
}
if (i == n) {
summax[i][j] = D[i][j];
} else {
int x = maxsum(i + 1, j);
int y = maxsum(i + 1, j + 1);
summax[i][j]=max(x, y) + D[i][j];
return summax[i][j];
}
return summax[i][j];
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= i; j++) {
scanf("%d", &D[i][j]);
summax[i][j] = -1;//
}
}
printf("%d", maxsum(1, 1));
}
*/
//、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、
#include<stdio.h>//改为动态规划,空间换时间
#define MAX 101
int D[MAX][MAX];
int dp[MAX][MAX];
int n;
int max(int a, int b) {
return a > b ? a : (a < b ? b : a);
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= i; j++) {
scanf("%d", &D[i][j]);
}
}
for (int i = 1; i <= n; ++i) {//初始化,方便状态的更新
dp[n][i] = D[n][i];
}
for (int i = n ; i >= 1; --i) {
for (int j = 1; j <=i; j++) {
dp[i][j] = max(dp[i + 1][j], dp[i + 1][j + 1]) + D[i][j];//状态转移方程
}
}
printf("%d", dp[1][1]);
}
/*
5
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
*/
过河卒:
题目描述
棋盘上 AA 点有一个过河卒,需要走到目标 BB 点。卒行走的规则:可以向下、或者向右。同时在棋盘上 CC 点有一个对方的马,该马所在的点和所有跳跃一步可达的点称为对方马的控制点。因此称之为“马拦过河卒”。
棋盘用坐标表示,AA 点 (0,0)(0,0)、BB 点 (n,m)(n,m),同样马的位置坐标是需要给出的。
现在要求你计算出卒从 AA 点能够到达 BB 点的路径的条数,假设马的位置是固定不动的,并不是卒走一步马走一步。
输入格式
一行四个正整数,分别表示 BB 点坐标和马的坐标。
输出格式
一个整数,表示所有的路径条数。
输入输出样例
输入 #1
6 6 3 3
输出 #1
6
说明/提示
对于 100%100% 的数据,1≤n,m≤201≤n,m≤20,0≤0≤ 马的坐标 ≤20≤20。
【题目来源】
NOIP 2002 普及组第四题
思路:
卒有两个方向,左和右,马有控制点,不能走到马的控制点,用dp[i][j]数组记录走到(i,j)点的所有路径,某点的路径条数为走到该点的左边和走到该点的上边的路径条数之和
代码:
#include<stdio.h>
#define lo long long//该题第三个测试点要long long
//卒有两个方向可以走,相当于背包问题中的拿与不拿,
//有马,即不能走马的控制点,
int main() {
lo tox[9] = {1, 2, 2, 1, 0, -1, -2, -2, -1};
lo toy[9] = {2, 1, -1, -2, 0, -2, -1, 1, 2};
lo a, b, c, d;
lo ma[21][21] = {0}, dp[21][21] = {0}; //dp[i][j]点表示的是到(i,j)点的路径条数
scanf("%lld %lld %lld %lld", &a, &b, &c, &d);
for (int i = 0; i < 9; i++) {//标记马的控制点
if (c + tox[i] >= 0 && d + toy[i] >= 0) { //标记点不能越界
ma[c + tox[i]][d + toy[i]] = 1;//标记为控制点
}
}
for (int x = 0; x <= a; x++) {
for (int y = 0; y <= b; y++) {
if (ma[x][y] == 1) {
continue;
} else {
if (x == 0 && y != 0) {//0的边界点
dp[x][y] = dp[x][y - 1];
} else if (y == 0 && x != 0) {
dp[x][y] = dp[x - 1][y];
} else if (y == 0 && x == 0) {
dp[x][y] = 1;
} else {
dp[x][y] = dp[x - 1][y] + dp[x][y - 1]; //该点的路径条数为走到该点的左边和走到该点的上边的路径条数之和
}
}
}
}
printf("%lld", dp[a][b]);
}