像大部分虛樹介紹一樣,以一道 爛大街 的例題 [sdoi2011]消耗戰 引入:
給定一棵大小為 \(n\) 的樹,每條邊有邊權,\(m\) 組詢問,每次詢問給定 \(k\) 個關鍵點,要求切斷 \(1\) 與所有關鍵點的路徑,求最小代價。
\(2 \leq n \leq 2.5 \times 10^5\),\(m \geq 1\),\(\sum \leq 5 \times 10^5\),\(1 \leq k \leq n - 1\)
設 \(f_u\) 表示切斷 \(u\) 與其子樹中所有關鍵點的最小代價。
設 \(w_\) 表示邊 \((u, v)\) 的權值。
列舉 \(u\) 的兒子 \(v\),轉移分為兩類:
樸素做法的時間複雜度為 \(o(nq)\),並過不掉這題,所以我們需要進行優化。
可以注意到,我們浪費的很多時間 \(dp\)非關鍵點,並且關鍵點的總數是與 \(n\) 同階的,所以考慮能否濃縮資訊,大樹變小。
這時引出虛樹的概念。
直觀的感受一下:
選取不同關鍵點,建出來的虛樹如下所示:
(以上圖** oi wiki)
任意兩個關鍵點的 \(lca\) 也需要儲存重要資訊,我們需要將其保留,所以虛樹中不一定只包含關鍵點。
以及我們可以發現,虛樹中祖先後代關係並不會改變。
接下來直接搬運一波建樹方法:
首先很直觀的,可以將所有關鍵點按 \(dfs\) 序排序,遍歷一遍,兩兩間求求 \(lca\),判判重,連連邊,就建完啦!(逃)
樸素演算法複雜度較高,考慮單調棧,單調棧的作用是:維護虛樹上的一條鏈。
為了方便,首先將根節點加入棧中,然後按 \(dfs\) 序遍歷關鍵點:
若當前點 \(u\) 與棧頂節點的 \(lca\) 是棧頂節點,那麼說明 \(u\) 與棧中節點在一條鏈上,直接將 \(u\) 壓入棧中
若 \(lca\) 不是棧頂節點,
需注意一些細節:
退棧時,一般棧頂節點在虛樹中的父親為第二節點,但是最後彈出的棧頂節點的父親為 \(lca\) (注意我們最後把 \(lca\) 加入棧中了);
如例題,我們需要多次建虛樹,則每次需要清空存圖的陣列,一般在節點入棧時清空即可(如鄰接表,前向行 \(head\) 陣列等);
如例題,每一條虛樹中的邊(或點)可能濃縮的原樹中幾條邊的資訊,所以我們有時需要先求出濃縮資訊,然後在連邊賦上。例題中我用了倍增的方法在連邊時 \(logn\) 求出一條虛樹上邊的權值(其實就是幾條原樹邊取個 \(min\))。
對於例題,建出虛樹後,直接在上面跑樸素 \(dp\) 就可以啦!
\(code\)
#include #include #include using namespace std;
#define n 1000000
#define l 19
#define fo(i, u) for(int i = head[u]; i; i = edge[i].next)
#define fo(i, x, y) for(int i = x, end_##i = y; i <= end_##i; ++ i)
#define fd(i, x, y) for(int i = x; i >= y; i --)
typedef long long ll;
void read(int &x)
struct edge edge[n << 1];
int head[n + 1], pre[n + 1][l + 1], g[n + 1][l + 1], sta[n + 1], dfn[n + 1], a[n << 1], dep[n + 1], key[n + 1];
ll f[n + 1];
int n, m, len, now = 0;
int cnt_edge = 1;
void add(int u, int v, int w) , head[u] = cnt_edge; }
void link(int u, int v, int w)
void input()
int tot_dfn = 0;
void dfs1(int u, int la)
void init()
bool cmp(int x, int y)
int get_lca(int u, int v)
int get_dis(int u, int v)
ll min(ll a, ll b)
void build()
sta[ ++ top ] = a[i], head[a[i]] = 0;
}fo(i, 1, top - 1)
add(sta[i], sta[i + 1], get_dis(sta[i + 1], sta[i]));
}void dfs2(int u)
}void solve()
int main()
return 0;
}
\(solution\)
待補充......
虛樹 Virtual Tree 學習筆記
description 在一場戰爭中,戰場由n個島嶼和n 1個橋梁組成,保證每兩個島嶼間有且僅有一條路徑可達。現在,我軍已經偵查到敵軍的總部在編號為1的島嶼,而且他們已經沒有足夠多的能源維繫戰鬥,我軍勝利在望。已知在其他k個島嶼上有豐富能源,為了防止敵軍獲取能源,我軍的任務是炸毀一些橋梁,使得敵軍不...
日常學習 Virtual Tree 虛樹)
虛樹,顧名思義就是虛構的樹,它是一種用來解決樹上問題的演算法,主要思想是只將原樹上必要的點和它們的最近公共祖先取出來,構成一棵虛樹,並保留他們在樹上的相對關係。我們先來看一道題 給定一棵n個點的樹,每次詢問給定乙個大小為k的點集,你需要切掉一些邊,使得點集中的點均不與1號點聯通,而每條邊都有被切掉所...
學習筆記 雜湊學習筆記
hash基本原理 hash就是乙個像函式一樣的東西,你放進去乙個值,它給你輸出來乙個值。輸出的值就是hash值。一般hash值會比原來的值更好儲存 更小 或比較。那字串hash就非常好理解了。就是把字串轉換成乙個整數的函式。而且要盡量做到使字串對應唯一的hash值。它的主要思路是選取恰當的進製,可以...