naoppyの日記

自分が欲しいなと思った記事を書きます。

【令和4年度】豊橋技科大編入試験 まとめ

豊橋技科大を滑り止めとして受験しました。前日~試験日までの話を色々書いていきます。 ちなみに情報・知能工学課程を選択しました。

前日

コンフォートホテル豊橋に泊まりました。駅近でコンビニが近く、勉強ができる明るい照明がついた広い机があったので良かったです。 編入で止まる際は勉強机があるかは大事だと思います。 このホテルは朝食も(しょぼいけど)ビュッフェがついていて良かったです、おすすめです。

当日朝

技科大でのお昼ご飯をコンビニで買って、バスに乗って技科大まで30分弱で行きます。臨時バスが大量に出るのでそこまで早く行かなくても全く問題ないと思います。運賃は片道450円で、往復切符を乗る前に買っておく形式でした。

スーツやワイシャツの人もかなりいました。Tシャツで受験する人はどちらかというと少数派ですが、全く問題ないので遠方の人はそちらをおすすめします。 技科大は受験生が多いからか、受験票をチェックすることさえされませんでした笑

試験

日程は以下の通り英語、国語、応用数学、専門の順でした。

f:id:naoppy_ac:20210629190206p:plain
技科大テスト日程

英語と専門の配点が大きく、国語が試験科目にあるのが特徴です。

英語

60分経ったところで10分弱のリスニングがあります。最初の長文問題以外はTOEIC Part5みたいな感じで一瞬でできるので、長文に1時間ぐらいかけれると思います。

リスニングなのですが、音質が最悪で聴きとるのが大変でした。殆どの受験生は取れてないのでわからなくても安心していいと思います。 また、問題と質問が2回ずつ読まれるのですが、繰り返すときに何も言わず、間髪入れずに繰り返すのでどこでループが始まったか本当にわかりずらいです!気を付けてください。

長文の内容は易化してました。

国語

勉強するだけ無駄です、時間が足りねぇ

いつも通りでした。

応数

行列、微積(偏微分と重積分)、確率がでました。 確率に関しては高校初期レベルですが、円順列などの考え方は押さえておきましょう。重積分極座標変換ができればいいです。

いつも通りでした。

専門(情報)

大問1はコンデンサの放電の過渡現象を、コンデンサではなく砂糖に問題を変えてでてきました。 微分方程式を解くのは簡単でしたが、その後のlogの計算でlogの性質を使った変形を色々する必要があって難しかったです。

大問2は最大公約数を求めるプログラムについてでした。証明の穴埋めなどかなり難しかったのですが、誘導が丁寧なのでなんとかなりました。 何回実行されるかを聞く定番の問題がでましたが、頭が混乱して失敗しました。

大問3は論理回路がでました。双対関数とかいう知らない概念がでて無理でした。また、どうせ加算回路がでるだろ~と思っていたら減算の繰り下がり桁を計算する回路がでました。 しかも何をする回路かは書かれておらず、真理値表のみが与えられ、そこから減算の繰り下がり桁を計算する回路だと答えさせるという鬼畜ぶり… 解けた人は少なかったと思います。 おなじみのカルノー図による簡約化もでました。

クソ難化してるように感じました。

結果

合格

感想など

今年は専門をどれだけ取れたかが合格に響いてきそうだなと思いました。

KOSEN セキュリティコンテスト2019 write-up

明石高専Bチームとして出て、結果は6位でした。 CTFは大会に二回だけ参加したことがあるだけで、セキュコンへの参加も初めてでしたが、ググったらなんとかなりました。 完全に競プロ系の問題を解く枠として参加するつもりだったんですけど、競プロの問題一切でなくて泣きました。

f:id:naoppy_ac:20191026183202p:plain

問題一覧

Bianry f:id:naoppy_ac:20191026183940p:plain Crypto f:id:naoppy_ac:20191026184013p:plain Forensics f:id:naoppy_ac:20191026184042p:plain Misc f:id:naoppy_ac:20191026184124p:plain Network f:id:naoppy_ac:20191026184253p:plain Web f:id:naoppy_ac:20191026184326p:plain

以下、解いたり考察して解いてもらった問題について解法を書いていきます。

Binary 20回もやれば、暗号と認めてやっても良いだろう

BASE64decodeを20回してやるとFLAGがでてくる。 CyberChefというサイトがオススメ。

Binary 復号せよ

暗号器のexeと出力結果の文字列が与えられる。

とありあえずJetBrains社のツールである「dotPeek」をインストールしてexeを読み込む。

www.jetbrains.com

すると、入力に対してAES暗号化して、BASE64で出力しているとわかる。(JetBrains様は素晴らしいです) なので、出力をBASE64デコードして、AES復号したら入力がわかる。

f:id:naoppy_ac:20191028124914p:plain

