题意
有如下几种拼图(具体形状见题面):
- 十字型(左凸右凸)
- 工字型(左凹右凹)
- 左凹右凸
- 左凸右凹
给你每种拼图的个数,问你将他们凸对凹拼在一起有多少种方案,答案对 998244353 998244353 998244353 取余。
题解
Part.1 前置知识
Part.1-1 隔板法
给你 n n n 个苹果,要求你将这些 相同 的苹果分给 m m m 个 不同 的小朋友,每个小朋友都有至少一个苹果,问你分发苹果的方案数。
我们可以先作图分析一下:
- 首先,我们画出 n n n 个苹果。
- 然后,我们随意在 两个苹果之间 画出 m − 1 m-1 m−1 个隔板。
- 接下来,我们将被隔板分成的 m m m 个区间里的苹果依次给这些小朋友。
可以发现,这时的方案数等于
C
n
−
1
m
−
1
C_{n-1}^{m-1}
Cn−1m−1。
若有的小朋友可以没有苹果,那么可以赠送每个小朋友一个苹果,那么此时的方案数等于 C n + m − 1 m − 1 C_{n+m-1}^{m-1} Cn+m−1m−1。
这时,若选出的苹果个数序列为 { 1 , 1 , 2 } \{1,1,2\} {1,1,2},那么每个小朋友实际分到的个数序列是 { 0 , 0 , 1 } \{0,0,1\} {0,0,1}。
Part.1-2 组合数
上一部分,我们介绍了隔板法。可以发现,我们用到了组合数,这里给出组合数的两种求法。
Part.1-2-1 杨辉三角
杨辉三角给出了组合数的递推式。
C n m = C n − 1 m + C n − 1 m − 1 ( 0 < m < n ) C_{n}^{m}=C_{n-1}^{m}+C_{n-1}^{m-1}~~(0<m<n) Cnm=Cn−1m+Cn−1m−1 (0<m<n)
C n 0 = C n n = 1 C_{n}^{0}=C_{n}^{n}=1 Cn0=Cnn=1
很像动态规划呀!
仔细看,可以发现每个组合数递推下来只有两种情况:选或不选。
C n − 1 m C_{n-1}^{m} Cn−1m 表示不选, C n − 1 m − 1 C_{n-1}^{m-1} Cn−1m−1 表示选。
Part.1-2-2 逆元
可以从组合数的手动求解开始:
C n m = n × ( n − 1 ) × ( n − 2 ) × ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ × ( n − m + 1 ) m × ( m − 1 ) × ( m − 2 ) × ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ × 1 C_{n}^{m}=\frac{n\times(n-1)\times(n-2)\times······\times(n-m+1)}{m\times(m-1)\times(m-2)\times······\times1} Cnm=m×(m−1)×(m−2)×⋅⋅⋅⋅⋅⋅×1n×(n−1)×(n−2)×⋅⋅⋅⋅⋅⋅×(n−m+1)
C n m = n ! m ! × ( n − m ) ! C_{n}^{m}=\frac{n!}{m!\times(n-m)!} Cnm=m!×(n−m)!n!
但是,我们虽然脱离了组合数,但是无法处理除法取余,那就用到逆元了。
逆元所需的知识如下:
Part.1-2-2-1 费马小定理
最终的结论是 n p − 2 n^{p-2} np−2 是 n n n 在模 p p p 意义下的逆元。
Part.1-2-2-2 快速幂
前面介绍的费马小定理需要求解幂次问题,而且幂次极大。暴力搜索无法通过,需要使用快速幂:
a 2 p + 1 = ( a p ) 2 × a a^{2p+1}=(a^{p})^{2}\times a a2p+1=(ap)2×a
a 2 p = ( a p ) 2 a^{2p}=(a^{p})^{2} a2p=(ap)2
代码:
int Pow(int a,int n){
int ans=1;
while (n){
if (n&1){
ans=ans*a%mod;
}a=a*a%mod;
n/=2;
}return ans;
}
Part.2 求解问题
Part.2-1 解的存在性
若十字型和工字型的差超过 1 1 1,那么一定会有两个十(工)字型在一起,他们无法拼接。
注意到,若将左凸右凹插在工字型后面,刚好还是工字型(十字型同理)。
Part.2-2 c 1 = c 2 c_{1}=c_{2} c1=c2
现在,我们终于要用到前缀知识了!
现在共有两种方案:
- 工-十-工-十
- 十-工-十-工
现在要把 3 3 3 号和 4 4 4 号拼图插入其中。
Part.2-2-1 工十工
这时可以发现此时有 c 1 + 1 c_{1}+1 c1+1 个位置可以放置 3 3 3 号, c 1 c_{1} c1 个位置放 4 4 4 号。
方案数为 C c 1 + c 3 c 3 − 1 × C c 1 + c 4 − 1 c 4 − 1 C_{c_{1}+c_{3}}^{c_{3}-1}\times C_{c_{1}+c_{4}-1}^{c_{4}-1} Cc1+c3c3−1×Cc1+c4−1c4−1
Part.2-2-2 十工十
这时的话可以发现此时有 c 1 c_{1} c1 个位置可以放置 3 3 3 号, c 1 + 1 c_{1}+1 c1+1 个位置放 4 4 4 号。
方案数为 C c 1 + c 3 − 1 c 3 − 1 × C c 1 + c 4 c 4 − 1 C_{c_{1}+c_{3}-1}^{c_{3}-1}\times C_{c_{1}+c_{4}}^{c_{4}-1} Cc1+c3−1c3−1×Cc1+c4c4−1
两种方案相加即可。
Part.2-3 剩下的情况
现在, 3 , 4 3,4 3,4 号都有 m a x ( c 1 , c 2 ) max(c_{1},c_{2}) max(c1,c2) 个位置,记为 k k k;
方案数为 C k + c 3 − 1 c 3 − 1 × C k + c 4 − 1 c 4 − 1 C_{k+c_{3}-1}^{c_{3}-1}\times C_{k+c_{4}-1}^{c_{4}-1} Ck+c3−1c3−1×Ck+c4−1c4−1。
代码有些混乱,敬请谅解。
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define mod 998244353
int fac[2000010];
int Pow(int a,int n){
int ans=1;
while (n){
if (n&1){
ans=ans*a%mod;
}a=a*a%mod;
n/=2;
}return ans;
}int C(int a,int b){
return fac[a]*Pow(fac[b],mod-2)%mod*Pow(fac[a-b],mod-2)%mod;
}
signed main(){
fac[0]=1;
for (int i=1; i<=2000000; i++){
fac[i]=fac[i-1]*i%mod;
} int t; cin>>t;
while (t--){
int a,b,c,d;
cin>>a>>b>>c>>d;
if (abs(a-b)>1){
cout<<"0\n";
}else{
int x=(a>b?a:a+1),y=(a>b?b+1:b);
if (a!=b) cout<<C(x+c-1,x-1)*C(y+d-1,y-1)%mod<<"\n";
else{
if (a==0){
if (c==0||d==0){
cout<<"1\n";
}else cout<<"0\n";
}
else cout<<(C(x+c-1,x-1)*C(y+d-1,y-1)%mod+C(y+c-1,y-1)*C(x+d-1,x-1)%mod)%mod<<"\n";
}
}
}
return 0;
}