「筆記」線段樹


寫在前面

大概是筆記吧
部分內容引用了\(\texttt{OI-Wiki}\)【AgOHの數據結構】主席樹
部分圖片出處:【AgOHの數據結構】主席樹
感謝 LuckyBlock 同學提供了 例3、例12 和 例15 的鏈接
如果有題目鏈接或者文章出現了錯誤,歡迎指出!

基本區間操作

  1. 單點修改,區間詢問:對於每個線段樹上的區間維護一個區間的信息之和

    \(eg.\)單點修改,詢問區間和,區間最大值

  2. 區間修改,單點詢問:每次修改時對區間打上一個修改表及,在詢問和修改時,一旦訪問到一個區間,就將他的標記下傳

    \(eg.\)區間加,求單點的值

例題

線段樹與標記下傳

區間修改區間詢問的線段樹實現的重點:

  • 標記之間的合並
  • 區間信息之間的合並
  • 區間信息與標記的合並

只要能快速地進行以上三個操作,理論上標記和區間信息可以是任何東西(划重點!),甚至是平衡樹

權值線段樹

權值線段樹就是對一個值域上值的個數進行維護的線段樹

舉個例子,對於\(1,2,2,3,4,4,5,5,6,7,8,8\),他的權值線段樹就是

其中中括號內是值域,圓圈內表示的是值在對應值域里的數的個數

線段樹合並

線段樹合並其實就是動態開點權值線段樹合並

動態開點: 就是用哪個地方就開哪個地方,每次訪問之前看看是否為空,為空就新建一個節點。詢問時如果遇到沒有開過點的點,就直接返回即可,時間復雜度是\(\log n\)

普通的靜態線段樹左兒子為\(rt<<1\),右兒子為\(rt<<1|1\),用的空間大概是\(4n\),動態開點線段樹中左右兒子是不定的,要根據實際情況判斷

