原题
题目描述
Sakits最近沉迷一款打boss游戏,游戏里有$n$个boss,其中第$i$个boss战力为$w_i$,若Sakits的战力$W$,那他能打赢第$i$个boss当且仅当$W$>$w_i$,并且打赢后Sakits的战力会提升$w_i$。游戏的版本也会随着时间更新,会存在boss的增加和删除。
共有$Q$个操作,每个操作是下面三种类型之一。
$1\ W\ k$ :Sakits的初始战力为$W$,Sakits至少打赢多少个boss才能将战力提升到$k$ (打boss的顺序随意,注意每个boss只能被击败一次)。
$2\ W$:新增一个战力为$W$的boss。
$3\ W$ :删除一个战力为$W$的boss,保证一定存在。
输入格式
第一行一个正整数$n$。
第二行$n$个正整数$w_1,w_2,\cdots,w_n$。
第三行一个正整数$Q$。接下来$Q$行,每行若干个整数,描述一个操作,格式如题面所述。
输出格式
对于每一个$1$操作,输出一个整数表示答案(若无法将战力提升到$k$,输出”-1”)。
数据范围
$n\le 300000,q\le 100000,1\le w_i,w\le 10^{12},1\le W,k<=10^{18}$
思路分析
我们先简化一下问题,在不考虑添加和删除操作,只有询问的情况下,应该怎么做呢?朴素的想法是找到打不过的boss里$w$最小的一个,不妨记为$nxt$,然后在能打过的boss从大往小打,直到能打过$nxt$并把它打掉,然后再不断重复这个过程,直到战斗力达到$k$了为止。这是一个比较显然的贪心想法,它能保证我们打掉的boss数是最少的。
思路比较简单,但有几个实现上的问题。一是怎么查找$nxt$。朴素地找每次需要$O(n)$遍历一遍,这不太合适,考虑到我们本身就是依赖于大小进行查找,于是可以进行排序+二分,将每次查找的时间复杂度降到$O(logn)$。二是由于每个boss只能打一次,怎么去维护这件事情并且在下一次询问的时候复原。事实上,由于打掉的boss一定是连续排列的,所以在没有添加和删除的情况下,我们只需要拿一个指针指一下目前已经打到哪儿了即可。
然而,出题人不是很友好,如果考虑添加和删除操作,直接采用上面的做法就不太合适,毕竟添加元素意味着需要重新排序,而每次都重排显然超时了。于是,我们需要利用一点小技巧,让上面的思路可以继续生效,即采用离线做法。
所谓离线,就是先把所有操作和询问读进来,最后统一处理
我们把可能出现的元素都放进一个数组里(即原来就有的和将来可能添加的),然后进行排序和去重,并且增加一个数组$cnt_i$来记录$w_i$出现的次数。这样,当我们添加$W$的时候,就可以将$W$对应的$cnt+1$,而还没添加的就可以用$cnt=0$来表示,二分的时候检查是否添加即可。
由于添加和删除会改变$cnt$和前缀和,我们还要考虑高效地维护它们。很自然地,我们想到可以使用线段树。把连续的一段boss全打死,相当于把这一段的和全部置零,同时$cnt$也要置零,于是我们用线段树去维护一个$sum$用来记录区间里的所有$w_i\times cnt_i$之和,再维护一个$num$来记录区间里的$cnt$之和。
还有个问题,是原本的二分查找也需要修改。由于单点询问$cnt$是$O(logn)$的复杂度,再套一个二分就是$O(log^2n)$,这其实不太优。我们可以直接在树上进行二分操作,甚至还可以将区间修改一并顺带进行。简述一下,就是先往大的区间里找,如果这个区间能全部打掉,就把这个区间整个修改,否则就继续进入小区间。
最后,我们每次询问结束之后,我们需要对这颗线段树进行还原,笔者的想法比较暴力,就是每次修改之前把这个节点压到栈里,询问结束之后按序弹出栈复原即可。
贴上代码:
代码仅供参考,请不要盲目抄袭代码来完成作业!!!
1 |
|
一些补充
细心的读者可能会注意到,上面的线段树在区间修改时没有打lazytag并进行标记下传,为什么呢?
这是由于这道题的特殊性,因为战斗力只会增加,所以当前能够整体访问的区间在战斗力增加后一定仍然能够整体访问,所以不用进行标记下传。
另外在时间复杂度方面,这道题似乎不是那么直观。在solve()
函数中,由于每次会吃掉一个当前本来打不败的boss,所以战斗力至少每次会翻倍,而$k \leq 10^{18}$,故在粗略分析下,外层的while (curW < k)
循环至多进行$logk_{max} = log10^{18} \approx 60$次。findNext()
函数时间复杂度是$O(logn)$的,work()
函数的分析比较复杂,实际上可以看成求出需要打的区间$[s,t]$,并对区间$[s, t]$进行区间修改,这两个过程分开每个都是$O(logn)$的,合并自然也是$O(logn)$的。至于rollBack()
操作,从上面可以看出,每次询问访问的节点总数应该在$60\times logn$级别,不会过大。
于是本题总时间复杂度在$O(qlognlogk_{max})$级别。
上述内容只是粗浅的分析,如果出现错误或疏漏,欢迎在评论区指正