这是一道麻将自动机的模板题(雾

其实这是一道dp套dp借助自动机实现的麻将好题!

首先把期望转化一下,拆成sigema p(x>i)

现在要计算i张牌不胡的概率,也就等价于计算i张牌不胡的方案数。

如果我们能建立一个关于麻将的自动机,支持插入麻将,判断当前牌型是否能胡,既可以在麻将自动机上dp解决本题。

先考虑如何压缩一副麻将,显然只需要把它的每一种牌型的个数算出来即可。

再考虑如何判定一副麻将是否能胡,这个可以借助dp。

dp[i][j][k]表示当前有i个x,还额外预留了j个(x,x-1),k表示是否有对子。

转移的时候枚举下一个位置预留几组,走个顺子即可。

现在我们要对这个过程建立自动机。

考虑两个麻将状态不同,当且仅当它们存在某种相同的状态,dp值却互不相同。

写一个结构体,然后bfs寻找状态+map判重即可。

不同的状态数不多,2000多种的样子。

建好这个东西后再去dp就可以了。

#include<bits/stdc++.h>
#define N 1100
#define M 5000
#define db double
#define ll long long
#define ldb long double
using namespace std;
inline int read()
{
char ch=0;
int x=0,flag=1;
while(!isdigit(ch)){ch=getchar();if(ch=='-')flag=-1;}
while(isdigit(ch)){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
return x*flag;
}
const int mo=998244353,inf=1e9+7;
struct node
{
int cnt,f[3][3][2];
void clear()
{
cnt=0;
for(int i=0;i<3;i++)for(int j=0;j<3;j++)for(int k=0;k<2;k++)f[i][j][k]=-inf;
}
};
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 goal;
void up(int &x,int k){x=max(x,k);}
node operator+(node v,int t)
{
if(v==goal)return goal;
static 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++)
{
int o=v.f[i][j][k];
if(o<0)continue;
for(int a=0;a<=min(i,t);a++)//枚举预留组数
for(int b=0;b<=min(j,t);b++)//枚举走几个顺子
if(a+b<=t)
{
for(int c=0;c<=1;c++)//枚举走几个对子
for(int d=0;d<=1;d++)//枚举走几个面子
if(a+b+c*2+d*3<=t)up(ans.f[min(2,t-a-b-c*2-d*3)][a][k|c],o+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;
}
node qwq[M];
queue<node>q;
map<node,int>mp;
int size,cnt[N],fac[N],C[N][N],dp[N][M],DP[N][M],nxt[M][10];
void build()
{
node st;
st.clear();goal.clear();
st.f[0][0][0]=0;goal.cnt=-1;
for(int i=0;i<3;i++)for(int j=0;j<3;j++)for(int k=0;k<2;k++)goal.f[i][j][k]=-mo;
mp[st]=++size;mp[goal]=++size;q.push(st);
for(int i=0;i<=4;i++)nxt[2][i]=2;
while(!q.empty())
{
node x=q.front();q.pop();
qwq[mp[x]]=x;
for(int i=0;i<=4;i++)
{
node to=x+i;
if(!mp.count(to))mp[to]=++size,q.push(to);
nxt[mp[x]][i]=mp[to];
}
}
}
int ksm(int x,int k)
{
int ans=1;
while(k){if(k&1)ans=1ll*ans*x%mo;k>>=1;x=1ll*x*x%mo;}
return ans;
}
int main()
{
build();
int n=read(),ans=0;
for(int i=1;i<=13;i++){cnt[read()]++;read();}
fac[0]=C[0][0]=1;
for(int i=1;i<=4*n;i++)
{
C[i][0]=1;fac[i]=1ll*fac[i-1]*i%mo;
for(int j=1;j<=i;j++)C[i][j]=(C[i-1][j-1]+C[i-1][j])%mo;
}
DP[0][1]=1;
for(int o=1;o<=n;o++)
{
for(int i=0;i<=4*o;i++)
for(int x=1;x<=size;x++)
dp[i][x]=DP[i][x],DP[i][x]=0;
for(int i=0;i<=4*o;i++)
for(int x=1;x<=size;x++)
{
if(!dp[i][x])continue;
for(int k=cnt[o];k<=4;k++)
{
int to=nxt[x][k];if(to==2)continue;
DP[i+k][to]=(DP[i+k][to]+(1ll*C[4-cnt[o]][k-cnt[o]]*dp[i][x]%mo))%mo;
}
}
}
for(int i=0;i<4*n;i++)
{
int tot=0;
for(int x=1;x<=size;x++)tot=(tot+DP[i][x])%mo;
tot=1ll*tot*fac[i-13]%mo*fac[4*n-i]%mo;
ans=(ans+1ll*tot*ksm(fac[4*n-13],mo-2))%mo;
}
printf("%d",(ans%mo+mo)%mo);
return 0;
}

[ZJOI2019]麻将的更多相关文章

  1. [ZJOI2019]麻将(动态规划,自动机)

    [ZJOI2019]麻将(动态规划,自动机) 题面 洛谷 题解 先做一点小铺垫,对于一堆牌而言,我们只需要知道这\(n\)张牌分别出现的次数就行了,即我们只需要知道一个长度为\(n\)的串就可以了. ...

  2. Luogu P5279 [ZJOI2019]麻将

    ZJOI2019神题,间接送我退役的神题233 考场上由于T2写挂去写爆搜的时候已经没多少时间了,所以就写挂了233 这里不多废话直接开始讲正解吧,我们把算法分成两部分 1.建一个"胡牌自动 ...

  3. 【题解】Luogu P5279 [ZJOI2019]麻将

    原题传送门 希望这题不会让你对麻将的热爱消失殆尽 我们珂以统计每种牌出现的次数,不需要统计是第几张牌 判一副牌能不能和,类似这道题 对于这题: 设\(f[i][j][k][0/1]\)表示前\(i\) ...

  4. 洛谷P5279 [ZJOI2019]麻将(乱搞+概率期望)

    题面 传送门 题解 看着题解里一堆巨巨熟练地用着专业用语本萌新表示啥都看不懂啊--顺便\(orz\)余奶奶 我们先考虑给你一堆牌,如何判断能否胡牌 我们按花色大小排序,设\(dp_{0/1,i,j,k ...

  5. 【洛谷5279】[ZJOI2019] 麻将(“胡牌自动机”上DP)

    点此看题面 大致题意: 给你13张麻将牌,问你期望再摸多少张牌可以满足存在一个胡的子集. 似乎ZJOI2019Day1的最大收获是知道了什么是胡牌? 一个显然的性质 首先我们要知道一个显然的性质,即对 ...

  6. 洛谷P5279 [ZJOI2019]麻将

    https://www.luogu.org/problemnew/show/P5279 以下为个人笔记,建议别看: 首先考虑如何判一个牌型是否含有胡的子集.先将牌型表示为一个数组num,其中num[i ...

  7. [ZJOI2019]麻将(DP+有限状态自动机)

    首先只需要考虑每种牌出现的张数即可,然后判断一副牌是否能胡,可以DP一下,令f[i][j][k][0/1]表示到了第i位,用j次i-1,i,i+1和k次i,i+1,i+2,是否出现对子然后最大的面子数 ...

  8. 题解 洛谷 P5279 【[ZJOI2019]麻将】

    这题非常的神啊...蒟蒻来写一篇题解. Solution 首先考虑如何判定一副牌是否是 "胡" 的. 不要想着统计个几个值 \(O(1)\) 算,可以考虑复杂度大一点的. 首先先把 ...

  9. 洛谷 P5279 - [ZJOI2019]麻将(dp 套 dp)

    洛谷题面传送门 一道 dp 套 dp 的 immortal tea 首先考虑如何判断一套牌是否已经胡牌了,考虑 \(dp\)​​​​​.我们考虑将所有牌按权值大小从大到小排成一列,那我们设 \(dp_ ...

随机推荐

  1. 最新阿里云申请免费SSL证书实现网站HTTPS化(图文教程一)

    一.申请免费SSL证书: 1.登录阿里云: 2.领取代金券礼包: https://promotion.aliyun.com/ntms/yunparter/invite.html?userCode=0a ...

  2. 279. Perfect Squares(动态规划)

    Given a positive integer n, find the least number of perfect square numbers (for example, 1, 4, 9, 1 ...

  3. kafka安装教程

    今天需要在新机器上安装一个kafka集群,其实kafka我已经装了十个不止了,但是没有一个是为生产考虑的,因此比较汗颜,今天好好地把kafka的安装以及配置梳理一下: 1,kafka版本选取: 现在我 ...

  4. hdu4779 组合计数+dp

    提交 题意:给了n*m的网格,然后有p个重型的防御塔,能承受1次攻击,q个轻型防御塔不能接受任何攻击,然后每个防御搭会攻击他所在的行和所在的列,最后求在这个网格上放至少一个防御塔的方案数, 我们枚举 ...

  5. 基于PLC1850平台的ICMP包请求与响应

    一.以太网IP包报文格式 IP包是连接在以太网首部(以太网目的MAC地址(6个字节)+以太网源MAC地址(6个字节)+帧类型(2个字节))之后. IP报文中各个字段分析如下: ①.版本:在IP报文中, ...

  6. mysql,jdbc、连接池

    show processlist; select * from information_schema.processlist; Command: The type of command the thr ...

  7. Goland开发工具安装教程

    开发工具: 文本类的编辑器:记事本,notepad,sublime text,atom... 通过命令执行程序 IED:集成开发环境(integrated development environmen ...

  8. 基于zigbee协议的空中下载技术(OTA)

    首先镜像服务器的解释: 镜像服务器(Mirror server)与主服务器的服务内容都是一样的,只是放在一个不同的地方,分担主机的负载. 简单来说就是和照镜子似的,能看,但不是原版的.在网上内容完全相 ...

  9. es7,es8

    ES7新特性 ES7在ES6的基础上添加了三项内容:求幂运算符(**).Array.prototype.includes()方法.函数作用域中严格模式的变更. Array.prototype.incl ...

  10. 使用GoldenGate EVENTACTIONS执行数据的实时触发和定制化

    Oracle GoldenGate不仅可以在线实时同步数据(包括增量和存量),也内置有一套事件触发流程,允许用户根据某张表某条记录的某个特殊字段值,触发相应的自定义执行流程,比如接收到某个银行账号的大 ...