AES暗号化に使用しているパラメータが特に隠されたり他のファイルに書いてあったりもなく、1ファイルに書いてあるので、そのまま使えばよい。

f:id:naoppy_ac:20191026190853p:plain

FLAGはCTFKIT{Enjoy_KOSEN_SECCON_2019}

Forensics お茶をさぐれ

apkファイルが与えられる。apkファイルは調べたらただのzipらしいので解凍する。なんか色々でてくる。classes.dexというファイルが実際のプログラムの塊らしいので、これをdex2jarというツールを使ってjarファイルに変換する。jarファイルはこれもzipらしいので解凍する。色々でてくるが、com.androidcom.androidxみたいなフォルダはどうせライブラリなので無視する。プログラム本体(classファイル)をJetBrains社製のIDEIntellij IDEAを用いて開くと、javaファイルにデコンパイルされる。

フラグを出力する部分があるが、めんどくさそうな計算をして文字列を組み立てているのがわかる。 なので出力と計算の部分だけ切り取って実際に実行してみると、フラグがでる。

実際に実行したコード

public class Main {
    public static void main(String[] args) {
        Main a = new Main();
        a.doIt();
    }

    public void doIt() {
        StringBuilder var4 = new StringBuilder();
        var4.append(this.f());
        var4.append(this.l());
        var4.append(this.o());
        var4.append(this.g());
        var4.append(this.g2());
        var4.append(this.e());
        var4.append(this.h());
        var4.append(this.i());
        var4.append(this.j());
        var4.append(this.k());
        var4.append(this.l2());
        var4.append(this.n());
        var4.append(this.o2());
        String var8 = var4.toString();
        StringBuilder var9 = new StringBuilder();
        var9.append("CTFKIT{");
        var9.append(this.nyaho(var8));
        var9.append("}");
        System.out.println(var9.toString());
    }

    public String nyaho(String var1) {
        char[] var5 = new char[var1.length()];

        for (int var4 = 0; var4 < var1.length(); ++var4) {
            char var3 = var1.charAt(var4);
            char var2;
            if (var3 >= 'a' && var3 <= 'm') {
                var2 = (char) (var3 + 13);
            } else if (var3 >= 'A' && var3 <= 'M') {
                var2 = (char) (var3 + 13);
            } else if (var3 >= 'n' && var3 <= 'z') {
                var2 = (char) (var3 - 13);
            } else {
                var2 = var3;
                if (var3 >= 'N') {
                    var2 = var3;
                    if (var3 <= 'Z') {
                        var2 = (char) (var3 - 13);
                    }
                }
            }

            var5[var4] = var2;
        }

        return String.valueOf(var5);
    }

    public String e() {
        return "v";
    }

    public String f() {
        return "h";
    }

    public String g() {
        return "f";
    }

    public String g2() {
        return "u";
    }

    public String h() {
        return "a";
    }

    public String i() {
        return "b";
    }

    public String j() {
        return "_";
    }

    public String k() {
        return "g";
    }

    public String l() {
        return "e";
    }

    public String l2() {
        return "r";
    }

    public String n() {
        return "n";
    }

    public String o() {
        return "r";
    }

    public String o2() {
        return "_";
    }
}

CTFKIT{ureshino_tea_}

Forensics 最後に消したファイル

ファイルは.7zで与えられるのでとりあえず解凍する。 でてきたやつのfileコマンドの出力が、DOS/MBR boot sector...みたいな感じになる。 binwalkをすると、RSAのprivate-keyが存在していることがわかる。 hexdumpして無理やり取得。

そのあと、ディスクイメージはzipらしいと知り、さらに解凍するとflag/mullin.encryptedが見つかる。 これをopensslコマンドを叩き、さっきのprivate keyを使用して復号した。

cat ./flag/mullin.encrypted | openssl rsautl -decrypt -inkey private-key.pem

CTFKIT{Pandemonium_Mont_Blanc}

Misc 拡張されたIPv6

ググったら、RFC8135だとわかる。 FLAGはCTFKIT{RFC8135} f:id:naoppy_ac:20191026184753p:plain

Misc ツートントン

モールス信号になってる白黒の画像ファイルが与えられる。 左から右、上から下に見ていき、連続した黒2つがトン、5つでツー、白は3つ以上空いたら文字の区切りという法則が見たらわかる。

f:id:naoppy_ac:20191028124454p:plain

チームのメンバーに、pythonで適当に書いてもらって., -, の3文字にしてから、適当なサイトでモールス複合をしたらでてきた。

復号に使ったサイト https://morse.ariafloat.com/en/

Network 名前を解決したい!

https://futuregadget-9.tech/ にアクセスできない、設定を間違えたのか..?みたいな問題文。 問題のタイトルからDNSやろってなる。 とりあえずnslookup, dig, whoisを叩いていく。

