#树状数组,线段树,分块#poj 3468 A Simple Problem with Integers

本文介绍了解决区间修改和查询问题的三种高效算法:树状数组、线段树及分块法。通过差分思想和懒标记等技术手段,实现对数组区间操作的快速响应。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目

需要满足两种操作,区间修改和区间查询


分析(树状数组)

tree[i]=a[i]−a[i−1]tree[i]=a[i]-a[i-1]tree[i]=a[i]a[i1](差分),那么容易得到:
tree[1]+tree[2]+…+tree[i]=a[i]tree[1]+tree[2]+…+tree[i]=a[i]tree[1]+tree[2]++tree[i]=a[i]这个公式
所以,只需要维护treetreetree数组就可以实现区间修改了。
如何实现区间查询呢?
我们已经推出了一个公式:
tree[1]+tree[2]+…tree[i]=a[i]tree[1]+tree[2]+…tree[i]=a[i]tree[1]+tree[2]+tree[i]=a[i]
那么,对于1到r的区间和,即为:
a[1]+a[2]+……+a[r−1]+a[r]a[1]+a[2]+……+a[r-1]+a[r]a[1]+a[2]++a[r1]+a[r]
=tree[1]+(tree[1]+tree[2])+…+(tree[1]+…+tree[r])=tree[1]+(tree[1]+tree[2])+…+(tree[1]+…+tree[r])=tree[1]+(tree[1]+tree[2])++(tree[1]++tree[r])
=(tree[1]∗r)+(tree[2]∗r−1)+…(tree[r]∗1)=(tree[1]*r)+(tree[2]*r-1)+…(tree[r]*1)=(tree[1]r)+(tree[2]r1)+(tree[r]1)
=r∗(tree[1]+tree[2]+…+tree[r])−(tree[1]∗0+tree[2]∗1+…+tree[r]∗(r−1))=r*(tree[1]+tree[2]+…+tree[r])-(tree[1]*0+tree[2]*1+…+tree[r]*(r-1))=r(tree[1]+tree[2]++tree[r])(tree[1]0+tree[2]1++tree[r](r1))
对于aaa的树状数组(差分)treetreetree,建立一个新的树状数组tree1tree1tree1使得:tree1[i]=tree[i]∗(i−1)tree1[i]=tree[i]*(i-1)tree1[i]=tree[i](i1)
之后,x到y的区间和即为:
(y∗sum(tree,y)−(x−1)∗sum(tree,x−1))−(sum(tree1,y)−sum(tree1,x−1))(y*sum(tree,y)-(x-1)*sum(tree,x-1))-(sum(tree1,y)-sum(tree1,x-1))(ysum(tree,y)(x1)sum(tree,x1))(sum(tree1,y)sum(tree1,x1))


代码(树状数组)

#include <cstdio>
typedef long long ll;
ll n,a[100001],b[100001],m,o;
ll in(){
    ll ans=0; char c=getchar(); int f=1;
    while ((c<48||c>57)&&c!='-') c=getchar();
    if (c=='-') f=-f,c=getchar();
    while (c>47&&c<58) ans=ans*10+c-48,c=getchar();
    return ans*f;
}
void add(ll x,ll k){while (x<=n) a[x]+=k,x+=-x&x;}//插入
void add1(ll x,ll k){while (x<=n) b[x]+=k,x+=-x&x;}//插入
ll sum(ll x){ll ans=0; while (x) ans+=a[x],x-=-x&x; return ans;}//求和
ll sum1(ll x){ll ans=0; while (x) ans+=b[x],x-=-x&x; return ans;}//求和
void print(ll ans){if (ans>9) print(ans/10); putchar(ans%10+48);}
int main(){
    n=in(); m=in(); ll x;
    for (register int i=1;i<=n;i++)
	    x=in(),add(i,x-o),add1(i,(i-1)*(x-o)),o=x;//初值
    while (m--){
        ll l,r; char c=getchar();
        while (c<65||c>90) c=getchar();
        if (c=='C'){
        	l=in(); r=in(); x=in();
        	add(l,x); add(r+1,-x); add1(l,x*(l-1)); add1(r+1,-x*r);//差分思想
        }
		else {
		    l=in(); r=in(); ll ans=r*sum(r)-sum1(r)-(l-1)*sum(l-1)+sum1(l-1);//前缀和
			if (ans<0) putchar('-'),ans=-ans;
			if (ans) print(ans); else putchar('0'); putchar('\n');
		}
    }
    return 0;
}

分析(线段树)

当然这道题可以用线段树去做,不过要满足区间修改和区间查询需要用懒标记(延迟标记)……


代码(线段树)

