P1002 过河卒
题目描述
棋盘上A点有一个过河卒,需要走到目标B点。卒行走的规则:可以向下、或者向右。同时在棋盘上C点有一个对方的马,该马所在的点和所有跳跃一步可达的点称为对方马的控制点。因此称之为“马拦过河卒”。
棋盘用座标表示,A点(0,0)、B点(n,m),同样马的位置座标是需要给出的。
现在要求你计算出卒从A点能够到达B点的路径的条数,假设马的位置是固定不动的,并不是卒走一步马走一步。
- 输入:一行四个正整数,分别表示B点座标和马的座标。
- 输出:一个整数,表示所有的路径条数。
解题步骤
- 初始化棋盘边界为1
- 状态方程:f[i][j] = f[i-1][j] + f[i][j-1]
注意事项
- 初始化数组时应 +1
- 状态方程的值会超过 int,可定义为 unsigned long long
- 考虑“马”处于横向边界时,该位置的右侧均初始化为0;处于纵向边界时,该位置的下方均初始化为0。
- 考虑“马”可一步跳到的位置处于横向纵向边界的情况,即“马”距离边界1或2的情况。
- 更新状态方程时,考虑经过位置是“马”一步跳到的位置时,该位置上的值应为0(不更新)。
提交代码
#include<iostream>
#include<math.h>
using namespace std;
int main() {
int bx, by, hx, hy; // b->B点,h->“horse”马的位置
cin >> bx >> by >> hx >> hy;
unsigned long long **map = new unsigned long long *[bx + 1];
for (int i = 0; i <= bx; i++) //动态二维数组
map[i] = new unsigned long long[by + 1];
for (int i = 0; i <= bx; i++)
map[i][0] = 1; //x轴边界初始化
for (int j = 0; j <= by; j++)
map[0][j] = 1; //y轴边界初始化
if (hx == 0) { //马在x边界
for (int j = hy; j <= by; j++)
map[0][j] = 0;
}
else if (hx == 1) { //马可跳到的位置在y边界(注:我没写错,就是y边界)
if (hy-2 >= 0) {
for (int j = hy-2; j <= by; j++)
map[0][j] = 0;
}
else if (hy + 2 <= by) {
for (int j = hy + 2; j <= by; j++)
map[0][j] = 0;
}
}
else if (hx == 2) { //马可跳到的位置在y边界
if (hy - 1 >= 0) {
for (int j = hy - 1; j <= by; j++)
map[0][j] = 0;
}
else if (hy + 1 <= by) {
for (int j = hy + 1; j <= by; j++)
map[0][j] = 0;
}
}
if (hy == 0) { //马在y边界
for (int i = hx; i <= bx; i++)
map[i][0] = 0
}
else if (hy == 1) { //马可跳到的位置在x边界
if (hx - 2 >= 0) {
for (int i = hx - 2; i <= bx; i++)
map[i][0] = 0;
}
else if (hx + 2 <= bx) {
for (int i = hx + 2; i <= bx; i++)
map[i][0] = 0;
}
}
else if (hy == 2) { //马可跳到的位置在x边界
if (hx - 1 >= 0) {
for (int i = hx - 1; i <= bx; i++)
map[i][0] = 0;
}
else if (hx + 1 <= bx) {
for (int i = hx + 1; i <= bx; i++)
map[i][0] = 0;
}
}
for (int i = 1; i <= bx; i++) {
for (int j = 1; j <= by; j++) {
if ((abs(i - hx) == 1 && abs(j - hy) == 2) || (abs(i - hx) == 2 && abs(j - hy) == 1) || (i == hx && j == hy))
map[i][j] = 0;
else
map[i][j] = map[i - 1][j] + map[i][j - 1];
}
}
cout << map[bx][by];
return 0;
}
P1044 栈
题目描述
宁宁考虑的是这样一个问题:一个操作数序列“1,2,…,n”,栈A的深度大于n。现在可以进行两种操作:
1. 将一个数,从操作数序列的头端移到栈的头端(对应数据结构栈的push操作)
2. 将一个数,从栈的头端移到输出序列的尾端(对应数据结构栈的pop操作)
使用这两种操作,由一个操作数序列就可以得到一系列的输出序列,你的程序将对给定的n,计算并输出由操作数序列1,2,…,n经过操作可能得到的输出序列的总数。
- 输入:一个整数n(1≤n≤18)
- 输出:可能输出序列的总数目
解题步骤
- 如图所示,规定从A到B只能够向右或向上移动,右移为入栈操作,上移为出栈操作,所求A点到B点的路径数目就是N个节点出栈序列的数目,并且从A点到B点的每一条路都代表一种出栈序列。
- 入栈次数>=出栈次数,向右次数>=向上次数。故只需右下部分矩阵(含对角线上的点)。
- 转化为棋盘的动态规划问题,模仿“P1002过河卒”解决,注意该棋盘只有右下部分。
提交代码
#include<iostream>
using namespace std;
int main() {
int n;
cin >> n;
unsigned long long **map = new unsigned long long *[n + 1];
for (int i = 0; i <= n; i++)
map[i] = new unsigned long long[n + 1];
for (int j = 0; j <= n; j++) //初始化下边界
map[0][j] = 1;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if (j > i)
map[i][j] = map[i - 1][j] + map[i][j - 1];
else if (j == i)
map[i][j] = map[i - 1][j];
else
continue;
}
}
cout << map[n][n];
return 0;
}
参考博客
- N个元素的进出栈总数-方法转换-动态规划
- 上述博客中还有优化方法——卡特兰数,但本博客着重动态规划,故不赘述。
P1048 采药
题目描述
辰辰是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。为此,他想拜附近最有威望的医师为师。医师为了判断他的资质,给他出了一个难题。医师把他带到一个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同的草药,采每一株都需要一些时间,每一株也有它自身的价值。我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。”
- 输入:第一行有2个整数T(1≤T≤1000)和M(1≤M≤100),用一个空格隔开,T代表总共能够用来采药的时间,M代表山洞里的草药的数目。接下来的M行每行包括两个在1到100之间(包括1和100)的整数,分别表示采摘某株草药的时间和这株草药的价值。
- 输出:1个整数,表示在规定的时间内可以采到的草药的最大总价值。
解题步骤
- 典型的0/1揹包问题
- 遍历考虑前i个物品的情况下的最优解
- 如果装不下当前物品,那么前i个物品的最佳组合和前i-1个物品的最佳组合是一样的。
- 如果装得下当前物品,有以下两种假设,选取其中较大价值的一个,为当前最佳组合的价值。
- 假设1:装当前物品,在给当前物品预留了相应空间的情况下,前i-1个物品的最佳组合加上当前物品的价值就是总价值。
- 假设2:不装当前物品,那么前i个物品的最佳组合和前i-1个物品的最佳组合是一样的。
- 注释部分包含了回溯输出索取物品是哪些
- 从后向前遍历n个物品
- 剩余空间相同的情况下,若前i个物品的组合与前i-1个物品的组合相同,则说明未装当前物品。
- 若当前组合符合上述的假设1,则说明装了当前物品,输出即可。
- 注意,找出一个物品后,应将剩余空间减少当前物品的重量。
提交代码
#include<iostream>
using namespace std;
int main() {
int t, m;
cin >> t >> m;
int * cost = new int[m + 1];
int * value = new int[m + 1];
for (int i = 1; i <= m; i++)
cin >> cost[i] >> value[i];
cost[0] = 0;
value[0] = 0;
int ** dp = new int*[m + 1];
for (int i = 0; i <= m; i++) {
dp[i] = new int[t + 1];
for (int j = 0; j <= t; j++)
dp[i][j] = 0;
}
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= t; j++) {
if (j >= cost[i])
dp[i][j] = dp[i - 1][j] > dp[i - 1][j - cost[i]] + value[i] ? dp[i - 1][j] : dp[i - 1][j - cost[i]] + value[i];
else
dp[i][j] = dp[i - 1][j];
}
}
cout << dp[m][t];
// 回溯揹包中的物品是哪些
/*
cout << endl << "What are they?" << endl;
int i = m, j = t;
while (i > 0) {
if (dp[i][j] == dp[i - 1][j]) {
i--;
continue;
}
else if (dp[i][j] == dp[i - 1][j - cost[i]] + value[i]) {
cout << cost[i] << " " << value[i] << endl;
j = j - cost[i];
}
i--;
}
*/
return 0;
}
参考博客
P1049 装箱问题
题目描述
有一个箱子容量为V(正整数,0≤V≤20000),同时有n个物品(0<n≤30,每个物品有一个体积(正整数)。要求n个物品中,任取若干个装入箱内,使箱子的剩余空间为最小。
- 输入:1个整数,表示箱子容量1个整数,表示有n个物品,接下来n行,分别表示这n个物品的各自体积。
- 输出:1个整数,表示箱子剩余空间。
解题步骤
- 典型0/1揹包问题
- 将物品体积同时看作空间消耗和价值即可
- 代码同上题,仅需修改读入参数即可,不再赘述。
P1060 开心的金明
题目描述
金明今天很开心,家里购置的新房就要领钥匙了,新房里有一间他自己专用的很宽敞的房间。更让他高兴的是,妈妈昨天对他说:“你的房间需要购买哪些物品,怎么布置,你说了算,只要不超过N元钱就行”。今天一早金明就开始做预算,但是他想买的东西太多了,肯定会超过妈妈限定的N元。于是,他把每件物品规定了一个重要度,分为5等:用整数1−5表示,第5等最重要。他还从因特网上查到了每件物品的价格(都是整数元)。他希望在不超过N元(可以等于N元)的前提下,使每件物品的价格与重要度的乘积的总和最大。
- 输入:第一行,为2个正整数,用一个空格隔开:nm(其中N(<30000)表示总钱数,m(<25)为希望购买物品的个数。)从第2行到第m+1行,第j行给出了编号为j−1的物品的基本数据,每行有2个非负整数vp(其中v表示该物品的价格(v≤10000),p表示该物品的重要度(1−5)
- 输出:1个正整数,为不超过总钱数的物品的价格与重要度乘积的总和的最大值(<100000000)。
解题步骤
- 典型0/1揹包问题
- 代码仅需修改
dp[i][j] = dp[i - 1][j] > dp[i - 1][j - v[i]] + v[i] * w[i] ? dp[i][j] = dp[i - 1][j] : dp[i - 1][j - v[i]] + v[i] * w[i];
即可,不再赘述。