1. 程式人生 > 其它 >區間操作---樹狀陣列&&線段樹

區間操作---樹狀陣列&&線段樹

技術標籤:技術


涉及區間操作的一些套路必須要會呀

區間加減為了偷懶能不寫線段樹so我選擇樹狀陣列!!

但是區間乘除,最大值我想了想還是用線段樹分塊吧。

樹狀陣列:

這裡用網上的一張圖:

這裡灰色陣列是原本的陣列(a[i])紅色陣列則是樹狀陣列(c[i])這裡直接給出結論:

c[i]=a[i-2^k+range[1,2^k]]

k是i的二進位制位從低到高位連續0的個數

與a[i]有關的 c[i+2^(k+j)] 且 i+2^(j+k)<n

這樣就很好實現單點更改,區間查詢了。

void lowbit(int x)
{
	return x&(-x);//求2^x
}
void updata(int x,int val)//在x處加val
{
	while(x<=n)
	{
		c[i]+=val;
		x+=lowbit(x);//x在不斷變化要不斷求lowbit 
	}
}
int getsum(x)//求a[1~x]的和 
{
	int ans=0;
	while(x)
	{
		ans+=c[x];
		x-=lowbit(x); 
	} 
	return ans;
}

  

那如何進行區間查詢呢?

這裡可以用到差分陣列

