1 条题解
-
0
自动搬运
来自洛谷,原作者为

unicorn1220
这个家伙很菜,什么也没有留下搬运于
2025-08-24 23:06:57,当前版本为作者最后更新于2025-08-07 11:37:33,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
[JOI Open 2019] Triple Jump
题意
给定一个长为 的序列 。有 个查询,每次给定 ,求出满足以下条件的三元组 使得 最大。
题解
暴力枚举情况太多,所以考虑去掉某些无需枚举的情况。
什么时候可以扔掉一个情况 呢?当存在一个 一定不劣于 时。
这里的不劣于,需要考虑两个条件。第一, 的贡献一定要比 大。第二,如果 对于一组 合法,那么 也合法,即区间 被 包含。
也就是说,满足 $A_{i_1}+A_{j_1}+A_{k_1} \leq A_{i_2}+A_{j_2}+A_{k_2}$,且 。
然而,这种约束情况一不太好找,二过于苛刻,可能会导致去掉的情况不够多。
我们观察 这个式子,显然当我们钦定 时, 的取值范围是一个后缀,准确来说是 。这样的话,如果我们缩小区间 ,那么 的取值范围一定是只会变大的。
我们惊奇的发现,这个缩小后的区间,正好对应了 。于是我们把约束条件中的 去掉,变成 ,且 。这时, 的贡献保证了不劣,而因为 的区间 更小,所以 的取值范围更大,也就保证了不劣。
现在来看还剩下多少有用情况。上面的式子意味着对于 ,如果他们之间存在 使得 或 ,那么 就会被 替代,它就没用了。即有用区间可以表示为 使得 。我们令 为 之前最后一个比 大的 , 为 之后第一个比 大的 。此时,所有的 和 即可描述所有的有用情况,最多 种。
这么少的情况可以直接用线段树维护。对于所有有用情况 ,区间 的所有数都可以获得 的贡献,取 加上原本的数即为答案。而 可以随便用单调栈维护。加上查询总复杂度为 。
最后,在处理询问时,有用区间应该按左端点降序插入线段树,同时边插边把当前左端点的询问做了,因为我们要保证插入线段树的区间也在询问范围内。
代码放个主函数吧。
signed main() { scanf("%lld",&n); for(int i=1;i<=n;i++) scanf("%lld",&a[i]); build(1,1,n);scanf("%lld",&Q); for(int i=1,ai,bi;i<=Q;i++) scanf("%lld%lld",&ai,&bi),ask[ai].push_back(make_pair(bi,i)); for(int i=1;i<=n;i++) { while(!q.empty()&&a[i]>a[q.top()]) q.pop(); if(!q.empty()) qj[q.top()].push_back(i); q.push(i); }//找Li while(!q.empty()) q.pop(); for(int i=n;i>=1;i--) { while(!q.empty()&&a[i]>a[q.top()]) q.pop(); if(!q.empty()) qj[i].push_back(q.top()); q.push(i); }//找Ri for(int i=n;i>=1;i--)//倒着插入 { for(int j:qj[i]) update(1,1,n,2*j-i,n,a[j]+a[i]); for(auto j:ask[i]) ans[j.second]=query(1,1,n,i,j.first); } for(int i=1;i<=Q;i++) printf("%lld\n",ans[i]); return 0; }
- 1
信息
- ID
- 11113
- 时间
- 2000ms
- 内存
- 512MiB
- 难度
- 6
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者