動態樹算法概述及習題

動態樹概述

一、適用問題

動態樹主要用於解決操作中帶有加邊、刪邊、換根的一系列問題,即樹結構發生變化的問題,理論上來說,樹鏈剖分的問題都能用 LCTLCT 進行解決。

二、函數解析

LCTLCT 本質是上對樹進行實鏈剖分,實鏈剖分的意思就是將一棵樹分成多條鏈,鏈中的邊稱爲實邊,鏈與鏈之間的邊則稱爲虛邊,每條鏈都是一個 splaysplay,在 splaysplay 中進行中序遍歷即可還原原來的樹結構。而 LCTLCT 就是不斷進行虛邊、實邊轉換的一個算法。

LCTLCT 中一共有 clearclearpushUppushUppushDownpushDownupdateupdaterotaterotatesplaysplayaccessaccessmakeRootmakeRootlinklinkcutcutfindfindsplitsplit 等函數,下面大致介紹一下每個函數的具體作用以及一些坑點,更多的是提綱挈領的作用,想要從最基礎的地方開始學的話,推薦 oiwiki

簡單函數(僅操作單個 splaysplay 的函數)

  1. clear(x)clear(x):清除一個點的信息,如父親、左右兒子、標記、維護信息等信息。
  2. pushUp(x)pushUp(x):由左右兒子的信息更新父節點的信息,與線段樹的 pushUp()pushUp() 函數沒有太大差別。
  3. pushDown(x)pushDown(x):將當前節點的標記下放到兒子節點,如加、減、翻轉等標記。
  4. update(x)update(x):一直遞歸到根節點,然後把標記信息不斷下放,沒有涉及任何虛實邊的轉換。
 void update(int p){ //遞歸地從上到下pushDown信息
   if(!isRoot(p)) update(f[p]);
   pushDown(p);
 }
  1. rotate(x)rotate(x):將當前節點向上旋轉一層,可以自己模擬一下。此處改變了 splaysplay 的內部結構,即子節點發生了改變,因此需要進行 pushUppushUp,但是仍然沒有進行任何虛實邊的轉換。
 inline void rotate(int x){ //將x向上旋轉一層的操作
   int y = f[x], z = f[y], k = Get(x);
   if(!isRoot(y)) ch[z][ch[z][1] == y] = x;
   ch[y][k] = ch[x][!k], f[ch[y][k]] = y;
   ch[x][!k] = y, f[y] = x, f[x] = z;
   pushUp(y); //要先pushUp(y)
   pushUp(x);
 }
  1. splay(x)splay(x):將當前點旋轉到 splaysplay 的根節點,splaysplay 到根節點作用在於不需要在向上進行更新。比如你現在要修改 xx 的點權,但是每個節點還要維護子樹 sumsum 的信息,如果 xx 不是其所在 splaysplay 的根節點,那麼修改 xx 的點權勢必影響到其祖先節點的 sumsum 信息,因此需要將 xx 旋轉爲其所在 splaysplay 的根後再進行單點修改。注意 splaysplay 函數也沒有進行實邊和虛邊的轉換。
 inline void splay(int x){ //把x旋轉到當前splay的根
   update(x); //將上面的標記完全下放
   for(int fa; fa = f[x], !isRoot(x); rotate(x)){
     if(!isRoot(fa)) rotate(Get(fa) == Get(x) ? fa : x);
   }
 }

以上函數都屬於 LCTLCT 函數中的簡單函數,因爲這些函數都只是在單個 splaysplay 中進行操作,不涉及任何虛實邊的轉換。

複雜函數(涉及多個 splaysplay 的操作,進行虛實邊轉換)

  1. access(x)access(x):將點 xx 到根的路徑經過的點放入同一個 splaysplay 中,且這個 splaysplay 中僅包含從 xx 到根路徑上經過的點。具體操作即是將 xx 點不斷轉成其所在 splaysplay 的根,然後再進行虛實邊轉換一直到根。此處 accessaccess 函數有返回值,返回值爲最後構成的 splaysplay 的根節點。
inline int access(int x){ //把從根到x的所有點放在一條實鏈裏, 返回這個splay的根
    int p; //每次改變右兒子的值,因爲整棵樹是中序遍歷,放入右兒子才能保證先遍歷父親再遍歷兒子
    for(p = 0; x; p = x, x = f[x]){
      splay(x), ch[x][1] = p, pushUp(x);
    }
    return p;
}
  1. makeRoot(x)makeRoot(x):換根操作,將點 xx 變成當前樹的根。具體過程爲先 accessaccessxx,然後再將點 xx 旋轉爲其 splaysplay 所在根,然後將所有節點的左右兒子翻轉即可。
inline void makeRoot(int p){ //使x點成爲整棵樹的根
    access(p); splay(p);
    swap(ch[p][0],ch[p][1]); //把整條鏈反向
    rev[p] ^= 1;
}
  1. split(x,y)split(x,y):從樹中拎出 xyx\rightarrow y 的路徑,返回該路徑的 splaysplay 根節點,可以查詢路徑最大值、點權和、邊權和等信息。
inline int split(int x,int y){
	makeRoot(x); 
	return access(y);
} 
  1. find(x)find(x):即返回點 xx 所在樹的根節點,不是所在 splaysplay 中的根節點,用於判斷兩點是否連通。
 inline int find(int p){ //找到x所在樹的根節點編號
   access(p), splay(p);
   while(ls) pushDown(p), p = ls;
   return p;
 }
  1. link(x,y)link(x,y):連接樹中 xxyy 兩點之間的邊,無邊變虛邊,如果題目中沒有保證操作一定合法,則需要自行判斷 xxyy 是否已經連通。
inline void link(int x,int y){ //在x、y兩點間連一條邊,連接了虛邊
   if (find(x) != find(y)) makeRoot(x), f[x] = y;
 }
  1. cut(x,y)cut(x,y):斷開樹中 xxyy 兩點之間的實邊,兩個點同時斷開即可。
inline void cut(int x,int p){ //把x、y兩點間邊刪掉,此處刪除的是實邊,注意實邊和虛邊的區別
   makeRoot(x), access(p), splay(p);
   if (ls == x && !rs) ls = f[x] = 0;
 }

三、具體細節

單點修改

由於 LCTLCT 中維護了多個 splaysplay,因此單點修改需要把該點修改的信息不斷上傳,所以我們需要先將點 xx 旋轉到 splaysplay 的根或者整棵樹的根,然後再進行單點修改。

如果題中只需要維護實鏈信息,則只需要旋轉到 splaysplay 的根,如果需要同時維護實鏈和虛鏈信息,即整棵子樹的信息的話,則需要令該點爲樹根,即調用 makeRoot()makeRoot() 函數。

