1. 程式人生 > 其它 >優雅的假資料結構: K-D Tree

優雅的假資料結構: K-D Tree

k-d tree

定義

一種平衡樹, 維護 \(k\) 維空間中的點的資訊.

每個節點表示一個點, 每個子樹表示對應的 \(k\) 維超長方體. 這樣說可能過於抽象, 那就先分析特殊情況, 因為 \(k = 1\) 的時候, 維護的資訊是序列上的, 所以這時的 1-d Tree 就是普通平衡樹.

\(k = 2\) 的情況, 維護的是平面上的點, 每次選一個點, 將平面分成兩半, 左右子樹各佔存一半的點. 每次選點之前, 首先確定要分割的這個四邊形, 將要存入這個子樹中的所有點的橫縱座標最大值和最小值都求出來, 這 \(4\) 個座標圍起來的四邊形就是要分割的四邊形.

然後選擇這個點, 看這個四邊形是橫著比較長還是豎著比較長, 也就是說我們要儘量保證這個四邊形的長寬差越小越好. 不失一般性, 假設這個四邊形是躺著的, 也就是橫著比較長. 這時需要把它豎著劈成兩半, 將這些點按橫座標排序, 選中間的點作為割點, 以它的橫座標為標準劃一根豎直線, 將四邊形分成兩個部分, 左邊的部分就是這個點的左子樹; 右邊的部分就是右子樹.

實現

由於每一個節點的分割方向都可以不同, 所以不能保證中序遍歷的點以某種規律單調, 因此沒法用基於旋轉的平衡樹維護, 這時我們就想到了替罪羊樹.

因為沒法以中序遍歷出的序列為重構之後的樹的序, 因此沒法想一般替罪羊的重構方式一樣重構, 需要重新求每一維座標的極差, 然後以某個維度的座標為序排序. 所以一次重構相比一般的替罪羊是增加了一個 \(log\) 的, 但是根據前面連結中文章的證明, 重構加一個 \(log\) 並不會影響複雜度. (其實還可以使用神奇的 nth_element() 避免排序, \(O(n)\) 找到中位數, 使一次重構還是線性的).

一般來說, 在大部分需要 k-d tree 的題目中, \(k = 2\)

, 結合具體題目分析:

簡單題

題意

這道題要求維護一個 \(n * n\) 的網格的資訊, 這個網格初始都是 \(0\), 支援:

  • 單點修改

將點 \((x, y)\) 的權值加 \(a\), 數量不超過 \(2 * 10^5\).

  • 矩形查詢

查詢點 \((x_1, y_1)\) 和點 \((x_2, y_2)\) 確定的矩形的權值總和.

\(n \leq 5 * 10^5, Ans \leq 2^{31} - 1\)

題解

因為網格一開始是空的, 所以不用儲存, 權值只能增加, 可以看成 Insert \(a\) 個點 \((x, y)\).

建一棵 2-d tree, 每個節點儲存 \(x\)

, \(y\) 表示座標, \(val\) 表示這個點的權值, 每次修改的時候做單點插入.

每個點還需要存 \(Min_x\), \(Min_y\), \(Max_x\), \(Max_y\), \(Sum\) 來進行查詢. 其中 \(Min\)\(Max\) 描述了包含這個節點的子樹中所有節點的最小矩形, 也就是這棵子樹對應的矩形, 而 \(Sum\) 表示這個節點的子樹的權值和.

查詢的時候, 如果這個點的子樹覆蓋的矩形被查詢的矩形完全包含, 則將這個節點的 \(Sum\) 統計到答案中, 如果不然, 遞迴兩個兒子.

這樣做類似於線段樹, 只不過是二維的, 接下來分析詢問的複雜度. 假設有 \(n\) 個點.

因為建立了 2-d tree, 所以原來的網格規模就和複雜度無關了.

首先分析點的分佈對複雜度的影響, 當點的座標在一維上十分集中, 在另一維上十分分散, 那麼這棵 2-d tree 就越來越像一棵線段樹了, 而線段樹的單次詢問複雜度是 \(O(logn)\).

然後就是兩維同樣離散, 則一次查詢可能訪問到的節點數取決於詢問矩形的邊界分割了多少節點的矩形.

因為一個節點的矩形被分割, 它的祖先的矩形也一定被分割. 所以考慮有多少最小的矩形 (暫時叫它們 "元矩形") 被分割.

這樣, 問題轉化成了求按 2-d tree 分割後, 一條直線最多經過多少元矩形.

首先, 元矩形的數量是 \(O(n)\) 的, 對於兩維離散程度相同的情況, 網格應該被分成 \(\sqrt n * \sqrt n\) 的元矩形, 則單次查詢 \(O(\sqrt n)\).

接下來是一般情況, 可能出現的最劣情況.

嘗試構造 \(\frac n2\) 個橫座標為 \(1\) 的點, 然後有 \(\frac n2\) 個橫座標為 \(2\) 的點, 縱座標和前面的點一一對應相等. 如果詢問所有橫座標為 \(1\) 的點, 則需要訪問 \(O(n)\) 個點, 複雜度 \(O(n)\).

但是顯然出題人強制線上卡掉 CDQ, \(20M\) 空間卡掉樹套樹, 他應該不會構造這種神奇資料卡掉 k-d tree, 所以就按 k-d tree 來寫好了.

<主 流>

二級標題源自張業琛的溫馨提示:

按照極差劃分的是上世紀的策略啦! 現在, 我們都用這個:

雖然聽起來有點像衛生巾廣告, 但是這種切法的穩定性確實比原來的切法要強.

對於複雜度是錯誤的使用廣泛的演算法 (如快速排序, SPFA等), 一般只有特定的資料才能構造出最劣情況, 所以往往隨機化可以減少這種資料的影響.

對於 2-d tree, 本題中貌似不需要矩形都趨近於正方形, 所以在尋找切點的時候可以無視極差, 按照節點深度交替切分. \(Dep % 2 = 0\) 時, 找橫座標中位數; \(Dep % 2 = 1\) 時, 找縱座標中位數.

時間複雜度的證明:

考慮把原策略卡掉的那組資料, 這種操作下, 一次詢問貌似只需要訪問 \(4\) 個節點 (貌似換一下交替的順序就是 \(2\) 個), 單次複雜度 \(O(1)\).

座標系中的點分佈成一個⚪, 切兩刀之後, 無論是哪種策略, 點都被分成了 \(4\)\(\frac 14\) ⚪. 這種圖形因為單調性, 無論按哪一維劃分, 切點都是一定的, 我們稱之為 "眾生平等⚪".

眾生平等⚪告訴我們, 什麼劃分的方法都不能改變複雜度, 只能減少被卡的機率. K-D Tree 就是假資料結構, 因為比較好用才流傳至今.

資料都是人造的, 俗話說得好, 人卡人, 卡死人, 所以就不管什麼維度, 相信隨機化的力量. 隨機切一維, 可以保證不會被惡意構造資料卡到 \(O(n)\), 期望複雜度 \(O(\sqrt n)\).

實現

由於不需要刪除, 所以不需要判斷空節點, 也不用寫惰性刪除, 在每個節點上記錄一個 \(Size\), 表示這個子樹的節點數, 當兩個子樹極度不平衡時 (左右子樹相差 \(3\) 倍以上), 重構替罪羊.

對於詢問, 根據當前節點的矩形和詢問的矩形分類討論:

  • 如果自己的矩形被完全包含, 記錄 \(Sum\) 到答案中

  • 如果自己的矩形和詢問矩形無交, 跳出遞迴

  • 其餘情況, 判斷自己的點是否在詢問矩形內, 統計答案, 然後遞迴左右子樹

程式碼

程式碼方面比替罪羊的邏輯簡單, 但是因為多了一維, 所以細節會更多. 但是因為空間限制 \(20MB\), 總是 MLE 的我除錯的方向一直向著奇怪的地方發展, 從指標改到陣列, 結果從 BZOJ 下了資料跑 \(m = 100\) 的資料都爆 \(500MB\), 恍然大悟重構的時候沒有清空葉子節點的左右兒子, 於是改過來以後成功 TLE.

我一開始是按 \(y\) 切的, 因為第一篇題解按 \(x\) 切都沒有被卡, 所以一氣之下使用了隨機化, 這時, 已經沒有隨機資料能卡掉我了.

