LG P5279 [ZJOI2019]麻将

Description

九条可怜是一个热爱打麻将的女孩子。因此她出了一道和麻将相关的题目,希望这题不会让你对麻将的热爱消失殆尽。

今天,可怜想要打麻将,但是她的朋友们都去下自走棋了,因此可怜只能自己一个人打。可怜找了一套特殊的麻将,它有$n(n\ge 5)$种不同的牌,大小分别为 $1$到 $n$,每种牌都有 $4$ 张。

定义面子为三张大小相同或者大小相邻的麻将牌,即大小形如$i,i,i(1 \le i \le n)$或者$i,i+1,i+2(1 \le i \le n-2)$。定义对子为两张大小相同的麻将牌,即大小形如$i,i(1 \le i \le n)$。

定义一个麻将牌集合 $S$ 是胡的当且仅当它的大小为 $14$ 且满足下面两个条件中的至少一个:

  • $S$ 可以被划分成五个集合 $S_1$ 至 $S_5$ 。其中 $S_1$ 为对子,$S_2$ 至 $S_5$ 为面子。
  • $S$ 可以被划分成七个集合 $S_1$至 $S_7$ ,它们都是对子,且对应的大小两两不同。

举例来说,下列集合都是胡的(这儿只标记了大小):

  • {1,1,1,1,2,3,4,5,6,7,8,9,9,9}
  • {1,1,2,2,4,4,5,5,6,6,7,7,8,8}
  • {1,1,2,2,3,3,4,4,5,5,6,6,7,7}

而下列集合都不是胡的:

  • {1,1,1,2,3,4,5,6,7,8,9,9,9}
  • {1,1,1,1,4,4,5,5,6,6,7,7,8,8}
  • {1,1,1,2,3,4,5,6,7,8,9,9,9,11}

可怜先摸出了 $13$张牌,并把剩下的$4n-13$张牌随机打乱。打乱是等概率随机的,即所有$(4n-13)!$种排列都等概率出现。

对于一个排列$P$,可怜定义 $S_i$ 为可怜事先摸出的 $13$ 张牌加上 $P$中的前 $i$ 张牌构成的集合,定义 $P$ 的权值为最小的 $i$ 满足 $S_i$ 存在一个子集是胡的。如果你对麻将比较熟悉,不难发现 $P$ 的权值就是理论上的最早胡牌巡目数。注意到 $n\ge 5$的时候,$S_{4n-13}$总是存在胡的子集的,因此 $P$ 的权值是良定义的。

现在可怜想要训练自己的牌效,因此她希望你能先计算出 $P$ 的权值的期望是多少。

Solution

记$f[i][j][k][0/1]$为当前选到第$i$种麻将,有$j$个顺子差最后一张,有$k$个顺子差最后两张,$0/1$代表是否有对子时的面子数量

枚举新来了几张$i+1$的牌,求出加入这些牌之后的状态,作为内层DP

记$dp[i][S]$为选$i$张牌,状态为$S$的方案数,作为外层DP

DP套DP求解,最终答案为

$\sum_{i=13}^{4n} \sum_{S} \frac{dp[i][S](i-13)!(4n-i)!}{(4n-13)!}$

