diff --git a/content/6_Advanced/SCC.mdx b/content/6_Advanced/SCC.mdx
index 2bb574e36f..2c9fdd1949 100644
--- a/content/6_Advanced/SCC.mdx
+++ b/content/6_Advanced/SCC.mdx
@@ -1,7 +1,7 @@
---
id: SCC
title: 'Strongly Connected Components'
-author: Benjamin Qi, Dong Liu, Neo Wang
+author: Benjamin Qi, Dong Liu, Neo Wang, Rameez Parwez
prerequisites:
- toposort
- BCC-2CC
@@ -11,28 +11,30 @@ description:
frequency: 1
---
-## SCCs
+# Strongly Connected Components (SCCs)
-The definition of a kingdom in this problem is equivalent to the definition of a strongly connected component. We can compute these components using either **Kosaraju's** or **Tarjan's** algorithms, both of which are described below.
+The definition of a kingdom in this problem is equivalent to the definition of a
+strongly connected component. We can compute these components using either
+**Kosaraju's** or **Tarjan's** algorithms, both of which are described below.
-### Kosaraju's Algorithm
+## Kosaraju's Algorithm
-
+
+
+in-house explanation?
+
+
-### Solution (Kosaraju's)
+## Implementation
+
+**Time Complexity:** $\mathcal{O}(N+M)$
@@ -163,7 +165,7 @@ public class Main {
-### Tarjan's Algorithm
+## Tarjan's Algorithm
@@ -171,111 +173,344 @@ public class Main {
-
+
-### Solution (Tarjan's)
+link to dpv in resources and just tell them to look at that, no way we're
+beating that explanation
-
+
+
+## Implementation
+
+**Time Complexity:** $\mathcal{O}(N+M)$
+
```cpp
-#include
-using namespace std;
-/**
- * Description: Tarjan's, DFS once to generate
- * strongly connected components in topological order. $a,b$
- * in same component if both $a\to b$ and $b\to a$ exist.
- * Uses less memory than Kosaraju b/c doesn't store reverse edges.
- * Time: O(N+M)
- * Source: KACTL
- * https://github.com/kth-competitive-programming/kactl/blob/master/content/graph/SCC.h
- * Verification: https://cses.fi/problemset/task/1686/
- */
-struct SCC {
- int N, ti = 0;
- vector> adj;
- vector disc, comp, st, comps;
- void init(int _N) {
- N = _N;
- adj.resize(N), disc.resize(N), comp = vector(N, -1);
+#include
+#include
+#include
+
+using std::cout;
+using std::endl;
+using std::vector;
+
+/** Takes in an adjacency list and calculates the SCCs of the graph. */
+class TarjanSolver {
+ private:
+ vector> rev_adj;
+ vector post;
+ vector comp;
+
+ vector visited;
+ int timer = 0;
+ int id = 0;
+
+ void fill_post(int at) {
+ visited[at] = true;
+ for (int n : rev_adj[at]) {
+ if (!visited[n]) { fill_post(n); }
+ }
+ post[at] = timer++;
}
- void ae(int x, int y) { adj[x].push_back(y); }
- int dfs(int x) {
- int low = disc[x] = ++ti;
- st.push_back(x); // disc[y] != 0 -> in stack
- for (int y : adj[x])
- if (comp[y] == -1) low = min(low, disc[y] ?: dfs(y));
- if (low == disc[x]) { // make new SCC, pop off stack until you find x
- comps.push_back(x);
- for (int y = -1; y != x;) comp[y = st.back()] = x, st.pop_back();
+
+ void find_comp(int at) {
+ visited[at] = true;
+ comp[at] = id;
+ for (int n : adj[at]) {
+ if (!visited[n]) { find_comp(n); }
}
- return low;
}
- void gen() {
- for (int i = 0; i < N; i++)
- if (!disc[i]) dfs(i);
- reverse(begin(comps), end(comps));
+
+ public:
+ const vector> &adj;
+
+ TarjanSolver(const vector> &adj)
+ : adj(adj), rev_adj(adj.size()), post(adj.size()), comp(adj.size()),
+ visited(adj.size()) {
+ vector nodes(adj.size());
+ for (int n = 0; n < adj.size(); n++) {
+ nodes[n] = n;
+ for (int next : adj[n]) { rev_adj[next].push_back(n); }
+ }
+
+ for (int n = 0; n < adj.size(); n++) {
+ if (!visited[n]) { fill_post(n); }
+ }
+ std::sort(nodes.begin(), nodes.end(),
+ [&](int n1, int n2) { return post[n1] > post[n2]; });
+
+ visited.assign(adj.size(), false);
+ for (int n : nodes) {
+ if (!visited[n]) {
+ find_comp(n);
+ id++;
+ }
+ }
}
+
+ int comp_num() const { return id; }
+
+ int get_comp(int n) const { return comp[n]; }
};
int main() {
- int n, m, a, b;
- cin >> n >> m;
-
- SCC graph;
- graph.init(n);
- while (m--) {
- cin >> a >> b;
- graph.ae(--a, --b);
- }
- graph.gen();
- int ID[200000]{};
- int ids = 0;
- for (int i = 0; i < n; i++) {
- if (!ID[graph.comp[i]]) { ID[graph.comp[i]] = ++ids; }
+ int planet_num;
+ int tele_num;
+ std::cin >> planet_num >> tele_num;
+ vector> adj(planet_num);
+ for (int t = 0; t < tele_num; t++) {
+ int from, to;
+ std::cin >> from >> to;
+ adj[--from].push_back(--to);
}
- cout << ids << '\n';
- for (int i = 0; i < n; i++) {
- cout << ID[graph.comp[i]] << " \n"[i == n - 1];
+
+ TarjanSolver scc(adj);
+ cout << scc.comp_num() << '\n';
+ for (int p = 0; p < planet_num - 1; p++) {
+ cout << scc.get_comp(p) + 1 << ' ';
}
+ cout << scc.get_comp(planet_num - 1) + 1 << endl;
}
```
-
-### Problems
+## Problems
-## 2-SAT
+# 2-SAT
+
+
+
+
+
+
-
+## Explanation
-implementation
+### Introduction
-
+The CSES problem already gives us a boolean formula in conjunctive normal form
+(CNF) that consists of a series of logical OR clauses ANDed together like so:
-### Tutorial
+$$
+(\lnot x_1 \lor x_2) \land (x_1 \lor \lnot x_2) \land (\lnot x_1 \lor \lnot x_2) \land (x_1 \lor \lnot x_3)
+$$
-
-
-
-
-
+Before we proceed, try linking this to graph theory. Hint: represent a variable
+and its negation with two nodes.
-
+### Construction
-mention KACTL - at most one
+Like the hint says, we can construct a graph with each variable having two
+nodes: one for itself and one for its negation. We're going to try to proceed by
+assigning each node a truth value. Note that the value of one of the variable's
+nodes determines the other, since if we know the value of one node, the other is
+the negation of that value.
-
+Now, for each clause $(a \lor b)$, we add two directed edges:
+$\lnot a \rightarrow b$ and $\lnot b \rightarrow a$. What this means for us is
+that if $a$ was false, then $b$ must be true, and vice versa.
+
+With these edges, an SCC implies a group of values that all have to have the
+same truth value.
+
+### Solving the Graph
+
+The only way for there to be an impossible assignment of truth values is if a
+node and its negation are in the same SCC, since this means that a boolean and
+its negation have to both be true, which is impossible.
+
+If the graph is consistent and there are no impossible configurations, we can
+start assigning truth values, starting from the SCCs that have no outgoing edges
+to other SCCs and proceeding backwards. With the initial SCCs, we set them all
+to true. As for other SCCs, if a value has already been assigned due to its
+negation being in a previously processed component, we have to assign all other
+values in the component to that value.
+
+Due to
+[certain properties about the graph we've constructed](https://en.wikipedia.org/wiki/2-satisfiability#Strongly_connected_components),
+we can guarantee that the resulting assignment of the variables does not have
+any "true" SCCs leading to "false" SCCs. A proof of this is beyond the scope of
+this module.
+
+## Implementation
+
+We use Tarjan's algorithm as it already provides us with a topological order to
+process the nodes in. However, it is also possible to use Kosaraju's algorithm.
+
+**Time Complexity:** $\mathcal{O}(N+M)$
+
+
+
+
+```cpp
+#include
+#include
+#include
+
+using std::cout;
+using std::endl;
+using std::vector;
+
+// BeginCodeSnip{SCC Solver}
+class TarjanSolver {
+ private:
+ vector> rev_adj;
+ vector post;
+ vector comp;
+
+ vector visited;
+ int timer = 0;
+ int id = 0;
+
+ void fill_post(int at) {
+ visited[at] = true;
+ for (int n : rev_adj[at]) {
+ if (!visited[n]) { fill_post(n); }
+ }
+ post[at] = timer++;
+ }
+
+ void find_comp(int at) {
+ visited[at] = true;
+ comp[at] = id;
+ for (int n : adj[at]) {
+ if (!visited[n]) { find_comp(n); }
+ }
+ }
+
+ public:
+ const vector> &adj;
+ TarjanSolver(const vector> &adj)
+ : adj(adj), rev_adj(adj.size()), post(adj.size()), comp(adj.size()),
+ visited(adj.size()) {
+ vector nodes(adj.size());
+ for (int n = 0; n < adj.size(); n++) {
+ nodes[n] = n;
+ for (int next : adj[n]) { rev_adj[next].push_back(n); }
+ }
+
+ for (int n = 0; n < adj.size(); n++) {
+ if (!visited[n]) { fill_post(n); }
+ }
+ std::sort(nodes.begin(), nodes.end(),
+ [&](int n1, int n2) { return post[n1] > post[n2]; });
+
+ visited.assign(adj.size(), false);
+ for (int n : nodes) {
+ if (!visited[n]) {
+ find_comp(n);
+ id++;
+ }
+ }
+ }
+
+ int comp_num() const { return id; }
+
+ int get_comp(int n) const { return comp[n]; }
+};
+// EndCodeSnip
+
+struct Clause {
+ int var1; // id of the first variable
+ bool neg1; // is it negated?
+ int var2;
+ bool neg2;
+};
+
+class SATSolver {
+ private:
+ vector> adj;
+ bool valid = true;
+ vector val;
+
+ /** @return the negation of the variable v */
+ int get_neg(int v) { return v % 2 == 1 ? v - 1 : v + 1; }
+
+ public:
+ SATSolver(const vector &clauses, int var_num)
+ : adj(2 * var_num), val(2 * var_num, -1) {
+ // 2 * var is the variable, while 2 * var + 1 is its negation
+ for (const Clause &c : clauses) {
+ // falseness of the first implies the truth of the second
+ adj[2 * c.var1 + !c.neg1].push_back(2 * c.var2 + c.neg2);
+ // and vice versa
+ adj[2 * c.var2 + !c.neg2].push_back(2 * c.var1 + c.neg1);
+ }
+
+ TarjanSolver scc(adj);
+ // a list of all the components in the graph
+ vector> comps(scc.comp_num());
+ for (int i = 0; i < 2 * var_num; i += 2) {
+ // do a node and its negation share the same component?
+ if (scc.get_comp(i) == scc.get_comp(i + 1)) {
+ valid = false;
+ return;
+ }
+ comps[scc.get_comp(i)].push_back(i);
+ comps[scc.get_comp(i + 1)].push_back(i + 1);
+ }
+
+ /*
+ * because of how our tarjan solver works, starting from
+ * starting from comp 0 and going up process the graph
+ * in reverse topological order- neat, huh?
+ */
+ for (const vector &comp : comps) {
+ int set_to = 1; // set all to true by default
+ // check if any values have had their negations set yet
+ for (int v : comp) {
+ if (val[get_neg(v)] != -1) {
+ set_to = !val[get_neg(v)];
+ break;
+ }
+ }
+
+ for (int v : comp) { val[v] = set_to; }
+ }
+ }
+
+ bool is_valid() const { return valid; }
+
+ bool get_var(int var) const { return val[2 * var]; }
+};
+
+int main() {
+ int req_num;
+ int topping_num;
+ std::cin >> req_num >> topping_num;
+ vector clauses(req_num);
+ for (Clause &c : clauses) {
+ char neg1, neg2;
+ std::cin >> neg1 >> c.var1 >> neg2 >> c.var2;
+ c.var1--;
+ c.var2--;
+ c.neg1 = neg1 == '-';
+ c.neg2 = neg2 == '-';
+ }
+
+ SATSolver sat(clauses, topping_num);
+ if (!sat.is_valid()) {
+ cout << "IMPOSSIBLE" << endl;
+ } else {
+ for (int t = 0; t < topping_num; t++) {
+ cout << (sat.get_var(t) ? '+' : '-') << ' ';
+ }
+ cout << endl;
+ }
+}
+```
+
+
+
+
+## Problems
+
+
diff --git a/content/6_Advanced/SCC.problems.json b/content/6_Advanced/SCC.problems.json
index 42a19ea33c..10f9e55e3b 100644
--- a/content/6_Advanced/SCC.problems.json
+++ b/content/6_Advanced/SCC.problems.json
@@ -142,5 +142,91 @@
"kind": "none"
}
}
+ ],
+ "2SAT": [
+ {
+ "uniqueId": "cf-1475F",
+ "name": "Unusual Matrix",
+ "url": "https://codeforces.com/contest/1475/problem/F",
+ "source": "CF",
+ "difficulty": "Easy",
+ "isStarred": false,
+ "tags": ["2SAT"],
+ "solutionMetadata": {
+ "kind": "internal"
+ }
+ },
+ {
+ "uniqueId": "cf-776D",
+ "name": "The Door Problem",
+ "url": "https://codeforces.com/contest/776/problem/D",
+ "source": "CF",
+ "difficulty": "Easy",
+ "isStarred": false,
+ "tags": ["2SAT", "DSU", "DFS"],
+ "solutionMetadata": {
+ "kind": "internal"
+ }
+ },
+ {
+ "uniqueId": "cc-HKRMAN",
+ "name": "Hackerman",
+ "url": "https://www.codechef.com/LTIME95A/problems/HKRMAN",
+ "source": "CC",
+ "difficulty": "Easy",
+ "isStarred": false,
+ "tags": ["2SAT", "DSU", "Sliding Window", "Greedy"],
+ "solutionMetadata": {
+ "kind": "internal"
+ }
+ },
+ {
+ "uniqueId": "kattis-illumination",
+ "name": "Illumination",
+ "url": "https://open.kattis.com/problems/illumination",
+ "source": "Kattis",
+ "difficulty": "Easy",
+ "isStarred": false,
+ "tags": ["2SAT"],
+ "solutionMetadata": {
+ "kind": "internal"
+ }
+ },
+ {
+ "uniqueId": "ac-CoprimeSolitaire",
+ "name": "Coprime Solitaire",
+ "url": "https://atcoder.jp/contests/abc210/tasks/abc210_f",
+ "source": "AC",
+ "difficulty": "Normal",
+ "isStarred": true,
+ "tags": ["2SAT"],
+ "solutionMetadata": {
+ "kind": "internal"
+ }
+ },
+ {
+ "uniqueId": "cf-1903F",
+ "name": "Babysitting",
+ "url": "https://codeforces.com/problemset/problem/1903/F",
+ "source": "CF",
+ "difficulty": "Hard",
+ "isStarred": true,
+ "tags": ["2SAT", "Binary Search", "Trees"],
+ "solutionMetadata": {
+ "kind": "internal"
+ }
+ },
+ {
+ "uniqueId": "cf-1089H",
+ "name": "Harder Satisfiability",
+ "url": "https://codeforces.com/problemset/problem/1089/H",
+ "source": "CF",
+ "difficulty": "Hard",
+ "isStarred": true,
+ "tags": ["2SAT", "DFS"],
+ "solutionMetadata": {
+ "kind": "internal"
+ }
+ }
]
}