1. 程式人生 > >HDU1166+POJ3468 樹狀陣列+線段樹

HDU1166+POJ3468 樹狀陣列+線段樹

題目大意:
第一行一個整數T,表示有T組資料。
每組資料第一行一個正整數N(N<=50000),表示敵人有N個工兵營地,接下來有N個正整數,第i個正整數ai代表第i個工兵營地裡開始時有ai個人(1<=ai<=50)。
接下來每行有一條命令,命令有4種形式:
(1) Add i j,i和j為正整數,表示第i個營地增加j個人(j不超過30)
(2)Sub i j ,i和j為正整數,表示第i個營地減少j個人(j不超過30);
(3)Query i j ,i和j為正整數,i<=j,表示詢問第i到第j個營地的總人數;
(4)End 表示結束,這條命令在每組資料最後出現;
每組資料最多有40000條命令

樹狀陣列:

#include<iostream>
#include<stdio.h>
#include<string.h>
#define LL long long
using namespace std;
long long delta[50010];  
long long sum[50010];  
int n,q;
int lowbit(int x){
    return x&(-x);
}
void update(LL *array,int pos,int d){
    while(pos<=n){
        array[pos]+=d;
        pos+=lowbit(pos);
    }
}
LL query(LL *array
,int pos){ LL ans=0; while(pos>0){ ans+=array[pos]; pos-=lowbit(pos); } return ans; } int main(){ int num[50010]; int T; cin>>T; int i=1; while(T--){ cout<<"Case "<<i++<<":"<<endl; cin>>n; memset
(delta,0,sizeof(delta)); sum[0]=0; int i,j,k; for(i=1;i<=n;i++){ scanf("%d",&num[i]); sum[i]=sum[i-1]+num[i]; } char str[8]; while(scanf("%s",str)!=EOF){ int a,b; if(str[0]=='E') break; scanf("%d%d",&a,&b); if(str[0]=='Q'){ int ans=0; ans+=sum[b]-sum[a-1]; ans+=query(delta,b)-query(delta,a-1); cout<<ans<<endl; } if(str[0]=='A'){ update(delta,a,b); } if(str[0]=='S'){ update(delta,a,-b); } } } return 0; }

這道題其實不用這樣寫,這種寫法與poj3468的樹狀陣列寫法有點相似。

線段樹:

#include<iostream>
#include<string.h>
#include<stdio.h>
using namespace std;
#define MAXN 50015
int num[MAXN];
struct TREE{
    int left,right;
    int sum;
}t[4*MAXN];
void buildtree(int root,int left,int right){
    t[root].right=right;
    t[root].left=left;
    t[root].sum=0;
    if(left==right){
        t[root].sum=num[left];
        return;
    }
    int mid=(left+right)/2;
    buildtree(root*2,left,mid);
    buildtree(root*2+1,mid+1,right);
    t[root].sum=t[root*2].sum+t[root*2+1].sum;
}
int query(int root,int begin,int end){
    if(t[root].left==begin&&t[root].right==end)   
        return t[root].sum;
    int mid=(t[root].left+t[root].right)/2;
    if(end<=mid)
        return query(2*root,begin,end);
    else if(begin>mid)
        return query(2*root+1,begin,end);
    else return query(2*root,begin,mid)+query(2*root+1,mid+1,end);
}

void update(int root,int pos,int value){
    if(t[root].right==t[root].left&&t[root].left==pos){  //right==left==pos
            t[root].sum+=value;
            return;
    }
    int mid=(t[root].right+t[root].left)/2;
    if(pos<=mid)
        update(root*2,pos,value);
    else update(root*2+1,pos,value);
    t[root].sum=t[root*2].sum+t[root*2+1].sum;
}
int main(){
    int t,n,i;
    int cnt=1;
    scanf("%d",&t);
    while(t--){
        printf("Case %d:\n",cnt++);
        scanf("%d",&n);
        for(i=1;i<=n;i++)          //因為結構體是從1開始存的,所以這裡為了方便也從1開始存 
            scanf("%d",&num[i]);
        buildtree(1,1,n);
        char s[8];
        while(scanf("%s",s)!=EOF){
            if(s[0]=='E') break;
            int a,b;
            scanf("%d%d",&a,&b);
            if(s[0]=='Q'){
                int ans=query(1,a,b);
                printf("%d\n",ans); 
            }           
            if(s[0]=='A'){
                update(1,a,b);
            }
            if(s[0]=='S'){
                update(1,a,-b);
            }           
        }
    }
    return 0;
}