比如在a=[1,5,7,2,3,7,1]則有的差分陣列d=[1,4,2,-5,1,4,-6](a[i]-a[i-1)a[0]=0,sum[d[1~i]]=a[i]

在區間x,y全部加k則可以在d[x]+k,d[y+1]-k 則在求1~i i in range[x,y]這部分割槽間的和即可得到a[i]+k。這樣就變成了單點修改區間查詢了

這樣實際上a陣列是沒有用的,’a‘則變成了d陣列,對d陣列構造樹狀陣列c

這樣就實現了區間修改,單點查詢:

單點查詢模板 https://www.luogu.com.cn/problem/P3368

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int N=5e5+6;
int n,m,q,p,w,x,ans,a[N],c[N];
int lowbit(int x)
{
	return x&(-x);
}
void update(int x,int val)//x點+val值 
{
	while(x<=n)
	{
		c[x]+=val;
		x+=lowbit(x);//x在不斷變化要不斷求lowbit 
	}
}
int getsum(int x)//求'a'[1~x]的和 
{
	int res=0;
	while(x)
	{
		res+=c[x];
		x-=lowbit(x); 
	} 
	return res;
}
int main()
{
	scanf("%d %d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		update(i,a[i]-a[i-1]);//建立差分陣列d 
	}
	for(int i=1;i<=m;i++)
	{
		scanf("%d",&x);
		if(x==1)
		{
			scanf("%d %d %d",&q,&p,&w);//q,p區間+w 
			update(q,w);
			update(p+1,-w);
		}
		else
		{
			scanf("%d",&q);
			ans=getsum(q); //求d的1~q的和即為a[q] 
			printf("%d\n",ans);
		}
	} 
	return 0;	
} 

  

那怎麼區間修改區間查詢呢?

sum[a[1~n]]=d[1]+d[1~2]+...+d[1~n]=n*d[1]+(n-1)d[2]+...+d[n]=n*d[1~n]-(0*d[1]+1*d[2]+...+(n-1)*d[n])(在樹狀數組裡就是另一種表示,這裡只是普通陣列表示)

可見兩部分變數可寫成=n*sum1[n]-sum2[n](又是區間求和了)

永遠要記住求誰的和構建誰的樹狀陣列然後再樹狀陣列求和,因為優化是樹狀陣列求和造成的

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int N=5e5+6;
int n,m,q,p,w,x,ans,a[N],c1[N],c2[N];
int lowbit(int x)
{
	return x&(-x);
}
void update(int x,int val)//x點+val值 
{
	int i=x;
	while(i<=n)
	{
		c1[i]+=val;//c1就是d陣列的樹狀陣列 
		c2[i]+=(x-1)*val;	//c2是d[i]*(i-1)的樹狀陣列 
		i+=lowbit(i);//i要不斷變化所以要不斷求lowbit 
	}
}
int getsum(int x)//求a[1~x]的和 
{
	int res=0,res1=0,res2=0,i=x;
	while(i)
	{
		res1+=c1[i]*x;//求d[1~x](sum1)根據分配律可直接求得sum1*n 
		res2+=c2[i];//求sum2 
		//res+=c[i]*x-c2[i];就是單純的求a[1~x]
		i-=lowbit(i); 
	} 
	res=res1-res2;//a[1~x]
	return res;
}
int main()
{
	scanf("%d %d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		update(i,a[i]-a[i-1]);//建立
	}
	for(int i=1;i<=m;i++)
	{
		scanf("%d",&x);
		if(x==1)
		{
			scanf("%d %d %d",&q,&p,&w);//q,p區間+w 
			update(q,w);
			update(p+1,-w);
		}
		else
		{
			scanf("%d",&q);
			ans=getsum(q)-getsum(q-1);  
			printf("%d\n",ans);
		}
	} 
	return 0;	
} 

  

線段樹整合:忘不了的線段樹開四倍

區間加減,乘除,最大值:

程式碼還是過那個模板題的

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int N=5e5+6;
int n,m,q,p,w,x,ans,a[N];
struct node
{
	int sum,add,mul;//add,mul是lazy_tag 
}t[N*4];
void push_down(int p)
{
	t[p].sum=t[p*2].sum+t[p*2+1].sum;//%mod	
	return ;
} 
void built(int p,int l,int r)
{
	t[p].add=0;
	t[p].mul=1;
	if(l==r)
	{
		t[p].sum=a[l];
		//mod;
		return ;
	}
	int mid=(l+r)/2;
	built(p*2,l,mid);
	built(p*2+1,mid+1,r); 
	push_down(p);
	return ;
}
int askmin(int p,int qx,int zx,int gl,int gr)
{
	if(qx>=gl&&zx<=gl)
	{
		return t[p].minn;
	}
	int mid=(qx+zx)/2,ans=9999999;
	if(gl<=mid)
		ans=min(ans,askmin(p*2,qx,mid,gl,gr));
	if(gr>mid)
		ans=min(ans,askmin(p*2+1,mid+1,zx,gl,gr));
        return ans;
} 
void push_tag(int p,int l,int r)
{
	if(t[p].mul!=1)//必須先更新乘法 
	{
		t[p*2].sum*=t[p].mul;//mod
		t[p*2+1].sum*=t[p].mul;
		t[p*2].add*=t[p].mul;//mod
		t[p*2+1].add*=t[p].mul; 
		t[p*2].mul*=t[p].mul;//mod
		t[p*2+1].mul*=t[p].mul;
		t[p].mul=1;
	}
	if(t[p].add)//加不會對乘的tag產生影響 
	{
		int mid=(l+r)/2;
		t[p*2].add+=t[p].add;//mod
		t[p*2+1].add+=t[p].add;
		t[p*2].sum+=(mid-l+1)*t[p].add;//mod
		t[p*2+1].sum+=(r-mid)*t[p].add;
		t[p].add=0;
	}
	return ;
}
void add(int p,int qx,int zx,int gl,int gr,int k)//區間加減
{
	if(qx>=gl&&zx<=gr)
	{
		t[p].sum+=(zx-qx+1)*k;
		t[p].add+=k;//mod
		return ;
	}
	int mid=(qx+zx)/2;
	push_tag(p,qx,zx);
	if(gl<=mid)
		add(p*2,qx,mid,gl,gr,k);
	if(gr>mid)
		add(p*2+1,mid+1,zx,gl,gr,k);
	push_down(p);
	return ;
} 
void mult(int p,int qx,int zx,int gl,int gr,int k)//區間乘 
{
	if(qx>=gl&&zx<=gr)
	{
		t[p].sum*=k;
		t[p].add*=k;
		t[p].mul*=k;//mod
		return ;
	}
	int mid=(qx+zx)/2;
	if(gl<=mid)
		mult(p*2,qx,mid,gl,gr,k);
	if(gr>mid)
		mult(p*2+1,mid+1,zx,gl,gr,k); 
}
int ask(int p,int qx,int zx,int gl,int gr)
{
	if(qx>=gl&&zx<=gr)
		return t[p].sum;//mod
	int ans=0,mid=(qx+zx)/2;
	push_tag(p,qx,zx);
	if(gl<=mid)
		ans+=ask(p*2,qx,mid,gl,gr);
	if(gr>mid)
		ans+=ask(p*2+1,mid+1,zx,gl,gr);
	return ans;
}	
int main()
{
	scanf("%d %d",&n,&m);
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]);
	built(1,1,n);
	for(int i=1;i<=m;i++)
	{
		scanf("%d",&x);
		if(x==1)
		{
			scanf("%d %d %d",&q,&p,&w);//q,p區間+w 
			add(1,1,n,q,p,w);
		}
		if(x==2)
		{
			scanf("%d",&q);  
			printf("%d\n",ask(1,1,n,q,q));
		}
	} 
	return 0;	
}