Processing math: 100%

AtCoder Grand Contest 028 A~C

A - Two Abbreviations

問題

  • 長さ N の文字列 S と長さ M の文字列 T
  • 以下の条件を満たす最短の文字列 X の長さ L を求めよ
    • LN でも M でも割り切れる
    • X を1文字目から LN 文字ごとに読むと S になる
    • X を1文字目から LM 文字ごとに読むと T になる
  • 存在しなければ'-1'を出力

S = ae
T = acp
↓
X = accept
    a  e
    a c p

解法

存在するなら LNM の最小公倍数となる。

Xi 文字目は○○でなければならない」という制約が S 側と T 側から存在する。同じ i に対し、双方からの制約が矛盾すれば不可。

S = abcdef
T = aceg
↓
L = LCM(6, 4) = 12
制約
S:  a.b.c.d.e.f.
T:  a..c..e..g..
    ~     ~     ←制約が共通する箇所
          x     ←矛盾する箇所

これを素直に確認すればよい。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from fractions import gcd
 
 
def solve(n, m, s, t):
    g = gcd(n, m)
    ng = n // g
    mg = m // g
 
    for h in range(g):
        if s[ng * h] != t[mg * h]:
            return -1
    return ng * mg * g
 
 
n, m = list(map(int, input().split()))
s = input()
t = input()
print(solve(n, m, s, t))

B - Removing Blocks

問題

  • 長さ N の数列 A1,A2,...,AN
  • ここから1つずつ数字を無くなるまで取り除いていく
  • 取り除くコストは、以下の通りである
    • Ai を取り除く時、その連結成分の Ak の総和のコストがかかる
    • xy が連結であるとは、xy の間の数字がまだ1つも取り除かれていないことを示す
  • さて、ここで取り除く順番は N! 通りある
  • 全ての取り除き方のコストの総和を求めよ

数列 1 2 3 4 5 6
     1   3 4 5 6  2を除去→123456 のコスト 21
     1   3   5 6  4を除去→  3456 のコスト 18
     1   3     6  5を除去→    56 のコスト 11
     1         6  3を除去→  3    のコスト  3
     1            6を除去→     6 のコスト  6
                  1を除去→1      のコスト  1 計 60

これを、$N!$ 通りの全ての順序で実践した総和を求める

解法

答えは、c1A1+c2A2+...+cNAN のような形になる。ci は、Ai のコストが N! 通りで総計何回足されることになるか。

ciAi の値には関係なく N と位置のみによって決まるので、まずこれを求めて、最後に ciAi を計算して足し合わせることにする。

どうやって求めるか。一発でたちどころに求まるようなものでもないので、何らかの基準で問題を分割したい。

何回目に取り出されるか? など動的計画法でうんうん考えていたが、O(N2) の計算量からなかなか減らせない。

ここでは、i,j について「i を取り除く時、j のコストが反映される場合の数 dij」を考えるとよかったようだ。

j を固定して i=1N について dij を求めると、cj=idij で求められる。これは漏れなく、ダブり無く数えられている。

1  2  3  4
c3 = 1 を除いた時に 3が反映される場合の数 d(1,3)
   + 2 を除いた時に 3が反映される場合の数 d(2,3)
   + 3 を除いた時に 3が反映される場合の数 d(3,3)
   + 4 を除いた時に 3が反映される場合の数 d(4,3)

コストが反映されるためには、i を取り除くまで、i+1j のどの数字も取り除かれていてはいけない。k=|ij| とすると、

  • 全体の場合の数=N!
  • ij の区間の取り除き方=(k+1)! 通り
  • ij の区間で、iを最初に取り除く場合の数=k! 通り
  • よって、dij=N!k!(k+1)!=N!k+1

このように、dijNk=|ij| のみによって決まる。d1,3=d2,4=d3,5 である。

ek=dij で表すことにする。

            1  2  3  4
d(1,3) e(2) *     *
d(2,3) e(1)    *  *
d(3,3) e(0)       *
d(4,3) e(1)       *  *

ek の並び方に着目すると、ある j について cj を求めるのに必要となる k は連続している。

言い換えると、j の左にある数字の個数を l、右側を r とすると、cj=el+el1+...+e1+e0+e1+...+er となる。

よって、k=0N1 についてek を求め、累積和を事前計算しておくと、各 j について O(1)cj を求められる。

