题目大意
给定一个
n×m
的网格。你要给网格涂色,总共有
p
种颜色选择。要求满足任意相邻两列,都总共出现了至少
计算方案数,答案对
998244353
取模。
n≤100,m≤109,q≤p≤100
题目分析
不要被神秘的模数吓到了。
条件只是限制了相邻两列,因此考虑单独考虑两列的合法方案数。
可以发现,如果在某一列之前的都是合法方案,我们在计算这一列时只不需要关注每个位置具体涂了什么颜色,只需要关心前一列填了多少种颜色,后一列填了多少种颜色,又有多少种颜色是两列都出现的。
设
fi,j
表示设当前考虑到第
i
列,当前列出现了
令
gi,j
表示前一列出现了
i
种不同的颜色,后一列出现了
那么我们有
fi,j=∑k=1nfi−1,kgk,j
怎么求这个 gi,j 呢?枚举 k 表示两列共同出现的颜色个数,那么我们就有
其中 S(n,j) 是第二类 Stirling 数,表示将 n 个不同元素划分入
这样的计算是 O(n3+nm) 的。
怎么优化呢?显然可以使用矩阵乘法优化 f 的计算,时间复杂度变为
代码实现
#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
const int P=998244353;
const int N=105;
struct matrix
{
int num[N][N];
int r,c;
}f,one,zero;
matrix operator*(matrix a,matrix b)
{
matrix ret;
memset(ret.num,0,sizeof ret.num);
ret.r=a.r,ret.c=b.c;
for (int i=0;i<ret.r;i++)
for (int j=0;j<ret.c;j++)
for (int k=0;k<a.c;k++)
(ret.num[i][j]+=1ll*a.num[i][k]*b.num[k][j]%P)%=P;
return ret;
}
matrix operator^(matrix x,int y)
{
matrix ret=zero;
for (;y;y>>=1,x=x*x) if (y&1) ret=ret*x;
return ret;
}
int C[N][N],h[N][N],fact[N];
int n,m,p,q,ans;
void calc()
{
fact[0]=1;
for (int i=1;i<=p;i++) fact[i]=1ll*fact[i-1]*i%P;
C[0][0]=1;
for (int i=1;i<=p;i++)
{
C[i][0]=1;
for (int j=1;j<=i;j++) C[i][j]=(C[i-1][j-1]+C[i-1][j])%P;
}
h[0][0]=1;
for (int i=1;i<=n;i++)
for (int j=1;j<=i;j++)
h[i][j]=(1ll*h[i-1][j]*j%P+h[i-1][j-1])%P;
one.r=one.c=n;
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++)
for (int k=max(i+j-p,0);k<=i&&k<=j&&k<=i+j-q;k++)
(one.num[i-1][j-1]+=1ll*C[i][k]*C[p-i][j-k]%P*h[n][j]%P*fact[j]%P)%=P;
zero.r=zero.c=n;
for (int i=0;i<n;i++)
zero.num[i][i]=1;
f.r=1,f.c=n;
for (int i=1;i<=n&&i<=p;i++) f.num[0][i-1]=1ll*C[p][i]*h[n][i]%P*fact[i]%P;
f=f*(one^(m-1));
ans=0;
for (int i=1;i<=n;i++) (ans+=f.num[0][i-1])%=P;
}
int main()
{
freopen("color.in","r",stdin),freopen("color.out","w",stdout);
scanf("%d%d%d%d",&n,&m,&p,&q);
calc();
printf("%d\n",ans);
fclose(stdin),fclose(stdout);
return 0;
}