[ZJOI2019]麻将(DP+有限状态自动机)

首先只需要考虑每种牌出现的张数即可,然后判断一副牌是否能胡,可以DP一下,令f[i][j][k][0/1]表示到了第i位,用j次i-1,i,i+1和k次i,i+1,i+2,是否出现对子然后最大的面子数量,j,k∈[0,2],转移也很容易。这样暴力枚举可以获得50pts的“好”成绩。

然后可以丢掉第一维,只考虑18个状态最大可能对子数,强制f值<=4,最大对子数<=7,发现状态不到5000种。

然后把所有状态预处理,丢掉重复的状态,把有用状态建在自动机上。所以仅需从头到尾插入一种状态即可知道是否胡牌。

然后可以DP了,f[i][j][k]表示DP到第i种牌,状态在自动机j位置,抽了k张没胡的方案,直接转移即可。

#include<bits/stdc++.h>
using namespace std;
const int N=5005,mod=998244353;
int n,tot,ans,c[5][5],fac[N],inv[N],has[N],f[2][N][405],ch[N][5];
struct node{
    int f[2][3][3],cnt;
    node(){memset(f,-1,sizeof f),f[0][0][0]=cnt=0;}
    bool operator<(const node&x)const
    {
        for(int i=0;i<2;i++)
        for(int j=0;j<3;j++)
        for(int k=0;k<3;k++)
        if(f[i][j][k]!=x.f[i][j][k])return f[i][j][k]<x.f[i][j][k];
        return cnt<x.cnt;
    }
}st[N];
void add(int&a,long long b){a=(a+b)%mod;}
map<node,int>S;
node trans(node u,int x)
{
    node ret;
    ret.cnt=min(u.cnt+(x>=2),7);
    for(int a=0;a<2;a++)
    for(int b=0;a+b<2;b++)
    for(int i=0;i<3;i++)
    for(int j=0;j<3;j++)
    for(int k=0;k<3;k++)
    if(i+j+k+b*2<=x&&u.f[a][i][j]!=-1)
    ret.f[a+b][j][k]=max(ret.f[a+b][j][k],min(u.f[a][i][j]+i+(x-i-j-k-b*2)/3,4));
    return ret;
}
int dfs(node u)
{
    if(S.count(u))return S[u];
    if(u.cnt==7)return 0;
    for(int i=0;i<3;i++)for(int j=0;j<3;j++)if(u.f[1][i][j]==4)return 0;
    st[++tot]=u,S[u]=tot;
    int pos=tot;
    for(int i=0;i<=4;i++)ch[pos][i]=dfs(trans(u,i));
    return pos;
}
int main()
{
    scanf("%d",&n);
    for(int i=1,x;i<=13;i++)scanf("%d%*d",&x),has[x]++;
    for(int i=0;i<=4;i++)
    {
        c[i][0]=1;
        for(int j=1;j<=i;j++)c[i][j]=c[i-1][j-1]+c[i-1][j];
    }
    fac[0]=inv[0]=inv[1]=1;for(int i=2;i<=4*n;i++)inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
    for(int i=1;i<=4*n;i++)fac[i]=1ll*fac[i-1]*i%mod,inv[i]=1ll*inv[i-1]*inv[i]%mod;
    dfs(node());
    f[0][1][0]=1;
    for(int i=1;i<=n;i++)
    {
        memset(f[i&1],0,sizeof f[i&1]);
        for(int j=1;j<=tot;j++)
        for(int k=has[i];k<=4;k++)
        if(ch[j][k])
        for(int t=0;t<=(i-1)*4;t++)
        add(f[i&1][ch[j][k]][k+t],1ll*f[i-1&1][j][t]*c[4-has[i]][k-has[i]]);
    }
    for(int i=1,sum;i<=n*4-13;i++)
    {
        sum=0;
        for(int j=1;j<=tot;j++)add(sum,f[n&1][j][i+13]);
        add(ans,1ll*sum*fac[i]%mod*fac[4*n-13-i]);
    }
    ans=(1ll*ans*inv[4*n-13]+1)%mod;
    printf("%d",ans);
}
View Code

 

posted @ 2019-05-06 16:04  hfctf0210  阅读(318)  评论(0编辑  收藏  举报