您的位置:首页 > 教育 > 锐评 > 手机自助建站平台_软件开发工具有哪些_欧美网站建设公司_竞价排名的定义

手机自助建站平台_软件开发工具有哪些_欧美网站建设公司_竞价排名的定义

2025/5/25 12:42:44 来源:https://blog.csdn.net/GaoGuohao2022/article/details/136057380  浏览:    关键词:手机自助建站平台_软件开发工具有哪些_欧美网站建设公司_竞价排名的定义
手机自助建站平台_软件开发工具有哪些_欧美网站建设公司_竞价排名的定义

(最近把早些年零散写的题解收集整理一下)
关于这篇题解的背景:
先前已经有人用树(堆)写了题解,但是时间复杂度还没有达到极致。于是我写了这篇时间复杂度为 O ( n ) O(n) O(n) 的做法题解。

梳理题意

题目链接。
每次求剩下未被擦除的所有数字中,最大连续子段和的值且不能包含任何被擦除的位置。

解题思路

每次调用一遍最大连续子段和时间复杂度为 O ( n ) O(n) O(n) ,那么总的时间复杂度为 O ( n 2 ) O(n^2) O(n2),如果常数小一些能勉强过掉 60 % 60\% 60% 的数据;但是该题与最大连续子段和模板题的最大区别在于 1 ≤ a i ≤ 1 0 9 1≤a_i≤10^9 1ai109是的, a i a_i ai均为正数!
那么很明显我们每一次得到的最大连续子段一定与两个’X’(被擦掉的位置)或开头或结尾相邻,即尽可能向外延伸。
这时候我们也就没必要每一个区间都打一个最大值,仅记录符合上文要求的区间最大值即可。
但是又遇到了问题:每一次擦掉数字,区间会改变:一个区间分裂为两个区间。既要消除前者的影响,也要将答案再和后者取最大值。消除前者的影响至少需要对数级别的复杂度(可以使用平衡树),但那样不如直接使用线段树了。或许每一次都再打一遍最大值?那么成了 O ( n 2 ) O(n^2) O(n2) 算法,刚刚的分析也就没意义了。此时,“正难则反”,要从反面来思考问题。
从后向前,由擦除数字改为每次加入数字,于是就变成:两个区间合并为一个区间。由于前面所提 a i > 0 a_i>0 ai>0 ,所以两个子区间分别的和必然小于合并后区间的和,所以不会对答案产生影响,自然无需消除影响(想一想,为什么?)。
于是只需将合并后区间的和与当前最大值比大小。这样一遍加数一边打擂台就可以考虑到所有的需要考虑的区间。但是又应该如何在 O ( 1 ) O(1) O(1) 的复杂度完成区间的求和?
考虑 c n t [ i ] cnt[i] cnt[i] 代表区间号为 i i i 的区间和; C N T CNT CNT 代表当前使用过的最大区间号,因此再加一就是一个空余区间号(类似于链式前向星的计数器); L E F T [ i ] LEFT[i] LEFT[i] 表示区间号为 i i i 的区间向左能扩展到的位置(个人程序实现时为方便而-1); R I G H T [ i ] RIGHT[i] RIGHT[i] 表示区间号为 i i i 的区间向右能扩展到的位置(个人程序实现时为方便而+1); l [ i ] l[i] l[i] 表示向左能扩展到位置 i i i 的区间的区间号; r [ i ] r[i] r[i] 表示向右能扩展到位置 i i i 的区间的区间号;而 a n s [ i ] ans[i] ans[i] 表示在 p [ i ] p[i] p[i] 被擦掉以后的答案。
或许你已经注意到: l l l L E F T LEFT LEFT r r r R I G H T RIGHT RIGHT 是维护双射的数组。(在编写与调试此类问题时极需要耐心)

具体实现

