USTCOJ 1388 钻石

这道题是一道原创的数学题,我的思路是直接算出询问点处的概率概率,仅供大家参考。

题目链接:http://acm.ustc.edu.cn/ustcoj/problem.php?id=1388

经过分析:不难得出,钻石下落后的行为是堆成一个小三角形,然后往两边继续堆,堆至最高点又形成一个稍大的三角形,然后继续往两边堆,如此往复.

因此,我们得出结论,对于n颗钻石堆成的一个小三角形加他的两个边,这样的钻石结构,如果询问点在小三角形内,概率p = 1;如果询问点在两边上,则需要特殊对待,

利用数学方法求得概率,而不符合以上两种的点,概率p = 0;

关键难点在于询问点在两边上的位置时的概率计算.(一开始曾考虑递归计算,后来发现写出来非常麻烦,wa了以后无法调试,就放弃了);

Step1:

我们先考虑两种简单情况,最后来解决数学问题.

显然如果钻石数是1, 6, 15, 28.....则恰好构成三角形,没有多余,判断点是否在三角形内也十分简单,

我们假设由这n颗钻石堆成的三角形底边是len,表示有len颗钻石,而不是表示长度,(len为奇数,1, 3, 5, 7...),虽然有可能出现如下图的情况:


但我还是把他当做底边为1的三角形和他的两条边来处理,而不是底边为2, 总之把对称轴放在钻石下落的线上好处理

那么底边为len的三角形,,最高处的钻石的高度坐标为len - 1,判断是否在三角形内只需要判断表达式:abs(x) + y < len - 1成立即可;

显然两边上的点,x,y满足abs(x) + y == len - 1,内部小于.

所以我们一起解决了,在三角形内,三角形外的两种情况.

Step2:

接下来算点在两边上的概率;

这时由于n的大小改变,我们易想到,如果n除了用于堆中间三角形的钻石,剩下的钻石还比较多的话,把一条边堆满了,剩下钻石就只会往另一边堆,

这是不是等概率的,而在那之前都是等概率,所以我们把这两种情况分开计算,

先判断是否能把一条边堆满(堆满是指:底边长有len的三角形,边上有len+1颗钻石,例如上图中,左边的边就被堆满了,这以后只能往右边落)

一共有n颗钻石,堆三角形用了(len + 1) * len / 2颗,剩下的即为两者之差d,差d和len + 1比较大小,大则必然会有钻石不能等概率选择左右落法,

小则之前的这么多颗钻石d都是等概率左右分配.

如果至多堆满一边,这d颗钻石每次等概率左右分配, 所有情况有2 ^ d种,对于询问的点,纵坐标反映了它所在边至少要有的钻石数,如果纵坐标是y,

那么至少要y + 1颗钻石, 所以符合要求的情况有C(y + 1, d)+ C(y + 2, d)+....+C(d, d).C(n, m)为组合数, 表示从m中选n个, 这种情况就是从d颗下落钻石中分别选y+1,y+2......d颗落在一边上.

如果满足可以把一条边堆满的情况,我们其实可以把他化为前一种情况,因为d > len + 1了,我们求出d和len+ 1的差记为t,那么我们知道无论怎么下落,两边上都至少有t颗钻石,(抽屉原理思想),又由于钻石是没有区别的,我们不妨把两边都先放上t颗钻石,那么可以知道,剩余的d-2t颗钻石最多填满一条边,化为了前一种情况,只需要注意一下,这时落在某边上的钻石数,从至少y+1颗变成了至少,y+1 - t颗,注意这个值可能是负的,说明,询问的点被我们提前处理时已经放上了钻石,所以特判,p = 1,其他就按原来的组合数求解即可.

最后可以写出如下代码,仅供参考:

#include <stdio.h>
#include <math.h>
#include<stdlib.h>

int com(int n, int m)
{
		long long re = 1, i;

		for (i = 1; i <= n; ++i)
				re *= (m + 1 - i);
		for (i--; i > 1; i--)
				re /= i;

		return re;
}

double pp(int x, int y, int n)
{
		int len, tem, i;

		for (len = 1; (len * len + len) / 2 < n; len += 2);
		if (abs(x) + y > len - 1)
				return 0.0;
		else
		{
				if (x == 0)
				{
						if (y)
						{
								if (n >= (y + 1) * (y + 2) / 2)
										return 1.0;
								else
										return 0.0;
						}
						else
								return 1.0;
				}
				else
				{
						if (abs(x) + y < len - 1)
								return 1.0;
						else
						{
								long long sum = 0;
								tem = n - (len - 2)* (len - 1) / 2;
								if (tem <= len - 1)
								{
										for (i = y + 1; i <= tem; ++i)
												sum += com(i, tem);
										return (double) sum / (double) (1 << tem);
								}
								else
								{
										int t = tem - len + 1;
										if (t > y + 1)
												return 1.0;
										else
										{
												tem -= 2 * t;
												for (i = y - t + 1; i <= tem; ++i)
														sum += com(i, tem);
												return (double) sum / (double) (1 << tem); 
										}
								}
						}
				}
		}
}

int main()
{
		int n, x, y, t, tt = 1;

		scanf("%d", &t);
		while (t--)
		{
				scanf("%d%d%d", &n, &x, &y);
				printf("Case #%d: %.6lf\n", tt++, pp(x, y, n));
		}

		return 0;
}

过程中写了一个组合数的函数,计算组合数可能会超过数据类型限制,所以题目中给的n不大.再大一点难度就会增加了,需要另外方法求出组合数,或者换个思路求概率。总之,这题就是这样啦。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值