ラベル TopCoder の投稿を表示しています。 すべての投稿を表示
ラベル TopCoder の投稿を表示しています。 すべての投稿を表示

2022年7月8日金曜日

2022 TCO Algo Round 3

 0完、チャレンジもできず敗退しました。


Easy TwoDimensionalSort 

 解法ツイートを見てAC。

 一つ一つ文字を揃えていこう……と考えていくと失敗する。
 せっかく26*26あるので、もっと盤面をめいっぱい使う方法はないだろうか。

 全ての文字の行数のみソートされていれば良いので、そうするためにどうすれば良いだろうか? と考える。すると、

・全ての文字の列数をdistinctにする

 ことさえできれば後は簡単だ。と、思いつける、のだろう。

 これが思いつかなかったため解けませんでした。

2022年5月22日日曜日

2022 TCO Algo Round 2A

 Easyのみだがレート的には軽傷で済んだ。

コンテスト後のツイート

 Medはコーナーに気付けなかった。ちょっと修正も面倒くさそうなので、ACしていない。
 Hardは半分全列挙らしい。半分全列挙自体は考えたはずなのに、それでいけると思えなかったのは良くない。ただ、PythonでACするのは無理だろうから、どっちみちC++で書き終えることはできなかっただろう。

2022年4月17日日曜日

2022 TCO Algo Round 1A

 全完。

コンテスト後のツイート

 MedをMidと書き間違えたツイートを貼り付けるのはちょっと恥ずかしい。

 Medの解法が上のツイートみたいになるのは分かりにくいけど、AとBが違うので、遷移図を描けば、まあ多分そうか、と思えた。(が、改めて考えるとよく分かっていない気もしてきた……)

2021年8月22日日曜日

TopCoder Single Round Match 811

 Easy, Medの二問解けて黄色に復帰。
 Easyはあまりにも簡単で驚いたが、他の人がすぐ提出しているのを確認して、提出。


Div1 Med SmoothDivisors 

 ちょっと実験したら、各素数について、その素数を因数に持つ約数が最低三つあればOKそう、と分かった。ので、p, qを素数としてp*q, p*p*qのときのみダメだろう、と。ちゃんとした証明はできてないけど、まあそうだろう、という気持ちで実装へ。

 ただ、Pythonでは間に合いそうにないので、C++を書くのに戸惑ってしまった。
 でもまあ、遅かったとはいえ、C++でちゃんとACできたこと自体が少ないので、ACできたことを喜ぼう。


2021年8月5日木曜日

2021 TCO Algo Regional Qualification 1

  Easyで誤読&実装方針をミス、解けたと思ったMedはシステムテストで落ちて0完。青に落ちてしまった。


OlympicShooting

 これは読解&実装問題。
・スコアの合計が高い方が勝ち
・同じなら、後ろから順に、25回ずつのスコアが高い方が勝ち。
・それも同じなら、より後でスコア獲得した方が勝ち。

 というのを実装する。

 最初、「25回ずつ」というのを読み飛ばして戸惑った。
 その後、上手い実装がないかと考えていたのが失敗(とはいえ、短くコードをまとめている人もいた)。多少時間がかかっても愚直に書けば良かった。

TreeTokens

 解法はすぐに分かり、それであっていたが実装でミスしてしまった。

 ROOTに駒がこないためには、ROOTの隣は1個しかあってはダメ。じゃあ、その隣は、というと、3個までしかあってはダメ。その隣は7個まで。さらにその隣は15個まで。

 つまり、ある頂点の駒が高々i個になるためには、その隣の頂点での駒は高々2*i+1個であれば良い。問題が直線状で、ROOTが端にあれば、N=(頂点数-1)として、2^N-1個が答えになる。

 では分岐があったときは? というと、葉が深い方の枝に駒を増やしていく(2*i+1倍していく)のが最善。もう一つの分岐は、駒1個からまた増やしていく。

 そういう感じで、各頂点について「葉までの距離のmax」を前計算した後、ROOTの方がから木DPをしていく感じに解いた。

 ……のだが、「葉までの距離のmax」の前計算する箇所で間違えた。
 これはよくある木DPなので、トポロジカルソートの逆順に見ていけば良いのだが、なぜかDFSみたいにしてしまっていた。

 焦っていたとはいえ良くない間違いでした。

 システムテストを通ったコードです。
 