f:id:naoppy_ac:20191026205224p:plain

nslookupででました。http://153.126.212.45/にアクセスすると、CTFKIT{naki_nureshi_megami_no_kikan}って書いてある。

Web JWT

Cookieを見ると、なにやら怪しいやつがある。それが認証に使われていることがわかる。

Cookieの値 eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJ1c2VyIjoiZ3Vlc3QiLCJpYXQiOjE1NzIwNTE2MDN9.D60_GbWe_xLD3QEMs9_IbvTcVTgFz4Uv6kGIbcLqWo2CNmDk8_lwFGqb74_J4T6bXMqUvuHJyflBYrAbwpmp7h_zRwyDV-VQCg0X5p3AlStjSeSkaq6vsitkJ18_Zbgw02iHerCP68tNJtArguqwAFEeAa0dX1-Y01nJ0ZvCnA9rS3Yj77U61Kpde4nIpvSImg_3sx5c8QUQ4JKVXBS3e_UNd7NodZQ7XBkUbk8p6DreqpRNda9TJNtlDEOr-E-6QcoLJWZCxn2VUqWDQjGlSpWCjdvsihtlbytR1fyd9KXOO1uezQWBFWCI54E1dPeC-gkxxMAxRHNn8r30nAEXdw

Base64デコードしたら、色々でてくるのでググると、Json Web Tokenという技術らしいので調べる。

f:id:naoppy_ac:20191026212154p:plain

header, payload, signatureの3つになってるっぽい。

とりあえず、渡しているjsonデータ(payload)の

{
    "user": "guest",
    "iat": 1572051603
}

{
    "user": "admin",
    "iat": 1572051603
}

にして送信したら良さそうかなー?となる。

じゃあ改ざん検知を欺瞞するためにどうするかというと、脆弱性があるらしい。

このサイトが非常に有益だった。

https://www.nccgroup.trust/uk/about-us/newsroom-and-events/blogs/2019/january/jwt-attack-walk-through/

header部分で検証に使用するアルゴリズム{"typ":"JWT","alg":"RS256"}から、{"typ":"JWT","alg":"HS256"}に変えて、最後のsignatureを頑張ってつくればいいとわかる。

脆弱性があるやつは、signatureの暗号化に使う鍵を、サーバーの証明書の鍵と同じにしているらしい。公開鍵なのでとりあえず手に入れて、それをwikiに従ってBase64エンコードなり色々して、Cookieを組み上げていく。 最後にCookie書き換えてリロードしたらでてくる。

CTFKIT{old_jwt_library_is_unsecure}

後でせやなってなったが、JWTを実装してるライブラリを使ったらもっと簡単に書けた(なんで自分で全部実装したんだろう...)。

おわりに

読んでいただきありがとうございました!来年もでたいな

Kotlin Heroes 2 writeup

Dashboard - Kotlin Heroes: Episode 2 - Codeforces

これに参加した。日本時間では23:35分スタートという過酷なコンテストだったが、最後まで粘った。 結果は画像の通り3完で133位だった。D, E共に考察を最後まで詰め切れず、とても悔しい。

f:id:naoppy_ac:20190908175644p:plain
順位

ちなみに、このコンテストは上位3名に賞金が、上位50人にTシャツとバッジがもらえるというもので、私も上位50位に入ろうと必死だったができなかった。

f:id:naoppy_ac:20190908180209p:plain

画像の通り、ボーダーは1h50m以内に5完という、比較的簡単なものだった。

ボーダーであるA-Eまでの問題についてコードと共に考察や解法を書いていこうと思う。というのも、公式の解法が見つからなかったからだ。(知っている方はコメントなどで教えてください)

なお、Kotlinバージョンは1.3を想定している。

A Three Problems

Problem - A - Codeforces

問題概要

問題の数nと、n個続く問題の難易度が与えられる。 うまく3つ問題を選ぶことで、1問目の難易度<2問目の難易度<3問目の難易度 とできるなら選んだ問題の1-indexedのインデックスを出力せよ。 複数ある場合はどれでもよい。 不可能なら"-1 -1 -1"を出力せよ。

サンプル

9
10 10 11 10 10 10 10 10 1

たとえば"9 1 3"が答えになる。意味は、1問目に9番目の問題、2問目に1番目の問題、3問目に3番目の問題を選ぶと、難易度が1<10<11となる。

解法

問題の難易度を、重複を取り除いてソートしてリストに入れる。 そのリストのサイズが3未満、つまり難易度の種類が3種類未満なら不可能。 リストのサイズが3以上なら前から3つの"インデックスを"を出力。

