普通分治
通过将区间分成两个区间,来将问题分成两个⼦问题求解
来康一些经典问题:
- 求所有区间的最⼤大值之和
- 计算mid,然后对于每个区间分成两个区间递归,边界显然是1
- 对于每个区间,设第一次计算固定左端点 \(l_1\) ,考虑\(O(1)\)求出所有的以\(l_1\)为左端点跨越mid的最大值
- 只需要先On处理出\(l_1 - mid\) 的最大值\(maxl\),再每次求出右边第一个比\(maxl\)大的位置\(maxr\),然后求出区间最大值前缀和\(f(i)\)
- 答案即为\((maxr-1-(mid+1)+1)*maxl+f[r]-f[maxr-1]\)
- 右边同理
-
求最大最小乘积之和
- 跟上面差不多,考虑求出左边每个位置\(maxl,miml\),求出右边第一个比他们大/小的位置,然后区间分类讨论,求一些前缀和即可
- 显然max,min单调增/减,右边位置单调增,用指针记录一下
-
gcd之和
- 考虑跨越中间的\(gcd\)之和
- \(gcd\)在枚举的区间左端点向左移动的过程中单减
- 考虑每次变化,必定除掉\(a[mid]\)的一个因子,故只有\(\log\)种取值
- 然后有:枚举左右\(gcd\),这样是每次\(n\log n+\log ^3 n\)的
-
平面最近点对
非常naiive,不想写
-
zz分治多项式
按\(a^{\frac{x}{2}}\)分类,也很naiive
复杂度计算
- 每个区间\(On\)
这样理解:每次\(On\),\(\log n\)层
-
每个区间\(n^2\)
\[f(n)=2*f((n/2)^2)+n^2 = n^2 \]下一层复杂度会指数级递减,然后总的大概是\(n^2\)
-
\(nlogn\)以上与2类似
例题选讲
旅行者
考虑把一个矩形切两半,然后枚举询问
如果询问两点都在同侧,递归处理
否则一定被这条线上的某个点更新,枚举这条线上的点求当前矩形内最短路更新dis即可
\(dis[a][b]=max(dis[a][b],dis[a][x]+dis[b][x])\)
优化:每次切长边,即保证了求最短路的次数不超过sqrt(m)甚至更小
复杂度证明:
第五步之后现场学习了一下定积分。。。然后直接计算器算了
连续区间
给定⼀个排列 p[1…n],求有⼏个区间 [L,R] 满⾜ p[L…R]
排序后是连续的
变化为求有多少个区间\(r-l+1=max[l...r]-min[l...r]+1\)
然后转化为\(r=max-min+l\)
然后分类讨论 \(r\) 的范围
枚举左端点 \(l\) , 求出左边的\(minl , maxl\) ,右边第一个大于/小于左边最大/最小值的位置\(minr,maxr\)
分情况讨论:
-
\(r \in [mid+1 , min(minr,maxr)-1]\) ,即最大最小值都在左边,于是直接判断\(l-minl+maxl\)是否在当前区间内
-
\(r \in [min(minr,maxr),max(minr,maxr)]\) ,最大最小中有一个在左边一个在右边,先假设\(minr<maxr\)
则此时最小值与左边无关,可以先处理出\(mid+1 - r\)的每个点的前缀最小值,用r单调递增判断\(r-max\)的范围与\(l-min\)的区间交即可(有些细节不写了
-
\(maxr<minr\)同理,处理出前缀最大值
-
\(r \in [max(minr,maxr),R]\) 类似地,运用前面\(max-min\)随\(r\)变大而变大 ,故求区间交
最后递归处理子区间,复杂度显然\(N\log N\)
想了好久。。。
XOR - MST
给定 n 个点,第 i 个点的点权是 a[1…n],现在定义边 (i,j)
的权值是 a[i] xor a[j],求最⼩⽣成树
考虑按最高位分成两个点集,然后两部分分别求\(MST\),然后找出两部分边权XOR最小连边
思路挺naiive的,但是实现好像要trie树?
最近不打算敲代码
区间统计
给定 a[1…n],求有⼏个区间 [L,R] 满⾜ a[L] or a[L+1]…or
a[R]>max(a[L…R])
要使 \(x=max\) ,即对于可行区间, \(y\) or \(x=y\)
统计\(x\)往左/往右 \(x\) xor \(a[i]= x\)的 满足条件的 \(i\) 的最小/最大值,然后乘一下算出区间总数
每次枚举最大值\(x\)即可
好像不用分治?(还是我口胡了望大佬指正
O(1)计算第二步方法:处理\(pos[i][k]\)为第k位为1在第i个数及之前的最后出现的位置
相似地,处理最早出现的位置,答案就很显然了
二分答案
——对答案分治
分数规划
有一些二元组\((a_i,b_i)\),从中选取一些二元组,使得\(\frac{\sum a_i} {\sum b_i}\)最大(最小
每次二分\(mid \in (0,\frac{\sum a[i]>0}{min(b_i)}]\) ,判断答案是否\(\geq mid\) (范围口胡的,视情况而定
也即 $$\frac{\sum a[i]}{\sum b[i]} \geq mid$$
转化为
好像 \(check\) 贪心就可以了吧?不是很懂
如果有固定的取的数量 \(k\) 排序取前 \(k\) 个就可以了
最小区间圆覆盖
首先要知道求一个区间圆覆盖复杂度是O(n)的
由于加点半径必定不下降,于是二分最后加点半径\(<=s\) 的右端点\(r\),之后贪心地对\([r+1,R]\)这个区间再递归进行二分操作
然后估出一个二分的上界,每次\(check(l+2^i)\) ,不行就记录为上界且退出
总复杂度\(\sum ^k_1 len_i*\log n = n\log n\)
估上界和二分都是\(\log n\)的啊QwQ 而且常数不大
整体二分
二分 \(mid\),判断每个询问答案是否\(\leq mid\)
每个询问 \(K\) 小值 \(\le mid\)
即 \(\leq mid\) 的数有没有 \(K\) 个
对于添加操作分情况讨论:
- \(c_i > mid\) 跳过
- \(c_i \leq mid+1\) 求区间之和,搞树状数组就可以了,时间顺序处理添加和询问
CDQ分治
经典离线算法!吊打主席树
精髓是要计算一边区间对另一边区间的价值
三维偏序问题
一开始有点难理解,康了康很多博客,说下我自己理解
设三个维度为\(x_i,y_i,z_i\)
- 对于\(l\)到\(r\)这段区间,如果想求解对于每一个\(i\in [l, r]\),有多少个\(j \in [l, r] ,i≠j\) 满足 \(i\) 的三个维度全部大于 \(j\) 的。
- 那么我们可以考虑,整段按照\(x\)从小到大排序,这样左边的就必定不会对右边产生贡献。先分治\([l,mid]\)与\([mid+1, r]\)这两段,然后再把右边的贡献加上即为这一段的答案。
- 于是,第一维此时就没有用了,只看 \(y\) 与 \(z\)。
- 参照归并排序思想,我们给左右两边按 \(y\) 排为有序,于是就可以归并+二维偏序BIT的方法去处理贡献。
- 即归并时插入的每一个右边的数,BIT记录已经插入的左边的数的第三位,搞区间查询即可
- 值得注意的是如果是多维偏序,可以继续套用CDQ,相应的,复杂度乘上 \(\log n\)
这下不难了吧
矩阵加,矩阵求和
首先矩阵求和转化为矩阵前缀和,\(naiive\)的技巧
首先考虑把加点操作分成三维,即\(x_i,y_i,t_i\)这三维
则在\(t_j\) 时间进行的\(x_j,y_j\)的前缀和询问操作
要加上对于\(x_i<x_j , y_i <y_j,t_i<t_j\)的添加操作的权值\(c_i\)
这就是经典的三维偏序问题了,只是BIT单点添加1的操作变成添加权值
缺 1 背包问题
考虑\(f[i][j]\)为缺少\((i,j)\)这个区间的最大价值和
则每个点答案为\(f[i][i]\)
怎么求 \(f\) 呢?
对于区间\((l,mid)\),它只能选从父亲计算时传下来的区间以及\((mid+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\)层,每层是\(O(n)\)甚至更小的时间复杂度,总复杂度\(O(n \log n)\)
但是万一树退化成链,不就是\(O(n^2)\)?
那么我们每次找子树的重心就可以了
由于树每次分成<=n/2的⼦树,于是也是\(\log n\)层的
复杂度跟区间分治分析差不多,主要在于get_ans的复杂度
统计答案的时候还可以顺便处理子节点的一部分答案信息
经典问题
-
求所有边数\(\le L\) 的链的权值之和
模板题
getans就dfs一遍就好了
主要是getroot函数重点理解
-
求到 \(x\) 边权\(\le L\) 的点的个数之和
也是模板题
讲下getans咋写
- dfs出每个vis=0的点到它的距离
- 排个序
- 双指针,判断如果\(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]\) 和价值 \(V[i]\),求价值最
⼤的重量不超过 S 的连通块,\(n,S\le 2000\)点分治后考虑必须经过 \(x\),求解原问题
显然树形dp
设计状态\(f[S]\)
对于父亲不选的,儿子也不能选(联通块)
则从上到下dfs地枚举每条边,然后枚举V,显然是父亲被儿子更新,枚举选或者不选
\[f[V]=max(f[V-V[son]]+w[son],f[V]) \]