#include<iostream>
#include<cstring>
#include<cstdio>
#include<queue>
#include<cmath>
#include<map>
using namespace std;
int siz,trans[5000][10],n,cnt[105],jc[505],C[500][500],dp[1000][5000],DP[1000][5000],ans;
const int mod=998244353;
struct Node
{
    int cnt,f[3][3][2];
    void clear()
    {
        cnt=0;
        memset(f,128,sizeof(f));
    }
}goal;
queue<Node>q;
map<Node,int>mp;
inline int read()
{
    int f=1,w=0;
    char ch=0;
    while(ch<'0'||ch>'9')
    {
        if(ch=='-')
            f=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9')
    {
        w=(w<<1)+(w<<3)+ch-'0';
        ch=getchar();
    }
    return f*w;
}
bool operator < (Node a,Node b)
{
    for(int i=0;i<3;i++)
    {
        for(int j=0;j<3;j++)
        {
            for(int k=0;k<2;k++)
            {
                if(a.f[i][j][k]!=b.f[i][j][k])
                {
                    return a.f[i][j][k]<b.f[i][j][k];
                }
            }
        }
    }
    return a.cnt<b.cnt;
}
bool operator == (Node a,Node b)
{
    for(int i=0;i<3;i++)
    {
        for(int j=0;j<3;j++)
        {
            for(int k=0;k<2;k++)
            {
                if(a.f[i][j][k]!=b.f[i][j][k])
                {
                    return false;
                }
            }
        }
    }
    if(a.cnt!=b.cnt)
    {
        return false;
    }
    return true;
}
Node operator + (Node v,int t)
{
    if(v==goal)
    {
        return goal;
    }
    Node ans;
    ans.clear();
    ans.cnt=v.cnt+(t>=2);
    if(ans.cnt>=7)
    {
        return goal;
    }
    for(int i=0;i<3;i++)
    {
        for(int j=0;j<3;j++)
        {
            for(int k=0;k<2;k++)
            {
                if(v.f[i][j][k]>=0)
                {
                    for(int a=0;a<=min(i,t);a++)
                    {
                        for(int b=0;b<=min(j,t);b++)
                        {
                            for(int c=0;c<=1;c++)
                            {
                                for(int d=0;d<=1;d++)
                                {
                                    if(a+b+c*2+d*3<=t)
                                    {
                                        ans.f[min(2,t-a-b-c*2-d*3)][a][k|c]=max(ans.f[min(2,t-a-b-c*2-d*3)][a][k|c],v.f[i][j][k]+b+d);
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    for(int i=0;i<3;i++)
    {
        for(int j=0;j<3;j++)
        {
            for(int k=0;k<2;k++)
            {
                ans.f[i][j][k]=min(ans.f[i][j][k],4);
                if(k==1&&ans.f[i][j][k]==4)
                {
                    return goal;
                }
            }
        }
    }
    return ans;
}
void bfs()
{
    Node st;
    st.clear();
    st.f[0][0][0]=0;
    goal.clear();
    mp[st]=++siz;
    mp[goal]=++siz;
    for(int i=0;i<=4;i++)
    {
        trans[2][i]=2;
    }
    q.push(st);
    while(q.size())
    {
        Node st=q.front();
        q.pop();
        for(int i=0;i<=4;i++)
        {
            Node to=st+i;
            if(!mp[to])
            {
                mp[to]=++siz;
                q.push(to);
            }
            trans[mp[st]][i]=mp[to];
        }
    }
}
long long ksm(long long a,long long p)
{
    long long ret=1;
    while(p)
    {
        if(p&1)
        {
            (ret*=a)%=mod;
        }
        (a*=a)%=mod;
        p>>=1;
    }
    return ret;
}
int main()
{
    bfs();
    n=read();
    for(int i=1;i<=13;i++)
    {
        cnt[read()]++;
        read();
    }
    jc[0]=C[0][0]=1;
    for(int i=1;i<=4*n;i++)
    {
        C[i][0]=1;
        jc[i]=1ll*jc[i-1]*i%mod;
        for(int j=1;j<=i;j++)
        {
            C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;
        }
    }
    DP[0][1]=1;
    for(int o=1;o<=n;o++)
    {
        for(int i=0;i<=4*o;i++)
        {
            for(int x=1;x<=siz;x++)
            {
                dp[i][x]=DP[i][x];
                DP[i][x]=0;
            }
        }
        for(int i=0;i<=4*o;i++)
        {
            for(int x=1;x<=siz;x++)
            {
                for(int k=cnt[o];k<=4;k++)
                {
                    int to=trans[x][k];
                    if(to!=2)
                    {
                        (DP[i+k][to]+=1ll*C[4-cnt[o]][k-cnt[o]]*dp[i][x]%mod)%=mod;
                    }
                }
            }
        }
    }
    for(int i=0;i<=4*n;i++)
    {
        int temp=0;
        for(int x=1;x<=siz;x++)
        {
            (temp+=DP[i][x])%=mod; 
        }
        if(i>=13)
        {
            temp=1ll*temp*jc[i-13]%mod*jc[4*n-i]%mod;
            (ans+=1ll*temp*ksm(jc[4*n-13],mod-2)%mod)%=mod;
        }
    }
    printf("%lld\n",ans%mod);
    return 0;
}
[ZJOI2019]麻将

猜你喜欢

转载自www.cnblogs.com/JDFZ-ZZ/p/13396431.html