1. 程式人生 > 其它 >Vue Element-ui 之 el-table自動滾動

Vue Element-ui 之 el-table自動滾動

  1. 參考:

    1. 演算法訓練營6.4

  2. 簡介:

    1. 名稱:樹套樹

    2. 本質:一個節點為另一種樹形結構(也可以是自己)的樹形結構。

    3. 一些abstract:我們用平衡樹實現過查詢過一棵樹中的第k小,但是沒有做到查詢某一個區間的第k小,更不用說帶動態修改的區間第k小了。如果不要求線上處理,cdq可以解決動態區間第k小,但是線上的話需要用樹套樹。

      樹套樹指在一個樹形資料結構上,每個節點不再是一個節點,而是另一種樹形結構,最常見的樹套樹有線段樹套線段樹、線段樹套平衡樹、樹狀陣列套平衡樹,嘗試做到兩個資料結構的功能的並集。

      以線段樹套平衡樹為例:線段樹可以用來點、區間更新以及查詢;平衡樹可以用來查詢第k小、排名、前驅和後繼。我們用線段樹維護區間,再用平衡樹維護區間中的動態修改。先構造出線段樹,每個線段樹的節點除了記錄左右邊界,還用一棵平衡樹維護這一個區間中的所有數,具體見例子,

  3. 例題:

    1. (P3380/bzoj3196/Tyvj1730)要求維護一個有序數列,需要支援:

      1. 查詢k在區間內的排名

      2. 查詢區間內排名為k的值

      3. 修改某一個位置上的數值

      4. 查詢k在區間內的前驅(最大的嚴格小於x的數,若不存在輸出-2147483647)

      5. 查詢k在區間內的後繼(最小的嚴格大於x的數,若不存在輸出2147483647)

      區間操作和動態更新,所以可以用線段樹+平衡樹解決。

      1. 演算法設計:

        為線段樹的每個節點都開闢一棵和區間大小相同的平衡樹,平衡樹一般用Treap或伸展樹。線段樹的每一層區間包含的元素個數都為n(因為每一層都是整個區間拆開的結果,然後每個節點都有一棵區間長度大小的平衡樹,所以相當於又合了起來)。至多有logn層,於是所有的平衡樹的節點總數是nlogn的。此樹套樹如圖所示:

      1. 演算法實現:

        1. 建立線段樹和平衡樹。

          先建立線段樹,然後每個節點的區間資料都插入該節點對應的平衡樹中。

           void build(int x, int l, int r) {
             a[x].root 0;
             for (int l; <= r; i++) {
               a[x].insert(a[x].root, p[i]);
            }
             if (== r) return;
             int mid >> 1;
             build(<< 1, l, mid);
             build(<< 1, mid 1, r);
           }
        2. 查詢k在[ql, qr]之間的排名(最後別忘了+1):

          線上段樹中執行區間查詢,把每個線段樹節點中的平衡樹中的排名加起來再加1就是最終排名。

           int queryrank(int x, int l, int r, int ql, int qr, int k) {  // 當前節點為x,其左右界限為[l, r],查詢區間為[ql, qr],查詢k的排名
             if(qr || ql) return 0;  // 不相交
             if (ql <= && <= qr) {  // 完全被查詢包括
               return a[x].rank(a[x].root, k);  // 拿到[l, r]中比k小的個數
            }
             int ans 0, mid >> 1;
             ans += queryrank(<< 1, l, mid, ql, qr, k);
             ans += queryrank(<< 1, mid 1, r, ql, qr, k);
             return ans;
           }

          線段樹查詢最多O(logn)層,平衡樹查詢最多O(logn)層,所以時間複雜度是O(loglog)的。

        3. 查詢[ql, qr]區間排名為k的值。

          區間內的元素是無序的,所以不能按區間查詢排名。而用值進行二分搜尋(初始l和r分別是總共的極小和極大值),每次查詢這個值的排名,看看和k比較一下。

           int queryval(int ql, int qr, int k) {
             int min_val, max_val, ans -1, rank;
             while(<= r) {
               mid >> 1;
               rank queryrank(1, 1, n, ql, qr, mid);
               if (rank <= k) {  // 如果排名已經為k,則還可以變大,l也是要mid + 1
                 ans mid;
                 mid 1;
              } else {
                 mid 1;
              }
            }
             return ans;
           }

          複雜度為O(lognlognlog(max-min))。

        4. 點更新:

          修改pos位置上的數為k。與線段樹的點更新差不多,外加要更新每個節點對應的平衡樹,最後修改p[pos] = k。

           void modify(int x, int l, int r, int pos, int k) {
             if (pos || pos r) return;  // 不在這個範圍內
             a[x].remove(a[x].root, p[pos]);  // 先刪除這個值
             a[x].insert(a[x].root, k);  // 再插入新值
             if (== r) return;
             int mid >> 1;
             modify(<< 1, l, mid, pos, k);
             modify(<< 1, mid 1, r, pos, k);
           }

          線段樹中查詢O(logn)層,刪除和插入的複雜度為O(logn),總複雜度為O(lognlogn)。

        5. 查詢k在[ql, qr]區間的前驅:

          若查詢區間和當前節點的無交集,返回-inf;若查詢區間覆蓋了當前節點,則在當前節點平衡樹中查詢前驅;否則在左右子樹中搜索,分別求前驅。

           int querypre(int x, int l, int r, int ql, int qr, int k) {
             if (qr || ql) return -inf;  // 不相交
             if (ql <= && <= qr) return a[x].pre(a[x].root, k);  // 完全覆蓋在整個平衡樹中查詢前驅
             int mid >> 1;
             int ans -inf;
             ans max(ans, querypre(<< 1, l, mid, ql, qr, k));
             ans max(ans, querypre(<< 1, mid 1, r, ql, qr, k));
             return ans;
           }

          線段樹一共O(logn)層,查詢複雜度也是O(logn),所以總時間複雜度為O(lognlogn)。

        6. 查詢k在[ql, qr]區間的後繼:

          基本同上:

           int querynxt(int x, int l, int r, int ql, int qr, int k) {
             if (qr || ql) return inf;  // 不相交
             if (ql <= && <= qr) return a[x].nxt(a[x].root, k);  // 完全覆蓋在整個平衡樹中查詢後繼
             int mid >> 1;
             int ans inf;
             ans min(ans, querynxt(<< 1, l, mid, ql, qr, k));
             ans min(ans, querynxt(<< 1, mid 1, r, ql, qr, k));
             return ans;
           }

          總時間複雜度O(lognlogn)。

    2. (POJ1195)矩形區域查詢。二維的點更新和區間查詢。因為只有點更新,所以之前用二維樹狀陣列解決過,這裡用線段樹套線段樹解決。

      線段樹一共有O(n)個節點,每個節點又有一個O(n)節點的線段樹,所以空間複雜度為O(n^2)的。查詢、更新操作總時間複雜度為O(lognlogn)的。

      1. 資料結構定義:建立一維線段樹和二維線段樹節點

         struct node_y {  // 第二維線段樹節點,用來維護縱座標的和
           int l, r;  // 縱座標的區間
           int sum;  // 和值
         };
         
         struct node_x {  // 第一維線段樹節點,維護二維區間的和
           int l, r;  // 橫座標的區間
           node_y s[maxn << 2];  // 第二維線段樹
         }tr[maxn << 2];
      2. 建立樹套樹:不同於一維的,需要多一個引數,代表為哪個一維線段樹節點建立二維線段樹

         void build_y(int i, int l, int r, int k) {  // i為二維節點,代表[l, r]區間,k為一維線段樹節點
           tr[k].s[i].l;
           tr[k].s[i].r;
           tr[k].s[i].sum 0;  // 原題初始化就全是0
           if (== r) return;
           int mid >> 1;
           build_y(<< 1, l, mid, k);
           build_y(<< 1, mid 1, r, k);
         }
         
         void build_x(int i, int l1, int r1, int l2, int r2) {  // i為一維線段樹節點,[l1, r1]是一維的範圍,[l2, r2]是二維的範圍,但這裡[l2, r2]只能是[1, y_max]
           tr[i].l1;
           tr[i].r1;
           build_y(1, l2, r2, i);
           if (l1 == r1) return;
           int mid l1 r1 >> 1;
           build_x(<< 1, l, mid, l2, r2);
           build_x(<< 1, mid 1, r, l2, r2);
         }
      3. 點更新:

         void update_y(int i, int y, int val, int k) {  // k是一維節點序號,i是二維節點序號 val是要加的值 y是要改的縱座標
           tr[k].s[i].sum += val;
           if (tr[k].s[i].== tr[k].s[i].r) return;
           int mid = (tr[k].s[i].tr[k].s[i].r) >> 1;
           if (<= mid) update_y(<< 1, y, val, k);
           else update_y(<< 1, y, val, k);
         }
         
         void update_x(int k, int x, int y, int val) {  // k是一維節點序號,(x, y)是座標,+val
           update_y(1, y, val, k);  // 對k節點的整棵樹進行點更新
           if (tr[k].== tr[k].r) return;
           int mid tr[k].tr[k].>> 1;
           if (<= mid) update_x(<< 1, x, y, val);
           else update_x(<< 1, x, y, val);
         }
      4. 區間查詢:

         int query_y(int i, int l, int r, int k) {
           if (tr[k].s[i].== && tr[k].s[i].== r) return tr[k].s[i].sum;
           int mid = (tr[k].s[i].tr[k].s[i].r) >> 1;
           if (<= mid) return query_y(<< 1, l, r, k);
           else if (mid) return query_y(<< 1, l, r, k);
           else return query_y(<< 1, l, mid, k) query_y(<< 1, mid 1, r, k);
         }
         
         int query_x(int k, int l1, int r1, int l2, int r2) {  // 查詢區間[l1, r1][l2, r2]
           if (tr[k].== l1 && tr[k].== r1) return query_y(1, l2, r2, k);
           int mid tr[k].tr[k].>> 1;
           if (r1 <= mid) return query_x(<< 1, l1, r2, l2, r2);
           else if (mid) return query_x(<< 1, l1, r2, l2, r2);
           else return query_x(<< 1, l1, r2, l2, r2) query_x(<< 1, l1, r2, l2, r2);
         }
    3. (HDU4819)點更新 & 區間查詢(最大值和最小值)

      所有二維的最小值的最小值是一維的最小值,所以可以樹套樹。

      1. 資料結構定義:

         struct node {
           int Max, Min;
         }tr[maxn << 1][maxn << 1];  // 第i維就是處理i維座標
      2. 建樹:

         void pushup_x(int i, int k) {  // 1-i2-k
           tr[k][i].Max max(tr[<< 1][i].Max, tr[<< 1][i].Max);
           tr[k][i].Min min(tr[<< 1][i].Min, tr[<< 1][i].Min);
         }
         
         void pushup_y(int i, int k) {  // 1-i2-k
         tr[k][i].Max max(tr[k][<< 1].Max, tr[k][<< 1].Max);
           tr[k][i].Min min(tr[k][<< 1].Min, tr[k][<< 1].Min);
         }
         
         void build_y(int i, int k, int l, int r, int flag) {  // i第二維座標;k第一維座標;處理第二維的[l, r];flag == 1代表橫座標區間已經是一個點了,此時不用管兒子,flag == 2表示橫座標仍然是一個區間,這時要根據兒子的答案取父親的答案
           int mid, val;
           if (== r) {
             if (flag == 1) {
               scanf("%d", &val);
             tr[k][i].Max tr[k][i].Min val;
            } else {
               pushup_x(i, k);
            }
             return;
          }
           mid = (r) >> 1;
           build_y(<< 1, k, l, mid, flag);
           build_y(<< 1, k, mid 1, r, flag);
         }
         
         void build_x(int k, int l, int r) {
           if (== r) {
             build_y(1, k, 1, n, 1);  // 整棵樹都要建,已經為葉子節點,flag == 1
             return;
          }
           int mid >> 1;
           // 一方面處理更小的豎著的矩形
           build_x(<< 1, l, mid);
           build_x(