tk555 diary

プログラミング、もしくはそれ以外のこと書きます。

[Excel VBA]Xmas Contest 2019のA-Signboard 1

atcoder.jp

クリスマスコンらしくプログラミングできなくても努力で何とかなる系の問題。



f:id:tk55513:20191225001217g:plain
エクセル方眼紙


Private Sub Worksheet_Change(ByVal Target As Range)
    If Application.Intersect(Target, Range("A1:H8")) Is Nothing Then
        Exit Sub
    Else
        Dim c As Range
        For Each c In Target
            If c.Value < 1 Or c.Value > 64 Then
                MsgBox ("ダメ")
                Exit Sub
            End If
            Dim PicPath As String, Pic As Picture, ImageCell As Range
            PicPath = "D:\pg\AtCoder\signboard_t1\pieces\p_" & c.Value & ".png"
            Set ImageCell = c.Offset(0, 9)
            Set Pic = ActiveSheet.Pictures.Insert(PicPath)
            With Pic
                .ShapeRange.LockAspectRatio = msoFalse
                .Left = ImageCell.Left
                .Top = ImageCell.Top
                .Width = ImageCell.Width
                .Height = ImageCell.Height
            End With
        Next c
        Exit Sub
    End If

End Sub

セルのRange(A1:H8)に[1,64]を入れるとRange(J1:Q8)のセルの位置に画像が追加される仕組み。

