Processing math: 25%

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) は、その個数だけ連結させるのが効率がよい。x1 個の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 を「お気に入りの数」とする
  • あり得る「お気に入りの数」の総和を求めよ
  • 1N1012

解法

「商と余り」に共通する値(k とする)を決めてしまえば、m は逆算できる。

m=Nkk

さらに、余りは割る数未満なので、m>k である。また、k を増やしていくと、m は小さくなっていく。

すると、k を小さい方から試して、逆算された mk 以下になった時点で打ち切ってよい。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
  • 要素の間に好きなだけ区切りを入れて、連続する区間に分割する。区切りの入れ方は 2N1 通りある
  • 区間内の全ての要素の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の場合、Xacc 上に出てくる全ての整数が候補として考えられる
    • →処理を分ける
  • 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))

programming_algorithm/contest_history/atcoder/2019/0511_diverta2019.txt · 最終更新: 2019/05/16 by ikatakos
CC Attribution 4.0 International
Driven by DokuWiki Recent changes RSS feed Valid CSS Valid XHTML 1.0