維護邊權

由於 LCTLCT 是不斷地進行虛邊、實邊轉換,因此沒有固定的邊結構,所以直接維護邊權十分困難,因此我們將邊轉成點,邊 (a,b)(a,b) 成爲一個點 xxlink(a,x)link(a,x)link(x,b)link(x,b) 即可。

維護子樹信息

普通 LCTLCT 只能維護具有可減性的子樹信息,比如子樹大小,子樹貢獻等,而子樹 maxmaxminmin 等問題則不具有可減性,難以維護。

維護子樹信息主要在於維護實邊信息和虛邊信息,而進行實虛轉換的函數只有 makeRoot()makeRoot()access()access()link()link() 三個函數,只需要在該三個函數進行一定的修改即可,下面習題中包含了該問題可供參考。


動態樹習題

1. [國家集訓隊] Tree II(模板題)

題意: nn 個點一棵樹,支持四種操作。(1n,q105,0c104)(1\leq n,q\leq 10^5,0\leq c\leq 10^4)

  • + u v c+\ u \ v \ c,將 uuvv 的路徑上的點的權值都加上 cc
  •  u1 v1 u2 v2-\ u_1 \ v_1 \ u_2\ v_2,將樹中原有的邊 (u1,v1)(u_1,v_1) 刪除,加入一條新邊 (u2,v2)(u_2,v_2),保證操作完之後仍然是一顆樹。
  • \  u v c*\ u \ v \ c,將 uuvv 的路徑上的點的權值都乘上 cc
  • / u vu \ v,詢問 uuvv 的路徑上的點的權值和,答案 mod 51061mod\ 51061

思路: 三個涉及到路徑的操作,都是先把 uu 變成樹根,然後 access(v)access(v),即拉起一條 uuvv 的路徑,使得 uuvv 路徑上的點都在一個 splaysplay 中,然後獲得這個 splaysplay 的根節點,即可對根節點打標記完成。

刪邊則是令 uu 爲根,拉起 uuvv 的路徑,將 vv 旋轉成 splaysplay 的根,然後兒子與父親雙向斷開聯繫。加邊則是令 uu 爲根,然後使 uu 的父親變成 vv

總結: 這題應該算是 LCTLCT 的模板題,涉及的操作都是基礎操作,沒有太多思維上的難點。

代碼:

#include <bits/stdc++.h>
#define rep(i,a,b) for(int i = a; i <= b; i++)
using namespace std;
#define int long long
const int N = 100010;
const int mod = 51061;
int n, q, u, v, c;
char op;

struct LCT{
  #define ls ch[p][0]
  #define rs ch[p][1]
  #define Get(p) (ch[f[p]][1] == p)
  int ch[N][2], f[N], sum[N], val[N], siz[N], rev[N], add[N], mul[N];

  inline void clear(int p){ //清除這個點的信息
    ch[p][0] = ch[p][1] = f[p] = siz[p] = val[p] = sum[p] = rev[p] = add[p] = 0;
    mul[p] = 1;
  }

  inline int isRoot(int p){
    clear(0);
    return ch[f[p]][0] != p && ch[f[p]][1] != p;
  }

  inline void pushUp(int p){
    clear(0);
    siz[p] = (siz[ls] + 1 + siz[rs]) % mod;
    sum[p] = (sum[ls] + val[p] + sum[rs]) % mod;
  }

  inline void pushDown(int p){
    clear(0);
    if(mul[p] != 1){ //乘法
      if(ls){ //左兒子
        mul[ls] = (mul[ls] * mul[p]) % mod;
        val[ls] = (val[ls] * mul[p]) % mod;
        sum[ls] = (sum[ls] * mul[p]) % mod;
        add[ls] = (add[ls] * mul[p]) % mod;
      }
      if(rs){ //右兒子
        mul[rs] = (mul[rs] * mul[p]) % mod;
        val[rs] = (val[rs] * mul[p]) % mod;
        sum[rs] = (sum[rs] * mul[p]) % mod;
        add[rs] = (add[rs] * mul[p]) % mod;
      }
      mul[p] = 1;
    }
    if(add[p]){
      if(ls){
        add[ls] = (add[ls] + add[p]) % mod;
        val[ls] = (val[ls] + add[p]) % mod;
        sum[ls] = (sum[ls] + add[p] * siz[ls] % mod) % mod;
      }
      if(rs){
        add[rs] = (add[rs] + add[p]) % mod;
        val[rs] = (val[rs] + add[p]) % mod;
        sum[rs] = (sum[rs] + add[p] * siz[rs] % mod) % mod;
      }
      add[p] = 0;
    }
    if(rev[p]){ 
      if(ls) rev[ls] ^= 1, swap(ch[ls][0],ch[ls][1]);
      if(rs) rev[rs] ^= 1, swap(ch[rs][0],ch[rs][1]);
      rev[p] = 0;
    }
  }

  void update(int p){ //遞歸地從上到下pushDown信息
    //沒有將實邊變成虛邊
    if(!isRoot(p)) update(f[p]);
    pushDown(p);
  }

  inline void rotate(int x){ //將x向上旋轉一層的操作
    //沒有將實邊變成虛邊
    int y = f[x], z = f[y], k = Get(x);
    if(!isRoot(y)) ch[z][ch[z][1] == y] = x;
    ch[y][k] = ch[x][!k], f[ch[y][k]] = y;
    ch[x][!k] = y, f[y] = x, f[x] = z;
    pushUp(y); //要先pushUp(y)
    pushUp(x);
  }

  inline void splay(int x){ //把x旋轉到當前splay的根
    //沒有將實邊變成虛邊
    update(x); //將上面的標記完全下放
    for(int fa; fa = f[x], !isRoot(x); rotate(x)){
      if(!isRoot(fa)) rotate(Get(fa) == Get(x) ? fa : x);
    }
  }

  inline int access(int x){ //把從根到x的所有點放在一條實鏈裏, 返回這個splay的根
    //進行了邊的虛實變換
    int p; //每次改變右兒子的值,因爲整棵樹是中序遍歷,放入右兒子才能保證先遍歷父親再遍歷兒子
    for(p = 0; x; p = x, x = f[x]){
      splay(x), ch[x][1] = p, pushUp(x);
    }
    return p;
  }

  inline void makeRoot(int p){ //使x點成爲整棵樹的根
    access(p); splay(p);
    swap(ch[p][0],ch[p][1]); //把整條鏈反向
    rev[p] ^= 1;
  }

