随便想的两道算法题+题解
在初中的时候,感觉打OI最有意思的事情之一就是出题。因为自己出的题目可以套一些奇奇怪怪的设定来整活,还可以名正言顺地迫害小伙伴。不过呢出的都不是什么正经题目。
现在几年过去了,感觉已经好久没有出过题目了,正好社团的友谊赛给了我一个契机来认真出两道题,个人认为这两题的难度,思维量,码量也都说的过去,就出一篇题解。
A.萌娘评鉴指南
题目大意就是给主角找对象啦(* /ω\*)
概括
给定个集合,这个集合组成一个序列,给定3种操作,一共进行次:
- 给编号l~r的集合每个集合添加一个数
- 给编号l~r的集合每个集合进行反转 (去掉有的数字,添加原先没有的数字)
- 询问编号l~r的集合中,有数字的集合数量是奇数还是偶数。
数据范围: .
思路:
位运算:
其实看到c的范围就可以找到这题的突破口。因为对于集合里的每个数,都只有“有”和“没有”两种状态,所以完全可以利用位运算,来把每个集合转化为二进制的数字,这样集合就可以以int
的形式存储了。
例: 1,2,4 -> 1011 -> 11
使用位运算,我们又可以重新翻译一下这道题目:
给定n个整数,进行q次以下可能的操作:
- 给编号l~r的数每个数都 按位或
1<<c
- 给编号l~r的数每个数都进行 取反
- 查询l~r的数的异或和 按位与
1<<c
的结果。
熟悉位运算的应该能非常容易作出这种转化,不熟悉的也会发现这几种操作可以和原先所说的完全对应起来。
线段树:
因为询问的是异或的结果,而异或是满足结合律的,结合数据范围,我们必须要想到一种能够在复杂度内进行操作的方法。
那么很明显就是用 线段树。 线段树维护的内容是区间内的异或和。
那么问题来了,怎么样完成1操作和2操作呢?
对于线段树而言,肯定是要建lazytag的,通过合理地设置lazytag,我们就可以合理建出满足要求的线段树 (车轱辘话时间)
显然,lazytag中包含以下两个内容: 区间要取或的值 和 区间是否取反。但是有一个问题:区间进行取反之后,区间要取或的值就变了!思考一下可以发现,要取或的值,取反之后,再取与就可以了。
所以实际上我们的lazytag要存储3个内容:
- 区间要取或的值
- 区间是否取反
- 区间要取与的值
此外,还有n个细节需要注意:
- 区间长度为偶数,异或和不需要取反,长度为奇数要取反
- 如果区间长度为偶数,那么取或操作应该改为取反后取与(因为某一位如果有偶数个1,那么异或和就是0)
- 是否取反的标记,应该使用 异或 操作,因为取反两次就相当于没有取反,如果用了赋值会出错。
代码:
1 |
|
B.新建文件夹
在出这道题之前,我开玩笑地说“题目已经在出了(新建文件夹)”,于是干脆就把这题叫作新建文件夹好了,题目大意也跟我出题的过程有关,只能说当时的精神状态极为良好(笑)
概括:
以深度优先的顺序访问一棵树的每个节点,直到访问到目标的叶子节点结束。询问访问节点数量的期望。
思路:
这题可以说跟上一题相比非常简单了。
我们可以发现,从根节点到目标叶子节点的这一条链,是一定会被访问到的。也就是说访问到这些节点的概率为1。
那么其它的节点呢?我们会发现,对于链上的每一个节点,他们的兄弟节点被访问的概率都是相等的,它们要么被访问到,要么没有被访问到,概率为。
对于这些节点的所有子孙节点,他们都是一定会被访问到的。因为只有把这些节点全部走完才可能返回上一级,所以他们的概率也是。
那么答案就出来了:从根节点到目标叶子节点的这一条链节点的数量+其它节点数量/2.
总码量奇短无比。
代码:
1 |
|