AtCoder Beginner Contest 183
コンテスト前のレート:1648
レート通りのパフォーマンスが得られる順位:632位(2100点、98分04秒)
A - ReLU
思考過程
- マイナスをに丸めるので、を出力する。
import java.util.Scanner; public class Main { public static void main(String[] args) throws Exception { Scanner sc = new Scanner(System.in); int x = sc.nextInt(); sc.close(); System.out.println(Math.max(x, 0)); } }
ここまで0分36秒+0ペナ。342位。
B - Billiards
思考過程
- ~反射点の長さ:反射点~の長さ
::が成り立つ。 - 少し見方を換えると、
~反射点の長さ:~の長さ
::となる。 - これを変形していくと、
import java.util.Scanner; public class Main { public static void main(String[] args) throws Exception { Scanner sc = new Scanner(System.in); int sx = sc.nextInt(); int sy = sc.nextInt(); int gx = sc.nextInt(); int gy = sc.nextInt(); sc.close(); double ans = (double) (gx - sx) * sy / (sy + gy) + sx; System.out.println(ans); } }
ここまで5分18秒+0ペナ。530位。
C - Travel
思考過程
- が間に合う制約なのでとりあえず順列全列挙する。
- 都市を除いた順列の前後に都市を追加したリストに対して、移動時間の合計を計算してと一致するか判定する。
import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; import java.util.Scanner; public class Main { static int n, k; static int[][] t; static int ans = 0; public static void main(String[] args) throws Exception { Scanner sc = new Scanner(System.in); n = sc.nextInt(); k = sc.nextInt(); t = new int[n][n]; for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { t[i][j] = sc.nextInt(); } } sc.close(); int[] target = new int[n - 1]; for (int i = 0; i < n - 1; i++) { target[i] = i + 1; } permutation(target, new LinkedHashSet<>()); System.out.println(ans); } // 以下、「if (set.size() == target.length)」内以外ライブラリ static void permutation(int[] target, LinkedHashSet<Integer> set) { if (set.size() == target.length) { List<Integer> list = new ArrayList<>(); list.add(0); for (int i : set) { list.add(target[i]); } list.add(0); int sum = 0; for (int i = 1; i < list.size(); i++) { sum += t[list.get(i - 1)][list.get(i)]; } if (sum == k) { ans++; } return; } for (int i = 0; i < target.length; i++) { if (!set.contains(i)) { set.add(i); permutation(target, set); set.remove(i); } } } }
ここまで12分33秒+0ペナ。619位。
D - Water Heater
思考過程
- 問題文がぱっと読み取れなかったので、とりあえずちゃんと図示してみる。
- いもす法をして、を超えている箇所がないか調べるだけ。
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)); String[] sa = br.readLine().split(" "); int n = Integer.parseInt(sa[0]); int w = Integer.parseInt(sa[1]); int[] s = new int[n]; int[] t = new int[n]; int[] p = new int[n]; for (int i = 0; i < n; i++) { sa = br.readLine().split(" "); s[i] = Integer.parseInt(sa[0]); t[i] = Integer.parseInt(sa[1]); p[i] = Integer.parseInt(sa[2]); } br.close(); long[] a = new long[200005]; for (int i = 0; i < n; i++) { a[s[i]] += p[i]; a[t[i]] -= p[i]; } for (int i = 1; i < a.length; i++) { a[i] += a[i - 1]; } for (int i = 0; i < a.length; i++) { if (a[i] > w) { System.out.println("No"); return; } } System.out.println("Yes"); } }
ここまで17分42秒+0ペナ。412位。
E - Queen on Grid
問題
できてみれば単純だったが、累積和をごちゃごちゃにしたりして時間かかってしまった。
思考過程
- 上、左、左上からもらうDPを考える。
- 上からの遷移は、遷移元候補の各マスから手で移動した場合の合計なので、となる(は壁の次のマス)。左と左上からの遷移も同様。
- このままだとなので、合計を求める部分を累積和にしたい。
- 累積和は方向別々に取る必要がある。
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)); String[] sa = br.readLine().split(" "); int h = Integer.parseInt(sa[0]); int w = Integer.parseInt(sa[1]); char[][] s = new char[h][w]; for (int i = 0; i < h; i++) { s[i] = br.readLine().toCharArray(); } br.close(); int mod = 1000000007; long[][] dp = new long[h][w]; long[][] dp1 = new long[h][w]; long[][] dp2 = new long[h][w]; long[][] dp3 = new long[h][w]; dp[0][0] = 1; for (int i = 0; i < h; i++) { for (int j = 0; j < w; j++) { if (s[i][j] == '#') { continue; } // 縦 if (i > 0 && s[i - 1][j] == '.') { dp[i][j] += dp1[i - 1][j]; dp1[i][j] += dp1[i - 1][j]; } // 横 if (j > 0 && s[i][j - 1] == '.') { dp[i][j] += dp2[i][j - 1]; dp2[i][j] += dp2[i][j - 1]; } // 斜め if (i > 0 && j > 0 && s[i - 1][j - 1] == '.') { dp[i][j] += dp3[i - 1][j - 1]; dp3[i][j] += dp3[i - 1][j - 1]; } dp1[i][j] += dp[i][j]; dp2[i][j] += dp[i][j]; dp3[i][j] += dp[i][j]; dp[i][j] %= mod; dp1[i][j] %= mod; dp2[i][j] %= mod; dp3[i][j] %= mod; } } System.out.println(dp[h - 1][w - 1]); } }
ここまで34分31秒+0ペナ。282位。
F - Confluence
思考過程
- クエリはただのUnionFind。
- クエリは素直に考えれば、UnionFindの各頂点にMap<クラス、人数>の情報があればで求められる感じ。
- このマップの更新(合流する度にマージする)に多くの計算量がかかってしまいそうだが、必ず小さい方から大きい方にマージするマージテクをすれば、計算量が落ちるという話を見たことがある気がする。
- ACLのDSUにはサイズを見てマージする実装が組み込まれているので、それにマップの処理を追加してみた。
import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; public class Main { public static void main(String[] args) throws Exception { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); String[] sa = br.readLine().split(" "); int n = Integer.parseInt(sa[0]); int q = Integer.parseInt(sa[1]); DSU uf = new DSU(n); sa = br.readLine().split(" "); for (int i = 0; i < n; i++) { int c = Integer.parseInt(sa[i]); uf.list.get(i).put(c, 1); } PrintWriter pw = new PrintWriter(System.out); for (int i = 0; i < q; i++) { sa = br.readLine().split(" "); if (sa[0].equals("1")) { int a = Integer.parseInt(sa[1]) - 1; int b = Integer.parseInt(sa[2]) - 1; uf.merge(a, b); } else { int a = Integer.parseInt(sa[1]) - 1; int b = Integer.parseInt(sa[2]); int ans = uf.list.get(uf.leader(a)).getOrDefault(b, 0); pw.println(ans); } } br.close(); pw.flush(); } } // 以下「追加」コメント箇所以外ライブラリ /** * Disjoint Set Union(Union Find) */ class DSU { private int n; private int[] parentOrSize; private int num; List<Map<Integer, Integer>> list; // 追加 /** * n頂点0辺の無向グラフを作る。<br> * O(n) * * @param n 頂点数 */ public DSU(int n) { this.n = n; this.parentOrSize = new int[n]; Arrays.fill(parentOrSize, -1); num = n; // 追加start list = new ArrayList<>(n); for (int i = 0; i < n; i++) { list.add(new HashMap<>()); } // 追加end } /** * 辺(a, b)を足す。<br> * ならしO(α(n)) * * @param a 頂点番号(0≦a<n) * @param b 頂点番号(0≦b<n) * @return 代表元 */ int merge(int a, int b) { assert 0 <= a && a < n : "a=" + a; assert 0 <= b && b < n : "b=" + b; int x = leader(a); int y = leader(b); if (x == y) { return x; } if (-parentOrSize[x] < -parentOrSize[y]) { int tmp = x; x = y; y = tmp; } parentOrSize[x] += parentOrSize[y]; parentOrSize[y] = x; num--; // 追加start Map<Integer, Integer> mapx = list.get(x); Map<Integer, Integer> mapy = list.get(y); for (Integer key : mapy.keySet()) { mapx.put(key, mapx.getOrDefault(key, 0) + mapy.get(key)); } mapy.clear(); // 追加end return x; } /** * 頂点a, bが連結かどうか。<br> * ならしO(α(n)) * * @param a 頂点番号(0≦a<n) * @param b 頂点番号(0≦b<n) * @return true:連結 false:非連結 */ boolean same(int a, int b) { assert 0 <= a && a < n : "a=" + a; assert 0 <= b && b < n : "b=" + b; return leader(a) == leader(b); } /** * 頂点aの属する連結成分の代表元を返す。<br> * ならしO(α(n)) * * @param a 頂点番号(0≦a<n) * @return 代表元 */ int leader(int a) { assert 0 <= a && a < n : "a=" + a; if (parentOrSize[a] < 0) { return a; } else { return parentOrSize[a] = leader(parentOrSize[a]); } } }
ここまで52分52秒+0ペナ。220位。
最終結果:ABCDEFの全完、52分52秒、220位、パフォーマンス2088
レート変動:1648→1701(+53)
今回はできる問題を手堅く通せたという感じ。EのDPはもう少しスムーズに書けたかった。
正直Fが想定解通りだとはあまり思っていなかった。最近考察力がひどいので、後で「何でそんなことに気付かなかったんだ」と思うような方法があるのではという気がしていた。
5~9月は3回に1回黄パフォだったが、10月からボロボロで今回は10回ぶりの黄パフォ。間が青3回、水4回、緑2回なので本当にひどかった。
Highestの1820までは2200~2300程度を2回か、2550程度を1回出す必要があるくらいなので、まだまだ回復の道のりは遠いが、ひとまず一発水色落ちの心配はなくなって一安心。