寒假的时候讲的 现在才开始补...惭愧啊
BZOJ 3333: 排队计划
我们发现, 你抽出后面的数排序后, [取出来的数之间]就没有逆序对了
然后我们又发现, 因为没取出来的都比取出来的数要大, 所以实际上原来 [取出来和没取出的数之间] 构成逆序对的地方现在还是逆序对, 之前后面比前面小的地方, 现在后面还是比前面小; 也就是[取出来的数和没取出来的数之间]的逆序对数量不变
我们考虑用树状数组找逆序对的过程: 找后面所有比他小的数的个数(或者前面比他大的数, 无所谓)的和
我们发现, 如果这个数被取出来了, 他后面就没有比他小的数了(排序后取出来的数比他大的一定在他后面, 没取出来的数比\(a_p\)都大, 肯定比取出来的所有数大)
我们计一个数\(i\)后面比他小的数的个数为\(nxd_i\)
那么我们只需要在他选出来的\(p\)后面, 取出所有比他小的数\(i\), 然后在答案里减去\(nxd_i\)就行了
具体实现的话, 为了去重我们要把已经取出来的数都设为\(\infty\), 防止被多次取出(因为已经排过序了)
所以代码的实现就是:
- 初始答案为整个序列的逆序对个数\(ans\)
- 不断的在\(p\)的后面找比\(p\)小的数\(i\)(用线段树), 把\(i\)设成\(\infty\), \(ans = ans - nxd_i\), 直到\(p\)后面的数都比\(a_p\)大, 输出\(ans\)
1 | /************************************************************** |
Codechef FRBSUM && [Fjoi 2016]神秘数
这题好神啊qwq
首先我们要知道一个显而易见的结论:
我们给要求的区间排序, 如果前\(i\)个数的神秘数为\(ans_i\), 那么:
- \(a_{i+ 1} \le ans_i \to ans_{i + 1} = ans_i + a_{i + 1}\): 考虑把\(a_i\)放在"底下"(或者说放在"前面")
- \(a_{i + 1} > ans_i \to ans_{\text{整个区间}} = ans_i\): 考虑如果这个数自己就超过了\(ans_i\), 那么之后的整个区间都不可能有数能到达\(ans_i\)了
想一想是不是这样的
那么, 我们就可以这样
- 先设一个\(ans = 1\)(因为\(0\)肯定能凑出来), 然后开始循环:
- 在主席树中查找给定区间里所有不大于\(ans\)的数的和1, 记作\(qwq\)
- 如果\(qwq\)小于\(ans\), 那么直接输出现在的\(ans\): 因为所有不大于\(ans\)的数加起来都不能凑出\(ans\), 而加入大于\(ans\)的数肯定也不可能正好是\(ans\), 也就是这个区间里不可能凑出\(ans\)
- 如果\(qwq\)大于\(ans\), 那么\(ans = qwq + 1\): 考虑上面的结论, 我们加入的数(其实就是第一个结论, 但是一次加了多个数)都没有超过\(ans\), 所以我们可以扩大\(ans\)到\(ans + a_x + a_y + \cdots = qwq + 1\)
感性理解一下就是:
在上一轮循环, 我们用了一些\(a\), 得到了一个\(ans\) 在这一轮循环里, 我们加入了一些\(a\), 这些\(a\)都满足上面的第一个结论, 每一个\(a_x\)都可以让\(ans\)变成\(ans + a_x\) 我们一次加了好多的\(a\), 每一个\(a\)都符合要求, 所以我们可以让\(ans\)扩大\(\sum \text{所有被加进来(也就是比$ans$小的)}a\), 而之前的\(ans\)就是前面几轮中被加入的\(a\), 所以新的\(ans\)就是所有比\(ans\)小的数的和了
因为如果要继续循环的话, \(ans\)一定比上一个\(ans\)的两倍还要大, 所以复杂度是\(\mathrm O(n\lg ^2n)\)的
1 | /************************************************************** |
By Cansult