动态规划题目练习

基础知识:

动态规划背包问题-CSDN博客

动态规划基础概念-CSDN博客

题目练习:

 题目1:过河卒

题目描述

棋盘上 A 点有一个过河卒,需要走到目标 B 点。卒行走的规则:可以向下、或者向右。同时在棋盘上 C 点有一个对方的马,该马所在的点和所有跳跃一步可达的点称为对方马的控制点。因此称之为“马拦过河卒”。

棋盘用坐标表示,A 点 (0,0)、B 点 (n,m),同样马的位置坐标是需要给出的。

现在要求你计算出卒从 A 点能够到达 B 点的路径的条数,假设马的位置是固定不动的,并不是卒走一步马走一步。

输入格式

一行四个正整数,分别表示 B 点坐标和马的坐标。

输出格式

一个整数,表示所有的路径条数。

输入输出样例

输入 #1复制

6 6 3 3

输出 #1复制

6

说明/提示

对于 100% 的数据,1≤n,m≤20,0≤马的坐标≤20。

【题目来源】

NOIP 2002 普及组第四题

代码加注释:

#include<stdio.h>  
#include<string.h>  
  
// 定义一个二维数组dp,用于存储到达每个位置的不同路径数  
long long int dp[100][100];  
// 定义一个二维数组s,用于标记障碍物位置  
bool s[100][100];  
  
int main() {  
	// 初始化dp数组为0  
	memset(dp, 0, sizeof(dp));  
	  
	int x, y, x1, y1;  
	// 输入起始点和终点坐标  
	scanf("%d%d%d%d", &x, &y, &x1, &y1);  
	  
	// 将坐标值加2,这样坐标(1,1)在数组中的位置就是(3,3),以便在数组边界外有空间  
	x += 2, y += 2, x1 += 2, y1 += 2;  
	  
	// 定义八个方向的偏移量,用于标记障碍物周围的位置  
	int next[8][2] = { {1,2},{1,-2},{2,1},{2,-1},{-1,-2},{-2,-1},{-1,2},{-2,1} };  
	  
	// 将终点标记为障碍物  
	s[x1][y1] = 1;  
	  
	// 将终点周围八个方向的位置也标记为障碍物  
	for (int i = 0; i < 8; i++) {  
		s[x1 + next[i][0]][y1 + next[i][1]] = 1;  
	}  
	  
	// 初始化起点位置到起点的路径数为1  
	dp[2][1] = 1;  
	  
	// 动态规划计算到达每个位置的不同路径数  
	for (int i = 2; i <= x; i++) {  
		for (int j = 2; j <= y; j++) {  
			// 如果当前位置是障碍物,则跳过  
			if (s[i][j]) continue;  
			// 当前位置的不同路径数等于它左边和上边位置的不同路径数之和  
			dp[i][j] = dp[i][j - 1] + dp[i - 1][j];  
		}  
	}  
	  
	// 输出从起点到终点的不同路径数  
	printf("%lld", dp[x][y]);  
  
	return 0;  
}
//注意:

//代码中使用x += 2, y += 2, x1 += 2, y1 += 2是为了让实际坐标映射到数组dp和s时留有边界空间,
//防止数组越界。
//dp[i][j]保存的是到达位置(i, j)的不同路径数。
//s[i][j]用于标记障碍物位置,其中true表示是障碍物,false表示不是障碍物。
//动态规划过程中,通过累加左边和上边位置的路径数来更新当前位置的路径数。

 题目2:装箱问题

题目描述

有一个箱子容量为 V,同时有 n 个物品,每个物品有一个体积。

现在从 n 个物品中,任取若干个装入箱内(也可以不取),使箱子的剩余空间最小。输出这个最小值。

输入格式

第一行共一个整数 V,表示箱子容量。

第二行共一个整数 n,表示物品总数。

接下来 n 行,每行有一个正整数,表示第 i 个物品的体积。

输出格式

共一行一个整数,表示箱子最小剩余空间。

输入输出样例

输入 #1复制

24
6
8
3
12
7
9
7

输出 #1复制

0

说明/提示

对于 100%100% 数据,满足0<n≤301≤V≤20000。

【题目来源】

NOIP 2001 普及组第四题

代码加解析:

#include<stdio.h>  // 引入标准输入输出库  
#include<iostream> // 引入输入输出流库,但实际上代码中并未使用iostream的功能  
using namespace std; // 使用标准命名空间  
  
