吉司机线段树

一、区间历史最值

以区间历史最大值为例。首先,相应地,设 maxb 表示一个节点的区间历史最大值。为了更新一个区间的子区间,再设一个 tag2 ,表示 tag1 从上次 push_down 以后到现在达到过的最大值。

code:

void push_up(int u){
    p[u].w=p[u<<1].w+p[u<<1|1].w;
    p[u].maxa=max(p[u<<1].maxa,p[u<<1|1].maxa);
    p[u].maxb=max(p[u<<1].maxb,p[u<<1|1].maxb);
}
void push_down(int u){//push_down时,注意先下传maxb,tag2,后下传maxa,tag1
    p[u<<1].w+=(p[u<<1].r-p[u<<1].l+1)*p[u].tag1;
    p[u<<1|1].w+=(p[u<<1|1].r-p[u<<1|1].l+1)*p[u].tag1;
    p[u<<1].maxb=max(p[u<<1].maxb,p[u<<1].maxa+p[u].tag2);
    p[u<<1|1].maxb=max(p[u<<1|1].maxb,p[u<<1|1].maxa+p[u].tag2);
    p[u<<1].tag2=max(p[u<<1].tag2,p[u<<1].tag1+p[u].tag2);
    p[u<<1|1].tag2=max(p[u<<1|1].tag2,p[u<<1|1].tag1+p[u].tag2);
    p[u<<1].maxa+=p[u].tag1;
    p[u<<1|1].maxa+=p[u].tag1;
    p[u<<1].tag1+=p[u].tag1;
    p[u<<1|1].tag1+=p[u].tag1;
    p[u].tag1=p[u].tag2=0;
}
void build(int u,int l,int r){
    p[u].l=l;p[u].r=r;
    if(l==r){
        p[u].w=p[u].maxa=p[u].maxb=a[l];
        return ;
    }
    int mid=(l+r)>>1;
    build(u<<1,l,mid);build(u<<1|1,mid+1,r);
    push_up(u);
}
void add(int u,int l,int r,int w){
    if(p[u].l>=l&&p[u].r<=r){
        p[u].w+=(p[u].r-p[u].l+1)*w;
        p[u].tag1+=w;
        p[u].maxa+=w;
        p[u].tag2=max(p[u].tag2,p[u].tag1);
        p[u].maxb=max(p[u].maxb,p[u].maxa);
        return ;
    }
    push_down(u);
    int mid=(p[u].l+p[u].r)>>1;
    if(mid>=l)
        add(u<<1,l,r,w);
    if(mid<r)
        add(u<<1|1,l,r,w);
    push_up(u);
}
int ask(int u,int l,int r,int opt){
    if(p[u].l>=l&&p[u].r<=r){
        if(opt==1) return p[u].w;
        else if(opt==2) return p[u].maxa;
        else return p[u].maxb;
    }
    push_down(u);
    int re=0,maxx=-1e18,mid=(p[u].l+p[u].r)>>1;
    if(mid>=l){
        if(opt==1) re+=ask(u<<1,l,r,opt);
        else maxx=max(maxx,ask(u<<1,l,r,opt));
    }
    if(mid<r){
        if(opt==1) re+=ask(u<<1|1,l,r,opt);
        else maxx=max(maxx,ask(u<<1|1,l,r,opt));
    }
    if(opt==1) return re;
    else return maxx;
}

区间最值操作

以区间最小值操作,即把区间的所有数变成 min(w,a[i]) 为例。设 max2 表示一个区间的严格次大值(不等于最大值), cnt 表示一个区间的最大值的个数。在进行区间最值操作时时,设给定的数为 w ,那么只有当 max2<w<maxa 时,才对当前区间更新。

此外,可以发现,该操作会导致区间最大值的 tag 和其他值的 tag 不一样。此时需要设四个 tag

tag1 表示区间最大值的 tagtag2 表示区间次大值的 tagtag3 表示 tag1 从上次 push_down 以后达到过的最大值; tag4 表示 tag2 从上次 push_down 以后达到过的最大值。

code:

void push_up(int u){
	p[u].w=p[u<<1].w+p[u<<1|1].w;
	p[u].maxa=max(p[u<<1].maxa,p[u<<1|1].maxa);
	p[u].maxb=max(p[u<<1].maxb,p[u<<1|1].maxb);
	p[u].max2=max(p[u<<1].max2,p[u<<1|1].max2);
	p[u].cnt=0;
	if(p[u].maxa==p[u<<1].maxa)
		p[u].cnt+=p[u<<1].cnt;
	else
		p[u].max2=max(p[u].max2,p[u<<1].maxa);
	if(p[u].maxa==p[u<<1|1].maxa)
		p[u].cnt+=p[u<<1|1].cnt;
	else
		p[u].max2=max(p[u].max2,p[u<<1|1].maxa); 
}
void mktag(int u,int k1,int k2,int k3,int k4){
	p[u].maxb=max(p[u].maxb,p[u].maxa+k3);
	p[u].maxa+=k1;
	if(p[u].max2!=-1e18)
		p[u].max2+=k2;
	p[u].w+=p[u].cnt*k1+(p[u].r-p[u].l+1-p[u].cnt)*k2;
	p[u].tag3=max(p[u].tag3,p[u].tag1+k3);
	p[u].tag4=max(p[u].tag4,p[u].tag2+k4);
	p[u].tag1+=k1;
	p[u].tag2+=k2;
}
void push_down(int u){
	int maxx=max(p[u<<1].maxa,p[u<<1|1].maxa);
	if(p[u<<1].maxa==maxx)
		mktag(u<<1,p[u].tag1,p[u].tag2,p[u].tag3,p[u].tag4);
	else
		mktag(u<<1,p[u].tag2,p[u].tag2,p[u].tag4,p[u].tag4);
	if(p[u<<1|1].maxa==maxx)
		mktag(u<<1|1,p[u].tag1,p[u].tag2,p[u].tag3,p[u].tag4);
	else
		mktag(u<<1|1,p[u].tag2,p[u].tag2,p[u].tag4,p[u].tag4);
	p[u].tag1=p[u].tag2=p[u].tag3=p[u].tag4=0;
}
void build(int u,int l,int r){
	p[u].l=l;p[u].r=r;
	if(l==r){
		p[u].w=p[u].maxa=p[u].maxb=a[l];
		p[u].cnt=1;
		p[u].max2=-1e18;
		return ;
	}
	int mid=(l+r)>>1;
	build(u<<1,l,mid);build(u<<1|1,mid+1,r);
	push_up(u);
}
void add(int u,int l,int r,int w){
	if(p[u].l>=l&&p[u].r<=r){
		mktag(u,w,w,w,w);
		return ;
	}
	push_down(u);
	int mid=(p[u].l+p[u].r)>>1;
	if(mid>=l)
		add(u<<1,l,r,w);
	if(mid<r)
		add(u<<1|1,l,r,w);
	push_up(u);
}
void minn(int u,int l,int r,int w){
	if(p[u].maxa<=w)
		return ;
	if(p[u].l>=l&&p[u].r<=r&&p[u].max2<w){
		mktag(u,w-p[u].maxa,0,0,0);
		return ;
	} 
	push_down(u);
	int mid=(p[u].l+p[u].r)>>1;
	if(mid>=l)
		minn(u<<1,l,r,w);
	if(mid<r)
		minn(u<<1|1,l,r,w);
	push_up(u);
}
int ask(int u,int l,int r,int opt){
	if(p[u].l>=l&&p[u].r<=r){
		if(opt==1) return p[u].w;
		else if(opt==2) return p[u].maxa;
		return p[u].maxb;
	}
	push_down(u);
	int mid=(p[u].l+p[u].r)>>1;
	if(mid>=r)
		return ask(u<<1,l,r,opt);
	if(mid<l)
		return ask(u<<1|1,l,r,opt);
	if(opt==1)
		return ask(u<<1,l,r,opt)+ask(u<<1|1,l,r,opt);
	return max(ask(u<<1,l,r,opt),ask(u<<1|1,l,r,opt));
}

P4314 CPU 监控

题目要求维护一个支持区间推平,区间加,区间历史最值的线段树。

tag1 表示区间加的 tagtag2 表示 tag1 的最大值, tag3 表示区间推平的 tagtag4 表示 tag3 的最大值,然后就可以维护了。情况比较复杂,不要漏掉或写错每一个情况。另外,别忘了把 tag4 的初始值设为 INF

code:

void mktag(int u,int k1,int k2,int k3,int k4,bool ok){
	if(ok){//先加后赋 
		p[u].maxb=max(p[u].maxb,max(p[u].maxa+k2,k4));
		p[u].maxa=k3;
		if(p[u].ok){
			p[u].tag4=max(p[u].tag4,max(p[u].tag3+k2,k4));
			p[u].tag3=k3;
		}
		else{
			p[u].tag2=max(p[u].tag2,p[u].tag1+k2);
			p[u].tag1+=k1;
			p[u].tag4=max(p[u].tag4,k4);
			p[u].tag3=k3;
		}
		p[u].ok=1;
	}
	else{//只有加 
		p[u].maxb=max(p[u].maxb,p[u].maxa+k2);
		p[u].maxa+=k1;
		if(p[u].ok){
			p[u].tag4=max(p[u].tag4,p[u].tag3+k2);
			p[u].tag3+=k1;
		}
		else{
			p[u].tag2=max(p[u].tag2,p[u].tag1+k2);
			p[u].tag1+=k1;
		}
	}
}
void push_down(int u){
	mktag(u<<1,p[u].tag1,p[u].tag2,p[u].tag3,p[u].tag4,p[u].ok);
	mktag(u<<1|1,p[u].tag1,p[u].tag2,p[u].tag3,p[u].tag4,p[u].ok);
	p[u].ok=p[u].tag1=p[u].tag2=p[u].tag3=0;
	p[u].tag4=-1e18;
}

UOJ 169 元旦老人与数列

就是线段树三的板子,还没有区间和。

posted @   andy_lz  阅读(19)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
点击右上角即可分享
微信分享提示