Processing math: 44%

AtCoder Beginner Contest 138 D~F問題メモ

D - Ki

問題

  • N 頂点の木があり、i 番目の辺は頂点 AiBi を結ぶ
  • 根は頂点1である
  • 各頂点にはカウンターが設置されており、最初は全て0である
  • 以下の操作を Q 回行う
    • i 番目の操作は pi,xi が与えられる
    • pi を根とする部分木の全ての頂点のカウンターを xi を増やす
  • 最終的な、全頂点それぞれのカウンターの数字を求めよ
  • 2N2×105
  • 1Q2×105

解法

親の情報を子に伝えながら木を根から探索する。

問題文を額面通り受け取って、毎回の操作で、部分木の全ての頂点のカウンターを更新していたら間に合わない。

木の構造上、「親に加算された数は、子孫にも必ず加算される」という性質を利用する。

    ○
    /\
  ○  ○ ←ここに加算された数は
      /\
     ○○ ←ここや
     /\
    ○○ ←ここにも必ず加算される

子の値は、「親の値」+「自身を起点に足された値」の合計となる。

今回のように入力量が多い問題は、input()だと遅いので、sys.stdinなどを利用する。

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, q = map(int, input().split())
lines = sys.stdin.readlines()
links = [set() for _ in range(n)]
for line in lines[:n - 1]:
    a, b = map(int, line.split())
    a -= 1
    b -= 1
    links[a].add(b)
    links[b].add(a)
adds = [0] * n
for line in lines[n - 1:]:
    p, x = map(int, line.split())
    p -= 1
    adds[p] += x
ans = [-1] * n
q = [(0, 0)]
while q:
    v, a = q.pop()
    a += adds[v]
    ans[v] = a
    for u in links[v]:
        if ans[u] == -1:
            q.append((u, a))
 
print(*ans)

E - Strings of Impurity

問題

  • 英小文字から成る2つの文字列 s,t が与えられる
  • s を無限回繰り返した文字列から、先頭 i 文字を切り取ったものを si とする
  • tsi の部分列となるような i が存在するか判定し、存在する場合は最小の i を求めよ
  • 1|s|,|t|105

解法

事前計算で目次を作成する。

s の各位置につき「次に文字 a が出てくる位置は b」という目次を作っておけば、t で出てくる順に目次を追っていくことで、最小の i が求められる。

i     0   1   2   3   4   5   6   7
s:        a   b   c   a   d   a   b
目次 a:1 a:4 a:4 a:4 a:6 a:6
     b:2 b:2 b:7 b:7 b:7 b:7 b:7
     c:3 c:3 c:3
     d:5 d:5 d:5 d:5 d:5

t:   b d c a
  b   ------> b
  d             --------> d
  c                         --------
      ----------> c
  a                 -> a

便宜上、i=0 は先頭からの各文字の最初の出現位置とする。

この目次は、s を後ろから見て順次、今の文字に対する位置を更新していけば作れる。 |s| の上限が 105、辞書のサイズが英小文字数26なので、2.6×106 となりなかなか重たくはなるが、AtCoderのメモリ制限はかなり緩いので大丈夫。

こうすると、今、位置 i にいて、t の次の文字が c だとしたとき、

  • 目次[i] に、c があったら、そこまでジャンプ(i=[i][c] とする)
  • c がなくても、次の周回であるかも知れないので、目次[0]を確認
  • あったら、次の周回での i までジャンプ(i=[0][c] とする)
  • なかったら、s 全体に文字 c が無いということなので、不可能

として、最後に ×|s|+i が答えとなる。

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
def solve(s, t):
    ls = len(s)
    nxt = {}
    follow = [{} for _ in range(ls + 1)]
    for i, c in enumerate(s[::-1]):
        i = ls - i
        follow[i] = nxt.copy()
        nxt[c] = i
    follow[0] = nxt.copy()
    ans = 0
    i = 0
    for c in t:
        if c not in follow[i]:
            if c not in follow[0]:
                return -1
            ans += len(s)
            i = follow[0][c]
            continue
        i = follow[i][c]
    return ans + i
 
 
s = input()
t = input()
print(solve(s, t))

F - Coincidence

問題

  • 整数 L,R が与えられる
  • 以下の条件を満たす整数の組 (x,y) の個数を \mod{10^9+7} で求めよ
    • L \le x \le y \le R
    • y\ \% \ x = y \ XOR \ x
  • 1 \le L,R \le 10^{18}

解法

2つの数を同時に扱う桁DP。

x,y の性質

まず、y\ \% \ x = y \ XOR \ x の条件を考えると、2進数で表したとき

y   1  0  1  0
x      1  1  0

のように xy の桁数が異なると、「XORは y の最上位が必ず残るため x より大きい」「剰余は必ず x 未満」で、この2つが一致することは無い。

よって、桁数は必ず等しい。

ということは、商は2以上にならず、x \le y のため必ず1である。つまり、割り算の余りというものの、実は引き算の結果と考えても同じことである。

y-x = y \ XOR \ x となるのはどういう時か。1桁同士の対応を見ていくと、

yx減算XOR備考
0000
0111より上位の桁に、繰り下がりの影響が発生
1011
1100

となり、基本的には一致するが、(0,1)の場合はくり下がりが発生するのでより上位の桁に影響する。 ここで、下位の桁からの繰り下がりの影響を受けた場合の関係性は、

