はじめに
こんにちは。nsubaruです。 夏休み中だったこともあり、この記事も止まっていたのですが、もう終わったので2週間遅れでこの記事を書いています。
今回も最終的には勝っているので良いですが、水色になってからはE問題まで解かないと下がるっていうプレッシャーで、毎コンテストドキドキですね。 だからといって Unrated で出るのは甘えなのでもちろん Rated で参加しました。
結果サマリ
-
WCPC参加人数: 5人
-
各問題の最速解答者(ペナルティ無し):
| 問題 | 参加者名 | タイム | 言語 |
|---|---|---|---|
| A | nsubaru | 0:47 | Java |
| B | ShoboiNamae | 6:31 | C++ |
| C | ShoboiNamae | 13:37 | C++ |
| D | ShoboiNamae | 38:32 | C++ |
| E | nsubaru | 57:13 | Java |
| F | - | - | - |
| G | - | - | - |
今週の出題
A問題
問題文を表示
問題文
ある信号機の色は B(青)、Y(黄)、R(赤)、B(青)、Y(黄)、R(赤) という順に、周期的に変化します。
現在の信号機の色を表す文字 が与えられます。次の信号機の色を出力してください。
制約
- は
B、Y、Rのいずれか
入力
入力は以下の形式で標準入力から与えられる。
出力
次の信号機の色を表す文字を出力せよ。
if 文を3つ書けば解ける問題ですね。タイピングがそこまで速くないので、クォーテーション書かないといけない問題系は嫌いです(´;ω;`)
B問題
問題文を表示
問題文
数直線上に 人の人がいます。人には から までの番号がついており、人 は座標 にいます。ただし、同じ座標に複数の人がいる場合もあります。
ある人が「一線を画す」とは、自分以外のどの人との距離も 以上であることをいいます。
厳密には、人 が「一線を画す」とは、 すべての に対して が成り立つことをいいます。
「一線を画す」人の番号をすべて昇順に出力してください。
制約
- 入力される値は全て整数
入力
入力は以下の形式で標準入力から与えられる。
出力
一線を画す人の人数を 、その人たちの番号を昇順に並べた列を とする。以下の形式で出力せよ。
ただし の場合、 行目は空行とせよ。
毎度のことながら B 問題って問題文を見落としがちで、人数を出力するだけかと思ったり、未満を判定しようとして以下になっていたり、安定の 1WA でした。 最近実際に自分の提出を目視調査してみたのですが、B問題のWA率は高めでしたね。問題に関しては、それぞれの人について全探索し、条件を満たす人を List なり Vector なりに加えていって最後にそのサイズと中の要素を出力すればよいですね。
C問題
問題文を表示
問題文
文字列 が与えられます。 個のクエリを処理してください。 クエリでは が与えられるので、 の 文字目から 文字目までからなる部分文字列に、 が部分文字列として含まれるかを判定してください。
部分文字列とは
の部分文字列とは、 の先頭から 文字以上、末尾から 文字以上削除して得られる文字列のことをいいます。
例えば、ab は abc の部分文字列ですが、ac は abc の部分文字列ではありません。
制約
- は長さ 以上 以下の英小文字からなる文字列
- は長さ 以上 以下の英小文字からなる文字列
- は整数
入力
入力は以下の形式で標準入力から与えられる。
各クエリ は以下の形式で与えられる。
出力
行出力せよ。 行目には 番目のクエリについて、 の 文字目から 文字目までからなる部分文字列に、 が部分文字列として含まれれば Yes を、含まれなければ No を出力せよ。
文字列 の指定された部分文字列に文字列 が含まれるか判定する問題です。
部分文字列として含まれるかだけを判定するのなら簡単なのですが、この問題は指定された区間内に含まれるかを判定しないといけないため少し難易度は上がります。
用いた解法は 内の 番目の位置から文字列 が作れるかどうかを前計算で判定しておき、 番目から文字列 が作れる場合は 1 、そうでなければ 0 となるような配列を作成します。 あとは任意区間について 1 となるような値が 1 つでも存在すれば が存在することを示せるので、その判定を行うために累積和を取り任意区間の総和が 1 以上かどうかという判定を行うことで解きました。
D問題
問題文を表示
問題文
個のマスが横一列に並んでおり、左から順にマス からマス までの番号がついています。色は英小文字で表されます。
はじめ、すべてのマスの色は a で、各マスには何も置かれていません。
個のクエリが与えられるので、与えられた順に処理してください。各クエリは以下の 種類のいずれかです。
-
1 X: マス にタイルが置かれていなければタイルを置き、置かれていれば取り除く。 -
2 C: タイルが置かれていないすべてのマスの色を に変更する。
すべてのクエリを処理した後の各マスの色を(タイルが置かれているかどうかにかかわらず)求めてください。
制約
- 各クエリはタイプ またはタイプ のいずれか
- タイプ のクエリにおいて、 は を満たす整数
- タイプ のクエリにおいて、 は英小文字 文字
入力
入力は以下の形式で標準入力から与えられる。
各クエリ は
または、
の形で与えられる。
出力
最終的な各マスの色を、左から順につなげた長さ の文字列として出力せよ。
最近大体のD問題がよくある問題だなと思ってきたのですが、これもまあ典型ですね。と言いつつ、ぱっと見分からず先にE問題解いたのですが…
クエリタイプ 1 の処理は 、2 の処理は です。そのため極端な場合、2 の処理しかなければ になり間に合いません。 この問題で求めなければいけないものはクエリを処理した後の最終的なマスの色です。このことからクエリの途中に各マスがどんな色になっているかは分からなくてもよい、つまり各マスごとに最後に塗られた色のみを覚えておけばよいと分かります。
これはクエリを逆順に見ていくというテクニックを用いることで解けます。逆順に見ていくと各マスが何色になるかが確定していくので、まだ色が確定していないマスの集合を保持しながら色を確定させていけば解くことができます。
この問題ちょっと意地悪で、Java で HashSet を使って提出したのですが、TLE になり C++ に書き換える必要がありました。この好プレーで一応 AC することが出来ました。 コンテスト後なんでダメだったのかなと考えていたのですが、この問題は集合に対してループする処理を行う必要があり、集合が保持している要素数に対して実際の内部配列のサイズは多くなっているため、そこで重くなったのかなと。 結果的に Java のコードでも TreeSet や他のデータ構造だと通ったため、HashSet に対する拡張 for 文は十分に気を付ける必要があるなと思いました。良い学びです。
E問題
問題文を表示
問題文
頂点に から の番号がついた 頂点の辺重み付き無向グラフが与えられます。 を満たす整数 について、頂点 と頂点 を結ぶ重み の辺があります。 また、 を満たす整数 について、頂点 と頂点 を結ぶ重み の辺があります。これ以外の辺はありません。
個のクエリを処理してください。クエリでは が与えられるので、頂点 から頂点 への最短経路の長さを求めてください。
制約
- 入力される値は全て整数
入力
入力は以下の形式で標準入力から与えられる。
各クエリ は以下の形式で与えられる。
出力
行出力せよ。 行目には 番目のクエリの答えを出力せよ。
はい。 D よりも先に解いた問題です。問題を見て解法がすぐに思いつかなかったのですが、ダイクストラを使いたいなという気持ちを持ちながらボードにお絵かきして考えていました。 E 問題は典型のアルゴリズムやテクニックをどう適用するかみたいな問題が多いなという印象で、今回もダイクストラを使いたいという気持ちが答えとなる発想に繋がったと思います。
こういった難しい問題に挑むにあたって大事なことは簡単な問題条件に変えて、絶対にわかることから潰していくことです。早期判定をいっぱい作ることで、解くべき問題を明確にすることができ自然と解法を導くことができます。
まず頂点 が無い場合を考えます。この場合は左回りか右回りの 2 通りになり、どちらの方がコストが低いのかを考えるだけです。 次に頂点 を絶対に通る場合について、頂点 を通って他の頂点に行った後、もう一度頂点 に戻ってくることは明らかに不要です。 したがって、頂点を通る場合はある頂点 から何らかのルートで頂点 に行き、頂点 からまた別のルートを通って頂点 に行くというルートになります。
この時自分が考えたのは、他の頂点から始まっても頂点 から次の頂点への最短経路を求める必要があるから、ここをダイクストラしておけばとりあえず半分は消せるなーと。 …ああ逆から見ても同じか!?となって、ある頂点から頂点 に行くというルートも頂点 を始点とするダイクストラを最初にしておけば、 -> 間も -> 間も で求められるということが分かりました。 後は頂点 を通る場合とそうでない場合(左回りか右回りか)の比較を行うだけで解くことができます。
F問題
問題文を表示
問題文
行 列のグリッドが与えられます。上から 行目では、左から 列目から 列目までのマスが黒く塗られており、それ以外のマスは白く塗られています。
個のクエリが与えられます。各クエリでは、以下の問題に答えてください。
- 整数 が与えられます。上から 行目から 行目まで、左から 列目から 列目までの長方形領域に含まれる黒いマスの個数を求めてください。
制約
- 各クエリについて、
- 各クエリについて、
- 入力される値はすべて整数
入力
入力は以下の形式で標準入力から与えられる。
各クエリ は
の形で与えられる。
出力
行出力せよ。 行目には、 番目のクエリに対する答えを出力せよ。
ここからはコンテスト中には全く分からなかったのですが、コンテスト後に解説ACすることが出来たので書いていこうと思います。 本当はコンテストから時間も経ったのでこの記事自体書くつもりがなかったのですが、この問題は初めての解法だったので書く価値があるなと思い書いています。
何から説明すべきか難しいのですが、問題を整理するとまず 行 列のグリッドとあり、これは であるためデータとして保持することはできないなと考えます。 ではどうやってグリッドの情報を表現し、クエリを処理していけばよいかというと、まずあるクエリ に着目し 行目から 行目までの行について、 列目から 列目までをループして探索するのではなく、1 列目から 列目までの黒マスの総数から1 列目から 列目までの黒マスの総数を引くということを考えます。
こうすることで 必要だった処理が、 になります。これはグリッドの情報を行ごとに 1 列目からの累積情報として持っていれば可能です。 この 1 列目からの累積情報というのが重要で、現在処理している列を とし、ある行 について 1 列目から 列目までの黒マスの総数の求め方を以下の3通りの条件に分けて整理することでどのように持てばよいかが見えてくると思います。
- の場合は
- の場合は、
- の場合は、
これを列ごとに全ての行を更新することなく管理する方法を考えます。ここで出てきている値について、, , は定数であることから、一度 を忘れて定数部分の遷移だけみると、各列ごとに毎回値を変える必要はなく初期値を 0 として , の場所で 2 回この定数を変えるだけでよさそうです。 またこの定数部分は各行ごとに求める必要があり、さらにその任意区間の総和(行 ~ までの区間和)が欲しいのですが、これは セグメント木 を使うことで簡単に求めることができます。
状態2については、この状態になっている行だけ を加算させることを考えます。これも同様に セグメント木 を用いることで を加算するべき状態 2 の個数として求めることができます。
最後に列ごとに処理を行いたいので、, , , を全てソートして 1 列に並べたときに適切にイベント(現在見ている値がクエリの左端なのか右端なのか、黒マスの開始か終了か)を処理すれば解くことができます。
G問題
問題文を表示
問題文
頂点に から の番号がついた 頂点の木が与えられます。 番目の辺は頂点 と頂点 を結んでいます。また、頂点 には整数 が書かれています。 個のクエリを処理してください。クエリでは整数 が与えられるので、以下の条件を満たす整数 の個数を求めてください。
- 頂点 と頂点 を結ぶパス上にある頂点のうち、整数 が書かれた頂点の個数を とする。この時、 が成り立つ。
制約
- 入力で与えられるグラフは木
- 入力される値は全て整数
入力
入力は以下の形式で標準入力から与えられる。
各クエリ は以下の形式で与えられる。
出力
行出力せよ。 行目には 番目のクエリの答えを出力せよ。
前提知識として、Mo’s Algorithm が必要で、そのうち木上のMoとかいうアルゴリズムを使えば簡単に解くことができる問題です。 ライブラリ化してしまえば誰でも解けるため、もっとdiffが低くてもおかしくないのではと思うのですが、そもそもレアなアルゴリズムで使いどころも見極めづらいのかなと思います。
木上のMoについての話をすると、まず使える前提としてクエリの問題でありそのクエリはそれぞれ独立していて、並び替えが可能であり、木上の任意の2頂点を結ぶパスに対してのクエリであるということです。 そしてそのパスに頂点が追加されたり、削除されたりした際の状態の変化が やそれに近いオーダーの場合に使用できます。
必要な知識としては、Euler Tour, LCA, Mo’s Algorithm かなと思います。
とりあえず今回ライブラリ化できたので、次は殴れます。
最後に
こういう記事は書くことで思考の過程を整理することができ、その解法に対する再現性が生まれてくるので非常に意味があるなと思っています。僕が担当する回はFとかGとかも積極的に書いていければなと思います。 最後までお読みいただきありがとうございました。nsubaruでした~。