int a[100]; // 定义一个整型数组a,用于存储n个物品的价值  
int dp[20100]; // 定义一个整型数组dp,用于存储容量为j时的最大价值  
  
int main()  
{  
    int V, n; // 定义两个整型变量V和n,分别表示背包的总容量和物品的数量  
    scanf("%d%d", &V, &n); // 输入背包的总容量V和物品的数量n  
  
    for (int i = 1; i <= n; i++) {  
        scanf("%d", &a[i]); // 输入每个物品的价值,并存储在数组a中  
    }  
  
    dp[0] = 0; // 初始化dp数组,当容量为0时,最大价值为0  
  
    // 动态规划的主体部分  
    for (int i = 1; i <= n; i++) { // 遍历每个物品  
        for (int j = V; j >= a[i]; j--) { // 遍历背包的每个可能容量,从大到小遍历是为了保证每个物品只被选取一次  
            dp[j] = max(dp[j], dp[j - a[i]] + a[i]); // 更新dp数组,如果当前物品能放入背包并且加上当前物品后的总价值更大,则更新dp[j]  
        }  
    }  
  
    printf("%d", V - dp[V]); // 输出剩余容量,即总容量V减去最大价值dp[V],即表示背包装不下的物品总价值  
  
    return 0; // 主函数返回0,表示程序正常结束  
}

 题目3:小A点菜

题目背景

uim 神犇拿到了 uoi 的 ra(镭牌)后,立刻拉着基友小 A 到了一家……餐馆,很低端的那种。

uim 指着墙上的价目表(太低级了没有菜单),说:“随便点”。

题目描述

不过 uim 由于买了一些书,口袋里只剩 M 元 (M≤10000)。