unsigned CntN(1), Rbd(0), RbFa(0), Root(1), List[200005], m, n, Cnt(0), A, B, Hd, t, Ans(0), Tmp(0), Opx, Opy, Opx_, Opy_, Opv;
char flg(1), nowDir(1), SonDir(0);
struct Node {
  unsigned LS, RS, Val, Sum, Size, x, y, Minx, Miny, Maxx, Maxy;
  char Dir;
}N[200005];
struct Stack {
  unsigned Val, x, y;
  inline const char operator <(const Stack &sx) const{
    return (nowDir) ? ((this->x ^ sx.x) ? (this->x < sx.x) : (this->y < sx.y)) : ((this->y ^ sx.y) ? (this->y < sx.y) : (this->x < sx.x));
  }
}S[200005];
inline void Clr() {}
unsigned Build(unsigned L, unsigned R) {
  register unsigned now(List[Hd--]);
  if(L == R) {
    N[now].Minx = N[now].Maxx = N[now].x = S[L].x;
    N[now].Miny = N[now].Maxy = N[now].y = S[L].y;
    N[now].Sum = N[now].Val = S[L].Val;
    N[now].Size = 1;
    N[now].LS = N[now].RS = 0;
    return now;
  }
  register unsigned Mid((L + R) >> 1);
   nowDir = rand() % 2;
  N[now].Dir = nowDir;
  nth_element(S + L, S + Mid, S + R + 1);
  N[now].Minx = N[now].Maxx = N[now].x = S[Mid].x;
  N[now].Miny = N[now].Maxy = N[now].y = S[Mid].y;
  N[now].Sum = N[now].Val = S[Mid].Val;
  N[now].Size = 1;
  if(Mid ^ L) {
    N[now].LS = Build(L, Mid - 1);
    N[now].Sum += N[N[now].LS].Sum;
    N[now].Size += N[N[now].LS].Size;
    N[now].Minx = min(N[now].Minx, N[N[now].LS].Minx);
    N[now].Maxx = max(N[now].Maxx, N[N[now].LS].Maxx);
    N[now].Miny = min(N[now].Miny, N[N[now].LS].Miny);
    N[now].Maxy = max(N[now].Maxy, N[N[now].LS].Maxy);
  } else {
    N[now].LS = 0;
  }
  N[now].RS = Build(Mid + 1, R);
  N[now].Sum += N[N[now].RS].Sum;
  N[now].Size += N[N[now].RS].Size;
  N[now].Minx = min(N[now].Minx, N[N[now].RS].Minx);
  N[now].Maxx = max(N[now].Maxx, N[N[now].RS].Maxx);
  N[now].Miny = min(N[now].Miny, N[N[now].RS].Miny);
  N[now].Maxy = max(N[now].Maxy, N[N[now].RS].Maxy);
  return now;
}
void DFS(unsigned now) {
  if(N[now].LS) DFS(N[now].LS);
  List[++Hd] = now, S[Hd].Val = N[now].Val, S[Hd].x = N[now].x, S[Hd].y = N[now].y;
  if(N[now].RS) DFS(N[now].RS);
}
unsigned Rebuild(unsigned now) {
  Hd = 0, DFS(now);
  return Build(1, Hd);
}
void Insert(unsigned now, unsigned Fa, const char Dire) {
  N[now].Sum += Opv;
  if(!((N[now].x ^ Opx) | (N[now].y ^ Opy))) {
    N[now].Val += Opv;
    return;
  }
  if(N[now].Dir) {
    if(Opx < N[now].x) {
      if(N[now].LS){
        N[now].Size -= N[N[now].LS].Size;
        Insert(N[now].LS, now, 0);
        N[now].Size += N[N[now].LS].Size;
      } else {
        ++(N[now].Size);
        N[now].LS = ++CntN;
        N[N[now].LS].Sum = N[N[now].LS].Val = Opv;
        N[N[now].LS].Size = 1;
        N[N[now].LS].Maxx = N[N[now].LS].Minx = N[N[now].LS].x = Opx;
        N[N[now].LS].Maxy = N[N[now].LS].Miny = N[N[now].LS].y = Opy;
      }
      N[now].Minx = min(N[now].Minx, N[N[now].LS].Minx);
      N[now].Miny = min(N[now].Miny, N[N[now].LS].Miny);
      N[now].Maxy = max(N[now].Maxy, N[N[now].LS].Maxy);
    } else {
      if(N[now].RS){
        N[now].Size -= N[N[now].RS].Size;
        Insert(N[now].RS, now, 1);
        N[now].Size += N[N[now].RS].Size;
      } else {
        ++(N[now].Size);
        N[now].RS = ++CntN;
        N[N[now].RS].Sum = N[N[now].RS].Val = Opv;
        N[N[now].RS].Size = 1;
        N[N[now].RS].Maxx = N[N[now].RS].Minx = N[N[now].RS].x = Opx;
        N[N[now].RS].Maxy = N[N[now].RS].Miny = N[N[now].RS].y = Opy;
      }
      N[now].Miny = min(N[now].Miny, N[N[now].RS].Miny);
      N[now].Maxy = max(N[now].Maxy, N[N[now].RS].Maxy);
      N[now].Maxx = max(N[now].Maxx, N[N[now].RS].Maxx); 
    }
  } else {
    if(Opy < N[now].y) {
      if(N[now].LS){
        N[now].Size -= N[N[now].LS].Size;
        Insert(N[now].LS, now, 0);
        N[now].Size += N[N[now].LS].Size;
      } else {
        ++(N[now].Size);
        N[now].LS = ++CntN;
        N[N[now].LS].Sum = N[N[now].LS].Val = Opv;
        N[N[now].LS].Size = 1;
        N[N[now].LS].Maxx = N[N[now].LS].Minx = N[N[now].LS].x = Opx;
        N[N[now].LS].Maxy = N[N[now].LS].Miny = N[N[now].LS].y = Opy;
      }
      N[now].Miny = min(N[now].Miny, N[N[now].LS].Miny);
      N[now].Minx = min(N[now].Minx, N[N[now].LS].Minx);
      N[now].Maxx = max(N[now].Maxx, N[N[now].LS].Maxx);
    } else {
      if(N[now].RS){
        N[now].Size -= N[N[now].RS].Size;
        Insert(N[now].RS, now, 1);
        N[now].Size += N[N[now].RS].Size;
      } else {
        ++(N[now].Size);
        N[now].RS = ++CntN;
        N[N[now].RS].Sum = N[N[now].RS].Val = Opv;
        N[N[now].RS].Size = 1;
        N[N[now].RS].Maxx = N[N[now].RS].Minx = N[N[now].RS].x = Opx;
        N[N[now].RS].Maxy = N[N[now].RS].Miny = N[N[now].RS].y = Opy;
      }
      N[now].Maxy = max(N[now].Maxy, N[N[now].RS].Maxy);
      N[now].Minx = min(N[now].Minx, N[N[now].RS].Minx);
      N[now].Maxx = max(N[now].Maxx, N[N[now].RS].Maxx); 
    }
  }
  if(N[now].Size > 3) {
    if(!((N[now].LS) && (N[now].RS))) {
      Rbd = now, RbFa = Fa, SonDir = Dire;
      return;
    }
    if((N[N[now].LS].Size * 3 < N[N[now].RS].Size) || (N[N[now].LS].Size > N[N[now].RS].Size * 3)){
      Rbd = now, RbFa = Fa, SonDir = Dire;
    }
  }
  return;
}
void Qry(unsigned now) {
  if(N[now].Minx >= Opx && N[now].Miny >= Opy && N[now].Maxx <= Opx_ && N[now].Maxy <= Opy_) {
    Ans += N[now].Sum;
    return;
  }
  if(N[now].Minx > Opx_ || N[now].Miny > Opy_ || N[now].Maxx < Opx || N[now].Maxy < Opy) {
    return;
  }
  if(N[now].x >= Opx && N[now].y >= Opy && N[now].x <= Opx_ && N[now].y <= Opy_) {
    Ans += N[now].Val;
  }
  if(N[now].LS) Qry(N[now].LS);
  if(N[now].RS) Qry(N[now].RS);
  return;
}
int main() {
  srand(time(0));
  n = RD();
  N[1].Minx = N[1].Miny = N[1].Maxx = N[1].Maxy = N[1].Size = N[1].y = N[1].x = 1, N[1].Sum = N[1].Val = 0;
  while (1) {
    A = RD();
    ++Cnt;
    if(A & 1) {
      if(A ^ 3) {
        Opx = RD() ^ Ans, Opy = RD() ^ Ans, Opv = RD() ^ Ans;
        Rbd = 0, Insert(Root, 0, false);
        if(Rbd) {
          if(!RbFa) {
            Root = Rebuild(Rbd);
          }
          else {
            if(SonDir) {
              N[RbFa].RS = Rebuild(Rbd);
            } else {
              N[RbFa].LS = Rebuild(Rbd);
            }
          }
        }
      } else break;
    } else {
      Opx = RD() ^ Ans, Opy = RD() ^ Ans, Opx_ = RD() ^ Ans, Opy_ = RD() ^ Ans;
      Ans = 0, Qry(Root);
      printf("%u\n", Ans);
    }
  }
  return Wild_Donkey;
}

