线段树历史值

P6242 【模板】线段树 3

支持区间加,区间取 \(\min\),区间求和,区间 \(\max\),区间历史 \(\max\)


先提一嘴吉司机。

就是对线段树的每个节点记录最大值,严格次大值和最大值个数,只在 \(se<v<mx\) 的区间操作,否则向下递归。如果没有区间加,复杂度势能分析是 \(O((n+m)\log n)\) 的,否则为 \(O(m\log^2 n)\)

考虑 \(\operatorname{pushdown}\) 需要的 \(\mathrm{tag}\)

  • \(\mathrm{tag1}\):区间 \(\max\) 的懒标记。

  • \(\mathrm{tag2}\):区间非 \(\max\) 的懒标记。

  • \(\mathrm{tag3}\):区间 \(\max\) 的懒标记的最大值。

  • \(\mathrm{tag4}\):区间非 \(\max\) 的懒标记的最大值。

注意要先更新历史值再更新当前值。

点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define N 500010
#define inf 2e9
using namespace std;
int read(){
	int x=0,w=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
	while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
	return x*w;
}
#define sum(x) tr[x].sum
#define len(x) tr[x].len
#define ma(x) tr[x].ma
#define cnt(x) tr[x].cnt
#define se(x) tr[x].se
#define mb(x) tr[x].mb
#define add1(x) tr[x].add1
#define add2(x) tr[x].add2
#define add3(x) tr[x].add3
#define add4(x) tr[x].add4
struct Tree{
	ll sum;int len;
	int ma,cnt,se,mb;
	int add1,add2,add3,add4;
}tr[N<<2];
#define ls p<<1
#define rs p<<1|1
void pushup(int p){
	sum(p)=sum(ls)+sum(rs);
	ma(p)=max(ma(ls),ma(rs)),mb(p)=max(mb(ls),mb(rs));
	if(ma(ls)==ma(rs))
		se(p)=max(se(ls),se(rs)),cnt(p)=cnt(ls)+cnt(rs);
	else if(ma(ls)>ma(rs))
		se(p)=max(se(ls),ma(rs)),cnt(p)=cnt(ls);
	else
		se(p)=max(ma(ls),se(rs)),cnt(p)=cnt(rs);
}
void change(int p,int k1,int k2,int k3,int k4){
	sum(p)+=1ll*k1*cnt(p)+1ll*k2*(len(p)-cnt(p));
	mb(p)=max(mb(p),ma(p)+k3),ma(p)+=k1;
	if(se(p)!=-inf)se(p)+=k2;
	add3(p)=max(add3(p),add1(p)+k3);
	add4(p)=max(add4(p),add2(p)+k4);
	add1(p)+=k1,add2(p)+=k2;
}
void pushdown(int p){
	int mx=max(ma(ls),ma(rs));
	if(ma(ls)==mx)
		change(ls,add1(p),add2(p),add3(p),add4(p));
	else change(ls,add2(p),add2(p),add4(p),add4(p));
	if(ma(rs)==mx)
		change(rs,add1(p),add2(p),add3(p),add4(p));
	else change(rs,add2(p),add2(p),add4(p),add4(p));
	add1(p)=add2(p)=add3(p)=add4(p)=0;
}
void build(int p,int l,int r){
	len(p)=r-l+1;
	if(l==r){
		sum(p)=ma(p)=mb(p)=read();
		cnt(p)=1,se(p)=-inf;
		return;
	}
	int mid=(l+r)>>1;
	build(ls,l,mid),build(rs,mid+1,r);
	pushup(p);
}
ll qsum(int p,int l,int r,int L,int R){
	if(L<=l&&r<=R)return sum(p);
	int mid=(l+r)>>1;pushdown(p);
	ll ret=0;
	if(L<=mid)ret+=qsum(ls,l,mid,L,R);
	if(R>mid)ret+=qsum(rs,mid+1,r,L,R);
	return ret;
}
int qma(int p,int l,int r,int L,int R){
	if(L<=l&&r<=R)return ma(p);
	int mid=(l+r)>>1;pushdown(p);
	if(R<=mid)return qma(ls,l,mid,L,R);
	if(L>mid)return qma(rs,mid+1,r,L,R);
	return max(qma(ls,l,mid,L,R),qma(rs,mid+1,r,L,R));
}
int qmb(int p,int l,int r,int L,int R){
	if(L<=l&&r<=R)return mb(p);
	int mid=(l+r)>>1;pushdown(p);
	if(R<=mid)return qmb(ls,l,mid,L,R);
	if(L>mid)return qmb(rs,mid+1,r,L,R);
	return max(qmb(ls,l,mid,L,R),qmb(rs,mid+1,r,L,R));
}
void uadd(int p,int l,int r,int L,int R,int k){
	if(L<=l&&r<=R){
		sum(p)+=1ll*k*len(p);
		ma(p)+=k,mb(p)=max(mb(p),ma(p));
		if(se(p)!=-inf)se(p)+=k;
		add1(p)+=k,add2(p)+=k;
		add3(p)=max(add3(p),add1(p)),add4(p)=max(add4(p),add2(p));
		return;
	}
	int mid=(l+r)>>1;pushdown(p);
	if(L<=mid)uadd(ls,l,mid,L,R,k);
	if(R>mid)uadd(rs,mid+1,r,L,R,k);
	pushup(p);
}
void umin(int p,int l,int r,int L,int R,int v){
	if(L>r||R<l||v>=ma(p))return;
	if(L<=l&&r<=R&&se(p)<v){
		int k=ma(p)-v;
		sum(p)-=1ll*cnt(p)*k;
		ma(p)=v,add1(p)-=k;
		return;
	}
	int mid=(l+r)>>1;pushdown(p);
	umin(ls,l,mid,L,R,v);
	umin(rs,mid+1,r,L,R,v);
	pushup(p);
}
int n,m;
int main(){
	n=read(),m=read();
	build(1,1,n);
	for(int opt,l,r;m;m--){
		opt=read(),l=read(),r=read();
		if(opt==1)uadd(1,1,n,l,r,read());
		if(opt==2)umin(1,1,n,l,r,read());
		if(opt==3)printf("%lld\n",qsum(1,1,n,l,r));
		if(opt==4)printf("%d\n",qma(1,1,n,l,r));
		if(opt==5)printf("%d\n",qmb(1,1,n,l,r));
	}
	
	return 0;
}