  inline void link(int x,int y){ //在x、y兩點間連一條邊,連接了虛邊
    if (find(x) != find(y)) makeRoot(x), f[x] = y;
  }

  inline void cut(int x,int p){ //把x、y兩點間邊刪掉,此處刪除的是實邊,注意實邊和虛邊的區別
    makeRoot(x), access(p), splay(p);
    if (ls == x && !rs) ls = f[x] = 0;
  }

  inline int find(int p){ //找到x所在樹的根節點編號
    access(p), splay(p);
    while(ls) pushDown(p), p = ls;
    return p;
  }
  //中序遍歷即可還原樹結構
  void print(int p){
    if(!p) return;
    pushDown(p);
    print(ls);
    printf("%lld ",p);
    print(rs);
  }
}st;

signed main() {
  scanf("%lld%lld", &n, &q);
  for (int i = 1; i <= n; i++) st.val[i] = 1;
  for (int i = 1; i < n; i++) {
    scanf("%lld%lld", &u, &v);
    st.link(u,v);
  }
  while (q--) {
    scanf(" %c%lld%lld", &op, &u, &v);
    if (op == '+') { //+ u v c, u到v的路徑上的點權值+c
      scanf("%lld", &c);
      //u變成樹根,拉起v到u的鏈,把v旋到splay的根
      st.makeRoot(u); v = st.access(v);
      st.val[v] = (st.val[v] + c) % mod;
      st.sum[v] = (st.sum[v] + st.siz[v] * c % mod) % mod;
      st.add[v] = (st.add[v] + c) % mod;
    }
    if (op == '-') { //- u1 v1 u2 v2, 刪除(u1,v1), 加上(u2,v2)
      st.cut(u,v);
      scanf("%lld%lld", &u, &v);
      st.link(u,v);
    }
    if (op == '*') { //* u v c, u到v的路徑乘上c
      scanf("%lld", &c);
      st.makeRoot(u); v = st.access(v);
      st.val[v] = st.val[v] * c % mod;
      st.sum[v] = st.sum[v] * c % mod;
      st.mul[v] = st.mul[v] * c % mod;
    }
    if (op == '/'){ //u v, 詢問u到v路徑權值和
      st.makeRoot(u); v = st.access(v);
      printf("%lld\n", st.sum[v]);
    }
  }
  return 0;
}
2. Query on a tree

題意: nn 個點一棵樹,支持兩種操作。(1n,q104)(1\leq n,q\leq 10^4)

  • CHANGE\text{CHANGE} ii tit_i,將第 ii 條邊的邊權改爲 tit_i
  • QUERY\text{QUERY} aa bb,查詢樹中點 aa 到點 bb 的路徑中的邊權最大值。

思路: 邊權 LCTLCT,需要對於每一條邊建一個節點,即樹中一共有 2n12*n-1 個節點,每個邊節點與上下兩個節點連邊。

建邊需要先確定每個節點的邊權之後再進行 linklink,因爲點修改會對該節點的祖先節點產生影響,需要將該點旋至 splaysplay 端點後才能進行修改。

總結: 總結一下構建 LCTLCT 構建的關鍵,構建 LCTLCT 需要先對各個頂點賦值然後再進行 linklink 操作,若是 linklink 之後再賦值相當於點修改,而點修改需要將點旋爲 splaysplay 根之後才能進行更改。

代碼:

#include <bits/stdc++.h>
#define rep(i,a,b) for(int i = a; i <= b; i++)
using namespace std;
const int N = 20000+10;
int n,val[N];

struct LCT{
  #define ls ch[p][0]
  #define rs ch[p][1]
  #define Get(p) (ch[f[p]][1] == p)
  int ch[N][2], f[N], maxn[N], val[N], siz[N], rev[N];

  inline void clear(int p){ //清除這個點的信息
    ch[p][0] = ch[p][1] = f[p] = siz[p] = val[p] = maxn[p] = 0;
  }

  inline int isRoot(int p){
    clear(0);
    return ch[f[p]][0] != p && ch[f[p]][1] != p;
  }

  inline void pushUp(int p){
    clear(0);
    siz[p] = siz[ls] + 1 + siz[rs];
    maxn[p] = max(val[p],max(maxn[ls],maxn[rs]));
  }

  inline void pushDown(int p){
    clear(0);
    if(rev[p]){ 
      if(ls) rev[ls] ^= 1, swap(ch[ls][0],ch[ls][1]);
      if(rs) rev[rs] ^= 1, swap(ch[rs][0],ch[rs][1]);
      rev[p] = 0;
    }
  }

  void update(int p){ //遞歸地從上到下pushDown信息
    if(!isRoot(p)) update(f[p]);
    pushDown(p);
  }

  inline void rotate(int x){ //將x向上旋轉一層的操作
    int y = f[x], z = f[y], k = Get(x);
    if(!isRoot(y)) ch[z][ch[z][1] == y] = x;
    ch[y][k] = ch[x][!k], f[ch[y][k]] = y;
    ch[x][!k] = y, f[y] = x, f[x] = z;
    pushUp(y); //要先pushUp(y)
    pushUp(x);
  }

  inline void splay(int x){ //把x旋轉到當前splay的根
    update(x); //將上面的標記完全下放
    for(int fa; fa = f[x], !isRoot(x); rotate(x)){
      if(!isRoot(fa)) rotate(Get(fa) == Get(x) ? fa : x);
    }
  }

  inline int access(int x){ //把從根到x的所有點放在一條實鏈裏, 返回這個splay的根
    int p;
    for(p = 0; x; p = x, x = f[x]){
      splay(x), ch[x][1] = p, pushUp(x);
    }
    return p;
  }

  inline void makeRoot(int p){ //使x點成爲整棵樹的根
    access(p); splay(p);
    swap(ch[p][0],ch[p][1]); //把整條鏈反向
    rev[p] ^= 1;
  }

  inline void link(int x,int y){ //在x、y兩點間連一條邊
    makeRoot(x), f[x] = y; //dfs建樹, 每條邊都是有效的, 因此不需要判斷是否有效
  }
}st;

int main(){
  int _; scanf("%d",&_);
  while(_--){
    scanf("%d",&n);
    rep(i,0,2*n) st.clear(i);
    rep(i,1,n-1){
      int a,b,c; scanf("%d%d%d",&a,&b,&c);
      st.val[i+n] = c;
      st.link(a,i+n);
      st.link(i+n,b);
    }
    while(1){
      char s[20]; scanf("%s",s);
      if(s[0] == 'D') break;
      else if(s[0] == 'C'){
        int a,b; scanf("%d%d",&a,&b);
        st.splay(a+n); //先轉爲splay根節點
        st.val[a+n] = b;
      }
      else{
        int a,b; scanf("%d%d",&a,&b);
        st.makeRoot(a);
        b = st.access(b);
        printf("%d\n",st.maxn[b]);
      }
    }
  }
  return 0;
}
3. Can you answer these queries VII