mod=1000000007

class TreeTokens ():
    def placeMax(self, N, G, L, seed):
        state = seed
        E=[[] for i in range(N)]
        for i in range(1,N):
            state = (state * 1103515245 + 12345) % (1<<31)
            tmp = (state // 1000) % L
            p = max(0, i-1-tmp)

            E[i].append(p)
            E[p].append(i)

        #print(E)

        ROOT=G

        QUE=[ROOT] 
        Parent=[-1]*(N+1)
        Parent[ROOT]=N # ROOTの親を定めておく.
        TOP_SORT=[] # トポロジカルソート

        while QUE: # トポロジカルソートと同時に親を見つける
            x=QUE.pop()
            TOP_SORT.append(x)
            for to in E[x]:
                if Parent[to]==-1:
                    Parent[to]=x
                    QUE.append(to)


        CC=[-1]*N

        for x in TOP_SORT[::-1][:-1]:
            if CC[x]==-1:
                CC[x]=1
            if Parent[x]==ROOT:
                continue
            CC[Parent[x]]=max(CC[Parent[x]],CC[x]+1)



        AC=[0]*N
        AC[ROOT]=0
        
        ANS=0

        QUE=[ROOT]
        while QUE:
            x=QUE.pop()

            MAX=-1
            if len(E[x])==1 and x!=ROOT:
                ANS+=AC[x]
                ANS%=mod
                
            for to in E[x]:
                if to==Parent[x]:
                    continue
                MAX=max(CC[to],MAX)

            USE=0
            for to in E[x]:
                if to==Parent[x]:
                    continue
                
                if CC[to]==MAX and USE==0:
                    USE=1
                    AC[to]=(AC[x]*2+1)%mod
                else:
                    AC[to]=1

                QUE.append(to)
                
        return ANS%mod

2021年7月25日日曜日

TopCoder Single Round Match 810

  EasyはHackされたけどMedが通って助かった。


Div1 Easy WatchedSnail


 二人ずつペアにして考えることが重要だった。
 三人以上重なってもワープできると思い込んでいたのでHackされたのも当然。図を描いたとき、一瞬、二人までしかダメじゃない? とは思ったんだけど、なぜか大丈夫な気がしてしまった。

Div1 Med IcelandRingRoad


 AtCoderのこの問題の部分問題になっている。

 この問題はACしたことなかったけど、問題を読んだことあったため、この公式解説を読む、というムーブが取れ、救われた。

 ハッシュを使った乱択解なのでやや怪しく見えるのか、tourist氏にチャレンジされたり、neal_wu氏に褒められたりした。SRMでチャットしたの多分初めて! ちょっと興奮した。

 これを機に、AtCoderの方の問題もACしました。

2021年2月5日金曜日

TopCoder Marathon Match 123

 参加していました。


 前回のMarathon Match 122も参加したのですが、実装終わると思ったものが書き終わらず、時間ギリギリで出したコードがバグあり&エラー出力消し忘れ(のため大きいサイズだとTLEする)がひどく、レートを大きく落としてしまいました……。

 それがショックだったので、今回は初日からちゃんと参加。とはいえ、初提出が終了日前日と、余裕はあまりありませんでしたが。

 ルールは、盤面のどこでも二点をSwapできるマッチ3ゲームで、1000ターンでどれだけ点数を出せるか(点数のルールは説明省略)。
 ビジュアライズが宝石になっていたので、「コラムス」か? と思いましたが、あれは斜めも消せました。

やったこと


 Xターン後、スコアを最大化できるようなSwapを探索……とかだと計算時間が間に合わなそう(特にPythonだと)なので、その方針はあまり考えず、適切なパターンを作って、それに当て嵌める方針でいきました。
 ツイッター等で他の人の解法を見る限り、その方針は正しかったよう。

 ただ、そのパターンへの組み換えへの実装には結構苦労しました。
 一連鎖目から色が揃っているかを見ていき、揃っていなければ、後の連鎖のやつの中で、その色を持っているやつからもらう……みたいな方針です。
 どの色で組むかは、その数個の中で最も多い個数のやつを採用するようにしました。

 パターンについて。
 点数計算を見ると、長い連鎖を組むのが良いですが、長いサイズの消しも一回くらいはあると良さそう、ということで、二連鎖目に横長の消しをするようにしています。暴発の防ぎ方が分からず、長い連鎖が組めなそう……と分かってからはそこを重視しました。
 ただ、横16マス消しも可能だということが分かったのが最終日夜だったのは痛かった。もっと前に気付いていれば、パターン作成を詰められたかもしれません。

 自分のパターンは、二連鎖目に長いサイズ(最大N-1個)を一番下の段で消して、そこから3個ずつ、前の連鎖に一個か二個重なるように置いて、連鎖を続けていくというもの。そのどれを選ぶか、というのを目で見て決めました。ただ、適当に置くと(連鎖ごとの色が全て異なっていても)連鎖中に暴発してしまうことがあるので、そこはプログラムでチェックしています。
 そうやって作ったパターンの中で、実際に実行したとき点数が良さそうなものを採用しました。

反省点

 点数が伸びなかった原因ははっきりしていて、パターン構築が甘かった、これにつきます。もちろん、パターンへの組み替え方の実装なども良くないのですが、一番の原因はそこ。
 一番下の段ではなく、盤面の中程で横長の消しを行い(それも、N-1個ではなく、N個を数段同時にやってもいい)、そこから下に向かって連鎖を繋げていくような方針なら、もっと安全に長い連鎖を組めた模様。

 パズル部分で考察が及んでいなかった、というのは残念。

 パターン次第ではもっと点数が伸びるはず、とはずっと思っていたのに、そこを詰められなかったのは良くないですね。

 実装力(や言語選択)のせいで負けた、と言えるような成績を取りたい。今回の自分の方針だと、他の言語に書き換えたとしてもほぼ点数は伸びません(一応、最後着火する前に、一手で連鎖が増やせるSwapがあるか調べる探索を入れているので、その部分は伸ばせるかもしれませんが、誤差の範囲でしょう)。

 マラソン系のコンテストでは、大体いつも、考察パズル部分ではっきり負けていた……とコンテスト後に分からされているので、もっとここに力を注ぐべきですね。

2021年1月24日日曜日

TopCoder Single Round Match 798

 Easyをパッと思いつけなかった上、Pythonじゃ通らない制約のためC++へ書き直したためタイムロス。そのためMedが解きおわらず。


Div1 Easy SuperSubset


 部分和問題なのでDPしようとは思えて、あとは上手く辻褄合わせれば良い。
 具体的には、初期値をDP[0]=pow(2,len(A))にして、一つ使ったら自由度が半分になるので、
・DP[i+a]+=DP[i]/2
 みたいに遷移させれば良い。

Div1 Med ExpectedValue


 コーディングフェーズ終了三分後に解き終わった。
 A[i]=jのとき、iとjを結ぶとすると、N個の要素が何個のグループに分けられるかで答えは決まる。
 愚直解を書いてグループの個数を調べると、
・DP[i][j]=(DP[i-1][j]*(i-1)+DP[i-2][j-1]*(i-1)
 のように推測でき、これで答えを求めたら通った。

 以下、Practiceでシステムテストを通ったコード。

mod=10**9+7
DP=[[0],[0],[0,1],[0,2],[0,6,3]]

for i in range(5,1501):
    X=[0]*(i//2+1)
    for j in range(1,i//2+1):
        if i%2==0 and j==i//2:
            X[j]=(DP[-2][j-1]*(i-1))%mod
        else:
            X[j]=(DP[-1][j]*(i-1)+DP[-2][j-1]*(i-1))%mod
            
    DP.append(X)
        

class ExpectedValue():
    def solve(self, N):
        S=sum(DP[N])
        ANS=0
        for i in range(len(DP[N])):
            ANS=ANS+i*DP[N][i]
            ANS%=mod
        return (S*N-ANS)*pow(S,mod-2,mod)%mod

2021年1月10日日曜日

TopCoder Single Round Match 797

0完ですがHack1成功でレートは若干プラス。

Div1 Easy FlightPlan

 使える高さを固定すれば、スタートからゴールまで最低何歩で行けるかはBFSで書けるので、高さを全探索すれば良い。ただ、$O(N^4)$をPythonで通すのは無理。頑張ってC++を書いたものの、システムテストで落ちてしまった。

 注意すべきは二点。

・(BFSの書き方にもよるけど)スタート地点が全探索時の高さより高いときバグる可能性がある。
・オーバーフロー。全探索する高さをintにしていたら、高さ*cupなどの計算でオーバーフローしていた。

 この二つに引っ掛かってたのでダメ。特に一点目は、Pythonで書いててもミスってた可能性がありますね……。



 以下、システムテストを通ったコード。

#include <bits/stdc++.h>
#include <bits/stdc++.h>
using namespace std;

class FlightPlan
{
public:
  long long fly(int R, int C, vector<int> H, int cup, int cdn, int clr)
  {
    set<long long> S;
    S.clear();
    vector<long long> DP(R * C);

    for (int i = 0; i < R * C; i += 1)
    {
      DP[i] = 9999;
      S.insert(H[i]);
    }
    DP[0] = 0;

    long long ANS = 1000000000000000000;

    for (auto s : S)
    {
      if (s < H[0])
      {
        continue;
      }
      if (s < H[R * C - 1])
      {
        continue;
      }
      deque<int> Q;
      Q.push_back(0);

      for (int i = 0; i < R * C; i += 1)
      {
        DP[i] = 9999;
      }
      DP[0] = 0;

      while (Q.size())
      {
        int x = Q.front();
        Q.pop_front();
        int r = x / C;
        int c = x % C;

        if (r + 1 < R && H[(r + 1) * C + c] <= s && DP[(r + 1) * C + c] > DP[r * C + c] + 1)
        {
          DP[(r + 1) * C + c] = DP[r * C + c] + 1;
          Q.push_back((r + 1) * C + c);
        }

        if (r - 1 >= 0 && H[(r - 1) * C + c] <= s && DP[(r - 1) * C + c] > DP[r * C + c] + 1)
        {
          DP[(r - 1) * C + c] = DP[r * C + c] + 1;
          Q.push_back((r - 1) * C + c);
        }

        if (c + 1 < C && H[r * C + c + 1] <= s && DP[r * C + c + 1] > DP[r * C + c] + 1)
        {
          DP[r * C + c + 1] = DP[r * C + c] + 1;
          Q.push_back(r * C + c + 1);
        }

        if (c - 1 >= 0 && H[r * C + c - 1] <= s && DP[r * C + c - 1] > DP[r * C + c] + 1)
        {
          DP[r * C + c - 1] = DP[r * C + c] + 1;
          Q.push_back(r * C + c - 1);
        }

        if (DP[(R - 1) * C + C - 1] < 9999)
        {
          long long ANSX = 0;
          ANSX += (s - H[0]) * cup;
          ANSX += (s - H[(R - 1) * C + C - 1]) * cdn;
          ANSX += DP[(R - 1) * C + C - 1] * clr;

          if (ANS > ANSX)
          {
            ANS = ANSX;
          }
        }
      }
    }
    return ANS;
  }
};

2020年7月3日金曜日

TopCoder Single Round Match 781

 EasyをHackされて0完でした。
 いや、これは解けなくちゃいけない問題でしたね。こういう、それほど難しくない構築は取りたい……。

Div1 Easy EpicPartition


 公式解説を読むと、色々方法がある、と書いてある(としか書いてない)。kmjpさんの解説もあるけど、色々な方法があるらしいので、別な方法を考えた。

 cの位置を決めることを考える。
 kmjpさんの解説にもある通り、cを埋めた残りが全て二個連続していれば(適当な四個を前半二個、後半二個に分けて)"abba"とすることで、aとbの和を一致させることができる。
 なので、できるだけ連続している位置にcを埋めたい。

 ここで、"c"を埋め込むときの平均値を考える。
 たとえば、N=6のとき、全体の和は300、"c"の和は150で8個なので、平均は18.75。なので、18と19を中心に、15~22を"c"にしようとすると2だけ不足する。のえ、最後の22を24に変えれば辻褄が合う。

 一般にこういう構成で良いことは立式すれば証明できる(できた)。
 1~24xの中から1/3を"c"にするとすると、"c"の和は$144x^2+6x$で、18.5x中心に構成した和は$144x^2+4x$。後者の構成の最大の数は18x+4xなので、それを1~24xの中で最大の数である24xにすれば辻褄が合うことが分かる。

 以下、システムテストを通ったコード。

class EpicPartition():
    def createPartition(self, N):

        if N%4!=0:
            return ""

        k=N*6
        ALL=k*(k+1)//2
        CK=(ALL//2)//(k//3)

        ANS=["a"]*k
        ANS[-1]="c"
        for i in range(CK-k//6,CK+k//6-1):
            ANS[i]="c"

        f=0
        for i in range(k):
            if f< k//3 and ANS[i]!="c":
                if f%2==0:
                    ANS[i]="a"
                else:
                    ANS[i]="b"
                f+=1

            elif ANS[i]!="c":
                if f%2==1:
                    ANS[i]="a"
                else:
                    ANS[i]="b"
                f+=1

        return "".join(ANS)

2020年6月6日土曜日

TopCoder Single Round Match 780

 Easyが通ってレートは微増。

Div1 Easy BeatTheStar

点数が1点もらえる試合~N点もらえる試合が一試合ずつあり、二人のうちどちらかにその得点が入る。G点の試合が勝負の分かれ目になる(つまり、その試合を勝てたら合計得点でも勝ち、負ければ合計得点でも負け)確率は? という問題。

 余事象を考えると、G点の試合以外で、片方の点数が合計何点以下ならそういう状況にならないか、が分かる。なので、片方の点数がそれ以下になる場合をDPで求め、$2^{N-1}$で割って、1から引く。
 
 Python内fastestだったようなので、自分のコードはここで見ることができます。

Div1 Medium Prominence

コンテスト中は問題内容を把握できていなかった。kmjpさんのブログ記事で問題内容を把握しました。

 内容を把握できれば解法を理解するのはそんなに難しくないですね。

 kmjpさんの解法では前半パートにsetを使っていますが、公式解説を読むと平衡二分木は必要ない模様。左右から山を見ていけばできるのですね。
 後半パートもセグメント木ではなくSparse Tableを使えばO(N)になるのかな。(Pythonでは実行時間が厳しそうだし、実装はサボります。とはいえ、一応O(N)なので、上手く書けば通ってもおかしくはないですか)

2020年5月28日木曜日

2020 Humbleool Cup Prelims

 オンサイトか何か(?)の予選で、Div.1、Div.2混合回。
 Div.2の難易度で、全完しないとまずいセットだったのに、Hardをミスして落とす。
 それでも、緑から青に戻ったけれど、これは出なくても良かったかなぁ、という気持ちになりました。

 なお、MedはPython最速だったらしく公式ページに自分の実装が載っています。

CardDrawPoints

 カードの枚数の制約が少ないことからも分かる通り、bit DPをする問題。
 どのカードを使った/使ってないか、の期待値をbit DPで計算すればOK。
 floatで割り算して誤差が大丈夫か不安だったけれど、それも問題なかった。

 コンテスト中は、残り枚数が0枚のときに、0除算して落としてしまった。ちゃんと気を付けないと!

 一応、システムテストを通ったコードを載せておきます。


class CardDrawPoints():
    def expectedScore(self, count):
        LEN=len(count)
        if max(count)==0 or len(count)==1:
            return 0

        LIST=[-1]*(1<<LEN)
        def calc(A):
            if LIST[A]!=-1:
                return LIST[A]
            
            rest=[0]*LEN
            NOW=0

            for i in range(LEN):
                rest[i]=count[i]
                if A & (1<<i)!=0 and count[i]>0:
                    NOW+=i
                    rest[i]-=1

            SUM=sum(rest)

            if SUM==0:
                return NOW

            ANS=float(0)

            for i in range(LEN):
                if A & (1<<i)!=0:
                    continue
                else:
                    ANS+=float(calc(A|(1<<i)))*rest[i]/SUM

            if NOW>ANS:
                LIST[A]=NOW
                return NOW
            else:
                LIST[A]=ANS
                return ANS

        for i in range((1<<LEN)-1,-1,-1):
            calc(i)

        return LIST[0]

2020年4月8日水曜日

TopCoder Single Round Match 779

 Easyを落として緑に落ちた。

 TopCoderのレートはあまり気にしていないつもりだったけど、緑に落ちたのは結構ショックだったので、もっと真剣にやらないと、と反省しました。
 それに、このEasyは通さなくてはいけない問題だったので、そこも反省。

Div1 Easy ArraySorting

 数列Aの要素をいくつか変更し、Aを広義単調増加にしたい。
 変更する要素の個数が最小になるようにし、辞書順最小のAを求める。

 まあ、LISを復元する問題といって良いと思う。
 LISに関する理解がちゃんとしていれば解けたはずなので、解けなかったのはLISの理解ができていなかったということでしょう。

 「辞書順最小」というのに惑わされるけど、「DP[i]で、長さiのLISの最終要素の最小値」を取る、という方法(たとえば、ここの説明が分かりやすいと思う)でLISを求める際に、復元用のリストを用意していれば、自然と辞書順最小のものが求められる。

 この問題も、yukicoder contest 237のNo.992 最長増加部分列の数え上げも、それほど捻った出題じゃないのに意外と難しく感じてしまうのは、LISのアルゴリズム自体がそもそも簡単じゃないからだと思う。
 とはいえ、基本的なアルゴリズムなのだから、ちゃんと身に着けておかないと……。

 以下、ACコード。
import bisect

class ArraySorting():
    def arraySort(self, A):
        N=len(A)
        DP=[1<<31]*N
        fr=[-1]*N # 復元用のリスト

        for i in range(N):
            a=A[i]
            pos=bisect.bisect_right(DP,a)
            DP[pos]=a
            if DP[pos-1]==1<<31:
                fr[i]=1
            else:
                fr[i]=DP[pos-1]

        ANS=0 # LISの長さ
        for i in range(N):
            if DP[i]!=1<<31:
                ANS=i
                LASTV=DP[i]

        NEXT=LASTV

        B=[]

        for i in range(N-1,-1,-1):
            a=A[i]
            if a==NEXT:
                B.append(a)
                NEXT=fr[i]
                
            else:
                B.append(NEXT)

        return B[::-1]

2020年4月3日金曜日

TopCoder Single Round Match 778

 0完の上、Hack失敗して大きくレートを落としてしまった。(そして、一回Hack失敗したのだからダメ元でもっと試してやろう、と思ったのだけど、なんかそれ以上Hackできなくなった)

 ……けど、まぁ仕方ないかな、という気持ち。

 Hack case作りに失敗したのは良くないけれど、Hackしようとした提出はシステムテストで落ちたので、「落ちそう」と思ったこと自体は間違ってなかった。

Div1 Easy KRectangleIntersection

 kmjpさんのブログに解説があり、解法は理解しました。

 が、想定解がO($n^3$log(n))ということで、多分Pythonでは通らないと思うのでACしていません。

 x座標を固定することは思いついていたのだけど、その後、何をすればいいか分からなかった。結構愚直な方法だし、想定計算量がO($n^3$log(n))だと分かっていれば思いつけたのでは? という気もする。
 ただ、Pythonだと三乗の時点で厳しいので、もっと速い解法を探してしまうのもまあ仕方ないかな……。SRMで戦うには、これくらいさっとC++で書ける力を身に着けなきゃいけないのだろうけど。

2020年2月21日金曜日

TopCoder Single Round Match 776

 今年はじめてEasyがACできた! と思ったらシステムテストで落ちて0完。単純なミスだったのでもったいない。

Div1 Easy EncloseArea

 斜めに線を引いて、指定された面積の多角形を作る問題。

 最小の正方形の面積は2で、その一辺を削って、隣に膨らませれば面積は+2される。
 なので、まず50*50の方眼紙の対角線上に斜めに長い長方形を作り、それを一つずつ膨らませる実装をした。一々、辺を増やして減らして……としているためかなり汚い実装になってしまった。

 そして、一番最初、面積2から始めてそこから求める面積まで増やしていく……というように書いてしまったため、面積2のときそのまま出力するのを忘れてしまってシステムテスト落ち。

・最小値・最大値で通るかチェック

 はきちんとしなくてはいけませんね。

 kmjpさんのブログの記事のように頂点に着目すればもっと簡単に実装できます。

 コンテスト中のコードを修正したものなので非常に読みにくいのですが、一応ACしたコードを載せておきます(載せるか迷ったのですが、ACした証拠として)。

class EncloseArea():
    def enclose(self, A):
        if A%2!=0:
            return tuple()
        if A>2402:
            return tuple()

        ANS=[["."]*50 for i in range(50)]

        ANS[0][0]="/"
        ANS[0][1]="\\"
        ANS[1][0]="\\"
        ANS[1][1]="/"
        S=2

        for i in range(1,49):
            if S==A:
                break
            
            ANS[i][i]="."
            ANS[i+1][i+1]="/"
            ANS[i][i+1]=ANS[i+1][i]="\\"
            S+=2

        if S==A:
            A=[]

            for ans in ANS:
                A.append("".join(ans))

            return tuple(A)

        NOWP=[0,1]
        NOWM=[1,0]

        def rewrite1(x,y):
            if ANS[x-1][y]==".":
                ANS[x-1][y]="/"
            else:
                ANS[x-1][y]="."

            ANS[x][y]="."
            ANS[x-1][y+1]="\\"
            ANS[x][y+1]="/"

        def rewrite2(x,y):
            if ANS[x][y-1]==".":
                ANS[x][y-1]="/"
            else:
                ANS[x][y-1]="."

            ANS[x][y]="."
            ANS[x+1][y-1]="\\"
            ANS[x+1][y]="/"
            
        while True:
            x,y=NOWP
            if 0<=x-1<50 and 0<=y+1<50:
                rewrite1(x,y)
                S+=2
            NOWP[0]+=1
            NOWP[1]+=1

            if NOWP[0]==49 or NOWP[1]==49:
                MIN=min(NOWP)
                NOWP[0]-=MIN
                NOWP[1]-=MIN
                NOWP[1]+=2

            if S==A:
                break
                

            x,y=NOWM
            if 0<=x+1<50 and 0<=y-1<50:
                rewrite2(x,y)
                S+=2
            NOWM[0]+=1
            NOWM[1]+=1

            if NOWM[0]==49 or NOWM[1]==49:
                MIN=min(NOWM)
                NOWM[0]-=MIN
                NOWM[1]-=MIN
                NOWM[0]+=2

            if S==A:
                break

        if S==A:
            A=[]

            for ans in ANS:
                A.append("".join(ans))

            return tuple(A)

Div1 Medium StringRings

 kmjpさんのブログの記事を参考にして通した。

 両端赤、両端緑、両端が異なる、のそれぞれのひもの個数をr, g, bとおき、実際に本数を求めてみよう、と式を書いてみたら、自然と再帰的に書け(多分、C++なら再帰のままでもACできる?)、それを整理したらさらに簡単な式になった。

・とりあえず立式

 さえしていればそれほど難しくなかった気がする。

 コンテスト本番では、Easyに時間を取られてしまったので解けなかったけど、ACしたい問題でしたね~。

 以下、ACしたコード。

class StringRings():
    def expectedRings(self, A, B):
        ANS=0
        for i in range(A):
            ANS+=1.0/(2*i+1)

        for i in range(B):
            ANS+=1.0/(A*2+i+1)

        return ANS

2020年2月7日金曜日

TopCoder Single Round Match 775

 Easyを見て、ちゃんと時間をかければできそうだけど詰めるのは辛そう、とMedへ行くも、Medが分からずの0完でした。Easyをやるべきだったかな、とも思いますが、結局ACするのは難しかった気がします。

Div1 Easy IterateOverACube

 kmjpさんのブログの記事かっつさんのブログの記事を参考にしましたが、少し違う方法です。

 合計がxになるタプルが何個あるか? を調べれば良い。場合分けが必要で混乱するけど、

・三次元の問題は断面を考える

 とすれば良いと思う。
 たとえば、N=3で、z軸が0, 1, 2 の場合に分けて考えると、合計が0~6のときの点の位置は、

  [1, 0, 0] (0, 0, 0)の一つ
→ [2, 1, 0] (1, 0, 0), (0, 1, 0)の二つがz=0に、(0, 0, 1)がz=1にある。
→ [3, 2, 1]
→ [2, 3, 2]
→ [1, 2, 3]
→ [0, 1, 2]
→ [0, 0, 1]

 のように変化する。
 なので、L = [1, 2, 3, 2, 1] という配列の長さ3の区間の和となっているので、尺取り法で求めることができる。

 合計が求まった後も、xを固定して同じように考えられる。こちらは、二次元平面上で考えられるので上よりは分かりやすい。

 しかし、この方法だと、色々工夫してもPython2じゃMLEやTLEになってACできなかった。
 えー辛い。私の書き方が悪いだけで、この計算量ならPythonでいけると思うんだけど……。

 Pythonで通せなかったので、C++で実装しました。以下、システムテスト通ったコードです。コメント付けてないし、C++は全く書き慣れていないので読みにくいと思いますが、ACできた証拠に。


#include <bits/stdc++.h>
using namespace std;

class IterateOverACube {
 public:
      vector <int> findCell(int N, long long index){
          index+=1;
          vector <int> L;

          for (int i=1;i=0;i-=1){
                    L.push_back(i);
          }
          for (int i=0;i<=N;i+=1){
                    L.push_back(0);
          }

          int LEN=L.size();
          int i=0;
          int j=0;
          vector<long long> S;
          S.push_back(1);
          long long SUM=1;

          while (i=index){
                       SUM=i;
                       rest=index-(NOW-S[i]);
                       break;
                    }
          }

          NOW=0;
          int x=0;

          for (int i=0;i=rest){
                        rest=rest-(NOW-L[SUM-i]);
                        x=i;
                        break;
                     }
          }

          int SYZ=SUM+1-x;
          int z=min(SYZ,N)-rest;
          int y=SUM-x-z;

          return {x,y,z};

      }
};

2020年2月2日日曜日

TopCoder Single Round Match 774

 Easyは解けたつもりだったのですが、システムテストで落ちて0完でした。

Div1 Easy LandSplitter


 kmjpさんのブログに解説があります。

 まず、元の問題文には三個の分割と二個の分割が乗っているが、結局コストは同じなので、二個の分割だけ使うと考えて良い。また、コストは分割の順番によらない。

 その上で、できるだけ大きな要素を作るのが最善。これは、A<B<C<Dのとき、A*D<B*Cなので、(B, C)という分割より(A, D)という分割の方が良いことから考えていけば分かる。

 ここまではコンテスト中に分かっていて、Bを使う回数を大きい順に試していく実装を考えていました。

 まず、

・残りxのとき, A~Bを使って分割可能か

 は簡単に計算できる。これは、Aを使える回数をt=x//A回としたとき、t*A+(Bを使うときの誤差分である(B-A)*t) 以下にxがあれば良い。

 なので、Bを大きい順に試し、分割可能だったら、それがBを使う回数。(A=Bのときを例外処理しておけば、計算量は抑えられる。Bを使う回数は最大N/B回だが、Bを最大に使ったときの余りが高々B-1で、Bを使う回数を減らすごとに最低でも1ずつ縮まるので、高々min(B/(B-A), N/B)回にはなる。なので、$O(\sqrt{N})$か。)
 
 で、コンテスト中はその後の実装がまずく(次に何を使えばいいかをB-1から大きい順に試していた)TLEになってしまっていた。

 できるだけ大きい要素を作るように分割しているので、残りの分割が「Aが何個かとA以上B未満のものが1個」になると言える。それを考えると、NからBを使った残りをM、残りのAを使う回数useaとすると、

・M-A*usea<B

 を満たす最大のものがuseaとなり、O(1)で求めることができる。

 TopCoderは他人のコードが見られなそうなので、一応コードを載せておきます。これでシステムテストは通りました。
 なお、TopCoderはPython2なのでrangeじゃなくxrangeにしています(rangeだとMLEした)。
class LandSplitter():
    def cheapest(self, N, A, B):
        if A==B:
            if N%A!=0:
                return -1
            else:
                x=N//A

                return A*A* (1+(x-1))*(x-1)//2

        def check(x,A,B): # 残りxのとき, A~Bを使って分割可能か調べる
            t=x//A
            s=B-A
            if x<=t*(A+s):
                return 1
            else:
                return 0

        for useb in xrange(N//B,-1,-1): #Bを使う回数を大きい方から探索して,
            if check(N-useb*B,A,B)==1: #分割可能なときにBを使う回数とする.
                break
        else:
            return -1 # 分割可能なことがなければ、不可能

        
        M=N-useb*B # Bを使った残り
        ANS=M*(useb*B)+B*B*((1+(useb-1))*(useb-1)//2) # ここまでのコスト

        usea=max(0,-(-(M-B)//A)) # Aの使用回数
        
        if usea==0: # Aを使わなくて良いときは既に分割できている
            return ANS
        
        S=M-usea*A # Aを使った残り
        ANS+=S*(usea*A)+A*A*((1+(usea-1))*(usea-1)//2) # 残りのコスト
        return ANS