[ZJOI2019]麻將(動態規劃,自動機)

[ZJOI2019]麻將(動態規劃,自動機)

題面

洛谷ios

題解

先作一點小鋪墊,對於一堆牌而言,咱們只須要知道這\(n\)張牌分別出現的次數就好了,即咱們只須要知道一個長度爲\(n\)的串就能夠了。
首先考慮如何判斷一副牌是否是能胡。
出現了七對牌的狀況很容易特判處理掉,只須要考慮第一種狀況。
那麼咱們考慮\(dp\)來判斷,設\(f[i][j][k][0/1]\)表示的當前考慮到了這個字符串的第\(i\)位,即考慮到了第\(i\)種牌,\(i-1,i,i+1\)的對子要用\(j\)次,\(i,i+1,i+2\)的對子要用\(k\)次,是否已經出現了一個對子。而這個\(dp\)值表示的是可以留下的最大的面子數量。不難發現\(j,k\)都不會超過\(2\)
那麼轉移的時候至關於讀進來當前的\(i\)有多少個,假設是\(x\),接下來\(x\)減去\(j+k\)組成順子,而後枚舉一下以多少\(x\)爲開頭組成順子。這裏再枚舉一下是否用當前的\(x\)組成對子或者刻字。
咱們把第一維丟掉,只考慮剩下的\(18\)個元素和最大的可能對子數,而且強制\(dp\)值不超過\(4\),最大對子數不超過\(7\)。這樣子就會存在大量重複的狀態,打表可得狀態只有不到\(2100\)種。
那麼咱們能夠提早把全部狀態所有預處理出來,預處理對於當前的一個狀態,插入後面一種牌\(x\)張的結果,這樣子就構成了一個自動機,那麼咱們只須要從頭至尾把一種狀態插入進去就能夠知道有沒有胡牌。
那麼此時咱們只須要知道抽了\(i\)張以後還未胡牌的機率,所有累加就是答案。
考慮在自動機上\(dp\),設\(f[i][p][k]\)表示當前考慮到第\(i\)種牌,且當前在自動機的\(p\)位置上,前面一共抽了\(k\)張牌且尚未胡的方案數。
轉移的時候枚舉這張牌用了多少次,用組合數帶進去進行計算,經過自動機進行狀態的轉移。ui

#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
using namespace std;
#define MOD 998244353
#define MAX 402
void add(int &x,int y){x+=y;if(x>=MOD)x-=MOD;}
inline int read()
{
    int x=0;bool t=false;char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')t=true,ch=getchar();
    while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    return t?-x:x;
}
struct Data
{
    int f[18],cnt;
    void init(){memset(f,-1,sizeof(f));f[0]=cnt=0;}
    bool check()
    {
        if(cnt>=7)return true;
        for(int i=0;i<3;++i)
            for(int j=0;j<3;++j)
                if(f[9+i*3+j]>=4)return true;
        return false;
    }
}QwQ,ST[2100];
bool operator<(Data a,Data b)
{
    if(a.cnt!=b.cnt)return a.cnt<b.cnt;
    for(int i=0;i<18;++i)if(a.f[i]!=b.f[i])return a.f[i]<b.f[i];
    return false;
}
Data Trans(Data a,int b)
{
    Data c;c.init();c.cnt=min(a.cnt+(b>=2),7);
    for(int i=0;i<3;++i)
        for(int j=0;j<3;++j)
        {
            if(~a.f[i*3+j])
            {
                for(int k=0;k<3&&i+j+k<=b;++k)
                    c.f[j*3+k]=max(c.f[j*3+k],min(a.f[i*3+j]+i+(b-i-j-k>=3),4));
                if(b>=2)
                    for(int k=0;k<3&&i+j+k<=b-2;++k)
                        c.f[9+j*3+k]=max(c.f[9+j*3+k],min(a.f[i*3+j]+i,4));
            }
            if(~a.f[9+i*3+j])
            {
                for(int k=0;k<3&&i+j+k<=b;++k)
                    c.f[9+j*3+k]=max(c.f[9+j*3+k],min(a.f[9+i*3+j]+i+(b-i-j-k>=3),4));
            }
        }
    return c;
}
map<Data,int> M;int tot;
void Build(Data x)
{
    if(x.check())return;
    if(M.find(x)!=M.end())return;
    ST[M[x]=++tot]=x;
    for(int i=0;i<=4;++i)Build(Trans(x,i));
}
int jc[MAX],jv[MAX],inv[MAX];
int f[2][2100][MAX];
int C(int n,int m){return 1ll*jc[n]*jv[m]%MOD*jv[n-m]%MOD;}
int n,ans,s[MAX],tr[2100][5];
int main()
{
    QwQ.init();Build(QwQ);
    jc[0]=jv[0]=inv[0]=inv[1]=1;
    for(int i=2;i<MAX;++i)inv[i]=1ll*inv[MOD%i]*(MOD-MOD/i)%MOD;
    for(int i=1;i<MAX;++i)jc[i]=1ll*jc[i-1]*i%MOD;
    for(int i=1;i<MAX;++i)jv[i]=1ll*jv[i-1]*inv[i]%MOD;
    n=read();for(int i=1;i<=13;++i)s[read()]+=1,read();
    for(int i=1;i<=tot;++i)for(int j=0;j<=4;++j)tr[i][j]=M[Trans(ST[i],j)];
    f[0][1][0]=1;
    for(int i=1,ss=0,nw=1,pw=0;i<=n;ss+=s[i],++i,nw^=1,pw^=1)
    {
        memset(f[nw],0,sizeof(f[nw]));
        for(int j=1;j<=tot;++j)
            for(int k=s[i];k<=4;++k)
            {
                if(!tr[j][k])continue;
                int w=1ll*C(4-s[i],k-s[i])*jc[k-s[i]]%MOD;
                for(int l=0;l<=n*4-k;++l)
                    if(f[pw][j][l])
                        add(f[nw][tr[j][k]][k+l],1ll*f[pw][j][l]*w%MOD*C(k+l-ss-s[i],k-s[i])%MOD);
            }
    }
    for(int i=13,val=1;i<=n*4;val=1ll*val*inv[n*4-i]%MOD,++i)
    {
        int ret=0;
        for(int j=1;j<=tot;++j)add(ret,f[n&1][j][i]);
        add(ans,1ll*ret*val%MOD);
    }
    printf("%d\n",ans);
    return 0;
}
相關文章
相關標籤/搜索