概要
数え上げ問題は貢献度を使った解答が多い気がするのでもう1つ解説
問題はこちらABC290E
自分なりの考え方を整理して実装してみる。
考察
問題を下記のように言い換える。
数字の配列Xが存在し、そのうち異なる2つの数字のペア(l,r)を選択する。
そのペアが解答にどれくらい貢献するかを考える。
AtCoderの例を考える。ここではl=X[1]とr=X[2]の数字を選択してみる。
5 2 1 2 2
このときの貢献数は2。なぜなら下記の太字の部分文字列を選んだ場合の2ケースが解答に貢献するため。
5 2 1 2 2
5 2 1 2 2
これは要するに選んだ数字から一番近い数列の端までの距離が貢献数になることを意味する。
数式で表すとmin(l+1, N-r)
maxやminが出てきた場合は場合分けすると解きやすい
l+1が貢献数になるのはlの位置の方がrよりも数列の端に近い場合(l+1 <= N-r
)。
lを固定するとr <= N-l-1
。rがN-l-1
より小さいもしくは同じ場合にl+1
が貢献数となる。
lを決めたときの全てのrに対する貢献数を計算する
lを固定すると貢献数がl+1
になるrの範囲はr <= N-l-1
よって貢献数は(l+1) * [l+1,N-l-1]の範囲にあるA[l]でない数字の数
これを全てのlに対して行うことで解答が求められる。
N-r
が貢献数となる場合は上記の数列をreverseすれば同じ要領で解くことが可能。
配列の中で区間[l,r]の間でXではない数字の個数を求める
これは各数字に対するインデックスの配列を作っておくことで二分探索を使って求めることが可能。
例: A = [5 2 1 2 2]
数字2に対するインデックス配列 P = [1 3 4]
Aの2<=i<=4に存在する数字2の個数は次の要領で計算が可能
upper_bound(P.begin(), P.end(), 4) - lower_bound(P.begin(), P.end(), 2)
よって区間に存在する数字の個数から上記の数を引くことでXではない数字の数が求まる。
実装
long long solve(vector<int>& x) {
int n = x.size();
map<int, vector<int>> index_array_by_number;
for (int i=0; i<n; i++) {
index_array_by_number[x[i]].push_back(i);
}
long long ret = 0;
for (long long l=0; l<n/2; l++) {
int r = n - l - 1;
// 要素がx[l]でないものの個数を数える
vector<int>& v = index_array_by_number[x[l]];
// [l+1,r]の範囲でx[l]と同じものの個数
int same_count = upper_bound(v.begin(), v.end(), r) - lower_bound(v.begin(), v.end(), l+1);
// [l+1,r]の範囲でx[l]でないものの個数
int diff_count = r - l - same_count;
ret += (l+1) * diff_count;
}
return ret;
}
int main() {
int N; cin >> N;
vector<int> A(N);
for (int i=0; i<N; i++) {
cin >> A[i];
}
long long ans = solve(A);
reverse(A.begin(), A.end());
ans += solve(A);
// l+1 == N-r-1のケースが重複して数えているのでその分を引く
for (int l=0; l<N/2; l++) {
int r = N-1-l;
if (A[l] != A[r]) {
ans -= l+1;
}
}
cout << ans << endl;
}