NOIP十連測 塗色遊戲
阿新 • • 發佈:2018-11-06
這是一道玄學組合數和神仙思路。。。
題目大意:給出一個n*m的網格,每個格子裡只能塗一種顏色,一共有p中顏色,要求任意相鄰兩列都出現了
至少q種顏色的方案數。
n≤100,m≤,q≤p≤100。
看這m的範圍,很容易想到矩陣乘法,所以可以先考慮遞推式。
設dp[i][j]表示前i列最後一列共有j種顏色的方案數。
那麼顯然可以得到dp[i][k]=dp[i-1][j]*ans[j][k]。其中ans[j][k]表示的是這層有j種顏色,下一層有k種。
那麼我們知道ans[j][k]怎麼求就可以得到答案了。
因為兩次選擇的顏色會有重複,直接利用組合數尋找規律需要很多容斥,也很困難。
所以我們考慮列舉兩次的並集,設並集為x,那麼這次的方案組成就是在滿足條件的情況下,
和上次相交的顏色的選擇的方案乘上這次的新顏色的選擇的方案。j+k-x表示的就是交集。
最後還要乘上在n個位置塗上k中顏色的方案數。設g[n][k]表示這個方案數。
那麼就是要求在i個位置塗上j個顏色的方案數,可以類比為有j個不同的盒子,需要把i個不同的東西放進去的方案數。
這個問題就是第二類斯特林,結論為,大概的意思就是
可以從放過的盒子裡在放一個,一共j種,也可以在沒放過的新盒子放一個,這個新盒子可以是j中的任何一個,
所以一共j種。
那麼最後得到的關於ans的表示式就是
對於dp的初值就是
用矩陣乘法優化一下即可,最後答案是。(PS:我懶了。。這個矩乘就直接用了)。。
#include<cstdio> #include<cmath> #include<cstring> #include<algorithm> #define mode 998244353 using namespace std; typedef long long ll; int n,m,p,q; int val[105][105]; int used[2][105]; ll sum; ll c[105][105]; ll g[105][105]; void yhsj() { for(int i=0;i<=102;i++) { c[i][0]=c[i][i]=1; for(int j=1;j<i;j++) { c[i][j]=(c[i-1][j]+c[i-1][j-1])%mode; } } } struct no { ll f[105][105]; }tmp,ans; no operator *(no a,no b) { no re; for(int i=1;i<=102;i++) { for(int j=1;j<=102;j++) { re.f[i][j]=0; for(int k=1;k<=102;k++) { re.f[i][j]+=a.f[i][k]*b.f[k][j]%mode; re.f[i][j]%=mode; } } } return re; } int main() { freopen("color.in","r",stdin); freopen("color.out","w",stdout); scanf("%d%d%d%d",&n,&m,&p,&q); yhsj(); g[0][0]=1; for(int i=1;i<=n;i++) { for(int j=1;j<=n;j++) { g[i][j]=j*(g[i-1][j]+g[i-1][j-1])%mode; } } for(int j=1;j<=p;j++)//這層的顏色數 { for(int k=1;k<=p;k++)//下層的顏色數 { for(int x=max(q,max(j,k));x<=min(p,j+k);x++) { tmp.f[j][k]+=c[j][j+k-x]*c[p-j][x-j]%mode; tmp.f[j][k]%=mode; } tmp.f[j][k]*=g[n][k]%mode;tmp.f[j][k]%=mode; } } m--; for(int i=1;i<=p;i++)ans.f[i][i]=1; while(m) { if(m%2==1) ans=ans*tmp; tmp=tmp*tmp;m>>=1; } for(int i=1;i<=p;i++) { for(int j=1;j<=p;j++) { sum=(sum+g[n][i]*ans.f[i][j]%mode*c[p][i]%mode)%mode;//相當於把初值的矩陣直接求了,真實的答案就是∑f[n][1-p]。 } } printf("%I64d\n",sum); return 0; }