洛谷P3373 【模板】線段樹 2
題目描述
如題,已知一個數列,你需要進行下面兩種操作:
1.將某區間每一個數加上x
2.將某區間每一個數乘上x
3.求出某區間每一個數的和
輸入輸出格式
輸入格式:
第一行包含三個整數N、M、P,分別表示該數列數字的個數、操作的總個數和模數。
第二行包含N個用空格分隔的整數,其中第i個數字表示數列第i項的初始值。
接下來M行每行包含3或4個整數,表示一個操作,具體如下:
操作1: 格式:1 x y k 含義:將區間[x,y]內每個數乘上k
操作2: 格式:2 x y k 含義:將區間[x,y]內每個數加上k
操作3: 格式:3 x y 含義:輸出區間[x,y]內每個數的和對P取模所得的結果
輸出格式:
輸出包含若幹行整數,即為所有操作3的結果。
輸入輸出樣例
輸入樣例#1:5 5 38
1 5 4 2 3
2 1 4 1
3 2 5
1 2 4 2
2 3 5 5
3 1 4
輸出樣例#1:
17
2
說明
時空限制:1000ms,128M
數據規模:
對於30%的數據:N<=8,M<=10
對於70%的數據:N<=1000,M<=10000
對於100%的數據:N<=100000,M<=100000
(數據已經過加強^_^)
題解:來敲第二發線段樹模板。
針對這樣的模板題,比只含加操作的情況又復雜一點。如果你對線段樹的操作還不熟悉,請參見線段樹模板1。網址鏈接:http://www.cnblogs.com/zk1431043937/p/7737021.html
如果你明白了線段樹模板1,相信你對這題升級版應該也有思路了吧。
在這裏先介紹一下在線段樹模板1中並未提及的懶標記的作用。
在線段樹模板1中,我們打了懶標記,懶標記的作用是提高效率,因為有些情況下並不需要真的直接完全下放懶標記下放到底。
我們可以想象一下,如果修改的區間是[1,n],不用懶標記,就會直接將約2n個節點全部遍歷一遍,如果m個操作全都是修改區間[1,n],那麽復雜度會退化到O(2MN),再乘上DFS的大常數,比直接暴力O(MN)的效率還要低下得多。
而懶標記的存在就會大大提高效率,它會將標記打在恰好能覆蓋到整個區間的一些線段上,一直累積,直到要往下遍歷時才會下放,這樣就避免了不必要的操作,使得復雜度達到O(Mlog2
好了,介紹完懶標記的作用,接下來讓我們看一看懶標記和本題的關系。
在線段樹模板1中,打的懶標記很顯然是儲存加上的數值的,那麽針對此題又能有什麽新的想法呢?
顯然,我們就需要使用兩個懶標記了。在這裏,我們采用mul作為乘法標記,add作為加法標記。
本題的整體框架與線段樹模板1基本相同,只是處理區間修改和懶標記時,針對不同的運算來分開寫。
這裏特別提一下兩個懶標記的下放運算,以免出錯。
對於加法運算,與線段樹模板1沒有太大區別,因為乘法的優先級本身比加法高,所以現在的加法運算對之前的乘法運算無影響,只需要註意取模。
對於乘法運算,因為乘法的優先級本身比加法高,所以現在的乘法運算對之前的加法運算有影響,相當於要在前面的所有運算上整體加一個小括號,然後再乘上該數。我們來分析一下:
假設當前節點的其中一個兒子節點已有mul標記和add標記,那麽將當前節點的mul標記下放時,應該使兒子節點的mul標記、add標記和區間值v都乘以當前節點的v並取模,利用乘法分配律的性質可以證出來。
還有要註意的是懶標記下放完,add清零,mul變為1。因為mul清零的話,一個數乘以0就會一直是零,這樣運算出的答案很顯然是錯誤的,因此要變為1。所以建樹時,初始化懶標記也應該這樣。
在這裏,我們就把add不為0時,看作有add標記,mul不為1時(特別註意mul是0時也有標記,因為mul為1才是初始狀態,為0是其實是有標記的),看作有mul標記,此時是需要下放的。
最後,註意如果一個節點同時有mul標記和add標記時應先下放mul標記,再下放add標記,因為乘法的優先級大於加法。
在這裏,有些人可能會疑惑如果在之前一系列的運算中乘法和加法先後順序並不是先乘後加怎麽辦?結論不就不成立了嗎?提出這樣的想法是有道理的,但其實不可能存在這樣的情況。因為我們下放mul懶標記時進行的乘法分配律運算避免了這樣的情況發生。
想明白這些,這題就完美解決了。時間復雜度為O(Mlog2N)。
下面附上代碼。
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int N=100005; 4 struct node{ 5 int l,r; long long v,mul,add; 6 }a[N<<2]; 7 int n,m,opt,x,y; 8 long long p,k; 9 void build(int u,int l,int r) 10 { 11 a[u].l=l; a[u].r=r; a[u].mul=1; a[u].add=0; 12 if (l==r) scanf("%lld",&a[u].v); 13 else 14 { 15 int mid=(l+r)>>1; 16 build(u<<1,l,mid); 17 build(u<<1|1,mid+1,r); 18 a[u].v=a[u<<1].v+a[u<<1|1].v; 19 } 20 } 21 void apply_mul(int u,long long v) 22 { 23 a[u].mul=a[u].mul*v%p; 24 a[u].add=a[u].add*v%p; 25 a[u].v=a[u].v*v%p; 26 } 27 void apply_add(int u,long long v) 28 { 29 a[u].add+=v; a[u].add%=p; 30 a[u].v+=(a[u].r-a[u].l+1)*v; a[u].v%=p; 31 } 32 void push_down(int u) 33 { 34 if (a[u].mul!=1) 35 { 36 apply_mul(u<<1,a[u].mul); 37 apply_mul(u<<1|1,a[u].mul); 38 a[u].mul=1; 39 } 40 if (a[u].add) 41 { 42 apply_add(u<<1,a[u].add); 43 apply_add(u<<1|1,a[u].add); 44 a[u].add=0; 45 } 46 } 47 void change_mul(int u,int l,int r,long long v) 48 { 49 if (a[u].l==l&&a[u].r==r) apply_mul(u,v); 50 else 51 { 52 int mid=(a[u].l+a[u].r)>>1; 53 push_down(u); 54 if (r<=mid) change_mul(u<<1,l,r,v); 55 else if (l>mid) change_mul(u<<1|1,l,r,v); 56 else change_mul(u<<1,l,mid,v),change_mul(u<<1|1,mid+1,r,v); 57 a[u].v=a[u<<1].v+a[u<<1|1].v; 58 } 59 } 60 void change_add(int u,int l,int r,long long v) 61 { 62 if (a[u].l==l&&a[u].r==r) apply_add(u,v); 63 else 64 { 65 int mid=(a[u].l+a[u].r)>>1; 66 push_down(u); 67 if (r<=mid) change_add(u<<1,l,r,v); 68 else if (l>mid) change_add(u<<1|1,l,r,v); 69 else change_add(u<<1,l,mid,v),change_add(u<<1|1,mid+1,r,v); 70 a[u].v=a[u<<1].v+a[u<<1|1].v; 71 } 72 } 73 long long query(int u,int l,int r) 74 { 75 if (a[u].l==l&&a[u].r==r) return a[u].v; 76 int mid=(a[u].l+a[u].r)>>1; 77 push_down(u); 78 if (r<=mid) return query(u<<1,l,r); 79 else if (l>mid) return query(u<<1|1,l,r); 80 return (query(u<<1,l,mid)+query(u<<1|1,mid+1,r))%p; 81 } 82 int main() 83 { 84 scanf("%d%d%lld",&n,&m,&p); 85 build(1,1,n); 86 for (int i=1;i<=m;++i) 87 { 88 scanf("%d",&opt); 89 if (opt==1) scanf("%d%d%lld",&x,&y,&k),change_mul(1,x,y,k); 90 if (opt==2) scanf("%d%d%lld",&x,&y,&k),change_add(1,x,y,k); 91 if (opt==3) scanf("%d%d",&x,&y),printf("%lld\n",query(1,x,y)); 92 } 93 return 0; 94 }View Code
洛谷P3373 【模板】線段樹 2