AtCoder Beginner Contest 171 - D - Replacing / E - Red Scarf

スピード勝負だった。Fは結局解けない。。

atcoder.jp

数列が与えられ、特定の数列の値を別の値に書き換える操作を行う。 操作ごとにすべての和を答える。

Dにしてはとても簡単。

初期段階での数列の和を取っておいたとき、 BをCに変化させたとき、Bがb個あったとすれば、(C - B) * bだけ和が増える。

Counterで個数を数えておきながらこの差分だけ計算すれば毎回O(1)の計算量で、和を求めることができる。

import collections

def solve(N, As, Q, BCs):
    counter = collections.Counter(As)
    current_sum = sum(As)
    ans = []
    for b, c in BCs:
        if b in counter:
            original = counter[b] * b
            to = counter[b] * c
            diff = to - original
            current_sum += diff
            if c not in counter:
                counter[c] = 0
            counter[c] += counter[b]
            del counter[b]
        ans.append(str(current_sum))
    return "\n".join(ans)

if __name__ == "__main__":
    N = int(input())
    As = list(map(int, input().split(" ")))

    Q = int(input())
    BCs = [tuple(map(int, input().split(" "))) for _ in range(Q)]

    print(solve(N, As, Q, BCs))

atcoder.jp

数列が与えられる。この数列はある別の数列を元に、自分のindex以外のすべての値のxorを取ったもの。 元の数列を求める。数列の長さは偶数であることが保証される。

これE問題?C問題ぐらいだろう。。

まず、与えられた数列すべてのxorを取りKとする。 このKは数列の長さをNとしたとき、元の数列の各値をN-1回ずつxorしたものと等しくなる。

Nが偶数であるため、N-1は奇数であり、Kにはすべての要素が奇数回ずつxorされて含まれていることになる。

ここに、最初の数列のある位置の値をもう一度xorすると、その位置の元の値だけが含まれていないため、 元の位置の値が奇数回、それ以外が偶数回xorされることになる。

偶数回のxorは値が0になるため、奇数回の値が浮かび上がり、それがそのまま答えになる。

def solve(N, As):
    k = 0
    for a in As:
        k ^= a
    ans = []
    for a in As:
        ans.append(str(k ^ a))
    return " ".join(ans)

if __name__ == "__main__":
    N = int(input())
    As = list(map(int, input().split(" ")))
    print(solve(N, As))

AtCoder Beginner Contest 169 - D - Div Game / E - Count Median

Eまで通せたが、もうちょっと早くやりたかった。

atcoder.jp

こっちは割と典型問題。素因数分解を行った後で、各素数pにおいて p1, p2, p3.... とzを取っていけばよい。素因数分解の実装がさくっとかけたのは満足。

def factrization(N):
    k = 2
    ret = {}
    L = N ** 0.5
    while k < L:
        if N % k == 0:
            ret.setdefault(k, 0)
            ret[k] += 1
            N //= k
        else:
            k += 1
    if N > 1:
        ret.setdefault(N, 0)
        ret[N] += 1
    return ret

def solve(N):
    f = factrization(N)
    ans = 0
    for n in f.values():
        i = 1
        while i <= n:
            n -= i
            i += 1
            ans += 1
    return ans

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

atcoder.jp

中央値の問題はいつも偶数がやっかい。

まずN=奇数のときから考える。

この時、ある整数が中央値となる可能性を持つためには、 その整数よりも小さい数が(N-1)//2以上存在しかつ、 その整数よりも大きい数は(N-1)//2つ以上存在する範囲を考えればよい。

例えば3個の以下のような範囲が並べられたときには、(3 - 1) // 2 = 1個以上、 左右に存在する範囲が中央値の存在する範囲となる。

f:id:mitsuo_0114:20200531223536p:plain

N=偶数の時はすこしやっかい。 まず、中央値の計算の元になる中央2つの数値の範囲を最初に求める。

中央二つのうち左側は その整数よりも小さい数がN//2-1つ以上存在しかつ、 その整数よりも大きい数はN//2つ以上存在すればよい。(下の図、存在範囲1)

中央二つのうち右側は その整数よりも小さい数がN//2つ以上存在しかつ、 その整数よりも大きい数はN//2-1つ以上存在すればよい。(下の図、存在範囲2)

さらにここから中央値が存在しうる範囲は、 (存在範囲1の最小と存在範囲2の最小の中点)~(存在範囲1の最大と存在範囲2の最大の中点)となる。(下図、中央値の存在範囲) f:id:mitsuo_0114:20200531224944p:plain

ソースコード量は大したことないが偶数の考察が厄介。

def solve(N, ABs):
    starts = []
    ends = []
    for a, b in ABs:
        starts.append(a)
        ends.append(b)
    starts.sort()
    ends.sort()

    if N % 2 == 1:
        k = (N - 1) // 2
        left = starts[k]
        right = ends[-(k + 1)]
        return right - left + 1
    else:
        k = N // 2 - 1
        left1 = starts[k]
        left2 = starts[k+1]
        left_mid = abs(left2 - left1) / 2 + left1

        right1 = ends[-(k+2)]
        right2 = ends[-(k+1)]
        right_mid = abs(right2 - right1) / 2 + right1
        d = (right_mid - left_mid) * 2 + 1
        return int(d)

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

AtCoder Beginner Contest 167 - E - ∙ (Bullet)

atcoder.jp

イワシは相性によってグルーピングができる。

このグルーピングさえうまく処理できれば、あとは数え上げればよい。

a匹のグループAとb匹のグループBの相性がそれぞれ悪かった時、 各グループの内部での取る取らないで2のa乗、2のb乗通りできる。 この時、1匹も取らないをダブルカウントしてしまっているので1を引けばよい。

グループAに対し、相性が悪いグループがいなければそのまま2のa乗とすればよい。

これらとは独立したグループC、グループDと居たとき、これらはグループA/Bとは独立しているため、 掛け算でMODを取りながら集計すればよい。

また、(0, 0)と(0, x), (x, 0)は特別扱いする必要がある。 といっても、(0, 0)はすべてと相性が悪いので、1つずつでグループを作る。 そのため、その数をそのまま足せばよい。(0, x), (x, 0)は特別な値として持てばよい。

と、ここまでは問題なかった。

グルーピングはまず、O(N2)では扱いきれないため、何か値としてハッシュマップに持っておく必要がある。

ここでキーは比の値が最初に思いつくが比の値が非常に小さくなり、誤差を扱いきれなくなる。 この誤差を扱おうとしたが時間切れ。

教訓として、この誤差を減らすように頑張るぐらいならそのまま持つのが吉。

「比は互いに素にしてしまえば、一意に決まる」ってまぁ考えれば当たり前なのを思いつかなかった。

正負の扱いに注意して対応すればまぁ時間内にもとけた気がするだけに悔しい。

import math
MOD = 1000000007

def solve(N, ABs):
    groups = {}
    zero_count = 0
    for a, b in ABs:
        if a == 0 and b == 0:
            zero_count += 1
            continue
        if a == 0:
            k = (0, 1)
        elif b == 0:
            k = (1, 0)
        else:
            g = math.gcd(abs(a), abs(b))
            a //= g
            b //= g
            if a * b < 0:
                k = (abs(a), -abs(b))
            else:
                k = (abs(a), abs(b))
        groups.setdefault(k, 0)
        groups[k] += 1

    visited = set()
    possibles = []
    for k, v in groups.items():
        if k in visited:
            continue
        p = 0
        p += pow(2, v, MOD)
        if k[1] == 0:
            m = (0, 1)
        elif k[0] == 0:
            m = (1, 0)
        else:
            if k[1] < 0:
                m = (-k[1], k[0])
            else:
                m = (k[1], -k[0])


        if m in groups.keys() and m not in visited:
            p += pow(2, groups[m], MOD)
            visited.add(m)
            p -= 1
        visited.add(k)
        possibles.append(p % MOD)

    ans = 1
    for p in possibles:
        ans *= p
        ans %= MOD

    if zero_count:
        ans += zero_count
        ans %= MOD
    return (ans - 1) % MOD


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

