動態開點永久標記線段樹的區間修改查詢
阿新 • • 發佈:2019-01-05
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<algorithm> #include<queue> #define ll long long using namespace std; struct node{ long long l,r,sum; }t[10000050]; void read(ll &x) { int f=1;x=0;char c=getchar(); while(c>'9'||c<'0'){if(c=='-')f=-1;c=getchar();} while(c<='9'&&c>='0'){x=10*x+c-'0';c=getchar();} x=x*f; } ll cnt;ll now=0; ll n,m; ll lazy[10000050]; void insert(ll &now,ll l1,ll r1,ll x,ll y) { if(now==0) now=++cnt;//還沒有節點,要新建一個 t[now].sum+=y;//這個節點表示的區間含有第x位數,所以它的值(區間和)加上第x位數也就是y if(l1==r1)return;//已經更新到了子節點於是結束 int mid=(l1+r1)/2;//判斷x在當前節點下的左子節點還是右子節點 if(x<=mid)insert(t[now].l,l1,mid,x,y);//在左子節點 else insert(t[now].r,mid+1,r1,x,y); //右子節點 }//單點插入&建樹 ll pd(ll l1,ll r1,ll p,ll q) { ll r=min(r1,q); ll l=max(l1,p); return r-l+1; } void insert2(ll &now,ll l1,ll r1,ll p,ll q,ll y) { if(now==0) now=++cnt;//沒節點加一個節點 t[now].sum+=y*pd(l1,r1,p,q);//1 if(p<=l1&&q>=r1) //如果[p,q]已經包含了當前節點的全部範圍 那麼顯然當前節點中每一個值都要修改,也就是說它的所有子孫節點都改。 //而我們懶得改(會T)所以在這個範圍最大的被完全包含的節點處記錄lazy //然後顯然就不需要再改下面的了~\(≧▽≦)/~ { lazy[now]+=y; return; } int mid=(l1+r1)/2;//判斷[p,q]包含當前節點下的左子節點還是右子節點 if(p<=mid)insert2(t[now].l,l1,mid,p,q,y);//左 if(q>mid)insert2(t[now].r,mid+1,r1,p,q,y); //右 //1: //由帶有~\(≧▽≦)/~的那一段註釋易知 如果[p,q]已經包含了當前節點的全部範圍,那麼它以下的所有節點暫時不予處理 //而同樣易知它及它以上的節點不改是不行的。 //怎麼改呢??(假裝沉思) //因為是它及它以上,所以可能大於[p,q]也可能小於等於 //那麼這一段的值總共要加上[l1,r1]與[p,q]的(重合部分的資料的個數)乘以(每個數要加的數(y))//()用來斷句。 } ll findd(ll now,ll l1,ll r1,ll x,ll y) { if(l1>=x&&r1<=y) { return t[now].sum; }//如果這個區間[l1,r1]已經被所求區間[x,y]包含,那麼易知這個區間[l1,r1]的值(即區間內所有的數值的和)是[x,y]的和的一部分 //然後就可以直接把它加入到所求結果([x,y]的和)裡 //·2:lazytag呢??下面的點呢?O(∩_∩)O~ ll mid=(l1+r1)/2; if(lazy[now])//如果當前的這個節點上有lazy值 { if(!t[now].l)//因為動態開點,所以有可能這個節點有lazy值,但它根本沒有子節點 t[now].l=++cnt;//造一個 lazy[t[now].l]+=lazy[now];//有可能它之前已經有過有過lazy,又傳給它一個,那麼它對自己的子孫節點的影響(lazy)就是二次之和 t[t[now].l].sum+=lazy[now]*(mid-l1+1);//(mid-ll+1)是指左子節點的資料個數。 //由上面的操作易知如果now處有lazy,那麼它的所有子節點都會有一個(子節點區間內資料個數*lazy值)的值要加,所以查詢的時候,把它加進去,才可以進入下一層 //上面那個帶O(∩_∩)O~的問題的ans //這裡我們把下一層遞迴裡的now(也就是這裡的子節點)的值已經加好,想用的話可以直接取 //而第一次進入這個dfs時的O(∩_∩)O~語句,其區間是1到n,是肯定處理好了的 (原因見57到59行) //那麼如果 下一層遞迴裡的now(也就是這裡的子節點)已經被所求節點完全包含,那麼它的子孫節點就沒必要求了 if(!t[now].r) t[now].r=++cnt; lazy[t[now].r]+=lazy[now]; t[t[now].r].sum+=lazy[now]*(r1-mid);//這四行和上四行是沒什麼區別的因為就連程式碼我都是複製的qwq lazy[now]=0;//now的lazy已經發下去了,就把它的lazy清零,不能再發一次 } ll suml=0;//由now分出的左邊的值 ll sumr=0;//右邊 if(x<=mid)suml=findd(t[now].l,l1,mid,x,min(mid,y)); //如果所求區間的左邊小於等於mid也就是在now的左子節點有一部分值就去dfs左子節點(不管右邊怎麼樣) //而往左右子節點分的時候以mid為分界線,所以這裡的子區間右端點最大隻能是mid(要不白分了啊qwq) //當然如果所求的區間右端點比mid小,那肯定不能取mid,要不所求區間都變了 ,所以取一個min就可以了 if(y>mid)sumr=findd(t[now].r,mid+1,r1,max(mid+1,x),y); //同上 //這樣偏右部分的往右子分,偏左的 往左子分,分分分分,然後把和全加起來就完了。 return suml+sumr;//撒花~~ } int main() { ll mm; read(n);read(m); ll root=0; for(ll i=1;i<=n;i++) { read(mm); insert(root,1,n,i,mm); } for(int i=1;i<=m;i++) { ll o; read(o); if(o==1) { ll x;ll y;ll mmm; read(x);read(y);read(mmm); insert2(root,1,n,x,y,mmm); } if(o==2) { ll x;ll y; int mmm; read(x);read(y); cout<<findd(root,1,n,x,y)<<endl; } }