题意
要点
直接算恰好不是很方便,考虑算至少然后容斥。
先解决容斥系数的问题:
先加上至少有K个极大的方案,再减去至少有K+1个极大的方案 * C(k + 1, k) (因为每一种方案都在前面算了这么多次),再加上…
因此容斥系数应该是
(
−
1
)
x
−
k
⋅
C
(
x
,
k
)
(-1)^{x-k}\cdot C(x,k)
(−1)x−k⋅C(x,k)。证明也很好证,就不放了。
先选出x个可以同时作为极大的点。(其实就相当于每一维选x个数然后再组合)C(n,x)C(m,x)C(l,x)
然后给这x个点安排一下大小顺序。(x!)^3
然后考虑:保证这x个点是极大的方案数怎么计数。
- 方案数有点麻烦,直接考虑概率:
- 考虑a1<a2<…<ax,a1是极大的概率是 1 ∣ S 1 ∣ , s i 是 i 的 邻 域 \frac 1 {|S1|},si是i的邻域 ∣S1∣1,si是i的邻域
- a2是极大的概率是 1 ∣ S 1 ∣ 并 ∣ S 2 ∣ \frac 1 {|S1| 并|S2|} ∣S1∣并∣S2∣1…
- 显然这些概率是独立的。因此直接乘起来就可以了。
- 关于如何去掉逆元的log: 事实上是求前缀逆元,像求阶乘逆元那样做最后一个然后再倒车就可以。
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int mo = 998244353, N = 5e6 + 1000;
ll T,n,m,l,k;
ll jc[N],njc[N],pre[N],ny[N],cj[N];
ll ksm(ll x,ll y) {
x %= mo;
ll ret = 1;
for(; y; y>>=1) {
if (y & 1) ret = ret * x % mo;
x = x * x % mo;
}
return ret;
}
ll C(ll n,ll m) {
return n >= m ? jc[n] * njc[m] % mo * njc[n - m] % mo : 0;
}
int main() {
freopen("cube.in","r",stdin);
// freopen("cube.out","w",stdout);
jc[0] = 1;
for(int i = 1; i <= 5000000; i++) jc[i] = jc[i - 1] * i % mo;
njc[5000000] = ksm(jc[5000000], mo - 2);
for(int i = 5000000 - 1; ~i; i--) njc[i] = njc[i + 1] * (i + 1) % mo;
for(cin>>T;T;T--){
scanf("%lld %lld %lld %lld\n",&n,&m,&l,&k);
ll ans = 0;
ll mi = min(min(n,m),l);
pre[0] = 1;
for(int i = 1; i <= mi; i++)
pre[i] = pre[i - 1] * (cj[i] = (n * m % mo * l % mo - (n - i) * (m - i) % mo * (l - i) % mo) % mo) % mo;
ny[mi] = ksm(pre[mi], mo - 2);
for(int i = mi - 1; i; i--) ny[i] = ny[i + 1] * cj[i + 1] % mo;
for(int i = k; i <= mi; i++) {
ans = (ans + ((i-k)&1?-1:1) * C(n,i) * C(m, i) % mo * C(l, i) % mo * ny[i] % mo * ksm(jc[i],3) % mo * C(i,k) % mo) % mo;
}
cout<<(ans + mo) % mo<<endl;
}
}
- ```