基礎線段樹
模板
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 / 花神遊歷各國
照題裡的這個資料範圍來看,直接暴力開方肯定會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);//否則查右邊
}
}
動態開點
CF915E Physical Education Lessons
\(now ~ loading...\)
掃描線
\(now ~ loading...\)
``