描述
小Hi和小Ho都是遊戲迷,“類比都市”是他們非常喜歡的一個遊戲,在這個遊戲裡面他們可以化身上帝模式,買賣房產。
在這個遊戲裡,會不斷的發生如下兩種事件:一種是房屋自發的漲價或者降價,而另一種是政府有關部門針對樓價的硬性調控。樓價的變化自然影響到小Hi和小Ho的決策,所以他們希望能夠知道任意時刻某個街道中所有房屋的樓價總和是多少——但是很不幸的,遊戲本身並不提供這樣的計算。不過這難不倒小Hi和小Ho,他們將這個問題抽象了一下,成為了這樣的問題:
小Hi和小Ho所關注的街道的長度為N米,從一端開始每隔1米就有一棟房屋,依次編號為0..N,在遊戲的最開始,每棟房屋都有一個初始價格,其中編號為i的房屋的初始價格為p_i,之後共計發生了M次事件,所有的事件都是對於編號連續的一些房屋發生的,其中第i次事件如果是房屋自發的漲價或者降價,則被描述為三元組(L_i, R_i, D_i),表示編號在[L_i, R_i]範圍內的房屋的價格的增量(即正數為漲價,負數為降價)為D_i;如果是政府有關部門針對樓價的硬性調控,則被描述為三元組(L_i, R_i, V_i),表示編號在[L_i, R_i]範圍內的房屋的價格全部變為V_i。而小Hi和小Ho希望知道的是——每次事件發生之後,這個街道中所有房屋的樓價總和是多少。
輸入
每個測試點(輸入檔案)有且僅有一組測試資料。
每組測試資料的第1行為兩個整數N、M,分別表示街道的長度和總共發生的事件數目。
每組測試資料的第2行為N+1個整數,其中第i個整數位p_i,表示編號為i的房屋的初始價格。
每組測試資料的第3-M+2行,按照發生的時間順序,每行描述一個事件,如果該行描述的事件為,“房屋自發的漲價或者降價”,則該行為4個整數0, L_i, R_i, D_i,意義如前文所述;如果該行描述的事件為“政府有關部門針對樓價的硬性調控”,則該行為4個整數1, L_i, R_i, V_i,意義如前文所述。
對於100%的資料,滿足N<=10^5,1<=p_i, |D_i|, V_i<=10^4,0<=l_i<r_i<=n。<>
對於100%的資料,滿足在任意時刻,任何房屋的價格都處於[1, 10^4]內。
輸出
對於每組測試資料,輸出M行,其中第i行為一個整數Ans_i,表示第i次事件發生之後,這個街道中所有房屋的樓價總和。
範例輸入
10 63195 2202 4613 3744 2892 4858 619 5079 9478 7366 8942 0 1 6 8861 0 2 97101 0 10 79800 4 9 -75940 2 8 15810 4 4 -1010
範例輸出
583047565287780422165328352273
解題思路:
首先利用給出的值構造一棵滿二叉樹,樹中每個節點代表它所維護的區間各點的和。
那麼此時,樹的根節點便是這個街道中所有房屋的樓價總和。
如果是房屋自發漲價或者降價(add),便從根節點往下,找到剛好可以維護這個區間的一個或幾個節點,標記它所維護區間[(add+x)(sum+=(r-l+1)*x)]
如果是房屋硬性調價(set),同樣,在這一個或幾個根節點中標記[set(x) (sum=(r-l+1)*x)]
需要注意的一點是,如果當前需要set的節點已經存在add,那麼這個add就會失效,而當前需要add的節點如果存在set的話並不會影響到結果
另外,如果從根節點向下尋找維護區間時,中途遇到帶有[set||add]的節點,可以想到,我們對它的子孫節點做修改的話,當前節點也會改變,所以簡單的辦法就是把當前節點的[add||set]屬性轉移到其子節點,然後重新計算這幾個節點所維護的值。
AC代碼:
#include<stdio.h>#include<string.h>#include<stdlib.h>#include<iostream>using namespace std;#define MAXX 110000typedef long ll;struct po{ ll add,set,sum; void init(int sum=0,int add=0,int set=-1) //修改屬性,當add為0時重設set為-1 { this->add=add; this->sum=sum; if(!add)this->set=set; } void addmax(int sum=0,int add=0) //屬性累加 { this->add+=add; this->sum+=sum; }} data[MAXX<<2];int LR=1; //滿二叉樹的寬度-1void init(int n){ while(LR<n)LR<<=1; //根據n計算滿二叉樹寬度 for(int i=0; i<=2*LR; i++) //初始化 data[i].init(); LR--;}void inse(int o,int k) //更新滿二叉樹最底層第o個節點,並自底而上更新其父節點的值{ o+=LR; //LR為滿二叉樹寬度-1,也是最底層第一個元素的下標 data[o].sum=k; while(o>1) { o/=2; data[o].sum=data[o*2].sum+data[o*2+1].sum; }}void update(int isset,int s,int l,int r,int ls,int lr,int o) //當前區間[l,r],目標區間[ls,lr],當前節點o,s為[add||set]的值{ if(l>lr||r<ls||r<l)return; //如果兩個區間交集為空白 if(r<=lr&&l>=ls) //如果目標區間包含當前區間,執行[set||add] { if(isset)data[o].init(s*(r-l+1),0,s); else { data[o].addmax(s*(r-l+1),s);// data[o].init(data[o].sum+s*(r-l+1),data[o].add+s); 不能寫成這樣,有一個很小很小的BUG (當data[o].add+s==0時會重設set) } return; } int m=(l+r)/2,lc=o*2,rc=lc+1; if(l!=r&&(data[o].set!=-1||data[o].add!=0)) //如果不在葉子節點並且當前節點帶有[set||add]屬性 { if(data[o].set!=-1) //帶有[set]屬性 { data[lc].init(data[o].set*(m-l+1),0,data[o].set); //轉移set到其兩個子節點 data[rc].init(data[o].set*(r-m),0,data[o].set); data[o].set=-1; //重設當前節點set } if(data[o].add!=0) //帶有[add]屬性 { data[lc].addmax((m-l+1)*data[o].add,data[o].add); //轉移add到其兩個子節點 data[rc].addmax((r-m)*data[o].add,data[o].add); data[o].add=0; //重設當前節點add } } update(isset,s,l,m,ls,lr,lc); //遞迴子節點 update(isset,s,m+1,r,ls,lr,rc); data[o].sum=data[lc].sum+data[rc].sum; //遞迴結束返回時計算父節點的值}int main(){ int k,t,n; scanf("%d%d",&n,&t); ++n; init(n); for(int i=1; i<=n; i++) { scanf("%d",&k); inse(i,k); } while(t--) { int a,b,c; scanf("%d%d%d%d",&k,&a,&b,&c); update(k,c,0,LR,a,b,1); printf("%ld\n",data[1].sum); } return 0;}