fun main() {
    val n = readLine()!!.toInt()
    val distinctList = readLine()!!.split(" ")
        .map { it.toInt() }
        .mapIndexed { index, complexity ->
            Problem(complexity, index + 1)
        }
        .distinctBy { it.complexity }
        .sortedBy { it.complexity }
    if (distinctList.size < 3) {
        println("-1 -1 -1")
    } else {
        println(distinctList.take(3).joinToString(" ") { it.index.toString() })
    }
}

data class Problem(val complexity: Int, val index: Int)

B Traveling Around the Golden Ring of Berland

Problem - B - Codeforces

問題概要

町の数nと、それぞれの町に何回以上行きたいかが与えられる。 町は1-nまで番号が割り振られており、町iからは町i+1にしかいけない。町nからは町1にいける。 町1の手前からスタートして、町に何回以上行きたいという要望を全て満たすとき最低いくつ町を訪れることになるか出力せよ。

サンプル

5
0 3 1 3 2

1-nを2周して、3周目で町1から町4まで訪れていけばいいので5+5+4で14つ訪れることになる。

3
1 0 0

町1の手前からスタートなので、答えは1である。

解法

ある町iをk回訪れるには、町1-nを(k-1)回訪れる必要がある。 そして最後の一回の訪問に町1からiまでiつの町を訪れるので、n * (k-1) + i訪れる。 答えは一番訪れる回数が多い町の中で、一番町1から離れているものを計算するだけ。

fun main() {
    val n = readLine()!!.toLong()
    val a = readLine()!!.split(" ").map { it.toInt() }
    val maxVisit = a.max()!!
    val maxIndex = a.lastIndexOf(maxVisit)
    println(n * (maxVisit - 1) + maxIndex + 1)
}

C Ice Cream

Problem - C - Codeforces

問題概要

夏の日がn日あります。i日目のアイス1個の価格はc_iです。 n日間を通して、トータルでちょうどk個アイスクリームを食べたいです。 i日目には最低a_i個、最高b_i個のアイスを食べます。 各日、何個食べれば一番安く、丁度k個食べれるか出力せよ。 不可能な場合、-1を出力せよ。

サンプル

3 7
3 5 6
0 3 4
3 3 3

3日あって、7個食べたい。 1日目には3つ、2日目には1つ、3日目には3つ食べれば6*3+4*1+3*3=31の価格で食べられる。 各日に最低限食べなければいけない量があることに注意!

解法

各日最低購入数の和がkを超えていたら不可能。 各日最高購入数の和がkを下回っていたら不可能。 それ以外の場合、丁度k個買うことは可能。 最低購入数よりも、(k - 各日最低購入数の和)個のアイスn日を通して買う必要がある。 あと追加で買える個数(最高購入数-最低購入数)と値段のペアを使って、貪欲に安いものから買っていけばOK。

import java.lang.Math.min

fun main(args: Array<String>) {
    val (n, k) = readLine()!!.split(" ").map { it.toLong() }
    val data = Array(n.toInt()) {
        readLine()!!.split(" ").map { it.toLong() }
    }.sortedBy { it[2] }

    var cost = data.map { it[0] * it[2] }.sum()
    var ice = data.map { it[0] }.sum()

    if (ice < k) {
        data.forEach {
            val eat = min(it[1] - it[0], k - ice)
            cost += eat * it[2]
            ice += eat
        }
    }

    println(if (ice == k) cost else -1)
}

D Teams

Problem - D - Codeforces

問題概要

n個の整数がある。 ある数をa個と、そのk倍の数をb個のペアをできるだけ作れ。 作れるペアの最大数を出力せよ。

サンプル

12 1 2 2
1 1 2 2 2 2 2 3 3 4 6 6

(3, 6, 6), (1, 2, 2), (1, 2, 2)の3つのペアが最大。

解法

ある数は、それをa個の方のグループとして使われるときと、b個の方のグループとして使われるときとの2パターンがある。 ここで、a < bのときはaとして使うのをひとつ我慢して、bとして使ってもペアを作れる個数が前より真に増えない。 a > bのときbとして使うのをひとつ我慢しても、aとして使ってもペアは真に我慢する前より増えない。 よって小さいほうを使うべきで、それによって大きい方から見るのか小さい方から見るのか変わってくる。

説明がよくわからないと思うが、コードを見てわかってほしい。

fun main() {
    val MAX_NUM = 1000000
    val (n, a, b, k) = readLine()!!.split(" ").map { it.toInt() }
    val r = readLine()!!.split(" ").map { it.toInt() }
    val map = Array(MAX_NUM + 10) { 0 }
    for (x in r) map[x]++
    var ans = 0
    if (a <= b) {
        for (i in MAX_NUM downTo 1) {
            if(i % k != 0) continue
            val q = Math.min(map[i / k] / a, map[i] / b)
            ans += q
            map[i / k] -= q * a
            map[i] -= q * b
        }
    } else {
        for (i in 1..MAX_NUM) {
            if(i * k > MAX_NUM) break
            val q = Math.min(map[i] / a, map[i * k] / b)
            ans += q
            map[i] -= q * a
            map[i * k] -= q * b
        }
    }
    println(ans)
}

E Double Permutation Inc.

Problem - E - Codeforces

問題概要

n個の数を含む数列を、RGBの3パターンのどれかに分類する。 この数列は数の順番や個数に対して全く制約がない。つまり、すべて同じ数などの場合もある。

  • R: Rに分類された数を数列の左から右に見ていくと、1から適当な数kまでがちょうど1つづつ出現する。
  • G: Gに分類された数を数列の左から右に見ていくと、1から適当な数k(Rのkと同じ)までがちょうど一つずつ出現する。ただし、Rと同じ相対順序を保たなければならない。
  • B: Bに分類された数と同じ値の数はR, Gには出現しない。

nつの数列を、できるだけ多く赤と緑に分類した時の、数列をRGBで表して出力せよ。

サンプル

5
1 2 3 2 1

答えは"RBBBG"。2は1との相対関係をRとGで同じにすることができないのでR, Gに選べない。 "GBBBR"も正解。

3
1 1 1

"BBB"が正解。どれか2つをRとGに分類すると、「Bに分類された数と同じ値の数はR, Gには出現しない」という制約を守れない。

10
3 9 3 1 2 1 2 4 4 4

"RBGRRGGBBB"が正解。

解法

まず、数をRまたはGに分類できる条件として、同じ値の数がちょうど2つである必要がある。 RやGに分類するのは1~kまでがそれぞれちょうど1種類しかでないようにしなければならない。 2つより多いと1つ以上をBに分類しなければいけないが、Bに分類する条件より、RやGに分類できないからである。 同じ値の2つの数字をRとGに分類するには左の数字をRに、右の数字をGにするなど左右とRGを一貫させればよい。 あとは、どの数kまでならRやGに分類しても大丈夫かを探す。 これを1から一つずつ試していると間に合わないので、二分探索で探す。

fun main() {
    readLine()
    val a = readLine()!!.split(" ").map { it.toInt() }
 
    /**
     * @param k 1からどの値までRとGに分類するか
     */
    fun solve(k: Int): Pair<Boolean, String> {
        val count = IntArray(k)
        val ans = CharArray(a.size) { 'B' }
        val rPermutation = mutableListOf<Int>()
        val gPermutation = mutableListOf<Int>()
        for ((index, v) in a.withIndex()) {
            if (v > k) continue
            if (count[v - 1] == 0) {
                ans[index] = 'R'
                rPermutation.add(v)
            } else if (count[v - 1] == 1) {
                ans[index] = 'G'
                gPermutation.add(v)
            }
            count[v - 1]++
        }
        return if (count.all { it == 2 } && rPermutation == gPermutation) true to String(ans)
        else false to ""
    }
 
    var low = 0
    var high = a.size / 2 + 1
    while (high - low > 1) {
        val mid = (low + high) / 2
        if (solve(mid).first) low = mid else high = mid
    }
    println(solve(low).second)
}

最後に

長い記事を最後まで読んでくださりありがとうございました! もしKotlin Heroes 3が開催されたら、Tシャツが欲しいですね。

気づいたことなどあればTwitter(@naoppyj)まで連絡をください。

DensanCTFに参加しました

チームMaru_Gameで参加して、全完3位でした。 僕はそんなに解いていません。 チームメンバーはtomohiroとtakoです。

Russia:いらすとやさん

2枚の画像が与えられる。どちらも同じように見える画像。 なにかが仕込まれているとのヒントがある。

2枚の内、サイズが大きいほうにFLAGがあると当たりをつける。 同じように見える画像なのでDiffを取りたい気持ちになったが、バイナリのDiffは可視化が難しく、できなかった。 諦めてバイナリエディタで見ると、PNGの終了コードIENDの後にもなにかある。 そこを切り出すと、zipであることがわかるので解ける。

FLAG{f1l31nclud3pn5}

Australia:Word File

wordファイルと言われてファイルが与えられる。

fileコマンドで種類を判定したいが、不明。 ヒントでエンディアンとあるので、エンディアンを逆にするツールを適当に落として適応。 再びfileコマンドを使うと、word2007+とか表示されるので、wordで開いておわり。

ちなみに僕はPCにwordを入れていないのと、OpenOfficeでは開けなかったため、チームメンバーに投げて開いてもらった。

FLAG{B1n4ry_1s_fun}

US:WIP Page

ファイル表示用のページと、暗号化したflagを表示するページがある。

message hereのURLが/message.php?p=linskslsisfjfie.txtという形式になっている。 p=で指定したファイルを表示するとわかる。 message.phpにp=flag.phpを渡すことで、flag.phpファイル自体を覗いて処理を見る。 wer8unvlgkxu84.txtをhashしたものを表示しているようだ。 よってp=wer8unvlgkxu84.txtでもう一度アクセス。

