普通分治
通过将区间分成两个区间,来将问题分成两个⼦问题求解
来康一些经典问题:
- 求所有区间的最⼤大值之和
- 计算mid,然后对于每个区间分成两个区间递归,边界显然是1
- 对于每个区间,设第一次计算固定左端点 l 1 l_1 l1 ,考虑 O ( 1 ) O(1) O(1)求出所有的以 l 1 l_1 l1为左端点跨越mid的最大值
- 只需要先On处理出 l 1 − m i d l_1 - mid l1−mid 的最大值 m a x l maxl maxl,再每次求出右边第一个比 m a x l maxl maxl大的位置 m a x r maxr maxr,然后求出区间最大值前缀和 f ( i ) f(i) f(i)
- 答案即为 ( m a x r − 1 − ( m i d + 1 ) + 1 ) ∗ m a x l + f [ r ] − f [ m a x r − 1 ] (maxr-1-(mid+1)+1)*maxl+f[r]-f[maxr-1] (maxr−1−(mid+1)+1)∗maxl+f[r]−f[maxr−1]
- 右边同理
-
求最大最小乘积之和
- 跟上面差不多,考虑求出左边每个位置 m a x l , m i m l maxl,miml maxl,miml,求出右边第一个比他们大/小的位置,然后区间分类讨论,求一些前缀和即可
- 显然max,min单调增/减,右边位置单调增,用指针记录一下
-
gcd之和
- 考虑跨越中间的 g c d gcd gcd之和
- g c d gcd gcd在枚举的区间左端点向左移动的过程中单减
- 考虑每次变化,必定除掉 a [ m i d ] a[mid] a[mid]的一个因子,故只有 log \log log种取值
- 然后有:枚举左右 g c d gcd gcd,这样是每次 n log n + log 3 n n\log n+\log ^3 n nlogn+log3n的
-
平面最近点对
非常naiive,不想写
-
zz分治多项式
按 a x 2 a^{\frac{x}{2}} a2x分类,也很naiive
复杂度计算
- 每个区间 O n On On
f ( n ) = 2 ∗ f ( n / 2 ) + n = n log n f(n)=2*f(n/2)+n = n\log n f(n)=2∗f(n/2)+n=nlogn
这样理解:每次 O n On On, log n \log n logn层
-
每个区间 n 2 n^2 n2
f ( n ) = 2 ∗ f ( ( n / 2 ) 2 ) + n 2 = n 2 f(n)=2*f((n/2)^2)+n^2 = n^2 f(n)=2∗f((n/2)2)+n2=n2下一层复杂度会指数级递减,然后总的大概是 n 2 n^2 n2
-
n l o g n nlogn nlogn以上与2类似
例题选讲
旅行者
考虑把一个矩形切两半,然后枚举询问
如果询问两点都在同侧,递归处理
否则一定被这条线上的某个点更新,枚举这条线上的点求当前矩形内最短路更新dis即可
d i s [ a ] [ b ] = m a x ( d i s [ a ] [ b ] , d i s [ a ] [ x ] + d i s [ b ] [ x ] ) dis[a][b]=max(dis[a][b],dis[a][x]+dis[b][x]) dis[a][b]=max(dis[a][b],dis[a][x]+dis[b][x])
优化:每次切长边,即保证了求最短路的次数不超过sqrt(m)甚至更小
复杂度证明:
T
(
S
)
=
2
T
(
S
2
)
+
O
(
S
1.5
log
(
S
)
)
T(S)=2T(\frac{S}{2})+O(S^{1.5}\log (S))
T(S)=2T(2S)+O(S1.5log(S))
T ( S ) = ∑ a = 0 log 2 S 2 a ( S 2 a ) 1.5 log S 2 a T(S) = \sum_{a=0}^{\log_2S}{2^a(\frac{S}{2^a})^{1.5} \log \frac{S}{2^a}} T(S)=a=0∑log2S2a(2aS)1.5log2aS
T ( S ) = ∑ a = 0 log 2 S 2 − 0.5 a S 1.5 ( log S − a ) T(S)= \sum_{a=0}^{\log_2S}{2^{-0.5a}S^{1.5}(\log S-a)} T(S)=a=0∑log2S2−0.5aS1.5(logS−a)
≤ ∑ a = 0 log 2 S 2 − 0.5 a S 1.5 \leq \sum_{a=0}^{\log_2S}2^{-0.5a}S^{1.5} ≤a=0∑log2S2−0.5aS1.5
= S 1.5 log S ∑ a = 0 log 2 S 2 − 0.5 a = S^{1.5}\log S\sum_{a=0}^{\log_2S}2^{-0.5a} =S1.5logSa=0∑log2S2−0.5a
≤ S 1.5 log S ∫ 0 log 2 S 2 − 0.5 x d x \leq S^{1.5}\log S\int_0^{\log_2S}2^{-0.5x}dx ≤S1.5logS∫0log2S2−0.5xdx
= S 1.5 log S ( − 2.89 e − 0.35 log 2 S + 2.89 ) = S^{1.5}\log S (-2.89e^{-0.35\log_2S}+2.89) =S1.5logS(−2.89e−0.35log2S+2.89)
= O ( S 1.5 log S ) = O(S^{1.5}\log S) =O(S1.5logS)
第五步之后现场学习了一下定积分。。。然后直接计算器算了
连续区间
给定⼀个排列 p[1…n],求有⼏个区间 [L,R] 满⾜ p[L…R]
排序后是连续的
变化为求有多少个区间 r − l + 1 = m a x [ l . . . r ] − m i n [ l . . . r ] + 1 r-l+1=max[l...r]-min[l...r]+1 r−l+1=max[l...r]−min[l...r]+1
然后转化为 r = m a x − m i n + l r=max-min+l r=max−min+l
然后分类讨论 r r r 的范围
枚举左端点 l l l , 求出左边的 m i n l , m a x l minl , maxl minl,maxl ,右边第一个大于/小于左边最大/最小值的位置 m i n r , m a x r minr,maxr minr,maxr
分情况讨论:
-
r ∈ [ m i d + 1 , m i n ( m i n r , m a x r ) − 1 ] r \in [mid+1 , min(minr,maxr)-1] r∈[mid+1,min(minr,maxr)−1] ,即最大最小值都在左边,于是直接判断 l − m i n l + m a x l l-minl+maxl l−minl+maxl是否在当前区间内
-
r ∈ [ m i n ( m i n r , m a x r ) , m a x ( m i n r , m a x r ) ] r \in [min(minr,maxr),max(minr,maxr)] r∈[min(minr,maxr),max(minr,maxr)] ,最大最小中有一个在左边一个在右边,先假设 m i n r < m a x r minr<maxr minr<maxr
则此时最小值与左边无关,可以先处理出 m i d + 1 − r mid+1 - r mid+1−r的每个点的前缀最小值,用r单调递增判断 r − m a x r-max r−max的范围与 l − m i n l-min l−min的区间交即可(有些细节不写了
-
m a x r < m i n r maxr<minr maxr<minr同理,处理出前缀最大值
-
r ∈ [ m a x ( m i n r , m a x r ) , R ] r \in [max(minr,maxr),R] r∈[max(minr,maxr),R] 类似地,运用前面 m a x − m i n max-min max−min随 r r r变大而变大 ,故求区间交
最后递归处理子区间,复杂度显然 N log N N\log N NlogN
想了好久。。。
XOR - MST
给定 n 个点,第 i 个点的点权是 a[1…n],现在定义边 (i,j)
的权值是 a[i] xor a[j],求最⼩⽣成树
考虑按最高位分成两个点集,然后两部分分别求 M S T MST MST,然后找出两部分边权XOR最小连边
思路挺naiive的,但是实现好像要trie树?
最近不打算敲代码
区间统计
给定 a[1…n],求有⼏个区间 [L,R] 满⾜ a[L] or a[L+1]…or
a[R]>max(a[L…R])
要使 x = m a x x=max x=max ,即对于可行区间, y y y or x = y x=y x=y
统计 x x x往左/往右 x x x xor a [ i ] = x a[i]= x a[i]=x的 满足条件的 i i i 的最小/最大值,然后乘一下算出区间总数
每次枚举最大值 x x x即可
好像不用分治?(还是我口胡了望大佬指正
O(1)计算第二步方法:处理 p o s [ i ] [ k ] pos[i][k] pos[i][k]为第k位为1在第i个数及之前的最后出现的位置
相似地,处理最早出现的位置,答案就很显然了
二分答案
——对答案分治
分数规划
有一些二元组 ( a i , b i ) (a_i,b_i) (ai,bi),从中选取一些二元组,使得 ∑ a i ∑ b i \frac{\sum a_i} {\sum b_i} ∑bi∑ai最大(最小
每次二分 m i d ∈ ( 0 , ∑ a [ i ] > 0 m i n ( b i ) ] mid \in (0,\frac{\sum a[i]>0}{min(b_i)}] mid∈(0,min(bi)∑a[i]>0] ,判断答案是否 ≥ m i d \geq mid ≥mid (范围口胡的,视情况而定
也即 ∑ a [ i ] ∑ b [ i ] ≥ m i d \frac{\sum a[i]}{\sum b[i]} \geq mid ∑b[i]∑a[i]≥mid
转化为
∑
a
[
i
]
≥
∑
b
[
i
]
∗
m
i
d
\sum a[i] \geq \sum b[i]*mid
∑a[i]≥∑b[i]∗mid
∑ a [ i ] − ∑ b [ i ] ∗ m i d > = 0 \sum a[i]-\sum b[i]*mid >=0 ∑a[i]−∑b[i]∗mid>=0
∑ ( a [ i ] − b [ i ] ∗ m i d ) > = 0 \sum (a[i]-b[i]*mid)>=0 ∑(a[i]−b[i]∗mid)>=0
好像 c h e c k check check 贪心就可以了吧?不是很懂
如果有固定的取的数量 k k k 排序取前 k k k 个就可以了
最小区间圆覆盖
首先要知道求一个区间圆覆盖复杂度是O(n)的
由于加点半径必定不下降,于是二分最后加点半径 < = s <=s <=s 的右端点 r r r,之后贪心地对 [ r + 1 , R ] [r+1,R] [r+1,R]这个区间再递归进行二分操作
然后估出一个二分的上界,每次 c h e c k ( l + 2 i ) check(l+2^i) check(l+2i) ,不行就记录为上界且退出
总复杂度 ∑ 1 k l e n i ∗ log n = n log n \sum ^k_1 len_i*\log n = n\log n ∑1kleni∗logn=nlogn
估上界和二分都是 log n \log n logn的啊QwQ 而且常数不大
整体二分
二分 m i d mid mid,判断每个询问答案是否 ≤ m i d \leq mid ≤mid
每个询问 K K K 小值 ≤ m i d \le mid ≤mid
即 ≤ m i d \leq mid ≤mid 的数有没有 K K K 个
对于添加操作分情况讨论:
- c i > m i d c_i > mid ci>mid 跳过
- c i ≤ m i d + 1 c_i \leq mid+1 ci≤mid+1 求区间之和,搞树状数组就可以了,时间顺序处理添加和询问
把 [L,R] 分成 [L,mid] 和 [mid+1,R]
考虑左边对右边的贡献
CDQ分治
经典离线算法!吊打主席树
精髓是要计算一边区间对另一边区间的价值
三维偏序问题
一开始有点难理解,康了康很多博客,说下我自己理解
设三个维度为 x i , y i , z i x_i,y_i,z_i xi,yi,zi
- 对于 l l l到 r r r这段区间,如果想求解对于每一个 i ∈ [ l , r ] i\in [l, r] i∈[l,r],有多少个 j ∈ [ l , r ] , i ≠ j j \in [l, r] ,i≠j j∈[l,r],i̸=j 满足 i i i 的三个维度全部大于 j j j 的。
- 那么我们可以考虑,整段按照 x x x从小到大排序,这样左边的就必定不会对右边产生贡献。先分治 [ l , m i d ] [l,mid] [l,mid]与 [ m i d + 1 , r ] [mid+1, r] [mid+1,r]这两段,然后再把右边的贡献加上即为这一段的答案。
- 于是,第一维此时就没有用了,只看 y y y 与 z z z。
- 参照归并排序思想,我们给左右两边按 y y y 排为有序,于是就可以归并+二维偏序BIT的方法去处理贡献。
- 即归并时插入的每一个右边的数,BIT记录已经插入的左边的数的第三位,搞区间查询即可
- 值得注意的是如果是多维偏序,可以继续套用CDQ,相应的,复杂度乘上 log n \log n logn
这下不难了吧
矩阵加,矩阵求和
首先矩阵求和转化为矩阵前缀和, n a i i v e naiive naiive的技巧
首先考虑把加点操作分成三维,即 x i , y i , t i x_i,y_i,t_i xi,yi,ti这三维
则在 t j t_j tj 时间进行的 x j , y j x_j,y_j xj,yj的前缀和询问操作
要加上对于 x i < x j , y i < y j , t i < t j x_i<x_j , y_i <y_j,t_i<t_j xi<xj,yi<yj,ti<tj的添加操作的权值 c i c_i ci
这就是经典的三维偏序问题了,只是BIT单点添加1的操作变成添加权值
缺 1 背包问题
考虑 f [ i ] [ j ] f[i][j] f[i][j]为缺少 ( i , j ) (i,j) (i,j)这个区间的最大价值和
则每个点答案为 f [ i ] [ i ] f[i][i] f[i][i]
怎么求 f f f 呢?
对于区间 ( l , m i d ) (l,mid) (l,mid),它只能选从父亲计算时传下来的区间以及 ( m i d + 1 , r ) (mid+1,r) (mid+1,r)而不能选它自己
这样每次复制一个数组,为计算好的结果,然后再把 ( m i d + 1 , r ) (mid+1,r) (mid+1,r)背包一遍
( m i d + 1 , r ) (mid+1,r) (mid+1,r)的处理同理
然后下传当前结果f,分治求出$f[i][mid] 和 和 和f[mid+1][j]$的答案
缺点最短路
自己yy了下,好像跟上一个差不多
就是枚举不选哪些点floyed就行了,然后把每次要处理的点进行floyed更新dis
同样分治处理
点分治
淀粉质不可或缺
区间分治的复杂度保证是由于每次分成<=n/2的区间
现在考虑树形结构处理关于链的问题,枚举是否经过当前点
如果是满二叉树的话,每次处理当前答案get_ans,然后把问题交给子节点处理
显然这样只有 log n \log n logn层,每层是 O ( n ) O(n) O(n)甚至更小的时间复杂度,总复杂度 O ( n log n ) O(n \log n) O(nlogn)
但是万一树退化成链,不就是 O ( n 2 ) O(n^2) O(n2)?
那么我们每次找子树的重心就可以了
由于树每次分成<=n/2的⼦树,于是也是 log n \log n logn层的
复杂度跟区间分治分析差不多,主要在于get_ans的复杂度
统计答案的时候还可以顺便处理子节点的一部分答案信息
经典问题
-
求所有边数 ≤ L \le L ≤L 的链的权值之和
傻逼模板题
getans就dfs一遍就好了
主要是getroot函数重点理解
-
求到 x x x 边权 ≤ L \le L ≤L 的点的个数之和
也是模板题
讲下getans咋写
- dfs出每个vis=0的点到它的距离
- 排个序
- 双指针,判断如果 d i s [ l ] + d i s [ r ] > l dis[l]+dis[r]>l dis[l]+dis[r]>l 则 r–,否则答案+=(r-l),l++
- 注意这样计算了一条边走两次的情况,要消除影响
因为之前学过,就放一下代码吧
void findrt(long long now,long long fa){ sz[now]=1,son[now]=0; for(register long long i=head[now];i;i=edge[i].nex){ long long v=edge[i].v; if(vis[v]||v==fa) continue; findrt(v,now); sv=sz[v]; sz[now]+=sv; if(sv>son[now]) son[now]=sv; } szn=size-sz[now]; if(szn>son[now]) son[now]=szn; if(son[now]<srt){ root=now; srt=son[now]; } } void dfs(long long now,long long fa,long long l){ dis[++tot]=l; for(register long long i=head[now];i;i=edge[i].nex){ long long v=edge[i].v; if(vis[v]||v==fa) continue; dfs(v,now,l+edge[i].w); } } inline void get_ans(long long now,long long l,long long c){ tot=0; dfs(now,0,l); sort(dis+1,dis+tot+1); long long lt=1,rt=tot; while(lt<=rt){ if(dis[lt]+dis[rt]<=m){ ans+=c*(rt-lt);lt++; }else rt--; } } inline void divide(long long now){ vis[now]=1; get_ans(now,0,1); for(register long long i=head[now];i;i=edge[i].nex){ long long v=edge[i].v; if(vis[v]) continue; get_ans(v,edge[i].w,-1); size=sz[v]; root=0; srt=n; findrt(v,now); divide(root); } }
-
给定⼀棵树,每个点有物品重量 W [ i ] W[i] W[i] 和价值 V [ i ] V[i] V[i],求价值最
⼤的重量不超过 S 的连通块, n , S ≤ 2000 n,S\le 2000 n,S≤2000点分治后考虑必须经过 x x x,求解原问题
显然树形dp
设计状态 f [ S ] f[S] f[S]
对于父亲不选的,儿子也不能选(联通块)
则从上到下dfs地枚举每条边,然后枚举V,显然是父亲被儿子更新,枚举选或者不选
f [ V ] = m a x ( f [ V − V [ s o n ] ] + w [ s o n ] , f [ V ] ) f[V]=max(f[V-V[son]]+w[son],f[V]) f[V]=max(f[V−V[son]]+w[son],f[V])
后记
不保证全部正确。。但是看起来都hin正确,有疑惑的答疑问了dls
有不对的请私信 or 评论,会第一时间修改
最好还是尝试自己去多理解,真的有用啊QwQ
来都来了,点个赞⑧