分治算法匯總


普通分治

通過將區間分成兩個區間,來將問題分成兩個⼦問題求解

來康一些經典問題:

  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