y繰り下がり後yx減算XOR備考
01010繰り下がり継続
01101繰り下がり継続
10001
10110繰り下がり継続

となり、くり下がりが継続するかどうか以前に、今の桁で全ての場合で一致しなくなる。つまり、くり下がりが1箇所でも発生してはいけないということになる。

言い換えると、y で“0”である桁では、x も“0”でないといけない。このような関係を、bitの文脈では「xy のsubset」という。

y  110010110010
x  100000110000

数え上げ

そのような x,y の組を、L~R の範囲で求める。 桁DPと予想は付くが、どのように状態を分ければ良いか難しい。

ここは、x,y 2つの数をセットで1つの状態と捉える。

よく、上限が決められた桁DPでは「上から i 桁目までが上限と一致し、下位の桁の自由度が制限されている場合」と「上限より小さいことが確定し、下位の桁は自由に決められる状態」で分けて数えるが、今回はそれを L の下限にも適用する感じ。

  • DP[i][k=0/1/2/3]= 上から i 桁目までで、以下の場合の (x,y) の組の数
    • k=0: y が上限ギリギリ、x が下限ギリギリ
    • k=1: y が上限ギリギリ、x は自由
    • k=2: y,x ともに自由
    • k=3: y は自由、x が下限ギリギリ

ここからさらにx,yの桁数を考慮するとこんがらがるので、x,yの桁数は固定とし、考慮すべき桁数分だけループを回す。

つまり、L=101,R=10110 のように桁数が違う場合は、以下のように別々に求めて、合計する。

桁数3: L=  101  R=  111
桁数4: L= 1000  R= 1111
桁数5: L=10000  R=10110

(まとめて1回でやろうと思えば、「ここまでの桁がx,yとも全て0フラグ」を持ってその状態も管理すれば可能)

DPの初期状態を考える。 まず、先頭桁は両者必ず“1”である必要があり、1桁目まで考慮した (y,x) の組は (1,1) の1通りしかない。 この状態は k=0y,x ともにギリギリ)に相当するため、以下で初期化する。

  • DP[0] = [1, 0, 0, 0]

次に、i-1 からの遷移を考える。

例えば i-1 桁目まで考慮された (y,x) の具体的な値を適当に1つ考える。(例えば5桁目までで (11101, 11001) など)

このそれぞれの末尾に0か1をくっつけて、i 桁目の状態に遷移したい。くっつけ方は、(y,x)←(0,0)(1,0)(1,1) の3通り。

ここから「ky がギリギリか、x がギリギリか)」「R_i が'0'か'1'か」「L_i が'0'か'1'か」「くっつける数は(0,0)(1,0)(1,1)のどれか」の、4*2*2*3=48通りについて、考えていく。

  • R_i が '0' の時、y が上限ギリギリの場合は、'1'はくっつけられない
  • L_i が '1' の時、x が下限ギリギリの場合は、'0'はくっつけられない

ことに注意。(遷移不能な場合は'x'で表す)

k=0(yギリギリ, xギリギリ)
Ri,Li       (0,0)   (1,0)   (0,1)   (1,1)
yに付加    1 1 0   1 1 0   1 1 0   1 1 0
xに付加    1 0 0   1 0 0   1 0 0   1 0 0
遷移するk  x x 0   1 0 3   x x x   0 x x

k=1(yギリギリ, x自由)
Ri,Li       (0,?)   (1,?)
yに付加    1 1 0   1 1 0
xに付加    1 0 0   1 0 0
遷移するk  x x 1   1 1 2

k=2(y自由, x自由)
Ri,Li       (?,?)
yに付加    1 1 0
xに付加    1 0 0
遷移するk  2 2 2
  
k=3(y自由, xギリギリ)
Ri,Li       (?,0)   (?,1)
yに付加    1 1 0   1 1 0
xに付加    1 0 0   1 0 0
遷移するk  2 3 3   3 x x

これを頑張って実装すると、通る。

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
35
36
37
38
39
def count(lb, rb):
    assert lb[0] == '1'
    assert rb[0] == '1'
    assert len(lb) == len(rb)
    dp = [1, 0, 0, 0]
    for lc, rc in zip(lb[1:], rb[1:]):
        ndp = [dp[0], 0, 0, 0]
        if rc == '0':
            ndp[1] += dp[1]
            if lc == '1':
                ndp[0] = 0
        else:
            ndp[1] += dp[1] * 2
            ndp[2] += dp[1]
            if lc == '0':
                ndp[1] += dp[0]
                ndp[3] += dp[0]
        if lc == '0':
            ndp[2] += dp[3]
            ndp[3] += dp[3] * 2
        else:
            ndp[3] += dp[3]
        ndp[2] += dp[2] * 3
        dp = ndp
    return sum(dp)
 
 
l, r = map(int, input().split())
lb = bin(l)[2:]
rb = bin(r)[2:]
ld = len(lb)
rd = len(rb)
ans = 0
MOD = 10 ** 9 + 7
for d in range(ld, rd + 1):
    tlb = lb if d == ld else '1' + '0' * (d - 1)
    trb = rb if d == rd else '1' * d
    ans = (ans + count(tlb, trb)) % MOD
print(ans)

programming_algorithm/contest_history/atcoder/2019/0818_abc138.txt · 最終更新: 2019/08/22 by ikatakos
CC Attribution 4.0 International
Driven by DokuWiki Recent changes RSS feed Valid CSS Valid XHTML 1.0