1. 程式人生 > >【學習筆記】Kruskal 重構樹(BZOJ3551【ONTAK2010】Peaks加強版)

【學習筆記】Kruskal 重構樹(BZOJ3551【ONTAK2010】Peaks加強版)

1. 例題引入:BZOJ3551

題目大意:有 N N 座山峰,每座山峰有他的高度 h i

h_i 。有些山峰之間有雙向道路相連,共 M M 條路徑,每條路徑有一個困難值,這個值越大表示越難走,現在有 Q Q
組詢問,每組詢問詢問從點 v v 開始只經過困難值小於等於 x x 的路徑所能到達的山峰中第 k
k
高的山峰的高度,如果無解輸出 1 -1 強制線上。

  • 這道題的離線做法可以是線段樹合併,可以參照我之前寫過的一篇文章,裡面有提到:【學習筆記】線段樹的擴充套件(線段樹的合併與分裂、可持久化線段樹)
  • 強制線上的話,我們似乎沒有什麼好思路。
  • 先不考慮求第 k k 大的權值,我們先考慮快速判斷點對 ( u , v ) (u,v) 能否通過邊權不超過 x x 的邊互相到達。
  • 從最優化的角度考慮,把問題轉化為從點 u u 出發,尋找一條 ( u , v ) (u,v) 之間的路徑,使得這條路徑的邊權最大值最小。我們需要判斷的是這個最小的最大邊權是否不超過 x x
  • 因為無向圖形態固定,所以我們要找到一條 ( u , v ) (u,v) 之間的路徑,使得這條路徑的邊權最大值最小,實際上就是找到最小生成樹中的 ( u , v ) (u,v) 之間的路徑的最大邊權。
  • 然而如果只是查詢最大邊權寫個樹上倍增就沒了。
  • 這裡我們需要訪問從點 u u 出發,能到達所有點的集合。很明顯,這個集合是一個連通塊,在 K r u s k a l Kruskal 演算法的過程中,這個連通塊必然在某一時刻是完整存在於一個集合的(因為邊一定從小到大接進來的),我們利用這一點,可以將點按照它們之間能到達的最大邊權進行分類,於是就有了 K r u s k a l Kruskal 重構樹。

2. Kruskal 重構樹的構造過程

  • 具體做法:
    • 我們把新構建出的圖叫做重構樹,開始重構樹中只有 n n 個孤立的點,我們將它們的點權視作 -\infty
    • K r u s k a l Kruskal 演算法求最小生成樹的過程中,遇到一條連線兩個不同集合的邊,我們在並查集中分別找到兩個集合的根 u , v u,v ,新建一個結點 w w ,合併兩個集合,並且令 w w 為新集合的根。
    • 在重構樹中將 w w 作為 u , v u,v 共同的父親,即在重構樹中連邊 w u , w v w\to u,w\to v 。令 w w 的點權為 ( u , v ) (u,v) 的邊權。

3. Kruskal 重構樹的性質

  • 根據此構造過程,我們可以得到關於重構樹的性質:
  1. 重構樹是一棵二叉樹,且滿足大根堆的性質。
  2. 原圖中的 n n 個點均為重構樹中的葉子結點。
  3. 對於點對 ( u , v ) (u,v) ,它們在原圖中的所有路徑中,最大邊權最小的路徑的最大邊權為, u , v u,v 在重構樹中 l c a lca 的權值。
  4. 對於一個葉子結點 u u ,它在原圖中經過邊權不超過 x x 的邊,能到達的點集為:找到一個深度最小的 u u 的祖先 v v ,使得 v v 的點權不超過 x x ,根據 K r u s k a l Kruskal 演算法的過程和重構樹的性質,可以知道, v v 的子樹中的葉子結點集合即為能到達的點集。對於一個葉子結點 u u ,它在原圖中經過邊權不超過 x x 的邊,能到達的點集為:找到一個深度最小的 u u 的祖先 v v ,使得 v v 的點權不超過 x x ,根據 K r u s k a l Kruskal 演算法的過程和重構樹的性質,可以知道, v v 的子樹中的葉子結點集合即為能到達的點集。

