diverta 2019 Programming Contest

残念、Unratedになってしまった。運営の方々お疲れ様です。

atcoder.jp

例のごとく、assertの量がドはまり感を醸し出す。 まず、”AB"が含まれている場合はよい。で、単に末尾のAと先頭のBだけでなく、両方とも満たす場合は別に考える必要がある。

ここからABの数を数えるときに両側にあるものを1列に並べることで、並べた個数をCとしたとき、C-1個の"AB"を作ることができる。

ここまではよかった。では、これと残りの末尾のA、先頭のBをどう組み合わせるかで微妙な勘違いをしてはまった。

回答的には単にCを並べているかどうか、a,bがあるかだけで、判断すればよい。

最後にaのみ、bのみを少ないほうに合わせた数でABができるのでそれを足す。

def solve(N, Ss):
    a = 0
    b = 0
    both = 0
    ans = 0
    for s in Ss:
        ans += s.count("AB")
        if s[-1] == "A" and s[0] == "B":
            both += 1
        else:
            if s[-1] == "A":
                a += 1
            if s[0] == "B":
                b += 1

    if both > 0:
        ans += both - 1

    if both > 0 and a > 0:
        ans += 1
        a -= 1

    if both > 0 and b > 0:
        ans += 1
        b -= 1

    ans += min(a, b)
    return ans


assert (solve(1, ["AB"]) == 1)
assert (solve(2, ["AB", "ABABAB"]) == 4)
assert (solve(2, ["AB", "ABABAB"]) == 4)
assert (solve(2, ["CCB", "CCB"]) == 0)
assert (solve(1, ["CABC"]) == 1)
assert (solve(2, ["CCA", "BCC"]) == 1)
assert (solve(4, ["CCA", "BCC", "CCA", "BCC"]) == 2)
assert (solve(3, ["CCA", "BCC", "CCA"]) == 1)
assert (solve(200, ["CCA"] * 100 + ["BCC"] * 100) == 100)
assert (solve(200, ["CCA"] * 110 + ["BCC"] * 90) == 90)
assert (solve(200, ["CCA"] * 90 + ["BCC"] * 110) == 90)
assert (solve(200, ["ABA"] * 90 + ["BCC"] * 110) == 90 + 90)
assert (solve(200, ["ABA"] * 90 + ["BCC"] * 110) == 90 + 90)
assert (solve(200, ["CCA"] * 110 + ["BAB"] * 90) == 90 + 90)
assert (solve(200, ["CCA"] * 90 + ["BAB"] * 110) == 90 + 110)
assert (solve(2, ["ABA", "BAB"]) == 3)
assert (solve(200, ["ABA", "BAB"] * 200) == 3 * 200)
assert (solve(2, ["ABB", "AAB"]) == 2)
assert (solve(200, ["ABB", "AAB"] * 100) == 200)
assert (solve(2, ["ACA", "BCB"]) == 1)
assert (solve(2, ["ACB", "BCA"]) == 0)
assert (solve(2, ["BCB", "BCA"]) == 1)
assert (solve(2, ["BCB", "ACA"]) == 1)

assert (solve(4, ["BCA", "BCA", "CA", "BC"]) == 3)
assert (solve(6, ["BCA", "BCA", "CA", "BC"] + ["CC", "CC"]) == 3)
assert (solve(4 * 2, ["BCA", "BCA", "CA", "BC"] * 2) == 6)
assert (solve(6, ["BCA", "CA", "BC"] * 2) == 4)

assert (solve(2, ["A", "B"]) == 1)
assert (solve(2, ["AA", "BB"]) == 1)
assert (solve(2, ["AAB", "BB"]) == 1)
assert (solve(2, ["AACB", "BB"]) == 0)
assert (solve(3, ["AACB", "BB", "AA"]) == 1)
assert (solve(300, ["BA", "BA", "BA"] * 100) == 299)
assert (solve(300, ["BABA", "BABA", "BABA"] * 100) == 299 + 300)
assert (solve(400, ["BABA", "BABA", "BABA", "AA"] * 100) == 299 + 300 + 1)
assert (solve(4, ["AA", "BA", "BA", "BB"]) == 3)
assert (solve(3, ["AB", "BB", "AA"]) == 2)
assert (solve(3, ["AB", "BA", "AA"]) == 2)
assert (solve(3, ["AB", "BA", "BA"]) == 2)
assert (solve(4, ["AB", "BA", "BA", "AA"]) == 3)
assert (solve(4, ["AB", "BA", "BA", "BB"]) == 3)
assert (solve(3, ["AB", "BA", "BB"]) == 2)
assert (solve(3, ["AB", "AA", "BB"]) == 2)
assert (solve(2, ["AB", "BA"]) == 1)
assert (solve(2, ["BA", "BA"]) == 1)
assert (solve(3, ["AB", "BA", "BA"]) == 2)
assert (solve(3, ["BA", "BA", "BA"]) == 2)
assert (solve(3, ["BCA", "BCA", "BCA"]) == 2)
assert (solve(3, ["BCC", "BCA", "BCA"]) == 2)
assert (solve(3, ["CCA", "BCA", "BCA"]) == 2)

print("OK")
if __name__ == "__main__":
    N = int(input())
    Ss = [input() for _ in range(N)]
    print(solve(N, Ss))

atcoder.jp

これも約数を求めてカウントすればよい、というところまではすぐに分かったがどうも実装がはまった。

まず、約数を求める範囲だが、

N = m * k + k 

としたとき、例にもあるようにN=8のときm = 7になるため、mを動かしてしまうと調べる範囲がどうも1 - Nの範囲になり間に合わない。

そこで、逆にkを動かそうと考えてみると、

N = m * k + k 
    = (m + 1) * k 

より、一つのkに対して、一つのmが定まることがわかる。

この式を満たすkを探すことを考えれば、考えられるすべてのkを求めるためにはO(√N)ですむ。

