NOIP十连测 涂色游戏

版权声明:版权声明:本文为博主原创文章,未经博主允许不得转载,欢迎添加友链。 https://blog.csdn.net/zzk_233/article/details/83510745

这是一道玄学组合数和神仙思路。。。

题目大意:给出一个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;
}

猜你喜欢

转载自blog.csdn.net/zzk_233/article/details/83510745