LibreOJ #3119.「CTS2019 | CTSC2019」随机立方体 容斥原理

题意

现在要给一个 n ∗ m ∗ l n*m*l nml的矩形的每一位等概率地填上 1 1 1 n ∗ m ∗ l n*m*l nml中的一个数,且满足每个数恰好出现一次。定义一个位置为极大值当且仅当所有坐标至少有一维和他相同的位置的数都比他小。问恰好有 k k k个极大值的概率。
n , m , l ≤ 5000000 , k ≤ 100 n,m,l\le5000000,k\le100 n,m,l5000000,k100

分析

考虑容斥,那么就要对每个 k ≤ i ≤ m i n ( n , m , l ) k\le i\le min(n,m,l) kimin(n,m,l)算出至少有 i i i个极大值的答案。
假设当前我们已经钦定了这 i i i个极大值的位置,显然他们每一维的坐标互不相同。
现在只用考虑至少有一维坐标和这 i i i个位置中的某一个相同的那些位置,显然合法的位置有 n m l − ( n − i ) ( m − i ) ( l − i ) nml-(n-i)(m-i)(l-i) nml(ni)(mi)(li)个。
如果我们把这 i i i个数按照从大到小的顺序填入,那么最大的数必然是这些位置中的最大值,概率为 1 n m l − ( n − i ) ( m − i ) ( l − i ) \frac{1}{nml-(n-i)(m-i)(l-i)} nml(ni)(mi)(li)1
填好最大的数后,和其他 i − 1 i-1 i1个格子没有坐标相同的那 ( n − i + 1 ) ( m − i + 1 ) ( l − i + 1 ) − ( n − i ) ( m − i ) ( l − i ) (n-i+1)(m-i+1)(l-i+1)-(n-i)(m-i)(l-i) (ni+1)(mi+1)(li+1)(ni)(mi)(li)个格子无论怎么填都必然合法。
同理,第二大的数必然是剩下的 n m l − ( n − i + 1 ) ( m − i + 1 ) ( l − i + 1 ) nml-(n-i+1)(m-i+1)(l-i+1) nml(ni+1)(mi+1)(li+1)个位置中的最大值,概率为 1 n m l − ( n − i + 1 ) ( m − i + 1 ) ( l − i + 1 ) \frac{1}{nml-(n-i+1)(m-i+1)(l-i+1)} nml(ni+1)(mi+1)(li+1)1
所以答案就是 ∑ i = k m i n ( n , m , l ) ( − 1 ) i − k C i k A n i A m i A l i ∏ j = 1 i 1 n m l − ( n − j ) ( m − j ) ( l − j ) \sum_{i=k}^{min(n,m,l)}(-1)^{i-k}C_i^kA_n^iA_m^iA_l^i\prod_{j=1}^i\frac{1}{nml-(n-j)(m-j)(l-j)} i=kmin(n,m,l)(1)ikCikAniAmiAlij=1inml(nj)(mj)(lj)1
时间复杂度 O ( n ) O(n) O(n)

代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>

typedef long long LL;

const int N=5000005;
const int MOD=998244353;

int n,m,l,k,jc[N],ny[N],pre[N];

int ksm(int x,int y)
{
	int ans=1;
	while (y)
	{
		if (y&1) ans=(LL)ans*x%MOD;
		x=(LL)x*x%MOD;y>>=1;
	}
	return ans;
}

int C(int n,int m)
{
	return (LL)jc[n]*ny[m]%MOD*ny[n-m]%MOD;
}

int A(int n,int m)
{
	return (LL)jc[n]*ny[n-m]%MOD;
}

void solve()
{
	jc[0]=jc[1]=ny[0]=ny[1]=1;
	for (int i=2;i<=l;i++) jc[i]=(LL)jc[i-1]*i%MOD,ny[i]=(LL)(MOD-MOD/i)*ny[MOD%i]%MOD;
	for (int i=2;i<=l;i++) ny[i]=(LL)ny[i-1]*ny[i]%MOD;
	pre[n]=1;
	for (int i=1;i<=n;i++) pre[n]=(LL)((LL)n*m%MOD*l%MOD+MOD-(LL)(n-i)*(m-i)%MOD*(l-i)%MOD)*pre[n]%MOD;
	pre[n]=ksm(pre[n],MOD-2)%MOD;
	for (int i=n;i>1;i--) pre[i-1]=(LL)((LL)n*m%MOD*l%MOD+MOD-(LL)(n-i)*(m-i)%MOD*(l-i)%MOD)*pre[i]%MOD;
	int ans=0;
	for (int i=k;i<=n;i++)
	{
		int w=(LL)C(i,k)*A(n,i)%MOD*A(m,i)%MOD*A(l,i)%MOD*pre[i]%MOD;
		if ((i-k)&1) (ans+=MOD-w)%=MOD;
		else (ans+=w)%=MOD;
	}
	printf("%d\n",ans);
}

int main()
{
	int T;scanf("%d",&T);
	while (T--)
	{
		scanf("%d%d%d%d",&n,&m,&l,&k);
		if (n>m) std::swap(n,m);
		if (n>l) std::swap(n,l);
		if (m>l) std::swap(m,l);
		solve();
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值