A 密码

明文的每个字符向后位移 kk 得到密文,故只需将密文的每个英文字母向前位移 kk 即可得明文。假设某小写字符为 c,则 (c - 'a' - k + 26) % 26 + 'a' 即为所求。对于大写字符同理

B 面积

由于题目保证围墙围成的是一块凸面积,且除了边界处不存在多余围墙,因此我们可以分别考虑字符矩阵每一行对所求面积的贡献

具体来说,若某一行存在围墙,设第一个围墙出现在第 ll 列,最后一个出现在第 rr 列,则 rl+1r-l+1 即为这一行对总面积的贡献。将所有行贡献求和即得总面积

C 合成

由于无论采用哪种方式合并,最后剩余物品的情况都是相同的,因此我们只需要尽可能快地找到可合并的物品然后将他们不断合并即可

考虑当前所有物品中体积最小的那种物品,设它的体积为 aa,数量有 bb

如果 bb 是偶数,则令该物品自己两两合并,得到体积为 2a2a,数量 b/2b/2 个新的物品

如果 bb 是奇数,依然令该物品自己两两合并,得到体积为 2a2a,数量 (b1)/2(b-1)/2 个新的物品,同时余下一个体积为 aa 的物品。考虑余下的这个体积为 aa 的物品:由于当前不存在和其体积相同的物品,同时以后合成出的物品体积都必然大于 aa,因此该物品不可能再参与到以后的物品合并中。我们可以抛弃这个物品,同时令答案+1+1

不妨称上述「取出最小体积物品,然后两两合并」的过程为「一次操作」。只要不断重复操作直到无法操作,我们就统计出了答案

不断操作的过程容易用一个 map 来维护,单次操作的复杂度为 O(logn)O(\log n)

对于初始数量为 bb 的一组物品,它会被操作 O(logb)O(\log b)

因此设 bib_i 的最大值为 VV,则该算法总复杂度 O(nlogVlogn)O(n\log V\log n),可以通过此题

source: ABC323D

D 水管

考虑 dp。设 dpidp_i 表示将水源从 11 号楼楼顶运输至 ii 号楼楼顶所需的最小代价

枚举最后一根水管是从之前的哪栋楼出发连到 ii 号楼的,即得状态转移

$$dp_i = \min_{1\le jh_i\\ (h_i-h_j+B)\times\text{dis}(j,i) & h_j\le h_i \end{matrix}\right.$$

其中 dis(j,i)\text{dis}(j,i) 表示从 jjii 之间的距离。若设 sumisum_idid_i 的前缀和,则有 dis(j,i)=sumisumj\text{dis}(j,i)=sum_i-sum_j

最终取出 dpndp_n 即为答案

易知该算法空间复杂度 O(n)O(n),时间复杂度 O(n2)O(n^2)

E 真值表

solution1 处理中缀表达式

以下叙述中默认大写字母 A, B, C, ... 表示一个合法的表达式,用 * 表示一个二元运算符

首先,显然连续的一段 ! 可以等价为 0 个(一共偶数个)或 1 个(一共奇数个)!,我们可以扫描一遍原表达式,得到精简 ! 之后的表达式

接着考虑建立中缀表达式树

设当前要对区间 [l,r][l,r] 所表示的表达式建树,则该区间内的表达式可能有以下几种情况:

  1. (...)
  2. !(...)
  3. A * B * C * ... * K

对于第一种和第二种情况,我们直接去掉括号和 !,然后递归地计算内层

【补充】为了确认最前的左括号和最后的右括号是匹配关系,我们可以事先用栈扫描一遍原表达式得到 matchimatch_i,使得若 ii 位置是括号,则 matchimatch_i 为与其匹配的另一个括号的位置

对于第三种情况,我们找到中间这些二元运算符里最后被计算的那个,即优先级最低且位置最靠后的那个,以它为分界劈成左右两个区间递归建树

【补充】跳过合法表达式段的过程依然可以借助上文处理的 matchmatch 数组完成


下图是一个建树的例子,蓝色为运算符节点,橙色为变量节点

经过上述过程,我们得到一棵表达式树,树上每个非叶子节点有一个或两个儿子

此时枚举所有变量的取值,然后后序遍历表达式树即可求出表达式的值

设表达式长度 nn,其中变量个数为 tt,数据组数为 TT。则建树复杂度 O(n2)O(n^2),树有 O(n)O(n) 个节点,计算真值表的复杂度为 O(2tn)O(2^tn)。总复杂度 O(T(n2+2tn))O(T(n^2 + 2^t n)),可以通过此题

(bonus) solution2 中缀表达式转后缀表达式

观察如下两个中缀表达式与后缀表达式的对应关系

a & b | c  ->  a b & c |
a | b & c  ->  a b c & |

我们可以观察规律得出以下算法:

维护一个存放运算符的单调栈。扫描原表达式,当遇到一个变量的时候,直接将其加入到后缀表达式中;当遇到一个运算符时,查看单调栈,只要栈顶运算符优先级高于当前运算符,就不断弹出栈顶然后加入到后缀表达式中,最后将当前运算符入栈

扫描完成后,将栈内剩余的运算符依次弹出加入后缀表达式即可


当存在一元运算符时,我们令 ! 优先级最高,然后处理方式与其他二元运算符相同

! ! a | ! b & c -> a ! ! b ! c & |

当存在括号时,我们做如下处理:

  • 如果扫描到一个左括号,直接将其入栈而不考虑弹出栈内其他运算符,同时将左括号的优先级设为最低,这样这个左括号就不会被其他运算符弹出
  • 如果扫描到一个右括号,不断弹出栈顶加入后缀表达式,直到弹出了一个左括号为止

这样,括号就起到了 “分隔” 的作用,正确性易于理解


设表达式长度 nn,其中变量个数为 tt,数据组数为 TT

综上所述,只需扫描一遍原中缀表达式即可构造对应的后缀表达式,复杂度 O(n)O(n)

然后枚举所有变量的取值,利用后缀表达式计算结果,复杂度 O(2tn)O(2^tn)

总复杂度 O(2tnT)O(2^tnT)

【补充】该算法需要选手对后缀表达式相关内容有初步了解,故不作为题目要求复杂度

2 条评论

  • 1