E問題は大体そのままやると2回、3回ミスるので 遅いが以下のような確実な解法と合わせてチェックすると考慮漏れが少ない。

def slow_solve(N, ABs):
    import itertools
    ans = 0
    for k in range(1, 2**N):
        b = ("{:0" + str(N) + "b}").format(k)
        taken = []
        for i, c in enumerate(b):
            if c == "1":
                taken.append(ABs[i])

        if len(taken) == 1:
            ans += 1
        elif all(comb[0][0] * comb[1][0] + comb[0][1] * comb[1][1] != 0
                 for comb in itertools.combinations(taken, 2)):
            ans += 1
    return ans % MOD

def create(N):
    import random
    ABs = []
    MM = 10
    for _ in range(N):
        ABs.append( (random.randint(-MM, MM), random.randint(-MM, MM)))
    return ABs

for _ in range(1000):
    for N in range(1, 10):
        ABs = create(N)
        if solve(N, ABs) != slow_solve(N, ABs):
            print("ERROR")
            print(solve(N, ABs))
            print(slow_solve(N, ABs))
            print(N)
            print(ABs)
            break

AtCoder Beginner Contest 163

atcoder.jp

30分ぐらい。もう少し早く解きたかった。

まず、10100のおかげで、K個取るとき、K+1個取るとき、K+2個取るとき、・・・で、和がかぶることはなく独立して考えることができる。

N=10とし、このうち3個だけを考えてみたとすると以下の11個の中から自由に3つ取り、何種類の和ができるかを考える。

0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10

すると、最小は一番左の3つを取った時の3,最大は一番右の3つを取った時の27となり、この間はすべて作ることができるため、 差+1の25パターンが存在することができる。

これを同様に4個、5個と計算していけばよい。

左から取ったとき、右から取ったときはあらかじめ累積和で計算しておけばここの部分の計算はO(1)で計算可能なので十分に間に合う。

def solve(N, K):
    M = 10 ** 9 + 7
    pre = []
    post = []
    s = 0
    for i in range(N + 1):
        s += i
        pre.append(s)

    s = 0
    for i in range(N, -1, -1):
        s += i
        post.append(s)
    post = post[::-1]

    ans = 0
    for i in range(K, N + 2):
        a = post[N + 1 - i] - pre[i - 1] + 1
        ans += a
        ans %= M
    return ans


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

atcoder.jp

うーむつらい。解説見てコード起こした。

大きい順に左右に振り分ける、というところまでは思いついた、が 局所解が大局解になりえないことに気が付いて(左右が同じ距離だった時局所解は同じだが、そのあとの状況によって大局解として異なる可能性がある)、 DPでも適応するのかな、で終わってしまった。

このDPの適応の仕方は思いつかなかった。。

自分なりの解説の解釈だとこうなった

dp[0][0] # => 1つも振り分けてないときの最大スコア

dp[1][0] # => 1番目に大きい値を左に振り分けたときの最大スコア
dp[2][0] # => 1番目に大きい値、2番目に大きい値をどちらも左に振り分けたときの最大スコア

dp[0][1] # => 1番目に大きい値を右に振り分けたときの最大スコア
dp[0][2] # => 1番目に大きい値、2番目に大きい値をどちらも右に振り分けたときの最大スコア

dp[1][1] # => 1番目に大きい値、2番目に大きい値をどちらか一つは左に、もう一つは右に振り分けたときの最大スコア

dp[1][1]は以下のどちらか大きいほうである。

  1. dp[0][1] + 2番目に大きい値を左に振り分けたときのスコア
  2. dp[1][0] + 2番目に大きい値を右に振り分けたときのスコア

