LoginSignup
24
13

More than 5 years have passed since last update.

累積和について解説(C++)

Last updated at Posted at 2018-12-20

この記事は高知工科大学 Advent Calendar 2018の23日目の記事です.
ここでは,累積和についてザックリ解説します.
※証明等はやりません.

背景

去年のアドベントカレンダーで競プロを布教する記事を書きましたが,
次はレートが上がる楽しさを知ってもらいたいと思い,
AtCoder頻出かつ蟻本に無い累積和を紹介しようと思いました.

累積和

整数配列の連続する区間の合計を,O(1)で求められる手法です.
(準備にO(配列の長さ)かかります)
主に,区間の合計を何度も求める際に使用します.
※配列の中身が変わってしまう場合には使えません.セグメント木を使いましょう

例えば配列の長さがN,区間の合計を求める回数がMの場合,
愚直に計算すると計算量はO(NM)になりますが,
累積和を使用するとO(N+M)になります.

例題を使って解説

例題を解きながら学びます.

問題文

整数N, Mが与えられます.
N回だけ整数Aiが与えられます.
M回だけだけ整数L, Rが与えられます.
区間ALからARの合計をそれぞれ求めてください.

制約

\begin{align}
1 \leq N, M \leq 100000 \\
1 \leq L \leq R \leq N \\
1 \leq A_i \leq 10000 \\
\end{align}

入力例

4 1
5 2 7 1
2 4

出力例

10

考え方

愚直に計算すると最悪計算量はO(NM)→O(1010)なので間に合いません.
累積和を使いましょう.

まず,長さN+1の配列を作り,1オリジンで入力します.
スクリーンショット 2018-12-20 14.15.28.png
次に,配列を左から右に足していきます.
スクリーンショット 2018-12-20 14.20.05.png
これで準備完了です.
最後に,求めたい領域の(右端)-(左端より1つ左側)をします.
スクリーンショット 2018-12-20 14.34.22.png
これで求めたい領域の合計がすぐ求まります.

ソースコード

#include <bits/stdc++.h>
#define rep(i,n) for(int i=0;i<n;++i)
using namespace std;

int main() {
    int N, M, L, R;
    cin >> N >> M;
    int A[N+1]; A[0] = 0; // 1オリジン
    rep(i, N) cin >> A[i+1]; // input
    rep(i, N) A[i+1] += A[i]; // 累積和
    rep(i, M) {
        cin >> L >> R;
        cout << A[R] - A[L-1] << endl;
    }
    return 0;
}

応用問題?

これを累積和と言っていいのか分からないですが,
左から右に足していく問題で良いのがあったので紹介します.

問題文

机の上にリボンをいくつか重ね,ある場所に何個のリボンが重なっているかを求めたいです.
整数L, M, Nが与えられます.
Lは机の長さ,Mはリボンの数,Nは答えを求める回数です.
M回だけ整数l, rが与えられます.
lはリボンの開始座標,rはリボンの終了座標です.
N回だけ整数pが与えられるので,座標pにリボンは何個重なっているか求めてください.

制約

\begin{align}
1 \leq L, M, N \leq 100000 \\
1 \leq l \leq r \leq L \\
1 \leq p \leq L \\
\end{align}

入力例

4 3 1
1 2
2 3
1 4
3

出力例

2

考え方

リボンの領域すべてを+1する愚直な方法だと,O(LM)→O(1010)なのでアウトですね.
累積和を使ってO(M+N)にします.

まず,長さL+2の配列を作り,
リボンの(開始座標)を+1,(終了座標の1つ右)を-1します.
スクリーンショット 2018-12-20 15.03.00.png

次に,左から右に足していきます.
スクリーンショット 2018-12-20 15.08.51.png
すると各座標のリボンの数が求まります.

ソースコード

#include<bits/stdc++.h>
#define rep(i,n) for(int i=0;i<n;++i)
using namespace std;

int main() {
    int L, M, N, l, r, p;
    cin >> L >> M >> N;
    int A[L+2]; rep(i, L+1) A[i] = 0; // init
    rep(i, M) {
        cin >> l >> r;
        A[l]++; A[r+1]--;
    }
    rep(i, L) A[i+1] += A[i];
    rep(i, N) {
        cin >> p;
        cout << A[p] << endl;
    }
    return 0;
}

おわりに

他にも役立つアルゴリズムは沢山あるので,皆さんも競プロやってみませんか?

24
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
24
13