2025.1.15 CQFLS讲课
以一道题目引入:给定一个长度为\(n\)的数列\(A\),要求查询其中的第\(k\)小数
- 暴力做法:找出最小的数,将其删除;找出次小的数,将其删除;以此类推。时间复杂度\(O(kn)\)
- 直观做法:利用排序做法将其排序,然后直接输出第\(k\)个数。时间复杂度\(O(n\log n)\)
- 二分做法:二分答案,设当前二分的值为\(mid\),扫描一遍序列统计不超过\(mid\)的数的个数,设为\(cnt\)
- \(k\leq cnt\):答案肯定在\([l,mid]\)之间
- \(k\gt cnt\):答案肯定在\([mid+1,r]\)之间
- 时间复杂度\(O(n\log V)\),\(V\)是值域
- 特殊做法:中位数的中位数,时间复杂度\(O(n)\)
再考虑一下另一道题目:给定一个长度为\(n\)的数列\(A\)和一个固定的整数\(s\),执行\(m\)次操作,操作类型有两种,如下
- 求序列\(A\)的第\(l\)个数到第\(r\)个数中不超过\(s\)的数的个数
- 将序列\(A\)的第\(x\)个数修改为\(y\)
- 要求单次操作的均摊复杂度为\(\log\)级别
先来解决第一个操作。如果将\(A\)中不大于\(s\)的数看做\(1\)(表示对答案有\(1\)的贡献),大于\(s\)的数看做\(0\),那么就变成了统计\([l,r]\)的和的问题。考虑到有修改操作,我们可以使用树状数组/线段树/平衡树
再来看看第二个操作。每次修改等价于去掉了一个\(A[x]\),增加了一个\(y\),而且都发生在\(x\)位置上。于是有
- \(A[x]\leq s\):\(\text{add}(x,-1)\),表示在\(x\)位置减一
- \(y\leq s\):\(\text{add}(x,1)\),表示在\(x\)位置加一
- 由于我们需要在其他时刻知道\(A[x]\)的值,所以我们还要\(A[x]\leftarrow y\),从而正确维护\(A\)
这两个问题看起来没有任何的关联,那如果第一个问题是多次查询呢?
我们尝试将前面的解法结合起来。初始化时,令\(L\)为\(A\)中最小的数,\(R\)为\(A\)中最大的数。先将所有询问读入,采取离线做法
设当前二分区间为\([L,R]\)(这里有一个前提,我们只考虑\(A\)中在\([L,R]\)的数,其余数全部变为\(0\)),二分值为\(mid\),扫描每个询问\(i=1\rightarrow m\),统计\([l_i,r_i]\)中不超过\(mid\)的数有多少个,记为\(c_i\).由于每次统计的时候,\(mid\)是一个固定的值,所以可以使用我们上面讨论的第二道题目的做法。再根据\(c_i\)对查询进行分类
- \(k_i\leq c_i\):第\(i\)个询问的答案在\([L,mid]\)中。将这类询问归为第一类
- \(k_i\gt c_i\):第\(i\)个询问的答案在\([mid+1,R]\)中。将这类询问归为第二类。但是注意,这里不是简单地归为第二类。与上面所讨论的第一道题目有区别,我们需要\(k_i\leftarrow k_i-c_i\)
将上述的递归过程完成即可
时间复杂度分析:递归树有\(O(\log V)\)层,每一层的复杂度是\(O((m+n)\log n)\),所以总的时间复杂度是\(O((m+n)\log n\log V)\)
思考:我们最开始举的例子的二分是不会有类似q[i].z-=cnt
的操作的,如果我这里仿照开始的例子,也选择不用q[i].z-=cnt
而是不改变值域,可以吗?
有了上面的例子,我们就可以知道整体二分的用途了:简单地说,可以先按照普通二分去想,发现一次二分的复杂度符合要求但是多次二分的复杂度太大了就可以把所有二分结合在一起解决,故名整体二分。更严格的要求可以去看OI-wiki
细心的同学可能发现,我们最开始举的例子,是带修的,而我们的例题却没有带修,那么整体二分可以做带修版本的例题吗?
答案当然是肯定的
这道题目就是动态查询区间第\(k\)小。借鉴静态查询区间第\(k\)小的思路,我们仍然把输入看做若干次添加。对于后面的修改操作,看成是删除加上添加。对于每个删除操作,我们将树状数组对应位置减一;对于每个添加操作,我们将树状数组对应位置加一即可
考虑一下正确性:在递归树上任意一点,任意一个查询操作,所有对其的影响一定会计入到树状数组中。分情况讨论。对于最开始的初始化操作,所有在\([L,R]\)的操作肯定都在当前操作序列的最前面。对于后面的修改操作,如果是\(-1\)标记,那么其对应的添加操作一定也在当前操作序列里面,而且在这个\(-1\)操作前面,所以对于任意一个元素,既不会漏记,也不会重记(任意一个元素的操作序列一定是+1 -1 +1 -1 ... +1 -1 +1
)
举一个例子,假设当前值域区间为\([5,10]\),树状数组为空。某一位数字的值是\(7\),而且其所经过的修改为\(6\rightarrow 3\rightarrow 8\rightarrow 7\),那么其操作序列就是+1 -1 +1 -1 +1 -1 +1
;对于当前操作序列,其包含一个子集为+1 -1 +1 -1 +1
,可以知道\(7\)不会漏记
代老师让讲一下提高组综合题目,范围给的太大了,类似于“计算机导论”这种课,思来想去,就觉得讲讲今年两道真题吧,蓝色
第一题是[CSP-S 2024] 染色。做这道题目的时候是一月九号。一月十号是算法与数据结构考试,当时在想怎么复习(由于太菜一直在ACM校队边缘徘徊,只能去搞人工智能qaq,许久没搞算法竞赛导致复习思路都忘了),突然想起来要不康康现在的CSP和NOIP是什么水平(NOIP1=分数线270,CQ真是一个美好的地方),结果一看果然很有水平b( ̄▽ ̄)d。挑了一道CSP的蓝色题目做,就是DP啦。从开始做到AC一共花了一小时四十分钟(我校陈同学考场上只花了半小时,给我秒杀了),其中前一小时二十分钟实现了单\(\log\)做法,其中思路一小时,代码二十分钟,结果现在CCF不让过,没办法只好优化成线性做法了
第二题是[NOIP2024] 编辑字符串。OI举办这么多年应该是第一次T1比T2难(反正我当年搞的时候做真题没有做到过)。其实也是因为T1的个体差异很大,当时NOIP考完了我看洛谷上面都要吵翻天了。我自己属于觉得这道题目比较简单的人,从开始做到AC一共花了半小时,其中思路十分钟,代码二十分钟。但我看到很多七级勾甚至有金钩的都卡了很久,果然个体差异很大┓( ´∀` )┏