FLAG{dis_is_a_vu1ner4bi1ity}

感想

初心者向けだという印象。そこまで苦戦するようなことはなかった。 RevはCutterというツールを使えば簡単に解けたらしい。

応用情報に合格しました

平成30年秋季の応用情報技術者試験に合格しました。 今更ですが、これから応用を受験する人に向けて応用の受験の流れを書いてます。

応用受験申し込み技術者試験

とりあえず応募しましょう。応募しなければ受験できません。

IPAの試験のTwitterアカウントなどの通知をonにして、申し込み期間中に忘れず申し込みましょう。

対策本を買う

まずは午前対策の本を買いましょう。基本情報に毛が生えた感じで、広く浅くという内容。

個人的オススメは、安心と信頼のキタミ式 Amazon CAPTCHA

注意:このリンクの本は30/31年用の本です。新しく買うなら最新のものを買いましょう。

人に借りる場合は、5年ぐらい古くてもなんとかなると思います。

まずはこれを数周しましょう

受験票が届く

試験の1,2週間前に受験票が届きます。

ここから焦り始めます。あるいは、「あっ申し込みしてたんだった」と気づきます。 正直そこから勉強を始めても受かる人は受かります。

そろそろ午後対策もする時期です。できれば午後の問題集を買ってやるのが理想です。 できない場合も、過去問サイトから直近の午後問題をやった方が絶対にいいです。

  • 午後試験の問題の雰囲気
  • 筆記がどれくらい/どんな感じで出題されているか
  • どのジャンルが自分は得意か

を確かめましょう。

応用起床試験、応用写真貼り付け試験

試験日にちゃんと起きて、試験日までに受験票に顔写真を貼り付ける。

これの合格率が6割ほどです(※要出典)。 これに合格すればもう受かったようなものです。

応用情報技術者試験

寝ない。人を呼ばずに勝手に退室しない(戒め)。

応用受験票保管試験

発表は正午です。午前午後どちらも6割取れていれば合格です。

受験票にある番号で結果の照会をします。 これができないと受かったかどうかわからないまま、合格証が届くのを待つことになり、精神衛生に非常に良くありません。

自分の場合

