[AGC038E] Gachapon

VI.[AGC038E] Gachapon

因为模型同III.重返现世长得很像,所以我们也来考虑minmax容斥。

首先,我们仍然翻出式子

max(S)=TS(1)|T|+1min(T)

然后,我们考虑计算 E(minT)

我们发现,对于某个 T,它的 min 的实际意义是其中一个元素率先全部被生成。而当该元素被生成完时,其它 T 中元素生成的数量都不能达到 bi。因此这启发我们要枚举结束时 T 中每个元素被生成的数量。设此数组为 {c}

我们发现,由 T 来推出所有合法的 {c} 是困难的,但是由 {c} 来反推 T 则是简单的——因为在处理 {c} 的时候肯定顺手就把 T 的位置给记录了。于是我们考虑给定了一个 {c},计算其作为其对应的 T 的终止状态的概率。

首先,我们考虑令 a 表示那个率先被全部生成的数的 a。显然,c=b,但是因为钦定了最后一个生成的数是 a,因此我们令 c 强制减一,这样现在所有 c 中元素间顺序便可以任意调换。

考虑恰好生成 c 中所有元素的概率,是

ac(a)c

但是,因为元素间有序,所以还要乘上多项式系数

(c)!c!

同时,别忘记最后一个生成的数必须是 a,因此还有

aa

考虑生成此种情形期望需要生成多少个数:因为期望 Sa(依照题面,有定义 S=i=0n1ai)次会摇到一个来自 T 中的元素,所以期望 (c+1)×Sa 次就能摇全。

所有东西怼一块,得到

(c+1)×Sa×ac(a)c×(c)!c!×aa

稍微整理一下,把与单个的 aa 有关的拆开来放

S×(c+1)×(c)!(a)c+2×ac×ac!

最开头的是常数,中间一个分数是与 c,a 有关的东西,最后一坨是与单个的 ac 有关的东西。

于是我们考虑DP。设 fi,j,k,odd,0/1 表示当前DP到位置 ic=j,a=k|T| 的奇偶性为 odd,且 a 未/已被决定。

转移分两种:确定 a 的转移和不确定 a 的转移。确定 a 的转移就强制你有 c=b1(注意到我们之前已经令 c 强制减一了),不确定 a 的转移,可以枚举 c[0,b)

乍一看,i,j,k 三维都是 400,剩下两维都是常数,但还要枚举一个 c,这不是 4004 的吗?

一开始我也苦恼了很久,但后来想到 i 维和 c 维加在一块等于 b=400,因此实际是 4003 的。

注意将一些共同的东西提前算出可以减少常数。

代码:

#include<bits/stdc++.h> 
using namespace std;
const int mod=998244353;
int ksm(int x,int y=mod-2){int z=1;for(;y;y>>=1,x=1ll*x*x%mod)if(y&1)z=1ll*z*x%mod;return z;}
int n,a[410],b[410],A,B,f[410][410][2][2],fac[410],inv[410],res;
void ADD(int &x,int y){x+=y;if(x>=mod)x-=mod;}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d%d",&a[i],&b[i]),A+=a[i],B+=b[i];
	fac[0]=1;for(int i=1;i<=B;i++)fac[i]=1ll*fac[i-1]*i%mod;
	inv[B]=ksm(fac[B]);for(int i=B;i;i--)inv[i-1]=1ll*inv[i]*i%mod;
//	for(int i=0;i<=B;i++)printf("%d ",fac[i]);puts("");
//	for(int i=0;i<=B;i++)printf("%d ",inv[i]);puts("");
	f[0][0][0][0]=1;
	for(int i=1;i<=n;i++){
		int tmp=1ll*ksm(a[i],b[i]-1)*inv[b[i]-1]%mod*a[i]%mod;
		for(int j=B;j>=0;j--)for(int k=A;k>=0;k--)for(int odd=0;odd<2;odd++){
	//		if(f[j][k][odd][0])printf("(%d,%d,%d,%d,%d):%d\n",i-1,j,k,odd,0,f[j][k][odd][0]);
	//		if(f[j][k][odd][1])printf("(%d,%d,%d,%d,%d):%d\n",i-1,j,k,odd,1,f[j][k][odd][1]);
			if(f[j][k][odd][0]){
	//			printf("%d %d %d %d\n",f[j][k][odd][0],ksm(a[i],b[i]-1),inv[b[i]-1],a[i]);
				ADD(f[j+b[i]-1][k+a[i]][odd^1][1],1ll*f[j][k][odd][0]*tmp%mod);
				for(int l=0,m=1;l<b[i];l++,m=1ll*m*a[i]%mod)ADD(f[j+l][k+a[i]][odd^1][0],1ll*f[j][k][odd][0]*m%mod*inv[l]%mod);
			}
			if(f[j][k][odd][1])for(int l=0,m=1;l<b[i];l++,m=1ll*m*a[i]%mod)ADD(f[j+l][k+a[i]][odd^1][1],1ll*f[j][k][odd][1]*m%mod*inv[l]%mod);
		}	
	}
	for(int j=0;j<=B;j++)for(int k=1;k<=A;k++)for(int odd=0;odd<2;odd++){
		int tmp=1ll*f[j][k][odd][1]*(j+1)%mod*A%mod*ksm(ksm(k,j+2))%mod*fac[j]%mod;
//		printf("%d,%d,%d:%d\n",j,k,odd,tmp);
//		printf("%d %d %d %d %d\n",f[j][k][odd][1],(j+1),A,ksm(ksm(k,j+2)),fac[j]);
		if(odd&1)(res+=tmp)%=mod;else (res+=mod-tmp)%=mod;
	}
	printf("%d\n",res);
	return 0;
}
posted @   Troverld  阅读(181)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?
点击右上角即可分享
微信分享提示