[JZOJ4870]涂色游戏

题目大意

给定一个 n×m 的网格。你要给网格涂色,总共有 p 种颜色选择。要求满足任意相邻两列,都总共出现了至少q种颜色。
计算方案数,答案对 998244353 取模。

n100,m109,qp100


题目分析

不要被神秘的模数吓到了。
条件只是限制了相邻两列,因此考虑单独考虑两列的合法方案数。
可以发现,如果在某一列之前的都是合法方案,我们在计算这一列时只不需要关注每个位置具体涂了什么颜色,只需要关心前一列填了多少种颜色,后一列填了多少种颜色,又有多少种颜色是两列都出现的。
fi,j 表示设当前考虑到第 i 列,当前列出现了j种不同的颜色的方案数,且满足限制条件的方案数。
gi,j 表示前一列出现了 i 种不同的颜色,后一列出现了j种不同的颜色,这两列满足限制条件的方案数。
那么我们有

fi,j=k=1nfi1,kgk,j

怎么求这个 gi,j 呢?枚举 k 表示两列共同出现的颜色个数,那么我们就有
gi,j=k=max(i+jp,0)min(i,j,q)(ik)(pijk)S(n,j)j!

其中 S(n,j) 是第二类 Stirling 数,表示将 n 个不同元素划分入j集合的方案数。递推式 S(n,m)=S(n1,m1)+S(n1,m)m
这样的计算是 O(n3+nm) 的。
怎么优化呢?显然可以使用矩阵乘法优化 f 的计算,时间复杂度变为O(n3+n3log2m)


代码实现

#include <iostream>
#include <cstring>
#include <cstdio>

using namespace std;

const int P=998244353;
const int N=105;

struct matrix
{
    int num[N][N];
    int r,c;
}f,one,zero;

matrix operator*(matrix a,matrix b)
{
    matrix ret;
    memset(ret.num,0,sizeof ret.num);
    ret.r=a.r,ret.c=b.c;
    for (int i=0;i<ret.r;i++)
        for (int j=0;j<ret.c;j++)
            for (int k=0;k<a.c;k++)
                (ret.num[i][j]+=1ll*a.num[i][k]*b.num[k][j]%P)%=P;
    return ret;
}

matrix operator^(matrix x,int y)
{
    matrix ret=zero;
    for (;y;y>>=1,x=x*x) if (y&1) ret=ret*x;
    return ret;
}

int C[N][N],h[N][N],fact[N];
int n,m,p,q,ans;

void calc()
{
    fact[0]=1;
    for (int i=1;i<=p;i++) fact[i]=1ll*fact[i-1]*i%P;
    C[0][0]=1;
    for (int i=1;i<=p;i++)
    {
        C[i][0]=1;
        for (int j=1;j<=i;j++) C[i][j]=(C[i-1][j-1]+C[i-1][j])%P;
    }
    h[0][0]=1;
    for (int i=1;i<=n;i++)
        for (int j=1;j<=i;j++)
            h[i][j]=(1ll*h[i-1][j]*j%P+h[i-1][j-1])%P;
    one.r=one.c=n;
    for (int i=1;i<=n;i++)
        for (int j=1;j<=n;j++)
            for (int k=max(i+j-p,0);k<=i&&k<=j&&k<=i+j-q;k++)
                (one.num[i-1][j-1]+=1ll*C[i][k]*C[p-i][j-k]%P*h[n][j]%P*fact[j]%P)%=P;
    zero.r=zero.c=n;
    for (int i=0;i<n;i++)
        zero.num[i][i]=1;
    f.r=1,f.c=n;
    for (int i=1;i<=n&&i<=p;i++) f.num[0][i-1]=1ll*C[p][i]*h[n][i]%P*fact[i]%P;
    f=f*(one^(m-1));
    ans=0;
    for (int i=1;i<=n;i++) (ans+=f.num[0][i-1])%=P;
}

int main()
{
    freopen("color.in","r",stdin),freopen("color.out","w",stdout);
    scanf("%d%d%d%d",&n,&m,&p,&q);
    calc();
    printf("%d\n",ans);
    fclose(stdin),fclose(stdout);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值