この時、3番目に以降に大きい値は、1番目と2番目がどのように割り振られたとしても状況は変わらないため、順々に計算をすることができ、 再帰的に以下のように言うことができる

dp[x][y] = max(
     dp[x-1][y] + x+y番目に大きい値を左に振り分けたときのスコア,
     dp[x][y-1] + x+y番目に大きい値を右に振り分けたときのスコア
)

以下、後述のコード解説。 以下の部分で、すべてを右に振り分けたとすべてを右に振り分けた場合を先に計算しておく。

    for a, i in q:
        dp[x+1][y] = dp[x][y] + a * abs(i - x)
        x += 1
    for a, i in q:
        dp[x][y+1] = dp[x][y] + a * abs(N - 1 - y - i)
        y += 1

右振り分けは右端からの距離の注意して以下の3つに分解できる。 - N - 1 (=一番右端の座標) - y (=右へ振り分けた後の右端からの距離) - i (=元の座標)

最初の二つを用いて座標を特定し、元の座標との差を取れば移動距離が出る。

肝は以下の部分。

            a, i = q[x+y-1]
            left = a * abs(i - (x - 1))
            right = a * abs(N - 1 - (y - 1) - i)
            dp[x][y] = max(dp[x-1][y] + left, dp[x][y-1] + right)
            ans = max(ans, dp[x][y])

x+y番目に大きい値をとってきた後、これを左に振り分けたときと右に振り分けたときのスコアを考える。

ここで注意しなければならないのは、左に振り分けたときの状況はdp[x-1][y]から考えるため、 左に振り分けたときのその座標はx-1となり、移動距離はabs(i - (x-i))となること。

同様に右に振り分けたときは、先ほどの式のうち、yがy-1になる。

def solve(N, A):
    q = [(a, i) for i, a in enumerate(A)]
    q.sort(reverse=True)
    dp = [[-1 for _ in range(N + 1)] for __ in range(N + 1)]

    x = 0
    y = 0
    dp[x][y] = 0
    for a, i in q:
        dp[x+1][y] = dp[x][y] + a * abs(i - x)
        x += 1
    x = 0
    for a, i in q:
        dp[x][y+1] = dp[x][y] + a * abs(N - 1 - y - i)
        y += 1

    ans = 0
    for x in range(1, N+1):
        for y in range(1, N+1):
            if x + y > N:
                continue
            a, i = q[x+y-1]
            left = a * abs(i - (x - 1))
            right = a * abs(N - 1 - (y - 1) - i)
            dp[x][y] = max(dp[x-1][y] + left, dp[x][y-1] + right)
            ans = max(ans, dp[x][y])
    # [print(row) for row in dp]
    return ans

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

dp[x][y]はx+y <=N という制限が付くため、 N+1 x N+1の行列のうち左上三角の部分だけが埋まることになる。

これらのうち最大が答えになる。

AtCoder Beginner Contest 161

atcoder.jp

上から作ったり、下から作ろうとしたり、dpを考えてみたが、 0の扱いが難しくて、下からは作れなかったし、上から作ると、何番目、の扱いが非常に面倒。

しばらく考えて苦肉の策として、Nがそこまで大きくないこととサンプルテストに最大値が出ていることに甘えて、 ルンルン数を100000番目を超える範囲ですべて作った。

コードテストで1000ms以下で実行できることを確認してからsubmit。

def create_lun(K):
    if K == 1:
        return ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
    else:
        r = []
        d = create_lun(K - 1)
        for n in d:
            c = int(n[-1])
            if K == 10 and int(n[0]) > 3:
                continue
            if c - 1 >= 0:
                r.append(n + str(c - 1))
            r.append(n + n[-1])
            if c + 1 <= 9:
                r.append(n + str(c + 1))
        r.extend(d)
        return r


def solve(K):
    A = create_lun(10)
    S = set([int(l) for l in A])
    S.remove(0)
    A = sorted(list(S))

    return A[K - 1]


if __name__ == "__main__":
    K = int(input())
    print(solve(K))