P4314 CPU 监控

支持区间 \(\max\),区间历史 \(\max\),区间加,区间推平。


记录 \(\mathrm{tag1}\)\(\mathrm{tag2}\) 表示区间加和推平的懒标记。

为了维护历史 \(\max\),新增两个懒标记 \(\mathrm{mtag1}\)\(\mathrm{mtag2}\)

点击查看代码
#include<bits/stdc++.h>
#define N 100010
using namespace std;
int read(){
	int x=0,w=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
	while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
	return x*w;
}
char gets(){
	char ch=getchar();
	while(ch<'A'||ch>'Z')ch=getchar();
	return ch;
}
#define mx(x) tr[x].mx
#define mxx(x) tr[x].mxx
#define tag1(x) tr[x].tag1
#define tag2(x) tr[x].tag2
#define mtag1(x) tr[x].mtag1
#define mtag2(x) tr[x].mtag2
#define assign(x) tr[x].assign
struct Tree{
	int mx,mxx;
	int tag1,tag2,mtag1,mtag2;
	bool assign;
}tr[N<<2];
#define ls p<<1
#define rs p<<1|1
void pushup(int p){
	mx(p)=max(mx(ls),mx(rs));
	mxx(p)=max(mxx(ls),mxx(rs));
}
void utag1(int p,int tag,int mtag){
	mxx(p)=max(mxx(p),mx(p)+mtag),mx(p)+=tag;
	if(assign(p)){
		mtag2(p)=max(mtag2(p),tag2(p)+mtag);
		tag2(p)+=tag;
	}
	else{
		mtag1(p)=max(mtag1(p),tag1(p)+mtag);
		tag1(p)+=tag;
	}
}
void utag2(int p,int tag,int mtag){
	tag2(p)=mx(p)=tag,mxx(p)=max(mxx(p),mtag);
	if(assign(p))
		mtag2(p)=max(mtag2(p),mtag);
	else assign(p)=true,mtag2(p)=mtag;
}
void pushdown(int p){
	utag1(ls,tag1(p),mtag1(p));
	utag1(rs,tag1(p),mtag1(p));
	tag1(p)=mtag1(p)=0;
	if(assign(p)){
		utag2(ls,tag2(p),mtag2(p));
		utag2(rs,tag2(p),mtag2(p));
		assign(p)=tag2(p)=mtag2(p)=0;
	}
}
void build(int p,int l,int r){
	if(l==r)return mx(p)=mxx(p)=read(),void();
	int mid=(l+r)>>1;
	build(ls,l,mid),build(rs,mid+1,r);
	pushup(p);
}
void uadd(int p,int l,int r,int L,int R,int x){
	if(L<=l&&r<=R)return utag1(p,x,x);
	int mid=(l+r)>>1;pushdown(p);
	if(L<=mid)uadd(ls,l,mid,L,R,x);
	if(R>mid)uadd(rs,mid+1,r,L,R,x);
	pushup(p);
}
void uval(int p,int l,int r,int L,int R,int x){
	if(L<=l&&r<=R)return utag2(p,x,x);
	int mid=(l+r)>>1;pushdown(p);
	if(L<=mid)uval(ls,l,mid,L,R,x);
	if(R>mid)uval(rs,mid+1,r,L,R,x);
	pushup(p);
}
int qcur(int p,int l,int r,int L,int R){
	if(L<=l&&r<=R)return mx(p);
	int mid=(l+r)>>1;pushdown(p);
	if(R<=mid)return qcur(ls,l,mid,L,R);
	if(L>mid)return qcur(rs,mid+1,r,L,R);
	return max(qcur(ls,l,mid,L,R),qcur(rs,mid+1,r,L,R));
}
int qall(int p,int l,int r,int L,int R){
	if(L<=l&&r<=R)return mxx(p);
	int mid=(l+r)>>1;pushdown(p);
	if(R<=mid)return qall(ls,l,mid,L,R);
	if(L>mid)return qall(rs,mid+1,r,L,R);
	return max(qall(ls,l,mid,L,R),qall(rs,mid+1,r,L,R));
}
int n,m;
int main(){
	n=read(),build(1,1,n);
	m=read();
	char opt;int l,r;
	while(m--){
		opt=gets(),l=read(),r=read();
		if(opt=='Q')printf("%d\n",qcur(1,1,n,l,r));
		if(opt=='A')printf("%d\n",qall(1,1,n,l,r));
		if(opt=='P')uadd(1,1,n,l,r,read());
		if(opt=='C')uval(1,1,n,l,r,read());
	}
	
	return 0;
}

