洛谷 P3919 可持久化線段樹 1 之主席樹模板(初級)

Nightmares_oi發表於2024-08-19

洛谷P3919題解


傳送錨點


摸魚環節

【模板】可持久化線段樹 1(可持久化陣列)

題目背景

UPDATE : 最後一個點時間空間已經放大

2021.9.18 增添一組 hack 資料 by @panyf

標題即題意

有了可持久化陣列,便可以實現很多衍生的可持久化功能(例如:可持久化並查集)

題目描述

如題,你需要維護這樣的一個長度為 $ N $ 的陣列,支援如下幾種操作

  1. 在某個歷史版本上修改某一個位置上的值

  2. 訪問某個歷史版本上的某一位置的值

此外,每進行一次操作(對於操作2,即為生成一個完全一樣的版本,不作任何改動),就會生成一個新的版本。版本編號即為當前操作的編號(從1開始編號,版本0表示初始狀態陣列)

輸入格式

輸入的第一行包含兩個正整數 $ N, M $, 分別表示陣列的長度和操作的個數。

第二行包含$ N $個整數,依次為初始狀態下陣列各位的值(依次為 $ a_i \(,\) 1 \leq i \leq N $)。

接下來$ M \(行每行包含3或4個整數,代表兩種操作之一(\) i $為基於的歷史版本號):

  1. 對於操作1,格式為$ v_i \ 1 \ {loc}_i \ {value}i \(,即為在版本\) v_i $的基礎上,將 $ a_i} $ 修改為 $ {value}_i $。

  2. 對於操作2,格式為$ v_i \ 2 \ {loc}i \(,即訪問版本\) v_i $中的 $ a_i} $的值,注意:生成一樣版本的物件應為 \(v_i\)

輸出格式

輸出包含若干行,依次為每個操作2的結果。

樣例 #1

樣例輸入 #1

5 10
59 46 14 87 41
0 2 1
0 1 1 14
0 1 1 57
0 1 1 88
4 2 4
0 2 5
0 2 4
4 2 1
2 2 2
1 1 5 91

樣例輸出 #1

59
87
41
87
88
46

提示

資料規模:

對於30%的資料:$ 1 \leq N, M \leq {10}^3 $

對於50%的資料:$ 1 \leq N, M \leq {10}^4 $

對於70%的資料:$ 1 \leq N, M \leq {10}^5 $

對於100%的資料:$ 1 \leq N, M \leq {10}^6, 1 \leq {loc}_i \leq N, 0 \leq v_i < i, -{10}^9 \leq a_i, {value}_i \leq {10}^9$

經測試,正常常數的可持久化陣列可以透過,請各位放心

資料略微兇殘,請注意常數不要過大

另,此題I/O量較大,如果實在TLE請注意I/O最佳化

詢問生成的版本是指你訪問的那個版本的複製

樣例說明:

一共11個版本,編號從0-10,依次為:

* 0 : 59 46 14 87 41

* 1 : 59 46 14 87 41

* 2 : 14 46 14 87 41

* 3 : 57 46 14 87 41

* 4 : 88 46 14 87 41

* 5 : 88 46 14 87 41

* 6 : 59 46 14 87 41

* 7 : 59 46 14 87 41

* 8 : 88 46 14 87 41

* 9 : 14 46 14 87 41

* 10 : 59 46 14 87 91


不得不說,發明主席樹的HJT可真是個添柴
這道可持久化線段樹的板子,今天也是有空幹掉了(絕不是之前懶得寫)。其小名主席樹也是OIer皆知,so what is 主席樹?說白了就是完成對某一時刻資料的修改與查詢(起碼這道題是這樣),此時就需要將這些狀態全快取下來,就可以達到目的了。


正片開始

如此持久特別的主席樹會不會很難寫?The answer is YES,線段樹不難寫嗎? NO,基本實現與線段樹無異,只是多了個儲存版本的過程。

1.儲存資訊

  1. 儲存樹的資訊可選擇結構體(簡單直觀)。
  2. 用root陣列儲存版本資訊(初始版本編號為0)。

2.建樹部分

與一般建樹無異,需要處理出節點編號個數。

code:

void build(int &u,int l,int r)
{
    u=++top;
    if(l==r){tree[u].val=a[l];return;}
    int mid=(l+r)>>1;
    build(tree[u].l,l,mid);
    build(tree[u].r,mid+1,r);
}

3.更新部分

對於每次更新,都需要新建版本節點。

code:

int add(int u)
{
    top++;
    tree[top]=tree[u];//將原先節點資訊傳遞給新建節點
    return top;
}
void update(int &u,int l,int r,int x,int v)
{
    u=add(u);//對於修改部分新建節點
    if(l==r){tree[u].val=v;return;}
    int mid=(l+r)>>1;
    if(x<=mid){update(tree[u].l,l,mid,x,v);}
    else {update(tree[u].r,mid+1,r,x,v);}
}

4.詢問部分

這與線段樹詢問一毛一樣。

code:

int query(int u,int l,int r,int x)
{
    if(l==r) return tree[u].val;
    int mid=(l+r)>>1;
    if(x<=mid) return query(tree[u].l,l,mid,x);
    else return query(tree[u].r,mid+1,r,x);
}

5.主體部分

直接見程式碼。

code:

build(root[0],1,n);//root陣列儲存版本根節點編號
for(int i=1;i<=m;i++)
{
    int rt,op,x,y;scanf("%d%d%d",&rt,&op,&x);
    if(op==1){scanf("%d",&y);root[i]=root[rt];update(root[i],1,n,x,y);}//操作一,i為版本編號,root[i]儲存版本資訊
    else{printf("%d\n",query(root[rt],1,n,x));root[i]=root[rt];}//操作二與操作一同理
}

6.注意!!警鐘長鳴!!

1.陣列大小

由於需要多次新建節點,所以陣列大小盡量開大,同時不要忘記tree需要開四倍空間。

2.TLE問題

由於測試點出現毒瘤良心資料,所以在讀入與輸出時,選擇快讀快輸或是\(scanf\) \(printf\)


完整程式碼

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e7-520;
int n,m;
int top,root[N],a[N];
struct node
{
    int l,r,val;
}tree[N*4];
int add(int u)
{
    top++;
    tree[top]=tree[u];
    return top;
}
void build(int &u,int l,int r)
{
    u=++top;
    if(l==r){tree[u].val=a[l];return;}
    int mid=(l+r)>>1;
    build(tree[u].l,l,mid);
    build(tree[u].r,mid+1,r);
}
void update(int &u,int l,int r,int x,int v)
{
    u=add(u);
    if(l==r){tree[u].val=v;return;}
    int mid=(l+r)>>1;
    if(x<=mid){update(tree[u].l,l,mid,x,v);}
    else {update(tree[u].r,mid+1,r,x,v);}
}
int query(int u,int l,int r,int x)
{
    if(l==r) return tree[u].val;
    int mid=(l+r)>>1;
    if(x<=mid) return query(tree[u].l,l,mid,x);
    else return query(tree[u].r,mid+1,r,x);
}
int main()
{
	scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    build(root[0],1,n);//root陣列儲存版本根節點編號
    for(int i=1;i<=m;i++)
    {
        int rt,op,x,y;scanf("%d%d%d",&rt,&op,&x);
        if(op==1){scanf("%d",&y);root[i]=root[rt];update(root[i],1,n,x,y);}
        else{printf("%d\n",query(root[rt],1,n,x));root[i]=root[rt];}
    }
	return 0;
} 

完結收工!!!!!

個人主頁

看完點贊,養成習慣

\(\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\)

相關文章