天天看點

#樹狀數組,線段樹,分塊#poj 3468 A Simple Problem with Integers題目分析(樹狀數組)代碼(樹狀數組)分析(線段樹)代碼(線段樹)分析(分塊)代碼(分塊)

題目

需要滿足兩種操作,區間修改和區間查詢

分析(樹狀數組)

設 t r e e [ i ] = a [ i ] − a [ i − 1 ] tree[i]=a[i]-a[i-1] tree[i]=a[i]−a[i−1](差分),那麼容易得到:

t r e e [ 1 ] + t r e e [ 2 ] + … + t r e e [ i ] = a [ i ] tree[1]+tree[2]+…+tree[i]=a[i] tree[1]+tree[2]+…+tree[i]=a[i]這個公式

是以,隻需要維護 t r e e tree tree數組就可以實作區間修改了。

如何實作區間查詢呢?

我們已經推出了一個公式:

t r e e [ 1 ] + t r e e [ 2 ] + … t r e e [ 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[r−1]+a[r]

= t r e e [ 1 ] + ( t r e e [ 1 ] + t r e e [ 2 ] ) + … + ( t r e e [ 1 ] + … + t r e e [ r ] ) =tree[1]+(tree[1]+tree[2])+…+(tree[1]+…+tree[r]) =tree[1]+(tree[1]+tree[2])+…+(tree[1]+…+tree[r])

= ( t r e e [ 1 ] ∗ r ) + ( t r e e [ 2 ] ∗ r − 1 ) + … ( t r e e [ r ] ∗ 1 ) =(tree[1]*r)+(tree[2]*r-1)+…(tree[r]*1) =(tree[1]∗r)+(tree[2]∗r−1)+…(tree[r]∗1)

= r ∗ ( t r e e [ 1 ] + t r e e [ 2 ] + … + t r e e [ r ] ) − ( t r e e [ 1 ] ∗ 0 + t r e e [ 2 ] ∗ 1 + … + t r e e [ 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]∗(r−1))

對于 a a a的樹狀數組(差分) t r e e tree tree,建立一個新的樹狀數組 t r e e 1 tree1 tree1使得: t r e e 1 [ i ] = t r e e [ i ] ∗ ( i − 1 ) tree1[i]=tree[i]*(i-1) tree1[i]=tree[i]∗(i−1)

之後,x到y的區間和即為:

( y ∗ s u m ( t r e e , y ) − ( x − 1 ) ∗ s u m ( t r e e , x − 1 ) ) − ( s u m ( t r e e 1 , y ) − s u m ( t r e e 1 , x − 1 ) ) (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))

代碼(樹狀數組)

#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 ) l o g n ) 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;
}