<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      FHQ版LCT教程

      LCT but FHQ:

      邪教 ——FHQ & LCT

      小言:

      • 每條重鏈單獨使用一個平衡樹進行維護。
      • LCT:實鏈剖分與平衡樹維護動態樹的信息,并且同時維護多個動態樹,所以全局是森林。
      • 每個節點認定一個實兒子,然后把這條邊看成實邊,其他為虛邊,然后忽視虛邊,維護一堆鏈,如果需要別的操作,虛實邊可以隨時轉換。
      • 通過一個個連邊建樹來建圖,開始連的都是虛邊,在以后通過 \(\operatorname{access}\) 函數轉實邊,時間復雜度看似很大,實際不錯。

      概念:

      • 原樹:原來的多叉樹,方便觀察原樹形態。
      • 輔助樹:把一整個平衡樹看成一個整塊,用虛邊連起來這些整塊,方便觀察各個平衡樹形態。一坨實鏈里是平衡樹(二叉),然后虛邊連了好幾坨(多叉)

      為了方便,我把下面的單個平衡樹(一坨實鏈)全說成輔助樹了。

      結合代碼講解:

      其實 LCT 就像個暴力算法,大膽去寫,下面未解釋區域是因為自己推導并不難。

      注意:
      \(\operatorname{link(x,y)}\)\(\operatorname{cut}(x,y)\) 如果不保證合法,就都要判合法。

      ……—— FHQ 定義

      bool fx[300030];
      struct treap {int l,r,fa,sui,xu,v,x;bool lan;} t[300030];
      struct node {int x,y;};
      

      \(\operatorname{pushdown}\) —— FHQ 下傳懶標記

      懶標記之翻轉:用于 \(\operatorname{access}\),是必寫的,同時也是為什么只能用 FHQ 和 splay 的原因。

      void pushdown(int o)
      {
      	if(t[o].lan) swap(t[o].l,t[o].r),t[t[o].l].lan^=1,t[t[o].r].lan^=1,t[o].lan=0;
      }
      

      \(\operatorname{updata}\) —— FHQ 更新

      這里的異或是題目要求異或和。

      inline void updata(int o)
      {
      	t[o].x=t[o].v^t[t[o].l].x^t[t[o].r].x;
      	if(t[o].l) t[t[o].l].fa=o;
      	if(t[o].r) t[t[o].r].fa=o;
      }
      

      \(\operatorname{hebing}\) —— FHQ 合并

      int hebing(int x,int y)
      {
      	if(!x||!y) return x+y;
      	if(t[x].sui<t[y].sui) return pushdown(x),t[x].r=hebing(t[x].r,y),updata(x),x;
      	else return pushdown(y),t[y].l=hebing(x,t[y].l),updata(y),y;
      }
      

      \(\operatorname{isroot}\) —— 是否是輔助樹樹根

      bool isroot(int o) {return (t[t[o].fa].l!=o&&t[t[o].fa].r!=o)||!t[o].fa;}}
      

      \(\operatorname{findroot}\) —— 找輔助樹的根

      這里的 fx 數組同時記錄分裂時的方向,因為要按 o 的位置分裂,所以用分裂要先用這個函數。

      int findroot(int o)
      {
      	top=0;
      	while(!isroot(o)) fx[++top]=(t[t[o].fa].l==o),o=t[o].fa;
      	return o;
      }
      

      \(\operatorname{findleft}\) —— 找當前輔助樹中中序最小的

      本質上是找輔助樹在原樹上深度最小的節點。

      int findleft(int o)
      {
      	o=findroot(o),pushdown(o);
      	while(t[o].l) o=t[o].l,pushdown(o);
      	return o;
      }
      

      另一種寫法直接省略此函數,直接用輔助樹的根的 fa 數組為原樹深度最小的節點的父親(fa 數組是 FHQ 的),如果按此方法寫,可以用 \(\operatorname{findroot}\) + fa 數組解決。

      復雜度是一樣的,因為 \(\operatorname{findroot}\)\(\operatorname{findleft}\) 復雜度一樣。

      但是 FHQ 貌似只能用 \(\operatorname{findleft()}\),splay 一般維護 fa 數組。

      \(\operatorname{split}\) —— 改的 FHQ 分裂

      在當前輔助樹中把位置 \(\le o\) 的分裂為左,余為右。

      關于如何找 \(o\) 的位置,我們在分裂前必須用 \(\operatorname{findroot}\) 來記錄數組。

      void split(int o,int &l,int &r)
      {
      	if(!top) return pushdown(o),l=o,r=t[o].r,t[o].r=0,updata(o),void();
      	bool d=fx[top--];
      	d^=t[o].lan,pushdown(o);
      	if(d) r=o,split(t[o].l,l,t[o].l);
      	else l=o,split(t[o].r,t[o].r,r);
      	updata(o);
      }
      

      \(\operatorname{access}\) —— 把當前節點到根全部改為實鏈

      int access(int o)
      {
      	int last=0;
      	while(o)
      	{
      		int shang,xia;
      		split(findroot(o),shang,xia);
      		t[findleft(last)].xu=0;
      		last=hebing(shang,last);
      		t[findleft(xia)].xu=o;
      		o=t[findleft(last)].xu;
      	}
      	return last;
      }
      

      \(\operatorname{root}\) —— 找當前節點所在原樹的根

      int root(int o) {return findleft(access(o));}
      

      \(\operatorname{changroot}\) —— 把當前節點改為原樹的根

      void changeroot(int o) {t[access(o)].lan^=1;}
      

      \(\operatorname{link}\) —— 連 x 到 y 的邊

      void link(int x,int y) {changeroot(x),t[x].xu=y;}
      

      \(\operatorname{cut}\) —— 切斷 x 到 y 的邊

      void cut(int x,int y) {changeroot(x),access(y),access(x),t[y].xu=0;}
      

      \(\operatorname{query}\) —— 查詢

      這個代碼是查詢 x 到 y 的路徑上的 xor 和:

      int query(int x,int y) {return changeroot(x),access(y),t[findroot(y)].x;}
      

      \(\operatorname{change}\) —— 修改

      這個代碼是將點 x 上的權值變成 y。

      void change(int o,int v)
      {
      	int o1,o2;
      	changeroot(o);
      	split(findroot(o),o1,o2);
      	t[o].v=v,hebing(o1,o2);
      }
      

      \(\operatorname{solve}\)

      cin>>n>>m;
      for(int i=1;i<=n;++i) cin>>t[i].v,t[i].x=t[i].v,t[i].sui=rand();
      for(int i=1,op,x,y;i<=m;i++)
      {
        cin>>op>>x>>y;
        if(op==0) cout<<query(x,y)<<'\n';
        else if(op==1&&root(x)!=root(y)) link(x,y);
        else if(op==2) cut(x,y);
        else if(op==3) change(x,y);
      }
      

      優秀的時間復雜度

      splay 時間復雜度 \(O(n \log n)\),常數 \(≈11.4514\)
      由于實現只要能維護翻轉操作就行,所以可以選擇 FHQ,FHQ 常數更小了,但是時間復雜度是 \(O(n \log ^2n)\),寫個快讀表現就和 splay 差不多了。

      關于 splay LCT 時間復雜度的嚴謹證明
      參照這個,我們發現訪問虛邊的勢能分析沒影響,但是 FHQ 無法均攤平衡樹復雜度,所以時間復雜度要把 FHQ 的 \(O( \log n)\) 和跳鏈的 \(O( \log n)\),最后證明是 \(O(n \log ^2n)\)

      LCT 應用:

      1. 動態樹問題。

      無需多言。

      2. 大部分樹鏈剖分操作。

      好處:

      • 在維護鏈用 splay 理論復雜度甚至更快,但是常數很大!樹剖非理論復雜度很快。
      • 比樹剖套數據結構的代碼短。

      壞處:

      • 維護子樹難
        最好學的 LCT 動態樹無法修改子樹!令人傷心。
        至于怎么路徑改子樹查……
        蒟蒻我寫 FHQ 維護寫掛了,沒調出來,思路大概是每個點用 set 維護虛子樹信息,然后查詢把所有兒子都變成虛兒子后查自己和 set 的信息并。
        我知道我的說法并不好,建議大家另找別的博客學習維護子樹,抱歉。

      3. 支持刪除邊的并查集(需保證無環)。

      找根操作。

      4. 可在線維護邊權。(最小生成樹之類)。

      看題理解吧。

      5. 在線加邊維護邊雙聯通分量。

      \(\operatorname{link}\) 的時候,如果發現 \(x\)\(y\) 在一個樹里,有環,那肯定要開始縮了,鎖點的時候,把這個環上的所有點全部鎖到這個輔助樹的根節點,因為可以把根節點的 \(fa\) 設為原樹父親,比較特殊,所以縮這里。

      根節點為標志節點,用并查集代表當前節點被鎖到哪去了,然后把當前環上的點提出來一個輔助樹,然后遞歸這個輔助樹更新并查集為標志節點,最后斷開標志節點與子樹的連接,同時在需要訪問原樹的父親節點的時候,需要套用并查集,因為這個點的父親可能已經被縮了。

      函數遞歸縮點:

      void del(int x,int y) {if(x) bcj[x]=y,del(lc,y),del(rc,y);}
      

      找爸爸:

      \(fa(x)←find(fa_x)\)

      6. 維護樹上染色聯通塊。

      看題理解吧

      7. 求 LCA

      inline int access(int x)
      {
          int y = 0;
          while (x) { splay(x); Rs(x) = y; pushup(x); x = Fa(y = x); }
          return y;
      }
      int lca = (access(u), access(v));
      

      我們拉完 \(u\) 到根的實鏈后再拉 \(v\) 的。則最后一個需要調整的實鏈頂對應的結點自然是 \(lca(u,v)\)

      在動態樹上,你想寫 LCA:

      忍住別寫:

      • 倍增 LCA:動態樹上不可離線。
      • 在動態樹上跳鏈 LCA:在動態樹上復雜度是均攤的,普通跳鏈復雜度不對。(\(\operatorname{access}\) 其實也是跳鏈,但是它一路上變了好多實鏈,這是與其不一樣的地方)。

      LCT 的痛點:

      1. 無法子樹修改

      只有 TopTree 可以子樹修改。

      2. 原樹根不固定

      你不要自以為是地 \(\operatorname{access}\)

      std

      #include<bits/stdc++.h>
      using namespace std;
      int n,m,top;
      bool fx[300030];
      struct treap {int l,r,fa,sui,xu,v,x;bool lan;} t[300030];
      void pushdown(int o)
      {
      	if(t[o].lan) swap(t[o].l,t[o].r),t[t[o].l].lan^=1,t[t[o].r].lan^=1,t[o].lan=0;
      }
      void updata(int o)
      {
      	t[o].x=t[o].v^t[t[o].l].x^t[t[o].r].x;
      	if(t[o].l) t[t[o].l].fa=o;
      	if(t[o].r) t[t[o].r].fa=o;
      }
      int hebing(int x,int y)
      {
      	if(!x||!y) return x+y;
      	if(t[x].sui<t[y].sui) return pushdown(x),t[x].r=hebing(t[x].r,y),updata(x),x;
      	else return pushdown(y),t[y].l=hebing(x,t[y].l),updata(y),y;
      }
      bool isroot(int o) {return (t[t[o].fa].l!=o&&t[t[o].fa].r!=o)||!t[o].fa;}
      int findroot(int o)
      {
      	top=0;
      	while(!isroot(o)) fx[++top]=(t[t[o].fa].l==o),o=t[o].fa;
      	return o;
      }
      void split(int o,int &l,int &r)
      {
      	if(!top) return pushdown(o),l=o,r=t[o].r,t[o].r=0,updata(o),void();
      	bool d=fx[top--];
      	d^=t[o].lan,pushdown(o);
      	if(d) r=o,split(t[o].l,l,t[o].l);
      	else l=o,split(t[o].r,t[o].r,r);
      	updata(o);
      }
      int findleft(int o)
      {
      	o=findroot(o),pushdown(o);
      	while(t[o].l) o=t[o].l,pushdown(o);
      	return o;
      }
      int access(int o)
      {
      	int last=0;
      	while(o)
      	{
      		int shang,xia;
      		split(findroot(o),shang,xia);
      		t[findleft(last)].xu=0;
      		last=hebing(shang,last);
      		t[findleft(xia)].xu=o;
      		o=t[findleft(last)].xu;
      	}
      	return last;
      }
      int root(int o) {return findleft(access(o));}
      void changeroot(int o) {t[access(o)].lan^=1;}
      void link(int x,int y) {changeroot(x),t[x].xu=y;}
      void cut(int x,int y) {changeroot(x),access(y),access(x),t[y].xu=0;}
      int query(int x,int y) {return changeroot(x),access(y),t[findroot(y)].x;}
      void change(int o,int v)
      {
      	int o1,o2;
      	changeroot(o);
      	split(findroot(o),o1,o2);
      	t[o].v=v,hebing(o1,o2);
      }
      void read(int &x)
      {
      	x=0;
      	char c=getchar();
      	while(c<'0'||c>'9') c=getchar();
      	while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
      }
      int main()
      {
      	cin>>n>>m;
      	for(int i=1;i<=n;++i) read(t[i].v),t[i].x=t[i].v,t[i].sui=rand();
      	for(int i=1,op,x,y;i<=m;i++)
      	{
      		read(op),read(x),read(y);
      		if(op==0) printf("%d\n",query(x,y));
      		else if(op==1&&root(x)!=root(y)) link(x,y);
      		else if(op==2) cut(x,y);
      		else if(op==3) change(x,y);
      	}
      	return 0;
      }
      
      posted @ 2025-09-17 22:21  _a1a2a3a4a5  閱讀(30)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 亚洲色拍拍噜噜噜最新网站| 亚洲日韩成人av无码网站| 视频一区二区不中文字幕| 在线 | 国产精品99传媒a| 无码人妻精品一区二区三区蜜桃| 高邑县| 国产在线一区二区在线视频| 久久亚洲精品11p| 亚洲中文久久久久久精品国产| 亚洲av综合av一区| 亚洲av国产成人精品区| 97se亚洲国产综合在线| 亚洲人成网站77777在线观看| 精品久久亚洲中文无码| 亚洲av中文乱码一区二| 2020久久国产综合精品swag| 国产在线观看播放av| 17岁日本免费bd完整版观看| 免费人成视频网站在线18| 国产成人8X人网站视频| 亚洲色欲在线播放一区| 熟妇人妻av中文字幕老熟妇 | 国产中文三级全黄| 亚洲综合精品一区二区三区| 国产老熟女伦老熟妇露脸| 亚洲国产精品久久久天堂麻豆宅男| 抚州市| 无遮高潮国产免费观看| 综合色久七七综合尤物| 国产一二三五区不在卡| 国产区精品福利在线熟女| 99中文字幕精品国产| 亚洲Av综合日韩精品久久久 | 国产一二三五区不在卡| 欧美gv在线| 亚洲av日韩在线资源| 青青草成人免费自拍视频| 色老板精品无码免费视频| 亚洲欧洲日韩精品在线| 国产一级特黄高清大片一| 中国美女a级毛片|