維護乙個動態森林,支援:
作為tarjan神犇研究的玩意,命名和union-find set如出一轍…
具體描述見**《qtree解法的一些研究,yangzhe,2007》,這裡對一些容易引發誤解的地方做出說明。
就是splay的森林,u,v在同一棵splay內當且僅當他們在原樹中位於同一條偏愛路徑。(splay中)u在v左邊當且僅當在原樹中u在v上方。不難證明森林和這裡的「splay森林」構成一一對應的關係。由於splay的旋轉不會改變splay中的左右關係,因而對應的splay森林不變,由於「一一對應」,原森林也不變。
access(u):暴力將當前節點到根的路徑變為偏愛路徑
make_rt(u):換根。將u設為其所在樹的根。
access u
splay u to
the root
reverse
the path which is u belonged
這也就是讓我(貌似還有很多人)迷惑的rev陣列的由來,其作用就是用lazytag的思想翻轉一條鏈。
link(i, j):連線i, j
link(i, j)
make-rt i
fa[i] = j
cut(i, j):切斷i和j間的路徑
cut(i, j)
make-rt i
access j
splay j
fa[i] = chl[j]
[0] = 0
這裡換根將兩種情況同一考慮了。
cut-fa(i): 切斷i和父親的路徑
cut-fa(i)
access i
splay i
fa[chl[i]
[0]] = 0
chl[i]
[0] = 0
對於維護有根樹的情景,由於不能隨意變動根,要採取這樣的方法。
用一些妙不可言的方法可以證明所有操作都是攤還 o(
lgn) 的,但是常數極大..由於是攤還時間所以在生活中很難真正應用,oi裡自然不怕(除非出題人是sbt腦殘粉專門寫互動題卡splay…)。
**總的來說還算清晰,和鏈剖複雜度類似,不過用起來比鏈剖要靈活的多了。用yangzhe神犇的話說,就是「不在意區域性的平衡而關注全域性平衡」帶來的優勢。
ds腦殘系列1,鬧不懂rev啥意思導致2hdebug…
#include
using
namespace
std;
const
int maxn = 10005;
struct link_cut_tree
void push_down(int nd)
void zig(int nd)
void splay(int nd)
else
if (tp == tg) zig(p), zig(nd);
else zig(nd), zig(nd);}}
void access(int x)
inline
void make_rt(int nd)
void link(int i, int j)
void cut(int i, int j)
int find(int i)
}lct;
char s[20];
int n, m;
int u, v;
int main()
}return
0;}
ds腦殘系列2, rev[nd]誤作rev導致1.5h的debug…
#include
using namespace std;
const int maxn = 30005;
int stk[maxn], top;
int chl[maxn][2], fa[maxn], mx[maxn], sum[maxn], rev[maxn], rt[maxn];
int n, m;
char s[20];
inline bool isrt(int nd)
void pdw(int nd)
void dfs()
inline void update(int nd)
void zig(int nd)
void splay(int nd)
else
if (tp == tg) zig(p), zig(nd);
else zig(nd), zig(nd);
}}void access(int
x)void mkt(int nd)
void link(int u, int v)
// 連線
void split(int u, int v)
// 提取區間
intx[maxn], y[maxn];
int main()
else
if (s[1] == 'm') split(u, v), printf("%d\n", mx[v]);
else
if (s[1] == 's') split(u, v), printf("%d\n", sum[v]);
}return
0;}
ds腦殘系列3, 維護有根樹貿然換根導致1.5hdebug……
#include
using
namespace
std;
const
int maxn = 200005;
int chl[maxn][2], fa[maxn], rev[maxn], siz[maxn];
int n, m;
int stk[maxn], top = 0;
inline
void update(int nd)
inline
bool isrt(int nd)
void pdw(int nd)
void zig(int nd)
void splay(int nd)
int tp = chl[p][0] != nd, tg = chl[g][0] != p;
if (tp == tg) zig(p), zig(nd);
else zig(nd), zig(nd);
}}void access(int x)
void mkt(int x)
void link(int x, int y)
int ask(int nd)
void dfs(int nd, int tab = 0)
int ki[maxn];
int main()
scanf("%d", &m);
for (int i = 1; i <= m; i++) else
if (opt == 2) else
}return
0;}
第一次lct 1a!
不過還是因為把nd寫成maxn除錯了半天…以後maxn一定用大寫…
思路就是用lct維護mst,將邊按a排序,然後逐個嘗試加入。如果成環就刪除環上最大的節點。時間複雜度為o(
(n+m
)lg(n
+m))
,雖然常數巨大但還是比不要臉的動態加邊spfa高到不知**去。
#include
using namespace std;
const int maxn = 200005;
int chl[maxn][2], fa[maxn], mx[maxn], rev[maxn], tp = 0; // 拆邊用節點
int rt[maxn];
int stk[maxn], top = 0;
inline bool isrt(int nd)
void pdw(int nd)
void dfs()
void update(int nd)
void zig(int nd)
void splay(int nd)
if (tp == tg) zig(p), zig(nd);
else zig(nd), zig(nd);
}// dfs();
}void access(int nd)
void make_rt(int nd)
void link(int i, int j)
void cut(int i, int j)
void del(int i)
int find_rt(int nd)
bool linked(int i, int j)
void insert(int i, int j, int k)
void split(int i, int j) // 提取區間
int n, m;
struct edge
} egs[maxn];
int read()
return a;
}int cmp(const edge &a, const edge &b)
intmain()
}if (linked(1, n))
}if (ans ==
int_max) puts("-1");
else
cout
<< ans
<< endl;
return0;}
動態樹問題
當一類題目中的樹需要支援換根 加邊 刪邊的這些操作時,會改變樹的形態,並要求維護一些資訊,這類問題稱為動態樹問題。這裡有我寫的學習小記 link cut tree學習小記 支援的功能 換根,加邊,刪邊,鏈上資訊維護。不支援的功能 子樹資訊維護。習題 彈飛綿羊,魔法森林,substring。我不知道真...
關於樹論 動態樹問題(LCT)
搬運 看一道caioj1439 題目描述 一開始給你一棵n個點n 1條邊的樹,每個點有乙個權值wi。三種操作 op 1 u v 在點u和點v之間建一條邊。op 2 u v 摧毀點u到點v之間的邊。op 3 w u v 將點u和點v之間路徑上的點 包括u,v 權值增加w。op 4 u v 詢問點u到點...
動態規劃與序列問題
1.最長公共子串問題描述 如果字串一的所有字元按其在字串中的順序出現在另外乙個字串二中,則字串一稱之為字串二的子串。注意,並不要求子串 字串一 的字元必須連續出現在字串二中。動態規劃 使用dp i j 表示 以x i 和y j 結尾的最長公共子串的長度,因為要求子串連續,所以對於x i 與y j 來...