CF1787I Treasure Hunt 题解

题目链接

题意描述:

定义一个序列的权值为一段前缀与一段子段,满足要么前缀与子段不交,要么完全包含的和的最大值,给定一个序列 \(a\),求 \(a\) 的所有子区间的权值和
\(n\le 10^6\)

发现如果前缀与子段有交且不包含,那么一定不优,因为子段未被包含的一段一定是正的,不然就不是最大子段了

因此可以把问题拆成两部分:所有子区间的最大前缀以及所有子区间的最大子段和

第一部分是 trivial 的,直接用单调栈或笛卡尔树求出贡献范围即可

第二部分显然考虑分治,对于跨过中点的区间的贡献,可以分成三种情况(即线段树求最大子段和的三种)

我们设 \(dp_l\)\(dp_r\)) 表示从中点至左边(右边) \(l\)\(r\))位置的最大子段和,\(sum_l\)\(sum_r\))为最大前缀和

在从中点向左扫的过程中,首先 \(dp_l,sum_l\) 肯定是单调不降的,而 \(dp_l-sum_l\) 也是单调不降的,因为 \(dp_l\ge sum_l\),而新加入一个元素能贡献到 \(sum_l\) 那么也一定能贡献到 \(dp_l\),否则也有可能贡献到 \(dp_l\),所以差距不会变小,讨论一下三种情况的大小关系:

  1. \(dp_l>\max(dp_r,sum_l+sum_r)\),拆成 \(dp_l>dp_r\)\(dp_l>sum_l+sum_r\),移项得 \(dp_l-sum_l>sum_r\),因此取到这种情况的分界点 \(r\) 一定单调不降
  2. \(dp_r>\max(dp_l,sum_l+sum_r)\),类似上一种,\(dp_r-sum_r>sum_l\),随着 \(l\) 的移动,可能的位置会越来越少
  3. \(sum_l+sum_r>\max(dp_l+dp_r)\),拆开移项得 \(sum_l>dp_r-sum_r,sum_r>dp_l-sum_l\),那么这种情况的分界点是不断向后移动的

于是贡献分成三段,第一段显然是 \(dp_l\),第三段显然是 \(dp_r\),那么中间就是 \(sum_l+sum_r\) 了,直接前缀和维护

一个感性的理解:\(dp_l\) 越来越大,在右边比较短的时候没必要取右边一点点大的地方,但当右边比较大的时候,就可以把两边拼起来,右边非常大的时候,由于这时 \(dp_r\)\(sum_r\) 的差距已经很大了,算上左边的 \(sum_l\) 也无济于事

时间复杂度 \(O(n\log n)\)

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define N 1000005
#define mid ((l+r)>>1)
using namespace std;
int read(){
	int w=0,h=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')h=-h;ch=getchar();}
	while(ch>='0'&&ch<='9'){w=w*10+ch-'0';ch=getchar();}
	return w*h;
}
const int mod=998244353;
int n,a[N],dp[N],ddp[N],sum[N],sumdp[N],sumsum[N],summ[N],mus[N];
int Divide(int l,int r){
	if(l==r)return max(0ll,a[l]);
	for(int i=l;i<=r;i++)dp[i]=ddp[i]=sum[i]=sumdp[i]=sumsum[i]=0;
	for(int i=mid+1,su=0;i<=r;i++){
		su+=a[i];sum[i]=max((i==mid+1)?0:sum[i-1],su);
		dp[i]=(i==mid+1)?a[i]:max(dp[i-1]+a[i],a[i]);
		ddp[i]=max((i==mid+1)?0:ddp[i-1],dp[i]);
	}
	for(int i=mid,su=0;i>=l;i--){
		su+=a[i];sum[i]=max((i==mid)?0:sum[i+1],su);
		dp[i]=(i==mid)?a[i]:max(dp[i+1]+a[i],a[i]);
		ddp[i]=max((i==mid)?0:ddp[i+1],dp[i]);
	}
	for(int i=mid+1;i<=r;i++){
		sumdp[i]=(ddp[i]+(i==mid+1?0:sumdp[i-1]))%mod;
		sumsum[i]=(sum[i]+(i==mid+1?0:sumsum[i-1]))%mod;
	}
	int cur1=mid+1,cur2=mid+1,ans=0;
	for(int i=mid;i>=l;i--){
		while(cur2<=r&&ddp[cur2]<max(sum[i]+sum[cur2],ddp[i]))cur2++;
		while(cur1<cur2&&sum[i]+sum[cur1]<max(ddp[i],ddp[cur1]))cur1++;
		(ans+=ddp[i]*(cur1-1-mid)%mod)%=mod;
		(ans+=sum[i]*(cur2-cur1)%mod+(sumsum[cur2-1]-sumsum[cur1-1])%mod+mod)%=mod;
		(ans+=sumdp[r]-sumdp[cur2-1]+mod)%=mod;
	}
	return (ans+Divide(l,mid)+Divide(mid+1,r))%mod;
}
namespace Sparse_Table{
	int Max[20][N],lg[N];
	int chkmax(int x,int y){return summ[x]>summ[y]?x:y;}
	void Build(){
		for(int i=2;i<=n;i++)lg[i]=lg[i>>1]+1;
		for(int i=1;i<=n;i++)Max[0][i]=i;
		for(int j=1;j<=lg[n];j++)
			for(int i=1;i+(1<<j)-1<=n;i++)
				Max[j][i]=chkmax(Max[j-1][i],Max[j-1][i+(1<<(j-1))]);
	}
	int Ask(int l,int r){
		if(l>r)swap(l,r);int k=lg[r-l+1];
		return chkmax(Max[k][l],Max[k][r-(1<<k)+1]);
	}
}
using namespace Sparse_Table;
int Prefix(int l,int r){
	if(l>r)return 0;int Mid=Ask(l,r);
	int res=(summ[Mid]*(Mid-l)%mod-(mus[Mid]-mus[l])%mod+mod+max(0ll,summ[Mid]-summ[l-1]))*(r-Mid+1)%mod;
	return (res+Prefix(l,Mid-1)+Prefix(Mid+1,r))%mod;
}
void solve(){
	n=read();
	for(int i=1;i<=n;i++)a[i]=read();
	for(int i=1;i<=n;i++)summ[i]=summ[i-1]+a[i],mus[i]=mus[i-1]+summ[i-1];
	Build();
	printf("%lld\n",(Divide(1,n)+Prefix(1,n))%mod);
}
signed main(){
	int T=read();
	while(T--)solve();
	return 0;
}

posted @ 2023-02-07 14:28  pidan007  阅读(37)  评论(0编辑  收藏  举报