部分文字列
与えられた長さ の数列 の全ての連続部分列を対象としています。
部分文字列の最初の位置を 、最後の位置を とする部分文字列については、
, , を見て、異なる数であれば変更する必要があります。
つまり、次のようなコードで答えが求められます。
ans = 0 for p = 1:N-1 for q = p+1:N for k = 0:div(p-q,2) if A[p+k] != A[q-k] ans += 1 end end end end
しかし、これでは時間がかかりすぎます。
比較回数を減らす
ここで、 が異なるとします。 が回文の対称位置になる部分文字列は、
で、 かつ を満たす 0 以上の の個数だけあります。
この個数は、 と書けます。
これを利用して、外側のループを ではなく で回すことにすると、
次のようなコードになります。
ans = 0 for i = 1:N-1 for j = p+1:N if A[i] != A[j] ans += min( i, N-j+1 ) end end end
ところが、これでも時間がかかってしまいます。
二重ループの解消
時間がかかるのは の2重ループになっていて、 の計算量になっているためです。
これを何とかしたいです。
そこで、 を固定して としてみます。このとき となるため
まとめて処理できそうです。
ところが をひとつずつ比較してしまうと2重ループのままです。
そこで、
という関係を利用します。
あらかじめ数列 に 1 が何個、2が何個、…を数えておく(個数の配列を用意する)と、
全部で( も含めて) 個の数のうち、 と同じ数が( も含めて)何個あるかが
すぐわかります。この差から、 のうち と異なる個数が求められます。
これで については数えられたので、個数の配列から の分を引きます。
次に とすると、 になりそうですが、
では値が異なります。そこで、 の次は の場合について数えていきます。
で の場合
で の場合
で の場合
で の場合
で の場合
で の場合
のように 交互に処理を進めていきます。
コード例 (Julia)
function solve() # 入力 readInt() = parse(Int,readline()) readInts() = parse.(Int,split(readline())) # 入力データ N = readInt() A = readInts() # 各数字が何回現れるか C = zeros(Int,N) for i = 1:N C[A[i]] += 1 end # A[i] と A[j] (i<j) が異なるの場合、A[i] とA[j] が回文で対応する位置になるような # 部分文字列は min(i,N-j+1) 種類あり、この値が答えに加算される。 head = 1 tail = N ans = 0 sumC = N while head < tail # head から tail の間に、A[head] と異なる値がいくつあるか p = sumC - C[A[head]] ans += p * head # 処理済みのA[head]の分を減らす C[A[head]] -= 1 sumC -= 1 # head+1 から tail の間に、A[tail] と異なる値がいくつあるか p = sumC - C[A[tail]] ans += p * head # 処理済みのA[tail]の分を減らす C[A[tail]] -= 1 sumC -= 1 # head, tail を内側に 1つずらす head += 1 tail -= 1 end println(ans) end # function solve # main