為什么要用動態開點呢?因為有些時候是不能用靜態線段樹的,比如說:

  • 值域很大

    比如\(10^9\),然而真正有效的點其實是很少的

    (當然,離散化牛逼

  • 可持久化

    \(n\)個線段樹\(T_1,T_2...T_n\),如果靜態開空間當然是開不下的,但是如果它和主席樹一樣,\(T_i\)是在\(T_{i-1}\)的基礎上進行修改的,就可以用可持久化的方法進行動態開點


兩個可重集合合並的方法

假設有兩個集合\(S,T\),假設為了維護它們的信息,已經建好了兩棵線段樹\(A_S,A_T\),現在要合並這兩個區間,應該如何合並呢?

  1. 啟發式合並

    如果\(|S|<|T|\),那么就枚舉\(S\)中的元素,將其加到\(T\)的線段樹中

    小的合並到大的上,復雜度\(O(n\log^2n)\),比較低效

  2. 線段樹合並

    根據線段樹 美妙的結構:如果下標相同,那么兩棵線段樹的結構就會完全相同

    設當前合並的節點為\(X,Y\),區間為\([L,R]\),用\(merge(X,Y,L,R)\)實現合並,考慮如何實現

    • 如果\(X\)為空,則返回\(Y\)

      if (X == NULL) return Y;
      
    • 如果\(Y\)為空,則返回\(X\)

      if (Y == NULL) return X;
      
    • 如果\(L=R\),即為葉節點,直接將兩點\(sum\)相加

      if (L == R) {
        int z = newnode();
        sum[z] = sum[X] + sum[Y];
        return z; //合並的結果是z
      }
      
    • 否則新建節點\(z\),分左右子樹合並

      int z = newnode(), mid = L + R >> 1;
      lson[z] = merge(lson[X], lson[Y], L, mid);//左子樹
      rson[z] = merge(rson[X], rson[Y], mid + 1, R);//右子樹
      update(z);//從子樹中獲得信息
      return z;
      

    以上就是線段樹合並的實現和偽代碼。復雜度為\(O(n\log n)\),證明:

    以上的執行過程中,其實只有兩種情況

    • \(X,Y\)中有一個為\(NULL\)

      直接返回,復雜度\(O(1)\)

    • \(X,Y\)都不為\(NULL\)

      單次執行過程復雜度為\(O(1)\)(只是單次)

      單次執行的過程中新建了一個節點,去掉了兩個節點,總共相當於去掉了一個節點,所以執行總次數就是\(O(\text{線段樹點個數})\)級別的

      一開始線段樹中的節點數是\(O(n\log n)\)級別的,所以均攤下來總復雜度就是\(O(n\log n)\)級別

可持久化線段樹

原理

關於 可持久化數據結構 的介紹,去看OI-Wiki

可持久化線段樹是一種可持久化的數據結構,其特點是:在更新時,可持久化線段樹盡可能與之前某一個版本共用一部分結點,從而起到節省空間的作用。


如下圖,現在有一顆有7個節點的線段樹

我們想要對圖中的紅點進行單點修改(其實就是單點修改,紅點是包含它的區間)

朴素的做法是建一棵新的線段樹,但是這樣十分浪費空間,因為線段樹一次修改只會修改\(\log\)個節點,為了修改這\(\log\)個節點而去重新建一棵線段樹是非常不可取的。

因此我們可以只去新建這\(\log\)個節點,並將新建的節點連接到原線段樹中要修改的點連接的位置上,如下圖

這樣就大大節省了空間。

實現

見此題洛谷 P3919 【模板】可持久化線段樹 1(可持久化數組)

/*
Author:Loceaner
*/
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define lson t[x].l
#define rson t[x].r
#define lc t[pre].l
#define rc t[pre].r
using namespace std;

const int A = 1e6 + 11;
const int B = 1e6 + 11;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;

inline int read() {
  char c = getchar(); int x = 0, f = 1;
  for ( ; !isdigit(c); c = getchar()) if (c == '-') f = -1;
  for ( ; isdigit(c); c = getchar()) x = x * 10 + (c ^ 48);
  return x * f;
}

int n, m, a[A], cnt, num, rt[A];
struct tree { int l, r, w; } t[A * 20];

void build(int &x, int l, int r) {
  x = ++num;
  if (l == r) { t[x].w = a[l]; return; }
  int mid = (l +r) >> 1;
  build(lson, l, mid), build(rson, mid + 1, r);
}

void update(int &x, int pre, int l, int r, int pos, int val) {
  x = ++num, lson = lc, rson = rc;
  if (l == r) { t[x].w = val; return; }
  int mid = (l + r) >> 1;
  if (pos <= mid) update(lson, lc, l, mid, pos, val);
  else update(rson, rc, mid + 1, r, pos, val); 
}

int query(int x, int l, int r, int k) {
  if (l == r) return t[x].w;
  int mid = (l + r) >> 1;
  if (k <= mid) return query(lson, l, mid, k);
  return query(rson, mid + 1, r, k);
}

int main() {
  n = read(), m = read();
  for (int i = 1; i <= n; i++) a[i] = read();
  build(rt[0], 1, n);
  while (m--) {
    int k = read(), opt = read(), pos, val, x;
    if (opt == 1) pos = read(), val = read(), update(rt[++cnt], rt[k], 1, n, pos, val);
    else if (opt == 2) x = read(), rt[++cnt] = rt[k], cout << query(rt[cnt], 1, n, x) << '\n';
  }
  return 0;
}

例題集合

例題1

給定序列\(a[1...n]\)\(Q\)次詢問,要求支持:

  • 區間乘-1
  • 求區間最大字段和

\(n,Q\le 10^5\)

由上面重點中所說,我們最重要的就是解決以下三個問題

  1. tag+tag
  2. 區間+區間
  3. 區間+tag

tag+tag

容易想到\(tag\)標記的就是區間是否乘\(-1\)
維護一個\(bool\)值,表示是否乘\(-1\),如果乘了偶數次就相當於沒乘

區間+區間

要維護區間的最大子段和\(ans\),不僅要維護值,還要維護左右端點
合並兩個子區間時,假設左子區間為\(L\),右子區間為\(R\),那么最大子段和的合並有以下三種情況:

  • \(L\)的最大子段和

  • \(R\)的最大子段和

  • 跨越\(L\)\(R\)的最大子段和

    對於這種情況可以分成兩部分,一個是\(L\)的后綴,另一個是\(R\)的前綴,因此還要維護區間的最大后綴和\(suf\)和最大前綴和\(pre\)

答案就是\(\max\{L_{ans},R_{ans},L_{suf}+R_{pre}\}\)

考慮如何更新最大后綴和和最大前綴和,以最大后綴和為例,兩個子區間合並時,最大后綴和有兩種情況:

  • 右子區間的最大后綴和\(R_{suf}\)
  • 右子區間的和\(R_{sum}\)+左子區間的最大后綴和\(L_{suf}\)

因此我們還需要維護區間的和\(sum\)
合並時最大前綴和同理,有 左子區間的最大前綴和\(L_{pre}\)左子區間的和\(L_{sum}\)+右子區間的最大前綴和\(R_{pre}\) 兩種情況

綜上我們要維護\(sum,ans,pre,suf\)四個值以及端點

區間+tag

當區間乘了\(-1\)時,\(sum\)可以直接變為相反數,但是其他三個值並不能直接這么干

考慮區間乘\(-1\)時,最大字段和的變化

  • 最大子段和 會變成 負的最小字段和

  • 負的最小字段和 會變成 最大子段和

因此我們在維護最大字段和、最大前綴、最大后綴時,要維護同一套的最小字段和、最小前綴、最小后綴,在乘\(-1\)時,直接將兩者交換,再乘\(-1\)即可

還有一些細節的地方,在此不再贅述(因為我也不會寫也沒有提交的地方(手動滑稽……然后這道題就做完了

SPOJ有幾道題和這題挺像,但是忘記叫啥名字了

例題2

給定序列\(a[1...n]\),支持單點修改,每次求區間單調棧大小

\(n,Q\le 10^5\)

區間單調棧是什么呢?對於一個區間,建立一個棧,首先將第一個元素入棧,從左往右掃,如果當前元素大於等於棧頂元素,就將其入棧,由此形成的棧即為單調不減的區間單調棧。

轉化一下,其實就是求區間內滿足\(a[i]=\max\limits_{j=1}^ia[j]\)\(a[i]\)的個數。

一個自然的想法是維護單調棧的大小\(siz\),那么如何去進行區間的合並呢?

合並兩個子區間時,假設左子區間為\(L\),右子區間為\(R\),考慮合並之后的單調棧的組成部分:

  • 第一部分:\(L\)的單調棧

    因為單調棧是從左往右做的,所以\(L\)的單調棧必然是大區間單調棧的一部分

  • 剩余部分

    設出函數\(calc(now,pre)\)\(now\)表示當前節點,\(pre\)表示當前單調棧的棧頂,\(calc\)函數計算剩余部分的單調棧的大小

總的單調棧大小\(siz\)就是\(L_{siz}+calc(R,L_{max})\)

calc的實現

現在有\(calc(now,pre)\)\(l\)表示\(now\)的左子樹,\(r\)表示\(now\)的右子樹

  • 如果\(pre>l_{max}\),說明整個左子區間都不用考慮了,此時答案就變成了\(calc(r,pre)\)
  • 如果\(pre\le l_{max}\),此時\(l\)是有貢獻的,他對\(siz\)的貢獻就是\(calc(l,pre)\),右子樹的貢獻為\(calc(r,l_{max})\),總貢獻就是\(calc(l,pre)+calc(r,l_{max})\)

至此\(calc\)就推完了,但是我們發現如果僅僅是這樣的話,在最壞的情況下,復雜度會爆炸,那么怎么優化呢?

觀察\(calc(r,l_{max})\),發現它就等於\(siz-l_{siz}\),所以第二種情況就可以變成\(calc(l,pre)+siz-l_{siz}\),其中\(siz\)都是可以處理好的

這樣我們就可以在\(O(\log n)\)的時間里完成一次合並

總時間復雜度\(O(Q\log^2 n)\)

原題:洛谷 P4198 樓房重建

例題3

有一個序列\(a[1...n],\)每個\(a[i]\)\((c,x,y)\),表示顏色和坐標

現在支持單點修改以及區間詢問:顏色不同的曼哈頓距離最大的一對點的距離

\(n,Q\leq10^5\)

這個題其實就是一堆普通的處理的小技巧疊加起來

曼哈頓距離:設坐標系中兩點的坐標為\((x_1,y_1)\)\((x_2,y_2)\),則\(|x_1-x_2|+|y_1-y_2|\)就是兩點之間的曼哈頓距離

簡單的小技巧就是把它拆成四個部分,如下

\[\max\begin{cases}x_1-x_2 + y_1-y_2\\x_1-x_2 +y_2-y_1\\x_2-x_1+y_1-y_2\\x_2-x_1+y_2-y_1\end{cases} \]

顯然這四種的最大值就是曼哈頓距離,所以分情況討論,討論每種情況下顏色不同的曼哈頓距離最大的一對點的距離,最后取最大值,就是我們要的答案了

以第一種為例,其實可以寫成\((x_1+y_1)-(x_2+y_2)\),這就相當於每個點有了一個權值\(w=x+y\),那么現在的問題就相當於找權值最大和權值最小的點,而且他們的顏色要不同

如果不考慮顏色不同,我們就可以用線段樹直接維護區間權值的最大值和最小值

那么如果考慮顏色不同該怎么做呢?一種不太顯然的做法是:在維護最大值和其顏色的同時,維護一個次大值以及其顏色,保證最大值和次大值的顏色不同,在維護最小值和其顏色的同時,維護一個次小值以及其顏色,保證最小值和次小值的顏色不同

當區間最大值和最小值顏色不同時,答案就是兩者之差,當兩者顏色相同時,有 最大值-次小值次大值-最小值 兩種情況,兩者顏色一定不同,所以取\(\max\)即可

顏色不同的次權值如何維護?

假設現在已經知道了左子區間的最大值、次大值及其顏色,右子區間的最大值、次大值及其顏色,那么新區間的最大值一定為兩個子區間種的最大值之一,次大值的話其他三個值都有可能,選出其中最大的且與最大值顏色不同的一個即可


其他三種情況也可以類似的處理

這樣的話這個題就做完了,直接開四棵線段樹就好

原題鏈接https://codeforces.com/gym/101955/problem/E

技巧總結

  1. 對曼哈頓距離的處理
  2. 對顏色不同的處理

例題4

給定\(a[1...n]\),要求支持單點修改,以及區間詢問\(a[l...r]\)不能組成的最小的數

\(n,Q\le2\times10^5,1\le a[i]\le10^9\)

時限\(15s\)

聽老師說十五秒直接三個\(log\)就能艹過了

首先一定要有一個值為\(1\)的數,否則最小的不能組成的數就是\(1\)

小結論

假設現在可以表示\(1\sim x\)中的所有數,這個時候如果來了一個值為\(x+1\)的數,那么我們就可以表示出\(1\sim 2x+1\)

這是比較顯然的,因為\(1\sim x\)都可以表示出來,\(x+1\)有了,之后的\(x+2\sim 2x+1\)就可以直接用\(x+1\)和之前的數拼起來了

推廣一下,如果現在已經可以表示出\(1\sim x\)中的所有數,如果來了一個值為\(y\)的數,且\(y\leq x+1\),那么我們就可以拼出\(1\sim x+y\)中的數


假設\(T\)表示能表示出\(1\sim T\),那么只要加入一個\(\leq T+1\)的數,\(T\)的范圍就會大大增加,所以就可以擴展到區間里\(\le T+1\)的數和

要用有鼠撞樹卒的主席樹維護對於一個二維區間里\(\leq T+1\)的數的和

我不會了,爬

例題5

給定\(a[1…n]\)要求支持:

  1. 區間加
  2. 區間變成\(max(a[i]+v,0)\)\(v\)可以是正數也可以是負數
  3. 求單點當前值
  4. 求單點歷史最大值
  5. 區間覆蓋

\(n,Q\le10^5\)

他 跳 了

例題6

給定序列列\(a[1…n]\),要求支持區間和以及讓\(a[L…R]\)\(x\)\(\min\)
\(n,Q\le10^5\)

他 跳 了

例題7

給定序列\(a[1...n]\)以及\(k,d\),求一個最長的區間,使得最多加入\(k\)個數之后,排序得到的是一個公差為\(d\)的等差數列

\(n\le2\times10^5\)

要素察覺:必須要是一個公差為\(d\)的等差數列

首先要特判掉\(d=0\)的情況,這樣的情況下就是要尋找最長的一段數字相同的區間

那么再來看別的情況,對於一個區間\([l,r]\)

  • 這個等差數列里的所有數\(\bmod d\)的結果應該一樣
  • 區間內沒有重復的數

考慮怎么進行

  • 首先將序列分成若干個\(x \bmod d\)都一樣的子區間

    在從左往右掃的過程中,如果遇到了與前面\(x\bmod d\)的值不同的數,就將左邊\(x\bmod d\)值相同的數作為一個獨立的區間來處理,最后就可以分成若干個\(x \bmod d\)都一樣的子區間

  • 對於一個滿足\(x\bmod d=c\)的數列,把所有的\(x\)變成\(\dfrac{x-c}{d}\)(因為整形的性質,可以直接除),這樣整個序列的公差就為\(1\)了,問題就轉化成了加入\(k\)個數,使區間\(sort\)后公差為\(1\)。(歸一化)

  • 對於一個區間\([L,R]\),考慮如何算出最少加幾個數

    • 首先不能有重復
    • 顯然最少加的數的個數就是\(\max(L,R)-\min(L,R)+1-(R-L+1)\)
  • 從小到大枚舉\(R\),相當於求最小的\(L\),使得

    • \([L,R]\)無重復

      從小到大枚舉\(R\),對於新的\(a[R]\)很容易知道它前面一個和他相等的數\(a[T]\)(可以用\(map\)實現),那么\(L\)至少要大於\(T\)

    • \(\max(L,R)-\min(L,R)+1-(R-L+1)\le k\)

      \(\max(L,R)-\min(L,R)+L\le k + R\)

      用線段樹維護\(w[L]=\max(L,R)-\min(L,R)+L\)

      假如\(L\)的下界是\(T\),那么我們要在\([T+1,R]\)中找最左的位置使得\(w\le k + R\)

如何維護\(w\)?用單調棧。維護一個單調遞減的棧,因為單調棧遞減的性質,所以當一個大於棧頂的元素加入時,會不斷地彈出棧頂,直到棧頂元素大於此元素為止,再將此元素入棧。

此處,單調棧可以將\(\max(L,R)\)分成遞減的若干段,考慮如何實現:

如圖所示,假設有一個單調遞減的單調棧,其中\(S1> S2> S3\)\(S3\)為棧頂元素

ddz1

由於單調棧的性質,\(S1\)和棧中上一個元素之間可能是有別的元素的,所以\(S1,S2,S3\)其實是代表了三個區間的最大值

此時,單調棧中來了一個新的元素\(a\),顯然\(a>S1>S2>S3\),所以我們要將\(S1,S2,S3\)彈棧

ddz2

在彈棧時,因為此時這三個元素的值不能代表這三個綠色段的最大值了,所以我們需要將三個段的貢獻從線段樹中減去

同時,新的元素\(a\)就加進來了,這個時候就可以發現原來三個段的代表元素被彈出之后,其實就被新元素所接管了,所以再對一整個區間進行區間加操作即可

這樣單調棧就實現了將\(\max(L,R)\)分成了遞減的若干段,由此就可以不斷進行段樹的區間加、區間減。

\(\min\)的維護同理。

最后再調用一下找最左邊的\(w\le k+R\)的算法就好了。

總時間復雜度\(O(n\log n)\)

原題:CF407E

例題8

給定序列\(a[1...n]\)\(Q\)次詢問\(a[L...R]\)\(L\le i\le R,L\le j\le R\)\(i\ne j\)\(|a_i-a_j|\) 的最小值

\(n,Q\le2\times10^5\)

維護區間的一種比較通用的處理方法:

從小到大枚舉\(R\),維護一顆線段樹,\(w\)的下標\(L\)表示\(L\sim R\)的答案,每次\(R+1\)時做一些修改

好處:每次只需考慮新的數


假設現在有一個\(a\)序列,枚舉右端點從\(R-1\)轉移到了\(R\),設尋找的一個數為\(x\)

那么可以從兩個方面考慮:

  • \(x>a[R]\)

    \(R\)開始向左尋找比\(a[R]\)大的最近的數\(a[x]\),由此可以得出\(L=1\sim x\)有一個新的備選的值:\(a[x]-a[R]\),取\(\min\)即可

    但這是不夠的,我們需要繼續往左尋找新的數,假設為\(a[y]\)

    那么\(a[y]\)首先必須滿足的條件就是\(a[R]\le a[y]\le a[x]\),否則一定不會更新答案,此時他就會造成一個\(a[y]-a[R]\)的影響,同時還有一個影響就是\(a[x]-a[y]\),這個影響其實在右端點枚舉到\(x\)時就已經統計過了,所以如果\(a[y]-a[R]\)已經比\(a[x]-a[y]\)大的話,就沒有更新的必要了,所以\(a[y]-a[R]<a[x]-a[y]\)

    所以\(y\)滿足的條件為\(y< x,a[y]\in[a[R],\dfrac{a[R]+a[x]}{2}]\),且\(y\)最大

    這個東西可以用主席樹維護:線段樹用權值線段樹,維護一下在這個權值區間里下標最大的數,查詢時查詢一下主席樹在\(x\)時的歷史信息。

    找到\(y\)之后,就用\(a[y]-a[R]\)去更新\(w[1...y]\)

    之后再找\(z\),同上要滿足的條件是:\(a[z]-a[R]<a[y]-a[z]\)

    所以\(a[z]\in[a[R],\dfrac{a[R]+a[y]}{2}]\)

    之后一次次尋找,最多找\(\log\)

  • \(x<a[R]\)

    做法同上。

做……完……了……

原題:CF765F

例題9

給定\(n\),定義\(work(x,y)\)等於\([1,n]\)的線段樹上對\([x,y]\)進行區間詢問后訪問到的點的個數

給定\(L,R\),求\(work(i,j)\)的和,其中\(L\le i\le j\le R\)

\(n,Q\le 10^5\)

考慮\(work(x,y)\)如何快速求

對於線段樹上一個點\([L,R]\),被訪問到的條件有:

  1. \([x,y]\)有交
  2. \([x,y]\)不包含\([fa_L,fa_R]\)\(fa\)表示父親節點)

假設現在的詢問是\([X,Y]\),那么要求的就是

\[ans=\sum\limits_{i=X}^{Y}\sum\limits_{j=i}^Ywork(i,j) \]

  1. 如果\(X,Y\)包含了父區間

    \(C(len)=\dfrac{len\times(len+1)}{2}\)

    那么首先要去掉的就是與\(L,R\)不交的區間:\(C(L-X)+C(Y-R)\)

    第二步取出的是雖然與有交,但是包含了父親區間即:\((fa_L-X+1)(Y-fa_R+1)\)

    最后答案是\(C(Y-X+1)-[C(L-X)+C(Y-R)]-(fa_L-X+1)(Y-fa_R+1)\)

  2. 如圖所示

    這樣的情況就是\(C(Y-X+1)-C(Y-R)\)

  3. 其他同理,分析一下即可

這樣的復雜度是\(O(nQ)\)的,如何優化呢?

\(C(Y-X+1)-[C(L-X)+C(Y-R)]-(fa_L-X+1)(Y-fa_R+1)\)展開其實就是一個二次函數,是可以預處理的

聽不懂了

爬。。有空再補


此題總結

  1. \(work(x,y)\)
  2. \(O(nQ)\)
  3. \(O(nQ)\)剪枝,卡常數
  4. 某些情況整個子樹可一起算

例題10

\(Q\)次操作,每次給定\(L,R,K,B\):對於$L<=i<=R \(令\)a[i]=\max(a[i],K\times i+B)\(,單點詢問\)a[i]$

\(n,Q\le10^5\)

顯然\(K\times i + B\)是一條線段,所以每次我們要加一條線段進去,就像下面這樣

一個想法是把線段視為一個tag,打到線段樹上去,但是顯然是不能這么做的,因為線段在某些情況下無法合並(或很難合並),如下圖

其實這題就是李超線段樹模板了

\(addline(rt,line)\)表示往\(rt\)上插一條線段\(line\),分類討論一下,無論如何進行,最后都會變成\(addline(child,line)\)的形式,所以給一個區間加一條線段的最壞復雜度為\(O(\log n)\),又因為是對線段樹的區間加直線,所以會給\(\log\)個區間進行\(addline\)操作,所以每次修改的總復雜度就是\(O(\log^2 n)\)

實現時需要記錄線段左端點、右端點和區間\(mid\)時線段的值,然后根據原來線段這三個點的大小和現在的線段這三個點的大小進行比較。

例題11

定義\(\min-\max\)樹是一顆二叉樹,每個葉子節點有一個權值,權值互不相同

現在定義每個非葉子的權值為:有\(p\)的概率是兩個兒子的權值的\(max\),有\(1-p\)的概率是兩個兒子的權值的\(min\)

對於所有可能的\(i\),輸出根節點權值為\(i\)的概率

\(n\le5\times10^5\)

概率不會,跳了

與線段樹合並有關

例題12

給定一棵\(n\)個點的樹,點有顏色,每次詢問點\(x\)子樹內距離$x $ 不超過\(d\)的點有多少種不不同的顏色
\(n\le10^5\)

無 內 鬼(他 忘 記 出 處)(某個群友提供了出處但是找不到記錄/kk

問題可以轉化為尋找不同顏色的\(y\)的個數,\(y\)滿足\(y\in x\)的子樹且\(dep[y]\le dep[x]+d\)

對樹上每個點開一個線段樹\(ans[x]\),用\(ans[x][i]\)表示在\(x\)的子樹中有幾種顏色深度最小是\(i\),假如能夠求出,那么最后的答案就是\(\sum\limits_{i=1}^{dep[x]+d}ans[x][i]\)

那么怎么做呢?

假設\(x\)節點有兩個兒子\(y,z\),一個簡單又粗暴的想法是直接用\(ans[x]=merge(ans[y],ans[z])\),但是這樣顯然是不太合理的,因為可能有些顏色在\(y\)中出現,同時也在\(z\)中出現

其實這個東西是可以用的,但是還需要進行一些處理,我們記它為\(DA[x]=merge(ans[y],ans[z])\),這個東西也有一個比較好的性質,就是重復的顏色剛好被算了兩次(為什么不會是三次,我覺得如果從下往上合並次數多了就會大於兩次啊……還是說他說的就只是指這一次合並啊?當然如果在底層處理好的話也沒啥問題了,求gyh解答)

我們要想辦法把這種影響給消除掉,比如說有一種顏色在\(y\)中的最小深度是\(y_1\),在\(z\)中的最小深度是\(z_1\),因為算重,會導致\(DA[x][y_1]++,DA[x][z_1]++\),我們要想辦法把\(y_1\)\(z_1\)之中深度比較大的那個減掉(為什么是較大的,較小的不行嘛?求gyh證明),比如說\(z_1\)\(y_1\)大,那么就把\(DA[x][z_1]--\),只要能把所有的這樣的\(z_1\)消除影響,最后得出的答案其實就是我們想要的答案了

那我們要知道被多算的顏色是什么,而且要枚舉每個被多算的顏色,因為我們對於每個被多算的顏色要在線段樹上進行一些修改。

考慮線段樹合並,對於\(y\)\(z\)建立以顏色為下標的線段樹\(c[y],c[z]\),合並的時候將這兩棵線段樹合並,即\(merge(c[y],c[z])\),那么當我們合並到葉子節點\(i\)時,說明葉子節點上的這種顏色\(i\)就剛好是在兩棵線段樹中都出現的顏色(非常巧妙\(qwq\)

但是我們該怎么刪呢?\(c[y][i]\)記錄\(i\)\(y\)的子樹中最小深度是什么,在合並到葉子節點時,這種顏色分別對應\(c[y][i]\)\(c[z][i]\),其實就是上面我們說的\(y_1,z_1\),找出深度較大的,在線段樹中進行單點修改把它刪掉,就完成了修改。

復雜度\(O(n\log n)\),證明:

有兩種線段樹\(ans[x],c[x]\),算法步驟:

  1. 合並\(ans[x]\)

  2. 合並\(c[x]\)

    合並時在葉子節點處會進行單點刪除,每次是\(O(\log n)\)

看起來好像是兩個\(log\),但其實是葉子節點之后\(O(n)\)個(可以理解成每個葉子節點都對應着原先樹上的某一個節點),每次這樣合並會減少一個葉子節點,每次減少時會有\(O(\log n)\)的復雜度,所以總的復雜度就是\(O(n\log n)\)

星星星

\(ans[x][i]\)表示\(x\)子樹內最小深度為\(i\)的顏色個數

\(c[x][i]\)表示顏色\(i\)\(x\)子樹內的最小深度

這道題運用了線段樹比較神奇的性質

一定不會有重復的葉子節點,如果一個顏色在這個子樹中有,在另一個子樹中沒有,那么在動態開點時另一個子樹中一定不會開出這個葉子節點

原題鏈接:七彩樹

例題13

給定數組 \(a[1…n]\),一開始都為\(1\),要求支持以下三種操作:

  1. 給定\(L,R\),對於\(L\le i\le R\),令\(a[i]=\varphi(a[i])\)
  2. 給定\(L,R,x\),對於\(L\le i\le R\),令\(a[i]=x\)
  3. 求區間和

\(n,Q\le 10^5,x\le 10^7\)

當沒有操作2時,做法還是比較顯然的

有引理:

  1. 一個奇數\(x\)的歐拉函數\(\varphi(x)\)必定是一個偶數

    根據歐拉函數的定義有:

    \[\varphi(x)=x\prod\dfrac{p_i-1}{p_i} \]

    其中\(p_i\)\(x\)質因數分解后得到的的質因子。

    因為\(x\)是奇數,所以質因數分解得到的每一個質因子也一定是奇數,因此\(p_i-1\)一定是偶數,又因為在分母上的\(p_i\)相乘的結果一定是\(x\)的因子,所以答案就是若干個偶數相乘或者一個奇數和若干個偶數相乘,得到的結果一定是偶數。

  2. 一個偶數的\(x\)的歐拉函數\(\varphi(x)\)一定小於等於這個數的一半

    和偶數互質的數一定是奇數,不然就會有共同的因子\(2\),因為偶數都是\(2\)的倍數,而小於\(x\)的正奇數不超過\(\dfrac{x}{2}\)個,所以\(\varphi(x)\le \dfrac{x}{2}\)

所以一個數最多進行\(\log\)次操作1就會變成\(1\),如果一個區間全部是\(1\)了就可以直接跳過,否則就往下遞歸,這樣的復雜度是\(O(n\log^2 n)\)

那么有覆蓋時應該怎么做呢?

維護區間內所有數是否相等。

如果相等就可以直接打一個區間覆蓋標記,直接 $ return$就好了

例題14

給定\(n\)個區間,以及每個點的價值\(val[1…M]\),對於每個區間可以選擇里面的一個點\(i\),獲得價值\(val[i]\),每個點最多只能被選一次,求最大價值

\(n,M\le5000\)

明顯是一個匹配問題,左邊是含義為區間的\(n\)個點,右邊是\(m\)個點,如果一個點在某個區間內,就從這個區間向這個點連邊,然后用網絡流——最大費用最大流去做,點數是\(O(n+m)\),邊數是\(O(nm)\)的,很明顯,單單這么做是過不了的。

建一棵\(1\sim m\)的線段樹,用線段樹優化連邊。

每個點都往自己的兒子連邊,葉子節點就是代表右邊的\(m\)個點,什么意思呢?

意思就是如果一個題目中所說的區間可以連接到線段樹上的某個區間,那么他就可以連接這個線段樹的區間內所有的葉子節點。

如果有一個區間\([L,R]\),就可以分成線段樹上的\(\log\)個區間,之后連邊到線段樹區間就好了

點數\(O(n+m)\),邊數\(O(n\log n)\)

他 說 應 該 能 過

例題15

\(n\)個點,需要支持兩種操作:

  1. 添加一條邊,保證加完后還是森林

  2. 給定\((u,v)\),保證\((u,v)\)是一條存在的邊,求有多少點對\((x,y)\)經過了了邊\((u,v)\)

可以離線

可以把樹(森林)建出來,這時候我們的\((u,v)\)肯定出在某一個包含它的連通塊中,那么我們要求的就是這一條邊 一邊的點數 乘上 另一邊的點數,用並查集是可以很容易知道一個連通塊有幾個點的,所以只要求這條邊在連通塊中一邊的點數,就可以知道另一邊的點數

現在要求的問題是:求對於建完的這棵樹,\(v\)的子樹里有幾個點現在跟\((u,v)\)在一個連通塊中

子樹這個問題可以用\(dfs\)序解決,然后把一段連續的\(dfs\)序變成區間,比如說\(v\)子樹的區間就是\([L(v),R(v)]\),那么問題就是求在\([L(v),R(v)]\)種有多少個節點和\((u,v)\)在一個連通塊中。

用並查集維護連通塊,用線段樹記一下有幾個點(下標就是\(dfs\)序的下標),合並集合時直接進行線段樹合並,在詢問的時候進行區間詢問。

總復雜度\(O(n\log n)\)

原題鏈接:P4219 [BJOI2014]大融合

例題16

給定一個數組\(a[1…n]\),首先有\(Q\)次操作,每次會將一個區間升序排序或者降序排序
求操作后\(a[K]\)的值

二分\(a[k]\),大於\(a[k]\)標成\(1\),小於\(a[k]\)標成0

然后線段樹分裂(不會)

例題17被吃了


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM