HDU - 4348 To the moon(主席树区间更新-标记永久化)
題目鏈接:點擊查看
題目大意:給出一個初始時長度為 n 的序列,有 m 次操作,每種操作分為下列四種類型:
題目分析:如果不考慮變量 t ,也就是不同版本的數組所帶來的影響,那么這就是線段樹的一道經典區間修改和區間查詢的題目,寫一個 lazy 下傳標記即可
考慮有多個不同版本的數組,也就對應了主席樹,換句話說這個題目考察的正是主席樹的區間修改和區間查詢
主席樹的單點修改比較簡單,每次只會涉及到一條鏈,即 logn 條節點,直接新建,其余的節點繼承上一個位置的即可
區間修改的話如果考慮 lazy 變量的下傳,最壞的情況下需要從根節點下傳到葉子節點,一次操作的時空復雜度都是 nlogn 級別的
那可能有的同學會說了:我就先存一下 lazy 標記,等查詢的時候再下傳不就行了?
很遺憾的說,這樣的時間復雜度單次操作也將會是 nlogn 級別的,因為不同版本的線段樹可能 lazy 標記不同,導致 nlogn 個節點的值最終都不相同,所以都需要新建才行
所以這里需要引入一個新的概念,也就是線段樹的標記永久化
對于普通線段樹的 lazy 標記下傳操作,也就是 pushdown 函數,對應過去就是:線段樹中每個節點的值都是真實的值
但標記永久化顧名思義,也就是 lazy 標記不下傳,這樣線段樹中的每個節點對應的值此時并不是真實的值,但在執行 query 操作時,一定會從其父節點下來,此時額外維護一個變量,用于記錄沿途路徑上的 lazy 的貢獻,最后統一計算一樣可以得到真實答案
但在執行標記永久化時需要注意的細節就是,不能使用 pushup 函數自底向上去傳遞貢獻,只能自頂向下去維護每個節點的貢獻,到達需要返回的位置時,打一個 lazy 標記直接返回即可,這樣的操作帶來的影響是:相應的區間上面的父節點中儲存的是真實值,而 lazy 標記下面的那些節點中存在的都是一個虛擬的值,需要與祖先節點中的 lazy 標記的貢獻進行加和才能得到真實答案
使用標記永久化的一個好處就是,每次修改仍然是至多修改 logn 個節點,這樣就能實現主席樹的區間修改的操作了
對于這個題目而言,注意一下空間大小,因為初始時需要建立一棵線段樹,這里需要 4 * n ,然后每次修改都需要新建 logn 條樹鏈,設 n 與 m 同階,最終需要新建 nlogn 個樹鏈,總的空間復雜度也就是 n ( 4 + nlogn ),logn 記為 20,所以開 25 * n 就好了
代碼:
//#pragma GCC optimize(2) //#pragma GCC optimize("Ofast","inline","-ffast-math") //#pragma GCC target("avx,sse2,sse3,sse4,mmx") #include<iostream> #include<cstdio> #include<string> #include<ctime> #include<cmath> #include<cstring> #include<algorithm> #include<stack> #include<climits> #include<queue> #include<map> #include<set> #include<sstream> #include<cassert> #include<bitset> #include<unordered_map> using namespace std;typedef long long LL;typedef unsigned long long ull;const int inf=0x3f3f3f3f;const int N=1e5+100;struct Node {int l,r;LL sum,lazy; }tree[N*25];int cnt,root[N];void build(int &k,int l,int r) {k=cnt++;tree[k].lazy=0;if(l==r){scanf("%lld",&tree[k].sum);return;}int mid=l+r>>1;build(tree[k].l,l,mid);build(tree[k].r,mid+1,r);tree[k].sum=tree[tree[k].l].sum+tree[tree[k].r].sum; }void update(int &k,int l,int r,int L,int R,LL val)//[l,r]:目標區間 [L,R]:當前區間 {tree[cnt++]=tree[k];k=cnt-1;tree[k].sum+=val*(r-l+1);if(L>=l&&R<=r){tree[k].lazy+=val;return;}int mid=L+R>>1;if(r<=mid)update(tree[k].l,l,r,L,mid,val);else if(l>mid)update(tree[k].r,l,r,mid+1,R,val);else{update(tree[k].l,l,mid,L,mid,val);update(tree[k].r,mid+1,r,mid+1,R,val);} }LL query(int k,int l,int r,int L,int R,LL add)//[l,r]:目標區間 [L,R]:當前區間 {if(R<l||L>r)return 0;if(L>=l&&R<=r)return tree[k].sum+add*(R-L+1);int mid=L+R>>1;return query(tree[k].l,l,r,L,mid,add+tree[k].lazy)+query(tree[k].r,l,r,mid+1,R,add+tree[k].lazy); }int main() { #ifndef ONLINE_JUDGE // freopen("data.in.txt","r",stdin); // freopen("data.out.txt","w",stdout); #endif // ios::sync_with_stdio(false);int n,m;while(scanf("%d%d",&n,&m)!=EOF){cnt=1;build(root[0],1,n);int time=0;while(m--){char op[5];scanf("%s",op);if(op[0]=='C'){int l,r,d;scanf("%d%d%d",&l,&r,&d);time++;root[time]=root[time-1];update(root[time],l,r,1,n,d);}else if(op[0]=='Q'){int l,r;scanf("%d%d",&l,&r);printf("%lld\n",query(root[time],l,r,1,n,0));}else if(op[0]=='H'){int l,r,t;scanf("%d%d%d",&l,&r,&t);printf("%lld\n",query(root[t],l,r,1,n,0));}else if(op[0]=='B'){scanf("%d",&time);}}}return 0; }?
總結
以上是生活随笔為你收集整理的HDU - 4348 To the moon(主席树区间更新-标记永久化)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 洛谷 - P2617 Dynamic R
- 下一篇: 牛客 - 红蓝图(克鲁斯卡尔重构树的df