[ZJOI2019]麻将

https://www.luogu.org/problemnew/show/P5279

题解

我们可以把一副牌看成一个字符串,字符串的第(i)位表示编号为(i)的牌有多少张。

注意一下胡牌的条件:1、四面+一对2、七对。

我们可以设计一个(dp)

(dp[i][j][k][0/1])表示已经做完了前(i)种权值,保留了((i-1,i))(j)对,除此之外还有(k)(i),除此之外(0)不保留(1)保留了对子时,面子数的最大值。

转移的话枚举下一种牌型放几张牌,然后枚举转移之后的(k),那么剩下的(x-k)张牌需要全部和前面的(i)个对子组成面子,和前面的(j)个单的组成对,所以我们的(i+j+k)不能超过新来点的牌的总和。

还有我们注意到当(j)或者(k)超过(2)时是可以直接组成面子的,所以我们的一个(dp)数组时(3 imes 3)的。

对于(0)(1)的转移,注意到当新来的牌超过(1)张时是可以从(0)转移到(1)的。

注意到我们刚才的(dp)状态中没有提到第二种胡牌的方式,这个我们可以在(dp)值中加上一个对子的个数来判断。

这个(dp)的第一维可以滚掉,于是我们的(dp)状态就变成了,当前牌有多少对牌,还有两个转移矩阵。

我们可以把这个(dp)的转移看做一个自动机,然后用(bfs)的方法去扩展,对于胡了的牌我们开一个节点,所有胡牌的转移边全部连上去。然后我们发现这个状态量只有(2092)

于是我们可以设(f[i][j][k])表示做到前(i)种牌,从后面的牌中拿了(j)张,现在在自动机的(k)节点上。

(dp)完之后我们相当于求出了取出(i)张牌每胡的概率,那么期望次数就是。

[(sum dp[i])+1 ]

代码

#include<bits/stdc++.h>
#define M 2100
#define N 402
using namespace std;
typedef long long ll;
const int mod=998244353;
ll dp[2][N][M],jie[N],ni[N];
int sum,nowsum,tot,a[N],n;
inline ll rd(){
    ll x=0;char c=getchar();bool f=0;
    while(!isdigit(c)){if(c=='-')f=1;c=getchar();}
    while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
    return f?-x:x;
}
inline ll power(ll x,ll y){
  ll ans=1;
  while(y){if(y&1)ans=ans*x%mod;x=x*x%mod;y>>=1;}
  return ans;
}
inline ll C(int n,int m){return jie[n]*ni[m]%mod*ni[n-m]%mod;}
inline ll calc(int now,int x,int y){
  return dp[now][x][y]*jie[x]%mod*jie[nowsum-x]%mod;
}
inline void MOD(ll &x){x=x>=mod?x-mod:x;}
struct matrix{
  int a[3][3];
  matrix(){memset(a,-1,sizeof(a));}
  inline bool operator !=(const matrix &b)const{
    for(int i=0;i<3;++i)
      for(int j=0;j<3;++j)
        if(a[i][j]!=b.a[i][j])return 1;
    return 0;
  }
  inline bool operator <(const matrix &b)const{
    for(int i=0;i<3;++i)
      for(int j=0;j<3;++j)
        if(a[i][j]!=b.a[i][j])return a[i][j]<b.a[i][j];
    return 0;
  }
  inline bool hu(){
    for(int i=0;i<3;++i)
      for(int j=0;j<3;++j)if(a[i][j]>=4)return 1;
    return 0;
  }
  inline void add(matrix b,int num){
    for(int i=0;i<3;++i)
      for(int j=0;j<3;++j)if(~b.a[i][j])
         for(int k=0;k<3&&i+j+k<=num;++k){
           a[j][k]=max(a[j][k],min(4,b.a[i][j]+i+(num-i-j-k)/3));
         }
  }
};
struct node{
  matrix p[2];
  int cnt,ch[5];
  node(){cnt=0;memset(ch,0,sizeof(ch));p[0]=p[1]=matrix();}
  inline bool hu(){
    if(cnt>=7)return 1;return p[1].hu();
  }
  inline bool operator <(const node &b)const{
    if(cnt!=b.cnt)return cnt<b.cnt;
    if(p[0]!=b.p[0])return p[0]<b.p[0];
    return p[1]<b.p[1];
  }
  inline void boom(){
    memset(ch,0,sizeof(ch));
    p[0]=p[1]=matrix();cnt=-1;
  }
  friend inline node operator +(node a,int num){
    if(a.hu()){a.boom();return a;}
    node t;
    t.p[0].add(a.p[0],num);
    t.p[1].add(a.p[1],num);
    t.cnt=a.cnt+(num>=2);
    if(num>=2)t.p[1].add(a.p[0],(num-2));
    if(t.hu())t.boom();
    return t;
  }
};
node nw[2100];
map<node,int>mp;
inline void bfs(int id){
  for(int i=0;i<5;++i){
    node x=nw[id]+i;
    if(mp.find(x)==mp.end())nw[mp[x]=++tot]=x;
    nw[id].ch[i]=mp[x];
  }
}
inline void prework(int n){
  jie[0]=1;
  for(int i=1;i<=n;++i)jie[i]=jie[i-1]*i%mod;
  ni[n]=power(jie[n],mod-2);
  for(int i=n-1;i>=0;--i)ni[i]=ni[i+1]*(i+1)%mod;
  tot=2;
  nw[1].p[0].a[0][0]=0;
  mp[nw[1]]=1;
  nw[tot].cnt=-1;
  mp[nw[tot]]=2;
  bfs(1);
  for(int i=3;i<=tot;++i)bfs(i);
    
}
int main(){
    n=rd();
    sum=n*4;
    nowsum=sum-13;
    prework(sum);
    for(int i=1;i<=13;++i){
      int x=rd(),y=rd();
      a[x]++;
    }
    int now=1,pre=0;
    dp[now][0][1]=1;
    for(int i=1;i<=n;++i){
      swap(now,pre);
      memset(dp[now],0,sizeof(dp[now]));
      for(int j=0;j<=nowsum;++j){
        for(int k=1;k<=tot;++k)if(dp[pre][j][k])
          for(int l=0;l<=4-a[i];++l){
            MOD(dp[now][j+l][nw[k].ch[l+a[i]]]+=dp[pre][j][k]*C(4-a[i],l)%mod);
          }
      }
    }
    ll ans=0;
    for(int i=1;i<=tot;++i)if(i!=2){
      for(int j=1;j<=nowsum;++j)MOD(ans+=calc(now,j,i));
    }
    ans=ans*ni[nowsum]%mod;
    MOD(ans+=1);
    cout<<ans;
    return 0;
}
原文地址:https://www.cnblogs.com/ZH-comld/p/11050328.html