HDU-#1166 敌兵布阵 (树状数组&线段树)

题目大意:动态连续和查询问题,标准的树状数组的题哈。

解题思路:直接按照树状数组模板就可AC。但是用树状数组写的,线段树均可以写。二者不可逆,详见code。不过线段树很强大很实用的一种算法,你值得拥有。

题目来源:http://acm.hdu.edu.cn/showproblem.php?pid=1166

BIT code:

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;

const int MAXN = 50000+10;
int arr[MAXN],t,val,ai,n,l,r,k;
char opr[10];

int lowbit(int i){return i&(-i);}

void add(int i,int val){//add操作
    while(i<=n){
        arr[i]+=val;
        i+=lowbit(i);
    }
}

int sum(int i){//sum操作
    int ans=0;
    while(i>0){
        ans+=arr[i];
        i-=lowbit(i);
    }
    return ans;
}

int main()
{
    k=0;
    scanf("%d",&t);
    while(t--){
        memset(arr,0,sizeof(arr));
        scanf("%d",&n);
        for(int i=1;i<=n;i++){ //将已有的值进行存入
            scanf("%d",&val);
            add(i,val);
        }
        printf("Case %d:\n",++k);
        while(scanf("%s",&opr) && opr[0]!='E'){ //接收操作,只要不是End就执行
            scanf("%d%d",&ai,&val);
            if(opr[0]=='A')
                add(ai,val);
            else if(opr[0]=='S') //减值等于加负
                add(ai,-val);
            else
                printf("%d\n",sum(val)-sum(ai-1));//求区间和的操作,类似前缀和
        }
    }
    return 0;
}

线段树是很强大的一种算法,可以做多种算法的事,当然可以替换树状数组的所有事,特别是卡时间的时候,线段树的优势一下就体现出来了。写出来和理解起来虽然没有BIT易懂,但确实很高效,很值得学习的。线段树有两种写法,一是递归的,一是非递归的。前者容易懂,但后者更高效。下面给出两种写法的code,详见注释。

递归code:

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;

const int MAXN = 50000+10;
int st[MAXN*4];
int t,k,n,x,y,ans;
char opr[10];

void Bulid(int o,int l,int r){ //建树:自底向上递归实现
    if(l==r) scanf("%d",st+o); //接收各结点值
    else{
        int m=(l+r)/2;
        Bulid(o*2,l,m);
        Bulid(o*2+1,m+1,r);
        st[o]=st[o*2]+st[o*2+1];//本结点值为左右节点之和
    }
}

void updata(int o,int l,int r,int x,int v){
    if(l==r) st[o]+=v; //叶结点,直接更新值
    else{
        int m=(l+r)/2;
        if(x<=m) updata(o*2,l,m,x,v); //先递归更新左子树
        else  updata(o*2+1,m+1,r,x,v);//或者右子树
        st[o]=st[o*2]+st[o*2+1];  //然后计算本节点的值
    }
}

void query(int o,int l,int r,int x,int y){
    if(x<=l && y>=r) ans+=st[o];  //当前结点完全包含在查询区间内,则直接为结点值
    else{
        int m=(l+r)/2;
        if(x<=m) query(o*2,l,m,x,y); //向左查询
        if(y>m) query(o*2+1,m+1,r,x,y);  //向右查询
    }
}

int main(){
    k=1;scanf("%d",&t);
    while(t--){
        scanf("%d",&n);
        Bulid(1,1,n); //建树
        printf("Case %d:\n",k++);
        while(scanf("%s",opr),opr[0]!='E'){
            scanf("%d%d",&x,&y);
            if(opr[0]=='Q'){
                ans = 0;
                query(1,1,n,x,y);
                printf("%d\n",ans);
            }
            else{
                if(opr[0]=='S') y*=-1;
                updata(1,1,n,x,y);
            }
        }
    }
    return 0;
}

非递归code:

书上说这种写法更快些,写不出来,在网上找了下,的确要高大上一些,弄了一会没弄懂,书上也写着递归的写法一般就足够了,这种用的很少,就没深究下去了。直接copy过来了,感兴趣可以看一下。拿到OJ 测试了下,没发现时间快了,不过内存倒大了些。不过还是膜拜下大神些。。。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;

const int MAXN = 50000+10;
int c[4*MAXN],M;

void add(int k, int v)
{
    for (c[k+=M]+=v, k>>=1 ; k; k>>=1)
        if (k<M)
            c[k]=c[k << 1] + c[k << 1 | 1];//就是c[k]=c[2*k]+c[2*k+1]更快计算的一种写法
    return ;
}

int query(int s, int t)
{
    int Ans=0;
    for (s=s+M-1,t=t+M+1; s^t^1; s>>=1,t>>=1)//s^t^1表示当s和t值一样的时候执行,否则不执行,^异或的用法
    {
        if (!(s & 1)) Ans+=c[s^1];
        if (t & 1)    Ans+=c[t^1];
    }
    return Ans;
}

int main()
{
    int T;
    scanf("%d",&T);
    for (int k=1; k<=T; k++)
    {
        int n;
        scanf("%d",&n);
        M=1 << (int)(log((double)n)/log(2.0)+1);//m=2的(log2(n)+1)次方,推一下就知道。
        for (int i=0; i!=n; i++)
           scanf("%d",&c[i+M]);
        for (int i=M-1; i>=1; i--)
            c[i]=c[i << 1]+c[i << 1 | 1];//c[i]=c[2*i]+c[2*i+1]

        char q[10];
        int x,y;
        printf("Case %d:\n",k);
        while (scanf("%s",q) && strcmp(q,"End"))
        {
            scanf("%d%d",&x,&y);
            if (q[0]=='A')
               add(x-1,y);
            else
               if (q[0]=='S')
                  add(x-1,-y);
               else
                  printf("%d\n",query(x-1,y-1));
        }
    }
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值