「总结」分治

其实很多题都是很久以前写的了。
做过的学过的总共就这么几种:
1.普通序列分治
2.CDQ分治
3.线段树分治
4.整体二分
5.点分治

一、普通序列分治

1.array http://hzoj.com/contest/166/problem/2
是之前的考试题了。
当时这道题我不会写单调栈做法,反而用了一种十分鬼畜的分治思路拿到了60分。
特别难写!(当时认为)
我拍了好多组才改对。
首先假设分治区间为([l,r])
已经处理了的区间是([l,mid],[mid+1,r])
设:
(fr[i])([mid+1,r])之间每个位置到(mid+1)之间所有数的最小值。
(tl[i])([l,mid])之间每个位置到(mid)之间所有数的最小值。
(res[i])为在处理区间([l,mid],[mid+1,r])之后第(i)位的答案。
这样的话我们现在只需要考虑跨越了(mid)的区间了。
怎么做呢?
我们发现右侧能够被更新答案的并非全部,因为很可能在某个数([mid+1,i])区间中存在大于(a[i])的数。
这样就不能够更新答案。
于是我们维护一个(lst)值,表示上次更新出的答案的位置,初始(lst=mid+1)
然后在记录一个左指针(L)来表示左侧可以与(i)构成区间的位置。
那么加入左侧指针左移的条件。
首先(L>=l)并且(a[L]<=a[i])
因为我们左侧的能够被更新的(i)所在的数值是单调递增的,那么也就是说(a[i])在递增。
所以左侧的(L)作一个单调指针也无所谓,尽可能的向左侧移动,增加长度即可。
注意这个时候并不是所有的(L)都会更新答案,这个(L)必须要满足(a[L]<=min(tl[L],fr[i])),才能够更新答案。
看起来这样就万事大吉了。
其实不然。
我们发现有意部分贡献是没有被更新的。比如指针(L)左移之前,可能存在一个解是使得(lst+k)这个位置能够被更新,而(L)更新过(lst)之后再次左移走了到了(L-1),
而这个位置并不能更新(lst+k)
怎么处理呢?
我们再次做一个类似(dp)的过程。
通过(lst)转移到(lst+k)
由于(a[lst-tr[lst]+1])一定是区间([lst-tr[lst]+1,mid])之间的最小值,所以可以用这个来更新答案。
但是还要加入判断条件:(a[lst-tr[lst]+1]<=fr[i])才可以更新。
也就是说当前扩展的(L)中最小的一个如果都比我大的话。
那么只能从剩下的位置中扩展了。

二、CDQ分治
CDQ分治是一种针对时间分治的分治算法。
在数据结构的应用里一般情况下是将所有操作离线,按照时间点分治。
之后用时间靠前的修改更新时间靠后的询问的答案。
复杂度是(O(cnlogn))其中(c)是每次操作的代价。
大多数是数状数组进行辅助,复杂度在(O(nlog^2n))
比较经典的是三维偏序问题(虽然不是在时间上的分治不过利用的是前面区间更新后面的套路)。

1.陌上花开
每个元素有三个属性,一朵花的排名为三维均小于等于它的花的个数。
直接三维偏序。
第一维分治。
第二维在归并之后两侧单调指针扫。
第三维用数状数组维护。

2.Mokia
很简单,一个询问拆成四个分别询问。
变成维护右下角的询问。
这样我们又可以转化成三维偏序了。
这次的第一维为时间了。

3.天使玩偶
分四次CDQ,反转坐标系。
由于每一次只找xy均比他小的。
所以直接查询比某个数要小的(x+y)最大的值即可。

4.货币兑换
CDQ的成名题了。
首先考虑提示。
为什么是对的呢?
如果在某一天我们并不全都买入股票,之后的某一天我们选择抛售,那么抛售的价格一定是高于买入的价格的,所以买入必然要尽可能多的买以获得最大的利润。
而如果某一天不全部抛售的话,也同样会导致这一天的利润较小。
考虑一下(n^2)(dp)
十分简单。
我们设(dp[i])为第(i)天手中最多的钱数。
首先的一种转移是(dp[i]=dp[i-1])就是这一天什么也不干。
设之前某一天的买入使得第(i)天手中的(B)股票为(x),(A)股票为(xR_i)
我们枚举这一天(j)
那么:

[dp[j]=A_jR_jx+B_jx ]

[x=frac{dp[j]}{A_jR_j+B_j} ]

所以:

[dp[i]=maxlimits_{j=1}^{i-1}frac{A_iR_jdp[j]}{A_jR_j+B_j}+frac{B_idp[j]}{A_jR_j+B_j} ]

这样可以直接(n^2)转移了。
考虑如何优化。
我们化一下式子。

[egin{aligned}\ dp[i]&=frac{dp[j](A_iR_j+B_i)}{A_jR_j+B_j}\ frac{dp[i]}{B_i}&=frac{A_i}{B_i}frac{dp[j]R_j}{A_jR_j+B_j}+frac{dp[j]}{A_jR_j+B_j}\ end{aligned}]

可以看出是斜率的形式。
我们用点来代表决策:

[(x_i,y_i)=(frac{dp[j]R_j}{A_jR_j+B-j},frac{dp[j]}{A_jR_j+B_j}) ]

这样就可以了。
我们维护斜率优化即可(或者你会维护凸包也行)。
即可个屁。
严重的问题,(x_i)不单调。
那我们就让它单调。
套上(CDQ)分治排个序在进行更新。
这样保证左侧区间(x_i)单调,右侧区间的(frac{A_i}{B_i})单调。
这样就可以愉快的斜率优化了。

三、线段树分治

1.Dash Speed
http://hzoj.com/contest/137/problem/3
这个题也是考试题。
主要思路就是按照值域开线段树,然后对于每条边,将其分为(logn)个区间。
那么进行线段树分治,将到达某个点的路径上所经过的点中所含有的边全部打到并查集上面去。
并查集要用按秩合并来可持久化,维护已经加入的点集中的直径。

四、整体二分
是基于值域的分治算法。
同时对于每一个询问二分。

1.网络 http://hzoj.com/contest/73/problem/7
https://www.cnblogs.com/Lrefrain/p/11620445.html

五、点分治
根据重心来划分树,这样最大的儿子不超过一半,所以最多递归(logn)层,所以复杂度还是(nlogn)
套路有两种,容斥和桶。

1.Tree http://hzoj.com/contest/73/problem/10
这个题就是用容斥的方法。
首先树分治递归下去。
首先求出每个点所控制的点分树中每个点互相之间的贡献。
然后再减去那些以它的儿子及子节点为(lca)的贡献即可。

2.Race http://hzoj.com/contest/73/problem/11
这个题采用树桶的方法。
每次递归到某一个节点的时候,计算以这个节点为(lca)的贡献。
考虑将每个子孙节点距离他的相应距离的点的个数放到桶里。
然后子树归并的时候查询答案即可。

3.聪聪可可 http://hzoj.com/contest/73/problem/12
其实就是求长度为3的倍数的路径数。
是个计数题。
仍旧照上一题的套路,将边长%3开桶直接子树归并或者容斥即可。

4.采药人的路径 http://hzoj.com/contest/73/problem/13
仍然是子树归并。
设黑为1,白为-1。
我们设(tmp[j][0/1])为当前已经归并的子树中边权和为(j)的 不存在/存在 中间一个前缀和为0的节点的方案数。
然后设(dp[j][0/1])为当前将要归并的子树中的边权和为(j)的 不存在/存在 中间一个前缀和为0的节点的方案数。
那么每次统计的时候:

[ans+=dp[0][0]tmp[0][0]+sumlimits_{i=-n}^{n}dp[i][0]tmp[-i][1]+dp[i][1]tmp[-i][0]+dp[i][1]tmp[-i][1] ]

5.开店 https://i-beta.cnblogs.com/posts/edit
由于强制在线所以。
是动态点分治了。
首先由于要求(qlogn)(lca)所以先用(ST)表预处理一下,然后(O(1))查询(lca)
求出他的所有分治子孙的所有点到他的距离,并且按照年龄为关键字(sort)求前缀和。
这样的话可以求出两个数组(sx[x])(sf[x])分别表示到当前的分治节点的距离前缀和以及到当前节点分治父亲的距离前缀和。
这样可以从点分树上从下向上直接容斥了。
也就是说用(sx[fa[x]]-sf[x])来代表除了当前子树外的所有子树的贡献。
这样容斥就好了。

6.幻想乡战略游戏 http://hzoj.com/contest/73/problem/15
在线带修求带权重心。
一样是(ST)表。
维护三个值。

[sx[x],sf[x],su[x]$$分别代表:从$x$的点分子树中到达x的距离和,从x的点分子树到x的分治父亲的距离和,x的点分子树大小。 这样对于某一个点。 可以在$logn$时间复杂度内完成查询和修改。 查询怎么办呢? 从树根向下暴力跳$logn$次,能跳的时候就跳,直到不能跳为止就到达了最优答案处。 这样复杂度是:$O(20nlog^2n)$ >]

原文地址:https://www.cnblogs.com/Lrefrain/p/12096251.html