Practice Round 2019
1. Number Guessing
- 水题,略过
2. Mural
- 基于如下事实:只要你想,你可以保住任何一段长为 ⌈ N / 2 ⌉ \lceil N/2 \rceil ⌈N/2⌉的墙,故只要求和最大的一段墙即可
3. Kickstart Alarm
- 本题的精髓是如何快速求 a 1 + a 2 + . . . + a n a^1+a^2+...+a^n a1+a2+...+an;类似于快速求幂法,只是要同时维护两个变量 a 1 + a 2 + . . . + a i a^1+a^2+...+a^i a1+a2+...+ai和 a i a^i ai
Round A 2019
1. Training
- 排序之后用窗口扫描即可;结果我用Go写第二问就超时,用C写就过了,可能是Go的IO效率低?
2. Parcels
- 本题涉及计算几何的经典问题:给定 N N N 个点,如何用 O ( N ) O(N) O(N) 时间求出其中最大的曼哈顿距离?(或给定 N N N 个点,预处理后,如何用 O ( 1 ) O(1) O(1) 时间求出新的一个点到其中最大的曼哈顿距离?)
- 上面的问题解决后,就可以用二分猜测法,每次用 O ( R C ) O(RC) O(RC) 时间扫描全盘来验证,总用时 O ( R C ⋅ l o g ( R + C ) ) O(RC\cdot log(R+C)) O(RC⋅log(R+C))
- 注意到若一个点可以实现最小距离 k k k,那么与它相邻的点可以实现的最小距离一定为 k − 1 k-1 k−1、 k k k、 k + 1 k+1 k+1 之一;故我们甚至没有必要进行二分猜测,只要全盘扫描一遍,记录找到的最小距离即可,总用时 O ( R C ) O(RC) O(RC)
3. Contention
- 基于如下事实:由于每次新覆盖的区域与之前的覆盖顺序无关,故为使每次新覆盖区域的最小值最大,必须使最后一次的区间新覆盖的区域尽可能大
- 这里倒着解答,每次取出新覆盖区域最大的区间,本人只看懂了每次用 O ( N ) O(N) O(N)扫一遍的方法,总用时 O ( N 2 ) O(N^2) O(N2)
Round B 2019
1. Building Palindromes
- 区间求和,判断数量为奇数的字母个数是否不超过1即可;由于不需要求每个字母的具体数量,所以可以用位运算异或来减少运算量
- 一看到区间求和我就上了树状数组,后来意识到这些字母并不会更新,所以可以用前缀和达到 O ( N ) O(N) O(N)的复杂度
2. Energy Stones
- 基于如下事实:若两块石头 i i i、 j j j 会被吃,且 S i L j < S j L i S_iL_j<S_jL_i SiLj<SjLi,则 i i i 一定先于 j j j 被吃;根据该事实进行排序,然后确定每块石头是否被吃即可
- 用动态规划,比较特别的是这是一个“我为人人”的动规,即用 m a x _ e n e r g y ( i , t ) max\_energy(i, t) max_energy(i,t) 更新 m a x _ e n e r g y ( i + 1 , t ) max\_energy(i+1, t) max_energy(i+1,t) 和 m a x _ e n e r g y ( i + 1 , t + t i m e [ i ] ) max\_energy(i+1, t+time[i]) max_energy(i+1,t+time[i]) 的最大值
3. Diverse Subarray
- 本题的精髓是使用差分的区间和来计算该区间上可以带的礼物的最大值,于是遍历所有左端点,求出右边区间的最大前缀和即可
- 这里用树状数组来做到单点更新和区间求最大前缀和,另外需要一个哈希链表来记录下一个被更新的点在哪里
Round C 2019
1. Wiggle Walk
- 一开始我也想到用某种办法一次性跨过之前走过的方块,奈何一下子想不出合适的数据结构,不得已看了解析,原来就是用STL的map存储区间
2. Circuit Board
- 如果熟练的话,本题应该能看出来这是直方图最大矩形的衍生问题;看出来之后从左到右用竖线做底扫一遍即可
- 做第2问的时候没注意
multiset
的erase
的语义,移除元素 x x x之一应该写ms.erase(ms.find(x))
,否则就是移除全部 x x x
3. Catch Some
- 先假设最后一次也要回家,设 m i n _ s t e p ( i , j ) min\_step(i, j) min_step(i,j)为使用前i种颜色访问j只狗所需的最小步数,则 m i n _ s t e p ( i , j ) = m i n ( m i n _ s t e p ( i − 1 , j − k ) + s u m ( i , k ) ) min\_step(i, j)=min(min\_step(i-1, j-k)+sum(i, k)) min_step(i,j)=min(min_step(i−1,j−k)+sum(i,k)),其中 s u m ( i , k ) sum(i,k) sum(i,k)是第i种颜色第k只狗的距离
- 为了使最后一次区别对待,需要用两个数组 m i n _ s t e p _ a l l _ h o m e d ( i , j ) min\_step\_all\_homed(i, j) min_step_all_homed(i,j)和 m i n _ s t e p _ o n c e _ u n h o m e d ( i , j ) min\_step\_once\_unhomed(i, j) min_step_once_unhomed(i,j),这样最小答案就在后者里
Round D 2019
1. X or What?
- 基于如下事实:合法区间必为 xor-even 数的个数为偶数的区间,而最大区间至少有一边在数组的端点,这样每次只需回答一个判断题,即取左端点还是右端点
2. Latest Guests
- 这题要是改成所有人都朝一个方向转就简单了,而考虑两个方向时只需要一个小技巧,就是在记录每户最后到访者时顺便记下时间,然后比较两个方向哪个更新即可
3. Food Stalls
- 这题和简单选点问题不同的是加上了距离代价,和上题有点相似的是,如果假设仓库必须建在所有商店的右边,那么就可以用绝对距离计算代价,然后用大小为 K K K的最大值堆从左到右扫一遍,留下的 K K K个点即是 K K K个商店
- 由直线上多点距离之和的性质得,仓库必须建在 K + 1 K+1 K+1个点的中点上,故先在左边建 ⌊ K / 2 ⌋ \lfloor K/2 \rfloor ⌊K/2⌋个商店,再在右边建 ⌈ K / 2 ⌉ \lceil K/2 \rceil ⌈K/2⌉个商店,最后求加在一起的最小值即可
- 有个坑就是
x
x
x和
c
c
c都特别大,要开
long long
才能解决,下次要注意看题
Round E 2019
1. Cherries Mesh
- 简单图问题,设用黑线连在一起的为一组,则答案为 N + G − 2 N+G-2 N+G−2,其中 N N N为总点数, G G G为总组数
2. Code-Eat Switcher
- 按 c o d e / e a t code/eat code/eat排序,大的放前面,每次优先将前面的 c o d e code code时间占满,然后在满足总 c o d e code code时间的情况下剩余的 e a t eat eat时间一定最大
- 这题在边界判断上卡了很久,最后自己造数据才发现问题,还有又踩了一次
long long
的坑
3. Street Checkers
- 先考虑每个单独的
X
X
X,设
X
X
X可以分解质因数为
X
=
2
n
⋅
a
1
m
1
⋅
a
2
m
2
⋅
.
.
.
X=2^n\cdot a_1^{m_1}\cdot a_2^{m_2}\cdot ...
X=2n⋅a1m1⋅a2m2⋅...,则
X
X
X符合条件等价于:
- n = 0 n=0 n=0且 X X X是质数(或 1 1 1)
- n = 1 n=1 n=1
- n = 2 n=2 n=2且 X / 4 X/4 X/4是质数(或 1 1 1)
- X = 8 X=8 X=8
- 然后就是怎么判断质数的问题了,由于 X ≤ 1 0 9 X\le 10^9 X≤109,故可以用 X X X除以 2 2 2到 1 0 9 \sqrt{10^9} 109之间的质数来判断,这之间有 3401 3401 3401个质数,虽然我觉得复杂度挺高但还是过了
Round F 2019
1. Flattening
- 这题属于容易做也容易错的题,方法很简单,就是用动态规划,从左到右扫一遍,并且动态更新 m i n _ r e b u i l d ( l a s t _ h e i g h t , c h a n g e s ) min\_rebuild(last\_height, changes) min_rebuild(last_height,changes)数组,复杂度 O ( N 3 ) O(N^3) O(N3)还是绰绰有余的
- 我一开始错就错在试图节约 l a s t _ h e i g h t last\_height last_height的初始数量,因为后面的高度可能会影响前面的高度,所以必须一开始就把所有高度加入状态集中
2. Teach Me
- 我一开始想的是计算 i i i不能教的人,但这样显然不能简化为子集查询的问题;应该是计算不能教 i i i的人,这样只要查询与 i i i的某个子集相同的人数
- 之所以能用子集查询是因为每个人最多有
5
5
5个技能,且总技能数不超过
1000
1000
1000,所以可以用
long long
作为任意子集的key
3. Spectating Villages
- 我想的和答案一样,都是看成树然后动态规划,但我觉得我的简单些(答案的动态规划套了娃)
- 维护三个最大值 l i g h t e d _ b y _ i t s e l f lighted\_by\_itself lighted_by_itself、 l i g h t e d _ b y _ c h i l d r e n lighted\_by\_children lighted_by_children和 u n l i g h t e d unlighted unlighted,第二个值需要注意,因为如果在所有子节点都灭灯的时候取到最大值,必须强行让一个子节点亮灯
Round G 2019
1. Book Reading
- 这题刚看到的时候被吓到了,因为想着 O ( ( 1 0 5 ) 2 ) O((10^5)^2) O((105)2)肯定超时,然后看了下答案,原来并没有那么复杂,虽然对于小的数统计一遍要 O ( 1 0 5 ) O(10^5) O(105),但从大的范围看,全部统计一遍的复杂度为 O ( N 1 + N 2 + . . . + N N ) ≈ N l o g N O(\frac{N}{1}+\frac{N}{2}+...+\frac{N}{N})\approx NlogN O(1N+2N+...+NN)≈NlogN,所以只要打个表再查就行
2. The Equation
- 按位来分析,k的每个位会使A数组的每个位翻转,所以会使得 S U M SUM SUM增大或减小
- 由于要使k最大,所以只需用贪心法,先将会减少的位设为1,然后从最高位开始,只要翻转之后不超过 S U M SUM SUM就设为1
3. Shift
- 这题算是枚举优化的模板题了,基本的方法还是枚举,但这样会产生 O ( 3 k ) O(3^k) O(3k)个结果,即使用了剪枝也过不了第二问
- 枚举的问题在于,有多少个结果它就枚举了多少次,然而我们不需要真的每一个结果,只需要计算数量就行,所以如果能批量计算就好了
- 答案的方法是将序列拆成两半 A A A和 B B B,分别枚举,然后统计对于每个 a i a_i ai,有多少个 b i b_i bi符合要求;为了加快速度,这里用了排序和区间树,本质上将 O ( ( 3 N / 2 ) 2 ) O((3^{N/2})^2) O((3N/2)2)的交互复杂度变为了 O ( 3 N / 2 l o g ( 3 N / 2 ) ) O(3^{N/2}log(3^{N/2})) O(3N/2log(3N/2))
Round H 2019
1. H-index
- 这题我想着用区间数肯定能做,但是第一题就用高级数据结构会不会想偏了,看了看答案,原来是用最小堆,由于 H = m i n ( q . t o p ( ) , q . s i z e ( ) ) H=min(q.top(), q.size()) H=min(q.top(),q.size()),而 q . s i z e ( ) q.size() q.size()不会增加,所以如果 q . t o p ( ) < q . s i z e ( ) q.top()<q.size() q.top()<q.size()就不断弹出,最后的结果即是当前的 H H H
2. Diagonal Puzzle
- 这题需要一点数学观察,同时也非常复杂(写了150+行),首先如何翻转肯定是有约束的,比如:每条线至多翻转1次,白点能且只能翻转1次,黑点只能翻转0或2次
- 但是最神奇的约束是,只要确定了两条大对角线(奇数时另一条对角线取第二长的,因为不能交叉于一格)有没有翻转,就可以确定剩下的所有翻转,因为这两条线的格点交叉的垂线覆盖了整张图
- 总的步骤是:(1) 遍历两条大对角线的翻转情况(4种);(2) 确定两条线格点交叉的垂线的翻转情况;(3) 确定所有非上述线的翻转情况
3. Elevanagram
- 这题对数学的要求太高了,也许这种整除题都有些套路,如何证明就不细究了,判断步骤为:(1) 如果有2个数都多于10个则真;(2) 如果有3个数都多于6个则真;(3) 遍历所有的组合情况,最坏时间复杂度的情况如下
5 5 5 5 5 5 5 9 99956
- 但实际上最后一个数不会被遍历99956次,因为正负各占一半,所以它的取法完全由前面的取法决定,最坏复杂度为 O ( 6 7 × 10 ) O(6^7\times 10) O(67×10)