赞
踩
线段树是用来解决区间和/区间最值/区间覆盖的问题,而本道题涉及到了区间和问题,是区间修改和区间查询的问题(单点查询和单点修改对应的就是
l
=
=
r
l==r
l==r的情况)。如果修改一个区间时,每次都修改到叶结点,那么一个叶节点的修改所需的复杂度是
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn),修改一次区间的时间复杂度是
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn),
Q
Q
Q次区间修改的时间复杂度是
O
(
n
2
l
o
g
n
)
O(n^2logn)
O(n2logn),效率甚至低于暴力。
因此,这里采用lazy-tag的形式,即修改仅针对区间整体进行操作,不去修改内部元素的具体内容,只有当区间的一致性被破坏时才把变化值传递给子区间,这样就可以维持一次操作的时间复杂度是
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn).
#include<bits/stdc++.h> #define close ios::sync_with_stdio(false);cin.tie(0);cout.tie(0) using namespace std; typedef long long ll; const int maxn=1e5+5; ll n,m,mark[maxn*4],tree[maxn*4],A[maxn]; void push_down(ll p,ll len) { mark[2*p]+=mark[p];mark[2*p+1]+=mark[p]; tree[2*p]+=mark[p]*(len-len/2); tree[2*p+1]+=mark[p]*(len/2); mark[p]=0; } void build(ll l=1,ll r=n,ll p=1) { if(l==r) tree[p]=A[l]; else{ ll mid=(l+r)/2; build(l,mid,2*p); build(mid+1,r,2*p+1); tree[p]=tree[p*2]+tree[p*2+1]; } } void update(ll l,ll r,ll d,ll p=1,ll cl=1,ll cr=n) { if(l>cr || r<cl) return; if(l<=cl && cr<=r) { tree[p]+=d*(cr-cl+1); if(cr>cl) mark[p]+=d; } else{ ll mid=(cl+cr)/2; push_down(p,cr-cl+1); update(l,r,d,p*2,cl,mid); update(l,r,d,p*2+1,mid+1,cr); tree[p]=tree[p*2]+tree[p*2+1]; } } ll query(ll l,ll r,ll p=1,ll cl=1,ll cr=n) { if(l>cr || r<cl) return 0; if(l<=cl && cr<=r) return tree[p]; else{ ll mid=(cl+cr)/2; push_down(p,cr-cl+1); return query(l,r,2*p,cl,mid)+query(l,r,2*p+1,mid+1,cr); } } int main() { close;cin>>n>>m; for(int i=1;i<=n;++i) cin>>A[i]; build(); while(m--) { int op;cin>>op; if(op==1){ int x,y,k;cin>>x>>y>>k; update(x,y,k); } else{ int x,y;cin>>x>>y; cout<<query(x,y)<<endl; } } }
这个题相较于上一题的难点在于,怎么样进行lazy-tag的传递,需要注意加减法的先后顺序问题。
#include<bits/stdc++.h> #define close ios::sync_with_stdio(false);cin.tie(0);cout.tie(0) using namespace std; typedef long long ll; const int maxn=1e5+5; ll n,m,mod,markadd[maxn*4],markmul[maxn*4],tree[maxn*4],A[maxn]; void push_down(ll p,ll len) { tree[2*p]=(tree[2*p]*markmul[p]%mod+markadd[p]*(len-len/2)%mod)%mod; tree[2*p+1]=(tree[2*p+1]*markmul[p]%mod+markadd[p]*(len/2)%mod)%mod; markmul[2*p]=markmul[2*p]*markmul[p]%mod; markmul[2*p+1]=markmul[2*p+1]*markmul[p]%mod; markadd[2*p]=(markadd[2*p]*markmul[p]%mod+markadd[p])%mod; markadd[2*p+1]=(markadd[2*p+1]*markmul[p]%mod+markadd[p])%mod; markadd[p]=0;markmul[p]=1; } void build(ll l=1,ll r=n,ll p=1) { if(l==r) tree[p]=A[l]%mod,markmul[l]=1; else{ ll mid=(l+r)/2; build(l,mid,2*p); build(mid+1,r,2*p+1); tree[p]=(tree[p*2]+tree[p*2+1])%mod; markmul[p]=1; } } void update_add(ll l,ll r,ll d,ll p=1,ll cl=1,ll cr=n) { if(l>cr || r<cl) return; if(l<=cl && cr<=r) { tree[p]=(tree[p]+d*(cr-cl+1)%mod)%mod; if(cr>cl) markadd[p]=(markadd[p]+d)%mod; } else{ ll mid=(cl+cr)/2; push_down(p,cr-cl+1); update_add(l,r,d,p*2,cl,mid); update_add(l,r,d,p*2+1,mid+1,cr); tree[p]=(tree[p*2]+tree[p*2+1])%mod; } } void update_mul(ll l,ll r,ll d,ll p=1,ll cl=1,ll cr=n) { if(l>cr || r<cl) return; if(l<=cl && cr<=r) { tree[p]=d*tree[p]%mod; if(cr>cl) markmul[p]=markmul[p]*d%mod,markadd[p]=markadd[p]*d%mod; } else{ ll mid=(cl+cr)/2; push_down(p,cr-cl+1); update_mul(l,r,d,p*2,cl,mid); update_mul(l,r,d,p*2+1,mid+1,cr); tree[p]=(tree[p*2]+tree[p*2+1])%mod; } } ll query(ll l,ll r,ll p=1,ll cl=1,ll cr=n) { if(l>cr || r<cl) return 0; if(l<=cl && cr<=r) return tree[p]%mod; else{ ll mid=(cl+cr)/2; push_down(p,cr-cl+1); return (query(l,r,2*p,cl,mid)+query(l,r,2*p+1,mid+1,cr))%mod; } } int main() { close;cin>>n>>m>>mod; for(int i=1;i<=n;++i) cin>>A[i]; build(); while(m--) { int op;cin>>op; if(op==1){ int x,y,k;cin>>x>>y>>k; update_mul(x,y,k); } else if(op==2){ int x,y,k;cin>>x>>y>>k; update_add(x,y,k); } else{ int x,y;cin>>x>>y; cout<<query(x,y)<<endl; } } }
求面积并应该是线段树一个非常重要的应用,使用了离散化的思想。核心的转化在于求解方式是 ∑ 截 线 段 的 长 度 ∗ 扫 过 的 面 积 \sum 截线段的长度*扫过的面积 ∑截线段的长度∗扫过的面积(参考的博客讲解:https://zhuanlan.zhihu.com/p/103616664)
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int maxn=2e5+100; struct ScanLine{ ll x,y1,y2,mark; ScanLine(){} ScanLine(ll x,ll y1,ll y2,ll mark){this->x=x;this->y1=y1;this->y2=y2;this->mark=mark;} bool operator < (const ScanLine& a)const {return x<a.x;} }line[maxn*2]; ll recy[maxn*2],cover[maxn*4],length[maxn*4]; ll tot; void Push_down(ll p,ll l,ll r) { if(cover[p]) length[p]=recy[r]-recy[l]; else if(l+1==r) length[p]=0; else length[p]=length[p*2]+length[p*2+1]; } void Update(ll l,ll r,ll d,ll p=1,ll cl=1,ll cr=tot) { if(l>cr || r<cl) return; if(cl>=l && cr<=r) {cover[p]+=d;Push_down(p,cl,cr);return;} if(cl+1==cr) return; else{ int mid=(cl+cr)/2; Update(l,r,d,p*2,cl,mid); Update(l,r,d,p*2+1,mid,cr); Push_down(p,cl,cr); } } int main() { int n;scanf("%d",&n);int cnt=0; for(int i=1;i<=n;++i) { ll x1,x2,y1,y2; scanf("%lld%lld%lld%lld",&x1,&y1,&x2,&y2); line[++cnt]=ScanLine(x1,y1,y2,1);recy[cnt]=y1; line[++cnt]=ScanLine(x2,y1,y2,-1);recy[cnt]=y2; } memset(length,0,sizeof(length)); memset(cover,0,sizeof(cover)); sort(line+1,line+cnt+1);sort(recy+1,recy+cnt+1); tot=unique(recy+1,recy+cnt+1)-(recy+1); ll ans=0; for(int i=1;i<cnt;++i) { int y_down=lower_bound(recy+1,recy+tot+1,line[i].y1)-recy; int y_up=lower_bound(recy+1,recy+tot+1,line[i].y2)-recy; Update(y_down,y_up,line[i].mark); ans+=length[1]*(line[i+1].x-line[i].x); } printf("%lld",ans); }
这道题表面上看着像一个模拟,但是做除法取模的时候我们要乘上他的逆元,这是模拟的时间复杂度的主要来源。但我们可以把每次操作视为一个叶结点,操作一就是把当次操作的乘数改为 n u m num num,操作二就是把操作 p o s pos pos的乘数改为1,因此每次的操作都是单点修改;然后每次输出的答案就是所有叶子结点的乘数之积,也就是区间查询。
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int maxn=1e5+100; ll Q,mod,tree[maxn*4]; void Update(ll l,ll r,ll d,ll p=1,ll cl=1,ll cr=Q) { if(l>cr||r<cl) return; if(cl>=l && cr<=r) tree[p]=d; else{ ll mid=(cl+cr)>>1; Update(l,r,d,p*2,cl,mid); Update(l,r,d,p*2+1,mid+1,cr); tree[p]=tree[p*2]*tree[p*2+1]%mod; } } int main() { int T;scanf("%d",&T); while(T--) { for(int i=1;i<maxn*4;++i) tree[i]=1; scanf("%lld%lld",&Q,&mod); for(int i=1;i<=Q;++i) { int op;ll num;scanf("%d%lld",&op,&num); if(op==1){Update(i,i,num);printf("%lld\n",tree[1]%mod);} else{Update(num,num,1);printf("%lld\n",tree[1]%mod);} } } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。