題面
題意
共有n種麻將牌,給出一開始的13張麻將牌,問期望摸上來幾張牌後,與開始的13張牌組合後存在一個大小爲14的能和的子集.
做法
因爲要求期望的摸牌次數,我們可以將這個次數轉化爲表示總共有i張牌後仍然沒和的概率,而表示總共有i張牌後仍然沒和的排列數量.
爲了求這個值,我們需要記錄此時已有的牌的狀態來判斷它是否能和.現在我們考慮如何判斷一個集合是否能和.記表示當前考慮到第i種牌,以爲開頭的順子有個,以爲開頭的順子有個時的最大面子數,因爲順子數量不可能超過2(否則可以轉化爲3個刻子),因而後兩維的大小都是3.
用上述dp可以記錄一個集合中的最大面子數,這樣一個集合是否能和就只要記錄三個量:(當前對子數)分別表示此時無對子和有對子的最大面子數,這樣的集合的種類其實很少.
然後考慮如何求,可以設計第二個dp,表示當前考慮到第i種牌,此時牌的集合的狀態爲j,已經摸了k張牌的方案數,然後只要枚舉第種牌摸了幾張牌即可轉移:
表示摸了幾張第種牌,表示開始13張牌中有幾張,爲的前綴和.
爲了節約空間,可以用滾動數組.
代碼
#include<bits/stdc++.h>
#define ll long long
#define N 110
#define MN 400
#define MM 4010
#define M 998244353
using namespace std;
ll n,ans,cm,yl[N],qz[N],to[MM][5],jc[N*4],nj[N*4],dp[N][MM][4*N];
bool now,cur;
inline void Min(ll &u,ll v){if(v<u) u=v;}
inline void Max(ll &u,ll v){if(v>u) u=v;}
inline void Add(ll &u,ll v){u=(u+v)%M;}
struct Zt
{
ll dp[3][3];
Zt(){memset(dp,-1,sizeof(dp));}
void init(){memset(dp,-1,sizeof(dp));}
bool operator < (const Zt &u) const
{
ll i,j;
for(i=0;i<3;i++)
{
for(j=0;j<3;j++)
{
if(dp[i][j]!=u.dp[i][j])
return dp[i][j]<u.dp[i][j];
}
}
return 0;
}
bool operator != (const Zt &u) const
{
ll i,j;
for(i=0;i<3;i++)
{
for(j=0;j<3;j++)
{
if(dp[i][j]!=u.dp[i][j])
return 1;
}
}
return 0;
}
void out()
{
ll i,j;
for(i=0;i<3;i++)
{
for(j=0;j<3;j++)
{
cout<<dp[i][j]<<" ";
}
puts("");
}
puts("");
}
Zt tran(ll u)
{
ll i,j,k;
Zt res;
// cout<<u<<":\n";
// out();
for(i=0;i<3;i++)
{
for(j=0;j<3;j++)
{
if(dp[i][j]==-1) continue;
// cerr<<i<<' '<<j<<endl;
for(k=0;k<=min(2ll,u-i-j);k++)
{
Max(res.dp[j][k],dp[i][j]+k+(u-i-j-k)/3);
Min(res.dp[j][k],4ll);
}
}
}
// res.out();
// for(i=1;i<=1000000000;i++);
return res;
}
};
inline Zt max(Zt u,Zt v)
{
ll i,j;
Zt res;
for(i=0;i<3;i++)
{
for(j=0;j<3;j++)
{
res.dp[i][j]=max(u.dp[i][j],v.dp[i][j]);
}
}
return res;
}
struct Mj
{
Zt A,B;
ll cnt;
bool hu;
Mj(){cnt=hu=0;}
bool operator < (const Mj &u) const
{
if(cnt!=u.cnt) return cnt<u.cnt;
if(A!=u.A) return A<u.A;
return B<u.B;
}
void out()
{
// cout<<cnt<<endl;
A.out(),B.out();
puts("");
}
Mj tran(ll u)
{
ll i,j,k;
Mj res;
// out();
res.B=B.tran(u);
res.cnt=cnt;
if(u>=2) res.cnt=min(res.cnt+1,7ll),res.B=max(res.B,A.tran(u-2));
res.A=A.tran(u);
if(res.cnt==7 || res.B.dp[0][0]==4) res.hu=1;
// res.out();
// puts("--------------------");
return res;
}
}st,mj[MM];
map<Mj,ll>mm;
inline ll A(ll u,ll v){return jc[u]*nj[u-v]%M;}
inline ll C(ll u,ll v){return jc[u]*nj[v]%M*nj[u-v]%M;}
inline ll po(ll u,ll v)
{
ll res=1;
for(;v;)
{
if(v&1) res=res*u%M;
u=u*u%M;
v>>=1;
}
return res;
}
inline ll in(Mj u)
{
mm[u]=++cm;
mj[cm]=u;
return cm;
}
ll dfs(Mj now)
{
if(mm.count(now)) return mm[now];
// now.out();
ll i,res=in(now);
for(i=0;i<=4;i++) to[res][i]=dfs(now.tran(i));
return res;
}
int main()
{
st.A.dp[0][0]=0;
ll i,j,k,z,p;
jc[0]=1;for(i=1;i<=MN;i++) jc[i]=jc[i-1]*i%M;
nj[MN]=po(jc[MN],M-2);for(i=MN-1;i>=0;i--) nj[i]=nj[i+1]*(i+1)%M;
dfs(st);
cout<<cm;return 0;
cin>>n;
for(i=1;i<=13;i++)
{
scanf("%lld%*d",&p);
yl[p]++;
}
for(i=1;i<=n;i++) qz[i]=qz[i-1]+yl[i];
now=1;
dp[1][mm[st]][0]=1;
for(i=0;i<n;i++)
{
swap(now,cur);
memset(dp[now],0,sizeof(dp[now]));
for(j=1;j<=cm;j++)
{
for(k=0;k<=4*i;k++)
{
if(!dp[cur][j][k]) continue;
for(z=yl[i+1];z<=4;z++)
{
ll t=to[j][z];
Add(dp[now][t][k+z],dp[cur][j][k]*A(4-yl[i+1],z-yl[i+1])%M*C(k+z-qz[i+1],z-yl[i+1])%M);
}
}
}
}
for(i=13;i<4*n;i++)
{
ll sum=0;
for(j=1;j<=cm;j++)
{
if(mj[j].hu) continue;
Add(sum,dp[now][j][i]);
}
// cerr<<i<<" "<<sum<<endl;
sum=sum*po(A(4*n-13,i-13),M-2)%M;
Add(ans,sum);
}
cout<<ans;
}