GSS2 - Can you answer these queries II

询问区间最大子段和。一种数只计算一次。


将询问离线后按右端点排序。如果固定答案右端点为当前的 \(r\),可以用线段树直接做。具体地,每个叶子节点维护 \(\sum\limits_{k\in[pos,i]}a_k\) 即可。

但是右端点为 \(r\) 不一定最优,所以再维护历史 \(\max\).

点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define N 100010
using namespace std;
int read(){
	int x=0,w=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
	while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
	return x*w;
}
#define m1(x) tr[x].m1
#define m2(x) tr[x].m2
#define tag1(x) tr[x].tag1
#define tag2(x) tr[x].tag2
struct Tree{
	ll m1,m2,tag1,tag2;
}tr[N<<2];
#define ls p<<1
#define rs p<<1|1
void pushup(int p){
	m1(p)=max(m1(ls),m1(rs));
	m2(p)=max(m2(ls),m2(rs));
}
void pushdown(int p){
	m2(ls)=max(m2(ls),m1(ls)+tag2(p));
	m2(rs)=max(m2(rs),m1(rs)+tag2(p));
	tag2(ls)=max(tag2(ls),tag1(ls)+tag2(p));
	tag2(rs)=max(tag2(rs),tag1(rs)+tag2(p));
	m1(ls)+=tag1(p),m1(rs)+=tag1(p);
	tag1(ls)+=tag1(p),tag1(rs)+=tag1(p);
	tag1(p)=tag2(p)=0;
}
void modify(int p,int l,int r,int L,int R,int v){
	if(L<=l&&r<=R){
		m1(p)+=v,tag1(p)+=v;
		m2(p)=max(m2(p),m1(p)),tag2(p)=max(tag2(p),tag1(p));
		return;
	}
	int mid=(l+r)>>1;pushdown(p);
	if(L<=mid)modify(ls,l,mid,L,R,v);
	if(R>mid)modify(rs,mid+1,r,L,R,v);
	pushup(p);
}
ll query(int p,int l,int r,int L,int R){
	if(L<=l&&r<=R)return m2(p);
	int mid=(l+r)>>1;pushdown(p);
	if(R<=mid)return query(ls,l,mid,L,R);
	if(L>mid)return query(rs,mid+1,r,L,R);
	return max(query(ls,l,mid,L,R),query(rs,mid+1,r,L,R));
}
struct Q{
	int l,r,id;
	bool operator<(const Q &t)const{
		return r<t.r;
	}
}q[N];
int n,m,a[N];
int dlt=100000,lst[N<<1],pre[N];
ll ans[N];
int main(){
	n=read();
	for(int i=1;i<=n;i++){
		a[i]=read();
		pre[i]=lst[a[i]+dlt],lst[a[i]+dlt]=i;
	}
	m=read();
	for(int i=1,l,r;i<=m;i++){
		l=read(),r=read();
		q[i]=(Q){l,r,i};
	}
	sort(q+1,q+1+m);
	for(int i=1,j=1;i<=n;i++){
		modify(1,1,n,pre[i]+1,i,a[i]);
		while(j<=m&&q[j].r==i)
			ans[q[j].id]=query(1,1,n,q[j].l,i),j++;
	}
	for(int i=1;i<=m;i++)
		printf("%lld\n",ans[i]);
	
	return 0;
}

