zoukankan      html  css  js  c++  java
  • @loj


    @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}),则有转移:

    [egin{cases} f_S &= 1+sum_{i=1}^{n}f_{S m{xor} 2^i} imes q_i\ f_0 &= 0 end{cases} ]

    转移可以写成卷积形式。不过由于 (f_0) 不满足转移,需要加上修正量 (k)。得到卷积式:

    [Fotimes Q + I = F+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)

    然后得到如下式子:

    [egin{aligned} fwt(Q-1)_S&=sum_{i otin S}q_i-sum_{iin S}q_i-1\ fwt(k-I)_S&=-sum_{Tsubseteq S}(-1)^{|T|} imes 2^{n-|S|}+k \ &=-2^{n-|S|} imessum_{i=0}^{|S|}{|S|choose i}(-1)^i+k \ &=k-[S = 0] imes 2^n\ end{aligned} ]

    注意到当 (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)

    [egin{aligned} fwt(F)_S=frac{2^n}{sum_{i otin S}q_i-sum_{iin S}q_i-1} end{aligned} ]

    类比得到 (ifwt) 的本质为 (ifwt(F)_S = frac{1}{2^n}sum_{T}(-1)^{|Scap T|}f_T)。则:

    [egin{aligned} F_S &= frac{1}{2^n}(sum_{T ot =empty}frac{(-1)^{|Scap T|} imes 2^n}{sum_{i otin T}q_i-sum_{iin T}q_i-1} + fwt(F)_0) \ &= sum_{T ot =empty}frac{(-1)^{|Scap T|} imessum p_i}{sum_{i otin T}p_i-sum_{iin T}p_i-sum p_i} + frac{fwt(F)_0}{2^n} \ &= -sum_{T ot =empty}frac{(-1)^{|Scap T|} imessum p_i}{2sum_{iin T}p_i} + frac{fwt(F)_0}{2^n} end{aligned} ]

    如果已知 (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 也算这一类型的题目?

  • 相关阅读:
    盒子的display属性
    html标签默认属性值之margin;padding值
    JS事件
    表单序列化
    2016年最后一天——前端心语
    原生JS--COOKIE
    原生JS--Ajax
    DOM,BOM
    JS面向对象
    RegExp类型,单体内置对象
  • 原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/13086827.html
Copyright © 2011-2022 走看看