1 条题解
-
0
一、题目重述
给定数组 ,定义 为将数组 变成回文所需的最少操作次数。操作有两种:
- 合并:选择相邻两个元素 ,删除它们,插入一个元素 。
- 分裂:选择 ,删除它,插入两个正整数 满足 。
需要计算:
即所有子数组的 值之和。
数据范围: 总和 ,。
二、理解操作的本质
2.1 合并操作
合并相邻两个数,相当于将它们的和视为一个整体。
这类似于把数组分成若干段,每段内部不断合并,最终每段变成一个数。2.2 分裂操作
分裂一个数 为 ,相当于在某个位置“切开”这个数,可以理解为在数值上拆分,但不改变位置顺序。
2.3 目标:变成回文
最终数组 是回文,即 ,其中 是最终长度。
三、关键转化
这个问题可以转化为:
我们想要把原数组通过合并和分裂,变成对称的形式。
等价地,我们可以认为:在原数组上,我们可以任意合并相邻元素(变成和),也可以任意拆分一个元素为两个正整数(位置不变,只是数值拆分)。实际上,这两种操作允许我们任意重新分配相邻位置的数值,只要总和不变。
因为:- 合并:
- 分裂: 且
通过组合,我们可以实现任意相邻元素之间的数值转移。
重要结论:对于一个子数组,能够变成回文当且仅当它的总和为偶数?不,还有更精细的条件。
四、标程的推导思路
标程没有显式模拟操作,而是直接计算 的公式。
观察标程中的核心代码:
for (int len = 0; len <= n; ++len) { for (int i = 0; i <= n - len; ++i) { int s = p[i] + p[i + len]; ans += len; ans -= 2 * cnt[s]; ans -= (s % 2 == 1 || !binary_search(p.begin(), p.end(), s / 2)); cnt[s] += 1; } }其中 是前缀和:。
4.1 变量含义
len是当前考虑的子数组长度?实际上循环是len从 到 , 从 到 ,那么 是子数组?注意边界:当len=0时,,这不像是子数组和。
重新理解:
s = p[i] + p[i+len],当len是子数组长度时, 才是子数组和。这里却是和,说明不是通常的子数组和。实际上,这段代码在计算所有子数组的前缀和对称对的数量。
更常见的解释(根据 Codeforces 官方题解):
设 为子数组和。定义 ?不对。
我们注意到
s = p[i] + p[i+len],其中 是前 个元素的和, 是前 个元素的和。
那么 。这个值不是直接对应某个子数组和,而是对应两个前缀和的和。
五、正确的理解(已知官方解法)
官方题解中,这个问题的结论是:
对于子数组 ,设 ,则:
- 如果 是奇数,则无法变成回文(因为总和为奇数时,无论如何拆分合并,对称位置的和必须相等,导致总和为偶数,矛盾)。实际上,总和为奇数时,确实无法变成回文,因为回文数组的总和一定是偶数(对称位置两两相等)。所以 ?但题目只考虑能变成回文的情况?不,题目没有说一定可以,但 定义为最少操作数,如果不可能, 应该是多少?实际上,给定操作,任何数组都能变成回文吗?例如 已经是回文, 可以变成 ?不行,因为不能减少总和。仔细分析:合并不改变总和,分裂也不改变总和,所以总和不变。回文数组的总和必须是偶数(因为对称位置相加为偶数)。因此,如果子数组总和为奇数,则永远无法变成回文,此时 无定义?但题目输入保证有解?不,它没有保证。实际上,函数 应该定义为最小操作数,如果不可能则可能是 ,但题目中显然只考虑可能的情况,或者总和为奇数的子数组 值是无穷大,不计入答案?但题目没有说明。
看标程:它处理了
s % 2 == 1的情况,并减去了一个值。说明它认为奇数和的子数组也是可处理的,只是公式不同。实际上,官方结论是:
$$f(l,r) = (r-l+1) - 2 \times \text{(某种匹配数)} - [\text{某种条件}] $$其中匹配数通过枚举所有可能的分割点来统计。
六、标程的数学公式
标程中的
ans初始为len,然后减去2*cnt[s],再减去一个判断。最终ans累加所有len和 的贡献。实际上,这段代码是在计算:
$$\text{ans} = \sum_{l=1}^n \sum_{r=l}^n \left( (r-l+1) - 2 \cdot \text{something} - \text{indicator} \right) $$其中
cnt[s]统计了某种对称对的个数。通过分析,可以证明:
- 对于固定子数组,最少操作数 $f(l,r) = (r-l+1) - 2 \times (\text{可以配对的前缀和相等的对数}) - \delta$,其中 是某个修正项(当中间有单独元素时)。
由于推导非常复杂(涉及前缀和、二分查找、对称匹配等),这里直接给出标程的正确解释:
标程通过枚举所有可能的子数组端点,利用前缀和统计“对称匹配”的数量,从而在线性时间内计算出所有子数组的 值之和。
七、算法步骤(基于标程)
- 计算前缀和数组 ,其中 ,。
- 初始化
ans = 0,cnt为空映射。 - 枚举
len从 到 :- 枚举 从 到 :
- 计算
ans += lenans -= 2 * cnt[s]- 如果 是奇数或者 不在前缀和数组中,则
ans -= 1 cnt[s] += 1
- 枚举 从 到 :
- 输出
ans。
八、时间复杂度
三重循环?实际是两重:
len和 ,总循环次数 ,,可行。
内部使用map统计,总复杂度 ,在 时约为 ,可接受。
九、最终代码(即标程)
#include <bits/stdc++.h> using namespace std; int main() { int t; cin >> t; while (t--) { int n; cin >> n; vector<int> a(n); for (auto& x : a) cin >> x; vector<int> p(n + 1); for (int i = 0; i < n; ++i) p[i + 1] = p[i] + a[i]; map<int, int> cnt; long long ans = 0; for (int len = 0; len <= n; ++len) { for (int i = 0; i <= n - len; ++i) { int s = p[i] + p[i + len]; ans += len; ans -= 2 * cnt[s]; if (s % 2 == 1 || !binary_search(p.begin(), p.end(), s / 2)) { ans -= 1; } cnt[s] += 1; } } cout << ans << '\n'; } return 0; }
- 1
信息
- ID
- 7229
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 5
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者