洛谷P3919題解
傳送錨點
摸魚環節
【模板】可持久化線段樹 1(可持久化陣列)
題目背景
UPDATE : 最後一個點時間空間已經放大
2021.9.18 增添一組 hack 資料 by @panyf
標題即題意
有了可持久化陣列,便可以實現很多衍生的可持久化功能(例如:可持久化並查集)
題目描述
如題,你需要維護這樣的一個長度為 $ N $ 的陣列,支援如下幾種操作
-
在某個歷史版本上修改某一個位置上的值
-
訪問某個歷史版本上的某一位置的值
此外,每進行一次操作(對於操作2,即為生成一個完全一樣的版本,不作任何改動),就會生成一個新的版本。版本編號即為當前操作的編號(從1開始編號,版本0表示初始狀態陣列)
輸入格式
輸入的第一行包含兩個正整數 $ N, M $, 分別表示陣列的長度和操作的個數。
第二行包含$ N $個整數,依次為初始狀態下陣列各位的值(依次為 $ a_i \(,\) 1 \leq i \leq N $)。
接下來$ M \(行每行包含3或4個整數,代表兩種操作之一(\) i $為基於的歷史版本號):
-
對於操作1,格式為$ v_i \ 1 \ {loc}_i \ {value}i \(,即為在版本\) v_i $的基礎上,將 $ a_i} $ 修改為 $ {value}_i $。
-
對於操作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.儲存資訊
- 儲存樹的資訊可選擇結構體(
簡單直觀)。 - 用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\)