[SDOI 2008]Cave 洞穴勘測
Description
輝輝熱衷於洞穴勘測。某天,他按照地圖來到了一片被標記為JSZX的洞穴群地區。經過初步勘測,輝輝發現這片區域由n個洞穴(分別編號為1到n)以及若幹通道組成,並且每條通道連接了恰好兩個洞穴。假如兩個洞穴可以通過一條或者多條通道按一定順序連接起來,那麽這兩個洞穴就是連通的,按順序連接在一起的這些通道則被稱之為這兩個洞穴之間的一條路徑。洞穴都十分堅固無法破壞,然而通道不太穩定,時常因為外界影響而發生改變,比如,根據有關儀器的監測結果,123號洞穴和127號洞穴之間有時會出現一條通道,有時這條通道又會因為某種稀奇古怪的原因被毀。輝輝有一臺監測儀器可以實時將通道的每一次改變狀況在輝輝手邊的終端機上顯示:如果監測到洞穴u和洞穴v之間出現了一條通道,終端機上會顯示一條指令 Connect u v 如果監測到洞穴u和洞穴v之間的通道被毀,終端機上會顯示一條指令 Destroy u v 經過長期的艱苦卓絕的手工推算,輝輝發現一個奇怪的現象:無論通道怎麽改變,任意時刻任意兩個洞穴之間至多只有一條路徑。因而,輝輝堅信這是由於某種本質規律的支配導致的。因而,輝輝更加夜以繼日地堅守在終端機之前,試圖通過通道的改變情況來研究這條本質規律。然而,終於有一天,輝輝在堆積成山的演算紙中崩潰了……他把終端機往地面一砸(終端機也足夠堅固無法破壞),轉而求助於你,說道:“你老兄把這程序寫寫吧”。輝輝希望能隨時通過終端機發出指令 Query u v,向監測儀詢問此時洞穴u和洞穴v是否連通。現在你要為他編寫程序回答每一次詢問。已知在第一條指令顯示之前,JSZX洞穴群中沒有任何通道存在。
Input
第一行為兩個正整數n和m,分別表示洞穴的個數和終端機上出現過的指令的個數。以下m行,依次表示終端機上出現的各條指令。每行開頭是一個表示指令種類的字符串s("Connect”、”Destroy”或者”Query”,區分大小寫),之後有兩個整數u和v (1≤u, v≤n且u≠v) 分別表示兩個洞穴的編號。
Output
對每個Query指令,輸出洞穴u和洞穴v是否互相連通:是輸出”Yes”,否則輸出”No”。(不含雙引號)
Sample Input
200 5
Query 123 127
Connect 123 127
Query 123 127
Destroy 127 123
Query 123 127
樣例輸入2 cave.in
3 5
Connect 1 2
Connect 3 1
Query 2 3
Destroy 1 3
Query 2 3
Sample Output
樣例輸出1 cave.outNo
Yes
No
樣例輸出2 cave.out
Yes
No
HINT
數據說明 10%的數據滿足n≤1000, m≤20000 20%的數據滿足n≤2000, m≤40000 30%的數據滿足n≤3000, m≤60000 40%的數據滿足n≤4000, m≤80000 50%的數據滿足n≤5000, m≤100000 60%的數據滿足n≤6000, m≤120000 70%的數據滿足n≤7000, m≤140000 80%的數據滿足n≤8000, m≤160000 90%的數據滿足n≤9000, m≤180000 100%的數據滿足n≤10000, m≤200000 保證所有Destroy指令將摧毀的是一條存在的通道本題輸入、輸出規模比較大,建議c\c++選手使用scanf和printf進行I\O操作以免超時
題解
板子題,一個$0$寫成$o$調了一下午。
$LCT$中最主要的是$Access$操作,$Access(u)$操作的含義是,從當前的節點$u$向他所在的根節點連一條重路徑,這是相當於把沿路的重路徑全都斷開,重新拉一條從$u$到根的重路徑。
$makeroot(u)$操作:
若想讓$u$成為當前樹的根節點,則可以先$access(u)$,再$splay(u)$,把$u$轉為當前$splay$的根節點。因為$splay$維護的是深度,所以$u$沒有右兒子(沒有比$u$還要深的點,因為重鏈定義),所以換根就相當於一次區間翻轉,交換左右子樹即完成區間翻轉。此時就可以打標記了。
所以,$makeroot(u)=access(u)+splay(u)+rev(u)$
$link(x,y)$操作:
在$u$和$v$之間連邊,可以$makeroot(u)$,再讓$u$的父親為$v$,這就相當於$v$本身是一棵$splay$,而$u$和$v$之間連了條輕邊。
$cut(u,v)$操作:
斷開$u$和$v$之間的邊,可以先$makeroot(u)$,再$access(v)$,再$splay(v)$,此時$v$的左兒子必定為$u$,於是斷開$v$和$v$的左兒子即可。
至於翻轉標記的傳遞,就是跟$Splay$一樣就行了。
但標記下放有一個問題。因為$splay$是時時刻刻在分裂與合並的,因為要動態維護每條重鏈,所以在$splay$之前,要先把根節點到當前節點全部下放一遍標記,防止標記下放不完全。
然後還要保存一些輕邊(虛邊),對於輕邊我們需要單獨記錄處理。在原樹上,當前重鏈的頂端節點與他的父親的邊即為輕邊,如果不記錄,樹將是不完整的。
具體到代碼實現,可以是當前$splay$的根節點的父親即為當前$splay$上面的那個重鏈所在的$splay$上的點,但上面的$splay$的兒子並不指向當前$splay$的父親,即為用$splay$的根節點的父親來存儲輕邊。
動態樹的主要時間消耗在$Access$上,而$Access$的時間復雜度是$O(nlogn)$。
1 //It is made by Awson on 2017.12.21 2 #include <map> 3 #include <set> 4 #include <cmath> 5 #include <ctime> 6 #include <queue> 7 #include <stack> 8 #include <vector> 9 #include <cstdio> 10 #include <string> 11 #include <cstdlib> 12 #include <cstring> 13 #include <iostream> 14 #include <algorithm> 15 #define LL long long 16 #define Max(a, b) ((a) > (b) ? (a) : (b)) 17 #define Min(a, b) ((a) < (b) ? (a) : (b)) 18 using namespace std; 19 const int N = 10000; 20 21 struct Link_Cut_Tree { 22 int ch[N+5][2], pre[N+5], isrt[N+5], rev[N+5]; 23 Link_Cut_Tree () { 24 memset(isrt, 1, sizeof(isrt)); 25 } 26 void pushdown(int o) { 27 if (!o || !rev[o]) return; 28 int ls = ch[o][0], rs = ch[o][1]; 29 swap(ch[ls][0], ch[ls][1]), swap(ch[rs][0], ch[rs][1]); 30 rev[ls] ^= 1, rev[rs] ^= 1; rev[o] = 0; 31 } 32 void rotate(int o, int kind) { 33 int p = pre[o]; 34 ch[p][!kind] = ch[o][kind], pre[ch[o][kind]] = p; 35 if (isrt[p]) isrt[o] = 1, isrt[p] = 0; 36 else ch[pre[p]][ch[pre[p]][1] == p] = o; 37 pre[o] = pre[p]; 38 ch[o][kind] = p, pre[p] = o; 39 } 40 void push(int o) { 41 if (!isrt[o]) push(pre[o]); 42 pushdown(o); 43 } 44 void splay(int o) { 45 push(o); 46 while (!isrt[o]) { 47 if (isrt[pre[o]]) rotate(o, ch[pre[o]][0] == o); 48 else { 49 int p = pre[o], kind = ch[pre[p]][0] == p; 50 if (ch[p][kind] == o) rotate(o, !kind), rotate(o, kind); 51 else rotate(p, kind), rotate(o, kind); 52 } 53 } 54 } 55 void access(int o) { 56 int y = 0; 57 while (o) { 58 splay(o); 59 isrt[ch[o][1]] = 1, isrt[ch[o][1] = y] = 0; 60 o = pre[y = o]; 61 } 62 } 63 void makeroot(int o) { 64 access(o); splay(o); 65 rev[o] ^= 1; swap(ch[o][0], ch[o][1]); 66 } 67 void link(int x, int y) { 68 makeroot(x); pre[x] = y; 69 } 70 void cut(int x, int y) { 71 makeroot(x); access(y); splay(y); 72 pre[x] = ch[y][0] = 0, isrt[x] = 1; 73 } 74 void query(int x, int y) { 75 while (pre[x]) x = pre[x]; 76 while (pre[y]) y = pre[y]; 77 if (x == y) printf("Yes\n"); 78 else printf("No\n"); 79 } 80 }T; 81 82 int n, m, x, y; 83 char s[20]; 84 85 void work() { 86 scanf("%d%d", &n, &m); 87 while (m--) { 88 scanf("%s%d%d", s, &x, &y); 89 if (s[0] == ‘Q‘) T.query(x, y); 90 else if (s[0] == ‘C‘) T.link(x, y); 91 else T.cut(x, y); 92 } 93 } 94 int main() { 95 work(); 96 return 0; 97 }
[SDOI 2008]Cave 洞穴勘測