本次比赛共有 401
个用户通过至少一道题目,平均通过 3
题,第一位做出全部题目的同学用时 45:26:37
。
如果有对题目的疑问或是见解,欢迎寄刀片给出题人。
预祝大家在决赛中取得好成绩!没有搜索引擎了看你怎么办(¬︿̫̿¬☆)
A. 一尺之棰
出题人是 constroy 。
这是一道贪心题,答案是
(m+1)⋅2n−1
。
也可以按每一天模拟,每次乘 2
后加 1
,模拟
n
天也会得到这个结果。
注意答案会超出 int
的表示范围,可能需要使用 long long
等类型表示答案。
B. 加法运算
出题人是 Dshawn 。
这个题目分两步进行运算:
- 计算
1+2+⋯+n 的进位次数。- 在我们知道上面这步怎么做了以后,二分答案是计算到哪个数字 R 结束。
对于第一步,我们可以从低位到高位(个位,十位,百位,……)依次来计算每一位的和,例如 9167 + 992
这种情况,最高位还会进位一下,不要落下了。
对于第二步,我们可以用
1+2+⋯+R
的进位次数减去
1+2+⋯+(L−1)
的进位次数算出来
L+(L+1)+⋯+R
的进位次数,从而得知最大的
R
。
由于二分查找的次数是
还有一种方法是直接计算每一位的进位次数,从低到高第
i
位的进位次数可以考虑所有数模
C. 圆圆的物理实验
出题人是 dale 。
枚举每个实验,如果两成绩均大于等于 3
则加上该积分。
D. 多米诺骨牌
出题人是 constroy 。
根据期望的线性性可知,答案是每个骨牌被检查前没倒下的概率之和(因为每个骨牌只会被检查一次,题目可能存在歧义)。
每个骨牌倒下的概率只跟它上面和左面的骨牌有关,这题就做完了。
注意不要用 float
类型表示概率,这会使得答案的精度不够。
有的选手使用整数除以 100
表示概率,整数除以 10000
表示答案,注意不要用 int
类型表示答案对应的整数,最坏情况下答案是
nm
,乘以 10000
后已经超出 int
的表示范围了。
E. 区域赛练习题
出题人是 IImare 。
首先读懂题意后,容易知道答案便是
但是 n 太大,直接计算必然超时。
换一种角度思考
注意 (−Ai,−Bi) 走到 (Ai,Bi) 的方案是不该统计的,而 (−Ai,−Bi) 走到 (Aj,Bj) 与 (−Aj,−Bj) 走到 (Ai,Bi) 的意义相同,可能造成重复计算。
由于数据比较随机,所以 O(4000n) 的方法也可能通过此题。
F. 点与多边形
出题人是 Tangjz 。
这是一道脑经急转弯类型的题目,可以证明
m
的取值只有 4
,或者
首先可以发现,只要存在由有理数点组成的正
假设由有理数点组成的正 叉积
计算这个正
m
边形的面积,可以发现它的面积也是有理数,而它的面积公式为
然而满足
m≥3,m≠4
且
sin(2πm)
是有理数的解是不存在的,相信这个问题大家已经在数学分析中解决,这里不再赘述。
对于本题,可以得到的解法是枚举正方形一条边上的两个顶点,检查其他两个顶点是否存在。而查询点集中是否存在某个顶点可以利用哈希算法做到每次操作均摊
O(1)
,因此总的时间复杂度是
O(∑n2)=O(max(n)⋅∑n)
。
注意到有些参赛选手使用 C++ STL
中的 map
进行点集的维护,但由于查询使用了 []
操作符导致超时,这是由于 []
操作符会把不在集合中的元素默认添加到集合中导致的,只需要改用 map\count()
即可。
由于数据的构造方法较弱,进行一些剪枝也可以
O(n4)
通过。
G. 火柴棍摆数字
出题人是 Dshawn 。
题目简单易懂有 trick 但是 trick 已经被标明了。
由于 1
只用 2
根火柴棍,7
只用 3
根火柴棍,所以很简单就能想到只用 1
和 7
就能完成
N≥5
所有数字的摆法。
方法是分奇偶讨论:
N
为奇数时,最大数字为 1
),次大数字为
17111⋯
(共
n−32
个 1
);
N
为偶数时,最大数字为 1
),次大数字为
77111111⋯
(共
n−62
个 1
)。
唯一的 trick 就是
N=4
的时候次大的不能按照正常的构造,只有
11
和
4
满足条件。
然而此题不想卡人,题目中说如果不存在的话要输出 I am so stupid
,有输出这句话的人请左转面壁。
本题也可以把每个数字的数值视为价值,需要的火柴棍视为体积,利用动态规划求解最优价值和次优价值。
H. 黑白相簿
出题人是 Tangjz 。
如果你看出题目要求的就是 所有单色矩形的面积之和 ,那么也许你已经发现这是一道 悬线法 的基础练习题(悬线法确实不仅限于解决最大子矩形问题)。
对于 0
和 1
可以分别处理,所以只用考虑矩阵中一种颜色的情况。
预处理出每个点向上方可以延伸到的同种颜色的长度,枚举一行作为单色矩形的下边界,再枚举一列作为单色矩形的右边界,尝试计算答案。
考虑到随着左边界的递减,可行的上边界只会更低,所以利用栈结构维护可能的每段上边界(显然是单调的区段),以及上边界对应的左边界的取值范围(对应的左边界是连续的一段)。
每个右边界的单色矩形可能不是在当前位置就被统计的,而是在右边界发生变化时,上边界的取值范围可能会发生变化,在这个时候再考虑哪些之前的右边界可以适用这个上边界(满足条件的右边界也是连续的一段),将这个上边界对应的左右边界整理出来,计算它们对应的单色矩形的面积之和即可。
因为在每一行时,列(右边界)变化时只会新增一个上边界,而栈中的上边界只会离开栈一次,所以上述算法的时间复杂度是
由于悬线法实际上是枚举了下边界具体值,枚举了上边界的取值范围,找到对应的左右边界的取值范围,所以稍作修改也可计算出 每种尺寸的单色矩形的个数 ,然后再计算答案,有兴趣可以试一试。
I. NAIVE 排序
出题人是 ez_fwtt08 。
题意很简单,就是给
M
个字符串,求出其中第 sort
是会超时的,毕竟数据有 30MB
,时限只给 1s
。(原本内存限制是 64MB
,还可以卡掉正规字典树的做法)由于某些不可描述的原因,出现了ASCII为 123 的字符,请注意。
当时想的是必须使用类似字典树的思想,但是不建树,因为只需要找其中一条线,其他线直接就可以放弃,因此没必要建树。但是后来发现这个数据量(数据是随机生成的)卡不住 nth_element
,所以使用这个 C++ STL
函数也能通过。(或许构造一下数据能卡掉这种做法,但是由于时间比较紧,就放开了)
因此正解是类字典树的划分(即整体二分)或者 nth_element
。注意:为了提高读写速度和减少内存使用,必须使用 char
数组,不要使用 string
。
nth_element
比较简单,就是直接调用函数 std::nth_element
就可以了,但是这样做比较慢,而且应该是刚好数据比较随机才能 AC
。这个 nth_element
的复杂度说是均摊线性,但是最坏情况还是
O(MlogM)
,可能存在一种数据能使它变得很慢。
还有就是被认定为正解的类字典树划分(整体二分)。首先对整个数组的第一个字符进行统计,因为ASCII
码只有 33
到 122
,所以开一个小于 100
的数组就可以了。然后通过基数排序的思想,我们可以得知第
N
小的字符串的首字符是什么。这也可以看作字典树的第一层。然后可以看第二个字符,但是这时候要把第一个字符已经不符合要求的筛去,出题人的做法是扫一遍,把第一个字符不是答案字符的字符串指针直接交换到数组最后,然后数组大小减一。之后每一个字符都用类似的方法处理,第 1
个字符串,前者随便选一个字符串即可(因为它们都是相等的),后者可以直接结束。
时间复杂度
O(|Sinput|)
, 30M
大概也就是
3⋅107
左右。标程跑了 424ms
,看到有些同学能跑到 300ms
左右,很厉害啊。
J. 勾肥大战
出题人是 CabinFever 。
因为本题中假定了屠夫不会影响友方单位的肉钩,所以我们可以对每个己方屠夫单独考虑。
考虑己方屠夫
a
和敌方屠夫
因为我们求的是范围,希望
α
尽量大,由
cosα=AB2+AC2−BC22AB⋅BC
可知,
|BC|=vb⋅t
时可以达到最大。代入余弦公式后,我们可以得到
cosα=v2a−v2b2⋅l⋅va⋅t+l2⋅va⋅1t
,这样我们就得到了一个
cosα
为因变量,
t
为自变量的函数。显而易见,这是一个 对勾函数
,当
但是,因为屠夫是不能越过峡谷的,所以可能取不到最大值,这时候就需要算出他们在
y
轴相遇的点,这个可以解一元二次方程求得。
设
采用一些技巧也可以避免解方程时产生精度损失,但是本题不卡精度。
K. 空间旅行
出题人是 adginqrw 。
将距离起点步数相同的点视为同一层,则答案为点数最多的层中的点的个数。
由于总点数个数小于等于
2⋅106
,所以可以枚举每个点,算出每一层有多少点,进而得到答案。
L. 裁纸片
出题人是 shell0011 。
直接的想法是枚举纸片的一个点,然后枚举纸片的大小,再判断该纸片是否合法。由于枚举的复杂度较高,判断合法性需要用预处理来降低复杂度。以枚举纸片左上角为例,我们预处理出
R[i][j]
和
D[i][j]
表示
(i,j)
这个点向右和向下最多可以连续多少个完整的小纸片。这样就能在常数级复杂度时间内判断合法性了。时间复杂度
O(n2m)
。
M. 最小内积
出题人是 yic 。
首先考虑问题的第一部分:如何使内积最小?以二维向量为例,
(x1,x2)⋅(y1,y2)=x1y1+x2y2
。由于各分量互不相同,不妨设
x1<x2
,则
因此 (x1,x2)⋅(y1,y2)<(x1,x2)⋅(y2,y1) 当且仅当 y1>y2 。
推广到多维的情况,若存在 i,j(i≠j) 使 xi<xj 且 yi>yj ,则我们可以交换 yi 和 yj 以获得更优的结果,因此最终结果一定有 ∀i≠j∈[1,n](xi−xj)(yi−yj)<0 ,即 A⃗ 的最小分量对应 B⃗ 的最大分量, A⃗ 的次小分量对应 B⃗ 的次大分量,……,以此类推。
知道了最终的对应关系后我们来考虑如何交换,稍加思考可知,对于一对位置 (i,j) ,交换 A⃗ 与交换 B⃗ 的结果是一样的,因此可以只交换 A⃗ 的分量,问题转换为给一个数列,要排列成指定顺序的最少交换次数。
我们从任意一个数出发,找到它的目标位置及这个位置上的数字,再找到新数的目标位置,这样不停找下去,由于不可能有两个不同的数对应同一个位置,因此最终必然形成一个长度为 L 的环,不同的环之间没有必要进行交换操作,在同一个环内每把一个数移动到目标位置都会使环的长度减一,总共需要交换
注意本题没有要求只能对两相邻分量进行交换,因此不用计算逆序对数,另外内积会超过
int
范围,需用 long long
型数据。