指標神教

前面提到, MLE 並不是指標的問題, 所以作為指標神教徒, 還是要把指標請回來的, 發現僅比陣列多了 \(1MB\), 並且總時間比陣列快了 \(1s\).

unsigned List[200005], m, n, Cnt(0), Rand(time(0)), A, B, Hd, t, Ans(0), Tmp(0), Opx, Opy, Opx_, Opy_, Opv;
char flg(1), nowDir(0), SonDir(0);
struct Node {
  Node *LS, *RS;
  unsigned Val, Sum, Size, x, y, Minx, Miny, Maxx, Maxy;
  char Dir;
}N[200005], *CntN(N), *Rbd(NULL), *RbFa(NULL), *Root(N);
struct Stack {
  unsigned Val, x, y;
  inline const char operator <(const Stack &sx) const{
    return (nowDir) ? ((this->x ^ sx.x) ? (this->x < sx.x) : (this->y < sx.y)) : ((this->y ^ sx.y) ? (this->y < sx.y) : (this->x < sx.x));
  }
}S[200005];
inline void Clr() {}
Node *Build(unsigned L, unsigned R) {
  register Node *now(N + List[Hd--]);
  if(L == R) {
    now->Minx = now->Maxx = now->x = S[L].x;
    now->Miny = now->Maxy = now->y = S[L].y;
    now->Sum = now->Val = S[L].Val;
    now->Size = 1;
    now->LS = now->RS = NULL;
    return now;
  }
  register unsigned Mid((L + R) >> 1);
  Rand = Rand * MOD1;
  nowDir = (Rand >> 1) % 2;
  now->Dir = nowDir;
  nth_element(S + L, S + Mid, S + R + 1);
  now->Minx = now->Maxx = now->x = S[Mid].x;
  now->Miny = now->Maxy = now->y = S[Mid].y;
  now->Sum = now->Val = S[Mid].Val;
  now->Size = 1;
  if(Mid ^ L) {
    now->LS = Build(L, Mid - 1);
    now->Sum += now->LS->Sum;
    now->Size += now->LS->Size;
    now->Minx = min(now->Minx, now->LS->Minx);
    now->Maxx = max(now->Maxx, now->LS->Maxx);
    now->Miny = min(now->Miny, now->LS->Miny);
    now->Maxy = max(now->Maxy, now->LS->Maxy);
  } else {
    now->LS = NULL; 
  }
  now->RS = Build(Mid + 1, R);
  now->Sum += now->RS->Sum;
  now->Size += now->RS->Size;
  now->Minx = min(now->Minx, now->RS->Minx);
  now->Maxx = max(now->Maxx, now->RS->Maxx);
  now->Miny = min(now->Miny, now->RS->Miny);
  now->Maxy = max(now->Maxy, now->RS->Maxy);
  return now;
}
void DFS(Node *now) {
  if(now->LS) DFS(now->LS);
  List[++Hd] = now - N, S[Hd].Val = now->Val, S[Hd].x = now->x, S[Hd].y = now->y;
  if(now->RS) DFS(now->RS);
}
Node *Rebuild(Node *now) {
  Hd = 0, DFS(now);
  return Build(1, Hd);
}
void Insert(Node *now, Node *Fa, const char Dire) {
  now->Sum += Opv;
  if(!((now->x ^ Opx) | (now->y ^ Opy))) {
    now->Val += Opv;
    return;
  }
  if(now->Dir) {
    if(Opx < now->x) {
      if(now->LS){
        now->Size -= now->LS->Size;
        Insert(now->LS, now, 0);
        now->Size += now->LS->Size;
      } else {
        ++(now->Size);
        now->LS = ++CntN;
        now->LS->Sum = now->LS->Val = Opv;
        now->LS->Size = 1;
        now->LS->Maxx = now->LS->Minx = now->LS->x = Opx;
        now->LS->Maxy = now->LS->Miny = now->LS->y = Opy;
      }
      now->Minx = min(now->Minx, now->LS->Minx);
      now->Miny = min(now->Miny, now->LS->Miny);
      now->Maxy = max(now->Maxy, now->LS->Maxy);
    } else {
      if(now->RS){
        now->Size -= now->RS->Size;
        Insert(now->RS, now, 1);
        now->Size += now->RS->Size;
      } else {
        ++(now->Size);
        now->RS = ++CntN;
        now->RS->Sum = now->RS->Val = Opv;
        now->RS->Size = 1;
        now->RS->Maxx = now->RS->Minx = now->RS->x = Opx;
        now->RS->Maxy = now->RS->Miny = now->RS->y = Opy;
      }
      now->Miny = min(now->Miny, now->RS->Miny);
      now->Maxy = max(now->Maxy, now->RS->Maxy);
      now->Maxx = max(now->Maxx, now->RS->Maxx); 
    }
  } else {
    if(Opy < now->y) {
      if(now->LS){
        now->Size -= now->LS->Size;
        Insert(now->LS, now, 0);
        now->Size += now->LS->Size;
      } else {
        ++(now->Size);
        now->LS = ++CntN;
        now->LS->Sum = now->LS->Val = Opv;
        now->LS->Size = 1;
        now->LS->Maxx = now->LS->Minx = now->LS->x = Opx;
        now->LS->Maxy = now->LS->Miny = now->LS->y = Opy;
      }
      now->Miny = min(now->Miny, now->LS->Miny);
      now->Minx = min(now->Minx, now->LS->Minx);
      now->Maxx = max(now->Maxx, now->LS->Maxx);
    } else {
      if(now->RS){
        now->Size -= now->RS->Size;
        Insert(now->RS, now, 1);
        now->Size += now->RS->Size;
      } else {
        ++(now->Size);
        now->RS = ++CntN;
        now->RS->Sum = now->RS->Val = Opv;
        now->RS->Size = 1;
        now->RS->Maxx = now->RS->Minx = now->RS->x = Opx;
        now->RS->Maxy = now->RS->Miny = now->RS->y = Opy;
      }
      now->Maxy = max(now->Maxy, now->RS->Maxy);
      now->Minx = min(now->Minx, now->RS->Minx);
      now->Maxx = max(now->Maxx, now->RS->Maxx); 
    }
  }
  if(now->Size > 3) {
    if(!((now->LS) && (now->RS))) {
      Rbd = now, RbFa = Fa, SonDir = Dire;
      return;
    }
    if((now->LS->Size * 3 < now->RS->Size) || (now->LS->Size > now->RS->Size * 3)){
      Rbd = now, RbFa = Fa, SonDir = Dire;
    }
  }
  return;
}
void Qry(Node *now) {
  if(now->Minx >= Opx && now->Miny >= Opy && now->Maxx <= Opx_ && now->Maxy <= Opy_) {
    Ans += now->Sum;
    return;
  }
  if(now->Minx > Opx_ || now->Miny > Opy_ || now->Maxx < Opx || now->Maxy < Opy) {
    return;
  }
  if(now->x >= Opx && now->y >= Opy && now->x <= Opx_ && now->y <= Opy_) {
    Ans += now->Val;
  }
  if(now->LS) Qry(now->LS);
  if(now->RS) Qry(now->RS);
  return;
}
int main() {
  n = RD();
  N[0].Minx = N[0].Miny = N[0].Maxx = N[0].Maxy = N[0].Size = N[0].y = N[0].x = 1, N[0].Sum = N[0].Val = 0;
  while (1) {
    A = RD();
    if(A & 1) {
      if(A ^ 3) {
        ++Cnt;
        Opx = RD() ^ Ans, Opy = RD() ^ Ans, Opv = RD() ^ Ans;
        Rbd = NULL, Insert(Root, NULL, false);
        if(Rbd) {
          if(!RbFa) {
            Root = Rebuild(Rbd);
          }
          else {
            if(SonDir) {
              RbFa->RS = Rebuild(Rbd);
            } else {
              RbFa->LS = Rebuild(Rbd);
            }
          }
        }
      } else break;
    } else {
      Opx = RD() ^ Ans, Opy = RD() ^ Ans, Opx_ = RD() ^ Ans, Opy_ = RD() ^ Ans;
      Ans = 0, Qry(Root);
      printf("%u\n", Ans);
    }
  }
  return Wild_Donkey;
}