线段树操作
线段树
线段树本质上的区间操作是把区间分解为一个个区间的分别操作。
如:对于操作\([2,8]\),分解为\([2,2],[3,4],[5,8]\)
对于线段树中的懒标记 (lazytag
) 实质上是在一个父亲上整体做的操作而儿子还未进行的操作。
模板线段树2
#include<algorithm>
#include<cstdio>
#include<iostream>
using namespace std;
typedef long long ll;
const ll N=1e5+10;
ll n,m,mod,dat[4*N],a[N],taga[4*N],tagm[4*N];
void build(ll p,ll l,ll r) {
tagm[p]=1;
if(l==r) {
dat[p]=a[l];
return ;
}
ll mid=(l+r)/2;
build(p*2,l,mid);
build(p*2+1,mid+1,r);
dat[p]=(dat[p*2]+dat[p*2+1])%mod;
}
void mul(ll p,ll l,ll r,ll v) {
tagm[p]=tagm[p]*v%mod;
taga[p]=taga[p]*v%mod;
dat[p]=dat[p]*v%mod;
}
void add(ll p,ll l,ll r,ll v) {
taga[p]=(taga[p]+v+mod)%mod;
dat[p]=(dat[p]+v*(r-l+1)+mod)%mod;
}
void pushdown(ll p,ll l,ll r) {
ll mid=(l+r)/2;
mul(p*2,l,mid,tagm[p]);
mul(p*2+1,mid+1,r,tagm[p]);
add(p*2,l,mid,taga[p]);
add(p*2+1,mid+1,r,taga[p]);
tagm[p]=1;
taga[p]=0;
}
void modify_add(ll p,ll l,ll r,ll x,ll y,ll v) {
if(x<=l&&r<=y) {
add(p,l,r,v);
return;
}
pushdown(p,l,r);
ll mid=(l+r)/2;
if(x<=mid) modify_add(p*2,l,mid,x,y,v);
if(y>mid) modify_add(p*2+1,mid+1,r,x,y,v);
dat[p]=(dat[p*2]+dat[p*2+1])%mod;
}
void modify_mul(ll p,ll l,ll r,ll x,ll y,ll v) {
if(x<=l&&r<=y) {
mul(p,l,r,v);
return;
}
pushdown(p,l,r);
ll mid=(l+r)/2;
if(x<=mid) modify_mul(p*2,l,mid,x,y,v);
if(y>mid) modify_mul(p*2+1,mid+1,r,x,y,v);
dat[p]=(dat[p*2]+dat[p*2+1])%mod;
}
ll query(ll p,ll l,ll r,ll x,ll y) {
if(x<=l&&r<=y) return dat[p]%mod;
pushdown(p,l,r);
ll mid=(l+r)/2;
ll res=0;
if(x<=mid) res+=query(p*2,l,mid,x,y);
if(y>mid) res+=query(p*2+1,mid+1,r,x,y);
return res%mod;
}
int main() {
scanf("%lld %lld %lld",&n,&m,&mod);
for(ll i=1; i<=n; i++) scanf("%lld",&a[i]);
build(1,1,n);
for(ll i=1,opt,x,y,v; i<=m; i++) {
scanf("%lld %lld %lld",&opt,&x,&y);
if(opt==1) {
scanf("%lld",&v);
modify_mul(1,1,n,x,y,v);
} else if(opt==2) {
scanf("%lld",&v);
modify_add(1,1,n,x,y,v);
} else {
printf("%lld\n",query(1,1,n,x,y)%mod);
}
}
return 0;
}
此处有两个tag
,分别为加法tag
和乘法tag
。
当父亲做乘法时,加法tag
和乘法tag
同时乘。
当父亲做加法是,加法tag
加。
儿子=儿子\(\times tagmul + tagadd\).
花神游历各国
这一题不支持区间修改的操作,因为\(\sqrt{a}+\sqrt{b}\neq \sqrt{a+b}\).
但是因为原题没有修改操作,所以单个数开根再开根到1的次数并不多。
直接每一个数分别开根即可。
这个时候维护区间最大值,若是区间最大值为1,那么就不用进行开根操作了。
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<iostream>
using namespace std;
typedef long long ll;
const int N=1e5+10;
ll a[N],sum[4*N],mx[4*N];
int n,m;
void build(int p,int l,int r) {
if(l==r) {
sum[p]=mx[p]=a[l];
return ;
}
int mid=(l+r)/2;
build(p*2,l,mid);
build(p*2+1,mid+1,r);
mx[p]=max(mx[p*2],mx[p*2+1]);
sum[p]=sum[p*2]+sum[p*2+1];
}
void modify(int p,int l,int r,int x,int y) {
if(l==r) {
sum[p]=mx[p]=sqrt(sum[p]);
return ;
}
int mid=(l+r)/2;
if(x<=mid&&mx[p*2]>1) modify(p*2,l,mid,x,y);
if(y>mid&&mx[p*2+1]>1) modify(p*2+1,mid+1,r,x,y);
mx[p]=max(mx[p*2],mx[p*2+1]);
sum[p]=sum[p*2]+sum[p*2+1];
}
ll query(int p,int l,int r,int x,int y){
if(x<=l&&r<=y) return sum[p];
int mid=(l+r)/2;
ll res=0;
if(x<=mid) res+=query(p*2,l,mid,x,y);
if(y>mid) res+=query(p*2+1,mid+1,r,x,y);
return res;
}
int main() {
scanf("%d",&n);
for(int i=1; i<=n; i++) scanf("%lld",&a[i]);
build(1,1,n);
scanf("%d",&m);
for(; m; m--) {
int opt,x,y;
scanf("%d %d %d",&opt,&x,&y);
if(x>y) swap(x,y);
if(opt==0) modify(1,1,n,x,y);
else printf("%lld\n",query(1,1,n,x,y));
}
return 0;
}
Segment Tree Beats
可实现区间 chkmin 操作:对于\(a_i(l\le i \le r)\),将 \(a_i\) 赋值为 \(\min(a_i,k)\).
还可实现区间求和,区间加.
操作如下:每个节点维护几个值,最大值 \(mx\), 严格次大值 \(se\), 最大值个数 \(cnt\), 区间和 \(sum\).
对于一节点进行 chkmin 操作,分类讨论:
若 \(mx\le k\) 对该节点无影响.
若 \(se\le k<mx\) 仅对最大值有影响,\(sum\) 减去 \(cnt\times (mx-k)\)
若 \(k<se\) 递归两个子树处理完后 pushup 即可.
如果还要实现区间加操作,设有两个tag,\((x,y)\),表示 chkmin x 再 +y.
考虑怎么合并 \((x,y)\), \((x_1,y_1)\), 合并为 \((\min(x,x_1-y),y+y_1)\).
#include<algorithm>
#include<cstdio>
#include<iostream>
using namespace std;
const int N=1e6+10;
int n,m,v1[N*4],cnt[N*4],tag[N*4],v2[N*4],sum[N*4],a[N];
int ls(int p) {return p*2;}
int rs(int p) {return p*2+1;}
void pushup(int p) {
if(v1[ls(p)]>v1[rs(p)]) {
v1[p]=v1[ls(p)];
v2[p]=max(v2[ls(p)],v1[rs(p)]);
cnt[p]=cnt[ls(p)];
} else if(v1[ls(p)]<v1[rs(p)]) {
v1[p]=v1[rs(p)];
v2[p]=max(v2[ls(p)],v1[rs(p)]);
cnt[p]=cnt[rs(p)];
} else {
v1[p]=v1[ls(p)];
v2[p]=max(v2[ls(p)],v2[rs(p)]);
cnt[p]=cnt[ls(p)]+cnt[rs(p)];
}
sum[p]=sum[ls(p)]+sum[rs(p)];
}
void build(int p,int l,int r) {
tag[p]=-1;
if(l==r) {
v1[p]=sum[p]=a[l];
v2[p]=-1; cnt[p]=1;
return ;
}
int mid=(l+r)/2;
build(ls(p),l,mid); build(rs(p),mid+1,r);
pushup(p);
}
void addtag(int p,int k) {
if(v1[p]<=k) return ;
sum[p]-=cnt[p]*(v1[p]-k);
v1[p]=tag[p]=k;
}
void pushdown(int p) {
if(!tag[p]==-1) {
addtag(ls(p),tag[p]);
addtag(rs(p),tag[p]);
tag[p]=-1;
}
}
void update(int p,int l,int r,int x,int y,int k) {
if(v1[p]<=k) return ;
if(x<=l&&r<=y&&k>=v2[p]) {addtag(p,k); return ;}
pushdown(p);
int mid=(l+r)/2;
if(x<=mid) update(ls(p),l,mid,x,y,k);
if(y>mid) update(rs(p),mid+1,r,x,y,k);
pushup(p);
}
int query(int p,int l,int r,int x,int y) {
if(x<=l&&r<=y) return sum[p];
int mid=(l+r)/2,ans=0;
pushdown(p);
if(x<=mid) ans+=query(ls(p),l,mid,x,y);
if(y>mid) ans+=query(rs(p),mid+1,r,x,y);
return ans;
}
int main() {
scanf("%d%d",&n,&m);
for(int i=1; i<=n; i++) scanf("%d",&a[i]);
build(1,1,n);
for(int i=1,x,y,z,k; i<=m; i++) {
scanf("%d%d%d",&x,&y,&z);
if(x==0) {
scanf("%d",&k);
update(1,1,n,y,z,k);
} else printf("%d\n",query(1,1,n,y,z));
}
return 0;
}
线段树合并
对于两个动态开点的线段树a,b,我们要怎么把b合并到a呢?
我们先从根节点开始递归:
若该节点a有,b无,该节点不变.
若该节点b有,a无,该节点赋值为b的该节点。
若a有,b有,递归左右子树。
若已经递归到叶子结点,将两个节点的值相加,update即可。
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
const int N=1e5+10;
int tot,head[N],ver[2*N],nxt[2*N];
int lc[60*N],rc[60*N],d[60*N],t[60*N];
int top[N],fa[N],deep[N],son[N],sum[N],qx[N],qy[N],qz[N],ans[N];
int n,m,rt[N],cnt,R,num;
void addedge(int x,int y) {
ver[++tot]=y;
nxt[tot]=head[x];
head[x]=tot;
}
void dfs1(int u) {
sum[u]=1; int maxx=-1;
for(int i=head[u]; i; i=nxt[i]) {
int v=ver[i];
if(!deep[v]) {
deep[v]=deep[u]+1; fa[v]=u;
dfs1(v); sum[u]+=sum[v];
if(sum[v]>maxx) {maxx=sum[v]; son[u]=v;}
}
}
}
void dfs2(int u,int topf) {
top[u]=topf;
if(!son[u]) return ;
dfs2(son[u],topf);
for(int i=head[u]; i; i=nxt[i]) {
int v=ver[i];
if(!top[v]) dfs2(v,v);
}
}
int lca(int u,int v) {
while(top[u]!=top[v]) {
if(deep[top[u]]<deep[top[v]]) swap(u,v);
u=fa[top[u]];
}
if(deep[u]<deep[v]) return u;
else return v;
}
void pushup(int p) {
if(d[lc[p]]>=d[rc[p]]) {
d[p]=d[lc[p]]; t[p]=t[lc[p]];
} else {
d[p]=d[rc[p]]; t[p]=t[rc[p]];
}
}
int modify(int p,int x,int y,int pos,int val) {
if(!p) p=++cnt;
if(x==y) {d[p]+=val; t[p]=x; return p;}
int mid=(x+y)/2;
if(pos<=mid) lc[p]=modify(lc[p],x,mid,pos,val);
else rc[p]=modify(rc[p],mid+1,y,pos,val);
pushup(p);
return p;
}
int merge(int p,int q,int x,int y) {
if(!p) return q;
if(!q) return p;
if(x==y) {d[p]+=d[q]; t[p]=x; return p;}
int mid=(x+y)/2;
lc[p]=merge(lc[p],lc[q],x,mid);
rc[p]=merge(rc[p],rc[q],mid+1,y);
pushup(p); return p;
}
void Redfs(int u) {
for(int i=head[u]; i; i=nxt[i]) {
int v=ver[i];
if(deep[v]>deep[u]) {
Redfs(v);
rt[u]=merge(rt[u],rt[v],1,R);
}
}
if(d[rt[u]]) ans[u]=t[rt[u]];
}
int main() {
scanf("%d%d",&n,&m);
for(int i=1,x,y; i<n; i++) {
scanf("%d%d",&x,&y);
addedge(y,x); addedge(x,y);
}
deep[1]=1; dfs1(1); dfs2(1,1);
for(int i=1; i<=m; i++) {
scanf("%d%d%d",&qx[i],&qy[i],&qz[i]);
R=max(R,qz[i]);
}
for(int i=1; i<=m; i++) {
int s=lca(qx[i],qy[i]);
rt[qx[i]]=modify(rt[qx[i]],1,R,qz[i],1);
rt[qy[i]]=modify(rt[qy[i]],1,R,qz[i],1);
rt[s]=modify(rt[s],1,R,qz[i],-1);
if(fa[s]) rt[fa[s]]=modify(rt[fa[s]],1,R,qz[i],-1);
}
Redfs(1);
for(int i=1; i<=n; i++) printf("%d\n",ans[i]);
return 0;
}