4. 回到例題:BZOJ3551

  • 那麼題目中的詢問,我們利用性質4,在重構樹中找到深度最小的滿足條件的結點 u u
  • 然後求子樹的葉子節點中的 k k 大權值,我們可以轉化為 d f s dfs 序的區間內的 k k 大權值,然後就是經典的靜態區間 k k 大問題。
  • 這個只需要對 d f s dfs 序的每個字首 1 i 1\dots i 利用主席樹(可持久化線段樹)維護出權值線段樹的每個值域區間的元素個數,查詢時候只需要差分一下,在兩棵權值線段樹上二分即可。
  • 如果不清楚靜態區間 k k 大的可以自行百度搜索一下主席樹。
  • 總結一下,對於圖的形態不變,並且需要限制通過邊權小於或大於某個值的邊,關於點的連通性的線上查詢問題,可以考慮 K r u s k a l Kruskal 重構樹。

5. 相關題目

附:例題 BZOJ3551 程式碼

#include <bits/stdc++.h>

inline char nextChar()
{
	static const int buffer_size = 2333333; 
	static char buffer[buffer_size]; 
	static const char *tail = buffer + buffer_size; 
	static char *head = buffer + buffer_size; 
	
	if (head == tail)
	{
		fread(buffer, 1, buffer_size, stdin); 
		head = buffer; 
	}
	return *head++; 
}

template <class T>
inline void read(T &x)
{
	static char ch; 
	while (!isdigit(ch = nextChar())); 
	x = ch - '0'; 
	while (isdigit(ch = nextChar()))
		x = x * 10 + ch - '0'; 
}

inline void putChar(char ch)
{
	static const int buffer_size = 2333333; 
	static char buffer[buffer_size]; 
	static const char *tail = buffer + buffer_size; 
	static char *head = buffer; 
	
	if (ch == '\0')
		fwrite(buffer, 1, head - buffer, stdout); 
	
	*head++ = ch; 
	if (head == tail)
		fwrite(buffer, 1, buffer_size, stdout), head = buffer; 
}

template <class T>
inline void putint(T x)
{
	static char buf[22]; 
	static char *tail = buf; 
	if (!x) return (void)(putChar('0')); 
	if (x < 0) x = ~x + 1, putChar('-'); 
	for (; x; x /= 10) *++tail = x % 10 + '0'; 
	for (; tail != buf; --tail) putChar(*tail); 
}

const int MaxNV = 2e5 + 5; 
const int MaxNE = 5e5 + 5; 
const int MaxLog = 20; 

const int MaxS = MaxNV * 30; 

struct edge
{
	int u, v, w; 
	inline bool operator < (const edge &rhs) const
	{
		return w < rhs.w; 
	} 
	inline void scan()
	{
		read(u), read(v), read(w); 
	}
}e[MaxNE]; 

struct halfEdge
{
	int v; 
	halfEdge *next; 
}adj_pool[MaxNE], *adj[MaxNV], *adj_tail = adj_pool; 

int n, m, Q, dfs_clock, last_ans, tot; 

int rt, cnt, idx[MaxNV], seg[MaxNV]; 
int h[MaxNV], lef[MaxNV], rit[MaxNV]; 
int dep[MaxNV], anc[MaxNV][MaxLog + 1]; 

int ufs_fa[MaxNV], val[MaxNV]; 
int real_num, real[MaxNV]; 

int lc[MaxS], rc[MaxS], sze[MaxS]; 

inline void addEdge(int u, int v)
{
	adj_tail->v = v; 
	adj_tail->next = adj[u]; 
	adj[u] = adj_tail++; 
}

inline int ufs_find(int x)
{
	return x == ufs_fa[x] ? x : ufs_fa[x] = ufs_find(ufs_fa[x]); 
}

inline int jump(int u, int k)
{
	for (int i = MaxLog; i >= 0; --i)
		if (anc[u][i] && val[anc[u][i]] <= k)
			u = anc[u