1. 程式人生 > >LCT(link cut tree) 動態樹

LCT(link cut tree) 動態樹

技術分享 spa turn ++ 知識點 操作 ini pla mda

模板參考:https://blog.csdn.net/saramanda/article/details/55253627

幾個知識點:

1、LCT中用Splay維護鏈,這些Splay叫做“輔助樹“。輔助樹以它上面每個節點的深度為關鍵字維護,就是輔助樹中每個節點左兒子的深度小於當前節點的深度,當前節點的深度小於右兒子的深度。

2、LCT相當於多棵splay被虛線連在一起,即splay森林;而最開始的時候是N個單獨的點與他的父親用虛線相連,每個點是一棵splay。

3、無論樹怎樣旋轉,怎樣變換,讀入時所連接(link)的邊(沒有被cut的),一直都連著的

4、在每棵splay中每一個結點左子樹中的節點都是他在原樹中的祖先,右子樹中的結點都是他在原樹中的孩子。

5、splay森林實例:

原樹:

技術分享圖片

一種可能的splay森林:

技術分享圖片

6、access(x)操作:

技術分享圖片

—————————題目—————————

1、Cave 洞穴勘測 HYSBZ - 2049

  題意:一開始有n個洞穴,兩兩之間沒有通道。每次將兩個洞穴連接或者兩個洞穴之間的通道摧毀,或者詢問兩個洞穴之間能否連通。

  思路:LCT模板題。連接則通過link(u,v)實現,摧毀通過cut(u,v)實現,兩個洞穴能否連通則考慮u的根和v的根是否相同。

