1 条题解
-
0
题目翻译
E. 另一种折叠纸条
每次测试的时间限制:2秒
每次测试的内存限制:256兆字节对于一个长度为 的数组 ,定义 如下:
考虑一个 的纸条,初始时所有格子的暗度为 。
你想把它变成第 个位置的暗度为 的纸条。
你可以进行如下操作,每次操作包含两步:- 在任意两个格子之间的线处折叠纸条。你可以折叠任意多次,也可以不折叠。
- 选择一个位置滴下黑色染料。染料从顶部渗透到底部,使其路径上的所有格子的暗度增加 。滴完染料后,你将纸条展开。
设 为达到目标配置所需的最少操作次数。可以证明,目标总是可以在有限次操作内实现。
现在给你一个长度为 的数组 。计算
$$\sum_{l=1}^{n} \sum_{r=l}^{n} f(a_l a_{l+1} \dots a_r) $$并对 取模。
题目理解与题解
1. 操作的本质理解
这个操作非常巧妙。让我们理解一下:
- 折叠:可以在任意位置折叠纸条,可以多次折叠。折叠后,纸条的某些格子会重叠在一起。
- 滴染料:选择一个位置滴染料,染料会向下渗透,增加当前可见的所有重叠格子的暗度。
- 展开:展开后,每个格子得到它被折叠时所在位置的所有染料贡献。
关键观察:一次操作相当于选择一组位置(在折叠后重叠在一起的位置),给这些位置都加上 。
2. 折叠的数学本质
如果我们把折叠看作是对纸条进行某种"折叠映射",那么一次操作就是:
- 选择一个折叠方案(即选择一种将 映射到某个更小区间的方式)
- 在映射后的某个位置滴染料,相当于给映射到该位置的所有原位置都
经过多次操作,每个位置 的最终暗度等于:有多少次操作中,位置 被映射到了滴染料的位置。
3. 关键结论
结论1:对于任意数组 , 等于 中所有正数的和?不对,因为一次操作可以给多个位置加 。
结论2:实际上,这个问题等价于:每次操作可以选择一个"对称区间"(关于折叠中心对称的区间)并给其中的所有位置加 。但折叠可以多层,所以更准确地说:
一次操作可以给任意一组满足某种对称性的位置加 。经过分析,这个问题的经典结论是:
等于 中所有正数的和减去可以"配对"消除的部分。更精确地说,?也不对。
让我们重新思考:考虑最简单的情况:
- 如果 ,
- 如果 ,
- 如果 ,(可以折叠中间,滴一次染料)
- 如果 ,(折叠两端重叠,滴一次)
4. 正确的理解
实际上,这个问题是 Codeforces 上的一个经典问题。经过推导,核心结论是:
等于将 中所有相邻的相同数字配对后,剩余数字的个数。
但更准确地说:考虑将数组 看作一个序列,每次操作可以:
- 选择一个中心点
- 给所有关于该中心对称的位置加
这意味着:如果两个位置在多次折叠后能重叠,那么它们必须是关于某个中心对称的。
经过深入分析,这个问题的标准解法是:
等于 中所有正数的最大值? 让我们验证:
- :最大值 , ✓
- :最大值 ,但示例说 ✓
- :最大值 , ✓
- :最大值 , ✓
但考虑 :最大值 ,但我们可以一次操作给两个位置都加 吗?可以,折叠使它们重叠,然后滴一次染料,所以 。最大值 ✓
考虑 :最大值 ,(需要两次操作,因为一次只能加 )✓
所以 !因为:
- 每次操作最多给任意位置加 ,所以至少需要 次操作
- 可以通过折叠使所有需要达到 的位置重叠,然后滴 次染料
等等,这个推理有问题:如果 ,最大值 ,但 是多少?位置2需要2,位置1需要1。我们可以:第一次操作折叠,使位置1和2重叠,滴一次,得到 ;第二次操作不折叠,只在位置2滴一次,得到 。所以 ,等于最大值。
考虑 :同样 。
考虑 :最大值 ,我们可以折叠两端重叠,滴两次染料,得到 。所以 。
结论: 对于所有非负整数数组成立!
验证示例2: 最大值 , ✓
5. 问题转化
所以原问题变成:
$$\sum_{l=1}^{n} \sum_{r=l}^{n} \max(a_l, a_{l+1}, \dots, a_r) $$即:求所有子数组的最大值之和。
6. 求解所有子数组的最大值之和
这是一个经典问题。我们可以用单调栈在 时间内解决。
对于每个位置 ,找到:
- :左边第一个大于 的位置(严格大于,避免重复计算)
- :右边第一个大于等于 的位置(大于等于,同样避免重复)
那么以 为最大值的子数组个数为:
贡献为:
7. 算法步骤
- 使用单调栈从左到右计算
- 使用单调栈从右到左计算
- 计算每个位置的贡献并求和
- 对 取模
时间复杂度: 每个测试用例
空间复杂度:
C++ 代码实现
#include <bits/stdc++.h> using namespace std; const int MOD = 998244353; void solve() { int n; cin >> n; vector<int> a(n); for (int i = 0; i < n; i++) { cin >> a[i]; } vector<int> left(n), right(n); stack<int> st; // 计算左边第一个大于 a[i] 的位置 for (int i = 0; i < n; i++) { while (!st.empty() && a[st.top()] <= a[i]) { st.pop(); } left[i] = st.empty() ? -1 : st.top(); st.push(i); } while (!st.empty()) st.pop(); // 计算右边第一个大于等于 a[i] 的位置 for (int i = n - 1; i >= 0; i--) { while (!st.empty() && a[st.top()] < a[i]) { st.pop(); } right[i] = st.empty() ? n : st.top(); st.push(i); } long long ans = 0; for (int i = 0; i < n; i++) { long long cnt = (long long)(i - left[i]) * (right[i] - i) % MOD; ans = (ans + (long long)a[i] % MOD * cnt) % MOD; } cout << ans << "\n"; } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int t; cin >> t; while (t--) { solve(); } return 0; }
- 1
信息
- ID
- 6369
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者