樹上倍增的寫法和應用(詳細講解,新手秒懂)
最近做了一些樹上的練習題,發現倍增真的是一種處理樹上問題的神奇、方便的方法。
我以前一直打樹鏈剖分打得多,但是學了倍增之後就再也不想打樹鏈剖分了(當然有些題目不得不打)。
倍增比起樹鏈剖分,程式碼短,容易查錯,時空複雜度也優很多(nlogn),只是功能有些欠缺。
倍增的思想是二進位制。
首先開一個n×logn的陣列,比如fa[n][logn],其中fa[i][j]表示i節點的第2^j個父親是誰。
然後,我們會發現有這麼一個性質:
fa[i][j]=fa[fa[i][j-1]][j-1]
用文字敘述為:i的第2^j個父親 是i的第2^(j-1)個父親的第2^(j-1)個父親
這是不是很神奇?這樣,本來我們求i的第k個父親的複雜度是O(k),現在複雜度變成了O(logk)。
我們知道,一個數的二進位制形式中,如果右邊數第i位上是1,表示這個數如果分解為若干個2的次冪的和的形式,其中有一項一定是2^(i-1)。舉個例子:10的二進位制表示為1010,它的第2位和第4位上是1,所以10=2^1+2^3。
下面是求i的第k個父親的程式碼段:
int father(int i,int k)
{
for(int x=0;x<=int(log2(k));x++)
if((1<<x)&k) //(1<<x)&k可以判斷k的二進位制表示中,第(x-1)位上 是否為1
i=fa[i][x]; //把i往上提
return i;
}
這樣講應該很容易理解吧?
我們可以通過一次dfs處理出fa陣列:(dep[i]表示i的深度,這個可以一起處理出來,以後要用)
如果待處理的樹有n個節點,那麼最多有一個節點會有2^(logn)個父親,所以我們的fa陣列第二維開logn就夠了。
這裡用max0表示logn。初始化fa為0,若fa[i][j]=0表示i沒有第2^j個父親。
void dfs(int x)
{
for(int i=1;i<=max0;i++)
if(fa[x][i-1]) //在dfs(x)之前,x的父輩們的fa陣列都已經計算完畢,所以可以用來計算x
fa[x][i]=fa[fa[x][i-1]][i-1];
else break; //如果x已經沒有第2^(i-1)個父親了,那麼也不會有更遠的父親,直接break
for(/*每一個與x相連的節點i*/)
if(i!=fa[x][0]) //如果i不是x的父親就是x的兒子
{
fa[i][0]=x; //記錄兒子的第一個父親是x
dep[i]=dep[x]+1; //處理深度
dfs(i);
}
}
這樣,我們在nlogn的時間內可以通過一遍dfs處理出這棵樹的相關資訊。然後就可以在logn的時間內完成一些操作。倍增的應用中,最基礎的應該就是求LCA(最近公共祖先),時間複雜度是logn。
對於求u、v的LCA,我們可以先把u、v用倍增法把深度大的提到和另一個深度相同。如果此時u、v已經相等了,表示原來u、v就在一條樹鏈上,直接返回此時的結果。
如果此時u、v深度相同但不等,則證明他們的lca在更“淺”的地方,此時需要把u、v一起用倍增法上提到他們的父親相等。為啥是提到父親相等呢?因為倍增法是一次上提很多,所以有可能提“過”了,如果是判斷他們本身作為迴圈終止條件,就無法判斷是否提得過多了,所以要判斷他們父親是否相等。不懂的可以詳見程式碼:
int LCA(int u,int v)
{
if(dep[u]<dep[v])swap(u,v); //我們預設u的深度一開始大於v,那麼如果u的深度小就交換u和v
int delta=dep[u]-dep[v]; //計算深度差
for(int x=0;x<=max0;x++) //此迴圈用於提到深度相同。
if((1<<x)&delta)
u=fa[u][x];
if(u==v)return u;
for(int x=max0;x>=0;x--) //注意!此處迴圈必須是從大到小!因為我們應該越提越“精確”,
if(fa[u][x]!=fa[v][x]) //如果從小到大的話就有可能無法提到正確位置,自己可以多想一下
{
u=fa[u][x];
v=fa[v][x];
}
return fa[u][0]; //此時u、v的第一個父親就是LCA。
}
倍增還可以有很多變化,這讓倍增法可以優更多的變化。比如用data[i][j]記錄i到他的第2^j個父親的路徑長度,就可以邊求LCA邊求出兩點距離,因為data[i][j]滿足倍增的遞推式:data[i][j]=data[i][j-1]+data[fa[i][j-1]][j-1]。或者用maxlen[i][j]記錄i到第2^j個父親的路徑上最長邊的邊權,它滿足maxlen[i][j]=max{maxlen[i][j-1],maxlen[fa[i][j-1]][j-1]},這樣就可以快速求出兩點路徑上最長邊的邊權……
總之,倍增是一種較為基礎的處理樹的方式,一定要熟練掌握,可以打幾個模板題先做做,然後找一些經典的倍增習題。
相關推薦
樹上倍增的寫法和應用(詳細講解,新手秒懂)
最近做了一些樹上的練習題,發現倍增真的是一種處理樹上問題的神奇、方便的方法。 我以前一直打樹鏈剖分打得多,但是學了倍增之後就再也不想打樹鏈剖分了(當然有些題目不得不打)。 倍增比起樹
centos 7 linux系統預設ftp安裝配置和部署(詳細講解)
轉載自:https://www.cnblogs.com/mujingyu/p/7677273.html 小生接觸 Linux 系統時間不長,想解決linux系統ftp安裝及部署問題,折騰了大半天,終於弄出來了,將各路 高手的配置方法綜合了一下,如有不對之處,歡迎各位看客指正,感謝! 一、
【Linux】centos 7 linux系統預設ftp安裝配置和部署(詳細講解)
小生接觸 Linux 系統時間不長,想解決linux系統ftp安裝及部署問題,折騰了大半天,終於弄出來了,將各路 高手的配置方法綜合了一下,如有不對之處,歡迎各位看客指正,感謝! 一、宣告: 本文采用作業系統版本: Centos 7 Linux系統 版本源:C
TCP連線擁塞控制四種方法總結(詳細簡單,穩的一批)
擁塞控制的一般原理 在某段時間,若對網路中某一資源的需求超過了該資源所能提供的可用部分,網路的效能就要變換,叫做擁塞 擁塞控制和流量控制的區別: 擁塞控制往往是一種全域性的,防止過多的資料注入到網路之中,而TCP連線的端點只要不能收到對方的確認資訊,猜想在網路中發生了擁塞,但並不知道發生
SQLserver2000附加資料庫時報錯資訊:處理資料庫的日誌出錯,錯誤:9004--解決辦法(詳細圖文,親測成功)
使用SQLserver2000資料庫由於種種原因,僅僅備份了mdf檔案,那麼恢復起來就是一件很麻煩的事情了。 如果您的mdf檔案是當前資料庫產生的,很幸運,還有恢復的可能性。 原因:資料庫檔案存在異常狀況,有可能是因為硬碟有壞區引起的 報錯資訊如下: 怎麼辦呢?
置換和輪換(新姿勢,摘自黑書)
參考論文 這一部分在黑書中, 是在群論這一部分介紹的 所以我們先了解什麼是群 群的定義 給定一個集合G={a,b,c…}和集合G上的一個二元計算*,滿足以下四個條件: (1)封閉性 若a,b∈G,則存在唯一確定的c∈G,使得a*b=c;
imx6 V4L2視訊採集和播放(輸入video0,輸出為video17)
next: memset(&capture_buf, 0, sizeof(capture_buf)); capture_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; capture_buf.memory = V4L2_M
關於自然常數e的來源與應用(超初級,也超恐怖)
附: 這是小數點後面兩千位: e=:2.71828 18284 59045 23536 02874 71352 66249 77572 47093 69995 95749 66967 62772 40766 30353 54759 45713 82178 52516 64274 27466 39193 20
支援向量機演算法的實現和應用(Python3超詳細的原始碼實現+圖介紹)
支援向量機演算法的實現和應用,因為自己推到過SVM,建議自己推到一遍, 這裡不對SVM原理做詳細的說明。 原理公式推到推薦看:https://blog.csdn.net/jcjx0315/article/details/61929439 #!/usr/bin/env python # enc
JUnit自動化單元測試(三):各常用註解和測試函式詳細講解
@Test:將一個方法修飾成一個可測試的方法;只有@Test修飾之後,這個方法才會被JUnit執行。 @Test(expected=XXException.class):表示這個方法一定會丟擲某個異常
JS的var和let的區別(詳細講解)
let是ES6新增的,它主要是彌補var的缺陷,你也可以把let看做var的升級版。下面我就來詳細講講var和let的區別 相同點: var和let都有函式級作用域 不同點: (1)var是全域性作用域,let不是 var 和 let 宣告的變數在全域性作用域中被定義時,兩者非常相似。但是,被l
java中的棧Stack的基本使用和應用(一)
string emp tac logs tor str col () bject 棧 定義 棧是一種只能在一端進行插入或刪除操作的線性表。(先進後出表) java中的Stack繼承Vector 實例化 Stack stack=new Stack(); 基本使用 判斷是
如何用Python實現堆棧和隊列詳細講解
Python語言 Python編程開發 Python案例應用 python實現堆棧 堆棧是一個後進先出的數據結構,其工作方式就像一堆汽車排隊進去一個死胡同裏面,最先進去的一定是最後出來。 我們可以設置一個類,用列表來存放棧中元素的信息,利用列表的append和pop方法可以實現棧的出棧po
知識點一,使用os庫遍歷文件夾(詳細講解)
python web 爬蟲 os 系統 使用os庫遍歷文件夾 有時候我們需要對文件進行批量處理,那麽遍歷文件夾這種操作就必不可少 如何操作: #path是你指定的路徑,如:"C:/" os.walk(path) os.walk()的返回值是什麽呢?其實你只要打印一下就知道了,是一個g
php中模擬post,get請求和接受請求詳細講解
上傳 有一種 har for nts input time verify 有時 在php中我們經常用到curl拓展來進行模擬post、get請求,下面就來具體說說怎麽模擬: 一、首先模擬post請求: function http_post_data($url, $quer
Timer定時器 (詳細講解)
Timer定時器主要做定時任務或者按照一定的時間間隔做任務,例如每天4點鐘定時執行作業等 Timer的特性 1、它屬於單執行緒的,每建立個Timer例項,就會建立一個新執行緒 2、Timer預設情況下不是守護執行緒,可以設定為守護執行緒new Timer(true),守護執行緒再程序中沒
jadx反編譯—下載和使用(傻瓜教程,非常詳細)
一、在GitHub上直接下載 https://github.com/skylot/jadx 可以下這個版本: 二、執行圖形化介面 1、將zip檔案解壓後定位到在lib資料夾中,在此處開啟命令列 2、執行jadx-gui-0.7.1.jar(前提是已經裝好了JDK1
Appium-python日誌logging模組的簡介和應用(2)
Python的logging模組定義的函式和類為應用程式和庫的開發實現提供了一個靈活的事件日誌系統。 Logging模組提供了兩種記錄日誌的方式: 第一種是使用logging提供的模組級別的函式 第二種是使用logging日誌系統的四大元件 此文主要使用
Centos系統下解除安裝、安裝MySQL及使用者的建立、授權和使用(詳細。。。。)
由於經常使用linux系統,並且大資料環境搭建中經常會使用到mysql,不像windows系統下的安裝,今天有點空寫一篇,下面我給大家演示一遍。 主要有三部分內容: 1、MySQL的解除安裝 2、MySQL的安裝 3、MySQL使用者的授權和使用者建立、刪除 一、MySQL的解除安裝(如果是僅僅安裝
Vue.js 2.x:元件的定義和註冊(詳細的圖文教程)
本文最初發表於部落格園,並在GitHub上持續更新前端的系列文章。歡迎在GitHub上關注我,一起入門和進階前端。 以下是正文。 前言 什麼是元件 元件: 元件的出現,就是為了拆分Vue例項的程式碼量的,能夠讓我們以不同的元件,來劃分不同的功能模組,將來我們需要什麼樣的功能,就可以去呼叫對應的元件即可