分治算法汇总


普通分治

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

来康一些经典问题:

  1. 求所有区间的最⼤大值之和
  • 计算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]\)
  • 右边同理
  1. 求最大最小乘积之和

    • 跟上面差不多,考虑求出左边每个位置\(maxl,miml\),求出右边第一个比他们大/小的位置,然后区间分类讨论,求一些前缀和即可
    • 显然max,min单调增/减,右边位置单调增,用指针记录一下
  2. gcd之和

    • 考虑跨越中间的\(gcd\)之和
    • \(gcd\)在枚举的区间左端点向左移动的过程中单减
    • 考虑每次变化,必定除掉\(a[mid]\)的一个因子,故只有\(\log\)种取值
    • 然后有:枚举左右\(gcd\),这样是每次\(n\log n+\log ^3 n\)
  3. 平面最近点对

    非常naiive,不想写

  4. zz分治多项式

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

复杂度计算

  1. 每个区间\(On\)

\[f(n)=2*f(n/2)+n = n\log n \]

​ 这样理解:每次\(On\)\(\log n\)

  1. 每个区间\(n^2\)

    \[f(n)=2*f((n/2)^2)+n^2 = n^2 \]

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

  2. \(nlogn\)以上与2类似

例题选讲

旅行者

ZJOI2016

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

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

否则一定被这条线上的某个点更新,枚举这条线上的点求当前矩形内最短路更新dis即可

\(dis[a][b]=max(dis[a][b],dis[a][x]+dis[b][x])\)

优化:每次切长边,即保证了求最短路的次数不超过sqrt(m)甚至更小

复杂度证明:

\[T(S)=2T(\frac{S}{2})+O(S^{1.5}\log (S)) \]

\[T(S) = \sum_{a=0}^{\log_2S}{2^a(\frac{S}{2^a})^{1.5} \log \frac{S}{2^a}} \]

\[T(S)= \sum_{a=0}^{\log_2S}{2^{-0.5a}S^{1.5}(\log S-a)}​ \]

\[\leq \sum_{a=0}^{\log_2S}2^{-0.5a}S^{1.5} \]

\[= S^{1.5}\log S\sum_{a=0}^{\log_2S}2^{-0.5a} \]

\[\leq S^{1.5}\log S\int_0^{\log_2S}2^{-0.5x}dx \]

\[= S^{1.5}\log S (-2.89e^{-0.35\log_2S}+2.89) \]

\[= O(S^{1.5}\log S​) \]

第五步之后现场学习了一下定积分。。。然后直接计算器算了

连续区间

给定⼀个排列 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\)

​ 分情况讨论:

  1. \(r \in [mid+1 , min(minr,maxr)-1]\) ,即最大最小值都在左边,于是直接判断\(l-minl+maxl\)是否在当前区间内

  2. \(r \in [min(minr,maxr),max(minr,maxr)]\) ,最大最小中有一个在左边一个在右边,先假设\(minr<maxr\)

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

  3. \(maxr<minr\)同理,处理出前缀最大值

  4. \(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$$

转化为

\[\sum a[i] \geq \sum b[i]*mid \]

\[\sum a[i]-\sum b[i]*mid >=0 \]

\[\sum (a[i]-b[i]*mid)>=0 \]

好像 \(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\)

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

  1. \(c_i > mid\) 跳过
  2. \(c_i \leq mid+1\) 求区间之和,搞树状数组就可以了,时间顺序处理添加和询问

CDQ分治

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

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

三维偏序问题

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

​ 设三个维度为\(x_i,y_i,z_i\)

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

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

经典问题

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

    模板题

    getans就dfs一遍就好了

    主要是getroot函数重点理解

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

    也是模板题

    讲下getans咋写

    1. dfs出每个vis=0的点到它的距离
    2. 排个序
    3. 双指针,判断如果\(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]\) 和价值 \(V[i]\),求价值最
    ⼤的重量不超过 S 的连通块,\(n,S\le 2000\)

    点分治后考虑必须经过 \(x\),求解原问题

    显然树形dp

    设计状态\(f[S]\)

    对于父亲不选的,儿子也不能选(联通块)

    则从上到下dfs地枚举每条边,然后枚举V,显然是父亲被儿子更新,枚举选或者不选

    \[f[V]=max(f[V-V[son]]+w[son],f[V]) \]


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM