题目链接:
https://vjudge.net/problem/HDU-1166
题目大意:
第一行一个整数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;
}
poj3468:
题目链接:
https://vjudge.net/problem/POJ-3468
题目大意:
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) {….}能不能省的问题思考了一下午,初学线段树感觉心态爆炸,所以提醒自己以后如果碰到类似复杂的是否可以简化更新的问题的话,不要为了省运行时间、少写代码而不去更新,很容易错。更新也不见得超时,超了时的话再回来思考哪里的更新可以省略才是正解~~
如果有理解不对的地方还请多多指教~~