noip 模擬賽2018.10.28 T2 color
阿新 • • 發佈:2018-11-01
好玄學的組合數遞推啊...
設狀態dp[i][j]表示以當前更新到了第i列,第i列使用了j種顏色的合法的方案數
那麼,由於題目要求考慮相鄰兩列的問題,所以我們還需列舉上一列的顏色種類以及兩列顏色種類總數,那麼可以進行轉移:
其中g[n][k]表示一列中用k中顏色來塗的不同的方案數,有遞推:
稍微證明一下組合數遞推式:
首先,我們列舉的x是相鄰這兩列的顏色種類之和,也就是種類之並集
而根據容斥原理:,可以看到第一個組合數計算的是從上一列的k種顏色中選出j+k-x種作為兩列共有顏色的方案數
那麼第二個組合數計算的就是下一列中與上一列不相同的顏色的取法
最後乘上g,表示這一列用了j種顏色的排布方案數
初值dp[1][j]=g[n][j]*C[p][j]即可
本題資料範圍較大,用矩陣乘法優化
#include <cstdio> #include <cmath> #include <cstring> #include <cstdlib> #include <iostream> #include <algorithm> #include <queue> #include <stack> #define ll unsigned long long #define mode 998244353 using namespace std; struct MAT { ll a[105][105]; }zero,ori; int n,m,p,q; ll g[105][105]; ll C[105][105]; MAT mul(MAT &x,MAT &y) { MAT ret=zero; for(int i=1;i<=p;i++) { for(int j=1;j<=p;j++) { for(int k=1;k<=p;k++) { ret.a[i][j]+=x.a[i][k]*y.a[k][j]%mode; ret.a[i][j]%=mode; } } } return ret; } MAT pow_mul(MAT x,int y) { MAT ret=zero; for(int i=1;i<=p;i++) { ret.a[i][i]=1; } while(y) { if(y&1) { ret=mul(ret,x); } x=mul(x,x); y/=2; } return ret; } void init() { C[0][0]=1; for(int i=1;i<=100;i++) { C[i][0]=1; for(int j=1;j<=i;j++) { C[i][j]=(C[i-1][j]+C[i-1][j-1])%mode; } } } int main() { freopen("color.in","r",stdin); freopen("color.out","w",stdout); scanf("%d%d%d%d",&n,&m,&p,&q); g[1][1]=1; init(); for(int i=2;i<=n;i++) { for(int j=1;j<=min(i,p);j++) { g[i][j]=j*((g[i-1][j]+g[i-1][j-1])%mode)%mode; } } for(int i=1;i<=p;i++)//這一列 { for(int j=1;j<=p;j++)//上一列 { for(int k=max(max(i,j),q);k<=min(p,i+j);k++)//並 { ori.a[i][j]+=g[n][i]*C[j][i+j-k]%mode*C[p-j][k-j]%mode; ori.a[i][j]%=mode; } } } MAT ans=pow_mul(ori,m-1); ll re=0; for(int i=1;i<=p;i++) { for(int j=1;j<=p;j++) { re+=ans.a[i][j]*g[n][j]%mode*C[p][j]%mode; re%=mode; } } printf("%I64d\n",re); return 0; }