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" + } + } ] }