【总结】cdq分治
Part 1.
拦截导弹
首先考虑用 cdq分治 求解 LIS。 i i i 对 j j j 的贡献可以看做 d p j = m a x ( d p j , d p i + 1 ) dp_j=max(dp_j,dp_i+1) dpj=max(dpj,dpi+1) 。这样的话可以考虑先求解 c d q ( 1 , m i d ) cdq(1,mid) cdq(1,mid) ,再计算跨区间的贡献,最后计算 c d q ( m i d + 1 , r ) cdq(mid+1,r) cdq(mid+1,r) 。归并排序维护数值的有序性,注意到左半区间是有序的,而右半区间是无序的,如果将右半区间排序的话,就分不清到底是左边贡献右边还是右边贡献左边了。所以要对左右区间分别排序。准确地说,只需要临时将右区间排序。
然后本题的式子是 f j = m a x ( f j , f i + 1 ) f_j=max(f_j,f_i+1) fj=max(fj,fi+1) , g j = ∑ g i ( f j = f i + 1 ) g_j=\sum{g_i}(f_j=f_i+1) gj=∑gi(fj=fi+1)
可以正着倒着跑一遍,这样每一个点的方案数为 g i g 2 i g_ig2_i gig2i ,总方案数为 ∑ g i ( f i = t o t ) \sum{g_i}(f_i=tot) ∑gi(fi=tot) ,注意这里不能是 ∑ g i g 2 i ( f i + f 2 i − 1 = t o t ) \sum{g_ig2_i}(f_i+f2_i-1=tot) ∑gig2i(fi+f2i−1=tot) ,因为计算的是单个点出现时的方案数,显然会算重。
然后左右区间排序时要用 t m p tmp tmp 临时数组,存一下下标,再还原回去。因为此时 c d q ( m i d + 1 , r ) cdq(mid+1,r) cdq(mid+1,r) 还没有处理,会打乱第一维的顺序。
当然下面这道题是可以直接将 q u e r y query query 数组排序的,因为左右子区间已经处理完了,可以合并成新的无序区间。
动态逆序对
话说这道题用二维树状数组不是很显然吗。时间复杂度 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)。
然后 c d q cdq cdq 分治会保证 a a a 单调递增,内部用一维树状数组维护,再加上时间戳的限制,大概也差不多。
总的来说,就是把序列删掉后剩下的数当成修改操作加进去,每次操作就拆分成一次查询和一次修改。
然后这里注意一个事实,就是修改操作必须先计算出增加它后的贡献,再算给查询操作,所以计算修改还是单点对单点,贡献为1,只不过多加了计算全局贡献的查询操作,用 s u m sum sum 维护一下就行了。
注意到一个细节问题,那就是查询操作没有实际意义,无法比较大小,那么每次归并时应该放在最前面。
然后发现一个问题,就是这里元素的贡献是有序对,也就是说第一维必须严格按照 a a a 的递减排序,这里时间戳的顺序是不成立的。
那么问题来了,每次修改的元素是无序的呀,可能前面的数更新它,总不能重新给它插入一个合适的位置吧。
然后我们发现如果计算两次逆序对,那么恰好会被计算一次。
然后你发现查询操作查询操作比较麻烦,因为修改操作的贡献在改变,你不知道什么时候累加了修改操作的贡献。
所以把前一部分做出来就行了,后一部分直接 c d q cdq cdq 分治后算一个前缀和即可。
然后就是本题给的是个排列,删除的是元素值。
总结
c d q cdq cdq 分治没有固定的写法,要看应用在怎样的背景中,多动动脑就想明白了。
Part 2.
[NOI2007] 货币兑换
A k y + B k x = I P A_ky+B_kx=IP Aky+Bkx=IP
y = R a t e x y=Rate_x y=Ratex
x = I P A k R a t e k + B k , y = I P ∗ R a t e k A K R a t e k + B k x=\frac{IP}{A_kRate_k+B_k},y=\frac{IP*Rate_k}{A_KRate_k+B_k} x=AkRatek+BkIP,y=AKRatek+BkIP∗Ratek
d p i = d p j + y A i + x B i = d p j + B i ( x + y A i B i ) dp_i=dp_j+yA_i+xB_i=dp_j+B_i(x+y\frac{A_i}{B_i}) dpi=dpj+yAi+xBi=dpj+Bi(x+yBiAi)
这不就是一个斜率优化嘛。考虑用李超线段树怎么做,我们知道李超树用于查询 x = k x=k x=k 时的线段上的最值。右边式子恰好是 y = a x + b y=ax+b y=ax+b 的形式,取 x = A i B i x=\frac{A_i}{B_i} x=BiAi 即可。
城市建设
必须辅助数据结构维护一些东西。
我们把这张图划分成两个部分,动态边 和 静态边。
如果 c d q cdq cdq 考虑 ( l , r ) (l,r) (l,r) 这一段操作序列,那么 l l l, r r r 内操作涉及的边就称为动态边,这张图其余的边被称为静态边。
很冒险,但是很宏大。
我们考虑简化静态边集合。将不会出现的边从静态边集中删去,将一定会出现的边在静态边集中缩点。(话说有毛用?一头雾水。)
- 如果把区间内的边全部修改为 -inf ,做mst,树上的其他边就是必须选的。(换句话说,动态边就只有r-l个,你静态边缩点出来的点的个数必须比它小,你静态边连出来的联通块可以看成一个点)
- 如果把区间内的边全部修改为 inf,做mst,非树上的其他边就是必定不选的。(换句话说,能用的静态边最多也只有r-l个)
这一步保证了 ∣ V ∣ , ∣ E ∣ = O ( r − l ) |V|,|E|=O(r-l) ∣V∣,∣E∣=O(r−l) 。
我们发现,会有一些动态边变为静态边,最后变成了一个纯静态的 k r u s k a l kruskal kruskal 。
边界条件:l==r或n=1。
注意, c d q cdq cdq 并没有脱离整体而存在,实际上它的研究整体依然是整个边集,这一点可以从暴力传参看出。
比较恶心的一点是缩点过后的序号和边并不对应,所以事实上我们要将并查集保留下来。然后最tm坑的是因为我们在子区间中都是操作的并查集的根,所以事先要把 所有边 的两个端点和根连起来,但不是真正赋值(我说了只能是利用并查集,不能改变自身)。然后tm并查集是可撤销的,在做完左区间回来时,只用把代表的爸爸变回自己即可。
第三遍重构了。。。
我tm不知道这个题为什么不能打路径压缩,哦对了,所有带撤销的并查集都不能这么干(如线段树分治),因为会造成父子信息的丢失。注意 带撤销的并查集不等于可持久化并查集。
总结
c d q cdq cdq 分治的思想博大精深,可以是将动态问题转化成静态问题,也可以是基于分治时间戳的玄学算法,总之目的在于将每次问题求解的规模控制在 O ( r − l ) O(r-l) O(r−l) ,这样很多不合理的暴力算法就变得可能了。
__EOF__

本文链接:https://www.cnblogs.com/cqbzly/p/17530364.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· DeepSeek 开源周回顾「GitHub 热点速览」