1. 程式人生 > 實用技巧 >基礎線段樹

基礎線段樹

模板

P3373 【模板】線段樹 2

\(mtag\)為乘法標記,\(atag\)為加法標記

對於下放後的每一個區間來說,\(x=x*mtag+atag*len\)(式\(1\))

\(x=x\cdot mtag_2+len\cdot atag_2=(x\cdot mtag_2+len\cdot atag_2)\cdot mtag_1+atag_1\cdot len\)

\(=x\cdot mtag_2\cdot mtag_1+len*atag_2\cdot mtag_1+atag_1\cdot len\)

\(=x\cdot (mtag_2\cdot mtag_1)+len\cdot (atag_2\cdot mtag_1+atag_1)\)

再根據前面的式\(1\),易得

\(mtag_2 = mtag_1\cdot mtag_2\)

\(atag_2=atag_2\cdot mtag_1+atag_2\)

核心(下放)程式碼:

void pushdown(tree2 *tree,int l,int r){
	if(tree->lazym==1&&tree->lazyp==0||tree->lson==NULL) return;
	int mid = (l+r)>>1;
	tree->lson->x = (tree->lson->x*tree->lazym+(long long)tree->lazyp*(mid-l+1))%mo;
	tree->rson->x = (tree->rson->x*tree->lazym+(long long)tree->lazyp*(r-mid))%mo;
	tree->lson->lazym = (tree->lazym*tree->lson->lazym)%mo;
	tree->rson->lazym = (tree->lazym*tree->rson->lazym)%mo;
	tree->lson->lazyp = (tree->lazym*tree->lson->lazyp+tree->lazyp)%mo;
	tree->rson->lazyp = (tree->lazym*tree->rson->lazyp+tree->lazyp)%mo;
	tree->lazym  = 1;
	tree->lazyp = 0;
}

基礎練習題

P4145 上帝造題的七分鐘2 / 花神遊歷各國

P6327 區間加區間sin和

P1438 無聊的數列

P4513 小白逛公園

P4588 [TJOI2018]數學計算

P2894 [USACO08FEB]Hotel G

P4145 上帝造題的七分鐘2 / 花神遊歷各國

照題裡的這個資料範圍來看,直接暴力開方肯定會T飛

通過觀察,不難發現數據範圍內最大的數也只需要\(6\)次開方就可以變為\(1\)

考慮剪枝優化:

當一個區間的最大值為\(1\)時,其整個區間的其他值肯定也為\(1\)

因此當區間內最大值等於\(1\)時,直接\(return\)

核心程式碼:

void change(int node,int l,int r){
	int L = tree[node].l,R = tree[node].r;
	if(L==R){
		tree[node].sum = sqrt(tree[node].sum);
		tree[node].maxn = sqrt(tree[node].maxn);
		return;
	}
	int mid = (L+R)>>1;
	if(l<=mid&&tree[lson].maxn>1){//最大值大於1時在進行修改操作
			change(lson,l,r);
	}
	if(r>mid&&tree[rson].maxn>1){
			change(rson,l,r);
	}
	pushup(node);
}

P6327 區間加區間sin和

挺好的一道題目,很適合線段樹初學者練手

學過和差角公式的應該都能很快想出解法

\(sin(a+x) = sinacosx+cosasinx\)

\(cos(a+x) = cosacosx-sinxsina\)

只需要線上段樹裡維護一個\(sinx\)和一個\(cosx\)即可

核心程式碼:

void update2(int node,double sinv,double cosv){//和差角公式
	double cosa = tree[node].cosx;
        double sina = tree[node].sinx;
	tree[node].cosx = cosa*cosv-sina*sinv;
	tree[node].sinx = sina*cosv+cosa*sinv;
	return;
}
void pushdown(int node){//下放
	if(tree[node].tag){
		double cosa = cos(tree[node].tag),sina = sin(tree[node].tag);
		update2(lson,sina,cosa);//更新兒子的值
		update2(rson,sina,cosa);
		tree[lson].tag+=tree[node].tag;//更新兒子的tag
		tree[rson].tag+=tree[node].tag;
		tree[node].tag = 0;b
	}
	return;
}

