JZOJ4870. 【NOIP2016提高A组集训第9场11.7】涂色游戏

107 篇文章 1 订阅
67 篇文章 1 订阅

Description

这里写图片描述

Data Constraint

这里写图片描述

Solution

我们设f[i][j]表示当前有i个格子恰好放了j种颜色的方案数,那么 f[i][j]=f[i1][j1](p(j1))+f[i1][j]j 。我们设g[j]表示n个格子恰好放了j种颜色的方案数,那么g[j]=f[n][j]。对于假如上一列放了j种颜色,这一列放k种颜色,两列颜色的并集为x,那么造成的方案数为 f1[j][k]=g[j]Cjpmin(j+k,p)x=max(q,j,k)Cj+kxjCxjpj

这里写图片描述

解释一下:我们刚开始讲的g[j]包含的是p中任意j种颜色以任意顺序放入n个格子的方案数,那么对于p中任意j种颜色的方案数都是相等的,即 g[j]Cjp 。明显对于j,k,它们的并集x最大是j+k,但由于最多只有p种颜色,所以x的上限为 minj+k,p ,同时,x必须必j和k都大,由于题目要求交集必须大于q,所以x的下限为 maxj,k,q 。现在要求出k的颜色的组成,为了不重复不遗漏,我们对是否在j和k的交集的颜色分别求 Cj+kxj 表示j和k的交集有多少种情况(即在j中选出j和k的交集)。 Cxjpj 表示在k中但不在j中有多少种情况(因为在k中必在x中,并且从除j外的剩余p-j中颜色中选出)。

现在我们设f2[i][k]表示现在做到第i列,第i列放了j种颜色的方案数,那么 f2[1][k]=g[k],f2[i][k]=pj=1f2[i1][k]f1[j][k] 。打一下矩阵乘法即可,时间复杂度为O( logM1003 )。

Code

#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
const ll maxn=105,mo=998244353;
ll n,i,t,j,k,l,m,p,q,x,f[maxn][maxn],g[maxn],g1[maxn],c1[maxn];
ll ans,d[maxn];
ll mi(ll x,int y){
    if (y==1) return x;
    if (!y) return 1;
    ll t=mi(x,y/2);
    if (y%2) return t*t%mo*x%mo;return t*t%mo;
}
ll c(ll x,ll y){
    return c1[x]*mi(c1[y]*c1[x-y]%mo,mo-2)%mo;
}
struct code{
    ll a[maxn][maxn];
    code friend operator * (code x,code y){
        code z;memset(z.a,0,sizeof(z.a));int i,j,k;
        for (i=1;i<maxn;i++)
            for (j=1;j<maxn;j++)
                for (k=1;k<maxn;k++)
                    z.a[i][j]=(z.a[i][j]+x.a[i][k]*y.a[k][j]%mo)%mo;
        return z;
    }
}f1,f2,b;
void mi1(int y){
    while (y>1) d[++d[0]]=y%2,y/=2;
    b=f1;
    for (i=d[0];i>=1;i--){
        b=b*b;
        if (d[i]) b=b*f1;
    }
}
int main(){
    //freopen("data.in","r",stdin);
//  freopen("color.in","r",stdin);freopen("color.out","w",stdout);
    scanf("%d%d%d%d",&n,&m,&p,&q);
    c1[0]=1;
    for (i=1;i<maxn;i++)
        c1[i]=c1[i-1]*i%mo;
    f[0][0]=1;
    for (i=1;i<=n;i++)
        for (j=1;j<=min(i,p);j++)
            f[i][j]=(f[i-1][j-1]*(p-(j-1))%mo+f[i-1][j]*j%mo)%mo;
    for (j=1;j<=p;j++)
        f2.a[1][j]=g[j]=f[n][j],g1[j]=(g[j]*mi(c(p,j),mo-2))%mo;
    for (j=1;j<=p;j++)
        for (k=1;k<=p;k++){
            for (x=max(j,max(k,q));x<=min(j+k,p);x++)
                f1.a[j][k]=(f1.a[j][k]+c(j,j+k-x)%mo*c(p-j,x-j)%mo)%mo;
            f1.a[j][k]=f1.a[j][k]*g1[k]%mo;
        }
    if (m>1) mi1(m-1),f2=f2*b;
    for (i=1;i<=p;i++)
        ans=(ans+f2.a[1][i])%mo;
    printf("%lld\n",ans);
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值