zoukankan      html  css  js  c++  java
  • HDU-1794 敌兵布阵(线段树单点更新求区间和)

    敌兵布阵
    Time Limit: 3000 MS Memory Limit: 32768 K
    Total Submit: 378(142 users) Total Accepted: 161(127 users) Rating: Special Judge: No
    Description
    C国的死对头A国这段时间正在进行军事演习,所以C国间谍头子Derek和他手下Tidy又开始忙乎了。A国在海岸线沿直线布置了N个工兵营地,Derek和Tidy的任务就是要监视这些工兵营地的活动情况。由于采取了某种先进的监测手段,所以每个工兵营地的人数C国都掌握的一清二楚,每个工兵营地的人数都有可能发生变动,可能增加或减少若干人手,但这些都逃不过C国的监视。
    中央情报局要研究敌人究竟演习什么战术,所以Tidy要随时向Derek汇报某一段连续的工兵营地一共有多少人,例如Derek问:“Tidy,马上汇报第3个营地到第10个营地共有多少人!”Tidy就要马上开始计算这一段的总人数并汇报。但敌兵营地的人数经常变动,而Derek每次询问的段都不一样,所以Tidy不得不每次都一个一个营地的去数,很快就精疲力尽了,Derek对Tidy的计算速度越来越不满:”你个死肥仔,算得这么慢,我炒你鱿鱼!”Tidy想:“你自己来算算看,这可真是一项累人的工作!我恨不得你炒我鱿鱼呢!”无奈之下,Tidy只好打电话向计算机专家Windbreaker求救,Windbreaker说:“死肥仔,叫你平时做多点acm题和看多点算法书,现在尝到苦果了吧!”Tidy说:”我知错了。。。”但Windbreaker已经挂掉电话了。Tidy很苦恼,这么算他真的会崩溃的,聪明的读者,你能写个程序帮他完成这项工作吗?不过如果你的程序效率不够高的话,Tidy还是会受到Derek的责骂的.
    Input
    第一行一个整数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条命令
    Output
    对第i组数据,首先输出“Case i:”和回车,
    对于每个Query询问,输出一个整数并回车,表示询问的段中的总人数,这个数保持在int以内。
    Sample Input
    1
    10
    1 2 3 4 5 6 7 8 9 10
    Query 1 3
    Add 3 6
    Query 2 7
    Sub 10 2
    Add 6 3
    Query 3 10
    End

    Sample Output
    Case 1:
    6
    33
    59

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn=5e4+9;
    int t,n,ans,a[maxn];
    struct node
    {
        int l,r,sum;
    }tre[maxn<<2];
    void build(int l,int r,int rt)
    {
        tre[rt].l=l;
        tre[rt].r=r;
        if(l==r)tre[rt].sum=a[l];
        else
        {
            int mid=(l+r)>>1;
            build(l,mid,rt<<1);
            build(mid+1,r,rt<<1|1);
            tre[rt].sum=tre[rt<<1].sum+tre[rt<<1|1].sum;
        }
    }
    void update(int pos,int val,int rt)
    {
        tre[rt].sum=tre[rt].sum+val;
        if(tre[rt].l==pos&&pos==tre[rt].r)return;
        int mid=(tre[rt].l+tre[rt].r)>>1;
        if(pos>mid) update(pos,val,rt<<1|1);
        else if(pos<=mid) update(pos,val,rt<<1);
    }
    void query(int l,int r,int rt)
    {
        if(tre[rt].l>=l&&tre[rt].r<=r)ans+=tre[rt].sum;
        else
        {
            int mid=(tre[rt].l+tre[rt].r)>>1;
            if(mid<l)query(l,r,rt<<1|1);
            else if(mid>=r)query(l,r,rt<<1);
            else
            {
                query(l,r,rt<<1);
                query(l,r,rt<<1|1);
            }
        }
    }
    int main()
    {
        int t,cas=1;
        scanf("%d",&t);
        while(t--)
        {
            scanf("%d",&n);
            for(int i=1;i<=n;i++)scanf("%d",&a[i]);
            build(1,n,1);
            char tmp[10];
            int l,r,val;
            printf("Case %d:
    ",cas++);
            while(scanf("%s",tmp)!=EOF)
            {
                if(tmp[0]=='E') break;
                if(tmp[0]=='A')
                {
                    scanf("%d%d",&l,&val);
                    update(l,val,1);
                }
                if(tmp[0]=='S')
                {
                    scanf("%d%d",&l,&val);
                    update(l,-val,1);
                }
                if(tmp[0]=='Q')
                {
                    scanf("%d%d",&l,&r);
                    ans=0;
                    query(l,r,1);
                    printf("%d
    ",ans);
                }
    
            }
        }
    }
    

    线段树基础入门,单点更新,要了解树的具体结构和运作方式,这个代码太丑了。是初学的时候写的。

    #include<stdio.h>
    #include<string.h>
    #define N 50000
    struct tree
    {
        int l,r,sum;
    } a[N*4];///一般定为最大值的四倍
    int b[50003],sums;///b存储一初始每个营地的人数,sums存储最终区间内人数和
    void build(int l,int r,int root)///建树
    {
        a[root].l=l;///从第一个节点开始,左边是1,右边区间是n
        a[root].r=r;///之后递归时会改变root(节点)的值,因此每一个左范围和右范围都被定为传入函数中的l值和r值
        if(l==r)///递归出口,当左右范围相等时,达到叶子节点,这个范围内等于当前区间范围(单点位置)所存储的值
        {
            a[root].sum=b[l];
        }
        else
        {
            int mid=(l+r)/2;///如果不是递归终点,找出左儿子和右儿子节点的范围,继续往下二分区间
            build(l,mid,root*2);///左儿子,节点为父亲节点*2,范围是l~mid中点
            build(mid+1,r,root*2+1);///右儿子,节点为父亲节点*2+1,范围是mid+1~r
            a[root].sum=a[root*2].sum+a[root*2+1].sum;///这里是建树之处的各个节点存储的总和,父亲节点等于已经建好的两个儿子节点之和
        }
    }
    void add(int i,int adds,int root)///单点更新(加)
    {
        a[root].sum=a[root].sum+adds;///一路从总和更新至叶子节点
        if(a[root].l==i&&i==a[root].r)return;
        if(i>(a[root].l+a[root].r)/2) add(i,adds,root*2+1);
        else add(i,adds,root*2);
    }
    void sub(int i,int subs,int root)///单点更新(减)
    {
        a[root].sum=a[root].sum-subs;///当前指向的父亲节点内存储的和减去某个节点被减的人数,从最上方的节点一直减到第i个指定的叶子节点
        if(a[root].l==i&&i==a[root].r)return;///直到递归至被减少人数的那个节点(叶子节点)
        if(i>(a[root].l+a[root].r)/2) sub(i,subs,root*2+1);///如果不是叶子节点(当指定营地i的值,大于mid时,找右儿子),也就是继续递归给下一个节点(右儿子的节点)中存储的和减人数,并继续递归
        else sub(i,subs,root*2);///否则就是左儿子减去相应人数
    }
    void query(int l,int r,int root)///查询
    {
        if(l<=a[root].l&&a[root].r<=r)///当前所查询节点的区间
        {///包含关系,所查询节点区间被包含在目的区间内,记得拿的是查询目的区间和节点的区间相比较,而不是找到叶子节点
            sums=sums+a[root].sum;
        }
        else
        {
            int mid = (a[root].l+a[root].r)/2;///这里是三种情况,当左值(较小值)都大于中点mid,说明完全是在右儿子区间范围的
            if(l>=mid+1)///右儿子
            {
                query(l,r,root*2+1);
            }
            else if(r<=mid)///左儿子
            {///第二种情况,当要查询区间的右值(较大值)都小于中点,说明要找到区间是左儿子范围
                query(l,r,root*2);
            }
            else///第三种情况,当要查询的区间,正好跨越中间两个节点的范围时,将同时向下搜索,这里实际的情况是,l值在表现在左儿子处,r值表现在右儿子处,上面两个条件都不符合,那么将两个儿子同时搜索
            {
                query(l,r,root*2);
                query(l,r,root*2+1);
    
            }
        }
    }
    ///依据:因为判断条件是==时是刚好卡位,那么必须传参mid,因为不可能有任何一个节点的区间符合要查询的区间
    ///包含的范围判定比==卡住更具有广泛性,因为同样情况,即使是包含最终实现时也会回到第一次找到符合条件的区间即是==情况
    ///总结: 情况1:三种情况if else if方法:递归出口采用==卡位:传参时第三种分叉情况必须传参mid和mid+1
    ///      情况2:三种情况 if else if方法:递归出口采用<=包含:传参可以是mid和mid+1,也可以是传参l r
    ///      情况3:两种if并行:递归出口采用<=包含:传参必须是l r
    int main()
    {
        int t;
        scanf("%d",&t);
        int flag=1;
        while(t--)
        {
            int n,i,j;
            scanf("%d",&n);
            for(i=1; i<=n; i++)
            {
                scanf("%d",&b[i]);
            }
            build(1,n,1);///建树
            printf("Case %d:
    ",flag++);
            char commed[7];
            while(scanf("%s",commed)!=EOF)
            {
                if(strcmp(commed,"End")==0)///结束判断
                {
                    break;
                }
                else if(strcmp(commed,"Add")==0)///单点更新(加)
                {
                    scanf("%d%d",&i,&j);
                    add(i,j,1);
                }
                else if(strcmp(commed,"Sub")==0)///单点更新(减)
                {
                    scanf("%d%d",&i,&j);
                    sub(i,j,1);
                }
                else if(strcmp(commed,"Query")==0)///查询
                {
                    scanf("%d%d",&i,&j);
                    sums=0;///求和初始化
                    query(i,j,1);///查询函数
                    printf("%d
    ",sums);
                }
            }
        }
        return 0;
    }
    
  • 相关阅读:
    ini_set /ini_get函数功能-----PHP
    【转】那个什么都懂的家伙
    word 2007为不同页插入不同页眉页脚
    August 26th 2017 Week 34th Saturday
    【2017-11-08】Linux与openCV:opencv版本查看及库文件位置等
    August 25th 2017 Week 34th Friday
    August 24th 2017 Week 34th Thursday
    August 23rd 2017 Week 34th Wednesday
    August 22nd 2017 Week 34th Tuesday
    August 21st 2017 Week 34th Monday
  • 原文地址:https://www.cnblogs.com/kuronekonano/p/11794362.html
Copyright © 2011-2022 走看看