題意: nn 個點一棵樹,每個節點有一個值,支持兩種操作。(1n,q105)(1\leq n,q\leq 10^5)

  • 11 aa bb,查詢樹中點 aa 到點 bb 的路徑中最大連續和
  • 22 aa bb cc,將樹中點 aa 到點 bb 路徑中所有點的值改爲 cc

思路: 對每一個點維護一個 lc[i]lc[i]rc[i]rc[i]maxn[i]maxn[i] 表示點 ii 子樹中左連續的最大值、右連續的最大值以及整棵子樹中的最大連續值。

需要注意一點,交換左右兒子的時候,還需要把每個節點的 lclcrcrc 進行交換,其餘細節見代碼。

代碼:

#include <bits/stdc++.h>
#define rep(i,a,b) for(int i = a; i <= b; i++)
using namespace std;
typedef long long ll;
const ll inf = 1e9+100;
const int N = 1e5+10;
int n,Q;

struct LCT{
  #define ls ch[p][0]
  #define rs ch[p][1]
  #define Get(p) (ch[f[p]][1] == p)
  int ch[N][2], f[N];
  ll maxn[N], sum[N], lc[N], rc[N], val[N], siz[N], lazy[N];
  bool rev[N];

  inline void clear(int p){ //清除這個點的信息
    ch[p][0] = ch[p][1] = f[p] = val[p] = maxn[p] = lc[p] = sum[p] = rc[p] = siz[p] = 0;
  }

  inline int isRoot(int p){
    return ch[f[p]][0] != p && ch[f[p]][1] != p;
  }

  inline void pushUp(int p){
    siz[p] = siz[ls] + 1 + siz[rs];
    sum[p] = val[p] + sum[ls] + sum[rs]; //ls、rs可能爲0
    maxn[p] = max(maxn[ls],max(maxn[rs],rc[ls]+lc[rs]+val[p]));
    lc[p] = max(lc[ls],sum[ls]+val[p]+lc[rs]);
    rc[p] = max(rc[rs],sum[rs]+val[p]+rc[ls]);
  }

  inline void pushDown(int p){
    if(rev[p]){ 
      if(ls) rev[ls] ^= 1, swap(ch[ls][0],ch[ls][1]);
      if(rs) rev[rs] ^= 1, swap(ch[rs][0],ch[rs][1]);
      swap(lc[ls],rc[ls]); swap(lc[rs],rc[rs]); //交換左右兒子時還要交換左右連續最大值
      rev[p] = 0;
    }
    if(lazy[p] != -inf){
      if(ls){
        sum[ls] = siz[ls]*lazy[p]; 
        val[ls] = lazy[ls] = lazy[p];
        lc[ls] = rc[ls] = maxn[ls] = lazy[p] > 0 ? sum[ls]:0;
      }
      if(rs){
        sum[rs] = siz[rs]*lazy[p]; 
        val[rs] = lazy[rs] = lazy[p];
        lc[rs] = rc[rs] = maxn[rs] = lazy[p] > 0 ? sum[rs]:0;
      }
      lazy[p] = -inf; 
    }
  }

  void update(int p){ //遞歸地從上到下pushDown信息
    if(!isRoot(p)) update(f[p]);
    pushDown(p);
  }

  inline void rotate(int x){ //將x向上旋轉一層的操作
    int y = f[x], z = f[y], k = Get(x);
    if(!isRoot(y)) ch[z][ch[z][1] == y] = x;
    ch[y][k] = ch[x][!k], f[ch[y][k]] = y;
    ch[x][!k] = y, f[y] = x, f[x] = z;
    pushUp(y); //要先pushUp(y)
    pushUp(x);
  }

  inline void splay(int x){ //把x旋轉到當前splay的根
    update(x); //將上面的標記完全下放
    for(int fa; fa = f[x], !isRoot(x); rotate(x)){
      if(!isRoot(fa)) rotate(Get(fa) == Get(x) ? fa : x);
    }
  }

  inline int access(int x){ //把從根到x的所有點放在一條實鏈裏, 返回這個splay的根
    int p;
    for(p = 0; x; p = x, x = f[x]){
      splay(x), ch[x][1] = p, pushUp(x);
    }
    return p;
  }

  inline void makeRoot(int p){ //使x點成爲整棵樹的根
    access(p);
    splay(p);
    swap(ch[p][0],ch[p][1]); //把整條鏈反向
    rev[p] ^= 1;
  }

  inline void link(int x,int y){ //在x、y兩點間連一條邊
    makeRoot(x), f[x] = y; //dfs建樹, 每條邊都是有效的, 因此不需要判斷是否有效
  }
}st;

int main(){
  scanf("%d",&n);
  rep(i,1,n){
    scanf("%lld",&st.val[i]);
    st.siz[i] = 1; st.sum[i] = st.val[i];
    st.lazy[i] = -inf;
    st.lc[i] = st.rc[i] = st.maxn[i] = st.val[i] > 0 ? st.val[i]:0;
  }
  rep(i,1,n-1){
    int a,b; scanf("%d%d",&a,&b);
    st.link(a,b);
  }
  scanf("%d",&Q);
  while(Q--){ 
    int op; scanf("%d",&op);
    if(op == 1){ //a->b max
      int a,b; scanf("%d%d",&a,&b);
      st.makeRoot(a);
      b = st.access(b);
      printf("%lld\n",st.maxn[b]);
    }
    else{ //a->b to c
      int a,b; ll c; scanf("%d%d%lld",&a,&b,&c);
      st.makeRoot(a);
      b = st.access(b);
      st.val[b] = st.lazy[b] = c;
      st.sum[b] = st.siz[b]*c;
      st.lc[b] = st.rc[b] = st.maxn[b] = c > 0 ? st.sum[b]:0;
    }
  }
  return 0;
}
4. [ZJOI2008] 樹的統計 Count

題意: 一棵樹上有 nn 個節點,每個節點都有一個權值 ww。支持三種操作:CHANGECHANGE uu tt:把結點 uu 的權值改爲 ttQMAXQMAX uu vv:詢問從點 uu 到點 vv 的路徑上的節點的最大權值;QSUMQSUM uu vv:詢問從點 uu 到點 vv 的路徑上的節點的權值和。(1n3104,1q2105)(1\leq n\leq 3*10^4,1\leq q\leq 2*10^5)

思路: 單點查詢 + 路徑最大值 + 路徑 sumsum 和,非常裸的題目,純當練習。

