維護一顆動態樹,並維護一個點對集合 \(S\) 。c++
動態查詢一條邊,是否被集合中全部點對構成的路徑包含。git
\(n \le 100000, m \le 300000\)dom
orz 前輩 毛爺爺。ui
一個頗有意思的 trick 。spa
若是一條邊,被一條路徑包含,那麼兩個端點分別存在與這條邊對應的兩個子樹內。debug
咱們就能夠利用這個巧妙的性質來作了。code
咱們每次給兩個端點異或上一個隨機的權值,而後就能夠每次查詢這條邊對應的任意一顆子樹內全部點異或和 \(res\) ,若是 \(res\) 不等於前面全部操做的異或和,那麼就是錯的,不然就是正確的。get
這利用了異或的自反性,若是兩個端點都在子樹中那麼貢獻就會抵消掉,因此就是不合法的。it
能夠證實這個正確率會很是的高。class
至於維護子樹信息,只須要 \(lct\) 多維護一個虛子樹信息就好了,也就是對於 \(access,link,cut,pushup\) 多進行一些操做就好了。
對於有些動態樹路徑的題,能夠考慮只維護兩個端點,不用維護整個路徑上的信息。
而後能夠利用異或的自反性,來保證集合中每一個數出現而且僅出現一(奇數)次。
具體實現就在代碼裏面了。
#include <bits/stdc++.h> #define For(i, l, r) for(register int i = (l), i##end = (int)(r); i <= i##end; ++i) #define Fordown(i, r, l) for(register int i = (r), i##end = (int)(l); i >= i##end; --i) #define Set(a, v) memset(a, v, sizeof(a)) #define Cpy(a, b) memcpy(a, b, sizeof(a)) #define debug(x) cout << #x << ": " << (x) << endl #define DEBUG(...) fprintf(stderr, __VA_ARGS__) using namespace std; typedef unsigned int ui; template<typename T> inline bool chkmin(T &a, T b) {return b < a ? a = b, 1 : 0;} template<typename T> inline bool chkmax(T &a, T b) {return b > a ? a = b, 1 : 0;} inline int read() { int x(0), sgn(1); char ch(getchar()); for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1; for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48); return x * sgn; } void File() { #ifdef zjp_shadow freopen ("207.in", "r", stdin); freopen ("207.out", "w", stdout); #endif } const int N = 1e5 + 1e3; #define ls(o) ch[o][0] #define rs(o) ch[o][1] template<int Maxn> struct Link_Cut_Tree { int ch[Maxn][2], fa[Maxn]; inline bool is_root(int o) { return ls(fa[o]) != o && rs(fa[o]) != o; } inline bool get(int o) { return rs(fa[o]) == o; } ui val[Maxn], sub[Maxn], isub[Maxn]; inline void push_up(int o) { sub[o] = sub[ls(o)] ^ sub[rs(o)] ^ isub[o] ^ val[o]; } void rotate(int v) { int u = fa[v], t = fa[u], d = get(v); fa[ch[u][d] = ch[v][d ^ 1]] = u; fa[v] = t; if (!is_root(u)) ch[t][rs(t) == u] = v; fa[ch[v][d ^ 1] = u] = v; push_up(u); push_up(v); } bool rev[Maxn]; inline void Get_Rev(int o) { rev[o] ^= 1; swap(ls(o), rs(o)); } inline void push_down(int o) { if (rev[o]) Get_Rev(ls(o)), Get_Rev(rs(o)), rev[o] = false; } void Push_All(int o) { if (!is_root(o)) Push_All(fa[o]); push_down(o); } inline void Splay(int o) { Push_All(o); for (; !is_root(o); rotate(o)) if (!is_root(fa[o])) rotate(get(o) != get(fa[o]) ? o : fa[o]); } inline void Access(int o) { for (int t = 0; o; o = fa[t = o]) Splay(o), isub[o] ^= sub[rs(o)], isub[o] ^= sub[rs(o) = t], push_up(o); } inline void Make_Root(int o) { Access(o); Splay(o); Get_Rev(o); } inline void Split(int u, int v) { Make_Root(u); Access(v); Splay(v); } inline void Link(int u, int v) { Split(u, v); fa[u] = v; isub[v] ^= sub[u]; push_up(v); } inline void Cut(int u, int v) { Split(u, v); ls(v) = fa[u] = 0; push_up(v); } }; Link_Cut_Tree<N> T; inline void Update(int o, ui val) { T.Access(o); T.Splay(o); T.val[o] ^= val; T.push_up(o); } random_device Rand; pair<int, int> Up[N * 3]; ui Val[N * 3], len = 0; int main () { File(); read(); int n = read(), m = read(); For (i, 1, n - 1) T.Link(read(), read()); ui cur = 0; For (i, 1, m) { int opt = read(); if (opt == 1) { int x = read(), y = read(), u = read(), v = read(); T.Cut(x, y); T.Link(u, v); } if (opt == 2) { int x = read(), y = read(); Val[++ len] = Rand(); cur ^= Val[len]; Update(x, Val[len]); Update(y, Val[len]); Up[len] = make_pair(x, y); } if (opt == 3) { int id = read(), x = Up[id].first, y = Up[id].second; cur ^= Val[id]; Update(x, Val[id]); Update(y, Val[id]); } if (opt == 4) { int x = read(), y = read(); T.Split(x, y); puts(T.sub[x] == cur ? "YES" : "NO"); } } return 0; }