前缀和的应用

前言

作者本以为前缀和非常简单,但是近来遇到了很多题。发现存在非常多的应用,写这篇博客用于作者本人归纳应用以及供读者学习和总结。

区间和的静态询问

对于前缀和数组 ,这个应用就非常的常见且普遍。主要是用于优化区间和的静态询问。

#include 
#include 
#include 
#include 

#define LL long long
#define PII std::pair<int ,=“” int=“”>

int main(){

std::ios::sync_with_stdio(0);
std::cout.tie(0);
std::cin.tie(0);

int n;
std::vector<ll> A(n + 1);
for (int i = 1; i &lt;= n; i ++ )
    std::cin &gt;&gt; A[i];

for (int i = 1;  i &lt;= n; i ++ ){
    A[i] = A[i] + A[i - 1];

    std::cout &lt;&lt; A[i] &lt;&lt; '\n';
}

return 0;

}

例题:
求区间和 - 洛谷
求和 - 洛谷

区间子段中 0 出现的次数

对于这个应用是非常的经典的,主要目的就是求连续的子段中0出现了多少次 主要是思想就是利用前缀和的值的变化 ,即对前$i$个元素求和, 如果出现 $sum_l == sum_r 且 l < r$ , 那么 区间$[l + 1 , r]$ 为 0。

注意: 整段数组为0的情况 , 这个情况需要注意一下。

下面给出$O(nlogn)$ 做法

#include 
#include 
#include 
#include 
#include 

const int N = 510 , INF = 0x3f3f3f3f;

#define LL long long
#define PII std::pair<int ,=“” int=“”>

void solved(){

int n;
std::cin &gt;&gt; n;
std::vector<ll> A(n + 1);
for (int i = 1; i &lt;= n; i ++ )
    std::cin &gt;&gt; A[i];

std::map<ll ,="" bool=""> mp;
mp[0] = 1;                                              // 注意整个序列为 0 的情况
for (int i = 1; i &lt;= n; i ++ ){
    A[i] = A[i] + A[i - 1];
    if (mp[A[i]]){
        std::cout &lt;&lt; "Yes" &lt;&lt; '\n';
        return ;
    }else
        mp[A[i]] = 1;
}

std::cout &lt;&lt; "No" &lt;&lt; '\n';

}

int main(){

std::ios::sync_with_stdio(0);
std::cout.tie(0);
std::cin.tie(0);

int tc = 1;
std::cin &gt;&gt; tc;
while (tc --)
    solved();

return 0;

}

这是map 红黑树的做法 ,利用树形结构来做到$O(logn)$的查询 , 明显我们不需要修改,只需要插入和查询。我们在之前可能学习过一个数据结构,主要支持查询和插入哈希表 , 通过hash到一个位置插入 , 如果有冲突可能是使用拉链法或者是开放寻址法。我们可以直接使用$STL$自带的$std::unordered_map$

对于unordered_map 不熟悉的点这里

下面给出$O(n)$做法:

注意: 使用优化的hash算法

#include 
#include 
#include 
#include 
#include 
#include 

const int N = 510 , INF = 0x3f3f3f3f;

#define LL long long
#define PII std::pair<int ,=“” int=“”>

struct custom_hash {
static LL splitmix64(LL x) {
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
return x;
}
size_t operator () (LL x) const {
static const LL FIXED_RANDOM = std::chrono::steady_clock::now().time_since_epoch().count(); // 时间戳
return splitmix64(x + FIXED_RANDOM);
}
};

void solved(){

int n;
std::cin &gt;&gt; n;
std::vector<ll> A(n + 1);
for (int i = 1; i &lt;= n; i ++ ){
    std::cin &gt;&gt; A[i];
    A[i] *= (i &amp; 1) ? 1 : -1;
}

std::unordered_map<ll ,="" bool="" custom_hash=""> mp;
mp[0] = 1;
for (int i = 1; i &lt;= n; i ++ ){
    A[i] = A[i] + A[i - 1];
    if (mp[A[i]]){
        std::cout &lt;&lt; "Yes" &lt;&lt; '\n';
        return ;
    }else
        mp[A[i]] = 1;
}

std::cout &lt;&lt; "No" &lt;&lt; '\n';

}

int main(){

std::ios::sync_with_stdio(0);
std::cout.tie(0);
std::cin.tie(0);

int tc = 1;
std::cin &gt;&gt; tc;
while (tc --)
    solved();

return 0;

}
</unordered_map>

题目:Problem - E - Codeforces

求连续子段中x出现的次数

对于这个问题,我们可以直接转化成连续子段中0出现次数,或者在这个基础上改进一下即可。

mp[A[i] + x]  // 是否出现过

最大子段和

这种应用是结合动态规划的思想完成的应用,用于求最大子段和。

核心思想就是如果当前的子段和小于0, 那么必然不能对后面的子段产生正增益, 所以需要抛弃。 我们只需要在这些子段中求出一个最大值即可。 为什么要在众多子段和中取最大,而不是最后一个子段的和就是最大的呢? 因为做到后面就一定要选后面的,如果不选后面的,肯定都没有做完,肯定是不能确定,最大子段和的。 可能最大子段和在前面,而不是后面。

例如:

1 2 3 -4 -5

参考代码:

#include 
#include 
#include 
#include 
#include 

const int N = 510 , INF = 2e9 + 10;

#define LL long long
#define PII std::pair<int ,=“” int=“”>

void solved(){

int n;
std::cin &gt;&gt; n;
std::vector<int> A(n + 1);
for (int i = 1; i &lt;= n; i ++ )
    std::cin &gt;&gt; A[i];

LL max_sum = -INF , cur_sum = 0;            // max_sum 一定取的足够小
for (int i = 1; i &lt;= n; i ++ ){
    cur_sum += A[i];
    max_sum = std::max (max_sum , cur_sum);

    if (cur_sum &lt; 0)
        cur_sum = 0;
}

std::cout &lt;&lt; max_sum &lt;&lt; '\n';

}

int main(){

std::ios::sync_with_stdio(0);
std::cout.tie(0);
std::cin.tie(0);

int tc = 1;
// std::cin &gt;&gt; tc;
while (tc --)
    solved();

return 0;

}

题目:
最大子段和
Problem - A - Codeforces

环形最大子段和

考虑破环为链即可 ,将序列展成两倍长度,做一遍即可。 对于破环为链不熟悉的,请自查一下。

#include 
#include 
#include 
#include 

const int N = 510 , INF = 2e9 + 10;

#define LL long long
#define PII std::pair<int ,=“” int=“”>

void solved(){

int n;
std::cin &gt;&gt; n;
std::vector<int> A(n);
for (int i = 0; i &lt; n; i ++ )
    std::cin &gt;&gt; A[i];

A.insert(A.end() , A.begin() , A.end());

LL max_sum = -INF , cur_sum = 0;            // max_sum 一定取的足够小
for (int i = 0; i &lt; int(A.size()); i ++ ){
    cur_sum += A[i];
    max_sum = std::max (max_sum , cur_sum);

    if (cur_sum &lt; 0)
        cur_sum = 0;
}

std::cout &lt;&lt; max_sum &lt;&lt; '\n';

}

int main(){

std::ios::sync_with_stdio(0);
std::cout.tie(0);
std::cin.tie(0);

int tc = 1;
// std::cin &gt;&gt; tc;
while (tc --)
    solved();

return 0;

}


这是一个从 https://juejin.cn/post/7368777511952957466 下的原始话题分离的讨论话题