GSS2—去重最大子段和

GSS2

题意:给定序列\(a\),若干次询问,求区间最大去重子段和。

询问次数与序列长度在1e5级别。

分析

超级神题。

在线算法,发现维护去重似乎非常困难,考虑将序列离线下来。有了这个离线的条件,由于没有修改操作,我们就可以考虑对询问顺序开始魔改处理了。

1e5常见的做法无非三种可能:\(O(n\sqrt n),O(n\log n),O(n\log^2 n)\)。我们来一个个考虑:

\(O(n\sqrt n)\):莫队 or 根号分治。莫队的话,由于离线后去重便转化为在某个区域内算上贡献,涉及区间操作,套个数据结构复杂度就变成\(O(n\sqrt n\log n)\)起步,无法承受。而根号分治明显就不行(显然应该不会存在什么高效的分类方式)。

\(O(n\log n),O(n\log^2 n)\):涉及根号和区间操作,明显应该是线段树/树状数组/\(Splay\)。因为\(Splay\)超大常数,并且最大子段和存在一个由线段树维护的分治做法,我们首先来考虑线段树。

然后再来考虑,如果是线段树该怎么做。利用离线操作,一个套路是:

对询问的某端点进行排序,高效维护某个端点单向移动且支持查询以这个移动端点为端点的区间的答案。

加上去重的特殊性,我们考虑将所有的询问按\(r\)排序,并且令最初的\(r\)指针为\(1\),不断向后移动并处理询问。

维护一个答案序列(也许可以理解为贡献序列)\(b\)
考虑移动指针\(r\)加入一个新的数会怎么样,显然\(a_r\)产生贡献的范围是:\([pre_{r}+1,r]\),其中\(pre_i\)表示值为\(a_i\)的数的上一次出现位置,可以在\(O(n)\)内预处理出。

所以我们将\(b_i,i\in[pre_r+1,r]\)全部加上\(a_r\),这样我们就满足,在任意时刻,\(b_x\)表示\([x,r]\)的去重后和。

再来考虑如何求解最大子段和问题。

首先,因为此时的\(b_i\)已经代表了\([i,r]\)的去重后和,那么我们就可以查个最大值即可统计出右端点为\(r\)的最大子段和。

受这种思想的启发,回溯一个阶段,当\(r'=r-1\)的时候,我们也可以查个最大值得到右端点为\(r-1\)的最大子段和。以此类推,进行若干次操作即可得到答案。

但,显然这个做法有优化的空间,我们明显可以保存\(r-1\)及其之前的最大值,也即维护区间历史最大值,每一次加入操作之后更新一下历史最大值即可。

这样我们就得到了一个做法:

  1. 读入询问,离线,统计\(pre\)
  2. 建立线段树,维护区间最大值,区间历史最大值
  3. 将询问按右端点排序,建立指针\(r\)并不断右移,每次插入就在\([pre_r+1,r]\)上加上\(a_r\)。每次查询就查找\([ask[i].l,ask[i].r](ask[i].r=r)\)的历史最大值。

至此,我们得到了一个\(O(n\log n)\)的优秀算法。

#define N 100050
#define ll long long
int n,m,a[N],pre[N],b[N];
ll ans[N];
struct node{
	int l,r;ll mx,hmx,lz,hlz;
}t[N<<2];
struct Ask{
	int l,r,id;
	bool operator<(const Ask b){
		return r==b.r?l<b.l:r<b.r;
	}
}ask[N];
#define lc x<<1
#define rc x<<1|1
void read(int &x){
	int s=0,w=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-')w=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		s=s*10+ch-'0';
		ch=getchar();
	}
	x=s*w;
}
void pushup(int x){
	t[x].mx=max(t[lc].mx,t[rc].mx);
	t[x].hmx=max(t[x].hmx,t[x].mx);
}
void pushdown(node &a,node &b,node &c){
	b.hmx=max(b.hmx,b.mx+a.hlz);
	c.hmx=max(c.hmx,c.mx+a.hlz);
	b.hlz=max(b.hlz,b.lz+a.hlz);
	c.hlz=max(c.hlz,c.lz+a.hlz);
	b.mx+=a.lz;
	c.mx+=a.lz; 
	b.lz+=a.lz;
	c.lz+=a.lz;
	a.lz=a.hlz=0;
}
void pushdown(int x){
	pushdown(t[x],t[lc],t[rc]);
}
void build(int l,int r,int x){
	t[x]={l,r,0,0,0,0};
	if(l==r)return ;
	int mid=l+r>>1;
	build(l,mid,lc);
	build(mid+1,r,rc);
}
ll find(int l,int r,int x){
	if(l<=t[x].l&&t[x].r<=r){
		return t[x].hmx;
	}
	pushdown(x);
	ll ans=0ll;
	int mid=t[x].l+t[x].r>>1;
	if(l<=mid)ans=max(find(l,r,lc),ans);
	if(mid<r)ans=max(find(l,r,rc),ans);
	pushup(x);
	return ans;
}
void change(int l,int r,ll k,int x){
	if(l<=t[x].l&&t[x].r<=r){
		t[x].lz+=k;
		t[x].hlz=max(t[x].hlz,t[x].lz);
		t[x].mx+=k;
		t[x].hmx=max(t[x].hmx,t[x].mx);
		return ;
	}
	pushdown(x);
	int mid=t[x].l+t[x].r>>1;
	if(l<=mid)change(l,r,k,lc);
	if(mid<r)change(l,r,k,rc);
	pushup(x);
}
void init(){
	read(n);
	build(1,n,1);
	for(int i=1;i<=n;i++)read(a[i]);
	for(int i=1;i<=n;i++)pre[i]=b[a[i]],b[a[i]]=i;
	read(m);
	for(int i=1;i<=m;i++)read(ask[i].l),read(ask[i].r),ask[i].id=i;
	sort(ask+1,ask+m+1);
}
void solve(){
	int l=1;
	for(int r=1;r<=n;++r){
		change(pre[r]+1,r,a[r],1);
		while(ask[l].r==r&&l<=m){
			ans[ask[l].id]=find(ask[l].l,ask[l].r,1);
			l++;
		}
	}
}
int main(){
	//freopen("data.in","r",stdin);
	//freopen("data.out","w",stdout);
	init();
	solve();
	for(int i=1;i<=m;i++){
		printf("%lld\n",ans[i]);
	}
}
posted @ 2022-12-30 18:23  spdarkle  阅读(106)  评论(0编辑  收藏  举报