普通分治
通過將區間分成兩個區間,來將問題分成兩個⼦問題求解
來康一些經典問題:
- 求所有區間的最⼤大值之和
- 計算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]) \]