1. 程式人生 > 實用技巧 >UOJ514 通用測評號 和 CF891E Lust

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_{0 \leqslant x_1, x_2, \cdots, x_m < b} \bigg( \frac{1}{m + 1} \bigg)^{a + \sum x_i} \frac{(a - 1 + \sum x_i)!}{(a - 1)! \prod x_i!} \]

列舉 \(\sum x_i = s\) ,那麼:

\[\sum \frac{1}{\prod x_i} = [x^s] \left( \sum_{i = 0}^{b - 1} \frac{x^i}{i!} \right)^m \]

帶上\((-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,

\[F_a(x)=\sum \frac{a-i}{i!}x^i=e^x(a-x) \]

\[G(x)=\prod_{i=1}^n F_{a_i}(x)=e^{nx}\prod_{i=1}^n(a_i-x) \]

所求即為 \(\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;
}