「LOJ2267」「SDOI2017Round2」龙与地下城

题目描述

小 Q 同学是一个热爱学习的人,但是他最近沉迷于各种游戏,龙与地下城就是其中之一。 在这个游戏中,很多场合需要通过掷骰子来产生随机数,并由此决定角色未来的命运,因此骰子堪称该游戏的标志性道具。

骰子也分为许多种类,比如 4 面骰、6 面骰、8 面骰、12 面骰、20 面骰,其中 20 面骰用到的机会非常多。当然,现在科技发达,可以用一个随机数生成器来取代真实的骰子,所以这里认为骰子就是一个随机数生成器。

在战斗中,骰子主要用来决定角色的攻击是否命中,以及命中后造成的伤害值。举个例子,假设现在已经确定能够命中敌人,那么 YdX Y d X (也就是掷出 Y Y X 面骰子之后所有骰子显示的数字之和)就是对敌人的基础伤害。在敌人没有防御的情况下,这个基础伤害就是真实伤害。

众所周知,骰子显示每个数的概率应该是相等的,也就是说,对于一个 X X 面骰子,显示
0,1,2X1 中每一个数字的概率都是 1X 1 X ​​ 。

更形式地说,这个骰子显示的数 W W 满足离散的均匀分布,其分布列为

W 0 0 1 2 2 X1 X − 1 P P 1X 1X 1 X 1X 1 X 1X 1 X

除此之外还有一些性质

W W 的一阶原点矩(期望)为

v1(W)=E(W)=i=0X1iP(W=i)=X12

​​
W W 的二阶中心矩(方差)为

μ2(W)=E((WE(W))2)=i=0X1(iE(W))2P(W=i)=X2112

​​
言归正传,现在小 Q 同学面对着一个生命值为 A A 的没有防御的敌人,能够发动一次必中的 YdX 攻击,显然只有造成的伤害不少于敌人的生命值才能打倒敌人。但是另一方面,小 Q 同学作为强迫症患者,不希望出现 overkill,也就是造成的伤害大于 B B 的情况,因此只有在打倒敌人并且不发生 overkill 的情况下小 Q 同学才会认为取得了属于他的胜利。
因为小 Q 同学非常谨慎,他会进行 10 次模拟战,每次给出敌人的生命值 A 以及 overkill 的标准 B B ,他想知道此时取得属于他的胜利的概率是多少,你能帮帮他吗?

输入格式

第一行是一个正整数 T ,表示测试数据的组数。
对于每组测试数据,第一行是两个整数 X X Y,分别表示骰子的面数以及骰子的个数。
接下来 10 10 行,每行包含两个整数 A A B,分别表示敌人的生命值 A A 以及 overkill 的标准 B

输出格式

对于每组测试数据,输出 10 10 行,对每个询问输出一个实数,要求绝对误差不超过 0.013579 0.013579 ,也就是说,记输出为 a a ,答案为 b,若满足 |ab|0.013579 | a − b | ≤ 0.013579 ,则认为输出是正确的。

样例

样例输入
1
2 19
0 0
0 1
0 2
0 3
0 4
0 5
0 6
0 7
0 8
0 9
样例输出
0.000002
0.000038
0.000364
0.002213
0.009605
0.031784
0.083534
0.179642
0.323803
0.500000

数据范围与提示

对于 100% 100 % 的数据, T10 T ≤ 10 2X20 2 ≤ X ≤ 20 1Y200000 1 ≤ Y ≤ 200000 0AB(X1)Y 0 ≤ A ≤ B ≤ ( X − 1 ) Y ,保证满足 Y>800 Y > 800 的数据不超过 2 2 组。

题解

应用中心极限定理 。中心极限定理 - 维基百科

设随机变量 X1,X2,,Xn 独立同分布, 且具有有限的数学期望和方差 E(Xi)=μ E ( X i ) = μ D(Xi)=σ20(i=1,2,,n) D ( X i ) = σ 2 ≠ 0 ( i = 1 , 2 , ⋯ , n ) 。记
X¯=1nni=1Xi X ¯ = 1 n ∑ i = 1 n X i ζn=X¯μσ/n ζ n = X ¯ − μ σ / n ,则 limnP(ζnz)=Φ(z) lim n → ∞ P ( ζ n ≤ z ) = Φ ( z )
其中 Φ(z) Φ ( z ) 是标准正态分布的分布函数。

