每周至少五篇博客:(2/5)
https://atcoder.jp/contests/abc310/tasks/abc310_f
题意
我们有 NNN 枚骰子。对于每个 i=1,2,…,Ni = 1, 2, \ldots, Ni=1,2,…,N ,当掷出 iii 这枚骰子时,它以相等的概率显示出 111 和 AiA_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 10ai≤10:加上投出点数 jjj ,那么对于之前前 i−1i - 1i−1 个骰子可以组合出的状态 maskmaskmask 。首先如果不选择第 iii 个骰子,那么可以组合出状态 maskmaskmask ,选择的话,对于 maskmaskmask 中第 kkk 位为 111 的 kkk,算上 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)←dpi−1,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}aiai−10 ,转移有 dpi,mask←dpi−1,mask×ai−10aidp_{i, mask}\gets dp_{i - 1, mask} \times \frac {a_i - 10} {a_i}dpi,mask←dpi−1,mask×aiai−10,同时包含上一情况的转移,但上文中的 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;
}