AtCoder [ABC310F] Make 10 Again

文章描述了一个使用概率动态规划(概率dp)解决的数学问题,涉及到多个骰子投掷后选取部分骰子使总点数为10的概率计算。通过状态压缩表示每个骰子是否能抛出1到10的点数,并设置转移方程进行求解,最后对答案取模得到结果。
摘要由CSDN通过智能技术生成

题目大意:

你有 N N N 个骰子,和一个序列 A i A_i Ai, 第 i i i 个骰子会等概率要到点数 1 ∼ A i 1 ∼ A_i 1Ai, 在同时抛 N N N 个骰子后,选出一些骰子,使得总点数是 10 10 10 的概率是多少,对 998 , 244 , 353 998,244,353 998,244,353 取模。

解决方法:

求概率,很明显的概率dp。

第一步, 应该设什么样的状态

一共 N N N 个骰子,每个骰子扔出后会对是否能抛出 1 ~ 10 1 ~ 10 110 产生影响,所以考虑每抛出一个骰子,能否抛出 1 ~ 10 1 ~ 10 110, 怎么表示呢?
数值很小是 10 10 10, 每个点数只有两种状态:能和不能,那么就可以用状态压缩。
状态即为: f [ i ] [ S ] f[i][S] f[i][S] 表示跑到第 i i i 个骰子的时候,可以抛出 x x x ( x ∈ S ) (x \in S) (xS)

第二步,初值怎么设

如果不抛,那么在 S S S 的第一位为 1 1 1 , 则 f [ 0 ] [ 1 ] = 1 f[0][1] = 1 f[0][1]=1

第三步,转移方程是什么

假设上一步的状态是 k k k, 这一个骰子投出 j j j 时,如果选,那么这一步的状态就是 k < < j k << j k<<j, 否则就是 k k k
所以,转移就是:

  • a i ⩽ 10 : a_i \leqslant 10 : ai10 f [ i ] [ S + 1 < < j ] = f [ i ] [ S ] + f [ i − 1 ] [ S ] ∗ 1 a i f[i][S+1<<j] = f[i][S] + f[i-1][S] * \frac{1}{a_i} f[i][S+1<<j]=f[i][S]+f[i1][S]ai1
  • 否则,在 a i a_i ai 超出 10 10 10 的部分, f [ i ] [ S ] = f [ i ] [ S ] + f [ i − 1 ] [ S ] ∗ a i − 10 a i f[i][S] = f[i][S] + f[i-1][S] * \frac{a_i - 10}{a_i} f[i][S]=f[i][S]+f[i1][S]aiai10
第四步,答案怎么求

答案再简单不过了,只需要求出 ∑ S = 0 ( 1 < < 10 ) − 1 f [ n ] [ S ] ( 10 ∈ S ) \sum_{S = 0}^{(1 << 10) - 1} f[n][S](10 \in S) S=0(1<<10)1f[n][S]10S) 即可。

相信逆元的过程不必我多说了,不懂的请自行百度(不懂得话估计也不会做这道题…)

#include <iostream>
#include <algorithm>
using namespace std;
const int NR = 110;
const int MOD = 998244353;
long long f[NR][(1 << 11) + 2], a[NR];
long long ksm(long long x, long long y)
{
    long long ans = 1;
    while (y)
    {
        if (y & 1) ans = ans * x % MOD;
        y >>= 1;
        x = x * x % MOD;
    }
    return ans;
}
long long inv(long long x)
{
    return ksm(x, MOD - 2);
}
long long max(long long x, long long y)
{
    if (x > y) return x;
    return y;
}
long long min(long long x, long long y)
{
    if (x < y) return x;
    return y;
}
signed main()
{
    int n;
    cin >> n;
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    long long S = (1 << 11) - 1;
    f[0][1] = 1;
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= min(a[i], 10); j++)
            for (int k = 0; k <= S; k++)
            {
                long long now = (((k << j) | k) & S);
                f[i][now] = (f[i][now] + f[i-1][k] * inv(a[i]) % MOD) % MOD;
            }
        for (int k = 0; k <= S; k++)
            f[i][k] = f[i][k] + f[i-1][k] * inv(a[i]) % MOD * max(0, a[i] - 10) % MOD;
    }
    long long ans = 0;
    for (int i = 0; i < (1 << 10); i++)
        ans = (ans + f[n][i | (1 << 10)]) % MOD;
    cout << ans << '\n';
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值