AtCoder Beginner Contest 338 F,G 問題メモ
F - Negative Traveling Salesman
問題
解法
Floyd-Warshall は、負辺があっても負閉路がなければ全頂点間同士の最短距離を求められる。
また、巡回セールスマンも通った頂点集合が小さい方から探索を進めれば、コストに負が出てきても大丈夫。
(Dijkstra的に、コストが小さい方から探索してしまうと、負辺が出てくる場合は上手くいかない)
Floyd-Warshall で求めた全頂点間の最短距離を使って、巡回セールスマンをおこなえば、答えとなる。
証明
以下を両方言えれば、上記の方法で答えが求められるといえる。
2)より、巡回セールスマン中に正解のコストがちゃんと探索・考慮されることが保証される。
1)より、全ての巡回セールスマンのパスは何らかのウォークに対応づけられる。
ウォークで表現できない何者かが、本来の正解のコストより小さくなってしまうことはない。
1)、②→①→④→⑤→…→③ という巡回セールスマンのパスに対して、
各移動(②→①、①→④、…)を最短距離を実現するパスに置き換えれば、全頂点を1回以上通るウォークとなる。
②→①→→→④→→→→→⑤→...→③
①→④ の最短距離を実現するパスは、①→②→④
④→⑤ の最短距離を実現するパスは、④→③→②→⑤
...etc.
↓ウォークを復元できる
↓(このパスから復元されるウォークは、各移動を最短距離でおこなうこれが最適)
②→①→②→④→③→②→⑤→...→③
2)、コスト最小となる正解のウォークが ②→①→②→④→③→②→⑤→…→③ みたいな感じであるとする。
負閉路はないので、始点と終点は別としてよい。
(始点と終点が一致ならループしており、ループ上のどこかにはコスト非負の辺が含まれる。
それを削除して切り開けば、悪化させずに始点と終点を別にすることができる)
すると、始点②と終点③はそのまま、
他は正解ウォーク中にはじめて出てくる順に抜き出した 1~N の順列(②→①→④→⑤→…→③)を作り、
その順に向かうことにすると、巡回セールスマンのパスで表現できる。
これを1)の方法で復元すると、元のウォークに戻るはずである。
もし、よりコストの低い別のウォークとなってしまったら、そっちの方が正解となり、最初の過程と矛盾する。
実装上の留意点として、負辺を含む場合、DP配列の初期値として十分大きい数 INF=1018 などで“不可能な状態”を表していると、
負辺を通って「INFよりちょっと少ない数」で更新されてしまう可能性があり、「最終的な結果=INFなら不可能」と言えなくなる。
巡回セールスマンの時の DP[S,i] の S って、通常の場合は「既に訪れた頂点」と意味づけることが多いので、
その意味合いのまま、今回の問題で元のウォークの状態を表現しようとすると、
「Froyd-Warshallにおける最短距離を実現するパス」の情報まで必要になってしまい、実装が大変になった。
途中の通過点を無視した実装でも、ちゃんといけるんだね。
Python3
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 |
import os
import sys
import numpy as np
def solve(inp):
n, m = inp[: 2 ]
uuu = inp[ 2 :: 3 ] - 1
vvv = inp[ 3 :: 3 ] - 1
www = inp[ 4 :: 3 ]
INF = 1 << 60
dist = np.full((n, n), INF, np.int64)
for i in range (n):
dist[i, i] = 0
for i in range (m):
u = uuu[i]
v = vvv[i]
w = www[i]
dist[u, v] = w
for k in range (n):
for i in range (n):
for j in range (n):
dist[i, j] = min (dist[i, j], dist[i, k] + dist[k, j])
dp = np.full(( 1 << n, n), INF, np.int64)
for i in range (n):
dp[ 1 << i, i] = 0
mask = ( 1 << n) - 1
for bitset in range ( 1 , 1 << n):
for u in range (n):
if dp[bitset, u] = = INF:
continue
for v in range (n):
if dist[u, v] = = INF or bitset & ( 1 << v) ! = 0 :
continue
dp[bitset | ( 1 << v), v] = min (dp[bitset | ( 1 << v), v], dp[bitset, u] + dist[u, v])
ans = dp[mask, :]. min ()
return ans
SIGNATURE = '(i8[:],)'
if sys.argv[ - 1 ] = = 'ONLINE_JUDGE' :
from numba.pycc import CC
cc = CC( 'my_module' )
cc.export( 'solve' , SIGNATURE)(solve)
cc. compile ()
exit()
if os.name = = 'posix' :
from my_module import solve
else :
from numba import njit
solve = njit(SIGNATURE, cache = True )(solve)
print ( 'compiled' , file = sys.stderr)
inp = np.fromstring(sys.stdin.read(), dtype = np.int64, sep = ' ' )
ans = solve(inp)
if ans = = 1 << 60 :
print ( 'No' )
else :
print (ans)
|
G - evall
問題
解法
まず、かけ算が優先されるので、そちらを先に評価した方がやりやすそう。'+'で区切ってしまう。
123*456*789+12345*6789+12*34*56*78*9
↓
[123*456*789, 12345*6789, 12*34*56*78*9]
区切られた1つ1つを「かけ算式」と呼ぶことにする。
各かけ算式について、加算される回数を求める。
①
123*456*789 ┐かけ算式内に左端があり、右端はこれより後のかけ算式内にある場合、
23*456*789 │こいつらは、それぞれ、これより後に出てくるかけ算式の、
3*456*789 │数字の個数分だけ加算される
456*789 │
56*789 │
6*789 │
789 │
89 │
9 ┘
②
123*456*789 ┐かけ算式内に右端があり、左端はこれより前のかけ算式内にある場合、
123*456*78 │こいつらは、それぞれ、これより前に出てくるかけ算式の、
123*456*7 │数字の個数分だけ加算される
: │
123 │
12 │
1 ┘
③
123*456*789 かけ算式を覆うように区間が設定される場合、全体が、
(これより前に出てくる数字の個数)×(後に出てくる数字の個数)
分だけ加算される
④かけ算式内に左端も右端もある場合(後述)
①②③は、各かけ算式のprefix,suffix(上記に列挙したような式)の総和と、数字の個数を数えておけばよい。
前からと後ろから調べれば、それぞれ O(|S|) で作れる。
④は、'*'で区切って数字のみの式(数字式と呼ぶことにする)にして、1文字ずつ評価すれば、
[123, 456, 789] に分割した内の、789 の '8' を評価中として、
(A)それより前の数字式のsuffixの和
123*456 ┐
23*456 │
: │
56 │
6 ┘
(B)現在の数字式の'8'以前の部分
78
(C)現在の数字式の'8'以前の部分のsuffixの和
78 ┐
8 ┘
④に分類される式のうち、'8'を右端としたものの総和は、A \times B + C で求められる。
数字式内では、B,Cを以下で更新していける。(数字 x で更新するとする)
数字式の終端まで来たら、A←A \times B + C で更新し、B,C←0 として次の数字式に移る。
これで全ての場合を O(|S|) で求められる。
Python3
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 |
from itertools import accumulate
s = input ()
MOD = 998244353
plus_split = s.split( '+' )
fwd_sum = []
bwd_sum = []
full_eval = []
counts = []
ans = 0
for t in plus_split:
mul_split = t.split( '*' )
tmp = 0
cnt = 0
p = 1
r = 0
for u in mul_split:
q = 0
m = 0
for i, c in enumerate (u, start = 1 ):
c = int (c)
q = (q * 10 + c) % MOD
m = (m * 10 + c * i) % MOD
tmp + = p * q
tmp % = MOD
ans + = r * q + m
ans % = MOD
cnt + = 1
p * = q
p % = MOD
r = (r * q + m) % MOD
fwd_sum.append(tmp)
counts.append(cnt)
full_eval.append(p)
tmp = 0
cnt = 0
p = 1
for u in reversed (mul_split):
q = 0
d = 1
for c in reversed (u):
c = int (c)
q = (c * d + q) % MOD
tmp + = p * q
tmp % = MOD
d * = 10
d % = MOD
p * = q
p % = MOD
bwd_sum.append(tmp)
fwd_cnt = [ 0 ] + list (accumulate(counts))
fwd_cnt.pop()
counts.reverse()
bwd_cnt = [ 0 ] + list (accumulate(counts))
bwd_cnt.pop()
bwd_cnt.reverse()
for i in range ( len (fwd_sum)):
ans + = fwd_sum[i] * fwd_cnt[i]
ans + = bwd_sum[i] * bwd_cnt[i]
ans + = full_eval[i] * (fwd_cnt[i] * bwd_cnt[i])
ans % = MOD
print (ans)
|