线段树是用来解决区间和/区间最值/区间覆盖的问题,而本道题涉及到了区间和问题,是区间修改和区间查询的问题(单点查询和单点修改对应的就是 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
#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
#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
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
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);}
}
}
}