餐馆虽低端,但是菜品种类不少,有 N 种 ((N≤100),第 i 种卖 ai​ 元 (ai​≤1000)。由于是很低端的餐馆,所以每种菜只有一份。

小 A 奉行“不把钱吃光不罢休”,所以他点单一定刚好把 uim 身上所有钱花完。他想知道有多少种点菜方法。

由于小 A 肚子太饿,所以最多只能等待 11 秒。

输入格式

第一行是两个数字,表示 N 和 M。

第二行起 N 个正数 ai​(可以有相同的数字,每个数字均在 10001000 以内)。

输出格式

一个正整数,表示点菜方案数,保证答案的范围在 int 之内。

输入输出样例

输入 #1复制

4 4
1 1 2 2

输出 #1复制

3

说明/提示

2020.8.29,增添一组 hack 数据 by @yummy

代码加详细解析:

#include<stdio.h>  
  
// 定义两个数组,a用于存储菜品的价格,dp用于存储动态规划的状态  
int a[200];  
int dp[200][10000];  
  
int main()  
{  
	int n, m;  
	scanf("%d%d", &n, &m); // 输入菜品的种类数n和拥有的钱数m  
  
	// 读取每种菜品的价格  
	for (int i = 1; i <= n; i++) {  
		scanf("%d", &a[i]);  
	}  
  
	// 初始化dp数组,dp[0][0]表示没有菜品可选且钱数为0时的方法数为1(不选任何菜品)  
	dp[0][0] = 1;  
  
	// 动态规划的主体部分  
	for (int i = 1; i <= n; i++) { // 遍历每种菜品  
		for (int j = 0; j <= m; j++) { // 遍历当前拥有的钱数  
			// 如果当前钱数大于菜品价格  
			if (j >= a[i]) {  
				// 则可以选择购买当前菜品,方法数等于不购买当前菜品的方法数加上购买当前菜品后剩余钱数的方法数  
				dp[i][j] = dp[i - 1][j] + dp[i - 1][j - a[i]];  
			} else {  
				// 如果当前钱数小于菜品价格,则不能购买当前菜品,方法数等于不购买当前菜品的方法数  
				dp[i][j] = dp[i - 1][j];  
			}  
		}  
	}  
  
	// 输出在拥有m元钱时,可以选择的菜品组合方法数  
	printf("%d", dp[n][m]);  
  
	return 0;  
}

题目4:考前临时抱佛脚

题目背景

kkksc03 的大学生活非常的颓废,平时根本不学习。但是,临近期末考试,他必须要开始抱佛脚,以求不挂科。

题目描述

这次期末考试,kkksc03 需要考 44 科。因此要开始刷习题集,每科都有一个习题集,分别有 s1​,s2​,s3​,s4​ 道题目,完成每道题目需要一些时间,可能不等(A1​,A2​,…,As1​​,B1​,B2​,…,Bs2​​,C1​,C2​,…,Cs3​​,D1​,D2​,…,Ds4​​)。

kkksc03 有一个能力,他的左右两个大脑可以同时计算 22 道不同的题目,但是仅限于同一科。因此,kkksc03 必须一科一科的复习。

由于 kkksc03 还急着去处理洛谷的 bug,因此他希望尽快把事情做完,所以他希望知道能够完成复习的最短时间。

输入格式

本题包含 55 行数据:第 11 行,为四个正整数 s1​,s2​,s3​,s4​。

第 22 行,为 A1​,A2​,…,As1​​ 共 s1​ 个数,表示第一科习题集每道题目所消耗的时间。

第 33 行,为 B1​,B2​,…,Bs2​​ 共 s2​ 个数。

第 44 行,为 C1​,C2​,…,Cs3​​ 共 s3​ 个数。

第 55 行,为 D1​,D2​,…,Ds4​​ 共 s4​ 个数,意思均同上。

输出格式

输出一行,为复习完毕最短时间。

输入输出样例

输入 #1复制

1 2 1 3		
5
4 3
6
2 4 3

输出 #1复制

20

说明/提示

1≤s1​,s2​,s3​,s4​≤20。

1≤A1​,A2​,…,As1​​,B1​,B2​,…,Bs2​​,C1​,C2​,…,Cs3​​,D1​,D2​,…,Ds4​​≤60。

代码加解析:

#include<stdio.h> // 引入标准输入输出库  
#include<iostream> // 引入标准输入输出流库  
using namespace std; // 使用标准命名空间  
  
// 定义全局变量  
int a[5]; // 数组,用于存储四组习题集的数量  
int sum; // 变量,用于计算每一组习题集的总时间  
int t; // 变量,用于存储一共所需的总时间  
int h[21]; // 数组,用于存储每个习题集的完成所需时间  
int dp[2051]; // 动态规划数组,用于存储中间结果  
  
int main() {  
    // 读取四组习题集的数量  
    for (int i = 1; i <= 4; i++) {  
        scanf("%d", &a[i]); // 从标准输入读取每组习题集的数量  
    }  
  
    // 处理每组习题集  
    for (int i = 1; i <= 4; i++) {  
        sum = 0; // 初始化sum为0,用于计算当前组习题集的总时间  
          
        // 读取当前组每个习题集的完成所需时间,并计算总时间  
        for (int j = 1; j <= a[i]; j++) {  
            scanf("%d", &h[j]); // 从标准输入读取当前习题集的完成所需时间  
            sum += h[j]; // 累加当前习题集的完成时间到sum  
        }  
  
        // 动态规划处理当前组习题集  
        for (int j = 1; j <= a[i]; j++) {  
            for (int k = sum / 2; k >= h[j]; k--) {  
                // 更新dp[k]为考虑第j个习题集时的最大和  
                dp[k] = max(dp[k], dp[k - h[j]] + h[j]);  
            }  
        }  
  
        // 计算当前组习题集的最大差值,并累加到总时间t  
        t += sum - dp[sum / 2];  
  
        // 重置dp数组,为下一组习题集做准备  
        for (int j = 1; j <= sum / 2; j++) {  
            dp[j] = 0;  
        }  
    }  
  
    // 输出总时间t  
    printf("%d", t);  
  
    return 0; // 程序正常结束  
}

题目5:挖地雷

 题目描述

在一个地图上有 N\(N <= 20) 个地窖,每个地窖中埋有一定数量的地雷。同时,给出地窖之间的连接路径。当地窖及其连接的数据给出之后,某人可以从任一处开始挖地雷,然后可以沿着指出的连接往下挖(仅能选择一条路径),当无连接时挖地雷工作结束。设计一个挖地雷的方案,使某人能挖到最多的地雷。

输入格式

有若干行。

第 1行只有一个数字,表示地窖的个数 N。

第 2 行有 N 个数,分别表示每个地窖中的地雷个数。

第 3 行至第 N+1 行表示地窖之间的连接情况:

第 3 行有 n-1 个数(0 或 1),表示第一个地窖至第 2 个、第 3 个 \dots 第 n 个地窖有否路径连接。如第 3 行为 11000\cdots 0,则表示第 1个地窖至第 2 个地窖有路径,至第 3 个地窖有路径,至第 4 个地窖、第 5 个 \dots 第 n 个地窖没有路径。

第 4 行有 n-2 个数,表示第二个地窖至第 3 个、第 4 个 \dots 第 n 个地窖有否路径连接。

……

第 n+1 行有 1 个数,表示第 n-1 个地窖至第 n 个地窖有否路径连接。(为 $0$ 表示没有路径,为 $1$ 表示有路径)。

 输出格式

第一行表示挖得最多地雷时的挖地雷的顺序,各地窖序号间以一个空格分隔,不得有多余的空格。

第二行只有一个数,表示能挖到的最多地雷数。

输入输出样例

输入 #1复制

5
10 8 4 7 6
1 1 1 0
0 0 0
1 1
1

输出 #1复制

1 3 4 5
27

说明/提示

【题目来源】

NOIP 1996 提高组第三题

代码加注释:

#include<iostream>  
using namespace std;  
  
// 定义二维数组L,用于存储图的邻接矩阵  
int L[24][24];   
// 定义数组a,用于存储每个节点的权值  
int a[24];  
// 定义数组p,用于记录当前搜索路径上的点  
int p[24], ans[24], cnt;  
// 定义数组ans,用于记录最优路径上的点  
// 定义变量cnt,用于记录最优路径的长度  
// 定义布尔数组b,用于标记每个点是否已经被访问过  
bool b[24];  
// 定义变量n,表示节点的数量  
int n;  
// 定义变量maxx,用于存储最大权值和  
int maxx;  
  
// 函数ch:检查节点x是否可以作为路径的终点  
bool ch(int x) {  
    for (int i = 1; i <= n; i++) {  
        // 如果节点x与节点i之间有边相连且节点i未被访问过,则返回false  
        if (L[x][i] && !b[i])return false;  
    }  
    // 如果没有边相连或所有相连节点都已被访问过,则返回true  
    return true;  
}  
  
// 函数dfs:深度优先搜索  
void dfs(int x, int s, int sum) {  
    // 如果节点x可以作为路径的终点  
    if (ch(x)) {  
        // 如果当前路径的权值和大于已知的最大权值和  
        if (sum > maxx) {  
            // 更新最大权值和  
            maxx = sum;  
            // 更新最优路径的长度  
            cnt = s;  
            // 更新最优路径上的点  
            for (int i = 1; i <= n; i++) {  
                ans[i] = p[i];  
            }  
        }  
        // 结束搜索  
        return;  
    }  
    // 遍历与节点x相邻的节点  
    for (int i = 1; i <= n; i++) {  
        // 如果节点x与节点i之间有边相连且节点i未被访问过  
        if (L[x][i] && !b[i]) {  
            // 标记节点i为已访问  
            b[i] = 1;  
            // 将节点i添加到当前搜索路径中  
            p[s + 1] = i;  
            // 递归搜索节点i  
            dfs(i, s + 1, sum + a[i]);  
            // 回溯,标记节点i为未访问  
            b[i] = 0;  
        }  
    }  
}  
  
int main() {  
    // 输入节点数量  
    cin >> n;  
    // 输入每个节点的权值  
    for (int i = 1; i <= n; i++) {  
        cin >> a[i];  
    }  
    // 输入图的邻接矩阵  
    for (int i = 1; i < n; i++) {  
        for (int j = i + 1; j <= n; j++) {  
            cin >> L[i][j];  
            // 注意:这里只输入了上三角矩阵,因此还需要将下三角矩阵置为相同值,表示无向图  
            L[j][i] = L[i][j];  
        }  
    }  
    // 从每个节点开始搜索最优路径  
    for (int i = 1; i <= n; i++) {  
        // 标记节点i为已访问  
        b[i] = 1;  
        // 将节点i作为搜索路径的起点  
        p[1] = i;  
        // 搜索最优路径  
        dfs(i, 1, a[i]);  
        // 回溯,标记节点i为未访问  
        b[i] = 0;  
    }  
    // 输出最优路径上的点  
    for (int i = 1; i <= cnt; i++) {  
        printf("%d ", ans[i]);  
    }  
    printf("\n");  
    // 输出最大权值和  
    printf("%d", maxx);  
    return 0;  
}

题目会隔几天更新一次,一直到对这个知识点熟练为止。

  • 35
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值