ここまで書きましたが、僕がやったことじゃないです。(は?

午前はキタミ式を買ったはいいものの、半分も読まず放置。 受験票が届いて焦る。キタミ式を3周する。この時点で試験前日。 過去問サイトで午前の過去問を40問解く。午後問題なにそれ?で受験会場へ。 午前が終わった後の休憩時間で午後の過去問を雰囲気だけ見る。 合格。

f:id:naoppy_ac:20181229164545p:plain
結果

当たり前ですが午後がギリギリですね。午後は選択なので、とりあえず全てに目を通して、解けそうな問題から解いていきましょう。

まとめ

すいません。だいぶネタ記事です。

気負わずにいきましょう。

GitChallenge10に参加しました

mixiの主催するGitChallengeというイベントに参加してきました。

GitChallengeとは、Gitに関係した様々な課題がリポジトリ単位で与えられるので、そのリポジトリの課題を解決する技術を競うというイベントです。 難しい課題は点数が高くなっており、2人ペアで点数を競います。まさに競技という感じでした。

当日までを振り返ってみます。

応募

息を吸うようにTwitterをしていると、交通費支給のイベントがあるぞ!ということでTLが盛り上がっていました。 mixiのマイページから早速応募。

応募するときに聞かれたことは、Gitを使った経験やGitをどれくらい使えるか、などなどです。 僕は「高専プロコンでGitを使ったけど一人だけで、複数人開発はしたことないのでやってみたい」みたいなことを書きました。今思えばイベントの趣旨に沿ってたのかもしれないですね。 その時の技術レベルは「git add, commit, push, branchがわかるよ、SourceTree上でポチポチしながらgit flowを使ってるよ」ぐらいのよわよわでした。

当落発表

パソコン甲子園の前日にmixiマイページに連絡がきました。倍率とかはわからないんですけど、すごい喜びました。

行けることがわかってから、gitの勉強を始めました。何をやったかというと、progit.pdfの日本語版を読みました。

github.com

ここからpdfをダウンロードしましょう。

不運にも、GitChallengeの前がテスト期間だったせいで、progitは読み終わりませんでした。

当日

会場まで

5時起床、新幹線で渋谷まで。当日は渋谷のmixiオフィスに11時集合だったので時間が余る。

渋谷の啓文堂書店で時間をつぶすことに、ここは技術書もまあまああり、駅から近いのでオススメです。 www.keibundo.co.jp

Kotlin イン・アクションを衝動買いしました。面白い本です。 Kotlinイン・アクション | マイナビブックス

会場

会場に入るとすぐにチーム番号(あらかじめ決められている)を言われたので、その席に行くと、もう誰かいる...。 おかしい...おかしい....。

どうやらチーム番号のエルとアイを間違えて伝えてしまったようです。いきなり焦った。

無事、自分の席へ。なんと相方は香川高専のMackysonさん。限界オタク同士で助かりました。

競技まで

11時からモンスターストライクの開発にgitがどう使われているか~というような内容のLTがありました。

昼食は用意して頂きました。ありがとうございます。 おいしいカレーでした(語彙力)。

競技

競技内容は次に参加する人のために言ってはいけないんですが、ざっくりいうとCONFLICT HELLです。

.gitの中身とかまで覚えて来る必要はありません。それは高難易度問題だけです。それぐらいの難易度です。

あと、僕はSourceTree(gitのGUIクライアント)を使えば見やすいからいけるやろ!と思っていたのですが、役に立ちませんでした。 というのも、GUIは起動や再描画に時間がかかり編集結果がすぐに反映されなかったり、おかしいデータをはじめから表示しなかったりと、使い物になりませんでした。 MackysonさんからtigコマンドというCUIクライアントを教えていただきました。使うならこれです。

競技終了

真ん中ぐらいの順位でした。相方のMackysonさんのおかげです...。

その後は問題解説を数問したのち、LTを2つ挟んで懇親会でした。懇親会は終電の関係上最後までいられませんでした。8時ぐらいまでしてたみたいです。

余談

競技開始前に限界オタク数人でやる気スイッチをポチポチしました。タノジイ!!

香川高専のお二人から名刺を頂きました。実は高度に偽装された香川の特産品紹介カードでした。流石タカマツ族。

参加者層の年齢は高2~学部4年ぐらいでした。多分僕が最年少でした。

まとめ

GitChallenge楽しかったです!!!!!

普段経験できないようなことを経験でき、自分のgit力に何が足りないのか把握できました。 弊学からも次からどんどん行ってほしいですね。強くなくても向上心さえあればすごい刺激になる場所です。

こんな素敵なイベントに呼んでくださったmixiに圧倒的感謝...

ABC115-D解説

お詫び

ここに書いてあった記事は naoppy-jyoken.hatenablog.com移転しました

この記事ではD問題の思考経緯などを書いていきます。

D - Christmas

問題

D - Christmas

思考経緯

法則性を見つける問題かなと思い、まずは問題文通りパティを P、バンを B で表すを実装していきます。

public class Dstr {
    public static void main(String[] args) {
        for (int i = 0; i < 5; i++)
            System.out.println(makeBG(i));
    }

    private static String makeBG(int level) {
        if (level == 0) return "P";

        return new StringBuilder("B")
                .append(makeBG(level - 1))
                .append("P")
                .append(makeBG(level - 1))
                .append("B")
                .toString();
    }
}

まずはレベル0~4までのバーガーを文字列化して出力してみました。

出力結果
P
BPPPB
BBPPPBPBPPPBB
BBBPPPBPBPPPBBPBBPPPBPBPPPBBB
BBBBPPPBPBPPPBBPBBPPPBPBPPPBBBPBBBPPPBPBPPPBBPBBPPPBPBPPPBBBB

左右は対称だということが問題文からも、結果からもわかりました。 バーガーを下から食べていく。という問題文は上から、と読み替えてもいいですし、バーガーを横にして左から(あるいは右から)、と考えてもいいことがわかりました。 問題からノイズを取り除いて、本質だけを見ましょう

ここからわかることは、nが50の時、バーガーは非常に長くなるであろう、ということです。 愚直にすることができないことがわかりました。 じゃあ、どれくらい長いの? というわけで、次はそれを求めていきます。

バーガーの長さ
public class Datsusa {
    public static long[] AtsusaDP = new long[51];

    static {
        AtsusaDP[0] = 1L;
        for (int i = 0; i < 50; i++) {
            AtsusaDP[i + 1] = 3L + 2 * AtsusaDP[i];
        }
    }

    public static void main(String[] args) {
        for (int i = 0; i < 51; i++)
            System.out.println(AtsusaDP[i]);
    }
}

厚さの漸化式はlevel_0で1、level_iで3+2*level_(i-1)です。これは問題文から自明だと思います。 よってレベルが低い方から求めていくと、順番に求まります。

では、出力結果を見てみましょう。

1
5
13
29
~~省略~~
2251799813685245
4503599627370493

レベル50のバーガーの厚さは4503599627370493ということがわかりました。一枚ずつシュミレーションしながら食べていっても、間に合いそうにはないですね。

この辺りから、バーガーを一気に食べられると楽だなという気持ちになってきます。 問題文は何枚食べるかが与えられます。もし、あるレベルのバーガーを一気に食べても、まだ食べられる余力がある(あるレベルのバーガーを食べた後のあと何枚食べるか >= 0)なら一気に食べたいです。そして、無理ならばバーガーを細かく分解して食べていきます。ただ、その時いくつのパティを求めたかがわからないと問題が解けません。

なので次はレベルごとのバーガーに含まれるパティの数を求めていきます。

public class Dpcount {
    public static long[] P_CountDP = new long[51];

    static {
        P_CountDP[0] = 1;
        for (int i = 0; i < 50; i++) {
            P_CountDP[i + 1] = 1L + 2 * P_CountDP[i];
        }
    }

    public static void main(String[] args) {
        for (int i = 0; i < 51; i++)
            System.out.println(P_CountDP[i]);
    }
}

パティの個数の漸化式はlevel_0で1個、level_iのとき1+2*level_(i-1)です。これも問題文から自明ですね。

出力結果は省略します。

これらあるレベルのバーガーの厚さや含まれるパティの数は一瞬(たった50回のループ)に求まりました。

では、一気に食べるOR分解する動作を実装してみましょう。

実装

再帰関数を使いながら食べていきます。 再帰関数には今食べている部分的なバーガーのレベル、そして残りの食べるべき数を渡します。 今までに食べたパティの総数は渡さなくていいの?と思うでしょうが、それはグローバル変数ansに記録していきます。 関数名はeatBGとします。

バーガーを一気に食べられる場合

/**
 * @param level ハンバーガーのレベル
 * @param rest  残りの食べるべき数
 * @return 新しい残りの食べるべき数
 */
private static long eatBG(int level, long rest) {
    if (rest >= AtsusaDP[level]) {
        rest -= AtsusaDP[level];
        ans += P_CountDP[level];
        return rest;
    }
}

できました。次はelse節を書いていきます。 else節には何を書けばいいでしょうか? rest >= AtsusaDP[level]がfalseな場合、つまり今の部分的なバーガーのサイズではその途中までしか食べられない場合です。 その場合、バーガーを分解していきましょう。 食べている途中でrestが0になるでしょう。そのとき、もう食べる必要がないのに食べて、今までに食べたパティの総数を増やしてしまわないように気を付けましょう。

} else {
    rest--;// B part
    if (rest > 0) rest = eatBG(level - 1, rest);// n-1 BG part
    if (rest > 0) {// P part
        rest--;
        ans++;
    }
    if (rest > 0) rest = eatBG(level - 1, rest);// n-1 BG part
    rest--;// B part
    return rest;
}