代碼:

#include <bits/stdc++.h>
#define rep(i,a,b) for(int i = a; i <= b; i++)
using namespace std;
typedef long long ll;
const int inf = 1e9+100;
const int N = 40000+10;
int n,Q,A[N],B[N];

struct LCT{
  #define ls ch[p][0]
  #define rs ch[p][1]
  #define Get(p) (ch[f[p]][1] == p)
  int ch[N][2], f[N];
  int maxn[N], sum[N], val[N];
  bool rev[N];

  inline void clear(int p){ //清除這個點的信息
    ch[p][0] = ch[p][1] = f[p] = val[p] = maxn[p] = 0;
  }

  inline int isRoot(int p){
    return ch[f[p]][0] != p && ch[f[p]][1] != p;
  }

  inline void pushUp(int p){
    sum[p] = val[p] + sum[ls] + sum[rs]; //ls、rs可能爲0
    maxn[p] = max(val[p],max(maxn[ls],maxn[rs]));
  }

  inline void pushDown(int p){
    if(rev[p]){ 
      if(ls) rev[ls] ^= 1, swap(ch[ls][0],ch[ls][1]);
      if(rs) rev[rs] ^= 1, swap(ch[rs][0],ch[rs][1]);
      rev[p] = 0;
    }
  }

  void update(int p){ //遞歸地從上到下pushDown信息
    if(!isRoot(p)) update(f[p]);
    pushDown(p);
  }

  inline void rotate(int x){ //將x向上旋轉一層的操作
    int y = f[x], z = f[y], k = Get(x);
    if(!isRoot(y)) ch[z][ch[z][1] == y] = x;
    ch[y][k] = ch[x][!k], f[ch[y][k]] = y;
    ch[x][!k] = y, f[y] = x, f[x] = z;
    pushUp(y); //要先pushUp(y)
    pushUp(x);
  }

  inline void splay(int x){ //把x旋轉到當前splay的根
    update(x); //將上面的標記完全下放
    for(int fa; fa = f[x], !isRoot(x); rotate(x)){
      if(!isRoot(fa)) rotate(Get(fa) == Get(x) ? fa : x);
    }
  }

  inline int access(int x){ //把從根到x的所有點放在一條實鏈裏, 返回這個splay的根
    int p;
    for(p = 0; x; p = x, x = f[x]){
      splay(x), ch[x][1] = p, pushUp(x);
    }
    return p;
  }

  inline void makeRoot(int p){ //使x點成爲整棵樹的根
    access(p);
    splay(p);
    swap(ch[p][0],ch[p][1]); //把整條鏈反向
    rev[p] ^= 1;
  }

  inline void link(int x,int y){ //在x、y兩點間連一條邊
    makeRoot(x), f[x] = y; //dfs建樹, 每條邊都是有效的, 因此不需要判斷是否有效
  }
}st;

int main(){
  scanf("%d",&n);
  st.maxn[0] = -inf;
  rep(i,1,n-1) scanf("%d%d",&A[i],&B[i]);
  rep(i,1,n){
    int hp; scanf("%d",&hp);
    st.val[i] = st.maxn[i] = st.sum[i] = hp;
  }
  rep(i,1,n-1) st.link(A[i],B[i]);
  scanf("%d",&Q);
  while(Q--){ 
    char s[20]; int u,v;
    scanf("%s%d%d",s,&u,&v);
    if(s[0] == 'C'){
      st.splay(u);
      st.val[u] = v;
      st.pushUp(u);
    }
    else if(s[1] == 'M'){
      st.makeRoot(u);
      v = st.access(v);
      printf("%d\n",st.maxn[v]);
    }
    else{
      st.makeRoot(u);
      v = st.access(v);
      printf("%d\n",st.sum[v]);
    }
  }
  return 0;
}
5. 最小差值生成樹

題意: nn 個點,mm 條邊的一個無向圖,求邊權最大值與最小值的差值最小的生成樹。(1n5104,1m2105)(1\leq n\leq 5*10^4,1\leq m\leq 2*10^5)

思路: 關於這類特殊生成樹問題,一般考慮用 LCTLCT 動態維護樹結構然後更新答案。

此題也可以這樣考慮。將邊按邊權從小到大排序,如果 (a,b)(a,b) 兩點不連通,則加上該邊,如果 (a,b)(a,b) 兩點連通,則將 aba\rightarrow b 路徑上邊權最小的邊去除,然後連上當前的邊。維護過程不斷更新最大值與最小值的差值,不斷取 minmin 即可。

因此只需要維護一個邊權 LCTLCT,並且維護路徑最小值以及最小值點的編號,然後動態加邊刪邊即可。還需要對在樹中的邊打上標記,去除的時候刪去標記,用於查找整棵樹中的最小邊權。

代碼:

#include <bits/stdc++.h>
#define rep(i,a,b) for(int i = a; i <= b; i++)
using namespace std;
const int N = 2e5+5e4+100;
const int M = 2e5+10;
int n, m, vis[N], pos = 1, num, ans = 1e9;
struct Edge{
  int a,b,w;
  bool operator < (Edge xx) const {
    return w < xx.w;
  }
}e[N];

void dbg() {cout << "\n";}
template<typename T, typename... A> void dbg(T a, A... x) {cout << a << ' '; dbg(x...);}
#define logs(x...) {cout << #x << " -> "; dbg(x);}

struct LCT{
  #define ls ch[p][0]
  #define rs ch[p][1]
  #define Get(p) (ch[f[p]][1] == p)
  int ch[N][2], f[N], val[N], minn[N], mpos[N], rev[N];

  inline void clear(int p){ //清除這個點的信息
    ch[p][0] = ch[p][1] = f[p] = val[p] = mpos[p] = minn[p] = 0;
  }

  inline int isRoot(int p){
    return ch[f[p]][0] != p && ch[f[p]][1] != p;
  }

  inline void pushUp(int p){
    minn[p] = val[p]; mpos[p] = p;
    if(ls && minn[ls] < minn[p]) minn[p] = minn[ls], mpos[p] = mpos[ls];
    if(rs && minn[rs] < minn[p]) minn[p] = minn[rs], mpos[p] = mpos[rs];
  }

  inline void pushDown(int p){
    if(rev[p]){ 
      if(ls) rev[ls] ^= 1, swap(ch[ls][0],ch[ls][1]);
      if(rs) rev[rs] ^= 1, swap(ch[rs][0],ch[rs][1]);
      rev[p] = 0;
    }
  }

  void update(int p){ //遞歸地從上到下pushDown信息
    if(!isRoot(p)) update(f[p]);
    pushDown(p);
  }