題目大意:
n個整數,兩種操作:詢問a-b的區間和;將a-b的每個數加上一個數c。對於詢問輸出和。

樹狀陣列寫法:

#include<iostream>
#include<stdio.h>
#include<string.h>
#define LL long long
using namespace std;
/* 設delta[i]表示[i,n]的公共增量 */
long long c1[100010];   /* 維護delta[i]的字首和 */
long long sum[100010];  
long long c2[100010];   /* 維護delta[i]*i的字首和 */
int n,q;

int lowbit(int x){
    return x&(-x);
}
void update(LL *array,int be,int d){
    while(be<=n){
        array[be]+=d;
        be+=lowbit(be);
    }
}
LL query(LL *array,int pos){
    LL ans=0;
    while(pos>0){
        ans+=array[pos];
        pos-=lowbit(pos);
    }
    return ans;
}
int main(){
    int num[100010];
    while(cin>>n>>q){
        memset(c1,0,sizeof(c1));
        memset(c2,0,sizeof(c2));
        sum[0]=0;
        int i,j,k;
        for(i=1;i<=n;i++){      
            scanf("%d",&num[i]);
            sum[i]=sum[i-1]+num[i];         
        }
        char ch;
        while(q--){
            getchar();
            scanf("%c",&ch);
            if(ch=='Q'){
                int a,b;
                LL ans=0;
                scanf("%d%d",&a,&b);
                ans=sum[b]-sum[a-1];
                ans+=(b+1)*query(c1,b)-query(c2,b);  //1-b的剩下兩部分的和       
                ans-=(a)*query(c1,a-1)-query(c2,a-1); //1-a-1的剩下兩部分的和               
                printf("%lld\n",ans);

            }
            if(ch=='C'){
                int a,b,d;
                scanf("%d%d%d",&a,&b,&d);
                update(c1,a,d);
                update(c1,b+1,-d);
                update(c2,a,a*d);
                update(c2,b+1,-d*(b+1));
            }
        }
    }
    return 0;
}

線段樹:

#include<iostream>
#include<stdio.h>
#define MAXN 100000
#define LL long long
using namespace std;
struct TREE{
    int l,r;
    LL sum;
    LL d;    //表示對某個父節點其對應的區間裡的變化量 
}t[4*MAXN+10];
LL num[MAXN+5];

//建樹 
void buildtree(LL k,LL l,LL r){
    t[k].l=l;t[k].r=r;t[k].sum=0;t[k].d=0;
    if(r==l){
        t[k].sum=num[l];   //注意是num[l]或num[r]  而不是num[k] 
        return;
    }
    LL mid=(l+r)/2;
    buildtree(k*2,l,mid);
    buildtree(2*k+1,mid+1,r);
    t[k].sum=t[k*2].sum+t[k*2+1].sum;
}