这个定理的含义是: n n 个独立同分布,且具有有限的数学期望 μ 和方差 σ2 σ 2 的随机变量,他们的平均值的分布函数在 n n → ∞ 时无限近似于期望为 μ μ ,方差为 σ2n σ 2 n 的正态分布。

考虑到题目要求的精度误差十分宽松,我们考虑在 Y Y 比较大的时候用正态分布函数的积分来近似答案。

由于正态分布函数的概率密度函数

Φ(x)=1σ2πe(xμ)22σ2

的不定积分不是初等函数,所以考虑自适应辛普森积分。函数是光滑的,所以误差很小。

有一点需要注意:由于峰比较窄,积分区间比较大的时候可能出现 l,3l+r4,l+r2,l+3r4,r l , 3 l + r 4 , l + r 2 , l + 3 r 4 , r 五处的函数值都几乎为零,但是峰在积分区间内的情况,这会导致答案错误。一种方法是分成若干个小的区间分别进行辛普森积分,第二种方式是只计算 [A,B][μ3σ,μ+3σ] [ A , B ] ∩ [ μ − 3 σ , μ + 3 σ ] 的积分(因为正态分布函数在 [μ3σ,μ+3σ] [ μ − 3 σ , μ + 3 σ ] 外的比率只占不到 0.3% 0.3 % ,可以忽略不计), 还有一种方法是直接使用误差函数 erf(x)=2πx0et2dt e r f ( x ) = 2 π ∫ 0 x e − t 2 d t 直接计算(需要C++11)。

My Code

#include <bits/stdc++.h>
#define pi acos(-1)
#define eps 1e-15

using namespace std;
int n, m;
double f[2][32005], g[2][32005];
void solve1(){
    int p = 0, q = 1;
    f[p][0] = 1; g[p][0] = 1;
    for(int i = 1; i <= n; i ++){
        for(int j = 0; j <= (m - 1) * i; j ++){
            double tmp = g[p][min(j, (m - 1) * (i - 1))]; if(j >= m) tmp -= g[p][j - m];
            f[q][j] = tmp / m; g[q][j] = f[q][j]; if(j > 0) g[q][j] += g[q][j - 1];
        //  printf("%d %d %.6lf\n", i, j, f[q][j]);
        }
        swap(p, q);
    }
    for(int i = 1; i <= 10; i ++){
        int a, b;
        scanf("%d%d", &a, &b);
        double ans = g[p][b]; if(a != 0) ans -= g[p][a - 1];
        printf("%.6lf\n", ans); 
    }
}
double mu, sigma, pi2;
inline double sqr(double x) {return x * x;}
inline double F(double x){
//  printf("%.10lf %.10lf\n", x, exp(-sqr(x - mu) / 2 / sqr(sigma)) / pi2 / sigma);
    return exp(-sqr(x - mu) / 2 / sqr(sigma)) / pi2 / sigma;
}
double cal(double l, double r){
    double mid = (l + r) / 2;
    return (r - l) / 6 * (F(l) + 4 * F(mid) + F(r));
}
double simpson(double l, double r, double cur){
    double mid = (l + r) / 2;
    double cl = cal(l, mid);
    double cr = cal(mid, r);
//  printf("%.11lf %.11lf %.11lf %.11lf %.11lf %.11lf\n", l, r, cl, cr, cur, fabs(cur - cl - cr));
    if(fabs(cur - cl - cr) < eps) return cur;
    return simpson(l, mid, cl) + simpson(mid, r, cr);
}

void solve2(){
    mu = 1.0 * (m - 1) / 2.0 * n;
    sigma = sqrt(n * 1.0 * (m * m - 1.0) / 12.0);
    //printf("%.6lf %.6lf\n", mu, sigma);
    pi2 = sqrt(2.0 * pi);
        for(int i = 1; i <= 10; i ++){
        int a, b;
        scanf("%d%d", &a, &b);
        double l = max((double)a, mu - 3 * sigma);
        double r = min((double)b, mu + 3 * sigma); 
        double ans = 0;
        if(l < r) ans = simpson(l, r, cal(l, r));
        printf("%.6lf\n", ans);
    }
}
int main(){
    int T;
    scanf("%d", &T);
    while(T--){
        scanf("%d%d", &m, &n);
        if(n <= 1600){
            solve1();
        }else{
            solve2();
        } 
    }   
    return 0;
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值