洛谷传送门
LOJ传送门
解析:
首先明确一点,我们要求的是这个东西:
∑ i i × P ( 胡 牌 最 小 寻 目 数 等 于 i ) \sum_{i}i\times P(胡牌最小寻目数等于i) i∑i×P(胡牌最小寻目数等于i)
其实稍微动一下脑子可以发现要求的其实是 ∑ i P ( 胡 牌 的 最 小 寻 目 数 大 于 i ) \sum_{i}P(胡牌的最小寻目数大于i) i∑P(胡牌的最小寻目数大于i)
我们计算拿所有张数的牌时候无法胡牌的情况就行了。
然后,怎么判断能否胡牌?
首先看面子怎么算。接下来顺子指三张连着的牌,三条指三张相同的牌。
我们发现三组相同的顺子和三组连着的三条没有任何区别。
强行规定相同类型的顺子只能出现至多两组。
设 d p [ i ] [ j ] dp[i][j] dp[i][j]表示当前已经考虑了前 k k k种牌,第 k − 1 k-1 k−1种牌开头的顺子至多有 i i i组,以第 k k k种牌开头的顺子至多有 j j j组的时候,所能组成的最多的面子数。(不记录 k k k,没有必要)。
现在考虑新增加了一种牌,有 b b b张,有很显然的转移,第 k − 1 k-1 k−1种牌开头的顺子能够产生实际贡献了,需要加上,然后剩下的牌除了顺子里面的全部考虑变成三条。
转移封装起来,就是代码里面第 44 44 44行到第 51 51 51行的DP了。
现在考虑怎么算七对子。
显然压两种情况,考虑对子放在面子里面(不作为对子产生贡献),考虑对子拿出来(贡献对子数)。
将状态压成三个东西,对子塞进面子里面的dp状态,对子拿出来考虑的dp状态,最多能够组成的对子数。
所有不同的状态只有3956种。
然后建立自动机,表示当前状态下,下一种牌有 i i i张转移到的状态。
暴力DP,顺便滚动数组优化。
然后按照第二个式子,算每种情况下不胡的概率。
代码:
#include<bits/stdc++.h>
#define ll long long
#define re register
#define gc getchar
#define cs const
namespace IO{
inline int getint(){
re char c;
while(!isdigit(c=gc()));re int num=c^48;
while(isdigit(c=gc()))num=(num+(num<<2)<<1)+(c^48);
return num;
}
}
using namespace IO;
using std::cout;
using std::cerr;
struct node{
int dp[3][3];
node(){memset(dp,-1,sizeof dp);}
int *operator[](int offset){return dp[offset];}
cs int *operator[](int offset)cs{return dp[offset];}
};
cs node unit=node();
inline bool operator<(cs node &a,cs node &b){
for(int re i=0;i<3;++i)
for(int re j=0;j<3;++j)if(a[i][j]!=b[i][j])return a[i][j]<b[i][j];
return false;
}
inline node operator+(cs node &a,cs node &b){
node res=unit;
for(int re i=0;i<3;++i)
for(int re j=0;j<3;++j)
res[i][j]=std::max(a[i][j],b[i][j]);
return res;
}
inline node operator+(cs node &a,cs int &b){
node res=unit;
for(int re i=0;i<3&&i<=b;++i)
for(int re j=0;j<3&&i+j<=b;++j)if(~a[i][j]){
for(int re k=0;k<3&&i+j+k<=b;++k)
res[j][k]=std::max(res[j][k],std::min(4,a[i][j]+i+(b-i-j-k)/3));
}
return res;
}
node starter(){
node s=unit;
s[0][0]=0;
return s;
}
struct mj{
node n,p;
int c;
mj():n(starter()),p(unit),c(0){}
mj(cs node &_n,cs node &_p,cs int &_c):n(_n),p(_p),c(_c){}
friend bool operator<(cs mj &a,cs mj &b){
if(a.c!=b.c)return a.c<b.c;
if(a.n<b.n)return true;
if(b.n<a.n)return false;
return a.p<b.p;
}
friend mj operator+(cs mj &a,cs int &b){
if(b>=2)return mj(a.n+b,(a.n+(b-2))+(a.p+b),std::min(a.c+1,7));
return mj(a.n+b,a.p+b,a.c);
}
};
cs int SIZE=3957;
std::map<mj,int> id;
mj state[SIZE];
int tr[SIZE][5],tot;
bool ok[SIZE];
inline bool check(cs mj &state){
if(state.c==7)return true;
for(int re i=0;i<3;++i)
for(int re j=0;j<3;++j)
if(state.p[i][j]==4)return true;
return false;
}
int dfs(cs mj &now){
if(id.count(now))return id[now];
id[now]=++tot;
int t=tot;
state[tot]=now;
ok[tot]=check(now);
for(int re i=0;i<=4;++i)
tr[t][i]=dfs(now+i);
return t;
}
cs int mod=998244353;
inline int add(int a,int b){return a+b>=mod?a+b-mod:a+b;}
inline void Inc(int &a,int b){(a+=b)>=mod?a-=mod:a;}
inline int dec(int a,int b){return a<b?a-b+mod:a-b;}
inline int mul(int a,int b){return (ll)a*b%mod;}
inline int quickpow(int a,int b,int res=1){
while(b){
if(b&1)res=mul(res,a);
a=mul(a,a);
b>>=1;
}
return res;
}
cs int N=101;
int n,ans;
int dp[2][N<<2][SIZE];
int used[N];
int c[5][5]={
{1},
{1,1},
{1,2,1},
{1,3,3,1},
{1,4,6,4,1}
};
signed main(){
dfs(mj());
n=getint();
for(int re i=1;i<=13;++i)++used[getint()],getint();
dp[0][0][1]=1;int now=1;
for(int re i=1;i<=n;++i,now^=1,memset(dp[now],0,sizeof dp[now]))
for(int re j=0;j<=4*(i-1);++j)
for(int re k=0;k<=tot;++k)if(dp[now^1][j][k])
for(int re t=used[i];t<5;++t)
Inc(dp[now][j+t][tr[k][t]],mul(dp[now^1][j][k],c[4-used[i]][t-used[i]]));
now^=1;
for(int re i=13;i<=n*4;++i){
int sum=0,fail=0;
for(int re j=1;j<=tot;++j){
Inc(sum,dp[now][i][j]);
if(!ok[j])Inc(fail,dp[now][i][j]);
};
Inc(ans,mul(fail,quickpow(sum,mod-2)));
}
cout<<ans<<"\n";
return 0;
}