  inline void rotate(int x){ //將x向上旋轉一層的操作
    int y = f[x], z = f[y], k = Get(x);
    if(!isRoot(y)) ch[z][ch[z][1] == y] = x;
    ch[y][k] = ch[x][!k], f[ch[y][k]] = y;
    ch[x][!k] = y, f[y] = x, f[x] = z;
    pushUp(y); //要先pushUp(y)
    pushUp(x);
  }

  inline void splay(int x){ //把x旋轉到當前splay的根
    update(x); //將上面的標記完全下放
    for(int fa; fa = f[x], !isRoot(x); rotate(x)){
      if(!isRoot(fa)) rotate(Get(fa) == Get(x) ? fa : x);
    }
  }

  inline int access(int x){ //把從根到x的所有點放在一條實鏈裏, 返回這個splay的根
    int p = 0;
    for(p = 0; x; p = x, x = f[x]){
      splay(x), ch[x][1] = p, pushUp(x);
    }
    return p;
  }

  inline void makeRoot(int p){ //使x點成爲整棵樹的根
    access(p); splay(p);
    swap(ch[p][0],ch[p][1]); //把整條鏈反向
    rev[p] ^= 1;
  }

  inline void link(int x,int y){ //在x、y兩點間連一條邊
    // if (find(x) != find(y)) 
    makeRoot(x), f[x] = y;
  }

  inline void cut(int x,int p){ //把x、y兩點間邊刪掉
    makeRoot(x), access(p), splay(p);
    if (ls == x && !rs) ls = f[x] = 0;
  }

  inline int find(int p){ //找到x所在樹的根節點編號
    access(p), splay(p);
    while(ls) pushDown(p), p = ls;
    return p;
  }
}st;

signed main() {
  scanf("%d%d", &n, &m);
  rep(i,0,n) st.val[i] = st.minn[i] = 1e5;
  rep(i,1,m) scanf("%d%d%d",&e[i].a,&e[i].b,&e[i].w);
  sort(e+1,e+1+m);
  rep(i,1,m) st.val[i+n] = st.minn[i+n] = e[i].w, st.mpos[i+n] = i+n;
  rep(i,1,m){
    if(e[i].a == e[i].b) continue;
    if(st.find(e[i].a) != st.find(e[i].b)){
      st.link(e[i].a,i+n); st.link(i+n,e[i].b);
      num++; vis[i] = 1;
      while(!vis[pos]) pos++;
      if(num == n-1) ans = min(ans,e[i].w-e[pos].w);
    }
    else{
      st.makeRoot(e[i].a);
      int p1 = st.access(e[i].b);
      p1 = st.mpos[p1];
      st.cut(e[p1-n].a,p1); st.cut(p1,e[p1-n].b); vis[p1-n] = 0;
      st.link(e[i].a,i+n); st.link(i+n,e[i].b); vis[i] = 1;
      while(!vis[pos]) pos++;
      if(num == n-1) ans = min(ans,e[i].w-e[pos].w);
    }
  }
  printf("%d\n",ans);
  return 0;
}
6. [BJOI2014] 大融合

題意: nn 個點,一共 qq 次操作。一共有兩種操作類型,A x yA \ x \ y 表示連通 (x,y)(x,y),保證操作合法,且始終是棵森林。Q x yQ\ x\ y 表示查詢去除 (x,y)(x,y) 邊之後,xx 所在樹的節點數 * yy 所在樹的節點數。(1n,q105)(1\leq n,q\leq 10^5)

思路: 我們一般遇到的都是維護鏈上節點個數的問題,而此題要求這顆樹上的節點個數,因此我們需要同時維護虛邊和實邊的信息。

我們令 sz[x]sz[x] 表示節點 xx 子樹中節點個數,sz2[x]sz2[x] 表示節點 xx 虛兒子的節點個數和。因此 sz[x]=sz[ls]+sz[rs]+1+sz2[x]sz[x]=sz[ls]+sz[rs]+1+sz2[x],而這也正是 pushUppushUp 函數。

因此我們只需要維護 sz2[x]sz2[x] 即可,然後觀察哪些函數會改變 sz2[x]sz2[x] 的值,不難發現,只有 makeRootmakeRootaccessaccesslinklinkcutcut 會改變邊的虛實關係,其中 makeRootmakeRoot 主要修改在於調用了 accessaccess 函數,而 cutcut 只是刪除實邊不會修改虛邊,因此真正關鍵的函數即爲 linklinkaccessaccess 函數,具體操作見代碼,不難思考。

這裏主要講解 linklink 中修改 sz2[y]sz2[y] 信息時爲什麼需要將節點 yy makeRoot(y)makeRoot(y),原因在於單點修改之後,其祖先節點維護的信息都會發生變化,因此一般的問題需要 splay(y)splay(y),因爲一般問題只需要維護實鏈信息。然後在該問題中還維護了虛鏈信息,因此需要 makeRoot(y)makeRoot(y) 而不是 splay(y)splay(y)

代碼:

#include <bits/stdc++.h>
#define rep(i,a,b) for(int i = a; i <= b; i++)
using namespace std;
typedef long long ll;
const int N = 100010;
int n, q;

struct LCT{
  #define ls ch[p][0]
  #define rs ch[p][1]
  #define Get(p) (ch[f[p]][1] == p)
  int ch[N][2], f[N], siz[N], siz2[N], rev[N];

  inline void clear(int p){ //清除這個點的信息
    ch[p][0] = ch[p][1] = f[p] = siz[p] = siz2[p] = rev[p] = 0;
  }

  inline int isRoot(int p){
    clear(0);
    return ch[f[p]][0] != p && ch[f[p]][1] != p;
  }

  inline void pushUp(int p){
    clear(0);
    siz[p] = siz[ls] + 1 + siz[rs] + siz2[p];
  }

  inline void pushDown(int p){
    clear(0);
    if(rev[p]){ 
      if(ls) rev[ls] ^= 1, swap(ch[ls][0],ch[ls][1]);
      if(rs) rev[rs] ^= 1, swap(ch[rs][0],ch[rs][1]);
      rev[p] = 0;
    }
  }

  void update(int p){ //遞歸地從上到下pushDown信息
    if(!isRoot(p)) update(f[p]);
    pushDown(p);
  }

  inline void rotate(int x){ //將x向上旋轉一層的操作
    int y = f[x], z = f[y], k = Get(x);
    if(!isRoot(y)) ch[z][ch[z][1] == y] = x;
    ch[y][k] = ch[x][!k], f[ch[y][k]] = y;
    ch[x][!k] = y, f[y] = x, f[x] = z;
    pushUp(y); //要先pushUp(y)
    pushUp(x);
  }