後は Aj とそれぞれ掛け合わせ、MOD をとればよい。

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 prepare(n, MOD):
    f = 1
    factorials = [1]
    for m in range(1, n + 1):
        f *= m
        f %= MOD
        factorials.append(f)
    inv = pow(f, MOD - 2, MOD)
    invs = [1] * (n + 1)
    invs[n] = inv
    for m in range(n, 1, -1):
        inv *= m
        inv %= MOD
        invs[m - 1] = inv
 
    return factorials, invs
 
 
def solve(n, aaa):
    factorials, invs = prepare(n, MOD)
    fn = factorials[n]
    coefs = [fn * invs[i + 1] * factorials[i] % MOD for i in range(1, n)]
    acc = [0]
    for c in coefs:
        tmp = (acc[-1] + c) % MOD
        acc.append(tmp)
 
    ans = 0
    for i, a in enumerate(aaa):
        ans += (acc[i] + acc[n - i - 1] + fn) * a
        ans %= MOD
    return ans
 
 
MOD = 1000000007
 
n = int(input())
aaa = list(map(int, input().split()))
print(solve(n, aaa))

C - Min Cost Cycle

問題

  • N 頂点の有向完全グラフ
  • 各頂点には、Ai,Bi の2つの数字が与えられている
  • 頂点 uv の辺のコストは、min(Au,Bv)
  • ハミルトン閉路(全頂点を訪れて戻ってくるルート)の、最短コストを求めよ
  • 2N105

解法

まず、Dijkstraなどの最短経路探索に落とし込む方法は、総計 N(N1) 本の辺が張られることになるため、計算量・空間量的に不可能。

全頂点間に辺が張られているので、各頂点で「Ai,Bi が採用されるか」を決めれば、(ある例外に注意すれば)好きなように閉路を構築できることを利用する。

頂点で採用されるコストは4パターンに分けられて、

  1. AiBi も共に使用される
  2. Ai のみ使用される
  3. Bi のみ使用される
  4. 両方使用されない

頂点u のパターンと、その次の頂点v のパターンは、Bu,Av のどちらか一方のみが採用されていないといけないことを踏まえると、遷移は以下のようになる。

パターン遷移できるパターン
1●●3◌●,4◌◌
2●◌1●●,2●◌
3◌●3◌●,4◌◌
4◌◌1●●,2●◌

よって、閉路を作ることが可能なパターンの組は、以下のいずれかとなる

  • 全頂点がパターン2
  • 全頂点がパターン3
  • パターン1と4が同数で、かつ最低1つは存在する(採用するコストを N 個選べば自動的に同数になるため、注意すべき条件は「最低1つ」)

Ai,Bi をまとめて 2N 個をソートし、小さい方から N 個を取ったものが、基本的には答えとなる。

ただし、小さい方から N 個を取った結果、閉路が作れないパターンの組み合わせが出てくる。それは「パターン2と3が混在しているが、パターン1や4が存在しない」状態で、その場合のみ微調整の必要がある。

微調整とはいっても、1つだけ組み替えて、パターン1(と4)を1つ作ればよい。

N 個取った結果パターン1の頂点が存在しない⇒選ばれた N 個の頂点は全て別々、選ばれなかった方も全て別々なので、選ばれた方と選ばれなかった方から1つずつ取り、入れ替えればよい。

基本的にはコストのソート結果のN番目とN+1番目を組み替えればよい。

しかし、N番目とN+1番目が同じ頂点由来であった場合は、単にその頂点のパターン2がパターン3に(あるいは逆に)なっただけでパターン1が作られない。

その場合は、「N1番目とN+1番目」「N番目とN+2番目」の、コストがより小さくなる方を組み替えればよい。

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
from operator import itemgetter
from itertools import chain
 
n = int(input())
aaa = [tuple(map(int, input().split())) for _ in range(n)]
flatten = sorted(chain.from_iterable([(a, i), (b, i)] for i, (a, b) in enumerate(aaa)))
picked = set()
ans = 0
 
for ab, i in flatten[:n]:
    picked.add(i)
    ans += ab
 
if len(picked) == n:
    exclude = flatten[n - 1]
    include = flatten[n]
    if exclude[1] == include[1]:
        tmp1 = include[0] - flatten[n - 2][0]
        tmp2 = flatten[n + 1][0] - exclude[0]
        ans += min(tmp1, tmp2)
    else:
        ans -= exclude[0]
        ans += include[0]
 
ans = min(ans, sum(map(itemgetter(0), aaa)), sum(map(itemgetter(1), aaa)))
 
print(ans)

programming_algorithm/contest_history/atcoder/2018/1013_agc028.txt · 最終更新: 2018/10/16 by ikatakos
CC Attribution 4.0 International
Driven by DokuWiki Recent changes RSS feed Valid CSS Valid XHTML 1.0