@description@
九条可怜是一个贪玩的女孩子。
这天,她和她的好朋友法海哥哥去玩密室逃脱。在他们面前的是 (n) 个开关,开始每个开关都是关闭的状态。要通过这关,必须要让开关达到指定的状态。目标状态由一个长度为 (n) 的 (01) 数组 (s) 给出,(s_i = 0) 表示第 (i) 个开关在最后需要是关着的,(s_i = 1) 表示第 (i) 个开关在最后需要被打开。
然而作为闯关者,可怜和法海并不知道 (s)。因此他们决定采用一个比较稳妥的方法:瞎按。他们根据开关的外形、位置,通过一些玄学的方法给每一个开关赋予了一个值 (p_i(p_i>0))。每一次,他们会以正比于 (p_i) 的概率(第 (i) 个开关被选中的概率是 (frac{p_i}{sum_{j=1}^{n} p_j}))选择并按下一个开关。开关被按下后,状态会被反转,即开变关,关变开。注意,每一轮的选择都是完全独立的。
在按开关的过程中,一旦当前开关的状态达到了 (s),那么可怜和法海面前的门就会打开,他们会马上停止按开关的过程并前往下一关。作为一名欧皇,可怜在按了 (sum_{i=1}^{n}s_i) 次后,就打开了大门。为了感受一下自己的运气是多么的好,可怜想要让你帮她计算一下,用这种随机按的方式,期望需要按多少次开关才能通过这一关。
@solution@
本题和 CTS2019 - 珍珠 类似,既可以用生成函数解,也可以用 fwt 解。
生成函数解法详见 粉兔大佬的blog(orz)。
我们考虑从期望 dp 的角度分析。记 (f_S) 表示当前开关状态为 (S),到达终态的期望步数。
不妨记 (q_i=frac{p_i}{sum p_i}),则有转移:
转移可以写成卷积形式。不过由于 (f_0) 不满足转移,需要加上修正量 (k)。得到卷积式:
其中 (I) 表示全 1 的序列。
上式可以进一步化成 (Fotimes (Q - 1) = k-I)。对其进行 fwt 变换,得到 (fwt(F) imes fwt(Q-1)=fwt(k-I))。
我们需要知道 fwt 变换的本质在干什么:(fwt(F)_S = sum_{T}(-1)^{|Scap T|}f_T)。
然后得到如下式子:
注意到当 (S = empty) 时 (fwt(Q-1)_0 = 0),那么 (fwt(k-I)_0 = k-2^n=0)。
也就是说可以解得 (k = 2^n)。
接下来求 (fwt(F)_S =frac{fwt(k-I)}{fwt(Q-1)}),注意此时有 (S ot = empty):
类比得到 (ifwt) 的本质为 (ifwt(F)_S = frac{1}{2^n}sum_{T}(-1)^{|Scap T|}f_T)。则:
如果已知 (frac{fwt(F)_0}{2^n}),以 (sum_{iin T}p_i) 为状态作背包 dp 就可以 (O(nsum p)) 算出 (F) 的某一项了。
注意到还有条件 (F_0 = 0),可以通过这个条件解出 (frac{fwt(F)_0}{2^n})。
@accepted code@
#include <cstdio>
#include <algorithm>
using namespace std;
const int MOD = 998244353;
const int MAX = 50000;
inline int add(int x, int y) {x += y; return x >= MOD ? x - MOD : x;}
inline int sub(int x, int y) {x -= y; return x < 0 ? x + MOD : x;}
inline int mul(int x, int y) {return (int)(1LL * x * y % MOD);}
int pow_mod(int b, int p) {
int ret = 1;
for(int i=p;i&1;i>>=1,b=mul(b,b))
if( i & 1 ) ret = mul(ret, b);
return ret;
}
int inv[MAX + 5];
void init() {
inv[1] = 1;
for(int i=2;i<=MAX;i++)
inv[i] = sub(0, mul(MOD / i, inv[MOD % i]));
}
int f[2][MAX + 5], a[105], p[105], sump, n;
int solve() {
for(int i=0;i<=MAX;i++)
f[0][i] = f[1][i] = 0;
f[1][0] = 1;
for(int i=1;i<=n;i++) {
for(int j=MAX;j>=p[i];j--)
for(int o=0;o<=1;o++)
f[o][j] = add(f[o][j], f[o^a[i]][j - p[i]]);
}
int ans = 0;
for(int i=1;i<=MAX;i++) {
ans = add(ans, mul(f[0][i], mul(inv[2], inv[i])));
ans = sub(ans, mul(f[1][i], mul(inv[2], inv[i])));
}
return mul(ans, sump);
}
int s[105];
int main() {
init(), scanf("%d", &n);
for(int i=1;i<=n;i++) scanf("%d", &s[i]);
for(int i=1;i<=n;i++) scanf("%d", &p[i]), sump = add(sump, p[i]);
int A = solve();
for(int i=1;i<=n;i++) a[i] = s[i];
int B = solve();
printf("%d
", sub(B, A));
}
@details@
感觉这个推导方法比用EGF快不少。
类似的题。。。可能 agc034f 也算这一类型的题目?