蓝桥杯——印章、拿金币

题目来源:蓝桥杯算法训练
知识点:动态规划(DP)

动态规划介绍

动态规划常用于求解最优问题。在我看来,动态规划的求解过程是走一步看一步,每走一步都确保当前的状态下是最优的。也可以说,动态规划将大问题划分为多个阶段的子问题,每个子问题都求得最优解,那么组合成的大问题就得到了最优解。

我将动态规划的实现写成如下四个步骤:

  • 定义DP数组,一般是二维数组。这个数组描述了问题的状态和对应状态下的。这个所谓的一般就是我们要求的东西(最大数、最小数)。数组的行、列分别代表什么含义也是需要想清楚的。
  • 确定状态转移方程。如在dp[i][j]位置的值可以由dp[i-1][j]或者dp[i][j-1]位置经过变换得到,就可以写出类似这样的状态转移方程:dp[i][j]=max(dp[i-1][j],dp[i][j-1])
  • 设置边界条件。边界条件可以限制问题的边界,还可以为动态规划提供初始值。一般常见的边界条件是DP数组的行、列取 0、1 这种值时,DP数组对应的值。这个视具体问题定。
  • 动态规划开始,填充DP数组。按照定义的状态转移方程,填写DP数组。

印章

问题描述
共有n种图案的印章,每种图案的出现概率相同。小A买了m张印章,求小A集齐n种印章的概率。
输入格式
一行两个正整数n和m。
输出格式
一个实数P表示答案,保留4位小数。

样例输入
2 3
样例输出
0.7500
数据规模和约定
1≤n,m≤20

问题分析

按照上述动态规划的步骤,逐步分析问题:

  • 定义二维数组dpdp[i][j]表示买了i张印章,集齐了j张,数组的值为概率

  • 确定状态转移方程。dp[i][j]是在dp[i-1][j]或者dp[i-1][j-1]的基础上又买了一张,并集齐了j张。买了i-1张时可能集齐了j-1张或j张。

    dp[i-1][j]的状态下,变换到dp[i][j]的公式为:dp[i][j] = dp[i-1][j] * (j/n)。因为在购买i-1张时已经集齐了j张,所以再买一张概率为j / n的印章(意为与前j张的其中一张相同)即可满足“买了i张印章,集齐了j张”的情况。

    dp[i-1][j-1]的状态下,变换到dp[i][j]的公式为:dp[i][j] = dp[i-1][j-1] * (n-(j-1)/n)。因为在购买i-1张时已经集齐了j-1张,所以新的一张不能与之前的重复才能集齐j张,概率就是n-(j-1)/n

    两种情况都可能存在,因此变换到dp[i][j]的公式,即状态转移方程为:dp[i][j] = dp[i-1][j] * (j/n) + dp[i-1][j-1] * (n-(j-1)/n)

  • 设置边界条件。若买的印章数量小于集齐印章的数量,即i<j,这种情况显然是不可能的,概率就是0,dp[i][j] = 0。若j = 1,只需要买一张就能集齐,后面再买只是图案变化,就会得到不同的印章组合,所以此时的概率为:dp[i][j] = pow(1/n, i-1)

  • 填充数组。使用循环,从dp[1][1]开始遍历数组,根据状态转移方程填写数组。

代码
#include <bits/stdc++.h>
using namespace std;

int main() {
	int n, m;
	cin >> n >> m;
	
	double p = 1.0 / n;
	double dp[25][25] = {0};
	
	for(int i=1; i<=m; i++) {
		for(int j=1; j<=n; j++) {
			if(j > i) break;
			if(j == 1) dp[i][j] = pow(p, i - 1);
			else dp[i][j] = dp[i-1][j] * (j * 1.0 / n) + dp[i-1][j-1] * (1.0 * (n - j + 1) / n);
		}
	}
	
	cout << fixed << setprecision(4) << dp[m][n] << endl;
	
	return 0;
}

拿金币

问题描述
有一个N x N的方格,每一个格子都有一些金币,只要站在格子里就能拿到里面的金币。你站在最左上角的格子里,每次可以从一个格子走到它右边或下边的格子里。请问如何走才能拿到最多的金币。
输入格式
第一行输入一个正整数n。
以下n行描述该方格。金币数保证是不超过1000的正整数。
输出格式
最多能拿金币数量。

样例输入
3
1 3 3
2 2 2
3 1 2
样例输出
11
数据规模和约定
n<=1000

问题分析

本题是最优解问题,显然可以使用DP。有了上面的基础,按照动态规划的四个步骤就可以快速解题:

  • 定义二维数组dp,数组的值为在该状态下的最大金币数量。
  • 确定状态转移方程。定义保存每个位置的金币数量的数组为coin,那么dp[i][j]可以由其左边的格子dp[i][j-1]或其上面的格子dp[i-1][j]变换过来,两个值取其中的最大值即可。状态转移方程可以写为:dp[i][j] = coin[i][j] + max(dp[i][j-1], dp[i-1][j])
  • 设置边界条件。在数组第一行的格子只能由其左边的格子变化而来,写为:dp[i][j] = coin[i][j] + dp[i][j-1]。第一列的格子只能有其上面的格子变化而来,写为:dp[i][j] = coin[i][j] + dp[i-1][j]
  • 填充DP数组。
提示

本题 n 的范围较大,最好动态给数组分配空间。

代码
#include <bits/stdc++.h>
using namespace std;

int main() {
	int n;
	cin >> n;
	
	int** coin = new int*[n + 1];
	for(int i=1; i<=n; i++) {
		coin[i] = new int[n + 1]();
		for(int j=1; j<=n; j++) {
			cin >> coin[i][j];
		}
	}
	
	int** dp = new int*[n + 1];
	for(int i=1; i<=n; i++) {
		dp[i] = new int[n + 1]();
		for(int j=1; j<=n; j++) {
			if(i == 1) dp[i][j] = coin[i][j] + dp[i][j-1];
			else if(j == 1) dp[i][j] = coin[i][j] + dp[i-1][j];
			else dp[i][j] = coin[i][j] + max(dp[i-1][j], dp[i][j-1]);
		}
	}
	
	cout << dp[n][n] << endl;
	
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值