最大子段和问题及其扩展
最大子段和问题及其扩展
普通最大子段和
设
单点修改最大子段和
带单点修改时,用数据结构维护,设
食用线段树维护,复杂度
这样可以查找任意子区间的最大子段和,单次查找
最小子段和
最小子段和类似于最大子段和,设
环形最大子段和
最大子段和有两种情况,一种是不跨环,此时就是一般最大子段和,另一种是跨环,正难则反,求一个最小子段和,用总和将其减去即可得到。
例子:*****###*,其中是最大子段和,###是最小子段和
两段最大子段和
令
求出解之后,设
环形两段最大子段和
与环形最大子段和类似,情况如下:设X是最大子段和中的元素,#不是
则
-
XXXX##XXXXX##
- XX####XXXX##XX
同样,求普通的两段最大子段和
int f[N],g[N],f1[N],g1[N],s[N],t[N],s1[N],t1[N],sum,a[N],ans1[N],ans2[N],n,ans=-1e9;
memset(f,0xcf,sizeof f);
memset(g,0xcf,sizeof f);
memset(s,0x3f,sizeof f);
memset(t,0x3f,sizeof f);
memset(f1,0xcf,sizeof f);
memset(g1,0xcf,sizeof f);
memset(s1,0x3f,sizeof f);
memset(t1,0x3f,sizeof f);
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=n;i++)sum+=a[i];
for(int i=1;i<=n;i++)f[i]=max(0,f[i-1])+a[i],f1[i]=max(f1[i-1],f[i]);
for(int i=n;i>=1;--i)g[i]=max(0,g[i+1])+a[i],g1[i]=max(g1[i+1],g[i]);
for(int i=1;i<=n;i++)s[i]=min(0,s[i-1])+a[i],s1[i]=min(s1[i-1],s[i]);
for(int i=n;i>=1;--i)t[i]=min(0,t[i+1])+a[i],t1[i]=min(t1[i+1],t[i]);
for(int i=1;i<=n;i++)ans1[i]=g1[i+1]+f1[i];
for(int i=1;i<=n;i++)ans2[i]=sum-s1[i]-t1[i+1];
for(int i=1;i<=n;i++)if(!ans2[i])ans2[i]=-0x3f3f3f3f;
for(int i=1;i<n;i++)ans=max(ans,max(ans1[i],ans2[i]));
cout<<ans;
限制端点最大子段和
这是一道限制端点的最大子段和题:
给定长度为
分析
通过这个问题,我们可以发现情况分为两种:
此时容易看出
是必须计算在内的,故我们用 即可求出解。
不妨设最终答案的左右端点为
,则分四种情况讨论。
,这种情况的最大值是 ,这种情况的最大值是 ,这种情况的最大值是 ,这种情况和 相似,类比可得最大值是
由此,我们建立一颗线段树即可解决问题。
using namespace std;
#define N 500050
struct node{
int l,r,lx,rx,mx,sum;
}t[N<<2];
int a[N],n,m;
char F[20];
#define lc x<<1
#define rc x<<1|1
inline 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;
}
inline void write(int x){
if(!x)putchar('0');
int tmp=x>0?x:-x;
if(x<0)putchar('-');
int cnt=0;
while(tmp>0){
F[cnt++]=tmp%10+'0';
tmp/=10;
}
while(cnt>0)putchar(F[--cnt]) ;
}
void merge(node &a,node &b,node &c){
a.lx=max(b.lx,b.sum+c.lx);
a.rx=max(c.rx,b.rx+c.sum);
a.mx=max(a.lx,max(a.rx,b.rx+c.lx));
a.mx=max(a.mx,max(b.mx,c.mx));
a.sum=b.sum+c.sum;
}
void build(int l,int r,int x){
t[x].l=l,t[x].r=r;
if(l==r){
t[x].lx=t[x].rx=t[x].mx=t[x].sum=a[l];
return ;
}
int mid=l+r>>1;
build(l,mid,lc);
build(mid+1,r,rc);
merge(t[x],t[lc],t[rc]);
}
node find(int l,int r,int x){
if(l<=t[x].l&&t[x].r<=r){
return t[x];
}
node ans={0,0,-0x3f3f3f3f,-0x3f3f3f3f,-0x3f3f3f3f,0};
node a=ans,b=ans;
int mid=t[x].l+t[x].r>>1;
if(l<=mid){
a=find(l,r,lc);
}
if(mid<r) {
b=find(l,r,rc);
}
merge(ans,a,b);
return ans;
}
int query(int l1,int r1,int l2,int r2){
if(r1<=l2){
return find(l1,r1,1).rx+find(r1,l2,1).sum+find(l2,r2,1).lx-a[r1]-a[l2];
}
node x=find(l2,r1,1),y=find(l1,l2,1),z=find(r1,r2,1);
return max(x.mx,max(x.sum+z.lx+y.rx-a[l2]-a[r1],max(x.lx+y.rx-a[l2],x.rx+z.lx-a[r1])));
}
int main(){
//freopen("data.in","r",stdin);
//freopen("data.out","w",stdout);
int tt;read(tt);
while(tt--){
read(n);
for(int i=1;i<=n;i++)read(a[i]);
build(1,n,1);
read(m);
while(m--){
int l1,r1,l2,r2;
read(l1),read(r1),read(l2),read(r2);
write(query(l1,r1,l2,r2));
}
}
}
去重最大子段和
在线算法,发现维护去重似乎非常困难,考虑将序列离线下来。有了这个离线的条件,由于没有修改操作,我们就可以考虑对询问顺序开始魔改处理了。
1e5
常见的做法无非三种可能:
然后再来考虑,如果是线段树该怎么做。利用离线操作,一个套路是:
对询问的某端点进行排序,高效维护某个端点单向移动且支持查询以这个移动端点为端点的区间的答案。
加上去重的特殊性,我们考虑将所有的询问按
维护一个答案序列(也许可以理解为贡献序列)
考虑移动指针
所以我们将
再来考虑如何求解最大子段和问题。
首先,因为此时的
受这种思想的启发,回溯一个阶段,当
但,显然这个做法有优化的空间,我们明显可以保存
这样我们就得到了一个做法:
- 读入询问,离线,统计
- 建立线段树,维护区间最大值,区间历史最大值
- 将询问按右端点排序,建立指针
并不断右移,每次插入就在 上加上 。每次查询就查找 的历史最大值。
至此,我们得到了一个
#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]);
}
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库