USTCOJ 1388 钻石

来源:互联网 发布:淘宝利润计算 编辑:程序博客网 时间:2024/03/29 15:50

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

题目链接: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;elsereturn 0.0;}elsereturn 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不大.再大一点难度就会增加了,需要另外方法求出组合数,或者换个思路求概率。总之,这题就是这样啦。