洛谷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)\)

解法推导过程:

  1. 展开求和项:
    \((i+j)(a_i+a_j) = i \cdot a_i + i \cdot a_j + j \cdot a_i + j \cdot a_j\)

  2. 改变求和方式:
    直接处理 \(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\) 的和)。

    这两个数组 s1s2 是在第一个循环中预处理得到的,它们包含了整个序列的信息。

  3. 分析代码计算的表达式:
    \(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} \]

  4. 逐项分析:

    • 第一项: \(\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\)

  5. 合并三项:
    \(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\)

  6. 时间复杂度:

    • 预处理 s1s2 数组需要 \(O(N)\) 时间。
    • 计算最终答案的循环也需要 \(O(N)\) 时间。
    • 因此,总时间复杂度为 \(O(N)\)
  7. 模运算的注意事项:
    在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)\) 的总复杂度。代数变形是其核心。

简直是天书,感觉学算法最挫败的时候就是这种情况了

posted @ 2025-05-09 14:21  Chuan81  阅读(45)  评论(1)    收藏  举报