UOJ514 通用测评号 和 CF891E Lust
通用测评号
为了实验通用测评号的实际效果,你被安排给通用测评号装填燃料。通用测评号上有 \(n\) 个燃料舱,初始时均为空。一个燃料舱被填满时可以储藏 \(a\) 单位的燃料。但为了完成本次实验,只需要每个燃料舱装填至少 \(b\) 单位的燃料即可。
装填燃料是个非常无聊的事情,因此你每次会等概率随机选一个没有满的燃料舱,并且在其中放入 \(1\) 单位的燃料,直至所有燃料舱均包含至少 \(b\) 单位的燃料。
但是由于设计上的失误,一个燃料舱被填满 \(a\) 单位的燃料后,该燃料舱与发动机连接的管道由于压力过大会坏掉。章北蚤发现了这个问题,他想估计坏掉的管道数量,所以想请你算一算期望有多少个燃料舱被填满了。
为了避免实数计算带来的浮点误差,你只需要输出答案对 \(998244353\) 取模后的结果。
对于所有测试数据,满足 \(1 \le n \le 250,1 \le b < a \le 250\)。
题解
https://hellomath.blog.luogu.org/uoj-514
首先发现也可以选择满了的燃料舱加燃料,这不会影响答案。
考虑算 \(1\) 号燃料仓被填满时存在燃料仓燃料小于 \(b\) 的概率,最后再将答案乘以 \(n\) 。
显然可以容斥。我们不妨钦定 \(m\) 个燃料仓燃料小于 \(b\) ,那么只需要关注这 \(m + 1\) 个燃料仓。枚举 \(1\) 号燃料仓被填满时其他 \(m\) 个燃料仓燃料有多少,那么就只需要算一个可重排列方案数(注意最后一个被填的燃料仓必须是 \(1\) 号):
枚举 \(\sum x_i = s\) ,那么:
带上\((-1)^m\binom{n-1}{m}\)的容斥系数。
用 NTT 预处理出多项式 \(F(x) = \sum_{i = 0}^{b - 1} \frac{x^i}{i!}\) 的 \(1 \sim (n - 1)\) 次幂即可。
时间复杂度为 \(O(n^2b \log(nb))\)。
CO int N=1<<16;
int omg[2][N],rev[N];
int fac[N],inv[N],ifac[N];
void NTT(poly&a,int dir){
int lim=a.size(),len=log2(lim);
for(int i=0;i<lim;++i) rev[i]=rev[i>>1]>>1|(i&1)<<(len-1);
for(int i=0;i<lim;++i)if(i<rev[i]) swap(a[i],a[rev[i]]);
for(int i=1;i<lim;i<<=1)
for(int j=0;j<lim;j+=i<<1)for(int k=0;k<i;++k){
int t=mul(omg[dir][N/(i<<1)*k],a[j+i+k]);
a[j+i+k]=add(a[j+k],mod-t),a[j+k]=add(a[j+k],t);
}
if(dir){
int ilim=fpow(lim,mod-2);
for(int i=0;i<lim;++i) a[i]=mul(a[i],ilim);
}
}
poly operator*(poly a,poly b){
int n=a.size()+b.size()-1,lim=1<<(int)ceil(log2(n));
a.resize(lim),NTT(a,0);
b.resize(lim),NTT(b,0);
for(int i=0;i<lim;++i) a[i]=mul(a[i],b[i]);
NTT(a,1),a.resize(n);
return a;
}
IN int C(int n,int m){
return mul(fac[n],mul(ifac[m],ifac[n-m]));
}
int main(){
omg[0][0]=1,omg[0][1]=fpow(3,(mod-1)/N);
omg[1][0]=1,omg[1][1]=fpow(omg[0][1],mod-2);
fac[0]=fac[1]=1;
inv[0]=inv[1]=1;
ifac[0]=ifac[1]=1;
for(int i=2;i<N;++i){
omg[0][i]=mul(omg[0][i-1],omg[0][1]);
omg[1][i]=mul(omg[1][i-1],omg[1][1]);
fac[i]=mul(fac[i-1],i);
inv[i]=mul(mod-mod/i,inv[mod%i]);
ifac[i]=mul(ifac[i-1],inv[i]);
}
int n=read<int>(),a=read<int>(),b=read<int>();
int ans=0;
poly f(ifac,ifac+b);
for(int i=1;i<n;++i,f=f*poly(ifac,ifac+b)){
int sum=0;
for(int j=0,pwr=fpow(inv[i+1],a);j<=i*(b-1);++j,pwr=mul(pwr,inv[i+1]))
sum=add(sum,mul(f[j],mul(fac[a-1+j],pwr)));
sum=mul(sum,mul(ifac[a-1],C(n-1,i)));
if(i%2==0) sum=mod-sum;
ans=add(ans,sum);
}
printf("%d\n",mul(ans,n));
return 0;
}
Lust
给定 \(n\) 个数 \(a_1,a_2,\dots ,a_n\) 和一个初值为 \(0\) 的计数器 \(cnt\) ,执行以下操作 \(k\) 次:
在 \(1,2,\dots,n\) 中等概率随机选择一个数 \(i\) ,令 \(cnt\) 加上 \(\prod_{j\neq i}a_j\),然后把 \(a_i\) 减 \(1\) .
求 \(k\) 次操作后计数器 \(cnt\) 的值的期望模 \(10^9+7\) .
\(n\le 5\times 10^3, k\le 10^9\) .
题解
https://jklover.hs-blog.cf/2020/06/09/CF891E-Lust/#more
考虑每次的贡献是 \(\prod_{j\neq i} a_j\) ,然后将 \(a_i\) 减掉 \(1\) ,这可以看成是 \(\prod_j a_j-\prod_j a_j’\) ,即减掉前后所有数乘积之差.
\(k\) 次操作的贡献会抵消成 \(\prod a_i-\prod (a_i-b_i)\), 其中 \(b_i\) 表示 \(a_i\) 这个数被在 \(k\) 次操作中一共被减了多少次.
前面的乘积是确定的,只用算后面那个乘积的期望,可以写出这个问题的 EGF,
所求即为 \(\frac{k!}{n^k}[x^k]G(x)\) , 可以暴力求出 \(\prod_{i=1}^n(a_i-x)\) 每项系数,再考虑每一项与 \(e^{nx}\) 卷积对答案贡献即可.
时间复杂度 \(O(n^2)\) .
CO int N=5e3+10;
int p[N];
int main(){
int n=read<int>(),m=read<int>();
int ans=1;
p[0]=1;
for(int i=1;i<=n;++i){
int a=read<int>();
ans=mul(ans,a);
for(int j=i-1;j>=0;--j)
p[j+1]=add(p[j+1],mod-p[j]),p[j]=mul(p[j],a);
}
int fac=1,inv=fpow(n,mod-2),pwr=1;
for(int i=0;i<=n and i<=m;++i){
ans=add(ans,mod-mul(mul(fac,pwr),p[i]));
fac=mul(fac,m-i);
pwr=mul(pwr,inv);
}
printf("%d\n",ans);
return 0;
}