给定一个除了 之外,最多存在一个 的数的序列,求其子段和的所有可能值,从小到大输出。
很容易就去思考如何从这个特殊的 入手。于是先排除这个特例,考虑全都是 的情形,那么顺序从左到右不断加入 ,可以发现可以通过维护当前值域的上下界来解决问题。最后我们对于 之前的数统计一遍, 之后的数倒序统计一遍,然后通过 把两边合并起来就好了。
赛时就是这么想的,思路也大体正确了。
但是存在的一个问题是,值域中的某些数,可能并不能和当前这个 拼接到一起,因为它可能不是以 结尾的。比赛的时候是这么写的,很麻烦,最后也没有调出来。
不妨换一个角度思考,一个 的子段和可以表示为 ,由于 比较特殊,我们还是像上文提到的那样对于 的两边分别统计,最后单独考虑 。
这样对于一个固定的 来说, 的取值一定是在整数意义下连续的。因为任意的 和 相比,绝对值只会相差 ,这就是 的带来的特殊性。(这段比较抽象,建议画一个 的散点图来理解)
所以只需要考虑前缀和的前缀 和 就可以了,对于一个 , 之间的所有整数一定都是可以取到的,这样我们就成功计算出了以 为结尾的子段值域。
注意,这仅仅是以 为结尾的值域情况,我们不断维护它,是为了到最后能和 拼接起来,但是如果直接把它当成答案,肯定会有所遗漏,因为答案子段的结尾并不仅仅只是一个固定的元素,因此,对于所有算出来的值域,我们要取并集。
至于这个取并集过程的实现,观察到相邻两个元素的值域相差不会太大,我们只需要把每次更新值域后 被遗漏的部分提前标记为 true 就可以了。当然,在差分数组上操作,最后前缀和一遍的 trick 也是可以的。
以上是对于 左侧的计算方法,对于 的右侧来说,倒序枚举如法炮制即可。
记 对应的下标为 。
假设左边以 为结尾的值域是 ,右边以 为结尾的值域是 。
很明显的是,最后能够取到的就是 ,把这一部分并进答案即可。
我个人的实现方法过于抽象,不过最后还是过了就行。
记得要单独把 也标记为 true。
| #include<bits/stdc++.h> |
| using namespace std; |
| int T,n; |
| const int N=2e5+10; |
| int a[N]; |
| set<int> les,mor; |
| vector<int> output; |
| inline void out(set<int> &s){for(int x:s)cout<<x<<' ';} |
| inline void solve() |
| { |
| cin>>n; |
| les.clear(),mor.clear(),output.clear(); |
| vector<int> vis(2*n+10,0); |
| auto upd=[&](int l,int r) |
| { |
| for(int i=l;i<=r;++i) |
| { |
| if(i<-n)les.insert(i); |
| else if(i>n)mor.insert(i); |
| else vis[i+n]=1; |
| } |
| }; |
| for(int i=1;i<=n;++i)cin>>a[i]; |
| int pos=n+1>>1; |
| for(int i=1;i<=n;++i)if(a[i]!=1&&a[i]!=-1){pos=i;break;} |
| int l=0,r=0,s=0; |
| int mx=0,mn=0; |
| for(int i=1,nowl,nowr;i<pos;++i) |
| { |
| s+=a[i]; |
| nowl=s-mx,nowr=s-mn; |
| for(int j=l;j<nowl;++j)vis[j+n]=1; |
| for(int j=r;j>nowr;--j)vis[j+n]=1; |
| mx=max(mx,s),mn=min(mn,s); |
| l=nowl,r=nowr; |
| } |
| int L=0,R=0; |
| s=0,mx=0,mn=0; |
| for(int i=n,nowl,nowr;i>pos;--i) |
| { |
| s+=a[i]; |
| nowl=s-mx,nowr=s-mn; |
| for(int j=L;j<nowl;++j)vis[j+n]=1; |
| for(int j=R;j>nowr;--j)vis[j+n]=1; |
| mx=max(mx,s),mn=min(mn,s); |
| L=nowl,R=nowr; |
| } |
| int x=a[pos]; |
| upd(l,r); |
| upd(L,R); |
| upd(x,x); |
| upd(l+x,r+x); |
| upd(L+x,R+x); |
| upd(l+L+x,r+R+x); |
| vis[n]=1; |
| for(int i=-n;i<=n;++i)if(vis[i+n])output.push_back(i); |
| cout<<les.size()+output.size()+mor.size()<<'\n'; |
| out(les); |
| for(int val:output)cout<<val<<' '; |
| out(mor); |
| cout<<'\n'; |
| } |
| int main() |
| { |
| ios::sync_with_stdio(0); |
| cin.tie(0),cout.tie(0); |
| cin>>T; |
| while(T--)solve(); |
| return 0; |
| } |
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!