void change(int node,int l,int r,int x){//更新操作
	int L = tree[node].l,R = tree[node].r;
	if(R<=r&&L>=l){//包圍在區間內,直接修改
		tree[node].tag+=x;
		update2(node,sin(x),cos(x));
		return;
	}
	pushdown(node);//下放
	int mid = (L + R)>>1;
	if(l<=mid) change(lson,l,r,x);
	if(r>mid) change(rson,l,r,x);
	update(node);//上傳更新
	return;
}

P1438 無聊的數列

利用線段樹來維護差分陣列。

每當進行一個操作\(1\)

將點\(l\)加上首相\(k\)

如果區間不是一個點的話,則將區間\([l+1,r]\)上的點都加上公差\(d\)

如果\(r<n\),則在\(r+1\)的位置上加上\(-(k+(r-l)\cdot d))\),便於差分

查詢時,將區間\([1,k]\)的值都加上即可,相當於查詢操作

區間查詢,區間修改,直接上線段樹模板即可

核心程式碼:

for(int i=1;i<=m;i++){
		int mode,l,r,k,d;
		scanf("%d",&mode);
		if(mode==1){
			scanf("%d%d%d%d",&l,&r,&k,&d);
			change(1,1,n,l,l,k);//修改左端點
			if(l!=r) change(1,1,n,l+1,r,d);//修改區間
			if(r+1<=n) change(1,1,n,r+1,r+1,-(k+(r-l)*d));//修改右端點
		}
		else{
			scanf("%d",&d);
			printf("%d\n",query(1,1,n,1,d)+a[d]);//差分陣列的值+原值
		}
	}

P4513 小白逛公園

線段樹經典題

維護一個從區間左端點開始的區間最大子段\(maxl\),從右端點開始的區間最大子段\(maxr\),總區間最大子段\(maxx\),和一個區間和\(sum\)

對於\(maxl\)來說,其右端點的位置有兩種可能:

  • 在左兒子中

  • 在右兒子中

得到方程:\(tree.maxl = max(lson.maxl,lson.sum+rson.maxl)\)

\(maxr\)也同理

方程:\(tree.maxr = max(rson.maxr,rson.sum+lson.maxr)\)

對於\(maxx\)來說,其區間範圍有三種可能

  • 只在左兒子中

  • 只在右兒子中

  • 既在左兒子中也在右兒子中

得到方程:\(tree.maxx = max(lson.maxx,rson.maxx,lson.maxr+rson.maxl))\)

查詢時只需輸出區間\([l,r]\)中的\(maxx\)即可

核心程式碼:

更新操作

void update(tree2 *tree,tree2 *lson,tree2 *rson){
	tree->sum = lson->sum+rson->sum;
	tree->maxl = max(lson->maxl,lson->sum+rson->maxl);
	tree->maxr = max(rson->maxr,rson->sum+lson->maxr);
	tree->maxX = max(lson->maxX,max(rson->maxX,lson->maxr+rson->maxl));
}

查詢操作

tree2 *query(tree2 *tree,int l,int r,int x,int y){
	if(x<=l&&y>=r)
	return tree;
	int mid = (l + r)>>1;
	tree2 *t1 = NULL,*t2 = NULL;
	if(x<=mid) t1 = query(tree->lson,l,mid,x,y);
	if(y>mid) t2 = query(tree->rson,mid+1,r,x,y);
	if(t1==NULL) return t2;
	if(t2==NULL) return t1;
	tree2 *ret = &dizhi[++t];
	update(ret,t1,t2);
	return ret;
}

P4588 [TJOI2018]數學計算

比較簡單的一道題目。

仔細觀察不難發現

所謂的操作\(1\)跟操作\(2\)其實就是在進行普通的單點修改操作而已

用一個線段樹在記錄一段區間內的總乘積

操作\(1\)是把點\(i\)的值從\(1\)修改為\(i\)

操作\(2\)則是把點\(pos\)的值修改為\(1\)

