我觉得这是很重要的一些题目。它们都在这里:硕巨结构
Challenge 0:给定数列,单点修改,单点查询修改。煞有介事,不过一数组耳。
Challenge 1:给定数列,单点修改,单点查询第k次操作后的值。当时写了链表,显然可以被卡。lemonoil说写主席树才是正道,其实不然。使用vector<pair<int,int> > ...[n]就可以了。插入显然都是有序的,查询直接二分就好了。
Challenge 2:数列最开始是空的,允许在最后插入一个数,单点询问,或撤销若干次操作(包括撤销与插入操作)。主席树很好写,主要是考察对“撤销”的理解(最开始就是不懂“撤销”,以为是直接把顶上的东西丢了)。其实不管插入还是撤销,总的操作数都加了1。而插入是基于前一次操作再加入一个数,而撤销是把若干次以前的再搬回顶上。使用一个len来辅助维护数列的长度。
Challenge 3:给定数列,单点修改,区间查询和。树状数组就好了,不必写线段树。
Challenge 4:给定数列,单点修改,区间查询相邻两数乘积和,区间查询任意两数乘积和,%1e9+7。记得这个当时调了很久很绝望,但最后发现错误很小很微不足道,就是所有单点的值都应该%MOD+MOD%MOD。因为如果不把这些数标准化,得到的也不会是标准的结果。而当时写struct写的很痛苦,后面写的直接把pair给typedef了,再加上预带的merge函数下面就可以变得很清新。这种套路也出现再IDY4月份的有序链剖中。
Challenge 5:给定数列,区间修改成同一个数,区间查询和。线段数的模板题,看似很简单。但就是这样的题目也是有坑点的。因为修改成的数可以为所欲为,所以常规状态下的什么-1就是大错特错了。要么再加一个bool变量,要么让flag的初始值变成绝对不可能取到的值。
Challenge 6:这道题目就是BZOJ2957楼房重建。IDY4月份当时还考过这道题目的进阶版本:单点修改,区间询问。首先,每个线段存的是区间最大值与区间可以看见的数目。而又有一个很重要的内函数,询问一个线段中高度>h的楼房数,也是二分(或分治)递归单次O(lgn),中间还是很有机巧的(充分利用题目性质)。这个东西在query和update都用得很多,于是整个是O(nlg2n)的。
而全局询问和区间询问的差别在哪里?对于后者,IDY的做法是把覆盖线段通通fetch出来再for一遍;而QJX告诉我,其实直接搞就是了。对于跨中点的询问,先处理左边,修正一下影响再处理右边,好像有一点CDQ的意思。
而此题其实可以区间修改。区间修改包括区间修改为同一个值和区间同增同减,我通常的做法是使用“kill”来打扫干净屋子再请客,而中间的顺序是很有意思的。最后写出来很长,大概可以用来考后来人了。
另外,此题使用分块也是可以的。
Challenge 7:给定数列,单点修改,询问一个数出现的次数。使用map可以过,但最开始使用multiset却稳稳的TLE。先以为是count过慢(但count确实非常慢),但直到做完Challenge 8才知道是为什么。
但COUNT确实很慢,我在自己的机子上试了一下这段代码:
1 #include <cstdio> 2 #include <set> 3 #include <ctime> 4 5 std::multiset<int> st; 6 int main() { 7 for( int i = 1, j; i <= 100000; i++ ) { 8 if(i%1000==0) printf("%d %d ",i,clock()); 9 st.insert(100);j=st.count(100); 10 } 11 printf("%d ",clock()); 12 }
得出了这样一张表:
进行n次插入与count | 时间(ms) |
1000 | 5 |
5000 | 101 |
10000 | 407 |
20000 | 1631 |
40000 | 6708 |
60000 | 15261 |
80000 | 28005 |
100000 | 43916 |
Challenge 8:给定一个集合,允许删除或插入数(不可重),询问集合中任意两数差绝对值的最小值。使用1个set存数字,1个multiset存答案。中间有曾经XGG那道巡游和YALI那道容斥集合的影子。但是最开始交上去直接TLE了,本地测试发现插入仅10000个数就会TLE,再排查就发现去掉lower_bound速度会快上很多很多很多倍。于是整个人迷惑了,不是O(logn)的么?在网上查,找到了一篇很好的博客:std::set::lower_bound与std::lower_bound的效率问题。长见识了,所以不应该std::lower_bound(st.begin(),st.end(),...)了,而是应该st.lower_bound(...)啊。
Challenge 9:给定数列,单点删除,询问单点。典型的简单平衡树。
Challenge 10:给定数列,单点修改,询问全局第k大。使用值域线段树或是平衡树。值域线段树需要注意对边界的处理(就像cheese那样虽然最后数据很水),使用-0x7fffffff~0x7fffffff是1000ms,使用0~0xfffffffe用unsigned大概快50~100ms。而平衡树可以不用建新节点,把原来的取出来,让它变成叶子结点(我在做phalanx的时候就是忘了改它的左右儿子和siz),再插回去。快得多,只要了380ms,但代码长度大约90行比线段树多一半。
Challenge 11:简单主席树。ZJC说可以用“可持久化树状数组”。
Challenge 12:经典题目。区间第k大无修改无插入。使用树状数组+线段树固然可以,但毕竟是O(nlog2n)的,时间4159ms,空间也大的吓人(开始就是空间开小了RE)。只使用主席树虽然无法修改,但复杂度就是O(nlogn)的了,时间1048ms。如果使用莫队,因为还是需要辅助数据结构,而O(nsqrtnlogn)素来是跑不过100000的。但是,这样就完了?不不不。还有一种神方法。使用区间线段树,每个线段一个vector,build的时候自下向上进行归并。查询的时候把log个线段搞出来,外面二分答案,内部for一遍加上upperbound。看上去是O(nlg3n)的,但其实就应该是O(nlg3n)的。但是数据如果强度不够高,水过去还是可以的。