杂题选做-4
#31 P2824
注意到只有一次询问,那么我们可以离线处理。
然后我们考虑一个弱化的问题,值域只有 \(\{0,1\}\)。
那么我们我们在处理的时候可以直接将区间 \([l,r]\) 内的一的数量 \(k\) 询问出来,然后将 \([l,r-k]\) 设为 \(0\),将 \([r-k+1,r]\) 设为 \(1\) 即可。
然后回到这道题:我们考虑类似中位数,将大于等于答案的数设为 \(1\),小于答案的数设为 \(0\),然后仿照上述方法操作,那么我们可以询问到操作结束后,\(q\) 位置上的数是否大于答案。
注意到这是一个具有单调性的信息,套个二分即可。
#32 P3582
区间内只出现一次的颜色权值和,太典了。
直接离线询问,然后按右端点排序,接下来扫描线一次就做完了。
#33 P3765
注意不同寻常的空间限制(毒瘤)。
绝对众数可以用摩尔投票法来确定。具体地,因为绝对众数的出现次数大于一半,因此我们对于每一个非绝对众数的数都可以找到一个绝对众数与之匹配。于是我们只需要记录当前投出的数是哪个,它目前还剩多少个即可。
这东西是可以合并的,显然。但是有一个问题:题目不保证存在绝对众数。也就是说我们只是得到了一个可能是答案的值,我们还需要判断它是否是答案。
这是简单的,就是询问一个数在一个区间内的出现次数。你可以通过数据结构维护它,我们选择平衡树。
#34 P14378
考虑没有修改怎么做。
首先,贪心地,我们希望 \(A\) 的较小值和 \(C\) 的较大值相加。于是我们将 \(A\) 升序排序,\(C\) 降序排序,然后就得到了初始答案数组。
然后考虑处理询问。注意到因为修改是独立的,我们观察一次修改会造成什么影响。
结论:我们只会特别处理修改前后 \(A\) 变动的位置。
这显然是一个连续区间,我们可以用线段树处理。
具体地,你可以用数据结构或 ST 表查询区间内 \(A\) 向左(向右)一位后得到的答案序列的区间极值,前后缀极值可以预处理,然后像上述情况回答询问即可。
#35 P14379
观察部分分:没有 \(1\)。
发现无论怎么选择区间,区间的 \(mex\) 值都是 \(1\)。因此答案是 \(0\)。
再观察部分分:只有至多一个 \(1\)。
注意到有且仅有包含 \(1\) 的区间是有贡献的,因为我们可以把区间分成 \([l,l]\) 和 \([l+1,r]\) 两部分,然后一定有且仅有一段是包含 \(1\) 的,其 \(mex\) 值为 \(2\)。于是我们统计这种区间的数量即可。
再再观察部分分:没有 \(2\)。
我们对区间进行分类讨论:
-
以非 \(1\) 开头:那么我们一定可以将序列分为 \([l,l]\) 和 \([l+1,r]\) 两段,然后第一段的 \(mex\) 为 \(1\),第二段只需要包含 \(1\) 即可;
-
以 \(1\) 开头:那么我们一定可以将序列分成 \([l,r-1]\) 和 \([r,r]\) 两段,然后第一段的 \(mex\) 至少为 \(2\),第二段只需要不包含 \(1\) 即可。
我们观察到符合条件的区间有点多,于是我们容斥一下,把不符合条件的区间减掉即可。也就是两头都是 \(1\) 的区间和没有 \(1\) 的区间。
然后就是考虑一下有了 \(2\) 怎么做。
首先,已经合法的区间不会变得非法:因为上文我们讨论得到的区间都是只和 \(1\) 相关的。
然后会有新的合法区间:两头都是 \(1\) 且包含 \(2\) 的区间,那么我们按照上述方法划分后左边是 \(3\) 右边是 \(2\),是合法的。
然后统计的话就是 set 加数据结构(树状数组完全可以)。
修改只需要处理左右两边的贡献即可。
#36 P3987
一个数最多被操作 \(O(\log n)\) 次这种常见的就不说了。
因此我们只需要找那些数被操作就好了。
因为在值域内,因数最多的数也只有 \(200\) 个因数,所以我们可以给每一个因数开一颗平衡树,把一个数 \(x\) 塞到它每一个因数的平衡树上。注意,我们找因数时应当枚举因数来找数,而不是通过数找因子,前者时间复杂度是 \(O(n \log V)\),后者是 \(O(n \sqrt V)\),有较大效率差距。
询问时就在询问的数的平衡树上找出 \([l,r]\) 这个区间,然后判断除完后它是否还是因数,不是的话就删掉。
因为在平衡树上访问 \(x\) 个连续端点的时间复杂度是 \(O(x+\log n)\)。
综上,时间复杂度为 \(O(nd+m\log^2 n)\),其中 \(d\) 是因数个数。
但是当你看到加强版:P5610 和它 500ms 的时间限制.....
注意到我们其实可以不用平衡树,我们其实可以用链表的,反正时间复杂度也是 \(O(\log n)\),且无需递归,常数较小。
但是光是这样还卡不过去,因为 STL 的常数太大了。我们要自己实现链表,具体地,我们可以开一个内存池,然后我们给所用使用的数组改成指针,只需要赋值后就可以和正常数组一样用了。
#37 P6189
首先,我们考虑这个问题有一个根号分治的特点,就是 \(i\) 至多出现 \(\lfloor \dfrac{n}{i} \rfloor\) 次。
然后我们就试着根号分治一下,设闸值 \(T\):
-
对于 \([1,T)\) 内的数,我们直接定义 \(f_{i,j}\) 表示选了 \(i\) 个数,且和为 \(j\) 的方案数,容易发现这是完全背包,直接 \(O(nT)\)。
-
对于 \([T,n]\) 内的数,我们定义 \(g_{i,j}\),含义同上。但是转移有点特别:\(g_{i,j}=g_{i,j-i}+g_{i-1,j-T}\),前者表示给所有已选的数加一,后者表示新选一个 \(T\)。显然我们可以给每一个操作序列找到一个答案序列与之对应。因为 \(i \le \dfrac{n}{T}\),所以时间复杂度为 \(O(\dfrac{n^2}{T})\)。
综上时间复杂度为 \(O(nT+\dfrac{n^2}{T})\),显然在 \(T=\sqrt n\) 时,时间复杂度取到最小值 \(O(n\sqrt n)\)。
#38 P4141
对,这是一道黄题,但是我认为它的思想是有借鉴意义的。
首先,我们抛开限制做一次背包,然后得到一个数组 \(f_i\) 表示容量为 \(i\) 时的方案数。
直接容斥,钦定最后一个物品选 \(a_i\),然后贡献就是 \(f_{m-a_i}\),所以答案就是 \(f_m-f_{m-a_i}\)。
但是,这个问题可以强化。
bonus:多次询问,每次给定一个物品集合 \(S\),然后询问不用 \(S\) 中物品达到容量 \(m\) 的方案数。
继续容斥,我们定义 \(sum(S)=\sum_{i\in S}a_i\),容斥结果就是:
这看似是一个 \(O(2^{|S|})\) 的算法,但是我们思考这个容斥系数的实际意义:选了多少个集合中物品。
那么这或许是可以背包解决的问题。具体地,我们定义 \(g_i\) 表示选集合中物品且容量为 \(i\) 的方案数,那么显然有 \(g_i=-\sum \limits_{j \in S}g_{i-a_j}\)
那么最后答案就是 \(\sum_ \limits{i=0}^nf_i\times g_{m-i}\),时间复杂度为 \(O(n|S|)\)。
值得一提的是这个题的 dp 求容斥系数降复杂度的方法是一个常见套路。
#39 P1712
首先,因为我们要求区间长度差最小,所以我们考虑先按长度排序。
然后我们直接暴力加区间,然后判断当前被覆盖数最大的点的被覆盖数是否大于等于 \(m\)。如果已经大于,我们就按插入顺序删边,直到不存在被覆盖数大于 \(m\) 的边。
这么做是正确的,因为我们如果这么操作没有得到答案,那么说明判断时加入的区间的长度比答案的区间长度要大;但是这也说其长度更短,也就是说它比当前区间先被加入,但是直到加入当前区间时才存在被覆盖数大于等于 \(m\) 的点,这就说明加入答案的区间没有使任何一个点的覆盖数大于等于 \(m\),与假设不符。
#40 P2048
首先,理解题意是有难度的。简单地描述就是:定义一个区间 \([l,r]\) 的权值为 \(\sum \limits_{i=l}^r a_i\),请求出所有区间长度在 \([L,R]\) 内的区间中,权值前 \(k\) 大的区间的权值之和。
然后我们考虑如果 \(L=R\) 怎么做。这非常简单,将所有长度为 \(L\) 的区间的权值算出来,然后丢到一个堆里,每一次贪心地选取堆顶区间即可。
那么如果我们保证每一个位置只选一次呢?那么我们可以设 \(f(x,l,r)=\max \limits_{t\in [l,r]}\sum \limits_{i=x}^ta_i\) ,简单地说就是求出以 \(x\) 为左端点,以 \([l,r]\) 中任意一点为右端点的区间权值最大值。我们对每一个位置算一次,再参照上述做法,然后就可以求出答案。
那么我们稍加推广:我们扩展上述做法的主要问题就是我们暂时不知道怎么找次大值。
我们设 \(p_{x,l,r}\) 表示 \(f(x,l,r)\) 中取到最大值的 \(t\)。然后每次如果我们选到了 \([x,p_{x,l,r}]\),那么我们就往里面加入 \(f(x,l,p_{x,l,r}-1)\) 和 \(f(x,p_{x,l,r}+1,r)\)。因为两者枚举范围的并集就是 \([l,r] \setminus \{p_{x,l,r}\}\),所以次小值一定在里面。
综上时间复杂度为 \(O((n+k)\log n)\)。

浙公网安备 33010602011771号