分治专题

toc

普通分治

通过将区间分成两个区间,来将问题分成两个⼦问题求解

来康一些经典问题:

  1. 求所有区间的最⼤大值之和
  • 计算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 l1mid 的最大值 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] (maxr1(mid+1)+1)maxl+f[r]f[maxr1]
  • 右边同理
  1. 求最大最小乘积之和

    • 跟上面差不多,考虑求出左边每个位置 m a x l , m i m l maxl,miml maxl,miml,求出右边第一个比他们大/小的位置,然后区间分类讨论,求一些前缀和即可
    • 显然max,min单调增/减,右边位置单调增,用指针记录一下
  2. 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
  3. 平面最近点对

    非常naiive,不想写

  4. zz分治多项式

    a x 2 a^{\frac{x}{2}} a2x分类,也很naiive

复杂度计算

  1. 每个区间 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)=2f(n/2)+n=nlogn

​ 这样理解:每次 O n On On log ⁡ n \log n logn

  1. 每个区间 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)=2f((n/2)2)+n2=n2

    下一层复杂度会指数级递减,然后总的大概是 n 2 n^2 n2

  2. n l o g n nlogn nlogn以上与2类似

例题选讲

旅行者

ZJOI2016

考虑把一个矩形切两半,然后枚举询问

如果询问两点都在同侧,递归处理

否则一定被这条线上的某个点更新,枚举这条线上的点求当前矩形内最短路更新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=0log2S2a(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=0log2S20.5aS1.5(logSa)

≤ ∑ 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=0log2S20.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=0log2S20.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.5logS0log2S20.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.89e0.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 rl+1=max[l...r]min[l...r]+1

然后转化为 r = m a x − m i n + l r=max-min+l r=maxmin+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

​ 分情况讨论:

  1. 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 lminl+maxl是否在当前区间内

  2. 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 &lt; m a x r minr&lt;maxr minr<maxr

    则此时最小值与左边无关,可以先处理出 m i d + 1 − r mid+1 - r mid+1r的每个点的前缀最小值,用r单调递增判断 r − m a x r-max rmax的范围与 l − m i n l-min lmin的区间交即可(有些细节不写了

  3. m a x r &lt; m i n r maxr&lt;minr maxr<minr同理,处理出前缀最大值

  4. 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 maxmin 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} biai最大(最小

每次二分 m i d ∈ ( 0 , ∑ a [ i ] &gt; 0 m i n ( b i ) ] mid \in (0,\frac{\sum a[i]&gt;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 &gt; = 0 \sum a[i]-\sum b[i]*mid &gt;=0 a[i]b[i]mid>=0

∑ ( a [ i ] − b [ i ] ∗ m i d ) &gt; = 0 \sum (a[i]-b[i]*mid)&gt;=0 (a[i]b[i]mid)>=0

好像 c h e c k check check 贪心就可以了吧?不是很懂

如果有固定的取的数量 k k k 排序取前 k k k 个就可以了

最小区间圆覆盖

首先要知道求一个区间圆覆盖复杂度是O(n)的

由于加点半径必定不下降,于是二分最后加点半径 &lt; = s &lt;=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 1klenilogn=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

对于添加操作分情况讨论:

  1. c i &gt; m i d c_i &gt; mid ci>mid 跳过
  2. c i ≤ m i d + 1 c_i \leq mid+1 cimid+1 求区间之和,搞树状数组就可以了,时间顺序处理添加和询问

把 [L,R] 分成 [L,mid] 和 [mid+1,R]
考虑左边对右边的贡献

CDQ分治

经典离线算法!吊打主席树

精髓是要计算一边区间对另一边区间的价值

三维偏序问题

一开始有点难理解,康了康很多博客,说下我自己理解

​ 设三个维度为 x i , y i , z i x_i,y_i,z_i xi,yi,zi

  1. 对于 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 的。
  2. 那么我们可以考虑,整段按照 x x x从小到大排序,这样左边的就必定不会对右边产生贡献。先分治 [ l , m i d ] [l,mid] [l,mid] [ m i d + 1 , r ] [mid+1, r] [mid+1,r]这两段,然后再把右边的贡献加上即为这一段的答案。
  3. 于是,第一维此时就没有用了,只看 y y y z z z
  4. 参照归并排序思想,我们给左右两边按 y y y 排为有序,于是就可以归并+二维偏序BIT的方法去处理贡献。
  5. 即归并时插入的每一个右边的数,BIT记录已经插入的左边的数的第三位,搞区间查询即可
  6. 值得注意的是如果是多维偏序,可以继续套用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 &lt; x j , y i &lt; y j , t i &lt; t j x_i&lt;x_j , y_i &lt;y_j,t_i&lt;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的复杂度

统计答案的时候还可以顺便处理子节点的一部分答案信息

经典问题

  1. 求所有边数 ≤ L \le L L 的链的权值之和

    傻逼模板题

    getans就dfs一遍就好了

    主要是getroot函数重点理解

  2. 求到 x x x 边权 ≤ L \le L L 的点的个数之和

    也是模板题

    讲下getans咋写

    1. dfs出每个vis=0的点到它的距离
    2. 排个序
    3. 双指针,判断如果 d i s [ l ] + d i s [ r ] &gt; l dis[l]+dis[r]&gt;l dis[l]+dis[r]>l 则 r–,否则答案+=(r-l),l++
    4. 注意这样计算了一条边走两次的情况,要消除影响

    因为之前学过,就放一下代码吧

    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);
        }
    }
    
  3. 给定⼀棵树,每个点有物品重量 W [ i ] W[i] W[i] 和价值 V [ i ] V[i] V[i],求价值最
    ⼤的重量不超过 S 的连通块, n , S ≤ 2000 n,S\le 2000 n,S2000

    点分治后考虑必须经过 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[VV[son]]+w[son],f[V])

后记

不保证全部正确。。但是看起来都hin正确,有疑惑的答疑问了dls

有不对的请私信 or 评论,会第一时间修改

最好还是尝试自己去多理解,真的有用啊QwQ

来都来了,点个赞⑧

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值