//詢問 
LL queryL(LL k,LL l,LL r){  
    if(t[k].l==l&&t[k].r==r){   //如果剛好是想要的區間 
        return t[k].sum;        //@@@語句(自己標註的,下文有用) 
    }

    //此步不能省!如果出現讓1-9每個都加3,然後詢問1-5區間和的情況,不寫會錯。
    //要保證與查詢區間相連並且在其上方的節點的d值全部為0,即這些節點全部被拆分完成,才能求出正確的和 
    if(t[k].d){                 //如果這個點對應的區間有改變數,且不是詢問的區間 
        t[2*k].d+=t[k].d;       //其子節點繼承這個區間的增量(換言之,區間拆分) 
        t[2*k].sum+=(t[2*k].r-t[2*k].l+1)*t[k].d;   //改變和 
        t[2*k+1].d+=t[k].d;
        t[2*k+1].sum+=(t[2*k+1].r-t[2*k+1].l+1)*t[k].d;
        t[k].d=0;               //由於這個區間的改變數已經計算在其子節點的sum裡,因此將其歸零 
    }


    LL ans=0;                   //遞迴函式內定義的ans,每次呼叫的函式裡面的ans都是不相同的 
    LL mid=(t[k].l+t[k].r)/2;    
    if(r<=mid) ans+=queryL(2*k,l,r);
    else if(l>mid) ans+=queryL(2*k+1,l,r);
    else {
    ans+=queryL(2*k,l,mid);
    ans+=queryL(2*k+1,mid+1,r);
    }
    return ans;
}

//更新 
void update1(LL k,LL l,LL r,LL d){
    if(t[k].l==l&&t[k].r==r){
        t[k].d+=d;      //注意是+=  而不是=  
        t[k].sum+=(t[k].r-t[k].l+1)*d;  //!!!語句 
        return;
    }
    if(t[k].l==t[k].r) return;


    //這一步也必須地寫,即每改變一次都要更新一次。
    //如果不寫,可以理解為把所有的變化量都記錄下來,等到詢問的時候再一次更新,顯然這樣有問題:
    //比如1-5區間有一個直接的增加3(即update(1,5,3)),還有一個從1-10那裡繼承來的增加6(即update(1,10,6)),
    //如果不每次更新的話,顯然對於1-5區間,有update(1,5,9) .因此對於1-5區間對應的節點sum值增加了45.
    //然而對於直接增加3,1-5區間已經增加了5*3=15(見!!!語句) 增加45現在多增加了。

    //那麼,如果把!!!語句去掉不就不會多加了嗎?
    //如果這裡去掉,那麼碰到要找的區間就不會加上它自身的改變值了(另修改程式碼單論),見@@@語句,即t[1~5].d==9,當查詢1-5時直接返回以前的sum值了

    //那麼,如果不去掉!!!語句,再其後加上一個t[k].d=0,不就既不會多加也不會少算了嗎?(即把直接的update加完後d歸零)
    //這樣就有了一個新的問題,如果再求1-2的區間和,就算不上1-5的增加3了。 

    //因此,這裡的if(t[k].d){...}不能省,如果要省必須大幅度修改程式碼,還是算了吧。  
    if(t[k].d){
        t[2*k].d+=t[k].d;
        t[2*k].sum+=t[k].d*(t[2*k].r-t[2*k].l+1);
        t[2*k+1].d+=t[k].d;
        t[2*k+1].sum+=t[k].d*(t[2*k+1].r-t[2*k+1].l+1);
        t[k].d=0;   //注意這步,如果改變數已經被拆分過了,就歸零了。因此不會出現多加的情況。 
    }

    LL mid=(t[k].l+t[k].r)/2;   
    if(r<=mid) update1(2*k,l,r,d);
    else if(l>mid) update1(2*k+1,l,r,d);
    else {
        update1(2*k,l,mid,d);
        update1(2*k+1,mid+1,r,d);
    }
    t[k].sum=t[2*k].sum+t[2*k+1].sum;  //更新父節點的和 
}

int main(){
    LL n,m;
    scanf("%lld%lld",&n,&m);
    for(LL i=1;i<=n;i++)
        scanf("%lld",&num[i]);
    buildtree(1,1,n);      //今天,你寫這句話了嗎? 日常忘記主函式建樹,日常作死。
    char ch;
    while(m--){
        getchar();
        scanf("%c",&ch);
        LL a,b,d;
        scanf("%lld%lld",&a,&b);
        if(ch=='Q'){
            printf("%lld\n",queryL(1,a,b));
        }
        else scanf("%lld",&d);
        if(ch=='C'){
            update1(1,a,b,d);           
        }
    }
    return 0;
}

