Loading [MathJax]/jax/output/CommonHTML/jax.js

AtCoder Beginner Contest 164 D,E,F問題メモ

D - Multiple of 2019

問題

  • 長さ N の'0'~'9'からなる文字列 S が与えられる
  • 連続する区間 [l,r] で、SlSr を10進数の整数として見たときに2019の倍数となっているようなものはいくつあるか求めよ
  • 1N2×105

解法

つい最近、似たような問題が出た気がする。復習大事。

1
2
3
4
5
6
7
8
9
10
11
12
13
from collections import defaultdict
 
s = input()
 
cnt = defaultdict(int)
cnt[0] += 1
tmp = 0
for i, c in enumerate(s[::-1]):
    c = int(c)
    tmp = (tmp + c * pow(10, i, 2019)) % 2019
    cnt[tmp] += 1
 
print(sum(c * (c - 1) // 2 for c in cnt.values()))

E - Two Currencies

問題

  • N 頂点 M 辺のグラフに見立てた都市と鉄道がある
  • あなたは今、都市1にいて、金貨を超沢山、銀貨を S 枚持っている
  • i 番目の鉄道は都市 UiVi を結び、運賃は片道銀貨 Ai 枚、所要時間は Bi
    • 金貨では支払えない
  • 各都市では両替ができて、都市 i では金貨1枚を銀貨 Ci 枚に、時間 Di で両替できる
    • 何枚でも両替できるが、時間はその都度かかる
  • 都市 2N のそれぞれについて、到達できる最短時間を求めよ
  • 2N50
  • N1M100
  • 1Ai50
  • 0S109
  • 0Bi,Ci,Di109

解法

頂点を多重にする最短経路探索の練習的問題?

N,Ai がなかなか少なめ。

運賃があるなら時間優先で各都市に向かいたいが、足りない場合、どこかで両替の必要がある。 遠く離れた都市でメッチャ速く両替できるなら、わざわざそこまで行って両替して、帰ってくるみたいなこともあり得る。 なので、1回訪れた都市を再度訪れるかも知れないので、普通のDijkstraなどでは上手くいかない。

だが、もし2回目に訪れたとき、1回目より所持銀貨が同じや少なかったら……?

その場合、2回目から時間 t かけて訪れられる都市は、1回目の時点でも当然同じ経路を辿れば同じように訪れられる。 従って、トータルでは絶対に遅くなるので、それ以上探索を広げる必要は無い。

所持銀貨の情報も合わせて考慮すればよさそう。

Dijksra法を使うとし、頂点を「(v,s)= 都市 v を、所持銀貨 s で訪れた状態」とする。DP[v,s] をその状態に至れる最短所要時間とする。

とある (v,s) に時間 t で到達できた場合、以下の2通りの遷移を行う。

  • 両替する
    • DP[v,s+Cv]DP[v,s]+Dv
    • 更新したなら、(v,s+Cv) をキューに追加
  • 移動する
    • v から伸びる鉄道 i を使って移動する(もう一方の都市を u とする)
    • DP[u,sAi]DP[v,s]+Bi
    • 更新したなら、(u,sAi) をキューに追加
    • ※ただし、運賃が足りる場合のみ

計算量

ここで s は、都市が50個しか無いので最遠でも 49 回鉄道に乗ればよくて、鉄道1回の運賃も50以下なので、 多くとも 50×49=2450 さえ持っていれば、それ以上両替の必要は無い。

よって、頂点数は O(N2Amax) となる。

辺数も、1頂点から伸びる1本が約2450倍に複製される感じなので、O(NMAmax) となる。

優先付きキューを使うと、Dijkstraの計算量は O((N2Amax+NMAmax)logN2Amax) で、上限の数字を当てはめるとだいたい6375000となる。

これは実際、Pythonではちょっと厳しくて、下手なことをするとTLEになってしまう。

たとえば、1回都市 v を訪れたなら、そこで両替できるだけしてしまうという考えもある。

  • (v,s+Cv) をキューに追加
  • (v,s+2Cv) をキューに追加
  • (v,s+3Cv) をキューに追加

だがこうすると、Dijkstraの実装によっては、キューに同じ状態を示す頂点が多く積み上がってしまうことになり、TLEとなる。(この事実に気付くのに時間かかった)

両替は1度に金貨1枚分とし、それがキューから引っ張り出されたら、改めてもう1枚両替を考慮すればよい。

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import sys
from heapq import heappop, heappush
 
n, m, ss = map(int, input().split())
lines = sys.stdin.readlines()
links = [set() for _ in range(n)]
aaa = []
for line in lines[:m]:
    u, v, a, b = map(int, line.split())
    u -= 1
    v -= 1
    links[u].add((v, a, b))
    links[v].add((u, a, b))
    aaa.append(a)
 
exchanges = []
for line in lines[m:]:
    c, d = map(int, line.split())
    exchanges.append((c, d))
 
req = sum(sorted(aaa, reverse=True)[:n - 1])
INF = 10 ** 18
ans = [INF] * n
ans_silver = [-1] * n
 
q = [(0, ss, 0)]  # (time, silver, city)
visited = set()
fixed = 0
while q:
    t, s, v = heappop(q)
    if (s, v) in visited or ans_silver[v] >= s:
        continue
    visited.add((s, v))
    if ans[v] == INF:
        ans[v] = t
        ans_silver[v] = s
        fixed += 1
        if fixed == n:
            break
 
    for u, a, b in links[v]:
        if s < a:
            continue
        ns = s - a
        nt = t + b
        if (ns, u) in visited or ans_silver[u] >= ns:
            continue
        heappush(q, (nt, ns, u))
 
    c, d = exchanges[v]
    ns = s + c
    if ns <= req and (ns, v) not in visited:
        heappush(q, (t + d, ns, v))
 
print('\n'.join(map(str, ans[1:])))

F - I hate Matrix Construction

問題

  • 整数 N と、長さ N の4つの数列 S,T,U,V が与えられる
    • S,T は '0','1'のみからなる
  • 以下の条件を満たす N×N 行列を一例、構築せよ
  • 条件
    • Si=0 の時、i 行目の要素を全て BitwiseAnd すると Ui
    • Si=1 の時、i 行目の要素を全て BitwiseOr すると Ui
    • Tj=0 の時、j 行目の要素を全て BitwiseAnd すると Vj
    • Tj=1 の時、j 行目の要素を全て BitwiseOr すると Vj
  • 1N500
  • 0Ui,Vi<264

解法

数字を2進数で見たとき、ある桁の処理は他の桁に影響しないので、桁毎に考える。U,V は 0/1 の2通りだけを考えればよくなる。

                       S  処理  U
      _   _   _   _  | 1   or   1
      _   _   _   _  | 1   or   0
      _   _   _   _  | 0  and   0
      _   _   _   _  | 0  and   1
    -----------------+
T     0   1   0   1
処理 and or  and or
V     1   1   0   0

ここで、まずは2つの事実により、確定する行・列がある。(T,V でも同じ)

  • Si=0:AND なら、Ui=1 の行は全て1
  • Si=1:OR なら、Ui=0 の行は全て0

これを埋める。この時点で、行と列でかち合うものがあれば、不可能。

                       S  処理  U
      _   _   _   _  | 1   or   1
      0   0   0   0  | 1   or   0  ←確定
      _   _   _   _  | 0  and   0
      1   1   1   1  | 0  and   1  ←確定
    -----------------+
T     0   1   0   1
処理 and or  and or
V     1   1   0   0
     ↑
     1確定だが、既に0が埋まっている

以下のようにすると、かち合うものを検出できる。

  • まず行(S,U)で確定できるところを埋める。その際、0,1それぞれ、確定したものがあったかを記録する
  • 次に列(T,V)で確定できるところを埋める際、埋めようとする値と矛盾するものが、行で確定していたら、不可能

さて、次に、確定しないものを考える。

  • Si=0:AND なら、Ui=0 の列の少なくとも1つは0
  • Si=1:OR なら、Ui=1 の列の少なくとも1つは1

ここで、未確定の行・未確定の列の個数で、処理を場合分けする。

ともに2つ以上ある

話は簡単で、未確定マスの中だけで市松模様状に敷き詰めればよい。必ず、どの行・列にも0,1が1マス以上存在するようになる。

                       S  処理  U
      1  [0]  1  [1] | 1   or   1
      1  [1]  1  [0] | 1   or   1
      1  [0]  1  [1] | 0  and   0
      1   1   1   1  | 0  and   1

どちらかが存在しない

マスは全て確定している。「確定する条件」の中では、矛盾はない。

後は、未確定条件が全て合致しているか調べればよい。

以下は、列側の要件により全てが“1”で確定した状態で 行側の要件を確認する過程だが、この時に「どれか1つは0」があると矛盾する。

                       S  処理  U
      1   1   1   1  | 1   or   1  [未確定]  どれか1つは1
      1   1   1   1  | 1   or   1  [未確定]  どれか1つは1
      1   1   1   1  | 0  and   0  [未確定]  どれか1つは0 → 矛盾
      1   1   1   1  | 0  and   1  [確定]

これも、確定行同士での矛盾を検出するときと同様、「0で埋まった列があるか」「1で埋まった列があるか」を記録しておけばよい。

どちらかが1つのみ

市松模様が使えない。この処理にちょっと混乱してWAを連発した。まぁこれは丁寧にいろんなケースを確認していくしかないんだろう。

未確定が1つのみなのが列であるとし、その列で少なくとも1つは存在しなければいけない値が b だとする。

直交する行に既に b が確定している
                       S  処理  U
      1   1   _   1  | 1   or   1  どれか1つは1
      1   1   _   1  | 1   or   1  どれか1つは1
      1   1   _   1  | 0  and   0  どれか1つは0
      1   1   1   1  | 0  and   1  [確定]
             ↑
             どれか1つは1 ... 既に満たされている

この場合、未確定列の要件は既に満たされているので、行方向で未確定の要件に求められているものを埋めればよい。

                       S  処理  U
      1   1  [1]  1  | 1   or   1  どれか1つは[1]
      1   1  [1]  1  | 1   or   1  どれか1つは[1]
      1   1  [0]  1  | 0  and   0  どれか1つは[0]
      1   1   1   1  | 0  and   1  [確定]
平行な列に、既に ˉb が確定している

ˉb は、反転したもの(0なら1、1なら0)とする。

                       S  処理  U
      0   1   _   1  | 1   or   1  どれか1つは1
      0   1   _   1  | 1   or   1  どれか1つは1
      0   1   _   1  | 0  and   0  どれか1つは0
      0   1   _   1  | 0  and   0  どれか1つは0
             ↑
             どれか1つは1

この場合、未確定な行の要件で、ˉb なものは既に満たされているので、全て b で埋めてよい。

                       S  処理  U
      0   1  [1]  1  | 1   or   1  どれか1つは1
      0   1  [1]  1  | 1   or   1  どれか1つは1
      0   1  [1]  1  | 0  and   0  どれか1つは0 [要件は既に満たされている]
      0   1  [1]  1  | 0  and   0  どれか1つは0 [要件は既に満たされている]
             ↑
             どれか1つは[1]
それ以外
                       S  処理  U
      1   1   _   1  | 0  and   0  どれか1つは0
      1   1   _   1  | 0  and   0  どれか1つは0
      1   1   _   1  | 0  and   0  どれか1つは0
      1   1   _   1  | 0  and   0  どれか1つは0
             ↑
             どれか1つは1

未確定な行で、入れたい値が ˉb なものは、まだ要件が満たされていないので、未確定の残る1列に入れるしかない。

よって、基本は未確定な行の要件で埋めていく。

そうして埋めていって、b の入る行が1つも無ければ、そこで不可能となる。

                       S  処理  U
      1   1   0   1  | 0  and   0  どれか1つは0
      1   1   0   1  | 0  and   0  どれか1つは0
      1   1   0   1  | 0  and   0  どれか1つは0
      1   1   0   1  | 0  and   0  どれか1つは0
             ↑
             どれか1つは1 ... 矛盾!

1つでもあれば、それで列の要件も満たされるので、よし。

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# なんて冗長なコードですこと。
from functools import reduce
 
 
def add_row(ans, n, i, k):
    for j in range(n):
        ans[i][j] |= k
 
 
def add_column(ans, n, j, k):
    for i in range(n):
        ans[i][j] |= k
 
 
def solve(n, sss, ttt, uuu, vvv):
    max_d = max(max(uuu).bit_length(), max(vvv).bit_length())
    ans = [[0] * n for _ in range(n)]
 
    for d in range(max_d):
        k = 1 << d
        ambiguous_i = []
        ambiguous_j = []
        filled_i = [False, False]
        filled_j = [False, False]
        for i, (s, u) in enumerate(zip(sss, uuu)):
            if s == 0:
                if u & k:
                    add_row(ans, n, i, k)
                    filled_i[1] = True
                else:
                    ambiguous_i.append((i, 0))
            else:
                if u & k:
                    ambiguous_i.append((i, 1))
                else:
                    filled_i[0] = True
        for j, (t, v) in enumerate(zip(ttt, vvv)):
            if t == 0:
                if v & k:
                    if filled_i[0]:
                        return -1
                    add_column(ans, n, j, k)
                    filled_j[1] = True
                else:
                    ambiguous_j.append((j, 0))
            else:
                if v & k:
                    ambiguous_j.append((j, 1))
                else:
                    if filled_i[1]:
                        return -1
                    filled_j[0] = True
 
        # print(*map('{:064b}'.format, [k] * (n + 1)))
        # for i in range(n):
        #     print(*map('{:064b}'.format, ans[i]), end=' ')
        #     print('{:064b}'.format(uuu[i]))
        # print(*map('{:064b}'.format, vvv))
        # print(ambiguous_i, ambiguous_j)
        # print(filled_i, filled_j)
        # print()
 
        if len(ambiguous_i) == 0:
            amb = set(bj for j, bj in ambiguous_j)
            for bj in amb:
                if not filled_i[bj]:
                    return -1
        elif len(ambiguous_j) == 0:
            amb = set(bi for i, bi in ambiguous_i)
            for bi in amb:
                if not filled_j[bi]:
                    return -1
        elif len(ambiguous_i) == 1:
            i, bi = ambiguous_i[0]
            if filled_j[bi]:
                for j, bj in ambiguous_j:
                    if bj:
                        ans[i][j] |= k
            elif filled_i[bi ^ 1]:
                if bi:
                    for j, bj in ambiguous_j:
                        ans[i][j] |= k
            else:
                ok = False
                for j, bj in ambiguous_j:
                    if bj:
                        ans[i][j] |= k
                    if bi == bj:
                        ok = True
                if not ok:
                    return -1
        elif len(ambiguous_j) == 1:
            j, bj = ambiguous_j[0]
            if filled_i[bj]:
                for i, bi in ambiguous_i:
                    if bi:
                        ans[i][j] |= k
            elif filled_j[bj ^ 1]:
                if bj == 1:
                    for i, bi in ambiguous_i:
                        ans[i][j] |= k
            else:
                ok = False
                for i, bi in ambiguous_i:
                    if bi:
                        ans[i][j] |= k
                    if bi == bj:
                        ok = True
                if not ok:
                    return -1
        else:
            for ii, (i, bi) in enumerate(ambiguous_i):
                for jj, (j, bj) in enumerate(ambiguous_j):
                    if (ii ^ jj) & 1:
                        ans[i][j] |= k
 
    return ans
 
 
n = int(input())
sss = list(map(int, input().split()))
ttt = list(map(int, input().split()))
uuu = list(map(int, input().split()))
vvv = list(map(int, input().split()))
 
ans = solve(n, sss, ttt, uuu, vvv)
if ans == -1:
    print(ans)
else:
    for row in ans:
        print(*row)

programming_algorithm/contest_history/atcoder/2020/0426_abc164.txt · 最終更新: 2020/04/29 by ikatakos
CC Attribution 4.0 International
Driven by DokuWiki Recent changes RSS feed Valid CSS Valid XHTML 1.0