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\) 张牌,不胡的方案,那么答案就是:

\[1 + \sum_{i = 1}^{4n - 13} \frac{h_i}{\binom{4n - 13}{i}} \]

  代码

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. 设置概率的好处:可以转移的时候直接乘上 \(1\),而不是还有一个 \(2^{子树叶子个数}\),比较方便。(虽然没有简化太多,只是略微简洁)
  2. 根本不可能改变的意思:
    比如,断开后的子树,如果根 \(x\) 的节点深度为奇数,那么儿子 \(y\) 的 DP 值就是这个点的权值 \(<W\) 的概率(对于取 \(\max\) 根本不会有任何影响)。

  容易发现,这个状态就是意味着对于深度为奇数的点,就是它权值 \(>W\) 的概率,深度为偶数相反。

  那么有转移:

\[f_x = \prod_{y \in son_x} 1 - f_y \]

  对于叶子里的 DP 的初值可以看代码(整个代码唯一一处注释)。


  然后考虑对于所有 \(K\) 求答案,我们注意到每个叶子只会改变依次,然后相当于对于一条链进行操作,这个可以使用动态 DP 维护,然后有点细节,这里不讲,建议出门右转。

  讲一个好写的写法,因为叶子 DP 初值只有两段,我们可以用线段树维护对于每个 \(K\) 的答案(维护乘法标记和加法标记),然后对于叶子可以快速打标记赋值,合并子树的时候可以直接线段树合并。

  代码

posted @ 2022-03-04 22:31  Werner_Yin  阅读(78)  评论(1编辑  收藏  举报