这题非常的神啊。。。蒟蒻来写一篇题解。

Solution

首先考虑如何判定一副牌是否是 "胡" 的。

不要想着统计个几个值 \(O(1)\) 算,可以考虑复杂度大一点的。

首先先把 \(7\) 个对子的状态判掉。然后考虑 \(4\) 个面子和 \(1\) 个对子的情况。

记录一个 \(dp_{i, j,k}\) : \(i\) 表示现在有没有留出对子,\(j\) 表示现在形如 \(i, i - 1\) 的牌的多余的个数, \(k\) 表示现在形如 \(i\) 的牌对的个数,整个状态表示现在的面子数量。

使用 dp 套 dp, 对于这个 \(dp_{i, j, k}\) 分别是多少给压缩起来。这个状态以及转移可以 \(bfs\) 找。发现这个状态数只有 \(2092\) 个,所以可以通过此题。

Code

#include<bits/stdc++.h>
#define L(i, j, k) for(int i = j, i##E = k; i <= i##E; i++)
#define R(i, j, k) for(int i = j, i##E = k; i >= i##E; i--)
#define ll long long
#define ull unsigned long long
#define db double
#define pii pair<int, int>
#define mkp make_pair
using namespace std;
const int N = 405;
const int M = 2200;
const int mod = 998244353;
int qpow(int x, int y) {
if(x == 0) return 0;
int res = 1;
for(; y; x = 1ll * x * x % mod, y >>= 1) if(y & 1) res = 1ll * res * x % mod;
return res;
}
int ny(int x) { return qpow(x, mod - 2); }
int n, winid, ans;
int max(int a, int b) { return a > b ? a : b; }
void Max(int &a, int b) { a = max(a, b); }
struct DPAM {
int f[3][3];
void clear() { L(i, 0, 2) L(j, 0, 2) f[i][j] = -1; }
};
DPAM operator + (DPAM aa, int bb) {
DPAM res; res.clear();
L(i, 0, 2) L(j, 0, 2) if(aa.f[i][j] != -1)
L(k, 0, 2) if(bb >= i + j + k) Max(res.f[j][k], min(aa.f[i][j] + i + (bb - i - j - k) / 3, 4));
return res;
}
void FMAX(DPAM &aa, DPAM bb) {
L(i, 0, 2) L(j, 0, 2) Max(aa.f[i][j], bb.f[i][j]);
}
struct node {
int cnt; DPAM dp[2];
void clear() { cnt = 0; dp[0].clear(), dp[1].clear(); }
};
node win() { node res; res.clear(), res.cnt = 114514; return res; }
bool check(node aa) {
if(aa.cnt >= 7) return 1;
L(i, 0, 2) L(j, 0, 2) if(aa.dp[1].f[i][j] >= 4) return 1;
return 0;
}
bool operator < (node aa, node bb) {
L(t, 0, 1) L(i, 0, 2) L(j, 0, 2) if(aa.dp[t].f[i][j] ^ bb.dp[t].f[i][j]) return aa.dp[t].f[i][j] < bb.dp[t].f[i][j];
return aa.cnt < bb.cnt;
}
node operator + (node aa, int bb) {
if(aa.cnt == 114514) return aa;
node res;
res.clear(), res.cnt = aa.cnt + (bb >= 2);
FMAX(res.dp[0], aa.dp[0] + bb);
FMAX(res.dp[1], aa.dp[1] + bb);
if(bb >= 2) FMAX(res.dp[1], aa.dp[0] + (bb - 2));
if(check(res)) return win();
return res;
}
int tot, dp[2][N][M];
map<node, int> mp;
int G[M][5], jc[N], njc[N];
int C(int x, int y) { return 1ll * jc[x] * njc[x - y] % mod * njc[y] % mod; }
void bfs() {
queue<node> q;
node gg;
gg.clear(), gg.cnt = 0, gg.dp[0].f[0][0] = 0;
mp[gg] = ++tot, q.push(gg);
while(!q.empty()) {
node u = q.front(); int id = mp[u];
q.pop();
L(i, 0, 4) {
node v = u + i;
if(!mp.count(v)) {
G[id][i] = mp[v] = ++tot, q.push(v);
if(v.cnt == 114514) winid = tot;
}
else G[id][i] = mp[v];
}
}
}
int cnt[N];
void work() {
dp[0][0][1] = 1;
L(i, 1, n) {
int now = (i & 1);
memset(dp[now], 0, sizeof(dp[now]));
L(j, 1, tot) L(k, 0, (i - 1) * 4) L(t, 0, 4 - cnt[i])
(dp[now][k + t][G[j][t + cnt[i]]] += 1ll * dp[now ^ 1][k][j] * C(4 - cnt[i], t) % mod) %= mod;
}
L(i, 0, n * 4 - 13) L(j, 1, tot) if(j != winid) (ans += 1ll * ny(C(4 * n - 13, i)) * dp[n & 1][i][j] % mod) %= mod;
}
int mian() {
bfs();
scanf("%d", &n);
jc[0] = njc[0] = 1;
L(i, 1, n * 4) jc[i] = 1ll * jc[i - 1] * i % mod, njc[i] = ny(jc[i]);
L(i, 1, 13) {
int x, y;
scanf("%d%d", &x, &y), cnt[x]++;
}
work();
printf("%d\n", ans);
return 0;
}

