为美好的XCPC献上典题——ABC310 F - Make 10 Again (状压概率dp)

每周至少五篇博客:(2/5)

https://atcoder.jp/contests/abc310/tasks/abc310_f

题意

我们有 NNN 枚骰子。对于每个 i=1,2,…,Ni = 1, 2, \ldots, Ni=1,2,,N ,当掷出 iii 这枚骰子时,它以相等的概率显示出 111AiA_iAi 之间的随机整数。

求同时投掷 NNN 个骰子时,满足以下条件的概率(模为 998244353998244353998244353 )。

有一种方法可以选择部分(可能是全部) NNN 颗骰子,使它们的结果之和为 101010

思路

首先对于 aia_iai(10,ai](10, a_i](10,ai] 的部分是不可能组合出 101010 的,所以组合出 101010 只需要考虑 [1,min⁡(ai,10)][1, \min(a_i, 10)][1,min(ai,10)] 的部分即可,因为值域很小,所以考虑状压dp

定义 dpi,maskdp_{i, mask}dpi,mask 表示前 iii 个骰子能组合出状态 maskmaskmask 的概率,其中 maskmaskmask 为二进制表示的集合,若第 jjj 位为 111 则表示可以前 iii 个骰子组合出值为 jjj 的数字。由上文得此处的 maskmaskmask 仅考虑能否组合出 [0,10][0, 10][0,10] 直接的数字

一开始一个骰子都没有,那么显然可以组合出 000 这个数,因为值 000 在二进制中的第 000 位,所以有 20=12^0 = 120=1,此时概率为百分百,所以初始化有 dp0,1=1dp_{0, 1} = 1dp0,1=1

对于第 iii 个骰子,考虑转移:

ai≤10a_i \le 10ai10:加上投出点数 jjj ,那么对于之前前 i−1i - 1i1 个骰子可以组合出的状态 maskmaskmask 。首先如果不选择第 iii 个骰子,那么可以组合出状态 maskmaskmask ,选择的话,对于 maskmaskmask 中第 kkk 位为 111kkk,算上 jjj 后可以组合出 k+jk + jk+j ,换到二进制中相当于 k<<jk << jk<<j ,对于状态整体则有 mask<<jmask << jmask<<j 。所以可以组合出状态 mask∪(mask<<j)mask \cup (mask << j)mask(mask<<j) 。因为是等概率显示,所以投出点数 jjj 的概率是 1ai\frac 1 {a_i}ai1,那么转移有 dpi,mask∪(mask<<j)←dpi−1,mask×1aidp_{i, mask \cup (mask << j)} \gets dp_{i - 1, mask} \times \frac 1 {a_i}dpi,mask(mask<<j)dpi1,mask×ai1

ai>10a_i > 10ai>10:包括上文部分还需要考虑 (10,ai](10, a_i](10,ai] 的影响。因为状态中只考虑 0,10{0, 10}0,10 的组合,所以仅考虑对 [0,10][0, 10][0,10] 的影响。对于某一状态 maskmaskmask,投出 (10,ai](10, a_i](10,ai] 的数字后我们显然不可能选择第 iii 个骰子了,所以只会有 maskmaskmask 转移到 maskmaskmask ,这个概率是 ai−10ai\frac {a_i - 10} {a_i}aiai10 ,转移有 dpi,mask←dpi−1,mask×ai−10aidp_{i, mask}\gets dp_{i - 1, mask} \times \frac {a_i - 10} {a_i}dpi,maskdpi1,mask×aiai10,同时包含上一情况的转移,但上文中的 jjj 在此情况固定为常数 101010

对于答案为前 nnn 个骰子能表示出包含 101010 的状态的总概率和,即 ∑dpn,mask×[mask&(1<<10)]\sum dp_{n, mask} \times [mask \& (1 << 10)]dpn,mask×[mask&(1<<10)]

时间复杂度为 O(n2mm)O(n2^{m}m)O(n2mm),这里的 mmm 为我们的考虑影响数字的个数 111111

代码

void solve() {
    int n;
    std::cin >> n;

    std::vector dp(n + 1, std::vector<Z>((1 << 11)));
    dp[0][1] = 1;
    for (int i = 1; i <= n; i ++) {
        Z x;
        std::cin >> x;
        for (int mask = 0; mask < (1 << 11); mask++) {
            for (int j = 1; j <= std::min(x.val(), 10); j ++)
                dp[i][((mask << j) & ((1 << 11) - 1)) | mask] += dp[i - 1][mask] * x.inv();

            if (x.val() > 10)
                dp[i][mask] += dp[i - 1][mask] * Z(x - 10) / x;
        }
    }

    Z ans = 0;
    for (int mask = 1 << 10; mask < (1 << 11); mask ++) ans += dp[n][mask];
    std::cout << ans;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值