以下の内容はhttps://torus711.hatenablog.com/entry/2024/09/15/181354より取得しました。


AtCoder Beginner Contest 371, E : I Hate Sigma Problems

問題概要

 $n$ 項からなる整数列 $A = \langle A_1, A_2, \dots, A_n \rangle$ が与えられる.関数 $f \mathpunct{:} \{ 1, 2, \dots, n \}^2 \rightarrow \mathbb Z_{ \geq 0 }$ を以下で定義する:
\begin{align*}
f( l, r ) &= \langle A_l, A_{ l + 1 }, \dots, A_r \rangle \text{ に含まれる値の種類数} \\
&= \left| \{ A_l, A_{ l + 1 }, \dots, A_r \} \right|
\end{align*}
 以下を求めよ:
\begin{equation*}
\sum_{ i = 1 }^n \sum_{ j = i }^n f( i, j )
\end{equation*}

制約

  • $1 \leq n \leq 2 \times 10^5$
  • $1 \leq A_i \leq n$

解法

 $\sum \sum$ を平易に読めば,「$A$ のすべての連続部分列について,それに含まれる要素の種類数を求めた和」です.(式を見たままですが)対象となる部分列は $\Theta( n^2 )$ 個あることから,連続部分列をひとつずつ調べるような解法は TLE するので工夫が必要です.
 $f( l, r )$ について「種類数」と一言で済ませているところを言い換えると,整数の内で $A[ l, r ]$ に含まれているものを数えているとも言えます.この気持ちで式にすると $\left| \{ x \mid x \in \mathbb Z, l \in \{ 1, 2, \dots, n \}, r \in \{ 1, 2, \dots, n \}, x \in A[ l, r ] \} \right|$*1 でしょうか.このように $x$ を先に固定すれば,$x$ を含む連続部分列の個数分だけ答えに $1$ ずつ寄与していることが分かります.これをすべての $x$ について計算し,和をとったものが問題の答えです.
 では,各 $x \in A$*2についてその寄与を計算する方法を考えます.今回は,直接数える代わりに「余事象」に着目します.すなわち,$x$ を含まない連続部分列の個数を数え,すべての連続部分列の個数から引くことで欲しい値を求めます.
 要素数 $w$ の列のすべての連続部分列の個数について考えます.問題文で言うような $l, r$ の取り方を数えてもよいのですが,この場合,

  • $l = r$ であるようなものが $w$ 通り
  • $l \neq r$ であるようなものが $\frac { w ( w - 1 ) } 2$ 通り

となってやや煩雑です.ここで見方を変えて,各要素の間(先頭の手前・末尾の後ろを含む)に「仕切り」があると考えます.こうすると,$w + 1$ 個の仕切りから異なる $2$ 個を選ぶことになるので,
\begin{equation*}
\frac { ( w + 1 ) w } 2
\end{equation*}
という一本の式になります.$w = n$ とすれば上記のすべての連続部分列の個数が求まります.
 これは固定した $x$ を含まない列の数え上げにも応用できます.$x$ の出現位置(添字)を集めたものに $0, n + 1$ を加えてソートしたものを $I$ とします.このとき,各 $i \in \{ 1, 2, \dots, |I| - 1 \}$ に $x$ を含まない極大な連続部分列 $A[ I_i + 1, I_{ i + 1 } - 1 ]$ が $1$ : $1$ 対応して,それぞれの長さは $I_{ i + 1 } - I_i - 1$ となります.よって,$w = I_{ i + 1 } - I_i - 1$ として先程の議論を適用すれば,$x$ を含まない連続部分列の個数が求まります.
 以上で必要な値の計算方法が分かったので,答えを求めることができます.計算量については,添字を $A$ の値ごとに分類してからそれぞれのリストを走査しているので,各添字は高々定数回だけ参照されることになります.よって,このアルゴリズムは $\Theta( n )$ 時間で動作します.

コード

LL solve( const int N, const VI occurences )
{
	LL res = LL( N ) * ( N + 1 ) / 2;

	REP( i, SZ( occurences ) - 1 )
	{
		const int w = occurences[ i + 1 ] - occurences[i] - 1;
		res -= LL( w ) * ( w + 1 ) / 2;
	}

	return res;
}

int main()
{
	IN( int, N );
	VI A( N );
	cin >> A;
	MAP_PRED( A );

	VVI occurences( N );
	for_each( ALL( occurences ), []( VI &row ){ row.PB( -1 ); } );
	REP( i, N )
	{
		occurences[ A[i] ].PB( i );
	}
	for_each( ALL( occurences ), [&]( VI &row ){ row.PB( N ); } );

	LL res = 0;
	REP( i, N )
	{
		res += solve( N, occurences[i] );
	}

	cout << res << endl;

	return 0;
}
main = do
	n <- readInt
	as <- readInts
	print $ sum $ map ( solve n ) $ classify as

classify as = elems $ runSTArray $ do
	lists <- newArray ( 1, n ) [ n + 1 ]
	forM_ ( reverse $ zip [ 1 .. ] as ) $ \( i, a ) -> do
		modifyArray lists a ( i : )
	forM_ [ 1 .. n ] $ \a -> do
		modifyArray lists a ( 0 : )
	return lists
	where
	n = length as

solve n is = f n - ( sum $ map ( f . pred ) $ zipWith (-) ( tail is ) is )
	where
	f w = w * ( w + 1 ) `div` 2

*1:$\in$ をやや濫用している気がしますがご容赦を…….ちゃんと書くと $\exists i \in [ l, r ], A_i = x$.

*2:明らかにこの範囲で調べれば十分です.




以上の内容はhttps://torus711.hatenablog.com/entry/2024/09/15/181354より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14