  inline void splay(int x){ //把x旋轉到當前splay的根
    update(x); //將上面的標記完全下放
    for(int fa; fa = f[x], !isRoot(x); rotate(x)){
      if(!isRoot(fa)) rotate(Get(fa) == Get(x) ? fa : x);
    }
  }

  inline int access(int x){ //把從根到x的所有點放在一條實鏈裏, 返回這個splay的根
    int p; //每次改變右兒子的值,因爲整棵樹是中序遍歷,放入右兒子才能保證先遍歷父親再遍歷兒子
    for(p = 0; x; p = x, x = f[x]){
      splay(x), siz2[x] += siz[ch[x][1]]-siz[p], ch[x][1] = p, pushUp(x);
    }
    return p;
  }

  inline void makeRoot(int p){ //使x點成爲整棵樹的根
    access(p); splay(p);
    swap(ch[p][0],ch[p][1]); //把整條鏈反向
    rev[p] ^= 1;
  }

  inline void link(int x,int y){ //在x、y兩點間連一條邊
    //makeRoot(x)的作用是使得x無父親
    //makeRoot(y)的作用是使得y無父親,因此可以修改y的信息,不用去更新y的祖先
    makeRoot(x), makeRoot(y), f[x] = y, siz2[y] += siz[x]; pushUp(y);
  }

  inline void cut(int x,int p){ //把x、y兩點間邊刪掉,此處刪除的是實邊,注意實邊和虛邊的區別
    makeRoot(x), access(p), splay(p);
    if (ls == x && !rs) ls = f[x] = 0;
  }

  inline int find(int p){ //找到x所在樹的根節點編號
    access(p), splay(p);
    while(ls) pushDown(p), p = ls;
    return p;
  }
}st;

signed main() {
  scanf("%d%d", &n, &q);
  rep(i,1,n) st.siz[i] = 1;
  while (q--) {
    char op[10]; int x,y; scanf("%s%d%d",op,&x,&y);
    if(op[0] == 'A') st.link(x,y);
    else{
      st.cut(x,y);
      st.makeRoot(x); st.splay(x);
      int a1 = st.siz[x];
      st.makeRoot(y); st.splay(y);
      int a2 = st.siz[y];
      st.link(x,y);
      printf("%lld\n",(ll)a1*(ll)a2);
    }
  }
  return 0;
}
7. Sone1

題意: 支持 1212 種操作,包括鏈 maxmaxminminsumsum,子樹 maxmaxminminsumsum,換根,換邊,子樹和鏈的修改與加值。(1n,m105)(1\leq n,m\leq 10^5)

思路: TopTreeTopTree 典型例題,主要思路是對於每個點維護了一個 splaysplay,詳情看 clarisclaris 的題解

代碼:
貼上 clairsclairs 的代碼。

