
配点: 300 点 / 実行時間制限: 2 sec / メモリ制限: 1024 MB / Difficulty: ??? / NoviSteps: 4Q
問題概要
長さ の整数列
が与えられるので、
の値を求めよ。
制約
- 入力は全て整数。
考察
二重シグマを愚直に計算していたのでは、 の計算量となってしまい TLE する。
これを避けるためには、どうにかして計算量を改善しなければならない。様々な考え方ができるが、ここでは汎用的な2つの例を書くことにする。
累積和を用いる
として考えてみると、求める答えは以下のように工夫して計算できる。
括弧でくくった部分は累積和の形となり、この部分は定数時間で計算できるようになる。
を一般の場合に変えた場合のアルゴリズムは以下の通り。
- 答え用の変数
と、累積和用の変数
をそれぞれ
で初期化する。
について、以下の処理を行う。
なお、今回は累積和テーブルの区間和が1個ずつ変更されないので、「前処理 で累積和テーブルを構築する」ようなことはしていない。
オーバーフローに注意すること。
式変形から導く
高校数学では、以下のような変形をよく用いたと思う。
これを一般化すると、以下のようになる(証明は割愛するが、帰納法で可能だと思う)。
あとは、これをオーバーフローに注意しつつ実装すればよい。
実装例
以下のコードは 0-indexed である。
考察1
#include <bits/stdc++.h> using namespace std; #define rep(i, start, end) for (auto i = (start); (i) < (end); (i)++) using ll = long long; // ======================================== // int main() { int N; cin >> N; vector<ll> A(N); rep(i, 0, N) cin >> A[i]; ll ans = 0, sum = A[0]; rep(i, 1, N) { ans += A[i] * sum; sum += A[i]; } cout << ans << endl; return 0; }
考察2
#include <bits/stdc++.h> using namespace std; #define rep(i, start, end) for (auto i = (start); (i) < (end); (i)++) using ll = long long; // ======================================== // int main() { int N; cin >> N; vector<ll> A(N); rep(i, 0, N) cin >> A[i]; ll sum = 0, sq_sum = 0; rep(i, 0, N) { sum += A[i]; sq_sum += A[i] * A[i]; } ll ans = (sum * sum - sq_sum) / 2; cout << ans << endl; return 0; }
実装時間: 5分以内
コメント
この問題とほぼ同じ問題が、 ABC177-C で出題されている。
知っていれば瞬殺できるので、二重シグマ系の問題は一通りまとめておきたい。
今回の類題パターンは以下の記事にまとまっていたので紹介する。