−目次
diverta 2019 Programming Contest C,D,E問題メモ
C - AB Substrings
問題
- 文字列が N 個、s1,s2,...,sN
- 好きな順に連結して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 を組にすればよい。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
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≤N≤1012
解法
「商と余り」に共通する値(k とする)を決めてしまえば、m は逆算できる。
m=N−kk
さらに、余りは割る数未満なので、m>k である。また、k を増やしていくと、m は小さくなっていく。
すると、k を小さい方から試して、逆算された m が k 以下になった時点で打ち切ってよい。O(√N) で抑えられる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
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 の数列 a1,a2,...,aN
- 要素の間に好きなだけ区切りを入れて、連続する区間に分割する。区切りの入れ方は 2N−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 と合わせて最終的な答えとなる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
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)) |