【poj3468】A Simple Problem with Integers 基础线段树or树状数组

       以前的博客提到过自己没怎么写过线段树。。现在要开始补起来了。

AC代码如下(一开始没看见负数读入优化写渣WA了几发。。真是无语):

<span style="font-size:18px;">#include<iostream>
#include<cstdio>
#include<cstring>
#define N 500005
#define ll long long

int n,m,icr[N],c[N][2]; ll sum[N];
int read(){
	int x=0,tmp=1; char ch=getchar();
	while (ch!='-' && (ch<'0' || ch>'9')) ch=getchar();
	if (ch=='-'){ tmp=-1; ch=getchar(); }
	while (ch>='0' && ch<='9'){ x=x*10+ch-'0'; ch=getchar(); }
	return x*tmp;
}
void pushdown(int k){
	if (icr[k]){
		int l=c[k][0],r=c[k][1],mid=(l+r)>>1;
		icr[k<<1]+=icr[k]; icr[k<<1|1]+=icr[k];
		sum[k<<1]+=(ll)icr[k]*(mid-l+1); sum[k<<1|1]+=(ll)icr[k]*(r-mid);
		icr[k]=0;
	}
}
void build(int k,int x,int y){
	c[k][0]=x; c[k][1]=y; icr[k]=0;
	if (x==y){ sum[k]=read(); return; } else{
		int mid=(x+y)>>1;
		build(k<<1,x,mid); build(k<<1|1,mid+1,y);
		sum[k]=sum[k<<1]+sum[k<<1|1];
	}
}
void ins(int k,int x,int y,int v){
	int l=c[k][0],r=c[k][1],mid=(l+r)>>1;
	if (x==l && y==r){
		icr[k]+=v; sum[k]+=(ll)v*(r-l+1); return;
	}
	pushdown(k);
	if (y<=mid) ins(k<<1,x,y,v); else
	if (x>mid) ins(k<<1|1,x,y,v); else{
		ins(k<<1,x,mid,v); ins(k<<1|1,mid+1,y,v);
	}
	sum[k]=sum[k<<1]+sum[k<<1|1];
}
ll qry(int k,int x,int y){
	int l=c[k][0],r=c[k][1],mid=(l+r)>>1;
	if (x==l && y==r) return sum[k];
	pushdown(k);
	if (y<=mid) return qry(k<<1,x,y); else
	if (x>mid) return qry(k<<1|1,x,y); else{
		ll tmp=qry(k<<1,x,mid); tmp+=qry(k<<1|1,mid+1,y);
		return tmp;
	}
}
int main(){
	while (~scanf("%d%d",&n,&m)){
		build(1,1,n); int i; char ch;
		for (i=1; i<=m; i++){
			ch=getchar(); while (ch!='Q' && ch!='C') ch=getchar();
			if (ch=='C'){
				int x=read(),y=read(),v=read(); ins(1,x,y,v);
			} else{ int x=read(),y=read(); printf("%I64d\n",qry(1,x,y)); }
		}
	}
	return 0;
}</span>

by lych

2016.1.8

-----------------------------------------------------------------------------------------------


       后来发现还有树状数组写法,补了一发(以后肯定写树状数组了才1k代码)。

       我们知道,区间修改单点查询可以用树状数组完成。假设原来的数组是a,利用一个辅助数组,比如说b,然后将a[i]转化为b[1]+b[2]+...+b[i],即a数组相当于b数组的前缀和,即将单点转化为前缀和,那么区间修改(x,y)都加上z只要b[x]+=z,b[y+1]-=z,简单推理就发现仍然满足a[i]=b[1]+b[2]+...+b[i]。于是就转化为了单点修改维护前缀和。如果是要区间查询,我们就相当于要维护前缀和的前缀和,这在bzoj上有这么一道题,具体那一道忘记了。

       实际上,令c[i]=a[1]+a[2]+...a[i]=b[1]*i+b[2]*(i-1)+...+b[i]*1=b[1]*n+b[2]*(n-1)+...+b[i]*(n-i+1)-(b[1]+b[2]+...+b[n])*(n-i),换句话说我们只要维护一个b数组的前缀和,再维护一个形如b[1]*n,b[2]*(n-1),...,b[n]*1的一个数组的前缀和就可以得到双重前缀和了。利用树状数组即可。

AC代码如下:

<span style="font-size:18px;">#include<iostream>
#include<cstdio>
#include<cstring>
#define ll long long
#define N 100005
using namespace std;

int n,m; ll c[2][N];
int read(){
	int x=0,tmp=1; char ch=getchar();
	while (ch!='-' && (ch<'0' || ch>'9')) ch=getchar();
	if (ch=='-'){ tmp=-1; ch=getchar(); }
	while (ch>='0' && ch<='9'){ x=x*10+ch-'0'; ch=getchar(); }
	return x*tmp;
}
void ins(int k,int x,ll t){
	int i; for (i=x; i<=n; i+=i&(-i)) c[k][i]+=t;
}
ll qry(int k,int x){
	ll sum=0,i; for (i=x; i; i-=i&(-i)) sum+=c[k][i]; return sum;
}
void mdy(int x,int y,ll t){
	ins(0,x,t); ins(0,y+1,-t);
	ins(1,x,t*(n-x+1)); ins(1,y+1,-t*(n-y));
}
int main(){
	while (~scanf("%d%d",&n,&m)){
		int i; char ch; memset(c,0,sizeof(c));
		for (i=1; i<=n; i++){
			int tmp=read(); mdy(i,i,(ll)tmp);
		}
		while (m--){
			ch=getchar(); while (ch!='Q' && ch!='C') ch=getchar();
			if (ch=='C'){
				int x=read(),y=read(),z=read(); mdy(x,y,(ll)z);
			} else{
				int x=read()-1,y=read();
				printf("%I64d\n",qry(1,y)-qry(0,y)*(n-y)-qry(1,x)+qry(0,x)*(n-x));
			}
		}
	}
	return 0;
}
</span>


by lych

2016.1.9

你可能感兴趣的:(线段树,tag,lazy,树状数组)