From 89fb705c23d6a4c3876ded0ae9458d4d729774ab Mon Sep 17 00:00:00 2001 From: Kevin Sheng Date: Wed, 6 Dec 2023 19:59:24 -0800 Subject: [PATCH] sirni sol and other things --- content/2_Bronze/Ad_Hoc.mdx | 2 +- content/3_Silver/Intro_Bitwise.mdx | 2 +- content/4_Gold/DSU.mdx | 257 +++++++++++++++++------------ content/4_Gold/MST.problems.json | 4 +- content/4_Gold/PURS.mdx | 4 +- solutions/gold/coci-17-sirni.mdx | 144 ++++++++++++++++ solutions/silver/cf-782b.mdx | 2 +- solutions/silver/cf-803D.mdx | 2 +- solutions/silver/cses-1085.mdx | 2 +- solutions/silver/usaco-594.mdx | 2 +- solutions/silver/usaco-668.mdx | 2 +- solutions/silver/usaco-716.mdx | 4 +- solutions/silver/usaco-991.mdx | 2 +- 13 files changed, 306 insertions(+), 123 deletions(-) create mode 100644 solutions/gold/coci-17-sirni.mdx diff --git a/content/2_Bronze/Ad_Hoc.mdx b/content/2_Bronze/Ad_Hoc.mdx index ba63b52c55..be3fe97a94 100644 --- a/content/2_Bronze/Ad_Hoc.mdx +++ b/content/2_Bronze/Ad_Hoc.mdx @@ -262,7 +262,7 @@ for i in range(k): def check(): """ - :return whether it's possible to construct a + :return: whether it's possible to construct a valid ordering with given fixed elements """ new_order = order.copy() diff --git a/content/3_Silver/Intro_Bitwise.mdx b/content/3_Silver/Intro_Bitwise.mdx index 44a215a511..22be6eef11 100644 --- a/content/3_Silver/Intro_Bitwise.mdx +++ b/content/3_Silver/Intro_Bitwise.mdx @@ -174,7 +174,7 @@ def ask(s: str, a: int, b: int) -> int: def sum(a: int, b: int) -> int: - """:return the sum of the elements at a and b (0-indexed)""" + """:return: the sum of the elements at a and b (0-indexed)""" a += 1 b += 1 and_ = ask("and", a, b) diff --git a/content/4_Gold/DSU.mdx b/content/4_Gold/DSU.mdx index aad5dfccb8..5bc6a53502 100644 --- a/content/4_Gold/DSU.mdx +++ b/content/4_Gold/DSU.mdx @@ -86,25 +86,38 @@ otherwise. #include using namespace std; -struct DSU { - vector e; - DSU(int N) { e = vector(N, -1); } - - // get representive component (uses path compression) - int get(int x) { return e[x] < 0 ? x : e[x] = get(e[x]); } - - bool same_set(int a, int b) { return get(a) == get(b); } - - int size(int x) { return -e[get(x)]; } - - bool unite(int x, int y) { // union by size - x = get(x), y = get(y); - if (x == y) return false; - if (e[x] > e[y]) swap(x, y); - e[x] += e[y]; - e[y] = x; - return true; - } +class DisjointSets { + private: + vector parents; + vector sizes; + public: + DisjointSets(int size) : parents(size), sizes(size, 1) { + for (int i = 0; i < size; i++) { + parents[i] = i; + } + } + + /** @return the "representative" node in x's component */ + int find(int x) { + return parents[x] == x ? x : (parents[x] = find(parents[x])); + } + + /** @return whether the merge changed connectivity */ + bool unite(int x, int y) { + int x_root = find(x); + int y_root = find(y); + if (x_root == y_root) { return false; } + + if (sizes[x_root] < sizes[y_root]) { + swap(x_root, y_root); + } + sizes[x_root] += sizes[y_root]; + parents[y_root] = x_root; + return true; + } + + /** @return whether x and y are in the same connected component */ + bool connected(int x, int y) { return find(x) == find(y); } }; ``` @@ -115,37 +128,38 @@ struct DSU { import java.util.*; public class DisjointSets { - int[] parents; // 0-indexed + int[] parents; int[] sizes; public DisjointSets(int size) { - sizes = new int[size]; parents = new int[size]; - Arrays.fill(sizes, 1); - Arrays.fill(parents, -1); + sizes = new int[size]; + for (int i = 0; i < size; i++) { + parents[i] = i; + sizes[i] = -1; + } } - // finds the "representative" node in a's component + /** @return the "representative" node in x's component */ public int find(int x) { - return parents[x] == -1 ? x : (parents[x] = find(parents[x])); + return parents[x] == x ? x : (parents[x] = find(parents[x])); } - // returns whether the merge changed connectivity - public boolean union(int x, int y) { + /** @return whether the merge changed connectivity */ + public boolean unite(int x, int y) { int xRoot = find(x); int yRoot = find(y); if (xRoot == yRoot) { return false; } + if (sizes[xRoot] < sizes[yRoot]) { - parents[xRoot] = yRoot; - sizes[yRoot] += sizes[xRoot]; - } else { - parents[yRoot] = xRoot; - sizes[xRoot] += sizes[yRoot]; + return unite(yRoot, xRoot); } + parents[yRoot] = xRoot; + sizes[xRoot] += sizes[yRoot]; return true; } - // returns whether two nodes are in the same connected component + /** @return whether x and y are in the same connected component */ public boolean connected(int x, int y) { return find(x) == find(y); } } ``` @@ -156,18 +170,18 @@ public class DisjointSets { ```py class DisjointSets: def __init__(self, size: int) -> None: - self.parents = [-1 for _ in range(size)] + self.parents = [i for i in range(size)] self.sizes = [1 for _ in range(size)] - # finds the "representative" node in a's component def find(self, x: int) -> int: - if self.parents[x] == -1: + """:return: the "representative" node in x's component""" + if self.parents[x] == x: return x self.parents[x] = self.find(self.parents[x]) return self.parents[x] - # returns whether the merge changed connectivity - def union(self, x: int, y: int) -> bool: + def unite(self, x: int, y: int) -> bool: + """:return: whether the merge changed connectivity""" x_root = self.find(x) y_root = self.find(y) if x_root == y_root: @@ -175,12 +189,13 @@ class DisjointSets: if self.sizes[x_root] < self.sizes[y_root]: x_root, y_root = y_root, x_root + self.parents[y_root] = x_root self.sizes[x_root] += self.sizes[y_root] return True - # returns whether two nodes are in the same connected component def connected(self, x: int, y: int) -> bool: + """:return: whether x and y are in the same connected component""" return self.find(x) == self.find(y) ``` @@ -192,18 +207,22 @@ DFS for computing connected components. ## Solution - Focus Problem -**Time Complexity:** $\mathcal{O}(Q \alpha(N))$ - Without union find, we would have to represent the graph with an adjacency list and use [flood fill](/silver/flood-fill) to calculate connected components. This approach takes $\mathcal{O}(NQ)$ time, which is too slow, motivating us to use **union find**. -By representing the graph with the union find data structure, we can use its -methods to both _unite_ vertices and check if two vertices $u_i$ and $v_i$ are -in the same connected component using only $\mathcal{O}(\alpha(N))$ amortized -time. This reduces the overall time complexity to $\mathcal{O}(Q \alpha(N))$, -which is a substantial improvement and allows us to pass all test cases. +By representing the graph with the union find data structure that was just +implemented above, we can use its methods to both _unite_ vertices and check +if two vertices $u_i$ and $v_i$ are in the same connected component using only +$\mathcal{O}(\alpha(N))$ amortized time. + +This reduces the overall time complexity to $\mathcal{O}(Q \alpha(N))$, which +is a substantial improvement and allows us to pass all test cases. + +## Implementation + +**Time Complexity:** $\mathcal{O}(Q \alpha(N))$ @@ -213,40 +232,53 @@ which is a substantial improvement and allows us to pass all test cases. using namespace std; // BeginCodeSnip{DSU} -struct DSU { - vector e; - DSU(int N) { e = vector(N, -1); } - - // get representive component (uses path compression) - int get(int x) { return e[x] < 0 ? x : e[x] = get(e[x]); } - - bool same_set(int a, int b) { return get(a) == get(b); } - - int size(int x) { return -e[get(x)]; } - - bool unite(int x, int y) { // union by size - x = get(x), y = get(y); - if (x == y) return false; - if (e[x] > e[y]) swap(x, y); - e[x] += e[y]; - e[y] = x; - return true; - } +class DisjointSets { + private: + vector parents; + vector sizes; + public: + DisjointSets(int size) : parents(size), sizes(size, 1) { + for (int i = 0; i < size; i++) { + parents[i] = i; + } + } + + /** @return the "representative" node in x's component */ + int find(int x) { + return parents[x] == x ? x : (parents[x] = find(parents[x])); + } + + /** @return whether the merge changed connectivity */ + bool unite(int x, int y) { + int x_root = find(x); + int y_root = find(y); + if (x_root == y_root) { return false; } + + if (sizes[x_root] < sizes[y_root]) { + swap(x_root, y_root); + } + sizes[x_root] += sizes[y_root]; + parents[y_root] = x_root; + return true; + } + + /** @return whether x and y are in the same connected component */ + bool connected(int x, int y) { return find(x) == find(y); } }; // EndCodeSnip int main() { int node_num, query_num; cin >> node_num >> query_num; - DSU dsu(node_num); + DisjointSets dsu(node_num); for (int i = 0; i < query_num; i++) { int type, u, v; cin >> type >> u >> v; if (type == 0) { dsu.unite(u, v); } else { - cout << dsu.same_set(u, v) << endl; + cout << dsu.connected(u, v) << endl; } } } @@ -260,40 +292,6 @@ import java.io.*; import java.util.*; public class Main { - private static class DisjointSets { - int[] parents; // 0-indexed - int[] sizes; - public DisjointSets(int size) { - sizes = new int[size]; - parents = new int[size]; - Arrays.fill(sizes, 1); - Arrays.fill(parents, -1); - } - - // finds the "representative" node in a's component - public int find(int x) { - return parents[x] == -1 ? x : (parents[x] = find(parents[x])); - } - - // returns whether the merge changed connectivity - public boolean union(int x, int y) { - int xRoot = find(x); - int yRoot = find(y); - if (xRoot == yRoot) { return false; } - if (sizes[xRoot] < sizes[yRoot]) { - parents[xRoot] = yRoot; - sizes[yRoot] += sizes[xRoot]; - } else { - parents[yRoot] = xRoot; - sizes[xRoot] += sizes[yRoot]; - } - return true; - } - - // returns whether two nodes are in the same connected component - public boolean connected(int x, int y) { return find(x) == find(y); } - } - public static void main(String[] args) { Kattio io = new Kattio(); int size = io.nextInt(); @@ -305,7 +303,7 @@ public class Main { int u = io.nextInt(); int v = io.nextInt(); if (type == 0) { - dsu.union(u, v); + dsu.unite(u, v); } else { if (dsu.connected(u, v)) { io.println(1); @@ -319,26 +317,65 @@ public class Main { // CodeSnip{Kattio} } + +// BeginCodeSnip{DSU} +class DisjointSets { + int[] parents; // 0-indexed + int[] sizes; + + public DisjointSets(int size) { + parents = new int[size]; + sizes = new int[size]; + for (int i = 0; i < size; i++) { + parents[i] = i; + sizes[i] = -1; + } + } + + /** @return the "representative" node in x's component */ + public int find(int x) { + return parents[x] == x ? x : (parents[x] = find(parents[x])); + } + + /** @return whether the merge changed connectivity */ + public boolean unite(int x, int y) { + int xRoot = find(x); + int yRoot = find(y); + if (xRoot == yRoot) { return false; } + + if (sizes[xRoot] < sizes[yRoot]) { + return unite(yRoot, xRoot); + } + parents[yRoot] = xRoot; + sizes[xRoot] += sizes[yRoot]; + return true; + } + + /** @return whether x and y are in the same connected component */ + public boolean connected(int x, int y) { return find(x) == find(y); } +} +// EndCodeSnip ``` ```py +# BeginCodeSnip{DSU} class DisjointSets: def __init__(self, size: int) -> None: - self.parents = [-1 for _ in range(size)] + self.parents = [i for i in range(size)] self.sizes = [1 for _ in range(size)] - # finds the "representative" node in a's component def find(self, x: int) -> int: - if self.parents[x] == -1: + """:return: the "representative" node in x's component""" + if self.parents[x] == x: return x self.parents[x] = self.find(self.parents[x]) return self.parents[x] - # returns whether the merge changed connectivity - def union(self, x: int, y: int) -> bool: + def unite(self, x: int, y: int) -> bool: + """:return: whether the merge changed connectivity""" x_root = self.find(x) y_root = self.find(y) if x_root == y_root: @@ -346,13 +383,15 @@ class DisjointSets: if self.sizes[x_root] < self.sizes[y_root]: x_root, y_root = y_root, x_root + self.parents[y_root] = x_root self.sizes[x_root] += self.sizes[y_root] return True - # returns whether two nodes are in the same connected component def connected(self, x: int, y: int) -> bool: + """:return: whether x and y are in the same connected component""" return self.find(x) == self.find(y) +# EndCodeSnip size, query_num = [int(i) for i in input().split()] @@ -361,7 +400,7 @@ dsu = DisjointSets(size) for _ in range(query_num): q_type, u, v = [int(i) for i in input().split()] if q_type == 0: - dsu.union(u, v) + dsu.unite(u, v) else: print(1 if dsu.connected(u, v) else 0) ``` diff --git a/content/4_Gold/MST.problems.json b/content/4_Gold/MST.problems.json index 64126ec0df..c7836fa218 100644 --- a/content/4_Gold/MST.problems.json +++ b/content/4_Gold/MST.problems.json @@ -164,14 +164,14 @@ }, { "uniqueId": "coci-17-sirni", - "name": "2017 - Sirni", + "name": "Sirni", "url": "https://oj.uz/problem/view/COCI17_sirni", "source": "COCI", "difficulty": "Hard", "isStarred": false, "tags": ["MST", "NT"], "solutionMetadata": { - "kind": "none" + "kind": "internal" } }, { diff --git a/content/4_Gold/PURS.mdx b/content/4_Gold/PURS.mdx index 3cfaa4a640..31748e6d0c 100644 --- a/content/4_Gold/PURS.mdx +++ b/content/4_Gold/PURS.mdx @@ -232,7 +232,7 @@ class MinSegmentTree: ind //= 2 def range_min(self, start: int, end: int) -> int: - """:return the minimum element of all elements in [start, end)""" + """:return: the minimum element of all elements in [start, end)""" start += self.len end += self.len min_ = float("inf") @@ -665,7 +665,7 @@ class BIT: ind += ind & -ind def pref_sum(self, ind: int): - """:return The sum of all values in [0, ind].""" + """:return: The sum of all values in [0, ind].""" ind += 1 sum_ = 0 while ind > 0: diff --git a/solutions/gold/coci-17-sirni.mdx b/solutions/gold/coci-17-sirni.mdx new file mode 100644 index 0000000000..ba794f6d19 --- /dev/null +++ b/solutions/gold/coci-17-sirni.mdx @@ -0,0 +1,144 @@ +--- +id: coci-17-sirni +source: COCI 2017 +title: Sirni +author: Kevin Sheng +--- + + + +With $10^5$ cards, there's way too many possible edges brute force would have to consider. +Is there any way we can cut out a majority of these possible edges? + + + + + +Say you have a card with value $5$, and you have two other cards with values $12$ and $13$. +Is it *ever* optimal to link $5$ to $13$? + + + + + +## Explanation + +Following the first hint, let's try to build up a candidate list of edges that have some semblance of a chance at being included in Daniel's MST. + +First, let's sort the cards. +We can also just not consider any duplicate cards, as those can be linked to other cards of the same value at zero cost. + +Now for each card $c$, we want to find all possible cards that might link up with it in an MST. +Since edges go both ways, we only have to consider cards with values greater than $c$. + +Here's where the second hint comes in. +No matter what other cards are in this example, it will always be optimal for $5$ to link up with $12$ instead of $13$. + +To generalize this, say we have a card with value $n$, and we have some values in between $kn$ and $(k+1) \cdot n$, where $k$ is some random multiple. +Among these values, it will always make sense for us to choose the one closest to $kn$, as that will give the least cost. + +For us to apply this observation, we do have to do some precalculations to find the next greater value for each possible card value, but this is doable in linear time. + +## Implementation + +**Time Complexity:** $\mathcal{O}(N \log{N})$ + + + + +```cpp +#include +#include +#include +#include + +using std::cout; +using std::endl; +using std::pair; +using std::vector; + +// BeginCodeSnip{DSU (from the module)} +class DisjointSets { + private: + vector parents; + vector sizes; + public: + DisjointSets(int size) : parents(size), sizes(size, 1) { + for (int i = 0; i < size; i++) { + parents[i] = i; + } + } + + int find(int n) { + return parents[n] == n ? n : (parents[n] = find(parents[n])); + } + + bool unite(int n1, int n2) { + n1 = find(n1); + n2 = find(n2); + if (n1 == n2) { + return false; + } + if (sizes[n1] < sizes[n2]) { + std::swap(n1, n2); + } + sizes[n1] += sizes[n2]; + parents[n2] = n1; + return true; + } +}; +// EndCodeSnip + +int main() { + int card_num; + std::cin >> card_num; + vector cards(card_num); + for (int& c : cards) { + std::cin >> c; + assert(c >= 1); + } + + std::sort(cards.begin(), cards.end()); + // we can erase the dupes bc modding them with the original one = 0 + cards.erase(std::unique(cards.begin(), cards.end()), cards.end()); + + int largest = cards.back(); // since we sorted the cards already + // next_largest[i] contains the index of lowest card value that's >= i + vector next_largest(largest + 1, -1); + for (int i = 0; i < cards.size(); i++) { + next_largest[cards[i]] = i; + } + for (int c = largest - 1; c >= 0; c--) { + // if this isn't assigned yet, assign it the previous one + if (next_largest[c] == -1) { + next_largest[c] = next_largest[c + 1]; + } + } + + vector>> good_links(largest + 1); + for (int i = 0; i < cards.size() - 1; i++) { + // get all relevant cards this card could be connected to + good_links[cards[i + 1] % cards[i]].push_back({i, i + 1}); + for (int at = 2 * cards[i]; at <= largest; at += cards[i]) { + int good_mod = next_largest[at]; + good_links[cards[good_mod] % cards[i]].push_back({i, good_mod}); + } + } + + long long total_cost = 0; + DisjointSets linked_cards(cards.size()); + for (int c = 0; c <= largest; c++) { + for (const pair& link : good_links[c]) { + bool result = linked_cards.unite(link.first, link.second); + total_cost += c * result; + } + } + + cout << total_cost << endl; +} +``` + + + + + diff --git a/solutions/silver/cf-782b.mdx b/solutions/silver/cf-782b.mdx index ddcecd3dd4..8993fb7079 100644 --- a/solutions/silver/cf-782b.mdx +++ b/solutions/silver/cf-782b.mdx @@ -195,7 +195,7 @@ def all_friends_converge(seconds: int) -> bool: Checks whether all friends can converge on one point in the specified time interval. :param seconds: Amount of seconds given for friends to converge. - :returns: If the friends can converge to a single point. + :return: If the friends can converge to a single point. """ overlap_lower, overlap_upper = 1, 10**9 diff --git a/solutions/silver/cf-803D.mdx b/solutions/silver/cf-803D.mdx index 81e478e4e9..b57e756106 100644 --- a/solutions/silver/cf-803D.mdx +++ b/solutions/silver/cf-803D.mdx @@ -113,7 +113,7 @@ Due to Python's constant factor, this code will TLE on a couple of test cases. ```py def width_valid(width, word_lengths, max_lines): - """:return if most optimized ad of given width satisfies constraints""" + """:return: if most optimized ad of given width satisfies constraints""" lines = 0 curr_width = 0 diff --git a/solutions/silver/cses-1085.mdx b/solutions/silver/cses-1085.mdx index b4849689d3..ca0729f14a 100644 --- a/solutions/silver/cses-1085.mdx +++ b/solutions/silver/cses-1085.mdx @@ -160,7 +160,7 @@ def can_divide_arrays(max_sum: int) -> bool: each subarray having a maximum sum of max_sum :param max_sum: The maximum sum of each subarray - :returns: If it is possible to divide nums with the above conditions. + :return: If it is possible to divide nums with the above conditions. """ num_subarrays = 0 cur_subarr_sum = 0 diff --git a/solutions/silver/usaco-594.mdx b/solutions/silver/usaco-594.mdx index daaa90cb48..1adbec3e07 100644 --- a/solutions/silver/usaco-594.mdx +++ b/solutions/silver/usaco-594.mdx @@ -103,7 +103,7 @@ def valid_blast_radius(blast_radius: int) -> bool: actual number of cows we have, then it is possible. Otherwise, it is not. :param blast_radius: The proposed blast radius to be tested. - :returns: True if it is possible to blow up all bales, False otherwise + :return: True if it is possible to blow up all bales, False otherwise """ last_blast_location = -float("inf") diff --git a/solutions/silver/usaco-668.mdx b/solutions/silver/usaco-668.mdx index f70b692229..4c318279c6 100644 --- a/solutions/silver/usaco-668.mdx +++ b/solutions/silver/usaco-668.mdx @@ -158,7 +158,7 @@ for i in range(cow_num): def reachable_cows(c: int) -> int: - """:return how many cows can be reached from a cow c""" + """:return: how many cows can be reached from a cow c""" global visited visited[c] = True reached = 1 # we can always reach the initial cow c diff --git a/solutions/silver/usaco-716.mdx b/solutions/silver/usaco-716.mdx index d732cca981..48368d53cd 100644 --- a/solutions/silver/usaco-716.mdx +++ b/solutions/silver/usaco-716.mdx @@ -231,7 +231,7 @@ from typing import List, Tuple def neighbors(r: int, c: int) -> List[Tuple[int, int]]: - """:return the 4 cardinal neighbors of a position""" + """:return: the 4 cardinal neighbors of a position""" return [(r - 1, c), (r + 1, c), (r, c - 1), (r, c + 1)] @@ -253,7 +253,7 @@ visited = [[False for _ in range(side_len)] for _ in range(side_len)] def connected_cow_num(r: int, c: int, prev_r: int, prev_c: int) -> int: - """:return the # of cows that a position can reach & marks them as visited""" + """:return: the # of cows that a position can reach & marks them as visited""" # check if we're out of bounds, if ( r < 0 diff --git a/solutions/silver/usaco-991.mdx b/solutions/silver/usaco-991.mdx index f1a940c048..deecb19471 100644 --- a/solutions/silver/usaco-991.mdx +++ b/solutions/silver/usaco-991.mdx @@ -135,7 +135,7 @@ public class Loan { ```py def can_repay(num_gallons: int, within_days: int, at_least: int, x_val: int) -> bool: """ - :return whether Farmer John gives Bessie at least N (num_gallons) + :return: whether Farmer John gives Bessie at least N (num_gallons) gallons of milk within within_days with the given X value """ g = 0