LYDSY模拟赛day3 涂色游戏
/* 非常好的题 */ #include <cstdio> #include <iostream> #include <cstdlib> #include <cstring> #include <algorithm> #include <cmath> #include <queue> using namespace std; typedef long long ll; const int N=105,mod=998244353; int c[N][N],s[N][N],f[N][N]; struct mat{ int a[N][N]; }ans,tmp; int p; mat operator*(mat a,mat b){ int i,j,k;mat c; for(i=1;i<=p;i++) for(j=1;j<=p;j++){ c.a[i][j]=0; for(k=1;k<=p;k++) c.a[i][j]=(c.a[i][j]+(ll)a.a[i][k]*b.a[k][j])%mod; } return c; } int ksm(int x,int y){ int tmp=x,ans=1; while(y){ if(y&1) ans=(ll)ans*tmp%mod; tmp=(ll)tmp*tmp%mod; y>>=1; } return ans; } int main(){ //freopen("color.in","r",stdin); //freopen("color.out","w",stdout); int n,m,q,i,j,k,x,sum=0; scanf("%d %d %d %d",&n,&m,&p,&q); c[0][0]=1;//组合数 for(i=1;i<=p;i++){ c[i][0]=c[i][i]=1; for(j=1;j<i;j++) c[i][j]=(c[i-1][j-1]+c[i-1][j])%mod;//求组合数的过程 } f[0][0]=1;//i个格子恰好涂j种颜色的方案数 for(i=1;i<=n;i++) for(j=1;j<=p;j++) f[i][j]=((ll)f[i-1][j-1]*(p-j+1)+(ll)f[i-1][j]*j)%mod;//分两类讨论,前i-1个格子中使用j-1种颜色而这一格新增一种颜色,前i-1个格子使用j种颜色,这一格涂的颜色在这j种颜色之中 /* tmp就是题解中所提到的转移函数,a[i][j]就是前一列涂了i个颜色而下一列涂了j个颜色的方案 我们之前已经处理出了i个格子j种颜色的方案数,注意这个方案数是不限制颜色的,也就是说这个j种颜色具体是什么不确定,要想把他确定下来,必须要除c(p,j)(排列组合原理) 考虑两行之间的转移,相邻两个格子分别用了j,k种颜色,两种颜色根据并的颜色的不同会产生不同的结果,枚举这个并的颜色的数量,下界在必须颜色数和两个集合颜色的种数中选取最大值,上界在两集合颜色总数和总共可使用的颜色中取最小值 并的颜色和不同的颜色分别用排列组合算数量再根据乘法原理合起来,最后乘以前一行确定颜色的方案数(确定颜色后方案之间是等价的) */ for(j=1;j<=p;j++) for(k=1;k<=p;k++){ for(x=max(q,max(j,k));x<=min(j+k,p);x++) tmp.a[j][k]=(tmp.a[j][k]+(ll)c[j][j+k-x]*c[p-j][x-j])%mod; tmp.a[j][k]=(ll)f[n][k]*tmp.a[j][k]%mod*ksm(c[p][k],mod-2)%mod; //printf("%d ",tmp.a[j][k]); } m--; for(i=1;i<=p;i++) ans.a[i][i]=1;//单位矩阵 //假设转移两遍,a[i][j] = sum(a[i][k]*a[k][j])(1<=i,j<=p)符合矩阵乘法的形式,所以可以用矩阵快速幂 while(m){ if(m&1) ans=ans*tmp; tmp=tmp*tmp;m>>=1; } for(i=1;i<=p;i++) for(j=1;j<=p;j++) sum=(sum+(ll)f[n][i]*ans.a[i][j])%mod;//转移 printf("%d\n",sum); return 0; }