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\),所以差距不会变小,讨论一下三种情况的大小关系:
- \(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\) 一定单调不降
- \(dp_r>\max(dp_l,sum_l+sum_r)\),类似上一种,\(dp_r-sum_r>sum_l\),随着 \(l\) 的移动,可能的位置会越来越少
- \(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;
}