AtCoder Beginner Contest 160

atcoder.jp

もう少し早くできた、、はず。

ワーシャルフロイドの本質をきちんと理解してなかったが故に実装に時間がかかった。 当然、Nの制約からワーシャルフロイドを直接使ったO(N3)はできないが、 それでも、ワーシャルフロイドの考え方がベースになっている。

ワーシャルフロイドは、「ある地点を経由したときに距離が更新されるか」を総当たりで試すもの。 この更新はアルゴリズムイントロダクションでは「緩和(relax)」と表現されている。

本問では、まずX地点とY地点の接続を考えずに2点間の距離を計算した後 X地点とY地点の接続(距離を1)し、そこから緩和をすればよい。

フロイドワーシャルの一番外側のループ、「どこを経由するか」を、XとYだけに限定すれば、 実質定数倍のため、O(N2)として計算することができる。

#include <bits/stdc++.h>

#define rep(i, a, b) for (long long int i = (a); i < (b); i++)

using namespace std;
using ll = long long int;
using ld = long double;

auto p = [](auto s) {
    cout << s << endl;
};

int main() {
    int N, X, Y;
    cin >> N >> X >> Y;
    vector<vector<int>> d(N, vector<int>(N));
    rep(i, 0, N) {
        rep(j, 0, N) {
            d[i][j] = abs(i - j);
        }
    }
    X--;
    Y--;

    d[X][Y] = 1;
    d[Y][X] = 1;
    rep(i, 0, N) {
        rep(j, 0, N) {
            d[i][j] = min(d[i][j], d[i][X] + d[X][j]);
        }
    }
    rep(i, 0, N) {
        rep(j, 0, N) {
            d[i][j] = min(d[i][j], d[i][Y] + d[Y][j]);
        }
    }
    vector<int> ans(N);
    rep(i, 0, N) {
        rep(j, 0, N) {
            ans[d[i][j]] += 1;
        }
    }

    rep(k, 1, N) {
        p(ans[k] / 2);
    }
    return 0;
}

atcoder.jp

代わってこっちは500点問題とは思えないぐらいの簡単さ。

これぐらいの問題で考えるのは特定の方法で貪欲的に行けるか、つまり局所解が最適解を導くかをとても気にする。 これがうまくいかないならば動的計画法に切り替える必要があるが今回は貪欲的に行ける。

P、Qの配列の中で上位X個、Y個だけに注目し、この中でRの中と交換を行ったときに一番利益が多くなるリンゴを考えると、 これは、P、Qの最小のものと、Rの中の最大のものを交換すればよいことになる。(もちろん交換する価値があれば)

その次はP、Qの2番目に小さいものと、Rの2番目に大きいもの、と順々に見て行って交換していけばよい。

その途中で、Rのリンゴが、PQよりも利益が小さくなった場合、Rの残ったリンゴの利益はすべて今調べているものより等しいか小さく、 PQの残ったリンゴも、等しいか大きい。つまりこれ以後チェックする価値はない。


def solve(X, Y, P, Q, R):
    P = sorted(P, reverse=True)[:X]
    Q = sorted(Q, reverse=True)[:Y]
    R.sort(reverse=True)
    PQ = sorted(P + Q)
    i = 0
    while i < len(R) and i < len(PQ) and R[i] > PQ[i]:
        PQ[i], R[i] = R[i], PQ[i]
        i += 1
    return sum(PQ)


if __name__ == "__main__":
    X, Y, A, B, C = tuple(map(int, input().split(" ")))
    P = list(map(int, input().split(" ")))
    Q = list(map(int, input().split(" ")))
    R = list(map(int, input().split(" ")))
    print(solve(X, Y, P, Q, R))

よく考えたら、上位X個、上位Y個とRすべてをソート、でもよいのか。 500点問題ってなんだったっけ、、、

AtCoder Beginner Contest 159

しばらくやめてたがまた再開する。

atcoder.jp

