目次
diverta 2019 Programming Contest C,D,E問題メモ
C - AB Substrings
問題
- 文字列が $N$ 個、$s_1,s_2,...,s_N$
- 好きな順に連結して1つの文字列にする
'AB' が多く含まれるように連結順を決めると、最大いくつ含めることができるか
解法
まず1つの文字列の中に'AB'が出てくるのは、そのまま使えるので数える。
問題は、「XXXA」「BXXX」みたいなのを連結していくつ増やせるか。
「末尾がA」「先頭がB」で1組作れるので、個数をそれぞれ数えて小さい方…とすると上手くいかない。
「BXXXA」のように両方に当てはまる文字列があり得るからである。 これを分けて数える。
- (1) 先頭がB、末尾がA: $x$ 個
- (2) 末尾がAのみ: $y$ 個
- (3) 先頭がBのみ: $z$ 個
(1) は、その個数だけ連結させるのが効率がよい。$x-1$ 個のABが作れる。
......... BXXA BXXXA BXA BXXXXA .........
さらに、$y,z$ がそれぞれ1つ以上あれば、もう1つずつ増やせる。
... XXXA BXXA BXXXA BXA BXXXXA BXXX ...
ここまでしたら、残りの $y,z$ を組にすればよい。
import sys
n = int(input())
ans = 0
tail_a = 0
head_b = 0
both = 0
for s in sys.stdin:
s = s.rstrip()
ans += s.count('AB')
if s[0] == 'B':
if s[-1] == 'A':
both += 1
else:
head_b += 1
elif s[-1] == 'A':
tail_a += 1
if both:
ans += both - 1
if tail_a:
ans += 1
tail_a -= 1
if head_b:
ans += 1
head_b -= 1
ans += min(tail_a, head_b)
print(ans)
D - DivRem Number
問題
- 正整数 $N$ が与えられる
- $N$ を正整数 $m$ で割ったとき、商と余りが一致するような $m$ を「お気に入りの数」とする
- あり得る「お気に入りの数」の総和を求めよ
- $1 \le N \le 10^{12}$
解法
「商と余り」に共通する値($k$ とする)を決めてしまえば、$m$ は逆算できる。
$m=\frac{N-k}{k}$
さらに、余りは割る数未満なので、$m>k$ である。また、$k$ を増やしていくと、$m$ は小さくなっていく。
すると、$k$ を小さい方から試して、逆算された $m$ が $k$ 以下になった時点で打ち切ってよい。$O(\sqrt{N})$ で抑えられる。
n = int(input())
i = 1
ans = 0
while True:
d, m = divmod(n, i)
d -= 1
if i >= d:
break
if m != 0:
i += 1
continue
ans += d
i += 1
print(ans)
E - XOR Partitioning
問題
- 長さ $N$ の数列 $a_1,a_2,...,a_N$
- 要素の間に好きなだけ区切りを入れて、連続する区間に分割する。区切りの入れ方は $2^{N-1}$ 通りある
- 区間内の全ての要素のxorを、その区間の「美しさ」とする
- 美しさが全ての区間で等しくなるような区切りの入れ方の数を、$\mod{10^9+7}$ で求めよ
- $1 \le N \le 5 \times 10^5$
- $0 \le a_i \lt 2^{20}$
解法
全ての区間でxorが等しいということは、累積xorを取れば2区間ごとに打ち消し合って、区間の最後の要素だけを拾うと「$X,0,X,0,...$」というようになっているはず。$X$ は任意の数。
a 001 010 | 110 100 001 | 011
xor 011 | 011 | 011
累積xor 001 011 101 001 000 011
~~~ ~~~ ~~~
なので、累積xorを求めてDPをする。$a$ の累積XORを $acc$ とし、$acc$ 上で先頭から「$X,0,X,0,...$」と拾える数が、区切りの入れ方の数となる。
DP構築の考察
- 数列の末尾の1要素は必ず拾われる。$acc$ の最終要素は「$X,0,X,0,...$」の最終要素でないといけない
- そのため、$acc$ の末尾が0以外の場合、それが唯一の $X$ である
- $acc$ の末尾が0の場合、$X$ は $acc$ 上に出てくる全ての整数が候補として考えられる
- →処理を分ける
- $X$ は0でもよいが、実装の上で分けて考えた方がやりやすい
$DP[x][i][k:0/x]=$「$X=x$ の場合において、$acc_i$ まで見たとき、最後に確定した区間の累積xorが $k=0/x$ である場合のパターン数」
すると、
- $acc_i=0$ の時 $DP[x][i][0] = DP[x][i-1][0] + DP[x][i-1][x]$
- $acc_i=x$ の時 $DP[x][i][x] = DP[x][i-1][0] + DP[x][i-1][x]$
となる。言及されていないものは前の結果を引き継ぐ。($DP[x][i][k] = DP[x][i-1][k]$)
例えば $acc_i=0$ の時、新規確定無し($i-1$ からの引き継ぎ)に加え、最後に確定した区間が $x$ だった場合から新しく区間を確定して 0 に遷移するケースを足し合わせることを示す。
直前に確定した区間
↓ i
... 0| ... 0 (iでは区間を確定しない) DP[x][i-1][0]
... x| ... 0| (iで新規区間確定) DP[x][i-1][x]
↑
直前に確定した区間
accの末尾が0以外だった場合
先述の通り、$X$ は一意に確定する。末尾の $X$ へは $DP[X][N][0]$ から遷移するので、それが答え。
末尾が $X$ だから見るべきは $DP[X][N][X]$ じゃないのかと一瞬思うが、これはあくまで「最後に決定した区間が $X$ であるもの」の数。$acc$ の末尾は必ず区間として確定しなければならないが、$DP[X][N][X]$ にはその前の $X$ で確定したまま末尾で未確定状態の場合も含まれる。$DP[X][N][0]$ であれば、最後に確定した 0 から必ず末尾の $X$ に遷移できる。
acc X 0 1 ... 0 X X 2 X
~~~~ ~ ←末尾は必ず使う
└DP[X][N][X]は、これとかで終わったまま
末尾では区間未確定なパターンも含まれる
accの末尾が0だった場合
まず、$X=0$ の場合、$acc_i$ が0の時に区切るか区切らないかを(末尾以外)独立に選べるので、$2^{0の出現回数-1}$ である。
$X \ne 0$ とする。
$X$ は最悪 $N$ の数だけ考えられるので、$X$ ごとに上記DPを繰り返すと $O(N^2)$ となり間に合わない。
まとめて複数の $X$ について処理することを考える。遷移式を再掲する。
- $acc_i=0$ の時 $DP[x][i][0] = DP[x][i-1][0] + DP[x][i-1][x]$
- $acc_i=x$ の時 $DP[x][i][x] = DP[x][i-1][0] + DP[x][i-1][x]$
$acc_i=x$ の時は、影響があるのが $DP[x][i][x]$ だけなのでそれだけ更新すればいいのだが、 $acc_i=0$ の時、今まで出てきた全ての $X=x$ について $DP[x][i][0]$ を更新しなければならない。これではやはり間に合わない。
$DP[x][i][0]$ も $acc_i=x$ の時のみ更新すればいい、ということにできれば、$O(N)$ にできそうである。
遷移を考えると、$x$ が出てこない間に0が複数回出てきても、その時に $DP[x][i][0]$ の更新に加算される $DP[x][i-1][x]$ の値は一律である。
xor x x x 0 0 0 x x 0 0 x 0
dp0 1 1 1 1 4 7 10 10 10 33 56 56 135
↓ ↓ ↓ ↑ ↑ ↑ ↓ ↓ ↑ ↑ ↓ ↑
dpx 0 1 2 3 3 3 3 13 23 23 23 79 79
~~~~~~~ ~~~~~
連続した0の時にdp0に加算される値は一定
よって「前回 $x$ が出てきてから、0が出現した回数」がわかれば、次に $x$ が出てきたときにまとめて $DP[x][i][0]$ の更新も行える。
$DP[x][i][0] = DP[x][i-1][0] + DP[x][i-1][x] \times (0の出現回数)$(※ $acc_i=x$ の時、遅延更新)
xor x x x 0 0 0 ┌x┐ x 0 0 ┌x┐ 0
dp0 1 1 1 1 1 1 1 10 10 10 10 10 56 56 56
↓ ↓ ↓ ,--,--,-3x3↑ ↓ ↓ ,--,-23x2↑ ↓
dpx 0 1 2 3 3 3 3 13 23 23 23 79 79
また、末尾が0以外だった場合と同じ理屈で、答えの算出に使用するのは $DP[x][N][x]$ の方。$DP[x][N][0]$ の最後の評価は必要ない。
最後までやったら、$DP[x][N][x]$ を全ての $x$ について合計したら、それが $X \ne 0$ の時の答え。$X=0$ と合わせて最終的な答えとなる。
from itertools import accumulate
from operator import xor
def solve(acc):
MOD = 10 ** 9 + 7
cnt0 = 0
dp0 = {}
dp1 = {}
last_0 = {}
for a in acc:
if a == 0:
cnt0 += 1
elif a not in dp1:
dp0[a] = 1
dp1[a] = 1
last_0[a] = cnt0
else:
bw0 = cnt0 - last_0[a]
if bw0 > 0:
dp0[a] = (dp0[a] + dp1[a] * bw0) % MOD
last_0[a] = cnt0
dp1[a] = (dp1[a] + dp0[a]) % MOD
if acc[-1] == 0:
return (pow(2, cnt0 - 1, MOD) + sum(dp1.values())) % MOD
else:
return dp0[acc[-1]]
n = int(input())
aaa = list(map(int, input().split()))
acc = list(accumulate(aaa, func=xor))
print(solve(acc))