思考關於 if (t[k].d) {….}能不能省的問題思考了一下午,初學線段樹感覺心態爆炸,所以提醒自己以後如果碰到類似複雜的是否可以簡化更新的問題的話,不要為了省執行時間、少寫程式碼而不去更新,很容易錯。更新也不見得超時,超了時的話再回來思考哪裡的更新可以省略才是正解~~
如果有理解不對的地方還請多多指教~~

相關推薦

HDU1166+POJ3468 陣列+線段

題目大意: 第一行一個整數T,表示有T組資料。 每組資料第一行一個正整數N(N<=50000),表示敵人有N個工兵營地,接下來有N個正整數,第i個正整數ai代表第i個工兵營地裡開始時有ai個人(1<=ai<=50)。 接下來每行有

noip提高組資料結構模板[並查集,st表,陣列,線段]

/*資料結構*/ //並查集 for(int i=1;i<=n;i++) fa[i]=i;*** int find(int x){return x==fa[x]?x:fa[x]=find(fa[x]);} //st表 for(int i=2;i<=n;i++) Log[i]=Lo

模板三連擊:陣列+線段+主席

沒事兒幹,複習模板...... 1.樹狀陣列 本來不想寫這個的,但是反正就幾分鐘就打完了,所以就寫了,水AC數。 洛谷 P3374 [模板]樹狀陣列 1 1 #include<cstdio> 2 3 typedef long long ll;

【2018ccpc區域賽網路賽】【hdu6447 YJJ's Salesman】【dp+離散化+陣列/線段優化】

連結: 分析:二維座標排序,x->大,y->小,由於我們每次走必須x,y均變大,那麼相當於只要考慮排序後的y的值。從左往右考慮y,dp[i]=max(dp[j])+val[i](i表示第i個點),由於y的資料範圍為1e9,需要離散化,然後用樹狀陣列維護求最大

hdu3887dfs序+陣列/線段

題目:http://acm.hdu.edu.cn/showproblem.php?pid=3887 大意:求在當前點的所有後代中,後代點的序號大小<當前點的序號大小,並統計他們的個數,這就是f[i]。 思路:dfs序+樹狀陣列/線段樹 dfs序:https

