【题解】P5326 [ZJOI2019] 开关
P5326 [ZJOI2019] 开关
题意
有一个 \(n\) 个二进制位的二进制数,初始时每一位都为 \(0\)。
每个单位时间,会随机取反一个二进制位。
对于第 \(i\) 位,它被取反的概率为 \(\displaystyle \frac{a_i}{\sum_{i=1}^n a_i}\)。
求出达到状态 \(S\) 的期望时间。
\(\displaystyle n\le 100\),\(\displaystyle \sum_{i=1}^n a_i \le 5\times 10^4\)。
题解
神题啊。
令 \(\displaystyle \sum_{i=1}^n a_i=m\)。
令 \(p_i=\displaystyle \frac{a_i}{m}\),得到概率。
设 \(f_S\) 为达到状态 \(S\) 的期望时间,初始有 \(f_0=0\)。
有转移,
\(\oplus\) 表示异或操作,那么 \(S\oplus i\) 就表示将 \(S\) 的第 \(i\) 位取反,从集合的方面讲,就是切换 \(i\) 在 \(S\) 中的存在性。
容易发现这个转移具有后效性,但是没关系,这相当于解一个有 \(2^n\) 个未知数的线性方程组,直接上高斯消元可以做到 \(O((2^n)^3)=O(8^n)\) 的时间复杂度,可以获得 \(40\) 分。
但这还远远不够,注意到上式可以转化成一个异或卷积的形式,
其中对于 \(g_B\) 的定义如下:
那多半和集合幂级数脱不了什么干系了。
记全集 \(U=\{1,2,\dots,n\}\),\(2^U\) 表示 \(U\) 的幂集,即 \(U\) 的所有子集组成的集合,无特殊说明,下文提到的所有集合都是 \(2^U\) 的子集,故省略 \(\in 2^U\)。
令 \(\displaystyle f=\sum_{S}f_S x^S\),\(\displaystyle g=\sum_{\{i\}} p_i x^{\{i\}}\),\(\displaystyle I=\sum_{S}x^S\)。
令 \(f_S\) 表示 \(f\) 中 \(x^S\) 前面的系数,而 \(f\cdot g\) 表示 \(f\) 与 \(g\) 的异或卷积,\(\widehat f\) 表示异或 FWT 之后的 \(f\)。
根据异或 FWT 的定义,有,
同样对于逆变换 IFWT 有,
尝试把之前的转移式写成集合幂级数的形式,
感觉没什么问题,但是上式忽略了一个事实,\(f_{\empty}=0\),所以后面还要加上一个 \(rx^{\empty}\),使得最终 \(f_{\empty}=0\),也就是,
尝试将两边都做一遍 FWT,
对于每个 \(S\) 都有,
注意到在变换后 \(f\cdot g\) 变为了 \(\widehat f \times \widehat g\),这里强调的是其变为了点乘的形式。
那么移项得到,
当 \(S=\empty\) 时,为了保证 \(f_{\empty}=0\),上式应仍然保持成立。
此时 \(\displaystyle \widehat g_{\empty}=\sum_{T} (-1)^{|\empty\cap T|} g_T=\sum_{T}g_T=\sum_{i=1}^n p_i=1\),所以等式左边为 \(0\)。
同理可以计算出 \(\widehat I_{\empty}=2^n\),所以 \(r=-2^n\)。
当 \(S\neq \empty\) 时,可以发现一定有 \(\widehat I_S=0\),证明如下,
参照定义有,
等价于,
即,
也就是,
至此得证。
假设 \(S\neq \empty\),则 \(\widehat f_S\) 可以表示为,
发现 \(\widehat g_S\) 还没怎么发掘过,依旧是根据定义得到,
根据前文定义,若 \(|S|\neq 1\),\(g_S=0\),所以有意义的只有 \(|S|=1\) 时的 \(g_S\),考虑换一种表示方式,
即为,
取补集,
代入上面推了一半的式子,
此时用一次逆变换的定义式,
发现 \(T=\empty\) 时 \(\widehat f_T\) 不太好处理,考虑利用 \(f_\empty=0\) 的性质,
移项化简得到,
把它再代回定义式,
观察到 \((-1)^{|S\cap T|}-1\in \{0,-2\}\),考虑改变形式,
此时果断代入前面推出来的 \(\widehat f_T\) 关于 \(p_i\) 的式子,
直接化简,
换成 \(a_i\),
\(\displaystyle n\le 100\),\(\displaystyle \sum_{i=1}^n a_i \le 5\times 10^4\),式子中 \(S\) 就是题目给定的 \(S\),这显然可以让做一个背包 dp 计数。
具体地,设 \(dp_{i,j,k}\) 表示考虑了前 \(i\) 位,选定的 \(a_i\) 总和为 \(j\),\(|S\cap T|\bmod 2=k\),此时的方案数,根据目标状态 \(S\) 这一位是 \(0\) 还是 \(1\) 来分类去转移就行了。
那么答案就是 \(\displaystyle m\sum_{i=1}^m \frac{f_{n,i,1}}{i}\)
时间复杂度 \(O(nm+m\log P)\),\(P\) 是模数。
#include<bits/stdc++.h>
using namespace std;
#define rep(i,l,r) for(int i=(l);i<=(r);++i)
#define per(i,l,r) for(int i=(r);i>=(l);--i)
#define pr pair<int,int>
#define fi first
#define se second
#define pb push_back
#define all(x) (x).begin(),(x).end()
#define sz(x) (int)(x).size()
#define bg(x) (x).begin()
#define ed(x) (x).end()
#define N 105
#define M 50010
#define int long long
const int mod=998244353;
int n,s[N],p[N],m,dp[M][2];
inline void add(int &x,int y){
x=(x+y)%mod;
}
inline int qpow(int a,int b=mod-2){
int ans=1;
while(b){
if(b&1){
ans=ans*a%mod;
}
a=a*a%mod;
b>>=1;
}
return ans;
}
signed main(){
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>n;
rep(i,1,n){
cin>>s[i];
}
rep(i,1,n){
cin>>p[i];
m+=p[i];
}
dp[0][0]=1;
rep(i,1,n){
per(j,p[i],m){
if(!s[i]){
add(dp[j][0],dp[j-p[i]][0]);
add(dp[j][1],dp[j-p[i]][1]);
}
else{
add(dp[j][1],dp[j-p[i]][0]);
add(dp[j][0],dp[j-p[i]][1]);
}
}
}
int ans=0;
rep(i,1,m){
add(ans,dp[i][1]*qpow(i)%mod);
}
cout<<ans*m%mod;
return 0;
}

浙公网安备 33010602011771号