n个小球放入m个盒子中_193:钉子和小球【4.5算法之动态规划】068薛

这篇博客介绍了如何使用动态规划解决一个概率问题:在一个钉子形成的金字塔结构中,计算小球滚落后落在特定格子的概率。文章通过分析问题,提出将问题转化为动态规划,并给出算法实现,包括递归计算和概率求和。同时,博主分享了在实现过程中遇到的大数溢出问题及解决方案,强调了算法在解决问题中的关键作用。
摘要由CSDN通过智能技术生成

3a0c8de43dd2bf826767e771df905bd6.png

钉子和小球,形成了一个按照等差数列来排序的金字塔结构

总时间限制:1000ms 内存限制:65536kB

同学们,我们不妨回忆一下,我们在哪里见到过这个图形呢???(#^.^#)

e1e360cb734b855a28a1c3bcfc81b932.png

好吧,就是以前老师上概率的时候呀。

描述

有一个三角形木板,竖直立放,上面钉着n(n+1)/2颗钉子,还有(n+1)个格子(当n=5时如图1)。每颗钉子和周围的钉子的距离都等于d,每个格子的宽度也都等于d,且除了最左端和最右端的格子外每个格子都正对着最下面一排钉子的间隙。
让一个直径略小于d的小球中心正对着最上面的钉子在板上自由滚落,小球每碰到一个钉子都可能落向左边或右边(概率各1/2),且球的中心还会正对着下一颗将要碰上的钉子。例如图2就是小球一条可能的路径。
我们知道小球落在第i个格子中的概率pi=

626c242e9e6b6dcc392dc7dce777c1aa.png

,其中i为格子的编号,从左至右依次为0,1,...,n。
现在的问题是计算拔掉某些钉子后,小球落在编号为m的格子中的概率pm。假定最下面一排钉子不会被拔掉。例如图3是某些钉子被拔掉后小球一条可能的路径。

输入

第1行为整数n(2 <= n <= 50)和m(0 <= m <= n)。以下n行依次为木板上从上至下n行钉子的信息,每行中'*'表示钉子还在,'.'表示钉子被拔去,注意在这n行中空格符可能出现在任何位置。

输出

仅一行,是一个既约分数(0写成0/1),为小球落在编号为m的格子中的概pm。既约分数的定义:A/B是既约分数,当且仅当A、B为正整数且A和B没有大于1的公因子。

样例输入

5 2
    *
   * .
  * * *
 * . * *
* * * * *

样例输出

7/16

题目分析

本题是典型的动态规划问题。每一个小球面临每一个“下落”都有3种不同的选择:

1。来自(i-1,j-1),来自左上方的“钉子”那么当前又有一个球经过i行j列

2。来自 (i-1,j),来自右上方“钉子”,那么当前又有一个球经过i行j列

3。来自(i-2,j-1)没有钉子的话,经过这个位置的小球不是来自“左上”“右上”格子因为“左上”和“右上”的钉子可能直接掉下去

也就是说,我们只需要用if判断每种情况然后用递归算出3种不同的情况对应的概率然后加起来就可以了。可是问题来了:概率?怎么表示??我的想法是,一个球一共有2^n种下落的情况,我们把2^n作为分母,把某个位置(i行j列)可能经过的次数作为分子,就可以得出概率。

数据结构

long long int a[60][60];//a[i][j]——有多少个球会经过第i行第j列的位置

int b[60][60];//整型数组b——记录当前对应位置有没有钉子

代码及其算法说明

#include<iostream>
#include<algorithm>
using namespace std;
	long long int  a[60][60];//a[i][j]——有多少个球会经过第i行第j列的位置
	int b[60][60];//整型数组b——记录当前对应位置有没有钉子 
	int n;//n为等差数列的最后一行钉子数 
	int m;//可能落入的格子编号 
int main(){

	 
	long long int ans=0; 	//ans表示可能的落入的分母值 
	char c;//c如果是*——钉子;c如果是。——没东西
	 
	cin>>n>>m;
	
	for(int i = 0;i < n;i++)
	{//i记录当前遍历的行数 
		for(int j = 0;j <= i;j++)
					{//j记录当前遍历的列数 
						cin>>c;
						if(c=='.')
						 b[i][j]=1;//如果没有钉子的话,就记录当前的整形数组对应位置为1 
						else
						 b[i][j]=-1;//如果有钉子的话,就记录当前的整形数组对应位置为-1 
					}
	}
	a[0][0]=1;
	for( int i=1;i<=n;i++){
		for( int j=0;j<=i;j++){
			/*每一个位置(i,j)的球可能来自三个地方(如果存在):(i-1,j-1),(i-1,j),(i-2,j-1),
			递归算出三个地方的概率再加起来就行了。*/
						if(j >= 0 && b[i-1][j-1] == -1) a[i][j]+= a[i-1][j-1];
						//如果来自(i-1,j-1)
						//那么当前又有一个球经过i行j列 
						if(j != i && b[i-1][j]  == -1) a[i][j]+= a[i-1][j];
						//如果来自 (i-1,j),
						//那么当前又有一个球经过i行j列 
						if(i >= 2 && j >= 1 && b[i-2][j-1] == 1) a[i][j] += a[i-2][j-1] * 4;
						//如果来自(i-2,j-1)没有钉子的话,经过这个位置的小球可能来自“左上”“右上”格子
						//因为“左上”和“右上”的钉子可能直接掉下去 
						if(i==n) ans = ans + a[i][j];//分母为2^n个球 
							}
						}
	while(a [n][m] % 2 == 0&& a[n][m] != 0){
	a[n][m]  /=2;//如果可以约分就约分
	 
	ans  /=2;
											}
	if(a[n][m]==0)
		 ans=1;//输出为0/1的情况 
		 
	cout<<a[n][m]<<"/"<<ans<<endl;
	return 0;
}

算法分析与优化

1。我们十分循规蹈矩地运用了递归,十分循规蹈矩地运用了约分。

2。我在提交的过程中失败了3次,一次4分,两次8分,后来只好借鉴网上的方法思路,才知道因为大数乘法产生的数太大了于是乎溢出了,要用long long int 才可以存储。于是我把a数组和ans 都变为long long int 型

3。至于有没有可以更进一步的地方,我觉得还是有的,比如这个算法必须单独考虑0/1的情况,有没有一个更好的算法可以不单独考虑这一个情况呢?

最后:还是要说——算法改变世界!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值