全区间函数值求和问题

摘要

    “全区间函数值求和”是一个常见的算法模式:给定数组 \(a[1..n]\),我们要对所有子数组 \([L, R] (1 ≤ L ≤ R ≤ n)\) 计算某个函数 \(f(L, R)\) 的值,并把它们相加,求:

\(\sum_{1 \leq L \leq R \leq n} f(L, R)\)

    这类问题在比赛和面试中非常常见。它的关键不在于穷举所有 \(O(n^2)\) 区间(这在 \(n ≤ 2e5\) 时不可行),而是找出 数学化、可组合或可分解 的技巧,把复杂的区间求和变成对元素(或位、或状态)的贡献统计。


核心思路:拆贡献 / 线性化

    别一上来就想着硬算所有区间,先问自己:能不能把 \(f(L, R)\) 拆成一些小块,然后每块分别统计它的“贡献”次数?

    想象一下,你在数乐高积木的总重量。直接把所有积木搭好再称?太累了。不如一个个积木称好,看看每个积木在多少个模型里出现过,再加起来。

常见两种拆法:

  • 线性可分型:比如子数组和。
  • 计数归约型(比如某个性质是否出现):例如 “某一位在区间 \(OR\) 中为 \(1\)” 或者 “区间 \(XOR\) 在某一位为 \(1\)” ,可以按位来算次数。

模板:

  1. 找到“原子”——最小的计算单元(元素 / 位 / 状态)。
  2. 对每个原子,数它在多少个区间里发挥作用(最大值、最小值、某位为 \(1…\))。
  3. 把所有原子的贡献累加,就是最终答案。

    常见的好搭档有:单调栈(找最大最小值)、双指针(滑动窗口统计)、哈希表(计数)、位运算(按位分治)等。


例 1:所有子数组的和

问题描述:

    给定一个长度为 \(n\) 的数组 \(a[1..n]\),你的任务是计算所有子数组的和的总和。

解题思路:

    这个问题中 \(f(L, R) = a_L + a_{L + 1} + …… + a_R \)。

    固定某个元素 \(a_i\),它会出现在多少个子数组中?左端点 \(L\) 有 \(i\) 种取法,右端点 \(R\) 有 \(n – i + 1\) 种取法,总出现次数:

\(c_i = i × (n – i + 1)\)

    于是总和:

\(S = \sum_{i=1}^{n} a_i \cdot i \cdot (n – i + 1)\)

    仅需一次遍历,复杂度 \(O(n)\)。

解决代码:

ll ans = 0;
for (int i = 1; i <= n; ++i)
    ans += a[i] * i * (n - i + 1);

    \(1 – based\) 版, \(ans\) 就为答案。


例 2:所有子数组的 XOR 和

题目链接:P3917 异或序列 – 洛谷

问题描述:

    给定一个长度为 \(n\) 的数组 \(a[1..n]\),对每个子数组进行异或计算,求所有子数组异或值求总和。

解题思路:

    这个问题中 \(f(L, R) = a_L \oplus a_{L + 1} \oplus …… \oplus a_R \)。

    按位拆分,利用前缀异或的性质来统计。

    先计算前缀异或数组:

\(pre[0] = 0, \quad pre[i] = pre[i-1] \oplus a[i]\)

    对于某一位 \(b\),子区间异或结果在这一位为 \(1\) 的条件是:

\(pre[R] \oplus pre[L-1] \quad \text{在第 } b \text{ 位为 } 1\)

    这等价于:

\(pre[R] \text{ 和 } pre[L-1] \text{ 在第 } b \text{ 位不同,一个是 } 0 \text{,一个是 } 1\)

    因此,统计所有前缀异或中该位为 \(1\) 的个数 \(cnt1\),和该位为 \(0\) 的个数 \(cnt0\)。

    每一个“\(1\)”都可以和每一个“\(0\)”组成一个有效区间,使得该位的子区间异或为 \(1\),因此贡献区间数为:

\(count_b = cnt0 \times cnt1\)

    该位的总贡献为:

\(count_b \times 2^b\)

    最终总和是所有位贡献的累加,复杂度为 \(O(n \cdot B)\),其中 \(B\) 是二进制位数。

解决代码:

vector<int> pre(n + 10, 0);
for (int i = 1; i <= n; ++i)
    pre[i] = pre[i - 1] ^ a[i];

