luogu P5279 [ZJOI2019]麻将

传送门

首先考虑怎么判断一堆牌里是否有个子集可以胡,首先7张对子比较好判,直接看个数(ge 2)的牌的总数,但是面子似乎不好贪心,考虑dp,设(f_{i,0/1,j,k})表示只有前(i)种牌的集合,是否有对子,以后要凑(j)(i-1,i,i+1)以及(k)(i,i+1,i+2),最多会有多少个面子.注意这里(j,k)(le 2)的,不然就可以搞出三个刻子,转移枚举(i+1)放多少张,然后枚举放多少个(i+1,i+2,i+3),然后枚举是否凑出一个对子,剩下的牌全部用来凑刻子进行转移

然后如果一副牌胡了,也就是对子(ge 7),或者面子(ge 4)还有对子,那么就不能转移的,所以考虑搜出所有不能胡的集合,然后要把重复的状态判掉,发现这样所有的不胡的状态只有(2091)

刚才搜状态的时候,可以发现这些状态构成了一个自动机,可以判断一副牌是否胡了.题目要求的是胡牌要摸多少牌的期望,根据期望的线性性可以看成摸了(i)张牌没有胡的概率之和.所以设(g_{i,j,k}),表示前(i)种牌,现在在自动机上的第(j)位,总共摸了(k)张牌的方案,转移枚举下一位放多少张,沿着自动机转移,还要乘上一个转移系数(就是组合数),最后算出的方案/总方案就是答案

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<vector>
#include<cmath>
#include<ctime>
#include<queue>
#include<map>
#include<set>
#define LL long long
#define db double

using namespace std;
const int N=100+10,M=2100+10,mod=998244353;
int rd()
{
    int x=0,w=1;char ch=0;
    while(ch<'0'||ch>'9'){if(ch=='-') w=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
    return x*w;
}
struct node
{
    int mz[2][3][3],dz;
    node(){memset(mz,-1,sizeof(mz)),dz=0;}
    bool operator < (const node &bb) const
    {
        for(int i=0;i<=1;++i)
            for(int j=0;j<=2;++j)
                for(int k=0;k<=2;++k)
                    if(mz[i][j][k]!=bb.mz[i][j][k]) return mz[i][j][k]<bb.mz[i][j][k];
        return dz<bb.dz;
   	}
}emp;
map<node,int> mp;
int tt,go[M][5],n,cn[N],fac[N<<2],iac[N<<2],f[2][M][N<<2];
int fpow(int a,int b){int an=1;while(b){if(b&1) an=1ll*an*a%mod;a=1ll*a*a%mod,b>>=1;} return an;}
int C(int n,int m){return m<0||n<m?0:1ll*fac[n]*iac[m]%mod*iac[n-m]%mod;}
bool hu(node x)
{
    if(x.dz>=7) return 1;
    for(int j=0;j<=2;++j)
        for(int k=0;k<=2;++k)
            if(x.mz[1][j][k]>=4) return 1;
    return 0;
}
node trans(node x,int nm)
{
    node nw;
    nw.dz=x.dz+(nm>=2);
    for(int i=0;i<=1;++i)
    {
        if(nm-i-i<0) continue;
        for(int m=i;m<=1;++m)
            for(int j=0;j<=2&&i+i+j<=nm;++j)
                for(int k=0;k<=2&&i+i+j+k<=nm;++k)
                    for(int l=0;l<=2&&i+i+j+k+l<=nm;++l)
                        if(~x.mz[m^i][j][k])
                            nw.mz[m][k][l]=min(4,max(nw.mz[m][k][l],x.mz[m^i][j][k]+j+(nm-(i+i+j+k+l))/3));
    }
    return nw;
}
void inii(node x)
{
    if(mp.find(x)!=mp.end()) return;
    mp[x]=++tt;
    for(int i=0;i<=4;++i)
    {
        node nt=trans(x,i);
        if(!hu(nt)) inii(nt);
        go[mp[x]][i]=mp[nt];
    }
}

int main()
{
    emp.mz[0][0][0]=0;
    inii(emp);
    n=rd();
    for(int i=1;i<=13;++i) ++cn[rd()],rd();
    fac[0]=1;
    for(int i=1;i<=n*4;++i) fac[i]=1ll*fac[i-1]*i%mod;
    iac[n*4]=fpow(fac[n*4],mod-2);
    for(int i=n*4;i;--i) iac[i-1]=1ll*iac[i]*i%mod;
    int nw=1,la=0;
    f[la][1][0]=1;
    for(int i=1;i<=n;++i)
    {
        for(int j=1;j<=tt;++j)
            for(int k=0;k<=n*4;++k)
            {
                if(!f[la][j][k]) continue;
                for(int l=cn[i];l<=4;++l)
                    if(go[j][l]) f[nw][go[j][l]][k+l]=(f[nw][go[j][l]][k+l]+1ll*f[la][j][k]*C(4-cn[i],l-cn[i])%mod)%mod;
                f[la][j][k]=0;
            }
        nw^=1,la^=1;
    }
    int ans=0;
    for(int j=1;j<=tt;++j)
        for(int k=13;k<=n*4;++k)
            ans=(ans+1ll*f[la][j][k]*fac[k-13]%mod*fac[n*4-k]%mod)%mod;
    printf("%lld
",1ll*ans*iac[n*4-13]%mod);
    return 0;
}
原文地址:https://www.cnblogs.com/smyjr/p/10754467.html