您的位置:首页 > 其它

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

2014-07-12 13:53 399 查看
题目大意:动态连续和查询问题,标准的树状数组的题哈。

解题思路:直接按照树状数组模板就可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));
}
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: