ZJOI 2019 题目选做
正在持续更新咕咕咕咕中。
LOJ3042「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\),它们都是对子,且对应的大小两两不同。
可怜先摸出了 \(13\) 张牌,并把剩下的 \(4n − 13\) 张牌随机打乱。打乱是等概率随机的,即所有 \((4n − 13)!\) 种排列都等概率出现。
对于一个排列 \(P\),可怜定义 \(S_i\) 为可怜事先摸出的 \(13\) 张牌加上 \(P\) 中的前 \(i\) 张牌构成的集合,定义 \(P\) 的权值为最小的 \(i\) 满足 \(S_i\) 存在一个子集是胡的。如果你对麻将比较熟悉,不难发现 \(P\) 的权值就是理论上的最早胡牌巡目数。注意到 \(n \ge 5\) 的时候,\(S_{4n−13}\) 总是存在胡的子集的,因此 \(P\) 的权值是良定义的。
现在可怜想要训练自己的牌效,因此她希望你能先计算出 \(P\) 的权值的期望是多少。
\(n \le 100\)。
动态规划
考虑怎么判定一副牌是胡的,我们发现,将牌划分成为对子这个是比较简单判断的,所以重要的是涉及面子的判断。
假设给了我们一个牌的个数的数组,那么我们从小到大去考虑每一个元素,假设考虑到了大小为 \(x\) 的牌,可以设 \(f(i, j, k)\) 表示当前是否有对子(\(i = 0 / 1\)),并且有 \(j\) 个 \((x - 1, x)\) 的这种牌强行配对,以及还剩下 \(k\) 个 \(x\) 牌的最多面子数。
转移的时候,枚举 \(d\) 表示 \(x +1\) 剩下几张牌,然后就可以知道 \((x - 1, x, x +1)\) 构成的面子数以及新的 \((x, x + 1)\) 的对数。
注意到 \(j, k \le 2\)(如果多了,可以之间单凑面子),于是可以把 \(f_i\) 当做一个 \(3\times 3\) 的矩阵,接着顺便维护一下目前的对子数,就利用 DP 的转移边建出了一个“胡牌自动机”了。
可以通过搜索发现状态数为 \(2092\),然后设 \(g(i, j, k)\) 表示现在考虑到了第 \(i\) 张牌,总共摸了 \(j\) 张牌,然后当前在胡牌自动机的第 \(k\) 个节点,每次转移的时候只要不走胡牌的点即可。
最后设 \(h_j\) 表示摸了 \(j\) 张牌,不胡的方案,那么答案就是:
LOJ3043「ZJOI2019」线段树
九条可怜是一个喜欢数据结构的女孩子,在常见的数据结构中,可怜最喜欢的就是线段树。
线段树的核心是懒标记,下面是一个带懒标记的线段树的伪代码,其中
tag
数组为懒标记:其中函数 \(\texttt{Lson}(\text{Node})\) 表示 \(\text{Node}\) 的左儿子,\(\texttt{Rson}(\text{Node})\) 表示 \(\text{Node}\) 的右儿子。
现在可怜手上有一棵 \([1, n]\) 上的线段树,编号为 \(1\)。这棵线段树上的所有节点的tag
均为 \(0\)。接下来可怜进行了 \(m\) 次操作,操作有两种:
- \(1\ l\ r\),假设可怜当前手上有 \(t\) 棵线段树,可怜会把每棵线段树复制两份(
tag
数组也一起复制),原先编号为 \(i\) 的线段树复制得到的两棵编号为 \(2i − 1\) 与 \(2i\),在复制结束后,可怜手上一共有 \(2t\) 棵线段树。接着,可怜会对所有编号为奇数的线段树进行一次 \(\texttt{Modify}(\text{root}, 1, n, l, r)\)。- \(2\),可怜定义一棵线段树的权值为它上面有多少个节点
tag
为 \(1\)。可怜想要知道她手上所有线段树的权值和是多少。\(n, m \le 10^5\)。
数据结构
动态规划
没怎么看题解,按照 [ZJOI2020] 传统艺能 的方式推了推,然后感觉写得比较复杂/kk。
感觉这两个题目几乎一样啊。
还是将节点分成 \(5\) 类,然后分类讨论,在线段树上每个节点维护 \((a, b, c, d)\) 表示 (都没有,存在祖先有标记,仅自己有标记,存在祖先有标记并且自己有标记),每次修改暴力更新,然后发现只有对于 父亲被包含 的点,暴力更新复杂度不对,然后根据其转移形式打一个标记即可。
感觉比其他题解稍微复杂,但是自我感觉看得最顺眼。
LOJ3044「ZJOI2019」Minimax 搜索
可怜有一棵有根树,根节点编号为 \(1\)。定义根节点的深度为 \(1\),其他节点的深度为它的父亲的深度加一。同时在叶子节点权值给定的情况下,可怜用如下方式定义了每一个非节点的权值:
- 对于深度为奇数的非叶子节点,它的权值是它所有子节点的权值最大值。
- 对于深度为偶数的非叶子节点,它的权值是它所有子节点的权值最小值。
最开始,可怜令编号为 \(i\) 的叶子节点权值为 \(i\),并计算得到了根节点的权值为 \(W\)。
现在,邪恶的 Cedyks 想要通过修改某些叶子节点的权值,来让根节点的权值发生改变。Cedyks 设计了一个量子攻击器,在攻击器发动后,Cedyks 会随机获得一个非空的叶子节点集合 \(S\) 的控制权,并可以花费一定的能量来修改 \(S\) 中的叶子节点的权值。
然而,修改叶子节点的权值也要消耗能量,对于 \(S\) 中的叶子节点 \(i\),它的初始权值为 \(i\),假设 Cedyks 把它的权值修改成了 \(w_i\)(\(w_i\) 可以是任意整数,包括负数),则 Cedyks 在这次攻击中,需要花费的能量为 \(\max_{i\in S} |i − w_i|\)。
Cedyks 想要尽可能节约能量,于是他总是会以最少的能量来完成攻击,即在花费的能量最小的情况下,让根节点的权值发生改变。令 \(w(S)\) 为 Cedyks 在获得了集合 \(S\) 的控制权后,会花费的能量。特殊地,对于某些集合 \(S\),可能无论如何改变 \(S\) 中叶子节点的权值,根节点的权值都不会发生改变,这时,\(w(S)\) 的值被定义为 \(n\)。为了方便,我们称 \(w(S)\) 为 \(S\) 的稳定度。
当有 \(m\) 个叶子节点的时候,一共有 \(2^m − 1\) 种不同的叶子节点的非空集合。在发动攻击前,Cedyks 想要先预估一下自己需要花费的能量。于是他给出了一个区间 \([L, R]\),他想要知道对于每一个 \(k \in [L, R]\),有多少个集合 \(S\) 满足 \(w(S) = k\)。
对于 \(100\%\) 的数据,保证 \(n \ge 2, 1 \le L \le R \le n\)。
数据结构
线段树
线段树合并
动态 DP
动态规划
找到了一个更加好写的写法!如果想看动态 DP 的做法的话可以出门右转:https://www.cnblogs.com/C202044zxy/p/15957137.html
首先,我们将权值等于 \(W\) 的点提出来,那么就构成了一条链,我们称它为主链。
显然,主链上面的每一个点的权值改变,都会影响根的权值。
题目要求的是 \(= K\) 的答案,我们可以考虑求 \(\le K\) 的答案,然后差分即可求出最后答案。
现在考虑我们如果固定 \(K\),应该怎么做。也就是有多少个集合的修改代价不超过 \(K\)。
将主链上的边全部断开,分别考虑每一个子树。
我们可以设 \(f_x\) 表示在代价不超过 \(K\) 的情况下,任意改变 \(x\) 子树的叶子的权值,根本不能改变 \(W\) 的权值的概率。
最后答案就是 \(2 ^ {ret} (1 - \prod_{x \in 主链} f_x)\),其中 \(ret\) 表示叶子个数。
一些理解:
- 设置概率的好处:可以转移的时候直接乘上 \(1\),而不是还有一个 \(2^{子树叶子个数}\),比较方便。(虽然没有简化太多,只是略微简洁)
- 根本不可能改变的意思:
比如,断开后的子树,如果根 \(x\) 的节点深度为奇数,那么儿子 \(y\) 的 DP 值就是这个点的权值 \(<W\) 的概率(对于取 \(\max\) 根本不会有任何影响)。
容易发现,这个状态就是意味着对于深度为奇数的点,就是它权值 \(>W\) 的概率,深度为偶数相反。
那么有转移:
对于叶子里的 DP 的初值可以看代码(整个代码唯一一处注释)。
然后考虑对于所有 \(K\) 求答案,我们注意到每个叶子只会改变依次,然后相当于对于一条链进行操作,这个可以使用动态 DP 维护,然后有点细节,这里不讲,建议出门右转。
讲一个好写的写法,因为叶子 DP 初值只有两段,我们可以用线段树维护对于每个 \(K\) 的答案(维护乘法标记和加法标记),然后对于叶子可以快速打标记赋值,合并子树的时候可以直接线段树合并。