Loj #3042. 「ZJOI2019」麻将

题目描述

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

今天,可怜想要打麻将,但是她的朋友们都去下自走棋了,因此可怜只能自己一个人打。可怜找了一套特殊的麻将,它有 \(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\) 的权值的期望是多少。

\(\\\)

神仙的\(DP\)套\(DP\)题。

首先想如何判断一个局面是胡的。对于第一种胡牌方式,我们用\(DP\)来维护。设\(f_{i,j,k,0/1}\)表示是考虑了前\(i\)种麻将,以\(i-1\)开头的面子有\(j\)个,以\(i\)开头的面子有\(k\)个,是否有对子的最大面子数。注意这里以\(i-1\)开头以及以\(i\)开头的面子都是还没有生效的,要放了大小为\(i+1\)的麻将后才能判断。转移的时候就枚举第\(i\)张牌放入了\(x\)个,枚举几种情况就好了。

对于第二种胡牌方式,直接记\(cnt\)表示不同的顺子个数就行了。

显然\(cnt\)不能达到\(7\),\(f_{i,j,k,1}\)的最大值不能达到\(4\)。于是我们就暴力将\(cnt\)以及\(f\)数组作为状态。如果\(f\)超过\(4\),就存成\(4\),\(cnt\)同理,以减小状态量。可以算出合法状态最多\(2091\)个。

然后就是\(DP\)计数了。设\(F_{i,j,S}\)表示考虑了前\(i\)种麻将,一共摸了\(j\)张牌,\(DP\)状态为\(S\)时还未胡牌的概率。转移的时候枚举额外摸了多少张\(i\)麻将(除去底牌)。

我们的定义是还未胡牌而不是已经胡牌的原因是我们枚举的麻将顺序是编号,不是模到这张牌的时间,所以对于一个胡牌的局面,我们不知道他到底是什么时候胡的。

利用一个经典的期望转概率的公式就可以算出答案了:

\[E=\sum i*P(x=i)\\
=\sum P(x\geq i)
\]

代码:

#include<bits/stdc++.h>
#define ll long long
#define N 105
#define M 405 using namespace std;
inline int Get() {int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}while('0'<=ch&&ch<='9') {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}return x*f;} const ll mod=998244353;
ll ksm(ll t,ll x) {
ll ans=1;
for(;x;x>>=1,t=t*t%mod)
if(x&1) ans=ans*t%mod;
return ans;
}
ll fac[M],ifac[M];
ll C(int n,int m) {return fac[n]*ifac[m]%mod*ifac[n-m]%mod;}
struct info {
int f[3][3][2];
int cnt;
info() {memset(f,-1,sizeof(f));f[0][0][0]=cnt=0;}
bool operator <(const info &a)const {
for(int i=0;i<3;i++)
for(int j=0;j<3;j++)
for(int k=0;k<2;k++)
if(f[i][j][k]!=a.f[i][j][k]) return f[i][j][k]<a.f[i][j][k];
return cnt<a.cnt;
}
bool chk() {
if(cnt>=7) return 0;
for(int i=0;i<3;i++)
for(int j=0;j<3;j++)
if(f[i][j][1]>=4) return 0;
return 1;
}
info trans(int x) {
info tem;
tem.cnt=cnt+(x>=2);
for(int i=0;i<3;i++) {
for(int j=0;j<3;j++) {
for(int k=0;k<2;k++) {
if(f[i][j][k]==-1) continue ;
for(int t=0;t<3&&i+j+t<=x;t++) {
tem.f[j][t][k]=max(tem.f[j][t][k],f[i][j][k]+i+(x-i-j-t>=3));
}
if(!k) {
for(int t=0;t<3&&i+j+t<=x-2;t++) {
tem.f[j][t][1]=max(tem.f[j][t][1],f[i][j][k]+i);
}
}
}
}
}
for(int k=0;k<2;k++)
for(int i=0;i<3;i++)
for(int j=0;j<3;j++)
tem.f[i][j][k]=min(tem.f[i][j][k],4);
return tem;
}
}; int n;
map<info,int>id;
info mj[4005];
int tot;
int Had[N];
info tem;
int trans[4005][5];
void dfs(info now) {
if(!now.chk()) return ;
id[now]=++tot;
mj[tot]=now;
for(int i=0;i<=4;i++) {
info tem=now.trans(i);
if(id.find(tem)==id.end()) dfs(tem);
trans[id[now]][i]=id[tem];
}
} ll f[N][M][2100];
int main() {
fac[0]=1;
for(int i=1;i<=400;i++) fac[i]=fac[i-1]*i%mod;
ifac[400]=ksm(fac[400],mod-2);
for(int i=399;i>=0;i--) ifac[i]=ifac[i+1]*(i+1)%mod;
info x;
dfs(x);
n=Get(); for(int i=1;i<=13;i++) {
int w=Get(),t=Get();
Had[w]++;
} f[0][0][1]=1;
for(int i=0;i<n;i++) {
for(int j=0;j<=i*4;j++) {
for(int k=1;k<=tot;k++) {
if(!f[i][j][k]) continue ;
for(int t=Had[i+1];t<=4;t++) {
if(trans[k][t]) (f[i+1][j+t-Had[i+1]][trans[k][t]]+=f[i][j][k]*C(4-Had[i+1],t-Had[i+1])%mod*C(j+t-Had[i+1],t-Had[i+1])%mod*fac[t-Had[i+1]])%=mod;
}
}
}
} ll ans=0;
for(int i=0;i<=4*n-13;i++) {
for(int j=1;j<=tot;j++) {
(ans+=f[n][i][j]*ifac[4*n-13]%mod*fac[4*n-13-i])%=mod;
}
}
cout<<ans; return 0;
}