技術分享圖片
  1 #include<iostream>
  2 #include<cstdio>
  3 using namespace std;
  4 const int maxn = 10000 + 10;
  5 struct LCT
  6 {
  7     struct node
  8     {
  9         int fa, ch[2]; //父親(Splay對應的鏈向上由輕邊連著哪個節點)、左右兒子
 10         int reverse;//區間反轉標記
 11         bool  is_root;   //是否是所在Splay的根
12 //int siz; 13 //int val; 14 //int sum; 15 }Tree[maxn]; 16 int n; 17 //int v[maxn];//每個結點的值 18 void init() 19 { 20 for (int i = 1; i <= n; i++) 21 { 22 Tree[i].reverse = Tree[i].fa = Tree[i].ch[0] = Tree[i].ch[1] = 0; 23 Tree[i].is_root = true; 24 //Tree[i].siz = 1; 25 //Tree[i].val = Tree[i].sum = v[i]; 26 } 27 } 28 29 bool getson(int x) 30 {//x是否為重兒子 31 return x == Tree[Tree[x].fa].ch[1]; 32 } 33 bool isroot(int x) 34 { 35 return Tree[Tree[x].fa].ch[0] != x && Tree[Tree[x].fa].ch[1] != x; 36 } 37 void pushreverse(int x) 38 { 39 if (!x)return; 40 swap(Tree[x].ch[0], Tree[x].ch[1]); 41 Tree[x].reverse ^= 1; 42 } 43 void pushdown(int x) 44 {//下傳反轉標記 45 if (Tree[x].reverse) 46 { 47 pushreverse(Tree[x].ch[0]); 48 pushreverse(Tree[x].ch[1]); 49 Tree[x].reverse = 0; 50 } 51 } 52 /* 53 void update(int x) 54 { 55 int l = Tree[x].ch[0], r = Tree[x].ch[1]; 56 Tree[x].siz = Tree[l].siz + Tree[r].siz + 1; 57 Tree[x].sum = Tree[l].sum + Tree[r].sum + Tree[x].val; 58 } 59 */ 60 void rotate(int x) 61 {//將x旋轉為根 62 if (Tree[x].is_root)return; 63 int k = getson(x), fa = Tree[x].fa; 64 int fafa = Tree[fa].fa; 65 pushdown(fa); pushdown(x); //先要下傳標記 66 Tree[fa].ch[k] = Tree[x].ch[k ^ 1]; 67 if (Tree[x].ch[k ^ 1])Tree[Tree[x].ch[k ^ 1]].fa = fa; 68 Tree[x].ch[k ^ 1] = fa; 69 Tree[fa].fa = x; 70 Tree[x].fa = fafa; 71 if (!Tree[fa].is_root)Tree[fafa].ch[fa == Tree[fafa].ch[1]] = x; 72 else Tree[x].is_root = true, Tree[fa].is_root = false; 73 //update(fa);update(x); //如果維護了信息,就要更新節點 74 } 75 void push(int x) 76 { 77 if (!Tree[x].is_root) push(Tree[x].fa); 78 pushdown(x); 79 } 80 int getFa(int x) 81 {//尋找x在原樹的父親 82 access(x); 83 Splay(x); 84 while (Tree[x].ch[0]) x = Tree[x].ch[0]; 85 return x; 86 } 87 void Splay(int x) 88 {//讓x成為Splay的根 89 push(x); //在Splay到根之前,必須先傳完反轉標記 90 for (int fa; !Tree[x].is_root; rotate(x)) { 91 if (!Tree[fa = Tree[x].fa].is_root) { 92 rotate((getson(x) == getson(fa)) ? fa : x); 93 } 94 } 95 } 96 void access(int x) 97 {//訪問某節點。作用是:對於訪問的節點x,打通一條從樹根(真實的LCT樹)到x的重鏈;如果x往下是重鏈,那麽把x往下的重邊改成輕邊。 98 int y = 0; 99 do { 100 Splay(x); 101 Tree[Tree[x].ch[1]].is_root = true; 102 Tree[Tree[x].ch[1] = y].is_root = false; 103 //update(x); //如果維護了信息記得更新。 104 x = Tree[y = x].fa; 105 } while (x); 106 } 107 void mroot(int x) 108 {//把某個節點變成樹根(這裏的根指的是整棵LCT的根) 109 access(x);//使x與根結點處在同一棵splay中 110 Splay(x);//x成為這棵splay的根,x只有左兒子 111 pushreverse(x); 112 } 113 void link(int u, int v) 114 {//連接u所在的LCT和v所在的LCT 115 mroot(u);//先讓u成為其所在LCT的根 116 Tree[u].fa = v; 117 } 118 void cut(int u, int v) 119 {//分離出兩棵LCT 120 mroot(u); //先讓u成為其所在LCT的根 121 access(v); //讓u和v在同一棵Splay中 122 Splay(v); //連接u、v,u是v的左兒子 123 pushdown(v); //先下傳標記 124 Tree[Tree[v].ch[0]].fa = Tree[v].fa; 125 Tree[Tree[v].ch[0]].is_root = true; 126 Tree[v].fa = 0; 127 Tree[v].ch[0] = 0; 128 //v的左孩子表示v上方相連的重鏈 129 //update(v); //記得維護信息 130 } 131 bool judge(int u, int v) 132 {//判斷u和v是否連通 133 while (Tree[u].fa) u = Tree[u].fa; 134 while (Tree[v].fa) v = Tree[v].fa; 135 return u == v; 136 } 137 }lct; 138 int main() 139 { 140 int m; 141 while (~scanf("%d%d", &lct.n, &m)) 142 { 143 lct.init(); 144 char op[50]; 145 int u, v; 146 for (int i = 1; i <= m; i++) 147 { 148 scanf("%s%d%d", op, &u, &v); 149 if (op[0] == Q) 150 { 151 if (lct.judge(u, v)) printf("Yes\n"); 152 else printf("No\n"); 153 } 154 else if (op[0] == C) 155 { 156 lct.link(u, v); 157 } 158 else 159 { 160 lct.cut(u, v); 161 } 162 } 163 } 164 return 0; 165 }
View Code

2、Bounce 彈飛綿羊 HYSBZ - 2002

  題意:有n個彈射裝置,當綿羊在第i個彈射裝置時,會被彈射到第i+val[i]個彈射裝置,val數組記錄每個彈射裝置的彈射系數。有兩個操作,要麽詢問當綿羊站在第x個彈射裝置時會經過多少次被彈飛,要麽修改某個彈射裝置的系數。

  思路:可以將第i個彈射裝置與第i+val[i]個裝置相連,新增第n+1個點作為樹根,表示被彈飛。詢問次數時即詢問當前x到樹根的距離即深度,然後-1即可(第n+1個結點不彈射);修改某個彈射裝置的系數相當於修改某個結點的父親,先將原父親刪除,再重新連接父親。

技術分享圖片
  1 #include<iostream>
  2 #include<cstdio>
  3 #include<algorithm>
  4 using namespace std;
  5 const int maxn = 200000 + 10;
  6 int val[maxn];
  7 struct LCT
  8 {
  9     struct node
 10     {
 11         int fa, ch[2]; //父親(Splay對應的鏈向上由輕邊連著哪個節點)、左右兒子
 12         int reverse;//區間反轉標記
 13         bool  is_root;   //是否是所在Splay的根
 14         int siz;
 15     }Tree[maxn];
 16     int n;
 17     void init(int maxn)
 18     {
 19         for (int i = 1; i <= maxn; i++)
 20         {
 21             Tree[i].reverse = Tree[i].fa = Tree[i].ch[0] = Tree[i].ch[1] = 0;
 22             Tree[i].is_root = true;
 23             Tree[i].siz = 1;
 24         }
 25     }
 26 
 27     bool getson(int x)
 28     {//x是否為重兒子
 29         return x == Tree[Tree[x].fa].ch[1];
 30     }
 31     bool isroot(int x)
 32     {
 33         return Tree[Tree[x].fa].ch[0] != x && Tree[Tree[x].fa].ch[1] != x;
 34     }
 35     void pushreverse(int x)
 36     {
 37         if (!x)return;
 38         swap(Tree[x].ch[0], Tree[x].ch[1]);
 39         Tree[x].reverse ^= 1;
 40     }
 41     void pushdown(int x)
 42     {//下傳反轉標記
 43         if (Tree[x].reverse)
 44         {
 45             pushreverse(Tree[x].ch[0]);
 46             pushreverse(Tree[x].ch[1]);
 47             Tree[x].reverse = 0;
 48         }
 49     }
 50 
 51     void update(int x)
 52     {
 53         int l = Tree[x].ch[0], r = Tree[x].ch[1];
 54         Tree[x].siz = 1;
 55         if (l) Tree[x].siz += Tree[l].siz;
 56         if (r) Tree[x].siz += Tree[r].siz;
 57     }
 58 
 59     void rotate(int x)
 60     {//將x旋轉為根
 61         if (Tree[x].is_root)return;
 62         int k = getson(x), fa = Tree[x].fa;
 63         int fafa = Tree[fa].fa;
 64         pushdown(fa); pushdown(x);    //先要下傳標記
 65         Tree[fa].ch[k] = Tree[x].ch[k ^ 1];
 66         if (Tree[x].ch[k ^ 1])Tree[Tree[x].ch[k ^ 1]].fa = fa;
 67         Tree[x].ch[k ^ 1] = fa;
 68         Tree[fa].fa = x;
 69         Tree[x].fa = fafa;
 70         if (!Tree[fa].is_root)Tree[fafa].ch[fa == Tree[fafa].ch[1]] = x;
 71         else Tree[x].is_root = true, Tree[fa].is_root = false;
 72         update(fa);update(x);    //如果維護了信息,就要更新節點
 73     }
 74     void push(int x)
 75     {
 76         if (!Tree[x].is_root) push(Tree[x].fa);
 77         pushdown(x);
 78     }
 79     int getFa(int x)
 80     {//尋找x在原樹的父親
 81         access(x);
 82         Splay(x);
 83         while (Tree[x].ch[0]) x = Tree[x].ch[0];
 84         return x;
 85     }
 86     void Splay(int x)
 87     {//讓x成為Splay的根
 88         push(x);   //在Splay到根之前,必須先傳完反轉標記
 89         for (int fa; !Tree[x].is_root; rotate(x)) {
 90             if (!Tree[fa = Tree[x].fa].is_root) {
 91                 rotate((getson(x) == getson(fa)) ? fa : x);
 92             }
 93         }
 94     }
 95     void access(int x)
 96     {//訪問某節點。作用是:對於訪問的節點x,打通一條從樹根(真實的LCT樹)到x的重鏈;如果x往下是重鏈,那麽把x往下的重邊改成輕邊。
 97         int y = 0;
 98         do {
 99             Splay(x);
100             Tree[Tree[x].ch[1]].is_root = true;
101             Tree[Tree[x].ch[1] = y].is_root = false;
102             update(x);    //如果維護了信息記得更新。
103             x = Tree[y = x].fa;
104         } while (x);
105     }
106     void mroot(int x)
107     {//把某個節點變成樹根(這裏的根指的是整棵LCT的根)
108         access(x);//使x與根結點處在同一棵splay中
109         Splay(x);//x成為這棵splay的根,x只有左兒子
110         pushreverse(x);
111     }
112     void link(int u, int v)
113     {//連接u所在的LCT和v所在的LCT
114         mroot(u);//先讓u成為其所在LCT的根
115         Tree[u].fa = v;
116         Tree[u].is_root = true;
117     }
118     void cut(int u, int v)
119     {//分離出兩棵LCT
120         mroot(u);   //先讓u成為其所在LCT的根
121         access(v); //讓u和v在同一棵Splay中
122         Splay(v);    //連接u、v,u是v的左兒子
123         pushdown(v);     //先下傳標記
124         Tree[Tree[v].ch[0]].fa = Tree[v].fa;
125         Tree[Tree[v].ch[0]].is_root = true;
126         Tree[v].fa = 0;
127         Tree[v].ch[0] = 0;
128         //v的左孩子表示v上方相連的重鏈
129         update(v);  //記得維護信息
130     }
131     bool judge(int u, int v)
132     {//判斷u和v是否連通
133         while (Tree[u].fa) u = Tree[u].fa;
134         while (Tree[v].fa) v = Tree[v].fa;
135         return u == v;
136     }
137     int Query_deep(int x)
138     {//詢問x到LCT根的距離(深度)
139         access(x);
140         Splay(x);
141         return Tree[x].siz;
142     }
143 }lct;
144 int main()
145 {
146     int m;
147     while (~scanf("%d", &lct.n))
148     {
149         lct.init(lct.n+1);////讓n+1表示被彈飛
150         for (int i = 1; i <= lct.n; i++)
151         {
152             scanf("%d", &val[i]);
153             int id = min(i + val[i], lct.n + 1);
154             lct.link(i, id);
155         }
156         lct.mroot(lct.n + 1);
157         scanf("%d", &m);
158         for (int i = 1; i <= m; i++)
159         {
160             int op, x;
161             scanf("%d%d", &op, &x);
162             x++;
163             if (op == 1) printf("%d\n", lct.Query_deep(x)-1);
164             else
165             {
166                 int v;
167                 scanf("%d", &v);
168                 int oid = min(x + val[x], lct.n + 1);
169                 int nid = min(x + v, lct.n + 1);
170                 lct.cut(x, oid);
171                 lct.link(x, nid);
172                 lct.mroot(lct.n + 1);
173                 val[x] = v;
174             }
175         }
176     }
177     return 0;
178 }
View Code

LCT(link cut tree) 動態樹