差分更新系の教育的問題かな
問題概要
長さ の数列が与えらえる。各 index k に対して、
- 数列から k 番目の要素を除いたものについて
- その中から異なる 2 つの要素を選ぶ方法であって (順番は問わず)
- その 2 つの要素の選び方が等しい
という選び方が何通りあるのかを求めよ。
制約
考えたこと
愚直に毎回の数列について計算したのでは の計算量を要してしまう。今回のような、各 について、それを除いたものについて考えるタイプの問題では
- まず全体の数列についての答えを求めておいて
- 各要素を取り除くことによる「影響」を計算し、
- その影響分を引く
という風にするのが定石。その影響を計算することをしばしば「差分更新」と呼んだりする。つまり、差分だけ高速に計算してしまおうという発想だ。
全体
何にしてもまずは、全体の数列について答えを求める必要がある。でもこれは簡単だ。
- nums[ v ] := 値 v が何個あるか
という配列を用意してあげて、各値 v について、
- = nums[ v ] として
- を加算する
という風にして求めることができる。
差分更新
ここまでわかれば差分計算も簡単だ。値 v を除くとき、影響があるのは nums[ v ] の部分だけだ。よって、その部分についてのみ、before / after を計算して補正すればよい。具体的には、
- = nums[ v ] として
- before:
- after:
という風になる。答えは、(全体) - before + after となる。計算量は全体で でできる。
#include <bits/stdc++.h> using namespace std; int N; vector<long long> a; void solve() { map<long long, long long> ma; for (int i = 0; i < N; ++i) ma[a[i]]++; long long res = 0; for (auto it : ma) res += it.second * (it.second - 1) / 2; for (int i = 0; i < N; ++i) { long long val = ma[a[i]]; long long before = val * (val - 1) / 2; long long after = (val - 1) * (val - 2) / 2; cout << res + after - before << endl; } } int main() { cin >> N; a.resize(N); for (int i = 0; i < N; ++i) cin >> a[i]; solve(); }