吉司机线段树

一、区间历史最值

以区间历史最大值为例。首先,相应地,设 \(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\) 表示区间最大值的 \(tag\)\(tag2\) 表示区间次大值的 \(tag\)\(tag3\) 表示 \(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\) 表示区间加的 \(tag\)\(tag2\) 表示 \(tag1\) 的最大值, \(tag3\) 表示区间推平的 \(tag\)\(tag4\) 表示 \(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 @ 2023-09-05 16:34  andy_lz  阅读(49)  评论(0)    收藏  举报