できました。しかしまだこれで終わりではありません。動かせばわかるのですが、level-0のところまで分解されると、level-(-1)に分解しようとしてしまいます。 よってlevel=0のときの処理を関数の先頭に書いてあげます。

if (level == 0) {
    if (rest > 0) {//P part
        ans++;
        return rest-1;
    } else {
        return rest;
    }
}

レベル0まで分解されたら、Pしかないバーガーなので食べれたら食べて終わりです。

では、コードをまとめて見てみましょう。

package ABC115;

import java.util.Scanner;

public class A {
    public static long[] AtsusaDP = new long[51];

    static {
        AtsusaDP[0] = 1L;
        for (int i = 0; i < 50; i++) {
            AtsusaDP[i + 1] = 3L + 2 * AtsusaDP[i];
        }
    }

    public static long[] P_CountDP = new long[51];

    static {
        P_CountDP[0] = 1;
        for (int i = 0; i < 50; i++) {
            P_CountDP[i + 1] = 1L + 2 * P_CountDP[i];
        }
    }


    public static long ans = 0L;

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        long x = sc.nextLong();

        eatBG(n, x);
        System.out.println(ans);
    }

    /**
     * @param level ハンバーガーのレベル
     * @param rest  残りの食べるべき数
     * @return 新しい残りの食べるべき数
     */
    private static long eatBG(int level, long rest) {
        if (level == 0) {
            if (rest > 0) {//P part
                ans++;
                return rest-1;
            } else {
                return rest;
            }
        }

        if (rest >= AtsusaDP[level]) {
            rest -= AtsusaDP[level];
            ans += P_CountDP[level];
            return rest;
        } else {
            rest--;//B part
            if (rest > 0) rest = eatBG(level - 1, rest);// n-1 BG part
            if (rest > 0) {//P part
                rest--;
                ans++;
            }
            if (rest > 0) {//n-1 part
                rest = eatBG(level - 1, rest);
            }
            rest--;//B part
            return rest;
        }
    }
}
最後に

めっちゃ丁寧に解説したので、D問題初めてやる人とかの足がかりになればいいな。 論理的な意味を重視したコードを書いたので冗長な部分は多々あります。

こんなクソ長い記事を読んでいただきありがとうございました。