P8868 [NOIP2022] 比赛

多次询问 \(\displaystyle \sum_{p=l}^{r}\sum_{q=p}^{r}(\max_{i\in[p,q]}\{a_i\})\cdot (\max_{j\in[p,q]}\{b_j\})\)

\(\{a_n\}\)\(\{b_n\}\) 均为 \(1\)\(n\) 的排列,答案对 \(2^{64}\) 取模。

\(n\le 2.5\times 10^5\)


扫描线之后,容易单调栈出线段树上的操作区间。想想现在要干嘛:

  • \(a\) 区间加。

  • \(b\) 区间加。

  • \(s_i\) 加上 \(a_i\cdot b_i\)

  • 查询 \(s_i\) 区间和。

维护点积属实是有点逆天了。先想想正常的历史和。


区间加历史和

线段树节点上维护了区间和 \(sum\),区间历史和 \(ans\),加法标记 \(add\)

  • \(+x\) 标记:\(sum\leftarrow sum+x\cdot len\)\(add\leftarrow add+x\)

  • \(\mathrm{update}\) 标记:\(ans\leftarrow ans+sum\)

考虑 \(\mathrm{pushdown}\),记儿子为 \(1\),父亲为 \(2\)

  • \(add_1\)\(sum_1\) 是容易的。

  • \(ans_1\leftarrow ans_1+sum_1\cdot upd_2+h_2\cdot len_1\),其中 \(upd\)\(\mathrm{update}\) 操作的个数,\(h\) 为所有 \(\mathrm{update}\) 操作的 \(add\) 之和。

还要维护 \(upd\)\(h\)\(upd\) 是容易的,\(h_1\leftarrow h_1+add_1\cdot upd_2+h_2\)


区间加区间点积历史和

回到原题。线段树上应维护区间 \(ab\) 的和 \(s\),区间 \(a\) 的和 \(sa\),区间 \(b\) 的和 \(sb\)\(ab\) 的历史和 \(ans\),加 \(a\) 标记 \(adda\),加 \(b\) 标记 \(addb\)

  • \(a+x\) 标记:\(s\leftarrow s+x\cdot sb\)\(sa\leftarrow sa+x\cdot len\)\(adda\leftarrow adda+x\)

  • \(b+x\) 标记:\(s\leftarrow s+x\cdot sa\)\(sb\leftarrow sb+x\cdot len\)\(addb\leftarrow addb+x\)

  • \(\mathrm{update}\) 标记:\(ans\leftarrow ans+s\)

考虑 \(\mathrm{pushdown}\),记儿子为 \(1\),父亲为 \(2\)

除了 \(ans\) 都是容易的。

  • \(ans_1\leftarrow ans_1+s_1\cdot upd_2+hb_2\cdot sa_1+ha_2\cdot sb_1+h_2\cdot len_1\),其中 \(upd\) 表示 \(\mathrm{update}\) 操作的个数,\(ha\)\(adda\) 之和,\(hb\)\(addb\) 之和,\(h\)\(adda\cdot addb\) 之和。

还需要维护 \(upd,ha,hb,h\),其中 \(upd\) 是容易的。

  • \(ha_1\leftarrow ha_1+adda_1\cdot upd_2+ha_2\)\(hb\) 同理。

  • \(h_1\leftarrow h_1+adda_1\cdot addb_1\cdot upd_2+adda_1\cdot hb_2+addb_1\cdot ha_2+h_2\)

维护上述所有标记。

