AtCoder Beginner Contest 266
- A - Middle Letter
- B - Modulo Number
- C - Convex Quadrilateral
- D - Snuke Panic (1D)
- E - Throwing the Die
- F - Well-defined Path Queries on a Namori
- G - Yet Another RGB Sequence
コンテスト前のレート:1928
レート通りのパフォーマンスが得られる順位:418位(2000点、57分50秒)
A - Middle Letter
思考過程
- とりあえずを出力する。
- もし前後に文字ズレているようだったら修正するつもりだったが、合っていたのでそのまま提出する。
import java.util.Scanner; public class Main { public static void main(String[] args) throws Exception { Scanner sc = new Scanner(System.in); String s = sc.next(); sc.close(); System.out.println(s.charAt(s.length() / 2)); } }
ここまで0分34秒+0ペナ。151位。
B - Modulo Number
思考過程
- として、を求める感じ?と思ったが考えすぎで、基本はでよさそう。
- それだけだと例2がになるので、の結果がマイナスの場合はを足す。
import java.util.Scanner; public class Main { public static void main(String[] args) throws Exception { Scanner sc = new Scanner(System.in); long n = sc.nextLong(); sc.close(); int mod = 998244353; long ans = n % mod; if (ans < 0) { ans += mod; } System.out.println(ans); } }
ここまで2分51秒+0ペナ。615位。
C - Convex Quadrilateral
思考過程
- 直線がなす角度が度未満かどうかは内積か外積のようなもので判定できたような・・・と思って調べようとしてみるも、調べるのが下手で知りたい式がすぐに出てこない。(解説見たらやっぱり外積だった)
- 誤差が怖いところではあるが、座標の制約がとかならまあ大丈夫だろうと思い、角ABCの角度であれば「辺BCの傾き辺ABの傾き」(ここでいう傾きはatan2で求まるラジアン単位の角度)のように求め、これが度より大きい角が存在するかどうかを判定することにする。
import java.util.Scanner; public class Main { public static void main(String[] args) throws Exception { Scanner sc = new Scanner(System.in); int n = 4; int[] x = new int[n]; int[] y = new int[n]; for (int i = 0; i < n; i++) { x[i] = sc.nextInt(); y[i] = sc.nextInt(); } sc.close(); for (int i = 0; i < n; i++) { int j = (i + 1) % n; int k = (i + 2) % n; double d1 = Math.atan2(y[j] - y[i], x[j] - x[i]); double d2 = Math.atan2(y[k] - y[j], x[k] - x[j]); double r = d2 - d1; if (r < 0) { r += Math.PI * 2; } if (r > Math.PI) { System.out.println("No"); return; } } System.out.println("Yes"); } }
ここまで10分38秒+0ペナ。417位。
D - Snuke Panic (1D)
問題
配るDPならi → i+1、もらうDPならi-1 → iになるように書くのがわかりやすいと思っているのだが、混在した。
思考過程
- 時刻の制約がしかないので、「時刻に座標に高橋君がいるときの最大値」を考えてみる。
- すると、時刻、座標~からの遷移が考えられる。
- 各遷移元のmaxを取り、遷移先がすぬけ君を捕まえられる時刻・座標であれば、大きさを加算する。
- 時刻・座標に該当するすぬけ君の情報をで取れるように入力の受け取り方を変更する。
- 答えはの最大値。
- 例2がになってしまう。時刻未満の間は遠くの座標には行けないため、座標が時刻以下であることの条件を付け足す。
import java.io.BufferedReader; import java.io.InputStreamReader; public class Main { public static void main(String[] args) throws Exception { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); int n = Integer.parseInt(br.readLine()); int m = 100001; // 4 int[] x = new int[m]; int[] a = new int[m]; for (int i = 0; i < n; i++) { String[] sa = br.readLine().split(" "); int t = Integer.parseInt(sa[0]); x[t] = Integer.parseInt(sa[1]); a[t] = Integer.parseInt(sa[2]); } br.close(); long[][] dp = new long[m][5]; for (int i = 1; i < m; i++) { for (int j = 0; j < 5; j++) { // j → j-1 if (j > 0) { dp[i][j - 1] = Math.max(dp[i][j - 1], dp[i - 1][j]); } // j → j dp[i][j] = Math.max(dp[i][j], dp[i - 1][j]); // j → j+1 if (j < 4) { dp[i][j + 1] = Math.max(dp[i][j + 1], dp[i - 1][j]); } } if (x[i] <= i) { // 6 dp[i][x[i]] += a[i]; } } // 5 long ans = 0; for (int i = 0; i < 5; i++) { ans = Math.max(ans, dp[m - 1][i]); } System.out.println(ans); } }
ここまで19分13秒+0ペナ。275位。
E - Throwing the Die
思考過程
- 例1はわかる。例2がどうやって求められているのか考える。
- 回目が以上なら終了し、そうでなければ回目でを出すとすると、で合う。
- 一般には、残り回数がの時、次の結果が回の期待値以上なら終了、そうでなければ回の期待値になるとする。これはメモ化再帰がわかりやすそう。(と思ったが1つ前しか見ないので普通に前から埋めていくDPでよかった。)
import java.util.Scanner; public class Main { static int n; static double[] memo; public static void main(String[] args) throws Exception { Scanner sc = new Scanner(System.in); n = sc.nextInt(); sc.close(); memo = new double[n + 1]; memo[1] = 3.5; System.out.println(dfs(n)); } static double dfs(int rem) { if (memo[rem] > 0) { return memo[rem]; } double v1 = dfs(rem - 1); int v2 = 0; int c = 0; for (int i = 1; i <= 6; i++) { if (i > v1) { v2 += i; } else { c++; } } return memo[rem] = (v1 * c + v2) / 6; } }
ここまで29分58秒+0ペナ。264位。
F - Well-defined Path Queries on a Namori
思考過程
- サイクルがつだけ存在し、その部分を通る場合"No"となる。
- サイクルを構成する頂点を通るかどうかを調べる? いや、サンプルではサイクルを構成する頂点をつ通るだけでは"Yes"となっており、つ通る必要がありそう。
- 元のグラフをサイクルの各頂点を根とするいくつかの木に分解してみると、との根が同じかどうかを判定する問題になる。
- サイクルを検出するために通った頂点の履歴を持ちながらDFSを行う。
- サイクルの各頂点からDFSを行って根がどこかの情報を設定しておく。
import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.PrintWriter; import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; public class Main { static int n; static List<List<Integer>> list; static int[] root; static LinkedHashSet<Integer> roots; public static void main(String[] args) throws Exception { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); n = Integer.parseInt(br.readLine()); list = new ArrayList<>(n); for (int i = 0; i < n; i++) { list.add(new ArrayList<>()); } for (int i = 0; i < n; i++) { String[] sa = br.readLine().split(" "); int u = Integer.parseInt(sa[0]) - 1; int v = Integer.parseInt(sa[1]) - 1; list.get(u).add(v); list.get(v).add(u); } int q = Integer.parseInt(br.readLine()); int[] x = new int[q]; int[] y = new int[q]; for (int i = 0; i < q; i++) { String[] sa = br.readLine().split(" "); x[i] = Integer.parseInt(sa[0]) - 1; y[i] = Integer.parseInt(sa[1]) - 1; } br.close(); // 4 roots = dfs(0, -1, new LinkedHashSet<>()); // 5 root = new int[n]; for (int i : roots) { dfs(i, -1, i); } PrintWriter pw = new PrintWriter(System.out); for (int i = 0; i < q; i++) { if (root[x[i]] == root[y[i]]) { pw.println("Yes"); } else { pw.println("No"); } } pw.flush(); } // 5 static void dfs(int x, int p, int r) { root[x] = r; for (int c : list.get(x)) { if (c != p && !roots.contains(c)) { dfs(c, x, r); } } } // 4 static LinkedHashSet<Integer> dfs(int x, int p, LinkedHashSet<Integer> his) { his.add(x); for (int c : list.get(x)) { if (c != p) { if (his.contains(c)) { // hisから頂点c以降の経路を抜き出して返す LinkedHashSet<Integer> ret = new LinkedHashSet<>(); int size = his.size(); Integer[] arr = his.toArray(new Integer[0]); for (int i = 0; i < size; i++) { if (arr[i] == c) { for (int j = i; j < size; j++) { ret.add(arr[j]); } return ret; } } } LinkedHashSet<Integer> res = dfs(c, x, his); if (res != null) { return res; } } } his.remove(x); return null; } }
ここまで46分50秒+0ペナ。197位。
この時点で勝ったと思いたかったが、Gも既に100人以上解いていて、この伸び方だと最終的には400人くらい解いてしまうかもという感じでまだ油断はできそうにない。
(解けなければ356位でパフォ1990ほどだった)
G - Yet Another RGB Sequence
思考過程
- "RG"(何か別の文字)を個、"R"を個、"G"を個、"B"を個並べると考える。ただし"R"の後ろに"G"は置けない。
- 上記種の合計個数をとして、"R"の後ろに"G"を置けない制約がなければになりそうだが、この制約をどのように考慮すればよいか。
- 個の中から個を選び、残りから個を選び、という考え方ではなく、初めに個並んでいるところの間に特に制約なく個を追加し、個を追加し、最後に個を追加する時のみ"R"を追加した箇所の後ろには追加できないと考える。
- 個のボールの間に個仕切りを追加する方法は、通り。最後だけではなくとなる。
import java.math.BigInteger; import java.util.Scanner; public class Main { public static void main(String[] args) throws Exception { Scanner sc = new Scanner(System.in); int r = sc.nextInt(); int g = sc.nextInt(); int b = sc.nextInt(); int k = sc.nextInt(); sc.close(); int mod = 998244353; Kaijou kai = new Kaijou(3000000, mod); r -= k; g -= k; long vr = kai.comb(k + r, r); long vb = kai.comb(k + r + b, b); long vg = kai.comb(k + b + g, g); long ans = vr * vb % mod * vg % mod; System.out.println(ans); } // 以下ライブラリ static class Kaijou { long[] p, pi; int m; public Kaijou(int n, int mod) { n++; m = mod; p = new long[n]; pi = new long[n]; p[0] = 1; pi[0] = 1; for (int i = 1; i < n; i++) { p[i] = p[i - 1] * i % m; } pi[n - 1] = BigInteger.valueOf(p[n - 1]) .modInverse(BigInteger.valueOf(m)).longValue(); for (int i = n - 1; i > 1; i--) { pi[i - 1] = pi[i] * i % m; } } public long comb(int n, int r) { if (n < r) return 0; return p[n] * pi[r] % m * pi[n - r] % m; } public long perm(int n, int r) { if (n < r) return 0; return p[n] * pi[n - r] % m; } } }
ここまで61分44秒+0ペナ。89位。
Exに40分近くも使えるまたとない機会だったが解けず。
時刻か座標かで並べ替えて、「番目まで見た時に番目の位置にいる場合の最大値」というDPを考えていたが、遷移元として可能な位置を全て調べてしまうとで駄目で、高速に求めるには二次元セグ木のようなものがほしい気がするけど持っておらず、普通のセグ木+平面走査的な方法でどうにかならないかと考えたりはしたが、厳しかった。
順位表を見ていて解けなくてもパフォ2400に変わりはないだろうと思い、残り10分を切った辺りで諦め。
最終結果:ABCDEFGの7完2600点、61分44秒、93位、パフォーマンス2400
レート変動:1928→1985(+57)
直近3回で-124というひどさでこの先どこまで転落するかと思ったが、まさかの半分近い回復。
もう二度と黄色に戻れないかもなんて言いつつ、こんなにすぐ射程圏内に戻れるならまだなんとかなるのかもしれない。
まあでも今回はFやGがDPではなかったおかげかな。
なお、今回はABCにおける過去最高順位だった。
外積と二次元セグ木は使えるようにしておかなければ。