Apple Tree(陣列+線段

Apple Tree Description: There is an apple tree outside of kaka’s house. Every autumn, a lot of apples will grow in the tree. Kaka

HDU 1166-敵兵佈陣【陣列&&線段單點更新】【模板】

C國的死對頭A國這段時間正在進行軍事演習,所以C國間諜頭子Derek和他手下Tidy又開始忙乎了。A國在海岸線沿直線佈置了N個工兵營地,Derek和Tidy的任務就是要監視這些工兵營地的活動情況。由於採取了某種先進的監測手段,所以每個工兵營地的人數C國都掌握的一清二楚,每個工兵營地的人數都有可能發生變動,可

【動態主席】ZOJ 2112【陣列+主席

題意:       給定一個區間,求這個區間第k小的數,支援單點修改。   思路:       動態主席樹裸題。       我們先來回顧一下靜態主席樹的做法,對於陣列中每一個位置

bzoj[3881]Divljak(dfs序+陣列+fail)

這道題利用了fail樹的神奇性質————父節點為其子節點的字首 先對Alice的集合建一個fail樹, Bob每插入一個串,都將串在自動機上經過的點在樹上打上標記(+1) 每次查詢的答案就是詢問串的結束節點的子樹的貢獻 所以還需要用到樹狀陣列來維護dfs序 因為Bob的一個串至多隻能對Alice的某

【BZOJ1146】網路管理(CTSC2008)-陣列+主席

測試地址:網路管理 做法: 本題需要用到樹狀陣列+主席樹。 經典的帶修改樹上路徑第kkk大問題,不過我太菜了居然忘了有樹上主席樹這個東西… 不帶修改的話能用樹上主席樹做,那麼帶修改怎麼辦呢?因為一次修改會影響一棵子樹上的所有線段樹,所以我們還是把樹拍成DFS序,

HDU1166 敵兵佈陣 (線段陣列

C國的死對頭A國這段時間正在進行軍事演習,所以C國間諜頭子Derek和他手下Tidy又開始忙乎了。A國在海岸線沿直線佈置了N個工兵營地,Derek和Tidy的任務就是要監視這些工兵營地的活動情況。由於採取了某種先進的監測手段,所以每個工兵營地的人數C國都掌握的一清二楚,每個工兵營地的人數都有可能發生變動,

【非原創】codeforces 1070C Cloud Computing 【線段&陣列

題目:戳這裡 學習部落格:戳這裡 題意:有很多個活動,每個活動有持續天數,每個活動會在每天提供C個CPU每個CPU價格為P,問需要工作N天,每天需要K個CPU的最少花費。 解題思路:遍歷每一天,維護當前天K個cpu的最小花費。具體方法是維護兩個線段樹(樹狀陣列也可以),維護每一天可以使用的cpu數和價格

逆序對的三種求法(歸併排序,陣列線段)

求逆序對個數的三種方法 逆序對: 對於一個序列 $a_1$,$a_2$,$a_3$..$a_n$,如果存在$a_i$>$a_j$且i<j,則$a_i$和$a_j$為一個逆序對。 這裡將介紹3種求逆序對對數的方法。 在此之前,預設為你已經會了歸併排序,樹狀陣列和線段樹。(不會的可以百度學習一下)

Luogu P3258 松鼠的新家(鏈剖分+線段/陣列

題面 題解   這種題目一看就是重鏈剖分裸題,還是區間修改,單點查詢,查詢之前在遍歷時要記一個\(delta\),因為這一次的起點就是上一次的終點,不需要放糖,所以可以用\(BIT\)來寫,但我寫完\(modify\)才反應過來,所以沒改了。 #include <cstdio> #inclu

bzoj4285 使者 陣列線段

Description 公元 8192 年,人類進入星際大航海時代。在不懈的努力之下,人類佔領了 宇宙中的 n 個行星,並在這些行星之間修建了 n - 1 條星際航道,使得任意兩個 行星之間可以通過唯一的一條路徑互相到達。 同時,在宇宙中還有一些空間跳躍點,有些跳躍點已經被發現

HDU1166陣列裸題

1.題意就不在介紹,只放一張圖和AC程式碼,因為這個東西的思想和線段樹有異曲同工之妙,所以不是太懂的只需要看一下線段樹是怎麼實現的。   #include"stdafx.h" #include <iostream> #include <algorithm>

線段的可刪減性談陣列

這可能是我最後一次更新部落格了呢 # 前言 很久之前,我初學樹狀陣列的時候感覺非常的複雜、神奇、晦澀難懂。。。 果然還是我太菜了。後來瞭解到線段樹的可刪減性,這兩者就自然而然的聯絡在一起了。   # 線段樹的可刪減性 很顯然,對於一些區間操作的問題,線段樹有著絕對的優勢,基本上只要是區間

2018.11.07【CQOI2011】【BZOJ3295】【洛谷P3157】動態逆序對(陣列套動態開點線段

BZOJ傳送門 洛谷傳送門 解析: 首先我們可以通過一個線段樹求出逆序對個數,然後就是亂搞的時間了。 顯然每次刪除一個數,需要我們查詢前面比他大的數的個數和後面比他小的數的個數,這個就是裸的樹套樹了。這道題可以用樹狀陣列套線段樹動態開點。 程式碼: #

CodeForces - 652D Nested Segments(線段/陣列+離散化)

題目連結 看了大佬的部落格:https://blog.csdn.net/chenquanwei_/article/details/79137969;   題意:給n個線段的左右端點,問每個線段包括多少線段;類似於https://blog.csdn.net/weixin_4275

OpenJ_Bailian - 2352 Stars(線段/陣列

題目連結 題意:統計幾級星的個數,對每個星星來說,有多少x,y都不超過他的星星,他就是幾級星(不包括自身); 題目給的星星是按y升序,y相等的時候x升序給出的,所以只需要知道每個星星前面有多少個x不超過他的就行; 很容易想到O(n2)的演算法,但時間複雜度太高; 這裡用線段樹或者樹狀