AtCoder Beginner Contest 173
- A - Payment
- B - Judge Status Summary
- C - H and V
- D - Chat in a Circle
- E - Multiplication 4
- F - Intervals on Tree
コンテスト前のレート:1790
レート通りのパフォーマンスが得られる順位:561位
A - Payment
問題
if文を使わずに書けないかと一瞬思ったけど思いつかなかった。
思考過程
- お釣りに関わるのはで割った余りの部分。
- 基本的に「余り」が答えだが、余りがの場合になるのでそれだけ場合分けする。
import java.util.Scanner; public class Main { public static void main(String[] args) throws Exception { Scanner sc = new Scanner(System.in); int n = sc.nextInt(); sc.close(); int m = n % 1000; if (m == 0) { System.out.println(0); } else { System.out.println(1000 - m); } } }
ここまで0分59秒+0ペナ。214位。
B - Judge Status Summary
問題
解説PDFに珍しくJavaのソースがあると思ったら間違い発見。(8行目が、×:nextLine ○:next)
提出して確認していないのだろうか・・。
思考過程
- 文字列をキーとして個数を数えるので、マップを使う。
- 個でも出力するので、初めに各文字列のエントリを作ってしまえば、containsKeyで場合分けとかが不要になる。
- 出力形式を間違えないように、"AC x "とかは出力例からコピーする。
import java.util.HashMap; import java.util.Map; import java.util.Scanner; public class Main { public static void main(String[] args) throws Exception { Scanner sc = new Scanner(System.in); int n = sc.nextInt(); Map<String, Integer> map = new HashMap<>(); map.put("AC", 0); map.put("WA", 0); map.put("TLE", 0); map.put("RE", 0); for (int i = 0; i < n; i++) { String s = sc.next(); map.put(s, map.get(s) + 1); } sc.close(); System.out.println("AC x " + map.get("AC")); System.out.println("WA x " + map.get("WA")); System.out.println("TLE x " + map.get("TLE")); System.out.println("RE x " + map.get("RE")); } }
ここまで3分54秒+0ペナ。636位。
3分近くかかったらやや遅いらしい。最後の4行を書くのに手間取ったかな・・。
C - H and V
問題
通り全パターンを調べるbit全探索って競プロ始めるまで書いたことなかったけど、今ではこれでいけると思ったら迷わず書けるようになった。
思考過程
- 各行や列を赤く塗るかどうかのパターンは、通り。制約は十分小さいのでこれで間に合う。
- ビットがの時は赤く塗っているとし、ビットがでない行や列の '#' を数えてと一致するか調べる。
import java.util.Scanner; public class Main { public static void main(String[] args) throws Exception { Scanner sc = new Scanner(System.in); int h = sc.nextInt(); int w = sc.nextInt(); int k = sc.nextInt(); char[][] c = new char[h][w]; for (int i = 0; i < h; i++) { c[i] = sc.next().toCharArray(); } sc.close(); int h2 = 1 << h; int w2 = 1 << w; int ans = 0; // 赤く塗るパターンの全探索 for (int x = 0; x < h2; x++) { for (int y = 0; y < w2; y++) { // 各パターンでの黒マスのカウント int cnt = 0; for (int i = 0; i < h; i++) { if ((x >> i & 1) == 1) { continue; // 赤い行はスキップ } for (int j = 0; j < w; j++) { if ((y >> j & 1) == 1) { continue; // 赤い列はスキップ } if (c[i][j] == '#') { cnt++; } } } if (cnt == k) { ans++; } } } System.out.println(ans); } }
ここまで9分03秒+0ペナ。288位。
D - Chat in a Circle
問題
実験ベースになって9割くらいの自信で出しがちだけど、ちゃんと合ってて助かった。
思考過程
- 適当にとかの入力値で、小さい順に追加するのと大きい順に追加するのと、どちらが良さそうか試してみると、大きい順の方が明らかに良い。そもそもまず追加しないと、その要素の値が心地よさに寄与しない。
- もう少し数を増やして、~について、例1のような図を書き、貪欲に最も良い場所に追加してみる。すると、最大の数が回、番目からは回ずつ使えそう。
- 例えば、のように並んでいるところに、新たにを追加すると、のようになり、という並びができることになるため、その後は使えなくなる。
降順に追加していくという前提の下では、ある数を回(最初だけ回)使うと両隣がより小さい数になるということにより、上記2.が示せた。多分。 - 実装は、まず降順ソートして先頭を足し、番目から番目くらいまでの倍を足して、が奇数ならさらに次の要素も足すという感じにしようと思うが、境界がかなり自信ない。奇数の場合と偶数の場合とそれぞれきちんとテストする。
import java.util.Arrays; import java.util.Scanner; public class Main { public static void main(String[] args) throws Exception { Scanner sc = new Scanner(System.in); int n = sc.nextInt(); int[] a = new int[n]; for (int i = 0; i < n; i++) { a[i] = sc.nextInt(); } sc.close(); Arrays.sort(a); reverse(a); long ans = a[0]; int n2 = n / 2; for (int i = 1; i < n2; i++) { ans += a[i] * 2; } if (n % 2 == 1) { ans += a[n2]; } System.out.println(ans); } // 以下ライブラリ static void reverse(int[] a) { for (int i = 0; i < a.length / 2; i++) { int tmp = a[i]; a[i] = a[a.length - 1 - i]; a[a.length - 1 - i] = tmp; } } }
ここまで20分16秒+0ペナ。271位。
E - Multiplication 4
問題
ぱっと見で、Pairs(ABC155-D)とかで見たような、正、0、負の場合分けが大変そうな雰囲気を感じる。
順位表を見たらEが5人、Fが25人とかだったので、とりあえずF問題も見るが、すぐに解けそうになく、ちょっと場合分け頑張ればできそうなE問題に戻ってくる。
しかしそのまま解けずに終了。
思考過程
- まず、答えが正かか負かを求めることを考える。正にできるなら正、正にできずにできるなら、それ以外は負とする。
- が個以上含まれている場合、にすることができる。
- 正にできるかを考える。正の要素数を、負の要素数をとして、
- の場合、全てを選択する必要があるため、が偶数の場合のみ可能。(本番中はとの偶奇が一致の場合可能とかしていたりして破滅。)
- の場合、かつが奇数の場合のみ不可。ならば負数の個数を最低個は調整できるため偶数にすることができ、でもが偶数なら正になるのでOK。
- の場合、が入るため不可。
- 答えが正、、負のパターンの内、簡単なところからやっていく。
- 答えがの場合、固定値を出力する。
- 答えが負の場合、絶対値が小さい順に個取る。
- 答えが正の場合、正数降順のPriorityQueueと負数昇順のPriorityQueueそれぞれの先頭要素を取り出して積を比較し、正の方が大きければ先頭つを採用、負の方が大きければ先頭つを採用し、採用しなかったものはキューに戻す。ということを個になるまで繰り返す。
いずれかのキューが残り個以下の場合、個まで残り個の場合などに気を付ける。積のmodを取ってから比較しないようにも気を付ける。
(本番中は、最後の個を採用して良いかというところばかり見直していたが、個先までの積ではなく個だけで比較していたため、他をいくら直しても根本的に駄目なことに変わりはなかった。)
一応個の積で比較するという情報だけ得て、場合分けで破滅しながらなんとかACしたソース(こちら)もあるが、番兵を使ったりなどしたらだいぶマシになったので、以下のソースはその実装方法で。
import java.util.Arrays; import java.util.PriorityQueue; import java.util.Scanner; public class Main { public static void main(String[] args) throws Exception { Scanner sc = new Scanner(System.in); int n = sc.nextInt(); int k = sc.nextInt(); int[] a = new int[n]; for (int i = 0; i < n; i++) { a[i] = sc.nextInt(); } sc.close(); // minus、zero、plusの個数 int m = 0; int z = 0; int p = 0; for (int i = 0; i < n; i++) { if (a[i] == 0) { z++; } else if (a[i] > 0) { p++; } else { m++; } } // 0にできるか boolean bz = false; if (z > 0) { bz = true; } // 正にできるか boolean bp = false; if (p + m == k) { if (m % 2 == 0) { bp = true; } } else if (p + m > k) { if (p > 0 || k % 2 == 0) { bp = true; } } int mod = 1000000007; if (bp) { PriorityQueue<Integer> qp = new PriorityQueue<>((o1, o2) -> o2 - o1); // 正数降順 PriorityQueue<Integer> qm = new PriorityQueue<>(); // 負数昇順 for (int i = 0; i < n; i++) { if (a[i] > 0) { qp.add(a[i]); } else { qm.add(a[i]); } } // 番兵 qp.add(0); qp.add(0); qm.add(0); qm.add(0); long ans = 1; for (int i = 0; i < k; i++) { Integer np = qp.poll(); Integer np2 = qp.poll(); Integer nm = qm.poll(); Integer nm2 = qm.poll(); long vp = (long) np * np2; long vm = (long) nm * nm2; // 正と負がどちらも残り1個の場合、vpもvmも0となり、 // そのような場合に正を採用するため">"ではなく">="とする。 // Kまで残り1個の場合も正を採用 if (vp >= vm || i == k - 1) { ans *= np; // 1つ採用 qp.add(np2); qm.add(nm); qm.add(nm2); } else { ans *= vm % mod; // 2つ採用 qp.add(np); qp.add(np2); i++; } ans %= mod; } System.out.println(ans); } else if (bz) { System.out.println(0); } else { // 絶対値に変換して昇順にソート for (int i = 0; i < n; i++) { if (a[i] < 0) { a[i] = -a[i]; } } Arrays.sort(a); long ans = 1; for (int i = 0; i < k; i++) { ans *= a[i]; ans %= mod; } // -ansにしてmodを取る ans = mod - ans; ans %= mod; System.out.println(ans); } } }
F - Intervals on Tree
問題
10分ほどで下記の4.まで考えたのにE問題に戻ってしまった。
終了直後にTwitterで「辺の寄与数」的な文言が目に入った瞬間にすぐピンときたのでもったいない。
思考過程
- つも連結されないとした全体の数は、連結成分の数頂点の数であり、となる。
- 辺を本引くごとに連結成分の数がつ減るので、全てのの組み合わせについて、作られるグラフの辺の数を求め、その合計を先ほどの全体の数から引けばよさそう。
- 例えば例1では、{}と{}は作られる(合計本)が、{}は作られない。となる。
- 作られる連結成分は、全ての隣接頂点が連結成分内の最小値未満か最大値超である場合っぽい?が、回DFSするとかやっていると以上かかるのは確実で、高速に求める方法がわからない。
- 見方を変えて、辺ごとに使われる回数を数えようとすると、として、「以下の頂点数以上の頂点数」となる。
import java.util.Scanner; public class Main { public static void main(String[] args) throws Exception { Scanner sc = new Scanner(System.in); int n = sc.nextInt(); int[] u = new int[n]; int[] v = new int[n]; for (int i = 0; i < n - 1; i++) { u[i] = sc.nextInt(); v[i] = sc.nextInt(); } sc.close(); long ans = 0; for (int i = 1; i <= n; i++) { ans += (long) i * (i + 1) / 2; } for (int i = 0; i < n - 1; i++) { ans -= (long) Math.min(u[i], v[i]) * (n - Math.max(u[i], v[i]) + 1); } System.out.println(ans); } }
最終結果:ABCDの4完、20分16秒、1166位、パフォーマンス1477
レート変動:1790→1762
F問題にも最低20分は取るべきだったと思う。
今回は、バグさえ直せば通ると思ってしまって、根本的な見落としに気付かなかったところが大きいが。
E問題をぎりぎりで解いて1500点になったところでパフォ1540程度と大して変わらず、ABCDFの1600点なら1860程度と400近くも変わるので、F問題に賭ける手もある。
4完時点での残り時間の半分を使ってE問題を解けず、かつF問題の正解者数が3桁いるくらいならば、もっと狙っていきたい。