#include <cstdio>
typedef long long ll;
ll w[400001],lazy[400001],a[100001],n,m;
ll in(){
	ll ans=0; char c=getchar(); int f=1;
	while ((c<48||c>57)&&c!='-') c=getchar();
	if (c=='-') f=-f,c=getchar();
	while (c>47&&c<58) ans=ans*10+c-48,c=getchar();
	return ans*f;
}
void build(int k,int l,int r){//建线段树
	if (l==r) {w[k]=a[l]; return;}
	int mid=(l+r)>>1;
	build(k<<1,l,mid); build(k<<1|1,mid+1,r);
	w[k]=w[k<<1]+w[k<<1|1];
}
void ask(int k,int l,int r,int x,int y,ll &ans){//询问
	if (l==x&&r==y) {ans+=w[k]; return;}
	int mid=(l+r)>>1;
	if (lazy[k]){//懒标记
		w[k<<1]+=lazy[k]*(mid-l+1);
		w[k<<1|1]+=lazy[k]*(r-mid);
		lazy[k<<1]+=lazy[k];
		lazy[k<<1|1]+=lazy[k];
		lazy[k]=0;
	}
	if (y<=mid) ask(k<<1,l,mid,x,y,ans);
	else if (x>mid) ask(k<<1|1,mid+1,r,x,y,ans);
	else ask(k<<1,l,mid,x,mid,ans),ask(k<<1|1,mid+1,r,mid+1,y,ans);
}
void update(int k,int l,int r,int x,int y,int t){//更新
	if (l==x&&r==y) {w[k]+=t*(r-l+1); lazy[k]+=t; return;}
	int mid=(l+r)>>1;
	if (lazy[k]){
		w[k<<1]+=lazy[k]*(mid-l+1);
		w[k<<1|1]+=lazy[k]*(r-mid);
		lazy[k<<1]+=lazy[k];
		lazy[k<<1|1]+=lazy[k];
		lazy[k]=0;
	}
	if (y<=mid) update(k<<1,l,mid,x,y,t);
	else if (x>mid) update(k<<1|1,mid+1,r,x,y,t);
	else update(k<<1,l,mid,x,mid,t),update(k<<1|1,mid+1,r,mid+1,y,t);
	w[k]=w[k<<1]+w[k<<1|1];
}
void print(ll ans){if (ans>9) print(ans/10); putchar(ans%10+48);}
int main(){
	n=in(); m=in();
	for (int i=1;i<=n;i++) a[i]=in();
	build(1,1,n); 
	while (m--){
		char c=getchar(); while (c<65||c>90) c=getchar();
		int l=in(); int r=in();
		if (c=='C') update(1,1,n,l,r,in());
		else {
			ll ans=0; ask(1,1,n,l,r,ans);
			if (ans<0) putchar('-'),ans=-ans;
			if (ans) print(ans); else putchar('0'); putchar('\n');
		}
	}
	return 0;
}

分析(分块)

然而可以用一种时间较长但是码长短又直观的方法解决这个问题,那就是分块,采取大段维护,小段朴素的方法,时间复杂度O((n+q)logn)O((n+q)logn)O((n+q)logn)(因为超直观,就不解释了)


代码(分块)

#include <cstdio>
#include <cmath>
typedef long long ll;
ll sum[100001],add[100001],a[100001];
int l[321],r[321],n,m,t,pos[100001];
ll in(){
    ll ans=0; char c=getchar(); int f=1;
    while ((c<48||c>57)&&c!='-') c=getchar();
    if (c=='-') f=-f,c=getchar();
    while (c>47&&c<58) ans=ans*10+c-48,c=getchar();
    return ans*f;
}
void print(ll ans){if (ans>9) print(ans/10); putchar(ans%10+48);}
void update(int x,int y,int d){
	int p=pos[x],q=pos[y];
	if (p==q){
		for (register int i=x;i<=y;i++) a[i]+=d;
		sum[p]+=d*(y-x+1);
	}
	else{
		for (register int i=p+1;i<=q-1;i++) add[i]+=d;
		for (register int i=x;i<=r[p];i++) a[i]+=d;
		sum[p]+=d*(r[p]-x+1);
		for (register int i=l[q];i<=y;i++) a[i]+=d;
		sum[q]+=d*(y-l[q]+1);
	}
}
ll ask(int x,int y){
	int p=pos[x],q=pos[y];
	ll ans=0;
	if (p==q){
		for (register int i=x;i<=y;i++) ans+=a[i];
		ans+=add[p]*(y-x+1);
	}
	else {
		for (register int i=p+1;i<=q-1;i++) ans+=sum[i]+add[i]*(r[i]-l[i]+1);
		for (register int i=x;i<=r[p];i++) ans+=a[i];
		ans+=add[p]*(r[p]-x+1);
		for (register int i=l[q];i<=y;i++) ans+=a[i];
		ans+=add[q]*(y-l[q]+1);
	}
	return ans;
}
int main(){
    n=in(); m=in(); int t=sqrt(n);
    for (register int i=1;i<=n;i++) a[i]=in();
    for (register int i=1;i<=t;i++) l[i]=(i-1)*t+1,r[i]=i*t;
    if (r[t]<n) t++,l[t]=r[t-1]+1,r[t]=n;
    for (register int i=1;i<=t;i++)
    for (register int j=l[i];j<=r[i];j++) pos[j]=i,sum[i]+=a[j];
    while (m--){
        char c=getchar(); while (c<65||c>90) c=getchar();
        int x=in(); int y=in();
        if (c=='C') update(x,y,in());
        else {
            ll ans=ask(x,y);
            if (ans<0) putchar('-'),ans=-ans;
            if (ans) print(ans); else putchar('0'); putchar('\n');
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值