ll ans = 0;
for (int i = 0; i < 31; ++i)
{
    ll cnt0 = 0, cnt1 = 0;
    for (int j = 0; j <= n; ++j)
    {
        if (pre[j] & (1 << i))
            ++cnt1;
        else
            ++cnt0;
    }

    ans += cnt0 * cnt1 * (1 << i);
}

    \(1 – based\) 版, \(ans\) 就为答案。


例 3:所有子串中任一字符最大出现次数之和

原题链接:https://codeforces.com/contest/2121/problem/G

问题描述:

    给定长度为 \(n\) 的二进制字符串 \(s = s_1 s_2 \cdots s_n\),定义函数 \(f(p)\) 为字符串 \(p\) 中某个字符出现的最大次数。例如:

  • \(f(00110) = 3\)(字符 ‘\(0\)‘ 出现 \(3\) 次)
  • \(f(01) = 1\)

    求所有子串 \(s_l s_{l+1} \cdots s_r\)(满足 \(1 \leq l \leq r \leq n\))的 \(f\) 值之和。

解题思路:

    题目要求我们计算所有区间的 \(f(s[l \dots r])\) 的值。

    根据题目描述,函数定义为:

\(f(s[l \dots r]) = \max(cnt_0, cnt_1)\)

    其中,\(cnt_1\)​ 是区间内字符 ‘\(1\)‘ 的个数,\(cnt_0\) 是区间内字符 ‘\(0\)‘ 的个数。

    通过:

\(\max(a, b) = \frac{a + b + |a – b|}{2}\)

    可得:

\(\max(cnt_0, cnt_1) = \frac{cnt_0 + cnt_1 + |cnt_0 – cnt_1|}{2}\)

    其中 \(cnt_0 + cnt_1\) 就是区间内全部元素个数既区间长度,由于区间长度为 \(len = r – l + 1\)

    可以将 \(f\) 转换为:

\(f = \frac{len + |cnt_1 – cnt_0|}{2}\)

    通过以上分析,问题就转化为两个子问题:

  • 求所有子数组长度的和,即 \(P(L, R)\);
  • 求所有子数组中两个元素差值绝对值的和,即 \(F(L, R)\)。

    这两个都是经典的全区间函数值求和问题。

求 P(L, R)

    所有长度为 \(i\) 的子数组数量是 \(n – i + 1\)。

    所以所有子数组长度之和为:

\(P(L, R) = \sum_{i=1}^n i \times (n – i + 1)\)

    这部分直接通过简单的数学求和即可。

求 F(L, R)

    先将字符串中字符 ‘\(1\)’ 赋值为 \(+1\),字符 ‘\(0\)’ 赋值为 \(-1\),
    构造前缀和数组 \(pre:\)

\(pre[i] = pre[i-1] + (s[i] == ‘1’ ? 1 : -1)\)

    那么任意区间的差值为:

\(cnt_1 – cnt_0 = pre[r] – pre[l-1]\)

    问题转化为求数组 \(pre\) 中所有两两元素差的绝对值之和。

    求数组中两两元素绝对值的差值也是一个非常经典的问题。计算方法:将 \(pre\) 数组排序,记排序后的数组仍为 \(pre\)。根据数学性质,对于排序数组,第 \(i\) 个元素对答案的贡献是:

\(pre[i] \times (2i – n)\)

    最终,将 \(P(L, R)\) 和 \(F(L, R)\) 相加除以 \(2\) 即得到所有区间的 \(f\) 的总和。

解决代码:

vector<ll> pre(n + 10, 0);
for (int i = 1; i <= n; ++i)
    pre[i] = pre[i - 1] + (s[i] == '1' ? 1 : -1);

ll ans = 0;
for (ll i = 1; i <= n; ++i)
    ans += i * (n - i + 1ll);

sort(pre.begin(), pre.begin() + n + 1);
for (ll i = 0; i <= n; ++i)
    ans += pre[i] * (i - (n - i));

ans /= 2;

    \(1 – based\) 版, \(ans\) 就为答案。


相关题目推荐:

1.P12177 [蓝桥杯 2025 省 Python B] 异或和 – 洛谷

2.码蹄集OJ-郭嘉破阵

3.https://codeforces.com/contest/2131/problem/F

4.码蹄集OJ-黛玉葬花

5.https://atcoder.jp/contests/abc423/tasks/abc423_e


暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