/*
Toptree即爲可以維護子樹信息的lct升級版
*/
#include<cstdio>
#define N 200010
const int inf=~0U>>1;
inline void swap(int&a,int&b){int c=a;a=b;b=c;}
inline int max(int a,int b){return a>b?a:b;}
inline int min(int a,int b){return a<b?a:b;}
inline void read(int&a){
  char c;bool f=0;a=0;
  while(!((((c=getchar())>='0')&&(c<='9'))||(c=='-')));
  if(c!='-')a=c-'0';else f=1;
  while(((c=getchar())>='0')&&(c<='9'))(a*=10)+=c-'0';
  if(f)a=-a;
}
struct tag{
  int a,b;//ax+b
  tag(){a=1,b=0;}
  tag(int x,int y){a=x,b=y;}
  inline bool ex(){return a!=1||b;}
  inline tag operator+(const tag&x){return tag(a*x.a,b*x.a+x.b);}
};
inline int atag(int x,tag y){return x*y.a+y.b;}
struct data{
  int sum,minv,maxv,size;
  data(){sum=size=0,minv=inf,maxv=-inf;}
  data(int x){sum=minv=maxv=x,size=1;}
  data(int a,int b,int c,int d){sum=a,minv=b,maxv=c,size=d;}
  inline data operator+(const data&x){return data(sum+x.sum,min(minv,x.minv),max(maxv,x.maxv),size+x.size);}
};
inline data operator+(const data&a,const tag&b){return a.size?data(a.sum*b.a+a.size*b.b,atag(a.minv,b),atag(a.maxv,b),a.size):a;}
//son:0-1:重鏈兒子,2-3:AAA樹兒子
int f[N],son[N][4],a[N],tot,rt,rub,ru[N];bool rev[N],in[N];
int val[N];
data csum[N],tsum[N],asum[N];
tag ctag[N],ttag[N];
inline bool isroot(int x,int t){
  if(t)return !f[x]||!in[f[x]]||!in[x];
  return !f[x]||(son[f[x]][0]!=x&&son[f[x]][1]!=x)||in[f[x]]||in[x];
}
inline void rev1(int x){
  if(!x)return;
  swap(son[x][0],son[x][1]);rev[x]^=1;
}
inline void tagchain(int x,tag p){
  if(!x)return;
  csum[x]=csum[x]+p;
  asum[x]=csum[x]+tsum[x];
  val[x]=atag(val[x],p);
  ctag[x]=ctag[x]+p;
}
inline void tagtree(int x,tag p,bool t){
  if(!x)return;
  tsum[x]=tsum[x]+p;
  ttag[x]=ttag[x]+p;
  if(!in[x]&&t)tagchain(x,p);else asum[x]=csum[x]+tsum[x];
}
inline void pb(int x){
  if(!x)return;
  if(rev[x])rev1(son[x][0]),rev1(son[x][1]),rev[x]=0;
  if(!in[x]&&ctag[x].ex())tagchain(son[x][0],ctag[x]),tagchain(son[x][1],ctag[x]),ctag[x]=tag();
  if(ttag[x].ex()){
    tagtree(son[x][0],ttag[x],0),tagtree(son[x][1],ttag[x],0);
    tagtree(son[x][2],ttag[x],1),tagtree(son[x][3],ttag[x],1);
    ttag[x]=tag();
  }
}
inline void up(int x){
  tsum[x]=data();
  for(int i=0;i<2;i++)if(son[x][i])tsum[x]=tsum[x]+tsum[son[x][i]];
  for(int i=2;i<4;i++)if(son[x][i])tsum[x]=tsum[x]+asum[son[x][i]];
  if(in[x]){
    csum[x]=data();
    asum[x]=tsum[x];
  }else{
    csum[x]=data(val[x]);
    for(int i=0;i<2;i++)if(son[x][i])csum[x]=csum[x]+csum[son[x][i]];
    asum[x]=csum[x]+tsum[x];
  }
}
inline int child(int x,int t){pb(son[x][t]);return son[x][t];}
inline void rotate(int x,int t){
  int y=f[x],w=(son[y][t+1]==x)+t;
  son[y][w]=son[x][w^1];
  if(son[x][w^1])f[son[x][w^1]]=y;
  if(f[y])for(int z=f[y],i=0;i<4;i++)if(son[z][i]==y)son[z][i]=x;
  f[x]=f[y];f[y]=x;son[x][w^1]=y;up(y);
}
inline void splay(int x,int t=0){
  int s=1,i=x,y;a[1]=i;
  while(!isroot(i,t))a[++s]=i=f[i];
  while(s)pb(a[s--]);
  while(!isroot(x,t)){
    y=f[x];
    if(!isroot(y,t)){if((son[f[y]][t]==y)^(son[y][t]==x))rotate(x,t);else rotate(y,t);}
    rotate(x,t);
  }
  up(x);
}
inline int newnode(){
  int x=rub?ru[rub--]:++tot;
  son[x][2]=son[x][3]=0;in[x]=1;
  return x;
}
inline void setson(int x,int t,int y){son[x][t]=y;f[y]=x;}
inline int pos(int x){for(int i=0;i<4;i++)if(son[f[x]][i]==x)return i;return 4;}
inline void add(int x,int y){//從x連出一條虛邊到y
  if(!y)return;
  pb(x);
  for(int i=2;i<4;i++)if(!son[x][i]){
    setson(x,i,y);
    return;
  }
  while(son[x][2]&&in[son[x][2]])x=child(x,2);
  int z=newnode();
  setson(z,2,son[x][2]);
  setson(z,3,y);
  setson(x,2,z);
  splay(z,2);
}
inline void del(int x){//將x與其虛邊上的父親斷開
  if(!x)return;
  splay(x);
  if(!f[x])return;
  int y=f[x];
  if(in[y]){
    int s=1,i=y,z=f[y];a[1]=i;
    while(!isroot(i,2))a[++s]=i=f[i];
    while(s)pb(a[s--]);
    if(z){
      setson(z,pos(y),child(y,pos(x)^1));
      splay(z,2);
    }
    ru[++rub]=y;
  }else{
    son[y][pos(x)]=0;
    splay(y);
  }
  f[x]=0;
}
inline int fa(int x){//x通過虛邊的父親
  splay(x);
  if(!f[x])return 0;
  if(!in[f[x]])return f[x];
  int t=f[x];
  splay(t,2);
  return f[t];
}
inline int access(int x){
  int y=0;
  for(;x;y=x,x=fa(x)){
    splay(x);
    del(y);
    add(x,son[x][1]);
    setson(x,1,y);
    up(x);
  }
  return y;
}
inline int lca(int x,int y){
  access(x);
  return access(y);
}
inline int root(int x){
  access(x);
  splay(x);
  while(son[x][0])x=son[x][0];
  return x;
}
inline void makeroot(int x){
  access(x);
  splay(x);
  rev1(x);
}
inline void link(int x,int y){
  makeroot(x);
  add(y,x);
  access(x);
}
inline void cut(int x){
  access(x);
  splay(x);
  f[son[x][0]]=0;
  son[x][0]=0;
  up(x);
}
inline void changechain(int x,int y,tag p){
  makeroot(x);
  access(y);
  splay(y);
  tagchain(y,p);
}
inline data askchain(int x,int y){
  makeroot(x);
  access(y);
  splay(y);
  return csum[y];
}
inline void changetree(int x,tag p){
  access(x);
  splay(x);
  val[x]=atag(val[x],p);
  for(int i=2;i<4;i++)if(son[x][i])tagtree(son[x][i],p,1);
  up(x);
  splay(x);
}
inline data asktree(int x){
  access(x);
  splay(x);
  data t=data(val[x]);
  for(int i=2;i<4;i++)if(son[x][i])t=t+asum[son[x][i]];
  return t;
}
int n,m,x,y,z,k,i,ed[N][2];
int main(){
  read(n);read(m);
  tot=n;
  for(i=1;i<n;i++)read(ed[i][0]),read(ed[i][1]); //連邊
  for(i=1;i<=n;i++)read(val[i]),up(i); //先賦點權,再連邊
  for(i=1;i<n;i++)link(ed[i][0],ed[i][1]); //每個點的權值
  read(rt); //給出根
  makeroot(rt);
  while(m--){
    read(k);
    if(k==1){//換根,x變成根
      read(rt);
      makeroot(rt);
    }
    if(k==9){//x的父親變成y,x父親換成y
      read(x),read(y);
      if(lca(x,y)==x)continue;
      cut(x);
      link(y,x);
      makeroot(rt);
    }
    if(k==0){//子樹賦值,以x爲根的子樹點權值改爲y
      read(x),read(y);
      changetree(x,tag(0,y));
    }
    if(k==5){//子樹加,x爲根子樹點權值加上y
      read(x),read(y);
      changetree(x,tag(1,y));
    }
    if(k==3){//子樹最小值,x爲根子樹中點權值求min
      read(x);
      printf("%d\n",asktree(x).minv);
    }
    if(k==4){//子樹最大值,x爲根子樹中點權值求max
      read(x);
      printf("%d\n",asktree(x).maxv);
    }
    if(k==11){//子樹和,x爲根子樹中點權sum
      read(x);
      printf("%d\n",asktree(x).sum);
    }
    if(k==2){//鏈賦值,x-y路徑上點權值改爲z
      read(x),read(y),read(z);
      changechain(x,y,tag(0,z));
      makeroot(rt);
    }
    if(k==6){//鏈加,x-y路徑上點權值加上z
      read(x),read(y),read(z);
      changechain(x,y,tag(1,z));
      makeroot(rt);
    }
    if(k==7){//鏈最小值,x-y路徑上點權值求min
      read(x),read(y);
      printf("%d\n",askchain(x,y).minv);
      makeroot(rt);
    }
    if(k==8){//鏈最大值,x-y路徑上點權值求max
      read(x),read(y);
      printf("%d\n",askchain(x,y).maxv);
      makeroot(rt);
    }
    if(k==10){//鏈和,x-y路徑上點權值求sum
      read(x),read(y);
      printf("%d\n",askchain(x,y).sum);
      makeroot(rt);
    }
  }
  return 0;
}

後記

本篇博客到這裏就結束了,祝大家 ACAC 愉快,一起愛上 LCTLCT 把!(๑•̀ㅂ•́)و✧

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章