于是逆序枚举:当每一个被擦掉的 p [ i ] p[i] p[i] “还原回来”,有以下操作:
1.更新 c n t cnt cnt 数组。每一个 p [ i ] p[i] p[i] 还原回来时都将原本两个独立的区间合并(可以为空),而 p [ i ] p[i] p[i] 则分别是这两个集合向左和向右能扩展到的位置,所以它们的区间号分别是 l [ p [ i ] ] l[p[i]] l[p[i]] r [ p [ i ] ] r[p[i]] r[p[i]] ,再加上 a [ p [ i ] ] a[p[i]] a[p[i]],因此:

cnt[++CNT]=cnt[l[p[i]]]+cnt[r[p[i]]]+a[p[i]];

2.维护 L E F T LEFT LEFT R I G H T RIGHT RIGHT 数组。在最开始时均初始化为 − 1 -1 1 。如果 L E F T [ r [ p [ i ] ] ] LEFT[r[p[i]]] LEFT[r[p[i]]] 为-1,说明原左侧区间为空,此时应当将现区间能扩展到的最左位置更新为 p [ i ] − 1 p[i]-1 p[i]1 ;否则不是空区间,那么最左端应该是左侧区间的左端即 L E F T [ r [ p [ i ] ] ] LEFT[r[p[i]]] LEFT[r[p[i]]] R I G H T RIGHT RIGHT 数组同理。于是得到:

if(LEFT[r[p[i]]]==-1)LEFT[CNT]=p[i]-1;
else LEFT[CNT]=LEFT[r[p[i]]];
if(RIGHT[l[p[i]]]==-1)RIGHT[CNT]=p[i]+1;
else RIGHT[CNT]=RIGHT[l[p[i]]];

3.维护 l l l r r r 数组。一开始均为 0 0 0 (我的区间号从 1 1 1 标起, 0 0 0 默认为空区间),由于 l l l L E F T LEFT LEFT r r r R I G H T RIGHT RIGHT 双向映射,而在第2步中 L E F T LEFT LEFT R I G H T RIGHT RIGHT 已经更新,所以只需要维护双射即可(若解释理解也可以,但是这样思考起来更方便):

l[LEFT[CNT]]=r[RIGHT[CNT]]=CNT;

4.更新 a n s ans ans 数组:

ans[i-1]=max(ans[i],cnt[CNT]);

总结反思

我的方法时间复杂度为 O ( n ) O(n) O(n) ,利用 a i > 0 a_i>0 ai>0 的条件避免冗余比较,总计只会考虑 n n n 个区间。虽然它只能处理 a i > 0 a_i>0 ai>0 ,而树(堆)的做法似乎没有限制,然而我相信出题人既然给出了 a i > 0 a_i>0 ai>0 的条件,那必定有它存在的价值,值得我们去探究,成为我们做题乃至优化的依据之一。一旦数据加到百万甚至千万级别, O ( n l o g n ) O(n\ log\ n) O(n log n) 的做法就有可能超时。最后,希望同学们不选得分最高的,要选最适合自己的算法!

注:修改于2024年8月6日。

#include <bits/stdc++.h>
#define int long long
using namespace std;int a[100010],n,p[100010],ans[100010],l[100010],r[100010],cnt[100010],CNT,LEFT[100010],RIGHT[100010];signed main(){cin>>n;for(int i=1;i<=n;++i){cin>>a[i];}for(int i=1;i<=n;++i)cin>>p[i];memset(LEFT,-1,sizeof(LEFT));memset(RIGHT,-1,sizeof(RIGHT));for(int i=n;i>=1;--i){cnt[++CNT]=cnt[l[p[i]]]+cnt[r[p[i]]]+a[p[i]];if(LEFT[r[p[i]]]==-1)LEFT[CNT]=p[i]-1;else LEFT[CNT]=LEFT[r[p[i]]];if(RIGHT[l[p[i]]]==-1)RIGHT[CNT]=p[i]+1;else RIGHT[CNT]=RIGHT[l[p[i]]];l[LEFT[CNT]]=r[RIGHT[CNT]]=CNT;ans[i-1]=max(ans[i],cnt[CNT]);}for(int i=1;i<=n;++i)cout<<ans[i]<<" ";return 0;
}

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com