どう考えても絶対確実に今後使わないプログラムなので保存はしないけど、なんか勿体ないのでここで供養代わりに上げておく(そのためにこれを書いている。

エクスプローラで縮小画像見ながらパズルしたけど、パワポでやるべきだったなあと思う。

Excelおじさんになりつつある。

[Java]ABC134の[B,E]の反省

ABCがABCDEFになってからかなり易化したのに緑パフォ出しちゃったのでこれはマズい。

B - Golden Apple

atcoder.jp

想定解だと(N+2D)/(2D+1)を出力してO(1)。

自分は左から一人ひとり監視員を置いていくシミュレーションを行ったO(N)。

シミュレーションを行った理由は数式で提出するとコーナーケースが漏れそうな気がするからだけど、変に気合入った設問でN<=10^10とかだったとすると多分躓く。早急に改善したいところ。

C - Exception Handling

atcoder.jp

最大値AmaxとなるAが二つ以上あると、どのAを取り除いたとしても最大値はAmax。

最大値が一つの時その最大値を取り除いた時の最大値は数列A1,A2,...の中で二番目に大きい数。

よってAの中で一番目に大きいものとそのインデックス、二番目に大きいもの、の3つを一回の精査で見つけておけばいい。

自分がリアルタイムでやった時は2番目に大きいものを見つけるためにソートしてて無駄が多い。ここも精進ポイント。

D - Preparing Boxes

atcoder.jp

i番目の箱にボールの数をBiとして考える。

よく考えると箱の後半部分の(∀i>n/2)でAi=Biであることが分かる。

次に箱の前半部分を考えると、i<=n/2のAi=Sum(Bj,j=i,2i,3i...)(mod 2)=>Bj=Ai+Sum(Bj,j=2i,3i...)(mod 2)なので、Biを降順を降順に求めることで全部求めることができる。

コンテスト後にTwitter見てるとこの計算量で躊躇してる人が多かったっぽい。

N+N/2+N/3+...ってのは何か大学受験した時の数学思い出して懐かしかった。(小並感)

E - Sequence Decomposing

atcoder.jp

解けなかった。これ千人通すのくっそ怖い。

とりあえず対偶取ったとこまでは良かったと思う。

最初にぱっと思いついたのが転倒数。

次に考えたのがLISのDPを変形してAの数列を増加部分列にうまく分割する方法で、それをずっと考えてた。

対偶取ったとこから素直にAi>=Ajを考えてれば減少部分列を思いついたかもしれない。猛省だ。

[Java]Arrays.fillの罠

Arrays.fillの中身って

public static void fill(Object[] a,Object val){
    for(int i=0,len=a.length;i<len;i++){
        a[i]=val;
    }
}

https://github.com/openjdk/panama/blob/master/src/java.base/share/classes/java/util/Arrays.javagithub.com

なんですね...

参照が渡されるから実際のオブジェクトは1個。

まあそらそうよな感じですけどいつかバグらせそう...というかバグらせたからここに覚書として書くわけだけど。

Supplier貰ってfillしてくれる関数も欲しい

M-SOLUTIONS(2019)-D

問題

atcoder.jp

解けなかった...
これ水で解けないのはさすがにひどい。完全に茶色適正

解法

そもそもこの問題でまず自分が詰まったのが「木構造のデータの持ち方(データ構造)」について。

リアタイではNodeやEdgeといったクラスを作ったりMapやSetを使ったりして試行錯誤しながら2時間椅子を温めてましたが、普通に隣接リストでいいんですね...(隣接行列は空間計算量の制約的にダメってぱっと思い浮かぶのにこっちは思い浮かばない)

他人のコード見てると

List<Integer>[] g=new List[n];
//Set<Integer>[] g=new Set[n];じゃない理由はあるんだろうか...

の形式が一番多いっぽい

次点で

int[][] g=method(n,from[],to[]);//とか(2番手)
Map<Integer,Set<Integer>> tree;//とか
Map<Integer,List<Integer>> tree;//とか
List<List<Integer>> g;//とか

あと次数を

int[] deg;

で持っとくのもいいテクニックだなって思った(小学生並みの感想)(リアタイではNodeが隣接してるノードの番号を示すSetを持ってて.size()で次数を取ってて詰んだ)。


あとwhileなんかで配列やリストにアクセスするとき

int idx=0;
while(hoge()){
    ~~~c[idx++]~~~
}

と自然にやれると良いな。

本質的な解法は単純に木の葉っぱの方からスコアが小さい順に埋めていくだけ。
やり方は思いついてただけに悔しいかんじ。

敗因は上記の「木のデータ構造」とDequeの扱い。


追伸

今JavaGold取ろうとしててその勉強しているけど、言語の詳細を覚えるにつれて競プロっぽい書き方が抜けて行ってる気がする。
ライブラリとか覚えるとどうにかしてそれ使おうとしちゃうんだろうな。

Google Code Jam Qualification Round 2019のやつ

2019年4月6日8:00からのGoogle Code Jam Qualification Round に参加したのでそれの解法とか記録。

参加した人はお疲れ様です。

A Foregone Solution

問題

いくつかの桁に'4'が含まれる正の整数Nが与えられるので桁に'4'を含まない正の整数であるA,Bを使ってA+Bで表せ。

考え

largeの時が1<N<10^100なのでNを文字列で読み取る必要がある。
A,Bも似たような長さで出力する必要があるのでStringBuilderで用意する。
Nを一文字目から見ていき'4'が含まれるなら3と1に分解してA,Bにappend。含まれないならその数と'0'をappend。

最後にA,Bの前方に含まれている'0'列をトリムして出力。

フラグとして「すでに4を見た」というものを持っておくと最後のトリムは必要なくなる。

    public static void main(String[] args) {
        Scanner sc=new Scanner();
        int n=sc.nextInt();
        StringBuilder ans=new StringBuilder();
        for (int i = 0; i < n; i++) {
            String s=sc.next();
            StringBuilder sb1 = new StringBuilder();
            StringBuilder sb2 = new StringBuilder();
            for (char c : s.toCharArray()) {
                if(c!='4'){
                    sb1.append(0);
                    sb2.append(c);
                }else{
                    sb1.append(1);
                    sb2.append(3);
                }
            }
            while(sb1.charAt(0)=='0'){
                sb1.deleteCharAt(0);
            }
            ans.append(format("Case #%d: %s %s\n",i+1,sb1,sb2));
        }
        put(ans);

    }

提出するためのクラス名が分からず3CE。



B You Can Go Your Own Way

問題

N*Nの方眼用紙があって左上のマスから右か下に一マスずつ進んでいき右下のマスに到着する手順が与えられる。自分も同様に左上のマスから右下のマスへ行きたいが、同じルートは通りたくない(同じマスは通ってよい)。同じルートを通らないような手順を出力せよ。

考え

線対称に動けば被らないように動くことができるので、EとSを反転するだけでいい。

    public static void main(String[] args) {
        Scanner sc=new Scanner();
        int count=sc.nextInt();
        StringBuilder ans=new StringBuilder();
        for (int i = 0; i < count; i++) {
            int n=sc.nextInt();
            String line=sc.next();
            ans.append(format("Case #%d: ",i+1));
            for (char c : line.toCharArray()) {
                if(c=='S'){
                    ans.append('E');
                }else{
                    ans.append('S');
                }
            }
            ans.append("\n");
        }
        put(ans);
    }

C Cryptopangrams

問題

Nより小さい26個の素数がA-Zに対応している。A-Zで構成されるL文字の文字列をその素数に変換した後隣り合った素数同士を掛け合わすとL-1個の数列を作ることができる。これが与えられるので元の文字列を復元せよ。

考え

largeが通らなかった。。。というかgcdに気づかず事前にエラトステネスで素数列を用意してその中から与えられた数列で隣り合った数字を両方割り切れるものを線形探索で探してた。通るはずがない。
あとBigIntegerは普段valueOfしか使わないからコンストラクタで文字列与えることができることに気づかなかった。JavaDocはよく読もう。


D Dat Bae

問題

Nビット送るとB個の桁が消されてN-Bビット返ってくる。F(smallなら10、largeなら5)回そのクエリを出すことができるので消される桁を推定して出力。

考え

完全に証明なし理解なしでlargeまで通せた(自慢?)。
出すクエリを01010101.....,00110011.....,0000111100....みたいにすることは典型で何も考えずそうした。
動的計画法で返ってきたビットの何桁目が最小の場合元のビットの何桁目を表しているか、最大の場合元のビットの何桁目を表しているかを求めることはできるっぽい。。。すべてのN-B個の桁において最小=最大になると答えが分かるがこれだと上手くいかない場合がある(1 2 3の時とか)。
なので返ってくるN-B個の桁それぞれに対して元の桁の候補をTreeSetとして持っておき、動的計画法的に(合ってるか?)その範囲を狭めていくことで答えを求めた。

    public static void main(String[] args) {
        Scanner sc=new Scanner();
        int T=sc.nextInt();
        for (int count = 0; count < T; count++) {
            int n=sc.nextInt();
            int b=sc.nextInt();
            int f=sc.nextInt();
            TreeSet<Integer>[] lists = new TreeSet[n-b];
            for (int i = 0; i < lists.length; i++) {
                lists[i] = new TreeSet<>();
                lists[i].addAll(IntStream.range(i,i+16).boxed().collect(Collectors.toSet()));
            }
            for (int i = 0; i < f; i++) {
                StringBuilder token = new StringBuilder();
                TreeSet<Integer> zeroSet = new TreeSet<>();
                TreeSet<Integer> oneSet = new TreeSet<>();
                for (int j = 0; j < n; j++) {
                    if(j%(1<<(i+1))<(1<<i)){
                        token.append(0);
                        zeroSet.add(j);
                    }else{
                        token.append(1);
                        oneSet.add(j);
                    }
                }
                put(token);
                final String s=sc.next();
                for (int j = 0; j < lists.length; j++) {
                    TreeSet<Integer> useSet=null;
                    if(s.charAt(j)=='0'){
                        useSet=zeroSet;
                    }else{
                        useSet=oneSet;
                    }
                    int start=j==0?0:lists[j-1].first()+1;
                    int end=j==lists.length-1?n:lists[j+1].last();
                    lists[j].retainAll(useSet.subSet(start,end));
                }
                if(Arrays.stream(lists).allMatch(set->set.size()==1))break;
            }
            StringBuilder sb = new StringBuilder();
            Set<Integer> notB = new HashSet<>();
            for(Set<Integer> set:lists){
                notB.addAll(set);
            }
            for (int j = 0; j < n; j++) {
                if(!notB.contains(j))sb.append(j+" ");
            }
            put(sb);
            int _=sc.nextInt();
        }
    }

largeのポイントはL=min(15,N-1)なんですね。全然分からんかった。

まとめ

もう一度同じ問題やってDの解法が出てくるかというと微妙。
解けたのも運の要素が大きい。

なので私の実力はA,B,Cのsmallまでといったところだろう。
Round1はまあかなり厳しそう。

いや新社会人で7日連続スーツ出勤最終日にこれだけ解けたんならマシじゃないか?(クソブラック)
すごい眠かったし。

あとQualificationって相談有りなのか。。。
「Round本番からは本気出す」みたいな人出てくるんだろ(自分はこれで精いっぱいだったけれど)
怖すぎる