「一つだけ抜く」パターンは全体をあらかじめ求めておいて、その抜かれた影響を考えることで計算量がO(N)になるケースが多い気がする。

今回もそんな感じで、抜かれた数字が元々C個あったとすると、そのうちの2個の組み合わせはC * (C - 1) / 2となる。 ここから1つ抜かれて、C-1個になれば(C - 1) * (C - 2) / 2個になるので、この差分をあらかじめ取っておけばよい。

Submission #11102912 - AtCoder Beginner Contest 159

import collections

def solve(N, A):
    c = collections.Counter(A)
    originals = [0 for i in range(N + 1)]
    minus = [0 for i in range(N + 1)]
    for (k, v) in c.items():
        originals[k] = v * (v-1) // 2
        minus[k] = ((v * (v-1)) - (v-1) * (v-2)) // 2
    S = sum(originals)

    ans = []
    for a in A:
        ans.append(str(S - minus[a]))
    return "\n".join(ans)

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

atcoder.jp

領域分割の問題。少し考えて、最適な分割方法を何かで探す、というのは早々にあきらめて、全探索ができるか検討。

Hの制約が非常に小さいことから、切るか切らないか、をH-1か所で最大512パターンになるのは予想でき、全探索で行けそうな予感がしたが、どうやってH分割を実装するかを少し悩んだ。

結論としては二進数で、前後が変わったら違うグループにする、という実装をした。 つまり、0101は4つのグループに分かれ、0000は1つのグループ、0011は2つのグループ、といった具合にする方針。

    for g in create_groups(H):
        M = len(set(g))
        s = [0 for _ in range(M)]

ここからcreate_groupsの戻り値は、あとで参照しやすいようにグループ番号を入れた。 例えば、0110の3つのグループに分かれた場合、gには[0, 1, 1, 2]という配列が入る。 このgはHの各行のグルーピングを表していて、sにはグループごとのカウントを入れている。

カウント時にはhの代わりにg[h]とすることで、対象グループのカウントを増やすことになる。

    s[g[h]] += 1

あとは、足しすぎたら直前で分割したことにして、再度この列を足し、

            if any([a > K for a in s]):
                s = [0 for _ in range(M)]
                tans += 1
                for h in range(H):
                    if fields[h][w] == "1":
                        s[g[h]] += 1

もし、この1列だけでもKを超えてしまうようならばH分割が足りてないので終了にする。

            if any([a > K for a in s]):
                tans = H * W + 1
                break

一番外側の1回のループで、求められる分割数はH分割+W分割であり、 全走査しながら、全体の分割数の最小値を取ればよい。

Submission #11123996 - AtCoder Beginner Contest 159

def create_groups(H):
    F = "{:0" + str(H) + "b}"
    ret = set()
    for n in range(2 ** H):
        g = create_group(F.format(n))
        ret.add(tuple(g))
    return list(sorted(ret))


def create_group(s):
    ret = []
    n = 0
    for i in range(len(s)):
        if i == 0:
            ret.append(0)
        else:
            if s[i-1] == s[i]:
                ret.append(n)
            else:
                n += 1
                ret.append(n)
    return ret


def solve(H, W, K, fields):
    ans = H * W + 1
    for g in create_groups(H):
        M = len(set(g))
        s = [0 for _ in range(M)]
        tans = M - 1
        for w in range(W):
            for h in range(H):
                if fields[h][w] == "1":
                    s[g[h]] += 1
            if any([a > K for a in s]):
                s = [0 for _ in range(M)]
                tans += 1
                for h in range(H):
                    if fields[h][w] == "1":
                        s[g[h]] += 1
            if any([a > K for a in s]):
                tans = H * W + 1
                break
        ans = min(ans, tans)
    return ans

if __name__ == "__main__":
    H, W, K = tuple(map(int, input().split(" ")))
    fields = []
    for _ in range(H):
        fields.append(input())
    print(solve(H, W, K, fields))

もう少し早く実装したいところだったがまぁパフォーマンス1600超えたので良しとしよう。