这是我第一次写$dp \; of \; dp$,大致思路参考了xyx的做法,可能有些地方不太一样,但也许会详细一点。

考虑给你一副牌,如何判断这副牌是否是胡的。

容易发现相同的顺子不会选三个以上,于是考虑花色从小到大进行$dp$。令$dp_{0/1, i, j, k}$表示是否有对子,考虑了前$i$种花色的牌,以花色$i - 1$的牌为开头的顺子准备选$j$个,以花色$i$的牌为开头的顺子准备选$k$个,此时能选的最大的面子数。转移只要枚举以花色$i + 1$的牌开头的顺子准备选几个,剩下的牌组成刻子就行了(具体细节可以看代码中结构体$State$中的$Trans$函数)。

判定胡牌的条件有两种:$\exists (j,k), dp_{1, n, j, k} \ge 4$;记$cnt$为牌数$\ge 2$的花色数,$cnt \ge 7$。

可以发现这个$dp$和状态$i$这一维关系不是很大,$dp_{0}$和$dp_{1}$的转移方式也都是一样的。只考虑有$dp_{*,j,k}$这个$3 \times 3$的矩阵,其转移本质上是接收一个数字(表示下一种花色的牌的数量),然后变成了另一个$3 \times 3$的矩阵,我们可以把它看成一个自动机的模型。事实上这样的$dp$状态($3 \times 3$的矩阵)并不很多。我们用结构体$State$来形容这个矩阵。为了表示整个当前胡牌状态,我们需要两个$State$分别表示有没有选对子(即$dp_0$和$dp_1$),以及$cnt$。我们把这三个东西放到结构体$Mahjong$中,我们很容易可以得到$Mahjong$的$Trans$关系。经搜索$Mahjong$的状态数总共有$3956$个。

回过来看我们要算的答案:$ans = \sum\limits_{a = 13}^{4n} p(a)$,其中$p(a)$表示你总共摸了$a$张牌之后仍然不能胡的概率。于是我们只要算摸了$a$张牌仍然不能胡的排列数即可。令$f_{i, j, k}$表示我们考虑了前$i$种花色的牌,当前胡牌状态为$j$($j$是一个$Mahjong$类),已经摸了$k$张牌的排列数有多少。转移枚举摸了花色$i + 1$的牌数$z$,则由$f_{i, j, k}$转移到$f_{i + 1, trans(j,z), k + z}$,乘上的系数就是$(4 - org_{i + 1})^{\underline{z - org{i + 1}}} \binom{k + z - sum_{i + 1}}{z - org_{i + 1}}$。$org_i$表示原有的$13$张牌中花色为$i$的有几张,$sum$则是$org$的前缀和,这个式子的意思就是我们需要在没被选过的$4 - org_{i + 1}$张牌中选$z - org_{i + 1}$张的排列,并且插入到之前的排列中,但前$13$张牌的顺序是固定的。

最后$p(a)$是很好算的,$p(a) = \frac{\sum\limits_{j \; can \; not \; win } f_{n, j, a}}{ (4n - 13)^{\underline{a - 13}} }$。

复杂度为$O(3956 * n^2)$。

#include <bits/stdc++.h>

using namespace std;