核心程式碼:

	void pushup(int node){
	tree[node].val = (tree[lson].val*tree[rson].val)%mo;
}
void build(int node,int l,int r){
	if(l==r){
		tree[node].val = 1;
		return;
	       }
	long long mid = (l + r)>>1;
	build(lson,l,mid);
	build(rson,mid+1,r);
	pushup(node);
}
void change(int node,int l,int r,int x,int y){
	if(l==r){
		tree[node].val = y;
		return;
	}
		int mid = (l + r)>>1;
		if(x<=mid) change(lson,l,mid,x,y);
		else change(rson,mid+1,r,x,y);
		pushup(node);
         }

P2894 [USACO08FEB]Hotel G

P4513 小白逛公園大同小異的思路

只是把單點修改操作換成了區間修改罷了

要注意的是這裡不存在負值的情況

因此在上傳操作時轉移沒那麼複雜,只用判斷\(maxl\)是否等於\(sum\)

若等於,說明左兒子中房間全為空,直接全部加上,再加上右兒子的\(maxl\)

若不等於,則為左兒子的\(maxl\)

\(maxr\)也同理

同時也要注意這裡的查詢查的是滿足長度為\(x\)的最左的端點

因此在查詢時要滿足"能左則左"

核心程式碼:

void pushup(int node){//上傳
	if(tree[lson].maxx==tree[lson].sum){//如果左區間全為空房
		tree[node].lmax = tree[lson].sum+tree[rson].lmax;//全部加上
	}
	else{
		tree[node].lmax = tree[lson].lmax;
	}
	if(tree[rson].maxx==tree[rson].sum){
		tree[node].rmax = tree[rson].sum+tree[lson].rmax;
	}
	else{
		tree[node].rmax = tree[rson].rmax;
	}
	tree[node].maxx = max(tree[lson].rmax+tree[rson].lmax,max(tree[lson].maxx,tree[rson].maxx));
}
void build(int node,int l,int r){
	
	tree[node].sum = tree[node].lmax = tree[node].rmax = tree[node].maxx =r-l+1;
	if(l==r){
		return;
	}
	build(lson,l,mid);
	build(rson,mid+1,r);
}
void pushdown(int node){//下放
	if(tree[node].lazy==0) return;
	if(tree[node].lazy==1){//退房
		tree[lson].maxx = tree[lson].rmax = tree[lson].lmax = 0;
		tree[rson].maxx = tree[rson].rmax = tree[rson].lmax = 0;
		tree[lson].lazy = tree[rson].lazy = 1;
	}
	if(tree[node].lazy==2){//開房
		tree[lson].maxx = tree[lson].rmax = tree[lson].lmax = tree[lson].sum;
		tree[rson].maxx = tree[rson].rmax = tree[rson].lmax = tree[rson].sum;
		tree[lson].lazy = tree[rson].lazy = 2;	
	}
	tree[node].lazy = 0;
}
void change(int node,int l,int r,int x,int y,int opt){//opt為1代表退房,為2代表開房
	
	
	if(x<=l&&y>=r){
		if(opt==1) tree[node].maxx = tree[node].lmax = tree[node].rmax = 0;
		
		else tree[node].maxx = tree[node].lmax = tree[node].rmax = tree[node].sum;
		
		tree[node].lazy = opt;
		return;
	}
	pushdown(node);
	if(x<=mid) change(lson,l,mid,x,y,opt);
	if(y>mid) change(rson,mid+1,r,x,y,opt);
	pushup(node);
}
int query(int node,int l,int r,int x){//查詢
	pushdown(node);
	if(l==r) return l;
	if(tree[lson].maxx>=x){//如果左區間的最大值大於x,直接查左邊
		return query(lson,l,mid,x);
	}
	else if(tree[lson].rmax+tree[rson].lmax>=x){//如果中間大於x
		return 1+mid-tree[lson].rmax;左兒子的右最大值,也就是最靠近左邊的端點
	} 
	else return query(rson,mid+1,r,x);//否則查右邊
}
}

動態開點

P5459 [BJOI2016]回轉壽司

CF915E Physical Education Lessons

\(now ~ loading...\)

掃描線

P5490 【模板】掃描線

P1502 視窗的星星

\(now ~ loading...\)

``