点击查看代码
#include<bits/stdc++.h>
#define int unsigned long long
#define ll unsigned long long
#define N 250010
using namespace std;
int read(){
	int x=0,w=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
	while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
	return x*w;
}
#define adda(x) tr[x].adda
#define addb(x) tr[x].addb
#define s(x) tr[x].s
#define sa(x) tr[x].sa
#define sb(x) tr[x].sb
#define upd(x) tr[x].upd
#define h(x) tr[x].h
#define ha(x) tr[x].ha
#define hb(x) tr[x].hb
#define ans(x) tr[x].ans
#define len(x) tr[x].len
struct Tree{
	ll adda,addb,s,sa,sb;
	ll upd,h,ha,hb,ans,len;
}tr[N<<2];
#define ls p<<1
#define rs p<<1|1
void pushup(int p){
	s(p)=s(ls)+s(rs);
	sa(p)=sa(ls)+sa(rs),sb(p)=sb(ls)+sb(rs);
	ans(p)=ans(ls)+ans(rs);
}
void spread(int p,Tree v){
	ans(p)+=s(p)*v.upd+sa(p)*v.hb+sb(p)*v.ha+v.h*len(p);
	h(p)+=adda(p)*addb(p)*v.upd+adda(p)*v.hb+addb(p)*v.ha+v.h;
	ha(p)+=adda(p)*v.upd+v.ha;
	hb(p)+=addb(p)*v.upd+v.hb;
	s(p)+=sa(p)*v.addb+sb(p)*v.adda+v.adda*v.addb*len(p);
	sa(p)+=v.adda*len(p),sb(p)+=v.addb*len(p);
	upd(p)+=v.upd,adda(p)+=v.adda,addb(p)+=v.addb;
}
void pushdown(int p){
	spread(ls,tr[p]),spread(rs,tr[p]);
	h(p)=ha(p)=hb(p)=upd(p)=adda(p)=addb(p)=0;
}
void build(int p,int l,int r){
	len(p)=r-l+1;
	if(l==r)return;
	int mid=(l+r)>>1;
	build(ls,l,mid),build(rs,mid+1,r);
}
void modify(int p,int l,int r,int L,int R,ll x,int o){
	if(L<=l&&r<=R){
		if(!o)spread(p,(Tree){x,0,0,0,0,0,0,0,0,0,len(p)});
		else spread(p,(Tree){0,x,0,0,0,0,0,0,0,0,len(p)});
		return;
	}
	int mid=(l+r)>>1;pushdown(p);
	if(L<=mid)modify(ls,l,mid,L,R,x,o);
	if(R>mid)modify(rs,mid+1,r,L,R,x,o);
	pushup(p);
}
ll query(int p,int l,int r,int L,int R){
	if(L<=l&&r<=R)return ans(p);
	int mid=(l+r)>>1;pushdown(p);
	ll ret=0;
	if(L<=mid)ret+=query(ls,l,mid,L,R);
	if(R>mid)ret+=query(rs,mid+1,r,L,R);
	return ret;
}
#define pii pair<int,int>
#define fi first
#define se second
#define mp make_pair
int n,m,a[N],b[N];
vector<pii>q[N];
int sta[N],topa,stb[N],topb;
ll ans[N];
signed main(){
	read(),n=read();
	for(int i=1;i<=n;i++)a[i]=read();
	for(int i=1;i<=n;i++)b[i]=read();
	m=read();
	for(int i=1,l,r;i<=m;i++){
		l=read(),r=read();
		q[r].push_back(mp(l,i));
	}
	build(1,1,n);
	for(int i=1;i<=n;i++){
		while(topa&&a[sta[topa]]<a[i]){
			modify(1,1,n,sta[topa-1]+1,sta[topa],-a[sta[topa]],0);
			topa--;
		}
		modify(1,1,n,sta[topa]+1,i,a[i],0);
		sta[++topa]=i;
		while(topb&&b[stb[topb]]<b[i]){
			modify(1,1,n,stb[topb-1]+1,stb[topb],-b[stb[topb]],1);
			topb--;
		}
		modify(1,1,n,stb[topb]+1,i,b[i],1);
		stb[++topb]=i;
		spread(1,(Tree){0,0,0,0,0,1,0,0,0,0,0});
		for(pii qry:q[i])
			ans[qry.se]=query(1,1,n,qry.fi,i);
	}
	for(int i=1;i<=m;i++)
		printf("%llu\n",ans[i]);
	
	return 0;
}
posted @ 2023-11-09 09:36  SError  阅读(18)  评论(0编辑  收藏  举报