(N // k) - 1 = m
def solve(N):
    s = 1
    e = int(pow(N, 0.5)) + 1
    ds = []
    for k in range(s, e):
        if N % k == 0:
            m = (N // k) - 1
            if m and N // m == N % m:
                ds.append(m)
 
    return sum(ds)


assert (solve(8) == 3 + 7)
assert (solve(10) == 4 + 9)
assert (solve(1000000000000) == 2499686339916)
if __name__ == "__main__":
    N = int(input())
    print(solve(N))

Google Code Jam Qualification Round 2019 - Cryptopangrams / Dat Bae

https://codingcompetitions.withgoogle.com/codejam/round/0000000000051705/000000000008830b

疑似的な暗号化問題。

26個の素数が選ばれ、大小関係を維持したまま各アルファベットに割り当てられる。

アルファベットから構成される文章が各文字ごとに対応する素数に変換され、 隣り合う素数との積を取った数列が"暗号文"として生成される。

暗号文のみが与えられたとき、元の文章を求めよ。

なお、素数には2は選ばれず、暗号文にはA-Zすべての文字が含まれることが保証される。

とりあえず素因数分解はすぐに必要だとわかる。

例えば、A = 3, B = 5, C = 7としたとき、 ABCという文章は"15 35"と暗号化されるため、隣り合う暗号文の共通因数を取ることで、 5が使われていることがわかる。

とりあえず共通因数をすべて取れば大体のアルファベットに対応する素数が取れるが、 一番端が計算されていないため、暗号文をさらに各素数で計算してあげればよい。 素数の範囲は大きいが、文字数が少ないためこの手法ができる。

構成される素数がすべてわかったら、変換を行う。 変換には最初の文字が必要だが、これの判別が少し面倒。

平文の1文字目の素数は暗号文の1文字目から構成される素数の中から2つ可能性として出てくる。 これらの候補を次の暗号文の文字から計算すればよいのだが、 この時、以下のように判定に使う暗号文の位置によって文字列の反転されていくことがわかる。

このため、最初の文字を求めるには、「2つの可能性のうちどちらで割り切れるか」だけでは不十分で ここに、「判別に使う暗号文までの距離」が必要。

ABCA 
 => [15, 35, 21]
BABCA
 => [15, 15, 35, 21]
ABABCA
 => [15, 15, 15, 35, 21]

計算的には、 "15 35"で、15と35の間からここで5が使われているとわかったら、最初の文字は15 / 5 = 3であるが、

"15 15 35"で、同様に5が使われているとわかった場合、15 / (15 / 5) = 5が最初の文字となり、

同様に、"15 15 15 35"では、15/ (15 / (15 / 5 )) = 3が最初の文字となる。

もちろん"15 21"のように3が使われていれば、それぞれ逆になる。

これらを含めて偶奇で判断するようにして、最初の文字を判定すれば、そのあとは暗号文を割りながら変換していけばよい。

今思うと候補はたかが2つなのだから両方試して、矛盾が出たほうを切り捨ててもよかったなぁと思う。

import string
import math

def gcd(a, b):
    if b == 0:
        return a
    else:
        return gcd(b, a % b)

def solve(N, L, S):
    Gs = []
    for i in range(L - 1):
        if S[i] != S[i + 1]:
            g = gcd(S[i], S[i + 1])
            Gs.append(g)

    Gs = list(set(Gs))
    while len(Gs) != len(string.ascii_uppercase):
        for s in S:
            for g in Gs:
                c = s // g
                if s % g == 0 and c not in Gs:
                    Gs.append(c)
        Gs = list(set(Gs))

    dec_table = {g: c for c, g in zip(string.ascii_uppercase, sorted(Gs))}

    def find_first(S, table):
        s = S[0]
        for t in table:
            if s % t == 0:
                a, b = t, s // t
                break
        i = 0
        while S[i] == s:
            i += 1
        if S[i] % a == 0:
            i += 1
        if i % 2 == 0:
            return b
        else:
            return a

    index = find_first(S, dec_table)
    ans = [dec_table[index]]
    for s in S:
        index = s // index
        ans.append(dec_table[index])

    return "".join(ans)

t = int(input())
for case in range(1, t + 1):
    N, L = tuple(map(int, input().split(" ")))
    S = list(map(int, input().split(" ")))
    print("Case #%d: %s" % (case, solve(N, L, S)))

実はこの問題、最後のコーナーケースがどうしても見つからなかった。 普段のコンテストだとあまりやらないが、 Qualificationで時間がたっぷりとれたので、自分で乱数生成して、暗号化して正しく複合できないケースを無理やり見つけられたのでよかった。 計算量確認もできるし、時間をかけたらかけただけモンキーテストが試せるので安心。

for _ in range(0, 10000):
    m = gen_map()
    str = string.ascii_uppercase * 10 + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
    str = [c for c in str]
    random.shuffle(str)
    str = "".join(str)
    print(str)
    encrypted = encrypt(str, m)
    assert (solve(0, len(str) - 1, encrypted) == str)

https://codingcompetitions.withgoogle.com/codejam/round/0000000000051705/00000000000881de

Interactiveな問題。これ準備にめっちゃ時間かかった。去年のinteractive問題で試してなかったら多分できなかった。

壊れたworkercomputer群に入力をして、その出力結果から壊れたコンピュータを探す。

F=10だけ解けた。

コンピュータ数は1024までのため、10bitをフルに使って、00000 00000 から 11111 11111を並べ、縦に切り取った数値を投げる。

N=8として3bitで表現すると、以下の中から11110000と00110011と01010101を切り取ればよい。

000
001
010
011
100
101
110
111

これを投げて返ってきたデータを再度並べなおす。3回投げて返ってきた結果が以下だったとする。

000
001
011
100
110
111

これらの数値を10進数変換して、0 ~ Nの間で抜けているものを探せばよい。

F=10だとこれらの並び順の情報を一切使ってないので、F=5の時は並び順の情報も使えば行けるんじゃないかなとは思う。解説はまだ見てない。。

from sys import stderr, stdout

def solve(N, B, F):
    outputs = ["" for _ in range(F)]
    for i in range(N):
        b = "{:010b}".format(i)
        for d in range(F):
            outputs[d] += b[d]

    response = []
    for out in outputs:
        print(out, flush=True)
        stdout.flush()
        response.append(input())

    resdata = ["" for _ in range(N - B)]
    for res in response:
        for i in range(N - B):
            resdata[i] += res[i]

    exists = []
    for res in resdata:
        exists.append(int(res, 2))
    ans = []
    for i in range(N):
        if i not in exists:
            ans.append(str(i))

    print(" ".join(ans), flush=True)

    verdict = input()
    if verdict == "1":
        return True
    else:
        return False

t = int(input())
for case in range(1, t + 1):
    N, B, F = tuple(map(int, input().split(" ")))
    if not solve(N, B, F):
      break
exit()

Google Code Jam Qualification Round 2019 - Foregone Solution / You Can Go Your Own Way

codingcompetitions.withgoogle.com

f:id:mitsuo_0114:20190504234252p:plain

1813位でした。

Foregone Solution

https://codingcompetitions.withgoogle.com/codejam/round/0000000000051705/0000000000088231

任意の数値を二つの和に分ける。ただし、その和は4を使ってはならない。

1桁ずつ、繰上りが発生しないように分けていく。 6以下は、奇数の時に備えてceil / floorを使えば単に半分にすればよい。 7以上はその実装では4が発生する可能性があるので、6を引けばよい。 これで桁ごとに計算が可能なので、計算量はO(logN)になる。

import math

t = int(input())
for i in range(1, t + 1):
  N = int(input())
  a = 0
  b = 0
  for d in str(N):
      a *= 10
      b *= 10
      d = int(d)
      if d >= 6:
          a += 6
          b += d - 6
      else:
          a += math.ceil(d / 2)
          b += math.floor(d / 2)
  print("Case #{}: {} {}".format(i, a, b))

You Can Go Your Own Way

https://codingcompetitions.withgoogle.com/codejam/round/0000000000051705/00000000000881da

これはまじでサンプルのイラストがひっかけ。

N * Nのマス目の一番左上から右下までの経路が与えられる。経路は右か下のみ。 この経路のいずれとも重なり合わない経路を一つ出す。

はじめはサンプルのように、与えられた経路に対して四角形を作るような経路を思い立つが、 既存経路を超えて四角形を作るか、既存経路を超えずに手前で作るかは、そのあとの経路次第で決まってしまうためしばらく悩む。

が、最終的に、一番最初と最後が一番重要であることに気が付く。

最初が右、最後が下で入った場合、左の壁と下の壁に沿った経路は被らないし、 最初が下、最後が右で入った場合も、同様に、上の壁と右の壁に沿えば被らない。

最初も最後も右であった場合、まず左の壁と右の壁に沿っていれば被らない。 どこかで左から右にわたれればよい。 そこで考えると、既存経路の縦と横は同数なのに、最初と最後に右を使っているため、 必ずどこかで下下と連続でつながっているところがあるはずである。ので、これを見つけて左の壁から右の壁に飛び移ればよい。

最初と最後が下であった場合も同様に、上の壁から下の壁に、右右のつながりを見つけて飛び移ればよい。

def solve(N, P):
    if P[0] != P[-1]:
        return P[-1] * (N - 1) + P[0] * (N - 1)
    else:
        t = "E" if P[0] == "S" else "S"
        c = 0
        for i in range(len(P)):
            if P[i] == t:
                c += 1
            if P[i] == t and P[i + 1] == t:
                break

        return c * t + P[0] * (N - 1) + t * (N - 1 - c)

t = int(input())
for case in range(1, t + 1):
    N = int(input())
    l = input()
    print("Case #%d: %s" % (case, solve(N, l)))

AtCoder Grand Contest 033

AtCoder Grand Contest 032はRatingは上がったけれど大した問題は解けなかったので割愛。

Google Code Jamも書かなきゃ。

atcoder.jp

幅探索だろうとは思いつつ、あまり実装してなかったので、複数のスタート地点をまとめても全く問題がないことに気が付くまで少しかかった。

で思いついたあとも幅探索でさくっと実装、なはずがpythonではTLE

atCoderではたまーにこういうことがあって、練習では飛ばしてたが本番なので、その場でPyCharmからCLionに切り替えて実装スタート。買っててよかったAll in One

まともに動かす環境すら整えてなかったので過去問の解答見つつ、ググって実装完了。

#include <iostream>
#include <algorithm>
#include <queue>

using namespace std;
int dx[4] = {0, 1, 0, -1};
int dy[4] = {1, 0, -1, 0};

int main() {
    int H, W;
    cin >> H >> W;
    int ch[H][W];
    char c;
    queue<int> q1;
    queue<int> q2;

    for (int i = 0; i < H; i++) {
        for (int j = 0; j < W; j++) {
            cin >> c;
            if (c == '#') {
                q1.push(i);
                q2.push(j);
                ch[i][j] = 0;
            } else {
                ch[i][j] = 3000;
            }
        }
    }

    int m = 0;
    while (q1.empty() == false && q2.empty() == false) {
        int i = q1.front();
        q1.pop();
        int j = q2.front();
        q2.pop();
        for (int k = 0; k < 4; k++) {
            if (0 <= i + dy[k] && i + dy[k] < H && 0 <= j + dx[k] && j + dx[k] < W) {
                if (ch[i][j] + 1 < ch[i + dy[k]][j + dx[k]]) {
                    ch[i + dy[k]][j + dx[k]] = ch[i][j] + 1;
                    q1.push(i + dy[k]);
                    q2.push(j + dx[k]);
                    m = max(m, ch[i][j] + 1);
                }
            }
        }
    }
    cout << m << endl;
    return 0;
}

atcoder.jp

貪欲でよいか、動的計画にする必要があるか、それにしばらく時間を使う。まぁこれは価値ある時間の使い方だったと思う。 結論としては、場合分けを行えば貪欲でよい。

「上下左右どちらに落ちるか」を決めた場合、それぞれ進める方向と戻す方向へ動かせるタイミングであれば、自分の得するほうへ動かせばよい。

ただし、戻しすぎて逆側に落ちないように注意する必要がある。

場合分けは高々4方向しかないため、計算量的にはO(N)で済む。

def solve(H, W, N, sr, sc, S, T):
    # Up
    i = sr
    for s, t in zip(S, T):
        if s == "U":
            i -= 1
        if i < 1:
            return False
        if t == "D" and i + 1 <= H:
            i += 1
    # Down
    i = sr
    for s, t in zip(S, T):
        if s == "D":
            i += 1
        if i > H:
            return False
        if t == "U" and i - 1 >= 1:
            i -= 1

    # Right
    i = sc
    for s, t in zip(S, T):
        if s == "R":
            i += 1
        if i > W:
            return False
        if t == "L" and i - 1 >= 1:
            i -= 1

    # Left
    i = sc
    for s, t in zip(S, T):
        if s == "L":
            i -= 1
        if i < 1:
            return False
        if t == "R" and i + 1 <= W:
            i += 1

    return True

if __name__ == "__main__":
    H, W, N = tuple(map(int, input().split(" ")))
    sr, sc = tuple(map(int, input().split(" ")))
    S = input()
    T = input()
    print("YES" if solve(H, W, N, sr, sc, S, T) else "NO")

水色になったことでBeginnerではRating変更がなく、コンテストががくっと減ってしまったので、 最近はLeetcodeとアルゴリズム以外の勉強もしてる。まだまだ精進せな。

AtCoder Grand Contest 031

ついに水色になれた。精進します。 f:id:mitsuo_0114:20190317191001p:plain

atcoder.jp

"文字列として同一でも、異なる位置から取り出された部分列は区別して数えることとします。" ここを若干勘違いして10分ほどロス。

それぞれの文字から1個以下ずつ選び、最後にすべてゼロの分を抜けばよい。

from collections import Counter
def solve(N, S):
    d = 10**9 + 7
    c = Counter(S)
    ans = 1
    for ch, count in c.items():
        ans *= (count + 1)
    ans %= d
    return ans - 1

assert(solve(1, "a") == 1)
assert(solve(2, "ab") == 3)
assert(solve(3, "abc") == 7)
assert(solve(3, "baa") == 5)
assert(solve(6, "abcab") == 17)

if __name__ == "__main__":
    N = int(input())
    S = input()
    print(solve(N, S))

atcoder.jp 700点問題を取れたのは初。600点もなかった気がする。

最初はそんな簡単なわけがないと思いつつ、各色ごとの中から2つ選ぶだけでよいと考えた。 これはある色だけを見たとき、その中から二つ選び間を塗るという行為は、任意の区分を塗ることに等しくなると思っていたから。 が、実際にはこれは異なる。区間が3つに分割されるとき、左右の区間を塗ることは二つを選ぶだけでは満たすことができない。

色の数、石の数共に20000で、計算量N2の解法は取れない。計算量Nの解法を突き詰めて考える。

ではDPっぽく考えてみる。ある地点まででKパターンできたとする。 ここでさらに一つ石を追加したときを考える。

同じ色の石がその地点までに一つもなかったらパターンは一つも増えない。 もし、同じ色の石がその地点までに一つ以上あった場合は増えそうだとわかる。

ここで、追加したことによって新しくできる「既存の一番右にある同じ色の石」と「新しく追加した石」の区間を考えると この間を塗る、塗らないのに2パターンに分けることができる。

この2パターンに分けたとき、全体としては「既存の一番右にある同じ色の石」が左側に構成する区間の通り数の分だけ増えることがわかり、 この地点はKパターン+「既存の一番右にある同じ色の石、が左側に構成する通り数」となり、DPが成立する。 なお、コーナーケースとして同じ色が連続していたらそれは区間を作ったことにならないので、はじく。

また、新しく追加した石と、一番右以外にある同じ色の石が作る区間は考えなくてよい。 なぜならば、その区間はすでに一番右にある石が追加された段階で数え上げられていると考えてよいため。

結果として、一次元のDPだけれども、ある地点の解を構成する要素は石の並び方によって異なることになる、ちょっと変形したDPになる。 漸化式としては作れず、トップダウンは、、実装できない気がする。ともあれ、ボトムアップの実装にならざるを得ない。 DPの勉強をしてた時にこういうケースもあるんだろうなと考えていたがさっそく現実に。

def solve(N, Cs):
    dp = [-1 for _ in Cs]
    latest_c = {}
    ans = 1
    d = 10 ** 9 + 7
    for i, c in enumerate(Cs):
        if c not in latest_c:
            latest_c[c] = i
            dp[i] = ans
        else:
            previous_i = latest_c[c]
            if previous_i != i - 1:
                ans += dp[previous_i]
            latest_c[c] = i
            dp[i] = ans
    return ans % d


assert (solve(1, [1]) == 1)
assert (solve(2, [1, 1]) == 1)
assert (solve(3, [1, 1, 1]) == 1)
assert (solve(3, [1, 2, 1]) == 2)
assert (solve(3, [2, 1, 1]) == 1)
assert (solve(4, [2, 1, 2, 1]) == 3)
assert (solve(5, [1, 2, 1, 2, 2]) == 3)
assert (solve(5, [1, 2, 1, 1, 2]) == 3)
assert (solve(5, [1, 1, 1, 1, 2]) == 1)
assert (solve(5, [1, 2, 1, 2, 1]) == 5)
assert (solve(7, [1, 3, 1, 2, 3, 3, 2]) == 5)
assert (solve(6, [1, 3, 1, 2, 3, 2]) == 5)
assert (solve(3, [1, 3, 1]) == 2)
assert (solve(4, [1, 3, 1, 2]) == 2)
assert (solve(5, [1, 3, 1, 2, 1]) == 4)

assert (solve(20000, [1] * 20000) == 1)
assert (solve(20000, [1] * 10000 + [2] * 10000) == 1)
assert (solve(6, [1, 2] * 3) == 8)

if __name__ == "__main__":
    N = int(input())
    Cs = [int(input()) for _ in range(N)]
    print(solve(N, Cs))

AtCoder Beginner Contest 121

400点とれなかったー。回答なしで時間切れ後にできるのは悔しい。

atcoder.jp

これは200点問題だろう、、。解説も要らん。

def solve(N, M, ABs):
    ABs = sorted(ABs)
    ans = 0
    for a, b in ABs:
        if b < M:
            ans += a * b
            M -= b
        else:
            ans += M * a
            break
    return ans


if __name__ == "__main__":
    N, M = tuple(map(int, input().split(" ")))
    ABs = [tuple(map(int, input().split(" "))) for __ in range(N)]
    print(solve(N, M, ABs))

atcoder.jp

そしてこっちは2時間かかって爆死。

Aから始まってBの間にはそれぞれのbitで周期性があることはすぐわかるので、この間の1の数を数える。

周期性というのは、例えば、3から10まで書き出したとき、一番下のビットは2つごとに同じビット列が並び、 次のビットは4つごと、さらに次は8つごととなってること。

0000000011
0000000100
0000000101
0000000110
0000000111
0000001000
0000001001
0000001010

どん詰まりしたのは、「Aから始まって、周期の始まり」、「周期中」、「周期の終わりからBまで」の3つをバラバラに求めてカウントしようとしたから。

はまりにはまって、コンテスト終了後に、AからBまでの1の数は「0からBまでの1の数」から「0~A-1までの1の数」を引けばよいことに気が付く。 これは、別のメソッドの切り出して実装。 もっと大局的に考えなきゃならん。

明らかに遅いとわかりつつ、今回はslow_solveの実装が楽だったのでさくっと書いてテストで実行。 これがあるとほぼ完ぺきなテストができるので安心。

countのメソッドに関してはテストそのものを間違えてしまった。0を周期にカウントしなかったために、一つずれていた。 修正は苦労しなかったが気が付くのに時間がかかった、、assertの量が苦労を物語る。

def slow_solve(A, B):
    ans = A
    for i in range(A + 1, B + 1):
        ans = ans ^ i
    return ans


def count(k, cycle):
    k += 1
    tans = (k // cycle) * (cycle // 2)
    res = k % cycle
    if res >= cycle // 2:
        tans += res % (cycle // 2)
    return tans


assert (count(8, 8) == 4)
assert (count(7, 8) == 4)
assert (count(6, 8) == 3)
assert (count(5, 8) == 2)
assert (count(4, 8) == 1)
assert (count(3, 8) == 0)
assert (count(2, 8) == 0)
assert (count(1, 8) == 0)
assert (count(0, 8) == 0)

assert (count(8, 4) == 4)
assert (count(7, 4) == 4)
assert (count(6, 4) == 3)
assert (count(5, 4) == 2)
assert (count(4, 4) == 2)
assert (count(3, 4) == 2)
assert (count(2, 4) == 1)
assert (count(1, 4) == 0)
assert (count(0, 4) == 0)

assert (count(8, 2) == 4)
assert (count(4, 2) == 2)
assert (count(3, 2) == 2)
assert (count(2, 2) == 1)
assert (count(1, 2) == 1)
assert (count(0, 2) == 0)


def solve(A, B):
    cycle = 2
    ans = ""
    for _ in range(40):
        acount = count(A - 1, cycle)
        bcount = count(B, cycle)
        ans = str((bcount - acount) % 2) + ans
        cycle *= 2
    return int(ans, base=2)


assert (slow_solve(2, 4) == solve(2, 4))
assert (slow_solve(2, 5) == solve(2, 5))
assert (slow_solve(2, 6) == solve(2, 6))
assert (slow_solve(2, 7) == solve(2, 7))

assert (solve(1, 1) == 1)
assert (solve(2, 4) == 5)
assert (solve(123, 456) == 435)

if __name__ == "__main__":
    A, B = tuple(map(int, input().split(" ")))
    print(solve(A, B))

もっとしっかり考えましょう。そして400問題で精進しよう。 来週はGrandコンテストでレート下降は避けられないので水色はしばらくお預けかな、、、

AtCoder Beginner Contest 120

全ACできた。

atcoder.jp

初めは01と10の探索から始めたが、発見をした後、2個進める必要があるためforじゃ回しにくいと、 そもそもindexの扱いにして最初と最後のindexの操作を実装した、、が1文字目のindex走査がかなり複雑になり、 簡単なテストケースは通ったものの、一回WA。

300点でこのindex操作量はなかなかないなと思いつつ、テストケースを作ってるうちに気が付く。 これ、どのような01の順序でも、消えない順序がない。 つまり、0と1がどのような順番で存在しても、必ず消えるだけ消えることに気が付く。 ということで0か1のどちらか少ないほうの倍がそのまま答えになる。

def solve(S):
    return min(S.count("1"), S.count("0")) * 2

assert (solve("1") == 0)
assert (solve("0") == 0)
assert (solve("10") == 2)
assert (solve("101") == 2)
assert (solve("100") == 2)
assert (solve("01") == 2)
assert (solve("011") == 2)
assert (solve("0110") == 4)
assert (solve("1100") == 4)
assert (solve("111000") == 6)
assert (solve("101010") == 6)
assert (solve("000000") == 0)
assert (solve("010000") == 2)
assert (solve("011000") == 4)
assert (solve("011010") == 6)
assert (solve("011110") == 4)
assert (solve("000011110000") == 8)
assert (solve("101110") == 4)
assert (solve("100010") == 4)
assert (solve("010") == 2)
assert (solve("0" * 10000) == 0)
assert (solve("1" * 10000) == 0)
assert (solve("1" * 5000 + "0" * 1) == 2)
assert (solve("10" * 5000) == 10000)

if __name__ == "__main__":
    S = input()
    print(solve(S))

直前でWAした実装で苦労したのでテストが多い。。

atcoder.jp

まず、"崩壊"は考えにくいので逆順にして"建設"をしていく。

あるa, bをつなぐ橋を建設したとき、 a, b がすでにつながっているならば不便さは変わらず、 a, bがつながっていないならば不便さが変わる。

a, b がつながっているかどうかの判定はUnionFindの出番。

さらに不便さの計算は、K個、L個、P個の島々のセットがあった時、 「自分の島々の個数」*「自分の島々から到達できない島の個数」 であるため以下3つを足し、最後に2で割ればよい。

K * (N-K)
L * (N-L) 
P * (N-P)

素直に実装すると、N個の全島群から不便さをM回計算することになる。島群のため、橋が建設されるたびに計算量は減っていくが、 減る量は特に最初のころはあまり期待できないなのでまだ改善が必要。

そこで、不便さの変化量を考える。 橋を建設するとき、島aが所属する島々の数と島bが所属する島々の数だけに依存し、それ以外が変わることはない。

このため、島aが所属する島々と、島bが所属する島々の不便さをいったん取り除き、 マージした後にできた新しい島々の不便さを再計算してやることで、再度すべての計算をしなくて済む。

初期状態はすべての橋が崩壊しているとき(=一つも建設されていない時)で、この時、不便さはN * (N - 1) // 2。

import sys
sys.setrecursionlimit(100000)

class UnionFind:

    def __init__(self, n):
        self.parents = [i for i in range(n + 1)]

    def root(self, i):
        if self.parents[i] == i:
            return i
        else:
            self.parents[i] = self.root(self.parents[i])
            return self.parents[i]

    def unite(self, i, j):
        self.parents[self.root(self.parents[i])] = self.root(j)

    def is_unite(self, i, j):
        return self.root(i) == self.root(j)


from collections import Counter


def solve(N, M, ABs):
    uf = UnionFind(N)
    counter = Counter([i + 1 for i in range(N)])
    ans = []
    tans = N * (N - 1)
    for a, b in ABs[::-1]:
        ans.append(str(tans // 2))

        if not uf.is_unite(a, b):
            ar = uf.root(a)
            br = uf.root(b)

            tans -= counter[ar] * (N - counter[ar])
            tans -= counter[br] * (N - counter[br])
            counter[br] += counter[ar]
            tans += counter[br] * (N - counter[br])

            uf.unite(a, b)
    return "\n".join(ans[::-1])


assert (solve(2, 1, [(1, 2)]) == "1")
assert (solve(4, 5, [(1, 2), (3, 4), (1, 3), (2, 3), (1, 4)]) == "0\n0\n4\n5\n6")
assert (solve(6, 5, [(2, 3), (1, 2), (5, 6), (3, 4), (4, 5)]) == "8\n9\n12\n14\n15")

if __name__ == "__main__":
    N, M = tuple(map(int, input().split(" ")))
    ABs = [tuple(map(int, input().split(" "))) for _ in range(M)]
    print(solve(N, M, ABs))

AC取れたのはよいが、今回は時間かかりすぎた感が否めない。

あとpythonの再帰回数にやられて謎のRE状況から10分悩んだ+15分ペナルティは痛い。 UnionFindで1000回以上して再帰してREってのはすぐに思いつかなかったわ。。