题解 洛谷 P5279 【[ZJOI2019]麻将】的更多相关文章

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

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

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

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

  3. 洛谷P5279 [ZJOI2019]麻将

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

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

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

  5. 题解 洛谷P5018【对称二叉树】(noip2018T4)

    \(noip2018\) \(T4\)题解 其实呢,我是觉得这题比\(T3\)水到不知道哪里去了 毕竟我比较菜,不大会\(dp\) 好了开始讲正事 这题其实考察的其实就是选手对D(大)F(法)S(师) ...

  6. 题解 洛谷 P3396 【哈希冲突】(根号分治)

    根号分治 前言 本题是一道讲解根号分治思想的论文题(然鹅我并没有找到论文),正 如论文中所说,根号算法--不仅是分块,根号分治利用的思想和分块像 似却又不同,某一篇洛谷日报中说过,分块算法实质上是一种 ...

  7. 题解-洛谷P5410 【模板】扩展 KMP(Z 函数)

    题面 洛谷P5410 [模板]扩展 KMP(Z 函数) 给定两个字符串 \(a,b\),要求出两个数组:\(b\) 的 \(z\) 函数数组 \(z\).\(b\) 与 \(a\) 的每一个后缀的 L ...

  8. 题解-洛谷P4229 某位歌姬的故事

    题面 洛谷P4229 某位歌姬的故事 \(T\) 组测试数据.有 \(n\) 个音节,每个音节 \(h_i\in[1,A]\),还有 \(m\) 个限制 \((l_i,r_i,g_i)\) 表示 \( ...

  9. 题解-洛谷P4724 【模板】三维凸包

    洛谷P4724 [模板]三维凸包 给出空间中 \(n\) 个点 \(p_i\),求凸包表面积. 数据范围:\(1\le n\le 2000\). 这篇题解因为是世界上最逊的人写的,所以也会有求凸包体积 ...

随机推荐

  1. malloc/free与new/delete的区别(转)

    相同点:都可用于申请动态内存和释放内存 不同点:(1)操作对象有所不同.malloc与free是C++/C 语言的标准库函数,new/delete 是C++的运算符.对于非内部数据类的对象而言,光用m ...

  2. 调用外部接口支持https请求

    1,创建RestTemplateConfig.java文件,内容如下: package com.htsec.monitor.internet.config;import com.htsec.monit ...

  3. 对App应用架构搭建的一些思考

    当下随着App开发技术的越来越成熟,多人协同开发必不可少,一个团队中每个人的代码风格.技术栈都存在差异,因此统一一套成熟的开发架构必不可少,可以提高开发效率.统一代码风格.为项目维护提供便利. 当下A ...

  4. FLEX弹性布局小结

    1. 弹性盒子中: flex: 0 1 auto表示什么意思 flex默认三个参数是flex-grow, flex-shrink, flex-basis,默认值是0 1 auto. flex-grow ...

  5. c++11-17 模板核心知识(八)—— enable_if<>与SFINAE

    引子 使用enable_if<>禁用模板 enable_if<>实例 使用Concepts简化enable_if<> SFINAE (Substitution Fa ...

  6. MindManager使用教程:如何导出HTML5交互式导图

    Mindmanager思维导图软件有着友好的用户界面以及丰富的思维导图制作功能.再搭配与Microsoft 软件的无缝集成功能,使得这款思维导图软件越来越受到职场人士的喜爱. 不仅是作为制作思维导图的 ...

  7. guitar pro系列教程(二十二):Guitar Pro在乐谱上的工作【二】

    我们在上一篇文章中给大家介绍了Guitar Pro的工作面板和音轨功能,今天我们将会给大家介绍Guitar Pro这款吉他谱学习软件得音频设置面板,在该面板中包含了声卡得选择.MIDI的输入输出及音轨 ...

  8. css3系列之属性选择器

    Attribute Selectors(属性选择器) E[attr ~="val"] E[attr |="val"] E[attr ^="val&qu ...

  9. Python多线程join和setDaemon区别与用法

    一直没有太搞清楚join和setDaemon有什么区别,总是对于它们两个的概念很模糊,需要做个实验然后记录一下. 先说结论: join: 子线程合并到主线程上来的作用,就是当主线程中有子线程join的 ...

  10. Linux三剑客grep、awk、sed

    何为Linux三剑客? 第一个剑客是 grep,grep 会根据正则表达式查找相关内容并打印对应的数据. 第二个剑客是 awk,awk 的名字来源于三个作者的名字简称,它可以根据定位到的数据行处理其中 ...