const int N = 1e2 + ;
const int M = 4e3 + ;
const int MOD = ; namespace {
int ch[M][], fac[];
int Add(int a, int b) { return (a += b) >= MOD? a - MOD : a; }
void Upd(int &a, int b) { a = Add(a, b); }
int Mul(int a, int b) { return (long long)a * b % MOD; }
int Inv(int x) { return (x == )? : Mul(MOD - MOD / x, Inv(MOD % x)); }
void Math_init() {
for (int i = ; i < M; ++i) {
ch[i][] = ;
for (int j = ; j <= min(i, ); ++j) {
ch[i][j] = Add(ch[i - ][j - ], ch[i - ][j]);
}
}
fac[] = ;
for (int i = ; i < ; ++i) {
fac[i] = Mul(fac[i - ], i);
}
}
} bool Chkmax(int &a, int b) {
return (a < b)? (a = b, ) : ();
} struct State {
int dp[][]; // (last last bar, last bar)
State() {
memset(dp, -, sizeof dp);
} friend bool operator < (State a, State b) {
for (int i = ; i < ; ++i)
for (int j = ; j < ; ++j)
if (a.dp[i][j] != b.dp[i][j])
return a.dp[i][j] < b.dp[i][j];
return ;
} friend State Max(State a, State b) {
for (int i = ; i < ; ++i)
for (int j = ; j < ; ++j)
a.dp[i][j] = max(a.dp[i][j], b.dp[i][j]);
return a;
} friend State Trans(State a, int b) {
State c;
for (int i = ; i < ; ++i)
for (int j = ; j < ; ++j)
if (~a.dp[i][j])
for (int k = ; k < && i + j + k <= b; ++k)
Chkmax(c.dp[j][k], min(i + a.dp[i][j] + (b - i - j - k) / , ));
return c;
} }; struct Mahjong {
pair<State, State> god;
int cnt;
Mahjong() {
memset(god.first.dp, -, sizeof god.first.dp);
memset(god.second.dp, -, sizeof god.second.dp);
god.first.dp[][] = cnt = ;
} friend bool operator < (Mahjong a, Mahjong b) {
return a.cnt != b.cnt? a.cnt < b.cnt : a.god < b.god;
} friend Mahjong Trans(Mahjong a, int b) {
a.cnt = min(a.cnt + (b >= ), );
a.god.second = Trans(a.god.second, b);
if (b >= ) {
a.god.second = Max(a.god.second, Trans(a.god.first, b - ));
}
a.god.first = Trans(a.god.first, b);
return a;
} bool right() {
if (cnt == ) return ;
for (int i = ; i < ; ++i)
for (int j = ; j < ; ++j)
if (god.second.dp[i][j] == ) return ;
return ;
} } mahjong[M]; int n, tot;
map<Mahjong, int> idx;
bool win[M];
int org[N], f[N][M][ * N], trans[M][]; void Dfs_mahjong(Mahjong now) {
if (idx.find(now) != idx.end()) return;
mahjong[++tot] = now;
win[tot] = now.right();
idx[now] = tot;
for (int i = ; i <= ; ++i) {
Dfs_mahjong(Trans(now, i));
}
} int main() {
Math_init();
Dfs_mahjong(Mahjong()); for (int i = ; i <= tot; ++i) {
for (int j = ; j <= ; ++j) {
trans[i][j] = idx[Trans(mahjong[i], j)];
}
} scanf("%d", &n);
for (int i = , x; i < ; ++i) {
scanf("%d%*d", &x);
++org[x];
} f[][][] = ;
for (int i = , cp = ; i < n; ++i) { // consider 1 ... i
cp += org[i + ];
for (int j = ; j <= tot; ++j) { // mahjong j
for (int l = org[i + ]; l <= ; ++l) { // trans
int *nf = f[i + ][trans[j][l]], *ff = f[i][j];
int tmp = Mul(ch[ - org[i + ]][l - org[i + ]], fac[l - org[i + ]]);
for (int k = ; k + l <= * n; ++k) { // have chosen k cards
if (!ff[k]) continue;
Upd(nf[k + l], Mul(ff[k], Mul(ch[k + l - cp][l - org[i + ]], tmp)));
}
}
}
} int ans = , dw = ;
for (int i = ; i <= * n; ++i) {
int up = ;
for (int j = ; j <= tot; ++j) {
if (!win[j]) Upd(up, f[n][j][i]);
}
Upd(ans, Mul(up, Inv(dw)));
dw = Mul(dw, * n - i);
}
printf("%d\n", ans); return ;
}

