网站坏了....所以今天决定看看线段树的博客,也是边看边写边理解吧。
首先还是敌兵布阵这道对于线段树和树状数组都比较基础的题目:
之前用它写过树状数组的模板,现在决定还是用这个题写写线段树的模板(最基础的单点更新,区间查询)。
#include<iostream>
#include<cmath>
#include<cstring>
#include<iomanip>
#include<cstdio>
using namespace std;
int sum[200010];
int pushup(int x){
return sum[x*2]+sum[x*2+1];
}
void build(int id,int l,int r){
if (l==r){
scanf("%d",sum[id]);
return ;
}
else {
int mid=(l+r)/2;
build (id*2,l,mid);
build (id*2+1,mid+1,r);
pushup(id);
}
}
int query(int ql,int qr,int id,int l,int r){
if (ql<=l&&r<=qr){
return sum[id];
}
int mid=(l+r)/2;
int ans=0;
if (ql<=mid)ans+=query(ql,qr,id*2,l,mid);
else ans+=quert(ql,qr,id*2+1,mid+1,r);
return ans;
}
void update(int x,int y,int id,int l,int r){
if (l==r){
sum[id]+=y;
return ;
}
else {
int mid=(l+r)/2;
if (id<=mid){
update(id,y,id*2,l,mid);
}
else {
update(id,y,id*2+1,mid+1,r);
}
pushup(id);
}
}
int main()
{
int t;
int i,j,k,l;
scanf("%d",&t);
for(k=1;k<=t;k++)
{
printf("Case %d:\n",k);
int n;
scanf("%d",&n);
build(1,1,n);
char str[20];
int u,v;
while(scanf("%s",str)==1&&str[0]!='E'){
scanf("%d%d",&u,&v);
if(str[0]=='Q') printf("%d\n",query(u,v,1,1,n));
else if(str[0]=='A') update(u,v,1,1,n);
else update(u,-v,1,1,n);
}
}
return 0;
}
之后网站恢复了,做了一道二维树状数组的题和一个离散化+树状数组求逆序的题。
因为看到两个题都是有关与区间更新的,所以就看了一下比较有名的Lazy标记大法。
Lazy标记法的原理就是若正好处理到要查询的区间范围,即t[id].l==l&&t[id].r==r,则只对根节点处的值进行更新,并lazy记录,然后return(这样是一步剪枝的操作,避免了不必要的重复操作),然后之后再用到子节点的时候再用记录的数进行更新,也就是常说的pushdown函数。
相关题目的ac代码如下:
#include<iostream>
#include<cmath>
#include<cstring>
#include<iomanip>
#include<cstdio>
using namespace std;
struct tree{
long long int l;
long long int r;
int mid(){
return (l+r)/2;
}
};
struct tree t[400010];
long long int sum[400010];
long long int a[400010];
long long int n;
long long int q;
void pushup(long long int id){
sum[id]=sum[id*2]+sum[id*2+1];
}
void pushdown(long long int id,long long int m){
if (a[id]){
a[id*2]+=a[id];
a[id*2+1]+=a[id];
sum[id*2]+=a[id]*(m-m/2);
sum[id*2+1]+=a[id]*(m/2);
a[id]=0;
}
}
void build(long long int id,long long int l,long long int r){
t[id].l=l;
t[id].r=r;
if (l==r){
scanf("%lld",&sum[id]);
return ;
}
long long int m=t[id].mid();
build(id*2,l,m);
build(id*2+1,m+1,r);
pushup(id);
}
void update(long long int id,long long int l,long long int r,long long int val){
if (t[id].l==l&&t[id].r==r){
a[id]+=val;
sum[id]+=val*(r-l+1);
return ;
}
if (t[id].l==t[id].r)return ;
pushdown(id,t[id].r-t[id].l+1);
long long int m=t[id].mid();
if (r<=m)update(id*2,l,r,val);
else if (l>m)update(id*2+1,l,r,val);
else {
update(id*2,l,m,val);
update(id*2+1,m+1,r,val);
}
pushup(id);
}
long long int query(long long int id,long long int l,long long int r){
if (t[id].l==l&&t[id].r==r){
return sum[id];
}
pushdown(id,t[id].r-t[id].l+1);
long long int m=t[id].mid();
long long int ans=0;
if (r<=m)ans+=query(id*2,l,r);
else if (l>m)ans+=query(id*2+1,l,r);
else {
ans+=query(id*2,l,m)+query(id*2+1,m+1,r);
}
return ans;
}
int main(){
long long int i,j,k,l;
long long int xx,yy,zz;
char fun[2];
while (scanf("%lld%lld",&n,&q)!=EOF){
build(1,1,n);
while (q--){
scanf("%s",&fun);
if (fun[0]=='Q'){
scanf("%lld%lld",&xx,&yy);
printf("%lld\n",query(1,xx,yy));
}
else if (fun[0]=='C'){
scanf("%lld%lld%lld",&xx,&yy,&zz);
update(1,xx,yy,zz);
}
}
}
}
然后发现了一道类似的更新区间的题,但是继续用线段树就tle了,因为这个题是区间更新,单点查询,时间复杂度较高,所以投机取巧的选择了树状数组的另类处理方式(思路借鉴的前两天在博客里看到的题,题意大概是区间内的球涂色,问涂了几次,这个题意大概是区间开花,问有几个开花,理解了就发现这两个其实就是一个题的变式),对树状数组稍加写写就完成了这个题。