noip 模拟赛2018.10.28 T2 color

版权声明:本博客中有原创标签的内容均为博主原创,转载请注明出处! https://blog.csdn.net/lleozhang/article/details/83507787

好玄学的组合数递推啊...

设状态dp[i][j]表示以当前更新到了第i列,第i列使用了j种颜色的合法的方案数

那么,由于题目要求考虑相邻两列的问题,所以我们还需枚举上一列的颜色种类以及两列颜色种类总数,那么可以进行转移:

\large dp[i][j]=\sum_{k=1}^{p}\sum_{x=max(q,k,j)}^{min(p,k+j)}C_{k}^{j+k-x}C_{x-k}^{j-k}dp[i-1][k]*g[n][j]

其中g[n][k]表示一列中用k中颜色来涂的不同的方案数,有递推:

\large g[i][j]=j(g[i-1][j-1]+g[i-1][j])

稍微证明一下组合数递推式:

首先,我们枚举的x是相邻这两列的颜色种类之和,也就是种类之并集

而根据容斥原理:\large card(A\bigcap B)=card(A)+card(B)-card(A\bigcup B),可以看到第一个组合数计算的是从上一列的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;
}

猜你喜欢

转载自blog.csdn.net/lleozhang/article/details/83507787