线段树操作

线段树

线段树本质上的区间操作是把区间分解为一个个区间的分别操作。
如:对于操作\([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;
}
posted @ 2022-08-13 13:36  s1monG  阅读(35)  评论(0编辑  收藏  举报