1. 程式人生 > >NOIP十連測 塗色遊戲

NOIP十連測 塗色遊戲

這是一道玄學組合數和神仙思路。。。

題目大意:給出一個n*m的網格,每個格子裡只能塗一種顏色,一共有p中顏色,要求任意相鄰兩列都出現了

至少q種顏色的方案數。

n≤100,m≤10^{9},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個不同的東西放進去的方案數。

這個問題就是第二類斯特林,結論為g[i][j]=j*(g[i-1][j-1]+g[i-1][j]),大概的意思就是

可以從放過的盒子裡在放一個,一共j種,也可以在沒放過的新盒子放一個,這個新盒子可以是j中的任何一個,

所以一共j種。

那麼最後得到的關於ans的表示式就是ans[j][k]=g[n][k]*\sum_{x=max(q,j,k)}^{min(p,j+k)}C_{j}^{j+k-x}C_{p-j}^{x-j}

對於dp的初值就是dp[1][j]=g[n][j]*C_{p}^{j}

,就是指從所有顏色中選出j個顏色的方案數乘上放j個顏色的方案數。

用矩陣乘法優化一下即可,最後答案是\sum_{i=1}^{p}f[n][i]。(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;
}