Loj #3042. 「ZJOI2019」麻将的更多相关文章

  1. 【LOJ】#3042. 「ZJOI2019」麻将

    LOJ#3042. 「ZJOI2019」麻将 如何判定一个集合牌有没有胡的子集是不是胡的 就用一个\(dp[j][k][0/1]\)表示有j个连续两个的串,有k个连续1个串,有没有对子,再记一下这个集 ...

  2. Loj #3044. 「ZJOI2019」Minimax 搜索

    Loj #3044. 「ZJOI2019」Minimax 搜索 题目描述 九条可怜是一个喜欢玩游戏的女孩子.为了增强自己的游戏水平,她想要用理论的武器武装自己.这道题和著名的 Minimax 搜索有关 ...

  3. Loj #3045. 「ZJOI2019」开关

    Loj #3045. 「ZJOI2019」开关 题目描述 九条可怜是一个贪玩的女孩子. 这天,她和她的好朋友法海哥哥去玩密室逃脱.在他们面前的是 \(n\) 个开关,开始每个开关都是关闭的状态.要通过 ...

  4. 【线段树 树链剖分 差分 经典技巧】loj#3046. 「ZJOI2019」语言【未完】

    还是来致敬一下那过往吧 题目分析 先丢代码 #include<bits/stdc++.h> ; ; ; struct node { int top,son,fa,tot; }a[maxn] ...

  5. 「ZJOI2019」麻将

    传送门 Solution  对于条件一:记录一个\(cnt\)表示牌个数\(≥2\)的个数 设\(dp_{i,0/1,j,k}\)表示考虑了\(1...i\),当前是否有对子,以\(i-1\),\(i ...

  6. @loj - 3043@「ZJOI2019」线段树

    目录 @description@ @solution@ @accepted code@ @details@ @description@ 九条可怜是一个喜欢数据结构的女孩子,在常见的数据结构中,可怜最喜 ...

  7. @loj - 3046@「ZJOI2019」语言

    目录 @description@ @solution@ @accepted code@ @details@ @description@ 九条可怜是一个喜欢规律的女孩子.按照规律,第二题应该是一道和数据 ...

  8. 【LOJ】#3046. 「ZJOI2019」语言

    LOJ#3046. 「ZJOI2019」语言 先orz zsy吧 有一个\(n\log^3n\)的做法是把树链剖分后,形成logn个区间,这些区间两两搭配可以获得一个矩形,求矩形面积并 然后就是对于一 ...

  9. 【LOJ】#3044. 「ZJOI2019」Minimax 搜索

    LOJ#3044. 「ZJOI2019」Minimax 搜索 一个菜鸡的50pts暴力 设\(dp[u][j]\)表示\(u\)用\(j\)次操作能使得\(u\)的大小改变的方案数 设每个点的初始答案 ...

随机推荐

  1. 百度编辑器UEditor,保存图片的配置问题

    前言: 在使用百度编辑器UEditor的时候,如何将图片保存到服务器,我刚开始以为是要自己写上传文件的方法,后来发现只需要配置一下即可,如果你也正在使用百度富文本编辑器UEditor的话,这篇文章将非 ...

  2. 如何利用 VisualStudio2019 遠端工具進行偵錯

    Hi 這次要來介紹 如何使用 Visual Studio 2019 遠端工具進行 Release 應用程式偵錯 首先我們先下載 2019 專用的遠端工具(這裡依照不同的 VisualStudio 版本 ...

  3. 基于log4net的日志组件扩展封装,实现自动记录交互日志 XYH.Log4Net.Extend(微服务监控)

    背景: 随着公司的项目不断的完善,功能越来越复杂,服务也越来越多(微服务),公司迫切需要对整个系统的每一个程序的运行情况进行监控,并且能够实现对自动记录不同服务间的程序调用的交互日志,以及通一个服务或 ...

  4. 解决eclipse打开文件乱码

    解决办法 需要设置的几处地方为: Window->Preferences->General ->Content Type->Text->JSP 最下面设置为UTF-8 W ...

  5. IntelliJ IDEA 中使用 Lambok (注解无效问题的解决)

    一,概述 Lambok可以说一个能很大提高开发效率的插件,只要在使用注解的方式就能实现很多常用的功能.如常用的@Data能在编译阶段自动生成toString方法,getter方法setter方法等. ...

  6. ANDROID培训准备资料之四大组件的简单介绍

    Android四大组件是一个android app 最基本的组成部分,这篇博客主要给大家简单的介绍一下四种组件 (1)Activities (2)Services (3)BroadcastReceiv ...

  7. python学习3-python views.py的返回值

    2.首先要说明一点是,对于HttpRequest对象来说,是Django自己创建的,但是HttpResponse就必须要我们自己创建.注意每个view方法都必须返回一个HttpResponse对象,H ...

  8. 获得用户的真实ip HTTP_X_FORWARDED_FOR

    工作中经常会有有获得用户真实ip的情况,HTTP_X_FORWARDED_FOR总是忘记,所以我这里记录下来吧. 在PHP 中使用 [“REMOTE_ADDR”] 来取得客户端的 IP 地址,但如果客 ...

  9. Python中创建虚拟环境(virtualenv模块)

    针对环境:win7,python2.7 需要virtualenv模块1.测试是否已安装virtualenv模块    import virtualenv  #没有报错则已安装    否则需要先安装vi ...

  10. linux 进程通信之 信号

    一,管道PIPE 二,FIFO通信 三,mmap通信 四,信号的概念 信号的特点:简单,但不能携带大量的信息,满足特定条件就会发生 信号的机制:进程B发送信号给进程A.信号是由内核来处理的. 信号的产 ...