點分治 模板 詳解

2021-09-11 05:02:46 字數 3404 閱讀 8090

動態點分治學了以後會在後面 update 的啦~

好久沒頹 blog 了 今天來寫一發

最近幾個月就學了這乙個東西啊 =-=

好了 進入正題 舌尖上的澱粉質

q1:點分治是什麼?

a1:就是像分治一樣把樹上的點咔擦成幾個小樹,然後繼續咔擦下去處理問題啦 像你把西蘭花掰開一樣

q2:點分治能用來幹什麼?

a2:據我所知,點分治通常是用來找樹上所有路徑和乙個不可告人的數字之間的關係=-=例如小於k的路徑啦,膜k餘233的路徑啦...

好了 按照套路 題目還是洛谷的模板 然後首先是

可能有點難看 但是放外面更難看=-=

2019.2.23 update:好像 son 陣列用不到 只要在 getrt 裡面 int son = 0 就可以了=-=

struct edgee[n << 1]; //存無向邊

int first[n],ans[10000005],siz[n],son[n];

//用於鄰接表 存路徑長度(桶) 子樹大小 最大子樹大小

int ed[n]; //記錄經過當前點的所有分路徑 用於累加 每次用直接覆蓋不用清空

int n,m,size,mnsiz,rt,tot; //tot存邊的時候用 然後記錄路徑的時候當成edg這個陣列的指標

//size:當前樹大小 mnsiz/rt:當前最小的 樹的(子樹大小/編號(也就是當前樹的重心))

bool o[n]; //判斷當前點有沒有被divide(分治)過 求樹的重心時用這個判斷當前樹的邊界

這裡就不用我多講了吧,能學到澱粉質的應該都會鄰接表了

這個部分網上很清楚了 這裡隨便講講

當前點 像樹剖一樣把子樹 siz 弄出來 (不懂樹剖也沒關係不重要) 然後找個最大

再然後呢 樹的完全體的點數為 n 當前點設為 p 則當前點 p 為根時 他到原根的那顆子樹的 siz 就是 n - p

這個就是 p 點往上的那個子樹啦 所以也要判斷更新

當然這個存不了 只能每次判斷 反正也花不了多少時間

每次分治之後都要求一次當前樹的重心!這個要記得

下放**

void getrt(int p,int fa) 

if (size - siz[p] > son[p]) son[p] = size - siz[p];

if (son[p] < mnsiz) mnsiz = son[p],rt = p;

}

這個網上的那群毒瘤dalao們都不講啊 講完求樹的重心就全放**跑了=-=

其實也挺簡單 就是求出當前重心後 對其所有兒子(當然不能讓他父親)分治

這裡統計完當前點的答案後 要把當前點子樹的答案分別算一遍並刪去

原因?戳這裡進去看看,我懶得畫圖了

對於 divide 我們先不思考如何 solve 更新答案 我們先把這個子程式搞定

怎麼搞呢 遍歷樹邊 然後分治子樹 好了沒了 exm?

然後分治子樹之前 要重置與求樹的重心有關的變數 我的這裡面是這幾個

mnsiz = inf,rt = 0,size = siz[b];
如果不知道為什麼要賦值成這樣的 我這裡再搬一下變數

//size:當前樹大小

//mnsiz/rt:當前最小的 樹的(子樹大小/編號(也就是當前樹的重心))

inf是取極大值方便更新 這個應該不用我補充(那你補充幹嘛)

話說為什麼要重新求重心呢?因為如果你分治的子樹的根又成了一條鏈的話....哼哼

放心 樹已經通過判斷的 o 陣列分開了的 你只會搜到這棵子樹的所有的點!

然後總的**在這裡 關於裡面的 solve 下面立馬會講到

void divide(int p) 

}

solve 就是處理答案啦 這裡的 query 就是求經過當前點 p 的 終點為 p 的路徑

然後記錄的話就是之前說的 ed 啦 相當於乙個桶 這東西用 tot 當作指標 所以要清零 tot

query 先放下來吧 也說不了什麼 主要就是深搜下去的時候要把連線兩點的邊權加上

還有一開始的 dis 為 0 是為了記錄經過 solve裡面的 p 的 終點為 p 的路徑 好吧其實不用說大家都懂 =-=

void query(int p,int fa,int dis)
solve 處理答案的時候 直接拼接即可 像氣泡排序一樣的打法 然後刪去的時候也是一樣

因此就不用打兩個子程式了 solve 裡加個 mode 變數 即可 這裡 1 為 記錄路徑 0 為 刪去重路徑

我寫的這個雙重迴圈在某點兒子很多的時候會跑得非常慢 像聰聰可可裡面乙個毒瘤資料 root 有10000+個兒子

題解裡有優化,快得多 =w=

路徑統計就是去掉重複的答案嘛 如果實在忘記為什麼的話 看回 4 裡面的那個 超連結 我搬下來了

下放**

void solve(int rt,int dis,int mode) 

for (int a = 1 ; a < tot ; ++ a)

for (int b = a + 1 ; b <= tot ; ++ b) --ans[ed[a] + ed[b]];

}

好了 以上就是全部 拼起來就可以了

下放總的** 順便把上面的模板鏈結拉下來

其實還是挺慢的 900+ms 吸氧 240+ms 別人有的都 10+ms

應該還有更好的方法 =-=

#include #define n 10010

#define inf 0x3fffffff

inline int r()

struct edgee[n << 1];

int first[n],ans[10000005],siz[n],son[n],ed[n];

int n,m,size,mnsiz,rt,tot;

bool o[n];

inline void add(int x,int y,int z)

void getrt(int p,int fa)

if (size - siz[p] > son[p]) son[p] = size - siz[p];

if (son[p] < mnsiz) mnsiz = son[p],rt = p;

}void query(int p,int fa,int dis)

void solve(int rt,int dis,int mode)

for (int a = 1 ; a < tot ; ++ a)

for (int b = a + 1 ; b <= tot ; ++ b) --ans[ed[a] + ed[b]];

}void divide(int p)

}int main()

點分治模板

luogu 3806 近些日子學了點分治,當然只是學了個模板。所謂點分治,使用於處理樹上路徑的一種分治手段。因為利用了重心的性質,時間複雜度可以保證呢。所謂演算法流程 選取當前子樹的重心 計算路徑總數,不管路徑是否過當前重心 後面會去重 計算起點和終點在同一顆子樹中的合法路徑 因為這條路徑不是簡單路...

點分治模板

這兩天跟著學了一手樹上點分治模板,然後有一些感悟,決定來寫一發部落格.首先,鑑於鄙人的經驗,如果想要較快速地學習乙個新演算法,肯定還是先看一道經典的例題比較好,所以我們先來一道例題.給你一棵tree,以及這棵樹上邊的距離.問有多少對點它們兩者間的距離小於等於k 輸入格式 n n 40000 接下來n...

點分治模板

bzoj1316 由於之前板子寫得太爛了,今天把它重新整理改進了一下 vis表示每個點是否已經當過根,所以注意dfs,findroot函式的計算過程中是不會對vis進行修改的 修改時只需要考慮對dfs和solve中的有關位置進行修改即可,其它部分基本不變 include using namespac...