洛谷P2671
P2671 [NOIP 2015 普及组] 求和
一道绿题 蓝桥杯能写出绿题的话我都稳国二了吧
因间隔y的存在所以x与z奇偶性必须相同
书上倒是写了暴力枚举会超时,不过这种题出现在比赛的话,先暴力拿下部分分再说吧,比赛时间其实很有限,反正OI赛制就得骗分啊
书上解法又是列表又是找规律算贡献的,看得头晕眼花,我找Gemini帮我解释了一遍
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int mod = 10007;
const int maxn = 1e5 + 10;
int n, m;
int a[maxn], b[maxn];//a数组存number, b数组存color
int s1[maxn][2], s2[maxn][2];
//s1存的是某种颜色且编号为奇数或偶数格子的数目
//s2存的是某种颜色且编号为奇数或偶数格子的数字之和
int ans;
int main() {
cin >> n >> m;
for(int i = 1; i <= n; i++) cin >> a[i];
for(int i = 1; i <= n; i++) {
cin >> b[i];
s1[b[i]][i % 2]++;
s2[b[i]][i % 2] = (s2[b[i]][i % 2] + a[i]) % mod;
}
for(int i = 1; i <= n; i++) {
int y = b[i];
ans += i * (s2[y][i % 2] + a[i] * (s1[y][i % 2] - 2) % mod) % mod;
ans %= mod;
}
cout << ans;
return 0;
}
看看Gemini怎么说
这个解法是针对洛谷 P2671 [国家集训队]求和 问题设计的。该问题要求计算以下和: $$S = \sum_{i=1}^{n} \sum_{j=i+1}^{n} \mathbb{I}(b_i = b_j \land (i \pmod 2) = (j \pmod 2)) \cdot (i+j) \cdot (a_i+a_j) \pmod{10007}$$ 其中 $\mathbb{I}(\text{condition})$ 是指示函数,当条件为真时为1,否则为0。$a_i$ 是第 $i$ 个物品的数字,$b_i$ 是第 $i$ 个物品的颜色。直接双重循环计算这个和的复杂度是 \(O(N^2)\),对于 \(N=10^5\) 来说太慢了。我们需要一个更高效的算法,通常是 \(O(N)\) 或 \(O(N \log N)\)。
解法推导过程:
-
展开求和项:
\((i+j)(a_i+a_j) = i \cdot a_i + i \cdot a_j + j \cdot a_i + j \cdot a_j\) -
改变求和方式:
直接处理 \(i<j\) 的约束比较复杂。我们尝试将求和式变形,使得可以对每个元素 \(k\) 单独计算其贡献或者某种累积值。
代码中的核心计算公式是:
ans += i * (s2[y][i % 2] + a[i] * (s1[y][i % 2] - 2) % mod) % mod;
其中:i是当前元素的下标。y = b[i]是当前元素的颜色。i % 2是当前元素下标的奇偶性。s1[y][p](代码中的s1[color][parity]) 存储颜色为y、下标奇偶性为p的元素的总个数。s2[y][p](代码中的s2[color][parity]) 存储颜色为y、下标奇偶性为p的元素的数字之和 (即 \(a_k\) 的和)。
这两个数组
s1和s2是在第一个循环中预处理得到的,它们包含了整个序列的信息。 -
分析代码计算的表达式:
令 \(C_k = b_k\) (颜色),\(P_k = k \pmod 2\) (下标奇偶性)。
\(N(C, P) = \sum_{l \text{ s.t. } b_l=C, l\%2=P} 1\) (即代码中的s1[C][P])
\(S_A(C, P) = \sum_{l \text{ s.t. } b_l=C, l\%2=P} a_l\) (即代码中的s2[C][P])代码计算的总和 \(S_{algo}\) 为:
\[S_{algo} = \sum_{k=1}^{n} \left( k \cdot \left( S_A(C_k, P_k) + a_k \cdot (N(C_k, P_k) - 2) \right) \right) \pmod{10007} \]展开后得到:
\[S_{algo} = \sum_{k=1}^{n} k \cdot S_A(C_k, P_k) + \sum_{k=1}^{n} k \cdot a_k \cdot N(C_k, P_k) - 2 \sum_{k=1}^{n} k \cdot a_k \pmod{10007} \] -
逐项分析:
-
第一项: \(\sum_{k=1}^{n} k \cdot S_A(C_k, P_k)\)
\(= \sum_{k=1}^{n} k \left( \sum_{l \text{ s.t. } b_l=b_k, l\%2=k\%2} a_l \right)\)
这个和是对所有满足条件 \((b_l=b_k, l\%2=k\%2)\) 的配对 \((k,l)\) 求 \(k \cdot a_l\)。
当 \(k=l\) 时,贡献是 \(\sum_{k=1}^{n} k \cdot a_k\)。
当 \(k \ne l\) 时,对于一对满足条件的 \((i,j)\) 且 \(i<j\),项 \(i \cdot a_j\) (当 \(k=i, l=j\)) 和 \(j \cdot a_i\) (当 \(k=j, l=i\)) 都会被加到和中。
所以,第一项可以写作: \(\sum_{i<j, \text{cond}} (i \cdot a_j + j \cdot a_i) + \sum_{k=1}^{n} k \cdot a_k\)。 -
第二项: \(\sum_{k=1}^{n} k \cdot a_k \cdot N(C_k, P_k)\)
\(= \sum_{k=1}^{n} k \cdot a_k \left( \sum_{l \text{ s.t. } b_l=b_k, l\%2=k\%2} 1 \right)\)
这个和是对所有满足条件 \((b_l=b_k, l\%2=k\%2)\) 的配对 \((k,l)\) 求 \(k \cdot a_k\)。
当 \(k=l\) 时,贡献是 \(\sum_{k=1}^{n} k \cdot a_k\)。
当 \(k \ne l\) 时,对于一对满足条件的 \((i,j)\) 且 \(i<j\),项 \(i \cdot a_i\) (当 \(k=i, l=j\)) 和 \(j \cdot a_j\) (当 \(k=j, l=i\)) 都会被加到和中。
所以,第二项可以写作: \(\sum_{i<j, \text{cond}} (i \cdot a_i + j \cdot a_j) + \sum_{k=1}^{n} k \cdot a_k\)。 -
第三项: \(-2 \sum_{k=1}^{n} k \cdot a_k\)。
-
-
合并三项:
\(S_{algo} = \left( \sum_{i<j, \text{cond}} (i \cdot a_j + j \cdot a_i) + \sum_{k=1}^{n} k \cdot a_k \right) + \left( \sum_{i<j, \text{cond}} (i \cdot a_i + j \cdot a_j) + \sum_{k=1}^{n} k \cdot a_k \right) - 2 \sum_{k=1}^{n} k \cdot a_k\)
\(S_{algo} = \sum_{i<j, \text{cond}} (i \cdot a_j + j \cdot a_i + i \cdot a_i + j \cdot a_j) + \left(\sum_{k=1}^{n} k \cdot a_k + \sum_{k=1}^{n} k \cdot a_k - 2 \sum_{k=1}^{n} k \cdot a_k\right)\)
后面三个带 \(\sum_{k=1}^{n} k \cdot a_k\) 的项正好抵消。
\(S_{algo} = \sum_{i<j, \text{cond}} (i \cdot a_i + i \cdot a_j + j \cdot a_i + j \cdot a_j)\)
\(S_{algo} = \sum_{i<j, \text{cond}} (i(a_i+a_j) + j(a_i+a_j))\)
\(S_{algo} = \sum_{i<j, \text{cond}} (i+j)(a_i+a_j)\)这正是原问题所要求的和 \(S\)。
-
时间复杂度:
- 预处理
s1和s2数组需要 \(O(N)\) 时间。 - 计算最终答案的循环也需要 \(O(N)\) 时间。
- 因此,总时间复杂度为 \(O(N)\)。
- 预处理
-
模运算的注意事项:
在C++中,负数的模运算结果可能也是负数(例如-5 % 3结果是-2)。
代码中的s1[y][i % 2] - 2可能会是负数 (例如当s1[y][i % 2]为1时,结果是-1)。
那么a[i] * (s1[y][i % 2] - 2)可能是负数。
X % mod如果X是负数,结果也是负数。
ans += term % mod; ans %= mod;如果ans是正数,term % mod是负数,那么ans可能在某一步变成负数。
虽然最终结果通常要求是[0, mod-1]区间,但如果中间步骤ans变为负数,ans % mod仍然是负数。
一个健壮的处理方法是确保每次加法和取模后结果都为非负:
long long term_factor = s1[y][i % 2] - 2;
long long val_prod = (1LL * a[i] * term_factor);// 1LL确保乘法不溢出 (虽然此题int范围够)
long long term1 = (val_prod % mod + mod) % mod;// 确保 term1 在 [0, mod-1]
long long current_sum_val = (s2[y][i % 2] + term1) % mod;
long long term_to_add = (1LL * i * current_sum_val % mod + mod) % mod;// 同样确保非负
ans = (ans + term_to_add) % mod;
最终输出ans即可,因为它通过这种方式会保持在[0, mod-1]。
不过,如果题目数据和编译器特性使得简单取模也能通过,那么原始代码可能也能AC。但在严谨的竞赛中,处理负数取模是必要的。
总结:该解法的巧妙之处在于将复杂的双重求和转换成了一个单重循环,并通过预计算颜色和奇偶性分组的元素个数及元素值之和,使得每个元素 \(i\) 的贡献可以在 \(O(1)\) 时间内计算出来,从而达到 \(O(N)\) 的总复杂度。代数变形是其核心。
简直是天书,感觉学算法最挫败的时候就是这种情况了

浙公网安备 33010602011771号