【ZJOI 2019】麻将(dp of dp)的更多相关文章

  1. [ZJOI2006]超级麻将(可行性dp)

    题目描述 要判断某人是否胡牌,显然一个弱智的算法就行了,某中学信息学小组超级麻将迷想了想,决定将普通麻将改造成超级麻将. 所谓超级麻将没有了砣.索.万的区分,每种牌上的数字可以是1~100,而每种数字 ...

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

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

  3. DP套DP HDOJ 4899 Hero meet devil(国王的子民的DNA)

    题目链接 题意: 给n长度的S串,对于0<=i<=|S|,有多少个长度为m的T串,使得LCS(S,T) = i. 思路: 理解的不是很透彻,先占个坑. #include <bits/ ...

  4. LightOJ1044 Palindrome Partitioning(区间DP+线性DP)

    问题问的是最少可以把一个字符串分成几段,使每段都是回文串. 一开始想直接区间DP,dp[i][j]表示子串[i,j]的答案,不过字符串长度1000,100W个状态,一个状态从多个状态转移来的,转移的时 ...

  5. 377. Combination Sum IV——DP本质:针对结果的迭代,dp[ans] <= dp[ans-i] & dp[i] 找三者关系 思考问题的维度+1,除了数据集迭代还有考虑结果

    Given an integer array with all positive numbers and no duplicates, find the number of possible comb ...

  6. [DP]数位DP总结

     数位DP总结 By Wine93 2013.7 1.学习链接 [数位DP] Step by Step   http://blog.csdn.net/dslovemz/article/details/ ...

  7. HDU4960Another OCD Patient(间隙dp,后座DP)

    Another OCD Patient Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 131072/131072 K (Java/Ot ...

  8. [CF697D]Puzzles 树形dp/期望dp

    Problem Puzzles 题目大意 给一棵树,dfs时随机等概率选择走子树,求期望时间戳. Solution 一个非常简单的树形dp?期望dp.推导出来转移式就非常简单了. 在经过分析以后,我们 ...

  9. 数位dp模板 [dp][数位dp]

    现在才想到要学数位dp,我是不是很弱 答案是肯定的 以一道自己瞎掰的题为模板 //题: //输入数字n //从0枚举到n,计算这n+1个数中含有两位数a的数的个数 //如12930含有两位数93 #i ...

随机推荐

  1. [Spark][Python]对HDFS 上的文件,采用绝对路径,来读取获得 RDD

    对HDFS 上的文件,采用绝对路径,来读取获得 RDD: In [102]: mydata=sc.textFile("file:/home/training/test.txt")1 ...

  2. retinex图像增强算法的研究

    图像增强方面我共研究了Retinex.暗通道去雾.ACE等算法.其实,它们都是共通的.甚至可以说,Retinex和暗通道去雾就是同一个算法的两个不同视角,而ACE算法又是将Retinex和灰度世界等白 ...

  3. JSP页面<%@ ...%>是什么意思?

    这表示是指令,主要用来提供整个JSP 网页相关的信息,并且用来设定JSP网页的相关属性,例如:网页的编码方式.语法.信息等.起始符号为: <%@终止符号为: %>目前有三种指令:page. ...

  4. linux下日志文件error监控报警脚本分享

    即对日志文件中的error进行监控,当日志文件中出现error关键字时,即可报警!(grep -i error 不区分大小写进行搜索"error"关键字,但是会将包含error大小 ...

  5. LVS+Keepalived 高可用环境部署记录(主主和主从模式)

    之前的文章介绍了LVS负载均衡-基础知识梳理, 下面记录下LVS+Keepalived高可用环境部署梳理(主主和主从模式)的操作流程: 一.LVS+Keepalived主从热备的高可用环境部署 1)环 ...

  6. Centos下堡垒机Jumpserver V3.0环境部署完整记录(1)-安装篇

    由于来源身份不明.越权操作.密码泄露.数据被窃.违规操作等因素都可能会使运营的业务系统面临严重威胁,一旦发生事故,如果不能快速定位事故原因,运维人员往往就会背黑锅.几种常见的运维人员背黑锅场景:1)由 ...

  7. Individual Project - Word_frequency

    0x00 预先准备和时间规划 1.因为要用到visual studio 2013,准备学习C#,预计一天时间能基本使用. 3.了解需求并设计基本数据结构与大致流程 20min 2.根据提议实现simp ...

  8. 《Linux内核分析》第八周:进程的切换和系统的一般执行过程

    杨舒雯(原创作品转载请注明出处) <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 实验目的: 使用gdb ...

  9. 结对项目 https://github.com/quchengyu/jiedui/tree/quchengyu-patch-1

    所选项目名称:文本替换      结对人:傅艺伟 github地址 : https://github.com/quchengyu/jiedui/tree/quchengyu-patch-1 用一个新字 ...

  10. my项目的总结2015.8.26编

    这已经是上上个星期的事了,现在回顾一下: 负责的模块是"my",更精准的说应该是my里面的个人信息管理 由于项目分域,模块已经分好了,涉及到的只是在现有的基础上解决分域后遗留的历史 ...