├── CMSC451.pdf ├── media ├── prefix.png ├── ufds1.png └── ufds2.png ├── march ├── mar12.tex ├── mar03.tex └── mar05.tex ├── jan ├── jan30.tex └── jan28.tex ├── main.tex ├── feb ├── feb27.tex ├── feb11.tex ├── feb25.tex ├── feb4.tex ├── feb20.tex ├── feb6.tex ├── feb13.tex └── feb18.tex ├── april └── apr7.tex ├── olympiad.asy └── ekesh.sty /CMSC451.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kekesh/CMSC451/HEAD/CMSC451.pdf -------------------------------------------------------------------------------- /media/prefix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kekesh/CMSC451/HEAD/media/prefix.png -------------------------------------------------------------------------------- /media/ufds1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kekesh/CMSC451/HEAD/media/ufds1.png -------------------------------------------------------------------------------- /media/ufds2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kekesh/CMSC451/HEAD/media/ufds2.png -------------------------------------------------------------------------------- /march/mar12.tex: -------------------------------------------------------------------------------- 1 | \newpage 2 | \section{Thursday, March 12, 2020} 3 | 4 | \subsection{All-Pairs Shortest Paths} 5 | 6 | Suppose we are given a weighted, directed graph $G = (V, E)$ with a weight function $w : E \rightarrow \mathbb{R}$ that maps edges to real-valued weights (the mapped values are henceforth referred to as \vocab{edge weights}). Moreover, for any two vertices $u, v\in V$, we define $\delta(u, v)$ as the \vocab{shortest path} (where we minimize the sum of edge weights) between the vertices $u$ and $v$. \\ 7 | 8 | We want to find, for every pair of vertices $u, v \in V$, the value of $\delta(u, v)$. This is known as the \vocab{all-pairs shortest path problem} (ASSP). Note that this differs from the single-source shortest path problem (SSSP) (which Dijkstra's algorithm solves) since SSSP only wants to compute the shortest path from a single source to a single destination. On the other hand, ASSP wants to compute the distances between \textit{all} pairs of vertices. \\ 9 | 10 | We can solve the ASSP by running a single-source shortest path algorithm $|V|$ times (once for each possible source vertex). Using a binary min-heap implementation of the minimum priority queue in Dijkstra's algorithm yields a running time of $\mathcal{O}(VE\log(V))$. However, Dijkstra's algorithm has a disadvantage: it doesn't work for negative edge weights. If $G$ has negative edge weights, then we need to use a different SSSP algorithm, like Bellman-Ford, which would increase our running time to $\mathcal{O}(V^4).$ Thus, we seek to do better. 11 | 12 | 13 | \subsubsection{Floyd-Warshall Algorithm} 14 | 15 | The \vocab{Floyd-Warshall algorithm} solves ASPS by 16 | 17 | \subsubsection{Transitive Closure} 18 | 19 | asdf 20 | -------------------------------------------------------------------------------- /jan/jan30.tex: -------------------------------------------------------------------------------- 1 | \newpage 2 | \section{Thursday, January 30, 2020} 3 | 4 | \subsection{Optimality and Correctness of Gale-Shapley} 5 | 6 | 7 | Last time, we introduced the Gale-Shapley algorithm to find a stable matching. Today, we'll prove that the algorithm is correct (i.e. it never produces an unstable matching), and it is optimal for men (i.e. the men always end up for their preferred choice). \\ 8 | 9 | First, we'll show that the algorithm is correct: \\ 10 | 11 | \begin{proposition} 12 | The matching generated by the Gale-Shapley algorithm is never an unstable matching. 13 | \end{proposition} 14 | 15 | \begin{proof} 16 | Suppose, for the sake of contradiction, that $m$ and $w$ prefer each other over their current partner in the matching generated by the Gale-Shapley algorithm. This can happen either if $m$ never proposed to $w$, or if $m$ proposed to $w$ and $w$ rejected $m$. In the former case, $m$ must prefer his partner to $w$, which implies that $m$ and $w$ do not form an unstable pair. In the latter case, $w$ prefers her partner to $m$, which also implies $m$ and $w$ don't form an unstable pair. Thus, we arrive at a contradiction. 17 | \end{proof} 18 | 19 | Next, we'll prove that the algorithm is optimal for men. However, before presenting the proof, observe that it is not too hard to see intuitively that the algorithm ``favors" the men. Since the men are doing all of the proposing and the women can only do the deciding, it turns out that the men always ends up with their most preferred choice (as long as the matching remains stable). 20 | 21 | \begin{proposition} 22 | The matching generated by the Gale-Shapley algorithm gives men their most preferred woman possible without contradicting stability. 23 | \end{proposition} 24 | 25 | \begin{proof} 26 | \\ 27 | 28 | 29 | 30 | To see why this is true, let $A$ be the matching generated by the men-proposing algorithm, and suppose there exists some other matching $B$ that is better for at least one man, say $m_0$. If $m_0$ is matched in $B$ to $w_1$ which he prefers to his match in $A$, then in $A$, $m_0$ must have proposed to $w_1$ and $w_1$ must have rejected him. This can only happen if $w_1$ rejected him in favor of some other man --- call him $m_2$. This means that in $B$, $w_1$ is matched to $m_0$ but she prefers $m_2$ to $m_0$. Since $B$ is stable, $m_2$ must be matched to some woman that he prefers to $w_1$; say $w_3$. This means that in $A$, $m_2$ proposed to $w_3$ before proposing to $w_1$, and this means that $w_3$ rejected him. Since we can perform similar considerations, we end up tracing a ``cycle of rejections" due to the finiteness of the sets $A$ and $B$. 31 | \end{proof} 32 | 33 | -------------------------------------------------------------------------------- /main.tex: -------------------------------------------------------------------------------- 1 | \documentclass[12pt]{scrartcl} 2 | \usepackage[sexy]{ekesh} 3 | \usepackage[noend]{algpseudocode} 4 | \usepackage{answers} 5 | \usepackage{array} 6 | \usepackage{tikz} 7 | \newenvironment{allintypewriter}{\ttfamily}{\par} 8 | \usepackage{listings} 9 | \usepackage{xcolor} 10 | \usetikzlibrary{arrows.meta} 11 | \usepackage{color} 12 | \usepackage{mathtools} 13 | \newcommand{\U}{\mathcal{U}} 14 | \newcommand{\E}{\mathbb{E}} 15 | \usetikzlibrary{arrows} 16 | \Newassociation{hint}{hintitem}{all-hints} 17 | \renewcommand{\solutionextension}{out} 18 | \renewenvironment{hintitem}[1]{\item[\bfseries #1.]}{} 19 | \renewcommand{\O}{\mathcal{O}} 20 | \declaretheorem[style=thmbluebox,name={Chinese Remainder Theorem}]{CRT} 21 | \renewcommand{\theCRT}{\Alph{CRT}} 22 | \setlength\parindent{0pt} 23 | \usepackage{sansmath} 24 | \usepackage{pgfplots} 25 | \usetikzlibrary{automata} 26 | \usetikzlibrary{positioning} % ...positioning nodes 27 | \usetikzlibrary{arrows} % ...customizing arrows 28 | \newcommand{\eqdef}{=\vcentcolon} 29 | \usepackage[top=3cm,left=3cm,right=3cm,bottom=3cm]{geometry} 30 | \newcommand{\mref}[3][red]{\hypersetup{linkcolor=#1}\cref{#2}{#3}\hypersetup{linkcolor=blue}}%<<,>=stealth', % Makes edges directed with bold arrowheads 41 | auto, 42 | semithick}} 43 | 44 | 45 | % Start of document. 46 | \newcommand{\sep}{\hspace*{.5em}} 47 | 48 | 49 | \begin{document} 50 | \title{Design and Analysis of Algorithms} 51 | \author{Ekesh Kumar\thanks{Email: \mailto{ekumar1@terpmail.umd.edu}}} 52 | \date{\today} 53 | 54 | \definecolor{dkgreen}{rgb}{0,0.6,0} 55 | \definecolor{gray}{rgb}{0.5,0.5,0.5} 56 | \definecolor{mauve}{rgb}{0.58,0,0.82} 57 | 58 | \lstset{frame=tb, 59 | aboveskip=3mm, 60 | belowskip=3mm, 61 | showstringspaces=false, 62 | columns=flexible, 63 | basicstyle={\small\ttfamily}, 64 | numbers=none, 65 | numberstyle=\tiny\color{gray}, 66 | keywordstyle=\color{blue}, 67 | commentstyle=\color{dkgreen}, 68 | stringstyle=\color{mauve}, 69 | breaklines=true, 70 | breakatwhitespace=true, 71 | tabsize=3 72 | } 73 | 74 | \maketitle 75 | These are my course notes for CMSC 451: Design and Analysis of Algorithms, taught by Professor Clyde Kruskal. Gaps in lecture material are filled in with CLRS and Kleinberg \& Tardos. Please send corrections to \url{ekumar1@terpmail.umd.edu}. 76 | 77 | \tableofcontents 78 | 79 | 80 | \newpage 81 | 82 | \input{jan/jan28} 83 | \input{jan/jan30} 84 | 85 | % Feb 86 | \input{feb/feb4} 87 | \input{feb/feb6} 88 | \input{feb/feb11} 89 | \input{feb/feb13} 90 | \input{feb/feb18} 91 | \input{feb/feb20} 92 | \input{feb/feb25} 93 | \input{feb/feb27} 94 | 95 | % March 96 | \input{march/mar03} 97 | \input{march/mar05} 98 | \iffalse 99 | \input{march/mar12} 100 | \fi 101 | % April 102 | \input{april/apr7} 103 | 104 | 105 | \end{document} -------------------------------------------------------------------------------- /feb/feb27.tex: -------------------------------------------------------------------------------- 1 | \newpage 2 | \section{Thursday, February 27, 2020} 3 | Last time, we introduced the matrix multiplication problem whose brute force solution runs in $\O(n^3)$ time. Today, we'll introduce \vocab{Strassen's algorithm}, a quicker solution to the matrix multiplication problem. 4 | 5 | 6 | \subsection{Strassen's Algorithm} 7 | 8 | Suppose we want to compute the matrix product $\mathbf{C} = \mathbf{A}\mathbf{B}$. Strasen's algorithm is a divide-and-conquer algorithm that works by partitioning the three matrices $\mathbf{A}, \mathbf{B}$, and $\mathbf{C}$ into equally sized block matrices as follows: \\ 9 | 10 | \[ 11 | \mathbf{A} = \begin{pmatrix} 12 | \mathbf{A}_{1,1} & \mathbf{A}_{1,2} \\ 13 | \mathbf{A}_{2, 1} & \mathbf{A}_{2,2} 14 | \end{pmatrix} \hspace{1cm} 15 | \mathbf{B} = \begin{pmatrix} 16 | \mathbf{B}_{1,1} & \mathbf{B}_{1,2} \\ 17 | \mathbf{B}_{2, 1} & \mathbf{B}_{2,2} 18 | \end{pmatrix} \hspace{1cm} 19 | \mathbf{C} = \begin{pmatrix} 20 | \mathbf{C}_{1,1} & \mathbf{C}_{1,2} \\ 21 | \mathbf{C}_{2, 1} & \mathbf{C}_{2,2} 22 | \end{pmatrix} 23 | \] 24 | 25 | 26 | Our naive algorithm would compute the following quantities: 27 | 28 | \begin{enumerate} 29 | \item $\mathbf{C}_{1,1} = \mathbf{A}_{1,1} \mathbf{B}_{1,1} + \mathbf{A}_{1,2}\mathbf{B}_{2,1}$, 30 | \item $\mathbf{C}_{1,2} = \mathbf{A}_{1,1} \mathbf{B}_{1,2} + \mathbf{A}_{1,2}\mathbf{B}_{2,2}$, 31 | \item $\mathbf{C}_{2,1} = \mathbf{A}_{2,1} \mathbf{B}_{1,1} + \mathbf{A}_{2,2}\mathbf{B}_{2,1}$, 32 | \item $\mathbf{C}_{2,2} = \mathbf{A}_{2,1} \mathbf{B}_{1,2} + \mathbf{A}_{2,2}\mathbf{B}_{2,2}$, 33 | \end{enumerate} 34 | 35 | 36 | With this construction, however, we require $8$ total multiplications to calculate our matrix. Strassen's algorithm works by cleverly rewriting some of these expressions so that we only require $7$ multiplications (similar to how Karatsuba's algorithm for large-integer multiplication). More precisely, we define the following matrices: 37 | 38 | \begin{enumerate} 39 | \item $\mathbf{M}_{1} \defeq (\mathbf{A}_{1,1} + \mathbf{A}_{2,2})(\mathbf{B}_{1,1} + \mathbf{B}_{2,2})$, 40 | \item $\mathbf{M}_{2} \defeq (\mathbf{A}_{2,1} + \mathbf{A}_{2,2})\mathbf{B}_{1,1}$, 41 | \item $\mathbf{M}_{3} \defeq \mathbf{A}_{1,1}(\mathbf{B}_{1,2} - \mathbf{B}_{2,2})$, 42 | \item $\mathbf{M}_{4} \defeq \mathbf{A}_{2,2}(\mathbf{B}_{2,1} - \mathbf{B}_{1,1})$, 43 | \item $\mathbf{M}_{5} \defeq (\mathbf{A}_{1,1} + \mathbf{A}_{1,2})\mathbf{B}_{2,2}$ 44 | \item $\mathbf{M}_{6} \defeq (\mathbf{A}_{2,1} - \mathbf{A}_{1,1})(\mathbf{B}_{1,1} + \mathbf{B}_{1,2})$, 45 | \item $\mathbf{M}_{7} \defeq (\mathbf{A}_{1,2} - \mathbf{A}_{2,2})(\mathbf{B}_{2,1} + \mathbf{B}_{2,2})$. 46 | \end{enumerate} 47 | 48 | Note that computing the values of these matrices requires only $7$ multiplications (one for each $M_{k})$ instead of the usual $8$. We can now express our block matrices in terms of the $M_{k}$ matrices as follows: 49 | 50 | \begin{enumerate} 51 | \item $\mathbf{C}_{1,1} = \mathbf{M}_{1} + \mathbf{M}_{4} - \mathbf{M}_{5} + \mathbf{M}_{7}$, 52 | \item $\mathbf{C}_{1,2} = \mathbf{M}_{3} + \mathbf{M}_{5}$, 53 | \item $\mathbf{C}_{2,1} = \mathbf{M}_{2} + \mathbf{M}_{4}$ 54 | \item $\mathbf{C}_{2,2} = \mathbf{M}_{1} - \mathbf{M}_{2} + \mathbf{M}_{3} + \mathbf{M}_{6}$. 55 | \end{enumerate} 56 | 57 | 58 | We can iterate the procedure of dividing our matrices into blocks recursively until the submatrices are just numbers. \\ 59 | 60 | How fast does Strassen's algorithm run? Let $f(n)$ denote the number of multiplication operations we perform on a $2^{n}\times 2^{n}$ matrix. By the recursive application of Strassen's algorithm, we find $f(n) = 7f(n - 1) + c4^{n}$, where $c$ is some positive constant that depends on the number of additions performed at each step of the operation. Thus, we find $f(n) = (7 + o(1))^{n}$. Letting $N = 2^{n}$, we conclude that Strassen's algorithm runs in $\mathcal{O}(N^{\log_{2}(7) + o(1)}) \approx \mathcal{O}(N^{2.8074})$ time. \\ 61 | 62 | 63 | \subsection{Closest Pair of Points} 64 | 65 | The closest pair of points problem is stated as follows: 66 | 67 | \begin{quote} 68 | Given $n$ points in the plane $P = \{p_1, p_2, p_3, \ldots, p_n\}$, find two points $p_i$ and $p_j$ such that the Euclidean distance between $p_i$ and $p_j$ is minimal. 69 | \end{quote} 70 | 71 | A simple brute force solution is to consider all ${n\choose 2}$ pairs of points, and keep track of the minimum distance value seen so far (this \verb!minimum! variable would initially be set to $\infty$). The runtime of this algorithm is $\mtahcal{O}(n^2)$ since computing the distance between two points is a constant-time operation. \\ 72 | 73 | Next class, we'll present a more efficient solution. -------------------------------------------------------------------------------- /jan/jan28.tex: -------------------------------------------------------------------------------- 1 | \section{Tuesday, January 28, 2020} 2 | 3 | \subsection{Introduction} 4 | 5 | This is CMSC $451$: Design and Analysis of Algorithms. We will cover graphs, greedy algorithms, divide and conquer algorithms, dynamic programming, network flows, NP-completeness, and approximation algorithms. 6 | 7 | 8 | \begin{itemize} 9 | \item Homeworks are due every other Friday or so; NP-homeworks are typically due every other Wednesday. 10 | \item There is a $25\%$ penalty on late homeworks, and there's one get-out-of-jail free card for each type of homework. 11 | \end{itemize} 12 | 13 | \subsection{Stable Marriage Problem} 14 | 15 | As an introduction to this course, we'll discuss the \vocab{stable marriage problem}, which is stated as follows: 16 | 17 | \begin{quote} 18 | Given a set of $n$ men and $n$ women, match each man with a woman in such a way that the matching is \textit{stable}. 19 | \end{quote} 20 | 21 | What do we mean when we call a matching is ``stable"? We call a matching \textit{unstable} if there exists some man $M$ who prefers a woman $W$ over the woman he is married to, and $W$ also prefers $M$ over the man she is currently married to. \\ 22 | 23 | 24 | 25 | In order to better understand the problem, let's look at the $n = 2$ case. Call the two men $M_1$ and $M_2$, and call the two women $W_1$ and $W_2$. 26 | 27 | \begin{itemize} 28 | \item First suppose $M_1$ prefers $W_1$ over $W_2$ and $W_1$ prefers $M_1$ over $M_2$. Also, suppose that $M_2$ prefers $W_2$ over $W_1$ and $W_2$ prefers $M_2$, then 29 | \item If both $W_1$ and $W_2$ prefer $M_1$ over $M_2$, and both $M_1$ and $M_2$ prefer $W_1$ over $W_2$, then it's still easy to see what will happen: $M_i$ will always match with $W_i$. 30 | \item Now let's say $M_1$ prefers $W_1$ to $W_2$, $M_2$ prefers $W_2$ to $W_1$, $W_1$ prefers $M_2$ to $M_1$, and $W_2$ prefers $M_1$ to $M_2$. In this case, the two men rank different women first, and the two women rank different men first. However, the men's preferences ``clash" with the women's preferences. One solution to this problem is to match $M_1$ with $W_1$ and $M_2$ with $W_2$. This is stable since both men get their top preference even though the two women are unhappy. 31 | \end{itemize} 32 | 33 | The solution to the problem starts to get a lot more complicated when the people's preferences do not exhibit any pattern. So how do we solve this problem in the general case? We can use the \vocab{Gale-Shapley algorithm}. Before discussing this algorithm, however, we can make the following observations about this problem: 34 | 35 | \begin{itemize} 36 | \item Each of the $n$ men and $M$ woman are initially unmarried. If an unmarried man $M$ chooses the woman $W$ who is ranked highest on their list, then we cannot immediately conclude whether we can match $M$ and $w$ in our final matching.This is clearly the case since if we later find out about some other man $M_2$ who prefers $W$ over any other woman, $W$ may choose $M_2$ if she likes him more than $M$. However, we cannot immediately rule out $M$ being matched to $W$ either since a man like $M_2$ may not ever come. 37 | \item Just because everyone isn't happy doesn't mean a matching isn't stable. Some people might be unhappy, but there might not be anything they can do about it (if nobody wants to switch). 38 | \end{itemize} 39 | 40 | 41 | Moreover, we introduce the notion of a man \textit{proposing} to a woman, which a woman can either accept or reject. If she is already engaged and accepts a proposal, then her existing engagement breaks off (the previous man becomes unengaged). \\ 42 | 43 | 44 | Now that we've introduced these basic ideas, we can now present the algorithm: 45 | 46 | 47 | \vspace{1em} 48 | \hline 49 | \vspace{1em} 50 | 51 | \begin{allintypewriter} 52 | \# Input: A list of n men and n women to be matched. 53 | 54 | \hspace{0.5cm} 55 | 56 | \# Output: A valid stable matching. 57 | 58 | 59 | \hspace{0.5cm} 60 | 61 | 62 | stable\_matching \string{ 63 | 64 | \hspace{0.5cm} set each man and each woman to "free" 65 | 66 | \hspace{0.5cm} while there exists a man m who still has a woman w to propose to \string{ 67 | 68 | \hspace{1cm} let w be the highest ranked woman m hasn't proposed to. 69 | 70 | \hspace{0cm} 71 | 72 | \hspace{1cm} if w is free \string{ 73 | 74 | \hspace{1.5cm} (m, w) become engaged 75 | 76 | \hspace{1cm} \string} else \string{ 77 | 78 | \hspace{1.5cm} let m' be the man w is currently engaged to. 79 | 80 | \hspace{1.5cm} if w prefers m' to m \string{ 81 | 82 | \hspace{2cm} (m', w) remain engaged. 83 | 84 | \hspace{1.5cm} \string} else \string{ 85 | 86 | \hspace{2cm} (m, w) become engaged and m' loses his partner. 87 | 88 | \hspace{1.5cm} \string} 89 | 90 | \hspace{1cm} \string} 91 | 92 | \hspace{0.5cm} \string} 93 | 94 | \string} 95 | 96 | \end{allintypewriter} 97 | \vspace{1em} 98 | \hline 99 | \vspace{1em} 100 | 101 | 102 | \begin{proposition} 103 | The Gale-Shapley algorithm terminates in $\mathcal{O}(n^2)$ time. 104 | \end{proposition} 105 | \begin{proof} 106 | In the worst case, $n$ men end up proposing to $n$ women. The act of proposing to another person is a constant-time operation. Thus, the $\mathcal{O}(n^2)$ runtime is clear. 107 | \end{proof}% -------------------------------------------------------------------------------- /april/apr7.tex: -------------------------------------------------------------------------------- 1 | \newpage 2 | \section{Tuesday, April 7, 2020} 3 | 4 | \subsection{Subset Sum Problem} 5 | 6 | Today, we'll discuss another classical dynamic programming problem, known as the \vocab{subset sum problem}.\footnote{The subset sum problem is a special case of another classical dynamic programming problem, known as the \vocab{knapsack problem}. In the knapsack problem, each item $1 \leq i \leq n$ has a value $v_i$ and weight $w_i$. For each item, we want to assign a number $x_i \in \{0, 1\}$ so that $\sum_{i}x_iv_i$ is maximized subject to $\sum_{i} x_i w_i \leq W$. } The subset problem is stated as follows: 7 | 8 | \begin{quote} 9 | Suppose we are given $n$ items $\{1, 2, \ldots, n\}$ each with nonnegative weight $w_i$. We are also given a bound $W$. How do we select a subset $S$ of the items so that $\sum_{i\in S} w_i$ is maximized subject to $\sum_{i\in S} w_i \leq W$? 10 | \end{quote} 11 | 12 | In other words, given $n$ items each with nonnegative weights, what's the closest we can get to a weight of $W$ without going over? \\ 13 | 14 | \begin{example} 15 | [Subset Sum Example] Suppose $n = 3$ with $w_1 = 2$, $w_2 = 3$, $w_3 = 4$, and $W = 5$. The solution to this instance of the subset problem is $\boxed{5}$ --- it is optimal to choose $w_1$ and $w_2$. 16 | \label{greedy:01} 17 | \end{example} 18 | 19 | Does a greedy solution work? One greedy rule might be to sort the items in ascending order by weight and always pick the item with maximal weight that hasn't been taken yet. However, this greedy rule fails in \Cref{greedy:01}; we'll end up with a total weight of $4$, which is sub-optimal. \\ 20 | 21 | We demonstrate how we can use dynamic programming to solve this problem. Recall that the main principles of dynamic programming are to come up with a recurrence so that we can relate the problem we want to solve to ``smaller" subproblems. The tricky issue is determining what a good set of subproblems consists of. \\ 22 | 23 | One general strategy in dynamic programming is to consider subproblems consisting of only the first $i$ ``requests," or items. We can use this strategy here. Formally, let $\OPT(i)$ denote the best possible solution using only the subset $\{1, \ldots, i\}$ of the original set of items. Now, the key to this problem is to concentrate on an optimal solution and consider two different cases, depending on whether or not the last item $n$ we processed is part of this optimum solution or not. Let $\mathcal{O}$ denote an optimal solution.\\ 24 | 25 | 26 | If $n \not \in \mathcal{O}$, then $\OPT(n) = \OPT(n - 1)$. This is obvious --- if the last item we processed isn't a part of our optimal solution, then the optimal solution using only $\{1, 2, \ldots, n - 1\}$ shouldn't change when $n$ is included (we now have the option to take $n$, but we don't want $n$ anyways!). \\ 27 | 28 | 29 | The only other case we have to consider is the case in which $n \in \mathcal{O}$. What we need to find is a simple recursion that tells us the best possible value we can obtain for solutions containing the last request $n$. Note that accepting request $n$ does not immediately imply that we have to reject any other requests. Instead, it means that for the subset of requests $S \subseteq \{1, 2, \ldots, n - 1\}$ that we will accept, we have less available weight left. More precisely, we will have $W - w_n$ weight left for the remaining set of items we accept. \\ 30 | 31 | 32 | This suggests that we need more subproblems: we cannot just use the value $\OPT(n - 1)$ when we're including item $n$ since the combined weight of the items in $\OPT(n - 1)$ and item $n$ might exceed $W$. What we precisely need is the best solution using the first $n - 1$ items when the total weight allowed is $W - w_n$. Thus, we require many more subproblems: one for each initial set $\{1, 2, \ldots, i\}$ of the items, and each possible value for the remaining weight available $w$. \\ 33 | 34 | More precisely, if each of our items $1, 2, \ldots, n$ have integer weights $w_i$ and our maximum weight bound is $W$, then we have a subproblem for each $i = 0, 1, \ldots, n$ and each integer $0 \leq w \leq W.$ We henceforth use $\OPT(i, w)$ to denote the value of the optimal solution using the subset of the items $\{1, 2, \ldots, i\}$ with maximum allowed weight $w$. That is, 35 | 36 | \[ 37 | \OPT(i, w) = \max_{S \subseteq \{1, 2, \ldots, i\}} \sum_{j \in S} w_j \hspace{1em} \text{subject to } \sum_{j \in S} w_j \leq w. 38 | \] 39 | 40 | Using this new set of subproblems, we can note that the final answer we want is $\OPT(n, W)$. Moreover, we can express the value $\OPT(i, w)$ as an expression from smaller problems. These results are summarized below: 41 | 42 | \begin{enumerate} 43 | \item If $n \not \in \O$, then $\OPT(n, W) = \OPT(n - 1, W)$ since we can ignore the item $n$. 44 | \item If $n\in \O$, then $\OPT(n, W) = w_n + \OPT(n - 1, W - w_n)$ since we now want to use the remaining capacity $W - w_n$ in an optimal way across the first $n - 1$ items. 45 | \end{enumerate} 46 | 47 | If $W < w_n$ for some item $n$, then we require $\OPT(n, W) = \OPT(n - 1, W)$ since we aren't allowed to take item $n$ due to our constraint. Now that we've considered both cases, we can get the optimum solution by simply taking the better of these two options. Therefore, we obtain the following recurrence: 48 | 49 | \[ 50 | \OPT(i, W) = \begin{cases} 51 | \max(\OPT(i - 1, w), w_i + \OPT(i - 1, W - w_i)) & \text{ if } w_i \leq W \\[1em] 52 | \OPT(i - 1, w) & \text{ otherwise.} 53 | \end{cases} 54 | \] 55 | 56 | Also, we have the base cases $\OPT(i, w) = 0$ provided that $i = 0$ since we aren't allowed to take any items when $i = 0$. \\ 57 | 58 | With our recurrence and base cases established, we are done. -------------------------------------------------------------------------------- /feb/feb11.tex: -------------------------------------------------------------------------------- 1 | \newpage 2 | \section{Tuesday, February 11, 2020} 3 | 4 | \subsection{Articulation Point Algorithm Implementation} 5 | 6 | Last time, we introduced the algorithm to find articulation points. Recall that if there's a vertex $u$ with neighbor $v$ satisfying \verb!dfs_low(v) >= dfs_num(u)!, then vertex $u$ is an articulation point. \\ 7 | 8 | In terms of the depth-first search tree, the quantity \verb!dfs_low(v)! is the lowest value that you can reach by going down the depth-first search tree rooted at $v$ and possibly taking a back edge up (we can't visit the immediate parent of $v$). The inequality \verb!dfs_low(v) >= dfs_num(u)! implies that we cannot visit any vertex with \verb!dfs_num! less than \verb!dfs_num(u)! when we start a depth-first search from $v$ (there aren't any back edges that go to a vertex visited before vertex $u$). \\ 9 | 10 | Furthermore, recall that the root of the depth-first search tree is an exception --- this vertex is an articulation point only if it has more than one child. \\ 11 | 12 | When actually implementing this algorithm, we need to be clever in order to maintain a linear time complexity. A pseudocode implementation is provided at \url{http://www.cs.umd.edu/class/spring2020/cmsc451/biconnected.pdf}. \\ 13 | 14 | 15 | \subsection{Strongly Connected Components} 16 | 17 | 18 | Recall that an undirected graph $G = (V, E)$ is called \vocab{connected} provided that for any pair of vertices $u, v \in V$, there exits a path between $u$ and $v$. \\ 19 | 20 | The corresponding analogue for connectivity in a directed graph is presented below: 21 | 22 | \begin{definition} 23 | We call a \textit{directed} graph \vocab{strongly connected} if, for every pair of vertices $u, v\in V$, there exists a directed path $u\leadsto p$. 24 | \end{definition} 25 | 26 | We're often interested in checking whether or not a graph is strongly connected (e.g. starting from \textit{anywhere} in a directed graph, is it possible to reach \textit{everywhere} else?). \\ 27 | 28 | 29 | 30 | Like connected components in an undirected graph, strongly connected components in a directed graph form a partition of the set of vertices. This is formalized through the following result: 31 | 32 | \begin{lemma} 33 | [Klekleinberg and Tardos, 3.17] 34 | For any two nodes $s$ and $t$ in a directed graph, their strong components are either identical or disjoint. 35 | \end{lemma} 36 | 37 | \begin{proof} 38 | Consider any two nodes $s$ and $t$ that are mutually reachable. We claim that the strong components containing $s$ and $t$ are identical. This is clearly true due to the definition of a strongly connected component --- for any node $v$, if $s$ and $v$ are mutually reachable, then $t$ and $v$ are mutually reachable as well (we can always go $s\leadsto t \leadsto v$). Similarly, if $t$ and $v$ are mutually reachable, then $s$ and $v$ must be mutually reachable as well. \\ 39 | 40 | Conversely, suppose $s$ and $t$ are \textit{not} mutually reachable. Then there cannot be a node $v$ in the strong component of both $s$ and $t$. Suppose such a node $v$ existed. Then $s$ and $v$ would be mutually reachable, and $v$ and $t$ would be mutually reachable. But this would imply that $s$ and $t$ are mutually reachable, which is a contradiction. 41 | \end{proof} 42 | 43 | A brute force algorithm to check whether a grpah is strongly connected is presented below: 44 | 45 | \begin{enumerate} 46 | \item For each vertex $v \in V$ in our input graph $G = (V, E)$, perform a depth-first search starting with vertex $v$. 47 | \item If there exists some vertex $u$ that we cannot from a vertex $v$, then we can conclude that $G$ is not strongly connected. 48 | \item If we finish iterating over all vertices with no issues, we can conclude that our graph is strongly connected. 49 | \end{enumerate} 50 | 51 | Since we perform $\mathcal{O}(V)$ depth-first search calls in the algorithm above, the runtime of this algorithm runs in $\mathcal{O}(V \times (V + E)) = \mathcal{O}(V^2 + VE)$ time on an Adjacency List. However, this is not as efficient as we can get. \\ 52 | 53 | 54 | It turns out that we can solve the problem of determining whether a graph is strongly connected in linear time using two depth-first search calls. Before presenting this algorithm, we'll need the following terminology: 55 | 56 | \begin{definition} 57 | Given a directed graph $G = (V, E)$, the \vocab{transpose graph} of $G$ is the directed graph $G^{T}$ obtained by reversing the orientation of each edge from $(u, v)$ to $(v, u)$. 58 | \end{definition} 59 | 60 | 61 | A summary of Kosaraju's algorithm is presented below: 62 | 63 | \begin{enumerate} 64 | \item Pick an arbitrary vertex $v \in V$ in our initial graph $G = (V, E)$. 65 | \item Perform a depth-first search from $v$ and verify that every other vertex in the graph can be reached from $v$. If there exists some vertex $u$ that cannot be reached from $v$, then we can immediately conclude that $G$ is not strongly connected. 66 | \item Compute $G^{T}$, the transpose graph of $G^{T}$. Perform a depth-first search on $G^{T}$ with the same source vertex $v$. If we can reach every vertex from $v$ in $G^{T}$ as well, then we can conclude that $G$ is strongly connected. 67 | \end{enumerate} 68 | 69 | 70 | Why does this work? Because a graph and its transpose always have the same connected components (for each directed $u\leadsto v$ path, we can just go in the reverse direction). \\ 71 | 72 | 73 | 74 | Now, this algorithm tells us \textit{if} a graph is strongly connected; however, it doesn't tell us \textit{what} the strongly connected components are (i.e. if a graph has many strongly connected components, which component does an arbitrary vertex $v$ belong in?). To answer this question, we'll first present a way to classify the edges in a depth-first search tree. \\ 75 | 76 | 77 | We will see that this edge-classification system is very closely related to finding strongly connected components in a graph. 78 | 79 | \subsection{Classifying Edges in a DFS Tree} 80 | 81 | While performing a depth-first search traversal, we generate a depth-first search spanning tree. In particular, this DFS tree's root is the source vertex from which we started the DFS traversal, and we add the edge $(u, v)$ if we traverse the edge $(u, v)$ during the DFS procedure. \\ 82 | 83 | Within the depth-first search tree, we can classify each edge into exactly one of four disjoint categories: 84 | 85 | \begin{enumerate} 86 | \item \vocab{Tree edges} are edges traversed by the depth-first search traversal (i.e. they are neighbors in the original graph, and we go from one of the vertices to the other). These are the only type of edges that are actually explored. 87 | \item \vocab{Back edges} are edges that are part of a cycle in the original graph. In particular, a back edge is an edge $(u, v)$ that we discover when we have started (but not finished) a DFS traversal from $v$ and we're exploring the neighbors of vertex $u$. 88 | \item \vocab{Forward edges} and \vocab{cross edges} are edges of the form $(u, v)$ where we have started (but not finished) the depth-first search traversal from $u$, and we find a vertex $v$ that has already been fully explored. 89 | \end{enumerate} 90 | -------------------------------------------------------------------------------- /feb/feb25.tex: -------------------------------------------------------------------------------- 1 | \newpage 2 | \section{Tuesday, February 25, 2020} 3 | 4 | 5 | \subsection{Prefix Codes} 6 | 7 | One particular class of encoding schemes are \vocab{prefix codes}. A prefix code for a set $S$ of letters is a function $\gamma$ that maps each letter $x\in S$ to some sequence of zeros and ones in such a way that for any $x, y \in S$ with $x \neq y$, the sequence $\gamma(x)$ is not a prefix of the sequence $\gamma(y)$. Why do many encoding schemes fall into this class? Because it removes ambiguity: --- if there exists a pair of letters where the bit string that encodes one letter is a prefix of the bit string that encodes the other, then there might be multiple interpretations of the same string. \\ 8 | 9 | The ambiguity of encoding schemes that aren't prefix codes is demonstrated through the following example: 10 | 11 | \begin{example} 12 | [Ambiguity Morse Code] In Morse code, we typically encode letters with dashes and dots. For our purpose, we can think of dots and dashes as zeros and ones. Suppose $e$ maps to $0$ (a single dot), $t$ maps to $1$, and $a$ maps to $01$. Then the string $0101$ can have several interpretations: it can mean $eta, aa, etet$, or $aet$. If the morse code were a prefix code, then this problem wouldn't be present. 13 | \end{example} 14 | 15 | 16 | Now, here's an example illustrating the ease of using a prefix code: 17 | 18 | \begin{example} 19 | [Prefix Code Example] Suppose we have a set $S = \{a, b, c, d, e\}$ with the encoding $\gamma(a) = 11, \gamma(b) = 01, \gamma(c) = 001, \gamma(d) = 10, \gamma(e) = 000$. This defines a prefix code since no encoding is a prefix of any other. The string $cecab$ is encoded as $0010000011101$, and a recipient of this message can decipher this message to our single unique message. 20 | \end{example} 21 | 22 | In order to efficiently decipher a prefix code, we need an effective way to represent the prefix code so that we can easily pick off the codeword. This is typically done with a binary tree in which the leaves of the tree store the characters of our alphabet. How does this work? We interpret the binary codeword for a character as a simple path from the root to that character; the bit $0$ tells us to go to the left child, whereas the bit $1$ tells us to go to the right child. \\ 23 | 24 | The following binary search tree illustrates a prefix code representation: 25 | 26 | \begin{figure}[h] 27 | \centering 28 | \includegraphics[scale=1]{media/prefix} 29 | \caption{Prefix Code Representation} 30 | \end{figure} 31 | 32 | If we had the sequence $001011101$, then we could start at the root, and we'd scan our sequence from left to right. First, we counter two zeros, so we go to the left twice. At this point, we'd be at the vertex labelled $58$. Next, we encounter a $1$, so we go to the right. Thus, we obtain the character $b$. Next, we start at the root again, and we follow our procedure again. The next character that we decipher is $d$. This process continues until there are no more bits to process. \\ 33 | 34 | 35 | Given a tree $T$ corresponding to a prefix code, we can now easily compute the number of bits required to encode a file. In particular, for each character $c$ in our alphabet $S$, we can let $\verb!freq[c]!$ denote the frequency of $c$ in our file. Moreover, we can let $d_{T}(c)$ denote the depth of $c$'s leaf in the tree. With this notation, the number of bits required to encode a file is given by 36 | \[ 37 | \sum_{c \in S} \verb!freq[c]! \cdot d_{T}(c). 38 | \] 39 | 40 | We call this the \vocab{cost} of the tree $T$. 41 | 42 | \subsubsection{Constructing a Huffman code} 43 | 44 | Now that we've introduced prefix codes, we'll talk about an optimal prefix code known as a \vocab{Huffman code}, whose tree representation has minimum cost. The algorithm constructing the tree is presented below: \\ 45 | 46 | \newpage 47 | 48 | \vspace{1em} 49 | \begin{center} 50 | \line(1,0){400} 51 | \end{center} 52 | 53 | \begin{allintypewriter} 54 | \# Input: A set C representing the set of all possible characters that 55 | 56 | \# might appear in our text, and an array freq[] in which freq[k] represents 57 | 58 | \# the frequency of the character k in our text. 59 | 60 | \# Output: The root of a binary representing our encoding minimum cost. 61 | 62 | \hspace{0cm} 63 | 64 | HUFFMAN(C, freq) \string{ 65 | 66 | \hspace{0.5cm} let Q be a minimum priority queue 67 | 68 | \hspace{0.5cm} for each element c in C \string{ enqueue c into Q \string } 69 | 70 | \hspace{0cm} 71 | 72 | \hspace{0.5cm} for i = 1 to n - 1 \string{ 73 | 74 | \hspace{1cm} let z be a new node 75 | 76 | \hspace{1cm} x = EXTRACT-MIN(Q) 77 | 78 | \hspace{1cm} y = EXTRACT-MIN(Q) 79 | 80 | \hspace{1cm} z.left = x 81 | 82 | \hspace{1cm} z.right = y 83 | 84 | \hspace{1cm} \verb!freq[z] = freq[x] + freq[y]! 85 | 86 | \hspace{1cm} insert z into Q. 87 | 88 | \hspace{0.5cm} \string} 89 | 90 | \hspace{0.5cm} return EXTRACT-MIN(Q) /* Return the root of the tree. */ 91 | 92 | \string} 93 | 94 | \begin{center} 95 | \line(1,0){400} 96 | \end{center} 97 | \end{allintypewriter} 98 | 99 | How does this algorithm work? 100 | 101 | \begin{enumerate} 102 | \item Firstly, we enqueue all of the characters in $C$ into our minimum priorty queue $Q$. 103 | \item The for-loop repeatedly extracts the two vertices with the lowest frequency and replaces them in the queue with a new node representing their ``merger" (parent). The frequency of $z$ is the sum of the frequencies of $x$ and $y$. 104 | \item After $n - 1$ merges, there's only one node left in the queue, which is the root of the code tree. 105 | \end{enumerate} 106 | 107 | If we're using a binary heap, then the algorithm runs in $\mathcal{O}(n\log(n))$ time since we perform $\mathcal{O}(n)$ calls to \verb!EXTRACT-MIN!, which is an $\mathcal{O}(\log(n))$ operation. 108 | 109 | 110 | While we won't show it, it can be shown that this construction of a tree is optimal. This procedure counts as a greedy algorithm since, at each step, we greedily extract the characters with the lowest frequency. 111 | 112 | 113 | \subsection{Matrix Multiplication} 114 | 115 | The next problem we'll discuss is stated as follows: 116 | 117 | \begin{quote} 118 | Given two $n\times n$ matrices $A$ and $B$, compute the $n \times n$ matrix $C$ whose $(i, j)^{\text{th}}$ entry is defined by $c_{ij} = \sum_{k=1}^{n}a_{ik} b_{kj}$. In other words, we want to compute the product $C = AB$. 119 | \end{quote} 120 | 121 | The brute force solution is $\mathcal{O}(n^3)$. In this algorithm, we just use three loops, and we compute each value $c_{ij}$ in $C$ as the summation provided in the problem statement. Of course, we want to do better. \\ 122 | 123 | Another idea is to perform a divide-and-conquer technique on the matrix. In particular, we can divide the matrix into four submatrices (top left corner, top right corner, bottom left corner, bottom right corner), and we can calculate the products recursively. The time complexity of this algorithm is given by the recurrence $T(n) = 8T(n/2) + \mathcal{O}(n^2)$. Unfortunately, by Master's Theorem, we know that the solution to this recurrence will be $\mathcal{O}(n^3)$, which isn't any better. -------------------------------------------------------------------------------- /feb/feb4.tex: -------------------------------------------------------------------------------- 1 | \section{Tuesday, February 4, 2020} 2 | 3 | Today, we'll recap graph terminology and elementary graph algorithms. 4 | 5 | \subsection{Graph Terminology} 6 | 7 | \begin{definition} 8 | A \vocab{graph} $G = (V, E)$ is defined by a set of vertices $V$ and a set of edges $E$. 9 | \end{definition} 10 | 11 | The number of vertices in the graph, $|V|$, is the \vocab{order} of the graph, and the number of edges in the graph, $|E|$, is the \vocab{size} of the graph. Typically, we reserve the letter $n$ for the order of a graph, and we reserve $m$ for the size of a graph. 12 | \begin{definition} 13 | We say a graph is \vocab{directed} if its edges can only be traversed in one direction. Otherwise, we say the graph is \vocab{undirected}. 14 | \end{definition} 15 | 16 | 17 | \begin{definition} 18 | A graph is called \vocab{simple} if it's an undirected graph without any loops (edges that start and end at the same vertex). 19 | \end{definition} 20 | 21 | \begin{definition} 22 | A graph is \vocab{connected} if for every pair of vertices $u, v$, there exists a path between $u$ and $v$. 23 | \end{definition} 24 | 25 | 26 | \subsection{Graph Representations} 27 | 28 | There are two primary ways in which we can represent graphs: \vocab{adjacency matrices} and \vocab{adjacency lists}. \\ 29 | 30 | 31 | An adjacency matrix is an $n\times n$ matrix \verb!A! in which \verb!A[u][v]! is equal to $1$ if the edge $(u, v)$ exists in the graph; otherwise, \verb!A[u][v]! is equal to $0$. Note that the adjacency matrix is symmetric if and only if the graph is undirected. \\ 32 | 33 | On the other hand, an adjacency list is a list of $|V|$ lists, one for each vertex. For each vertex $u \in V$, the adjacency list \verb!Adj[u]! contains all vertices $v$ for which there exists an edge $(u, v)$ in $E$. In other words, \verb!Adj[u]! contains all of the vertices adjacent to $u$ in $G$. 34 | 35 | 36 | Each graph representation has its advantages and disadvantages in terms of runtime. This is summarized by the table below. 37 | 38 | \begin{figure}[h] 39 | \centering 40 | \begin{tabular}{ | m{2cm} | m{4cm}| m{4cm} | } 41 | \hline 42 | & \textsc{Adjacency List} & \textsc{Adjacency Matrix} \\ 43 | \hline 44 | Storage & $\mathcal{O}(n + m)$ & $\O(n^2)$ \\ 45 | \hline 46 | Add vertex & $\mathcal{O}(1)$ & $\O(n^2)$ \\ 47 | \hline 48 | Add edge & $\mathcal{O}(1)$ & $\O(1)$ \\ 49 | \hline 50 | Remove vertex & $\mathcal{O}(n + m)$ & $\O(n^2)$ \\ 51 | \hline 52 | Remove edge & $\mathcal{O}(m)$ & $\O(1)$ \\ 53 | \hline 54 | \end{tabular} 55 | \caption{Adjacency Matrix vs Adjacency List} 56 | \end{figure} 57 | 58 | An explanation of these runtimes are provided below: 59 | 60 | \begin{itemize} 61 | \item An adjacency list requires $\O(n + m)$ since there are $n$ lists inside of the adjacency list. Now for each vertex $v_i$, there are $\text{deg}(v_i)$ vertices in the $i^{\text{th}}$ adjacency list. Since $\sum_{i} \text{deg}(v_i) = \O(m)$, we conclude that the adjacency list representation of a graph requires $\O(n + m)$ space. On the other hand, the adjacency matrix representation of a graph requires $\mathcal{O}(n^2)$ space since we are storing an $n\times n$ matrix. 62 | \item We can add a vertex in constant time in an adjacency list by simply inserting a new list into the adjacency list. On the other hand, to insert a new vertex in an adjacency matrix, we need to increase the dimensions of the adjacency matrix from $n\times n$ to $(n + 1) \times (n + 1)$. This requires $\mathcal{O}(n^2)$ time since we need to copy over the old matrix to a new matrix. 63 | \item We can insert an edge $(u, v)$ into an adjacency list in constant time by simply appending $v$ to the end of $u$'s adjacency list (and $u$ to the end of $v$'s adjacency list if the graph is undirected). Similarly, we can insert an edge in an adjacency matrix in constant time by setting \verb!A[u][v]! to $1$ (and also seting \verb!A[v][u]! to $1$ if the graph is undirected). 64 | \item Removing a vertex requires $\mathcal{O}(n + m)$ time in an adjacency list since we need to traverse the entire adjacency list and remove any incoming our outgoing edges to the vertex being removed. Similarly, this operation takes $\O(n^2)$ time in an adjacency matrix since we need to traverse the entire matrix to remove incoming and outgoing edges. 65 | \item Removing an edge $(u, v)$ requires $\mathcal{O}(m)$ time in an adjacency matrix since we only need to search the adjacency lists of $u$ and $v$ (in the worst case, these vertices have all $m$ edges in their adjacency list). On the other hand, this operation takes constant time in an adjacency matrix since we're just setting \verb!A[u][v]! to $0$. 66 | \end{itemize} 67 | 68 | 69 | \subsection{Graph Traversal} 70 | 71 | Before discussing recapping the two primary types of graph traversal, we will introduce some more terminology. \\ 72 | 73 | \begin{definition} 74 | A \vocab{connected component} of a graph is a maximially connected subgraph of $G$. Each vertex belongs to one connected component as does each edge. 75 | \end{definition} 76 | 77 | There are two primary ways in which we can traverse graphs: using \vocab{breadth-first search} or \vocab{depth-first search}. These two methods of graph traversal are very similar, and they allow us to explore every vertex in a connected components of a graph. 78 | 79 | 80 | \begin{enumerate} 81 | \item Breadth-first search starts at some source vertex $v$ and all vertices with distance $k$ away from $v$ before visiting vertices with distance $k + 1$ from $v$. This algorithm is typically implemented using a queue, and it can be used to find the shortest path (measured by the number of edges) from the source vertex. 82 | \item Depth-first search starts from a source vertex and keeps on going outward until we cannot proceed any further. We must subsequently backtrack and begin performing the depth-first search algorithm again. This algorithm is typically implemented using a stack, whether it be the data structure or the function call stack. 83 | \end{enumerate} 84 | 85 | Both of these algorithms run in $\O(n^2)$ time on an adjacency matrix and $\O(n + m)$ time on an adjacency list. \\ 86 | 87 | Since breadth-first search and depth-first search are guaranteed to visit all of the vertices in the same connected component as the starting vertex, we can easily write an algorithm that counts the number of connected components in a graph. \\ 88 | 89 | Some C++ code is provided below. 90 | 91 | \lstset{frame=tb, 92 | aboveskip=3mm, 93 | belowskip=3mm, 94 | showstringspaces=false, 95 | columns=flexible, 96 | basicstyle={\small\ttfamily}, 97 | numbers=none, 98 | language=C++, 99 | numberstyle=\tiny\color{gray}, 100 | keywordstyle=\color{blue}, 101 | commentstyle=\color{dkgreen}, 102 | stringstyle=\color{mauve}, 103 | breaklines=true, 104 | breakatwhitespace=true, 105 | tabsize=3 106 | } 107 | 108 | \begin{lstlisting} 109 | /* visited[] is a global Boolean array. */ 110 | /* AdjList is a global vector of vectors. */ 111 | void dfs(int v) { 112 | visited[v] = true; 113 | for (int i = 0; i < AdjList[v].size(); i++) { 114 | int u = AdjList[v][i]; 115 | if (!visited[u]) { 116 | dfs(v); 117 | } 118 | } 119 | } 120 | 121 | int main(void) { 122 | /* Assume AdjList and other variables have been declared. */ 123 | int numCC = 0; 124 | for (int i = 0; i < num_vertices; i++) { 125 | if (!visited[i]) { 126 | numCC = numCC + 1; 127 | dfs(i); 128 | } 129 | } 130 | } 131 | \end{lstlisting} -------------------------------------------------------------------------------- /olympiad.asy: -------------------------------------------------------------------------------- 1 | 2 | /////////////////////////////////////////////////// 3 | // Olympiad Asymptote Package 4 | // By Maria Monks and AoPS community 5 | // Last Updated: 08/26/2007 6 | ///////////////////////////////////////////////////// 7 | 8 | // This package contains many constructions and calculations 9 | // that often come up in Olympiad-level Geometry problems. 10 | // 11 | 12 | include graph; 13 | include math; 14 | real markscalefactor=0.03; 15 | 16 | /////////////////////////////////////////// 17 | // USEFUL POINTS 18 | /////////////////////////////////////////// 19 | 20 | // Substitutes origin for (0,0) for ease of notation: 21 | pair origin; 22 | origin=(0,0); 23 | 24 | // The point r of the way along path p with respect to arc length, where r is a real value between 0 and 1 inclusive: 25 | pair waypoint(path p, real r) 26 | { 27 | return point(p,reltime(p,r)); 28 | } 29 | 30 | // The midpoint of path p: 31 | pair midpoint(path p){ return waypoint(p,.5);} 32 | 33 | // The foot of the perpendicular from P to line AB: 34 | pair foot(pair P,pair A, pair B) 35 | { 36 | real s; 37 | s=dot(P-A,unit(B-A)); 38 | return (scale(s)*unit(B-A)+A); 39 | } 40 | 41 | // The point on the angle bisector of 0) 249 | { 250 | direct=unit(dir(g,arctime(g,r*l))); 251 | startpt=r*l-(n-1)/2*space; 252 | for (int i=0; i A[j]! hold. 73 | \end{definition} 74 | 75 | 76 | 77 | 78 | The inversion problem is stated as follows: 79 | 80 | \begin{quote} 81 | Given an array \verb!A!, count the number of inversions in \verb!A!. 82 | \end{quote} 83 | 84 | We can think of the number of inversion in an array as the number of ``bubble sort swaps" (swap between pairs of consecutive items) needed in order to sort the array. 85 | 86 | \begin{example} 87 | [Reverse-Sorted Array] 88 | The array \verb!A = [3, 2, 1]! has exactly $3$ inversions. Namely, $(1, 2), (1, 3)$ and $(2, 3)$. 89 | \end{example} 90 | 91 | \begin{example} 92 | [Unsorted Array] 93 | The array \verb!A = [3, 2, 1, 4]! also has $3$ inversions. 94 | \end{example} 95 | 96 | The most obvious solution is to simply use two nested for-loops and increment an \verb!answer! variable every time we find an inversion. Here's an implementation of the brute force solution: 97 | 98 | \begin{lstlisting} 99 | int main(void) { 100 | /* Assume we have initialized an array "A" */ 101 | int answer = 0; 102 | for (int i = 0; i < N; i++) { 103 | for (int j = i + 1; j < N; j++) { 104 | /* The condition i < j is always true. */ 105 | if (A[i] > A[j]) { 106 | /* (i, j) is an inversion. */ 107 | answer = answer + 1; 108 | } 109 | } 110 | } 111 | cout << "Number of inversions: " << answer << endl; 112 | } 113 | \end{lstlisting} 114 | 115 | The runtime of this algorithm is $\mathcal{O}(n^2)$, but of course, we want to do better. \\ 116 | 117 | Once again, we can take a divide and conquer approach for the inversion problem. More precisely, we can just modify the \verb!MergeSort! algorithm. The key observation is that during the \verb!merge! process of merge sort, if the frnot of the right sorted sublist is taken rather than the front of the left sorted sublist, then we can say that one or more inversions occur. We increment our inversion counter by the size of the current left sublist since all of those indices cause an inversion with the current element we are looking at in our right sublist. \\ 118 | 119 | The runtime of this algorithm is $\mathcal{O}(n\log(n))$ since we're only adding a few constant-time operations to the merge sort procedure. \\ 120 | 121 | A full implementation is provided on the next page. 122 | 123 | \newpage 124 | 125 | \begin{lstlisting} 126 | int merge(vector& A, int l, int m, int r) { 127 | vector B(r - l + 1); 128 | int i = l, j = m + 1, k = 0; 129 | int inversions = 0; 130 | 131 | while (i <= m && j <= r) { 132 | if (A[i] <= A[j]) { 133 | B[k++] = A[i++]; 134 | } else { 135 | B[k++] = A[j++]; 136 | inversions += (m + 1 - i); 137 | } 138 | } 139 | 140 | /* Only one of the following two while-loops 141 | will be executed. */ 142 | while (i <= m) B[k++] = A[i++]; 143 | while (j <= r) B[k++] = A[j++]; 144 | 145 | for (int i = l; i <= r; i++) { 146 | A[i] = B[i - l]; 147 | } 148 | 149 | return inversions; 150 | } 151 | 152 | int mergesort(vector &A, int l, int r) { 153 | int inversions = 0; 154 | if (r > l) { 155 | int m = l + (r - l)/2; 156 | inversions += mergesort(A, l, m); 157 | inversions += mergesort(A, m + 1, r); 158 | inversions += merge(A, l, m, r); 159 | } 160 | return inversions; 161 | } 162 | 163 | /* (i, j) is an inversion if A[i] > A[j] and i < j. 164 | O(n*log(n)) inversion counting. */ 165 | int inversion_count(vector& A) { 166 | return mergesort(A, 0, A.size() - 1); 167 | } 168 | \end{lstlisting} -------------------------------------------------------------------------------- /feb/feb20.tex: -------------------------------------------------------------------------------- 1 | \newpage 2 | 3 | \section{Thursday, February 20, 2020} 4 | 5 | Today, we'll discuss two more algorithmic problems, both of which have greedy optimal solutions. 6 | 7 | \subsection{Interval Scheduling} 8 | 9 | The first problem we'll discuss is known as the \vocab{interval scheduling problem}, which is stated as follows: 10 | 11 | \begin{quote} 12 | Given a pair of parallel arrays $\verb!start[1...N]!$ and $\verb!finish[1...N]!$, call a set of indices $S$ \vocab{compatible} if, for any pair of indices $i, j \in S$, the intervals $\verb!(start[i], finish[i])!$ and $\verb!(start[j], finish[j])!$ are disjoint. Moreover, call a compatible set $S$ \vocab{optimal} if its cardinality is maximal. The goal is to find an optimal set. 13 | \end{quote} 14 | 15 | Why do we care about this problem? For each index $1 \leq k \leq N$, we can interpret the quantity $\verb!start[k]!$ and $\verb!finish[k]!$ as the starting time and ending time of an event. Under this interpretation, our task is to fit as many events as possible into our calendar. \\ 16 | 17 | There are several algorithms that we can implement that following the greedy heuristic: 18 | 19 | \begin{enumerate} 20 | \item One approach is to always select the next available event that always starts the earliest (i.e. keep on picking $\argmin_{k \in \{1, 2, \ldots N\}} \verb!start[k]!$), and remove $k$ from our set afterwards. This method, however, is not optimal. A counterexample can be generated by considering the case in which the event with the earliest start time is very very long. By accepting this request, we'll miss out on many other events. 21 | \item A second approach is to keep on picking $\argmin_{k\in \{1, 2, \ldots, N\}}$ $\verb!finish[k] - start[k]!$ and remove the index we picked from our set. While this is better than the other approach, this isn't optimal either. 22 | \item A third approach is to pick the next request that finishes first (that is, pick $k = \argmin_{k\in \{1, 2, \ldots, N\}} \verb!finish[k]!$) over and over again. This algorithm seems similar to our first idea. Surprisingly, however, this is the optimal solution. 23 | \end{enumerate} 24 | 25 | Some pseudocode illustrating how this procedure works is presented below: 26 | 27 | \vspace{1em} 28 | \begin{center} 29 | \line(1,0){400} 30 | \end{center} 31 | 32 | \begin{allintypewriter} 33 | \# Input: A set $S$ representing the 34 | 35 | \# Output: An optimal solution $A$. 36 | 37 | SCHEDULING(S) \string{ 38 | 39 | \hspace{0.5cm} let A be the empty set. 40 | 41 | \hspace{0.5cm} while S isn't empty \string{ 42 | 43 | \hspace{1cm} let e be the event in S with the smallest finishing time. 44 | 45 | \hspace{1cm} add request e to set A. 46 | 47 | \hspace{1cm} remove any events that aren't compatible with e from S. 48 | 49 | \hspace{0.5cm} \string} 50 | 51 | \hspace{0.5cm} return A 52 | 53 | \string} 54 | 55 | \begin{center} 56 | \line(1,0){400} 57 | \end{center} 58 | \end{allintypewriter} 59 | 60 | Next, we'll prove that the set $A$ returned by this algorithm is an optimal solution. 61 | 62 | 63 | \begin{proposition} 64 | The set $A$ returned by our algorithm is a compatible set of events. 65 | \end{proposition} 66 | \begin{proof} 67 | On each iteration, we add an event, and we remove any events that \textit{aren't} compatible with the event we just added. Since the compatibility relationship between events is symmetric, we know that we'll never have a pair of incompatible events in $A$. 68 | \end{proof} 69 | 70 | Now we need to show that the set $A$ produced by this algorithm has maximal cardinality. In order to do so, let $\mathcal{O}$ be an optimal set of intervals. We want to show $|\mathcal{O}| = |A|$. \\ 71 | 72 | In other words, if $A = \{i_1, i_2, \ldots, i_k\}$ and $\mathcal{O} = \{j_1, j_2, \ldots, j_m\}$, then our goal is to show $k = m$. \\ 73 | 74 | In order to show that this is true, we need to make use of the following lemma:\\ 75 | 76 | \begin{lemma} 77 | For any indices $r \leq k$, we have $\verb!finish[!i_{r}\verb!]! \leq \verb!finish[!j_r\verb!]!$. 78 | \end{lemma} 79 | \begin{proof} 80 | For brevity, this proof writes $f(k)$ and $s(k)$ represent $\verb!finish[k]!$ and $\verb!start[k]!$, respectively. \\ 81 | 82 | When $r = 1$, the statement holds since our algorithm always picks the index $i_1$ corresponding to the event with the minimum finish time. Now suppose $f(i_{r-1}) \leq f(j_{r-1})$. We want to show $f(i_{r}) \leq f(j_{r})$. But this is clearly true since $f(j_{r - 1}) \leq s(j_{r})$ implies $f(i_{r - 1}) \leq s(j_r)$. This means that $j_r$ is in the set $S$ of compatible events at the time when the greedy algorithm chooses $i_r$. Since the greedy algorithm always picks the event with the minimum finish time, we must have $f(i_r) \leq f(j_r)$. 83 | \end{proof} 84 | 85 | Lemma $8.2$ means precisely that our greedy algorithm's intervals are finished at least as soon as the corresponding intervals in $\mathcal{O}$. \\ 86 | 87 | We can now prove our original claim: 88 | 89 | 90 | \begin{proposition} 91 | The set $A$ returned by our greedy algorithm has maximal cardinality. 92 | \end{proposition} 93 | \begin{proof} 94 | If $A$ doesn't have maximal cardinality, then an optimal set $\mathcal{O}$ must have more requests. In other words, we require $m > k$. Applying our previous lemma with $r = k$, we find $f(i_k) \leq f(j-k)$. But since $m > k$, there exists some request $j_{k+1}$ in $\mathcal{O}$. Since this request starts after the event corresponding to $j_k$ ends, deleting all of the requests that aren't compatible with $i_1, \ldots, i_k$ will still contain $j_{k+1}$. However, this means that the greedy algorithm stops with a request present in set, when it's actually only supposed to stop when $S$ is empty. 95 | \end{proof} 96 | 97 | \subsubsection{Extensions: Minimizing Lateness} 98 | 99 | Once again, consider the situation in which we have a set of $n$ events that we want to schedule in an interval of time. But now, instead of a start time and a finish time, each event has a \textit{deadline}. We say an event $k$ is \vocab{late} if our finish time is greater than its deadline. Moreover, we define the \textit{lateness} of a late event as the difference between the time at which it was finished and the time of the deadline. The objective of this problem is to minimize the number of late events. \\ 100 | 101 | The greedy algorithm in this problem is to sort the jobs in increasing order of their deadlines, and schedule them in this order (i.e. we process the events with the earliest deadline first). We will not prove the correctness of this algorithm. 102 | 103 | 104 | 105 | \subsection{Caching} 106 | 107 | A \vocab{cache} is a piece of hardware or software that stores data in a special location so that future requests for that data can be served in a high-speed manner. The idea of caching is to store frequently-used values in a special area so that we can access the values in a quick manner. If a value is \textit{not} cached, then we say that the value is stored in \vocab{main memory}. \\ 108 | 109 | In order to have an effective cache, it should usually be the case that when we're trying to access a piece of data, it's already present in the cache. Today, we'll talk about a cache maintenance algorithm that determines what to keep in the cache and what to toss out of the cache when new data is brought in. \\ 110 | 111 | Our problem is stated as follows: \\ 112 | 113 | \begin{quote} 114 | Let $U$ be a set containing $n$ pieces of data stored in main memory, and let $C$ denote our cache that can hold $k < n$ pieces of memory. Given a sequence of data items $d_1, d_2, \ldots, d_m$ drawn from $U$, we must process them in order and determine which of the $k$ items to keep in the cache. When an item $d_i$ is presented that isn't in $C$, we say a \vocab{cache miss} occurs (we want to minimize these), and we have the option to evict some other data element in $C$ in exchange for $d_i$. Thus, our problem consists of computing the minimum number of cache misses necessary to process our data sequence. 115 | \end{quote} 116 | 117 | 118 | \begin{example} 119 | [Caching Example] 120 | Suppose $U = \{a, b, c\}$, and our cache size is $k = 2$. Moreover, suppose we are presented with the sequence 121 | \[ 122 | a,b,c,b,c,a,b. 123 | \] 124 | If the cache initially contains items $a$ and $b$, then on the third item in the sequence, we can evict $a$ to bring in $c$, and on the sixth item, we could evict $c$ to bring in $a$. This results in two total cache misses. It can be shown that no solution can have fewer than two cache misses. 125 | \end{example} 126 | 127 | 128 | \subsection{Farthest in Future Algorithm} 129 | 130 | Surprisingly, the solution to the caching problem is fairly short. When data element $d_i$ needs to be brought into the cache, we should always evict the item that is needed the farthest into the future. This is known as the \vocab{Farthest-in-Future algorithm}, and it was discovered by Belady. \\ 131 | 132 | We won't prove optimality; however, it's important to take note that that greedy algorithms might not always be obvious (why do we evict the element farthest in the future as opposed to the least frequent element?) -------------------------------------------------------------------------------- /feb/feb6.tex: -------------------------------------------------------------------------------- 1 | \newpage 2 | \section{Thursday, February 6, 2020} 3 | 4 | Today, we'll discuss algorithms to find articulation points and biconnected components. 5 | 6 | \subsection{Articulation Points} 7 | 8 | \begin{definition} 9 | An \vocab{articulation point} or \vocab{cut vertex} is a vertex in a graph $G = (V, E)$ whose removal (along with any incident edges) would disconnect $G$. 10 | \end{definition} 11 | 12 | \begin{definition} 13 | A graph is said to be \vocab{biconnected} if the graph not have any articulation points. 14 | \end{definition} 15 | 16 | For example, consider the following graph: 17 | 18 | \begin{figure}[h] 19 | \centering 20 | \begin{tikzpicture} 21 | \begin{scope}[every node/.style={circle,thick,draw}] 22 | \node (A) at (0,0) {A}; 23 | \node (B) at (0,3) {B}; 24 | \node (C) at (2.5,4) {C}; 25 | \node (D) at (2.5,1) {D}; 26 | \node (E) at (2.5,-3) {E}; 27 | \node (F) at (5,3) {F} ; 28 | \end{scope} 29 | 30 | \begin{scope}[>={Stealth[black]}, 31 | every node/.style={fill=white,circle}, 32 | every edge/.style={draw=red,very thick}] 33 | \path [-] (A) edge (B); 34 | \path [-] (B) edge (C); 35 | \path [-] (A) edge (D); 36 | \path [-] (D) edge (C); 37 | % \path [-] (A) edge (E); 38 | \path [-] (D) edge (E); 39 | \path [-] (D) edge (F); 40 | \path [-] (C) edge (F); 41 | % \path [-] (E) edge (F); 42 | % \path [-] (B) edge[bend right=40] (F); 43 | \end{scope} 44 | \end{tikzpicture} 45 | \caption{A Graph with an Articulation Point} 46 | \end{figure} 47 | 48 | In the diagram above, Vertex $D$ is an articulation point. To see why, note that if we were to remove Vertex $D$ (and any incident edges to $D$) from the graph, then we would end up with two connected components: the first component would contain the vertices $A, B, C,$ and $F$, whereas the second component would only contain the vertex $E$. \\ 49 | 50 | Why are articulation points important? One example in which searching for articulation points is important is in the study of networks. In a network modeled by a graph, an articulation point represents a vulnerability: it is a single point whose failure would split the network into two or more components (preventing communication between the nodes in different networks). 51 | \\ 52 | 53 | 54 | How do we find an articulation points? The brute force algorithm is as follows: 55 | 56 | \begin{enumerate} 57 | \item Run an $\mathcal{O}(V + E)$ depth-first search or breadth-first search to count the number of connected components in the original graph $G = (V, E)$. 58 | \item For each vertex $v\in V$, remove $v$ from $G$, and remove any of $v$'s incident edges. Run an $\mathcal{O}(V + E)$ depth-first search or breadth-first search again, and check if the number of connected components increases. If so, then $v$ is an articulation point. Restore $v$ and any of its incident edges. 59 | \end{enumerate} 60 | 61 | This naive algorithm calls the depth-first search or breadth-first search algorithm $\mathcal{O}(V)$ times. Hence, it runs in $\mathcal{O}(V \times (V + E)) = \mathcal{O}(V^2 + VE)$ time. \\ 62 | 63 | 64 | While this algorithm \textit{works}, it is not as efficient as we can get. We will now describe a linear-time algorithm that runs the depth-first search algorithm just \textit{once} to identify all articulation points and bridges. This algorithm is often accredited to Hopcraft and Tarjan. \\ 65 | 66 | 67 | In this modified depth-first search, we will now maintain two numbers for each vertex $v$: \verb!dfs_num(v)! and \verb!dfs_low(v)!. The quantity \verb!dfs_num(v)! represents a label that we will assign to nodes in an increasing fashion. For instance, the vertex from which we call depth-first search would have a \verb!dfs_num! of $0$. The subsequent vertex we visit would be assigned a \verb!dfs_num! of $1$, and so on. \\ 68 | 69 | On the other hand, the quantity \verb!dfs_low(v)!, also known as the \vocab{low-link value} of the vertex $v$, represents the smallest \verb!dfs_num! reachable from that node while performing a depth-first (including itself). \\ 70 | 71 | Here's an example. Consider the following directed graph: 72 | 73 | 74 | \begin{figure}[h] 75 | \centering 76 | \begin{tikzpicture} 77 | \begin{scope}[every node/.style={circle,thick,draw}] 78 | \node (A) at (0,0) {A}; 79 | \node (B) at (2,0) {B}; 80 | \node (C) at (4,0) {C}; 81 | \node (D) at (6,0) {D}; 82 | % \node (E) at (2.5,-3) {E}; 83 | % \node (F) at (5,3) {F} ; 84 | \end{scope} 85 | 86 | \begin{scope}[>={Stealth[black]}, 87 | every node/.style={fill=white,circle}, 88 | every edge/.style={draw=red,very thick}] 89 | \path [-] (A) edge (B); 90 | \path [-] (B) edge (C); 91 | \path [-] (C) edge (D); 92 | \path [-] (C) edge[bend right=40] (A); 93 | 94 | % \path [-] (A) edge (D); 95 | % \path [-] (D) edge (C); 96 | % \path [-] (A) edge (E); 97 | % \path [-] (D) edge (E); 98 | % \path [-] (D) edge (F); 99 | % \path [-] (C) edge (F); 100 | % \path [-] (E) edge (F); 101 | % \path [-] (B) edge[bend right=40] (F); 102 | \end{scope} 103 | \label{artpoint:ex} 104 | \end{tikzpicture} 105 | \caption{Articulation Point Example} 106 | \end{figure} 107 | 108 | Suppose we perform a depth-first search starting at Vertex $A$. 109 | 110 | \begin{itemize} 111 | \item Vertex $A$ will be assigned a \verb!dfs_num! of $0$ since this is the first vertex that we're visiting. Moreover, $0$ is the smallest \verb!dfs_num! that is reachable from $A$ (all other vertices have their \verb!dfs_num! set to \verb!nil! or \verb!INFINITY!). Hence, we set \verb!dfs_num(A) = 0! and \verb!dfs_low(A) = 0!. 112 | \item Next, we visit vertex $B$. Vertex $B$ is assigned a \verb!dfs_num! of $1$ since it's the second vertex we're visiting. Moreover, Vertex $B$ has a \verb!dfs_low! value of $0$ since we can reach a vertex with a \verb!dfs_num! value of $0$ through the path $B \rightarrow C \rightarrow A$. Note that it would be invalid to say that the path $B \rightarrow A$ causes \verb!dfs_low(B)! to equal $0$ since we cannot go backwards in the depth-first search traversal. 113 | \item Applying similar reasoning, we find that vertex $C$ ends up with a \verb!dfs_num! value of $2$, and it also has a \verb!dfs_low! value of $0$ (we can reach vertex $A$). 114 | \item Finally, vertex $B$ ends up with a \verb!dfs_num! value of $3$; however, no vertices with a lower \verb!dfs_num! value are reachable from $D$. Hence, the \verb!dfs_low! value of $D$ is also equal to $3$. Note that it is incorrect to say that $D$ has a \verb!dfs_low! value of $0$ through the path $D\rightarrow C\rightarrow A$ since we cannot revisit vertices while performing the depth-first search algorithm. 115 | \end{itemize} 116 | 117 | 118 | Why do we care about these \verb!dfs_num! and \verb!dfs_low! values? It becomes more clear when we consider the depth-first search tree produced by calling the depth-first search algorithm. The quantity \verb!dfs_low(v)! represents the smallest \verb!dfs_num! value reachable from the current depth-first search spanning subtree rooted at the vertex $v$. The value \verb!dfs_low(v)! can only be made smaller if there's a back edge (an edge from a vertex $v$ to an ancestor of $v$) in the depth-first search tree. \\ 119 | 120 | This leads us to make the following observation: If there's a vertex $u$ with neighbor $v$ satisfying \verb!dfs_low(v) >= dfs_num(u)!, then we can conclude that vertex $u$ is an articulation point. Note that this makes sense intuitively since it means that the \textit{smallest} numbered vertex that we can ever reach starting from vertex $v$ is greater than or equal to the number we assigned to $u$. Hence, removing $u$ would disconnect \verb!v! from any vertex with smaller \verb!dfs_num! than \verb!dfs_num(u)!. \\ 121 | 122 | 123 | Going back to the previous graph figure, we can note that the following: 124 | \[ 125 | \verb!3 = dfs_num(D) >= dfs_low(C) = 0! 126 | \] 127 | As stated previously, this implies that Vertex $C$ is an articulation point. Note that removing Vertex $C$ would disconnect the vertices $A$ and $B$ from Vertex $D$. \\ 128 | 129 | Now, there's one special case to this algorithm. The root of the depth-first search spanning tree (the vertex that we choose as the source in the first depth-first search call) is an articulation point only if it has more than one children. This one case is not detected by the algorithm; however, it is easy to check in implementation. \\ 130 | 131 | % When actually implementing the algorithm, we can assign \verb!dfs_num! values ``on-the-fly" when we visit the vertex. A C++ implementation is provided below: \\ 132 | 133 | % \begin{lstlisting} 134 | % int dfsCounter; /* Used to assign vertices their dfs_num value. */ 135 | % int dfs_num[10000]; /* Stores the dfs_num values of each vertex. */ 136 | % int dfs_lo[10000]; /* Stores the dfs_low values of each vertex. */ 137 | % int color[10000]; /* Check if we've visited the vertex yet. */ 138 | % int parent[10000]; /* We maintain the parent of a vertex so we don't update the dfs_low value with an already-visited vertex. */ 139 | % bool isArticulationPoint[10000]; 140 | 141 | 142 | % /* O(V + E) Articulation Point Algorithm. */ 143 | % void dfs(int u, const vector>& AdjList) { 144 | % color[u] = 1; 145 | % dfs_num[u] = dfs_lo[u] = dfsCounter++; 146 | 147 | % for (int i = 0; i < AdjList[u].size(); i++) { 148 | % int v = AdjList[u][i]; 149 | % if (!visited[v]) { 150 | % /* Vertex v hasn't been visited. */ 151 | % parent[v] = u; 152 | % dfs(v, AdjList); 153 | 154 | % if (dfs_lo[v] >= dfs_num[u]) { 155 | % // Vertex u is an articulation point. 156 | % isArticulationPoint[u] = true; 157 | % } 158 | % /* Update the dfs_lo value of u. */ 159 | % dfs_lo[u] = MIN(dfs_lo[u], dfs_lo[v]); 160 | % } else if (color[v] == 1 && parent[u] != v) { 161 | % /* (u, v) is a back edge in the depth-first search tree. */ 162 | % /* We've already visited vertex v, so we just update the dfs_lo value. */ 163 | % dfs_lo[u] = min(dfs_lo[u], dfs_num[v]); 164 | % } 165 | % } 166 | % color[u] = 2; 167 | % } 168 | % \end{lstlisting} 169 | 170 | -------------------------------------------------------------------------------- /march/mar05.tex: -------------------------------------------------------------------------------- 1 | \newpage 2 | \section{Thursday, March 5, 2020} 3 | 4 | Today, we'll begin discussing our last divide-and-conquer topic: the Fast Fourier (``four-ee-aye") Transform. The problem that we are trying to solve is stated as follows: 5 | 6 | \begin{quote} 7 | Given two vectors $a = (a_0, a_1, \ldots, a_{n - 1})$ and $b = (b_0, b_1, \ldots, b_{n - 1})$, compute the convolution $a\star b$ of $a$ and $b$. 8 | \end{quote} 9 | 10 | Before discussing the algorithm that lets us do this, let's first discuss convolutions and why they're important. 11 | 12 | \subsection{Convolutions} 13 | 14 | A \vocab{convolution} of two vectors $a$ and $b$ is a method of ``combining" the two vectors. More precisely, we define the convolution of the vectors $a = (a_0, a_1, \ldots, a_{n - 1})$ and $b = (b_0, b_1, \ldots, b_{n - 1})$ by the vector $c = (c_0, c_2, \ldots, c_{2n - 2})$ in which 15 | 16 | \[ 17 | c_k = \sum_{(i, j) \mid i + j = k} a_i b_j. 18 | \] 19 | 20 | In other words, we have, 21 | 22 | \[ 23 | a\star b = (a_0b_0, a_0b_1 + a_1b_0, \ldots, a_{n-1}b_{n-1}). 24 | \] 25 | 26 | Note that each summand in the $k^{\text{th}}$ component of this vector exhausts all possible pairs of indices that sum to $k$. Moreover, note that the convolution of two $n$-dimensional vectors produces a $(2n - 1)$-dimensional vector. However, unlike the vector sum and inner product, the convolution can easily be generalized to vectors of different lengths: if $a = (a_0, a_1, \ldots, a_{m - 1})$ and $b = (b_0, b_1, \ldots, b_{n - 1})$, then we define $a \star b$ to be a vector with $m + n - 1$ coordinates, where the $k^{\text{th}}$ coordinate is equal to the sum over all $a_ib_j$ in which $i + j = k$, $i < m$ and $b < n$. \\[1em] 27 | 28 | 29 | Why do we care about the convolution? Here are some examples in which convolutions are useful: 30 | 31 | \begin{example} 32 | [Polynomial Multiplication] 33 | Suppose we have two polynomials $A(x) = a_0 + a_1x + a_2x^2 + \cdots + a_{m - 1}x^{m - 1}$ and $B(x) = b_0 + b_1x + b_2x^2 + \cdots + b_{n - 1}x^{n - 1}$ and we wish to compute the product $C(x) = A(x) \cdot B(x)$. In order to do so, we can define the vectors $a = (a_0, a_1, \ldots, a_{m - 1})$ and $b = (b_0, b_1, \ldots, b_{n - 1})$ and compute the convolution $c = a\star b$. In the polynomial $C(x)$, the coefficient of $x^{k}$ is equal to the $k^{\text{th}}$ component of $c$. 34 | \end{example} 35 | 36 | 37 | \begin{example} 38 | [Combining Histograms] 39 | Suppose we're studying a population of people, and we have two histograms. The first histogram shows the annual income of all the men in the population, and the other shows the annual income of all the women. We would like to produce a new histogram showing for each $k$ the number of pairs $(M, W)$ for which man $M$ and woman $W$ have a combined income of $k$. This problem can be restated as a convolution. More precisely, let $a = (a_0, \ldots, a_{m - 1})$ and $b = (b_0, \ldots, b_{n - 1})$ be our histograms, and let $c_k$ denote the number of $(m, w)$ pairs with combined income $k$. Observe that $c_k$ is the number of ways to choose a man with income $a_i$ and woman with income $b_j$ with $i + j = k$. This quantity is given by a convolution. 40 | \end{example} 41 | 42 | \begin{example} 43 | [Sum of Independent Random Variables] 44 | If one is familiar with probability theory, then they may have encountered a theorem which tells us that the probabiliy distribution function for the sum of two random variables is a convolution of the distributions of the summands. 45 | \end{example} 46 | 47 | Now that we've motivated the importance of convolutions, we'll now discuss how to compute convolutions efficiently. For simplicity, we consider the case in which our two vectors have equal length (i.e. $m = n)$; however, our results hold for vectors of unequal length. \\ 48 | 49 | Computing a convolution efficiently is more difficult than it seems. If, for each $k$, we just calculate the sum $\sum_{(i, j) \mid i + j = k} a_ib_j$ and use it as the $k^{\text{th}}$ coordinate in our convolution vector, we end up with an $\mathcal{O}(n^2)$ algorithm. Fortunately, we can do better --- the \vocab{fast Fourier Transform} allows us to compute convolutions in $\mathcal{O}(n\log(n))$ time. 50 | 51 | \subsection{The Fast Fourier Transform} 52 | 53 | In order to compute convolutions quickly, we will make use of the connection between the convolution and polynomial multiplication. However, rather than using convolutions to perform polynomial multiplication, we will exploit the connection in the opposite direction. \\ 54 | 55 | 56 | Given two vectors $a = (a_0, \ldots, a_{n - 1})$ and $b = (b_0, \ldots, b_{n - 1})$, we define $A(x)$ and $B(x)$ to be the polynomials $a_0 + a_1x + \cdots + a_{n-1}x^{n-1}$ and $b_0 + b_1x + \cdots + b_{n-1}x^{n-1}$, respectively. Under this interpretation, we wish to compute the product $C(x) = A(x)B(x)$ in $\mathcal{O}(n\log(n))$ time. From there, we can simply ``read off" the convolution directly from the coefficients of $C(x)$. \\ 57 | 58 | Now, instead of multiplying $A$ and $B$ directly, we can treat them as functions of the variable $x$ and multiply them with the following three steps: 59 | 60 | \begin{enumerate} 61 | \item Choose $2n$ values $x_1, x_2, \ldots, x_{2n}$ and evaluate $A(x_j)$ and $B(x_j)$ for each $j = 1, 2, \ldots, 2n$. 62 | \item Now for each index $1 \leq j \leq 2n$, we can easily compute $C(x_j)$. In particular, $C(x_j)$ is equal to the product of the two numbers $A(x_j)$ and $B(x_j)$. 63 | \item Finally, we need to recover the polynomial $C$ from its values on $x_1, x_2, \ldots, x_{2n}$. Since any polynomial of degree $d$ is fully determined by a set of $d + 1$ or more points, this is clearly possible. Since each $A$ and $B$ have degree at most $n - 1$, their product $C$ has degree at most $2n - 2$. Thus, it can be reconstructed from the values $C(x_1), C(x_2), \ldots, C(x_{2n})$ that we computed earlier. 64 | \end{enumerate} 65 | 66 | This approach to multiplying polynomials sounds promising, but there are a couple of issues we need to address. Evaluating the polynomials $A$ and $B$ on a single point takes $\Omega(n)$ operations (using Horner's method\footnote{\url{https://en.wikipedia.org/wiki/Horner\%27s_method}}, and our plan calls for performing $2n$ such evaluations. This brings us back up to quadratic time immediately. Moreover, we need a way to quickly reconstruct the polynomial $C$ from the points $C(x_1), C(x_2), \ldots, C(x_2n)$. \\ 67 | 68 | We address these two issues separately. 69 | 70 | \subsubsection{Polynomial Evaluation} 71 | 72 | We need to evaluate the polynomials $A$ and $B$ on $2n$ different points quickly. The key idea to doing this quickly is to find a set of $2n$ points $x_1, \ldots, x_{2n}$ that are related in some way so that the work in evaluating $A$ and $B$ on all of them can be shared across different evaluations. A set that works very well for us is the roots of unity. \\ 73 | 74 | \begin{definition} 75 | An \vocab{$n^{\text{th}}$ root of unity} is a number $z$ satisfying the equation $z^{n} = 1$. 76 | \end{definition} 77 | 78 | It can be shown that there are $n$ $n^{\text{th}}$ roots of unity. Moreover, these roots are given by $e^{2k\pi i/n}$ for $k = 0, 1, \ldots, n - 1$. Clearly, each of these complex numbers satisfy our definition since 79 | \[ 80 | (e^{2k \pi i/n})^{n} = e^{2k \pi i} = (e^{2 \pi i})^{k} = 1^{k} = 1. 81 | \] 82 | 83 | For our numbers $x_1, \ldots, x_{2n}$ on which to evaluate $A$ and $B$, we will choose the $(2n)^{\text{th}}$ roots of unity, and we propose a recursive procedure to compute $A$ on each of the $(2n)^{\text{th}}$ roots of unity. For simplicity, we henceforth assume that $n$ is a power of $2$. \\ 84 | 85 | Let $A_{\text{even}}(x)$ and $A_{\text{odd}}(x)$ be two polynomials that consist of the even and odd coefficients of $A$, respectively. That is, we have, 86 | \[ 87 | A_{\text{even}}(x) = a_0 + a_2x + a_4x^2 + \cdots + a_{n - 2}x^{(n - 2)/2}, 88 | \] 89 | 90 | and 91 | 92 | \[ 93 | A_{\text{odd}}(x) = a_1 + a_3x + a_5x^2 + \cdots + a_{(n - 1)}x^{(n - 2)/2}. 94 | \] 95 | 96 | By simple algebra, we can see that we can express $A(x)$ as 97 | 98 | \[ 99 | A(x) = A_{\text{even}}(x^{2}) + xA_{\text{odd}}(x^{2}), 100 | \] 101 | 102 | which demonstrates that we can compute $A(x)$ in a constant number of operations provided that we already have $A_{\text{even}}$ and $A_{\text{odd}}$. Now suppose we evaluate both $A_{\text{even}}$ and $A_{\text{odd}}$ on the $n^{\text{th}}$ roots of unity. This is an exact replica of the problem we face with $A$ and the $(2n)^{\text{th}}$ roots of unity, except the input is half as large; the degrees of our two polynomials are $(n - 2)/2$ rather than $n - 1$. Moreover, we have $n$ roots of unity rather than $2n$. Thus, we can perform these evaluations recursively in time $T(n/2)$ for each of $A_{\text{even}}$ and $A_{\text{odd}}$, for a total of $2T(n/2)$ time. \\ 103 | 104 | But, how do we perform these evaluations? This can be done with $\mathcal{O}(n)$ additional operations given the results from the recursive calls on $A_{\text{even}}$ and $A_{\text{odd}}$. Let $\omega = e^{2\pi i k/2n}$ be a $(2n)^{\text{th}}$ root of unity for some integer $k$. The quantity $\omega^{2}$ is equal to $e^{2\pi k i/n}$, which is an $n^{\text{th}}$ root of unity. \\ 105 | 106 | Thus, when we go to compute $A(\omega) = A_{\text{even}}(\omega^{2}) + \omega \cdot A_{\text{odd}}(\omega^2)$,we find that both evaluations on the right-hand side have been performed in a recursive step, which means that we can compute $A(\omega)$ in a constant number of operations. Repeating for each of the $2n$ roots of unity is therefore $\mathcal{O}(n)$ additional operations. \\ 107 | 108 | Therefore, our bound $T(n)$ on the number of operations satisfies $T(n) = 2T(n/2) + \mathcal{O}(n)$, which gives us the desired $\mathcal{O}(n\log(n))$ bound for the first step of our algorithm. \\ 109 | 110 | 111 | \subsubsection{Polynomial Interpolation} 112 | 113 | Next, we'll discuss how to Now, we've seen how to evaluate $A$ and $B$ on the set of all $(2n)^{\text{th}}$ roots of unity using $\mathcal{O}(n\log(n))$ operations. Also, we can clearly perform the second step of our algorithm naively in linear time. Thus, to conclude the algorithm for multiplying $A$ and $B$, we need to reconstruct the polynomial $C$ from its values on the $(2n)^{\text{th}}$ roots of unity in $\mathcal{O}(n\log(n))$ time. \\ 114 | 115 | 116 | The reconstruction of $C$ can be achieved by defining an appropriate polynomial and evaluating it at the $(2n)^{\text{th}}$ roots of unity. This is exactly what we've just seen how to do using $\mathcal{O}(n\log(n))$ operations, so we'll do it here again. This requires an additional $\mathcal{O}(n\log(n))$ operations, and it concludes our algorithm. \\ 117 | 118 | Consider a polynomial $C(x) = \sum_{s=0}^{2n - 1} c_sx^s$ that we want to reconstruct from its values at the $C(\omega_{s, 2n})$ at the $(2n)^{\text{th}}$ roots of unity. Define a new polynomial $D(x) \defeq \sum_{s = 0}^{2n - 1} d_{s}x^{s}$ where $d_s = C(\omega_{s, 2n}$. We now consider the values of $D(x)$ at the $(2n)^{\text{th}}$ roots of unity: 119 | 120 | \begin{align*} 121 | D(\omega_{j, 2n}) &= \sum_{s = 0}^{2n - 1} C(\omega_{s, 2n}) \omega_{j, 2n}^{s} \\[1em] 122 | &= \sum_{s=0}^{2n - 1}\left( \sum_{t = 0}^{2n - 1} c_{t} \omega_{s, 2n}^{t} \right)\omega_{j, 2n}^{s}. 123 | \end{align*} 124 | 125 | Now since $\omega_{s, 2n} = (e^{2\pi i/2n})^{s}$, we get that 126 | \[ 127 | D(\omega_{j, 2n}) = \sum_{t=0}^{2n - 1}c_t \sum_{s = 0}^{2n - 1} \omega_{t + j, 2n}^{s}). 128 | \] 129 | However, note that for any $(2n)^{\text{th}}$ root of unity $\omega \neq 1$, we have $\sum_{s = 0}^{2n - 1} \omega^{s} = 0$. Thus, the only term that of the last line's outer sum that is not equal to $0$ is for $c_t$ such that $\omega_{t + j, 2n} = 1$. This happens precisely when $t + j = 2n - j$. \\ 130 | 131 | It follows immediately that for any polynomial $C(x) = \sum_{s = 0}^{2n - 1}c_sx^{s}$ and corresponding polynomial $D(x) = \sum_{s = 0}^{2n - 1} C(\omega_{s, 2n}) x^{s}$, we have $c_s = \frac{1}{2n} D(\omega_{2n - s, 2n})$. \\ 132 | 133 | Thus, we can reconstruct the polynomial $C$ from its values on the $(2n)^{\text{th}}$ roots of unity, and the coefficients of $C$ are the coordinates in the convolution vector $a \star b$ that we were originally seeking. Therefore, we are done. -------------------------------------------------------------------------------- /feb/feb13.tex: -------------------------------------------------------------------------------- 1 | \newpage 2 | 3 | \section{Thursday, February 13, 2019} 4 | 5 | Last time, we started discussing strongly connected components, and we presented an edge-classification system. Today, we'll show how we can use our edge-classification system to identify what vertices lie in strongly connected components. 6 | 7 | 8 | \subsection{Kosaraju's Algorithm} 9 | 10 | Now, we'll show how we can identify strongly connected components in linear time. The algorithm that we will describe is \vocab{Kosaraju's algorithm}. \\ 11 | 12 | The pseudocode corresponding to the algorithm is presented below: 13 | 14 | 15 | 16 | \begin{lstlisting} 17 | procedure kosarajuSCC(graph G) { 18 | 19 | for each node v in G: 20 | color v gray. 21 | 22 | let L be an empty list. 23 | for each node v in G: 24 | if v is gray: 25 | run DFS starting at v, appending each node to list L when it is we've finished processing that node. 26 | 27 | let G' be the transpose graph of G 28 | 29 | for each node v in G': 30 | color v gray. 31 | 32 | let SCC be a new array of length n. 33 | let index = 0 34 | 35 | for each node in v in L, in reverse order: 36 | if v is gray: 37 | run DFS on v in G' and set scc[u] = index 38 | for each node u visited during the traversal. 39 | index = index + 1 40 | 41 | 42 | return scc 43 | } 44 | \end{lstlisting} 45 | 46 | 47 | How is this working? 48 | 49 | \begin{enumerate} 50 | \item Firstly, we look at the original graph $G = (V, E)$, and we perform a depth-first search on the components of $G$. Once we've finished visiting each node $v$, we append $v$ to the end of a list $L$ (we are placing the vertices into $L$ in \hyperlink{https://en.wikipedia.org/wiki/Topological_sorting}{reverse-topological order}). The list $L$ ends up being sorted in reverse-order of finishing time. The entire purpose of this first depth-first search traversal is to be able to number the vertices according to their finish time. 51 | \item Next, we'll construct the transpose graph $G^{T}$, and we'll iterate over $L$ in reverse-order. Recall that the strongly connected components in $G^{T}$ are exactly the same as those in $G$. Also, we mark each 52 | \item For each vertex $v$ we visit in $L$, if we haven't already call DFS on while iterating over $L$, any set of vertices that we visit forms a strongly connected component. 53 | \end{enumerate} 54 | 55 | Some more intuition is provided below. \\ 56 | 57 | Note that, when performing a depth-first search in $G^{T}$ in post-order from a node $v$, the depth-first search first visits nodes that can reach $v$ followed by $v$ itself, and finally followed by nodes that cannot reach $v$. On the other hand, when we perform a depth-first search in pre-order on the original graph $G$ from a node $v$, the depth-first search first visits $v$, followed by any nodes reachable from $v$, and finally the nodes that are not reachable from $v$. \\ 58 | 59 | 60 | \subsection{Topological Sorting} 61 | 62 | Next, we'll begin discussing our next problem. First, we'll present a couple of definitions. 63 | 64 | \begin{definition} 65 | A \vocab{directed acyclic graph}, also known as a ``DAG," is (as its name suggests), a directed graph that doesn't have any cycles. 66 | \end{definition} 67 | 68 | 69 | \begin{definition} 70 | A \vocab{topological sort} of a directed acyclic graph $G = (V, E)$ is a linear ordering of all its vertices such that if $G$ contains an edge $(u, v)$, then $u$ precedes $v$ in the ordering. 71 | \end{definition} 72 | 73 | 74 | Clearly, a graph with a cycle cannot be topologically sorted --- we wouldn't be able to order the vertices that form the cycle. 75 | 76 | 77 | It's important to remember that, unlike number sorting algorithms, topological sorts are not unique. Each graph $G$ can have multiple valid topological sorts. \\ 78 | 79 | 80 | Topological sorts are really helpful when we're considering a graph that represents precedences among events or objects. Here are a few examples: 81 | 82 | 83 | \begin{example} 84 | [Figure 22.7, CLRS] 85 | Professor Bumstead gets dressed in the morning. The professor must wear certain garments before others (e.g. socks before shoes), whereas other pairs of items can be put on in any order (e.g. socks and pants). We can represent this situation with a directed acyclic graph $G = (V, E)$ in which a directed edge $(u, v)$ indicates that garment $u$ must be donned before garment $v$. The professor can topologically sort this graph in order to get a valid order for getting dressed. 86 | \end{example} 87 | 88 | Here's another example. 89 | 90 | \begin{example} 91 | [Pick-up Sticks] 92 | The game of \textit{pick-up sticks} involves two players. The game consists of dropping a bundle of sticks. Subsequently, players take turns trying to remove sticks without disturbing any of the others. In order to model this game, we can use a directed graph $G = (V, E)$ in which each vertex represents a stick. We place a directed edge $(u, v)$ between sticks $u$ and $v$ if stick $u$ is on top of stick $v$. By topologically sorting the graph, we can find a valid way to pick up the sticks on top first. 93 | \end{example} 94 | 95 | Now, we've seen a couple of examples in which topological sorts can be useful, but how do we perform a topological sort? \\ 96 | 97 | 98 | It turns out we can topologically sort a graph in linear time. We will present two algorithms. \\ 99 | 100 | 101 | Firstly, we present \vocab{Kahn's algorithm}, which relies on the following fact: 102 | 103 | 104 | \begin{proposition} 105 | Every directed acyclic graph has at least one vertex with in-degree $0$. 106 | \end{proposition} 107 | \begin{proof} 108 | Suppose not. For each vertex $v$, we can move backwards through an incoming edge. But due to the finiteness of the graph $G$ and absence of a cycle, this process must eventually terminate. The vertex we terminate must have in-degree $0$. 109 | \end{proof} 110 | 111 | Now that we've established this fact, a summary of Kahn's algorithm is presented below: 112 | 113 | \begin{enumerate} 114 | \item Enqueue all vertices with in-degree $0$ into a priority queue $Q$. At least one such vertex must exist due to Proposition $6.5$. 115 | \item Let $L$ be an empty list. This will store our vertices in topologically sorted order. 116 | \item While the $Q$ isn't empty, extract the next vertex $u$ from $Q$. Remove the vertex $u$ from the original graph $G$ along with any incident edges, and add $u$ to $L$. If this removal causes another vertex $v$ to have in-degree $0$, then enqueue $v$ into $Q$. 117 | \item Once the while-loop terminates, $L$ will contain every vertex in topologically sorted order. 118 | \end{enumerate} 119 | 120 | 121 | While we won't prove correctness for this algorithm, it should be a little clear as to why it works. Since we're always choosing vertices with in-degree $0$, we know that there is no other vertex that should come before the vertex we're choosing. Hence, the vertices we pick are always ``safe." This is pretty similar to the selection sort algorithm used to sort numbers in which we repeatedly pick the minimum element in an array to place at the front of the array. This algorithm runs in $\mathcal{O}(V + E)$ time on an adjacency list. \\ 122 | 123 | 124 | Here's a second algorithm that correctly performs a topological sort. This is just a slight modification to the DFS algorithm. 125 | 126 | \begin{enumerate} 127 | \item Let $G = (V, E)$ be our original graph. Mark each vertex $v\in V$ as ``unvisited." 128 | \item For each unvisited vertex, call \verb!DFS(v)!, and 129 | prepend \verb!v! into an array \verb!A! once we've finished visiting all of its neighbors. 130 | \item Once we've finished visiting every vertex in $G$, the array \verb!A! will be in reverse-topological order. We can reverse the array in linear time, and we're done. 131 | \end{enumerate} 132 | 133 | This algorithm runs in $\mathcal{O}(V + E)$ time as the runtime is dominated by our depth-first search calls. \\ 134 | 135 | 136 | Once again, we won't prove correctness of this algorithm, but it should be clear why this algorithm works. Our call to depth-first search will end pushing vertices with out-degree $0$ onto the stack first (because they won't have any more neighbors to visit), which are always safe to place at the end of the topological ordering since no vertex is ``greater" than them. This is followed by other vertices in ascending order of out-degree. \\ 137 | 138 | 139 | A C++ implementation of this algorithm is presented below: 140 | 141 | 142 | 143 | \lstset{frame=tb, 144 | aboveskip=3mm, 145 | belowskip=3mm, 146 | showstringspaces=false, 147 | columns=flexible, 148 | basicstyle={\small\ttfamily}, 149 | numbers=none, 150 | language=C++, 151 | numberstyle=\tiny\color{gray}, 152 | keywordstyle=\color{blue}, 153 | commentstyle=\color{dkgreen}, 154 | stringstyle=\color{mauve}, 155 | breaklines=true, 156 | breakatwhitespace=true, 157 | tabsize=3 158 | } 159 | 160 | \begin{lstlisting} 161 | vector> AdjList; /* Our graph. */ 162 | vector toposort; /* Global array to store topological sort. */ 163 | bool visited[10000]; 164 | 165 | void dfs(int u) { 166 | visited[u] = true; 167 | for (int i = 0; i < AdjList[u].size(); i++) { 168 | int v = AdjList[u][i]; 169 | if (!visited[v]) { 170 | dfs(v); 171 | } 172 | } 173 | toposort.push_back(u); 174 | } 175 | 176 | int main(void) { 177 | memset(visited, false, sizeof(visited)); 178 | for (int i = 0; i < V; i++) { 179 | if (!visited[i]) { 180 | dfs(i); 181 | } 182 | } 183 | reverse(toposort.begin(), toposort.end()); 184 | /* Topological sort is complete. */ 185 | } 186 | \end{lstlisting} 187 | 188 | 189 | 190 | 191 | \subsection{Bipartite Graphs} 192 | 193 | Finally, we'll discuss bipartite graphs. 194 | 195 | \begin{definition} 196 | A graph $G = (V, E)$ is called \vocab{bipartite} if we can partition its vertex set $V$ into two disjoint sets $U$ and $V$ such that each edge $(u, v) \in E$ has one endpoint in $U$ and the other endpoint in $V$. 197 | \end{definition} 198 | 199 | Here's an equivalent definition that we sometimes like to use: 200 | 201 | \begin{definition} 202 | A graph $G = (V, E)$ is said to be \vocab{bipartite} if we can color each vertex either black or white such that no two adjacent vertices have the same color. 203 | \end{definition} 204 | 205 | 206 | In order to test whether a graph is bipartite, we can perform a graph search in which we color vertices as we go along. Although we can use either breadth-first search or depth-first search for this check, breadth-first search is often the more natural approach. Pretty much, we start by coloring the source vertex with value $0$, color the direct neighbors of the source vertex with $1$, the neighbors of the neighbors of the source vertex with color $0$, and so on. If we encounter any violations (i.e. two adjacent vertices with the same color) as we go along, then we can conclude that the given graph is not bipartite. \\ 207 | 208 | 209 | A C++ implementation is provided below: 210 | 211 | 212 | \begin{lstlisting} 213 | vector> AdjList; /* Our graph. */ 214 | 215 | bool isBipartite(int src) { 216 | queue q; 217 | q.push(src); 218 | vector color(V, INFINITY); 219 | color[src] = 0; 220 | bool isBipartite = true; 221 | 222 | while (!q.empty() && isBipartite) { 223 | int u = q.front(); q.pop(); 224 | 225 | for (int i = 0; i < AdjList[u].size(); i++) { 226 | int v = AdjList[u][i]; 227 | if (color[v] == INFINITY) { 228 | /* We haven't colored v yet. */ 229 | color[v] = 1 - color[u]; 230 | q.push(v); 231 | } else if (color[v] == color[u]) { 232 | /* We've found a violation. */ 233 | isBipartite = false; 234 | break; 235 | } 236 | } 237 | } 238 | return isBipartite; 239 | } 240 | \end{lstlisting} 241 | 242 | The runtime of this algorithm is dominated is $\mathcal{O}(V + E)$ on an adjacency list since we're just performing a breadth-first search. \\ 243 | 244 | Another useful fact regarding bipartite graphs is the following: 245 | 246 | \begin{fact} 247 | A graph is bipartite if and only if it has no odd cycles (i.e. cycles of length $3, 5, 7,$ etc). 248 | \end{fact} -------------------------------------------------------------------------------- /feb/feb18.tex: -------------------------------------------------------------------------------- 1 | \newpage 2 | 3 | \section{Tuesday, February 18, 2020} 4 | 5 | Last time, we finished graph algorithms. Today, we'll begin \vocab{greedy algorithms}, which are a class of algorithms that repeatedly make ``locally optimal" decisions in an attempt to find a globally optimal solution. 6 | 7 | 8 | \subsection{The Union-Find Data Structure} 9 | 10 | \subsubsection{Motivating the Union-Find Data Structure} 11 | 12 | 13 | Before we introduce Kruskal's algorithm, we'll need to first introduce a data structure known as the \vocab{union-find} or \vocab{disjoint-set} data structure. Why? Because this data structure is used in the implementation of Kruskal's algorithm, which is one of the two minimum spanning tree algorithms we will be talking about. \\ 14 | 15 | The union-find data structure consists of a collection of disjoint sets (i.e. a set of sets). Each disjoint set is uniquely determined by a \vocab{set representative}, which is some member of the set. In most applications, it doesn't actually matter which member of the set is used as the representative; all we care is that, if we ask for the representative of a set twice without making any modifications, we should get the same answer both times. \\ 16 | 17 | The union-find data structure supports the following operations: 18 | 19 | \begin{enumerate} 20 | \item The \verb!MAKE-SET(x)! operation creates a new set whose only member is $x$. Since $x$ is the only member of this newly created set, $x$ must also be the representative of this set. Moreover, since we require the sets to be disjoint, we require that $x$ not already be in some other set. 21 | \item The \verb!UNION(x, y)! operation unites the two sets that contain the elements $x$ and $y$. More precisely, if $S_x$ and $S_y$ are the sets containing $x$ and $y$, then we remove both of these sets from our collection of sets, and we form a new set $S \defeq S_x \cup S_y$, which is subsequently added to the collection of sets. What element becomes the representative of the new set? Typically, if $S_x$ was originally larger than $S_y$, then we make the representative of $S_x$ the representative of $S$. Otherwise, we make the representative of $S_y$ the representative of $S$. 22 | \item The \verb!FIND-SET(x)! method takes in an element $x$ and returns the representative of the set containing $x$. Note that this means that \verb!FIND-SET(x)! might return \verb!x! itself (if \verb!x! is the representative of its set). 23 | \end{enumerate} 24 | 25 | 26 | There are several applications of the union-find data structure. One of the many applications arises when we are trying to determine the connected components in an undirected graph. In particular, we can answer queries of the form ``Are vertices $u$ and $v$ in the same connected component?" with a quick running time by using this data structure. \\ 27 | 28 | 29 | Consider the following pseudocode: 30 | 31 | \vspace{1em} 32 | \begin{center} 33 | \line(1,0){400} 34 | \end{center} 35 | 36 | \begin{allintypewriter} 37 | \# Input: A graph G. 38 | 39 | \hspace{0cm} 40 | 41 | \# Output: Nothing. This function is called as a preprocessing step 42 | 43 | \# in order to use the function SAME-COMPONENT(u, v). 44 | 45 | \hspace{0.5cm} 46 | 47 | 48 | CONNECTED-COMPONENTS(G) \string{ 49 | 50 | \hspace{0.5cm} for each vertex v $\in$ G \string{ 51 | 52 | \hspace{1cm} MAKE-SET(v) 53 | 54 | \hspace{0.5cm} \string} 55 | 56 | \hspace{0.5cm} for each edge (u, v) $\in$ G \string{ 57 | 58 | \hspace{1cm} if FIND-SET(u) != FIND-SET(v) \string{ 59 | 60 | \hspace{1.5cm} UNION(u, v) 61 | 62 | \hspace{1cm} \string} 63 | 64 | \hspace{0.5cm} \string} 65 | 66 | \string} 67 | 68 | \hspace{0cm} 69 | 70 | \# Input: Two vertices u and v. CONNECTED-COMPONENTS(G) must be 71 | 72 | \# called prior to using this function. 73 | 74 | \hspace{0cm} 75 | 76 | \# Output: True if u and v are in the same connected component; 77 | 78 | \# otherwise false. 79 | 80 | \hspace{0cm} 81 | 82 | SAME-COMPONENT(u, v) \string{ 83 | 84 | \hspace{0.5cm} return (FIND-SET(u) == FIND-SET(v)) 85 | 86 | \string} 87 | \end{allintypewriter} 88 | 89 | \begin{center} 90 | \line(1,0){400} 91 | \end{center} 92 | 93 | 94 | How does these functions work? 95 | 96 | \begin{itemize} 97 | \item We use a single union-find data structure that is initially empty. At first, we create a new disjoint set for each vertex. Each disjoint set in our union-find data structure will represent a connected component in our graph. 98 | \item Next, we traverse every edge in our graph $G$. For each edge $(u, v)$, we merge the two disjoint sets containing $u$ and $v$ (since they must be in the same component). 99 | \item Finally, we can call the \verb!SAME-COMPONENT! function with two vertices $u$ and $v$ which simply compare the representatives of the sets $u$ and $v$ are in to determine whether the two vertices are in the same component. 100 | \end{itemize} 101 | 102 | \subsubsection{Implementation of the Union-Find Data Structure} 103 | 104 | In our connected components example, we use a union-find data structure, but we never explain how the functions \verb!MAKE-SET!, \verb!UNION!, or \verb!FIND-SET! are implemented. In this section, we'll discuss how to implement these three methods. \\ 105 | 106 | Union-find data structures are typically implemented as a \vocab{disjoint-set forest} in which each member only points to its parent (the root of each tree is the representative of the disjoint set, and it is its own parent). The following figure from CLRS illustrates this idea: 107 | 108 | 109 | \begin{figure}[h] 110 | \centering 111 | \includegraphics[scale=0.4]{media/ufds1} 112 | \caption{A Disjoint Forest} 113 | \end{figure} 114 | 115 | The disjoint-set forest above represents the two sets $\{c, h, b, e\}$ with representative $c$ and $\{f, d, g\}$ with representative $f$. Note that the parent of any representative is itself. \\ 116 | 117 | How do we keep track of the parent of each vertex? This is easy --- we can just include an array called \verb!parent! as a part of our data structure implementation. For any vertex $v$, we can store the parent of $v$ in \verb!parent[v]!. \\ 118 | 119 | Now, we will discuss two heuristics to improve the running-time of various union-find operations. The first heuristic, known as the \vocab{union by rank heuristic}, is a heuristic that is applied when performing the \verb!UNION! operation. In particular, this heuristic specifies to make the root of the tree with fewer nodes to point to the root of the tree with more nodes. Why? Because following the union by rank heuristic minimizes the overall depth of the resulting tree. \\ 120 | 121 | 122 | The following diagram illustrates the resulting tree that comes from performing the \verb!UNION! operation on two elements in the disjoint sets from the previous figure: 123 | \newpage 124 | 125 | \begin{figure}[h] 126 | \centering 127 | \includegraphics[scale=0.4]{media/ufds2} 128 | \caption{Our Disjoint Forest after performing \texttt{UNION}} 129 | \end{figure} 130 | 131 | Note that $f$ is the representative of the resulting tree since we make the tree with fewer nodes point to the root of the tree with more nodes. \\ 132 | 133 | It would be computationally expensive to keep on recomputing the number of roots in each tree whenever we perform a \verb!UNION! operation. Thus, we can instead just maintain an array \verb!rank! which stores an upper bound on the height of each node. During a \verb!UNION! operation, we simply make the root with a smaller rank point to the root with the larger rank. \\ 134 | 135 | 136 | The second heuristic, known as \vocab{path compression} is a heuristic that is used during \verb!FIND-SET! operations to make each node on the find path point directly to the root. This technique is fairly easy to implement, and its purpose is to keep the depth of the tree small. \\ 137 | 138 | 139 | A C++ implementation of the union-find data structure is presented below: 140 | 141 | 142 | \begin{lstlisting} 143 | /* An implementation of the union-find data structure. */ 144 | class UnionFind { 145 | private: 146 | vector parent; 147 | vector rank; 148 | public: 149 | /* A constructor to initialize a union-find data structure with capacity N. */ 150 | UnionFind(int N) { 151 | parent.assign(N, 0); 152 | rank.assign(N, 0); 153 | 154 | /* Each vertex is initially its own parent. */ 155 | for (int i = 0; i < N; i++) { 156 | parent[i] = i; 157 | } 158 | } 159 | 160 | /* findSet(u) returns the representative of the set that u belongs to. */ 161 | int findSet(int u) { 162 | if (parent[u] == u) { 163 | /* u is the representative of its set. */ 164 | return u; 165 | } 166 | /* Path compression heuristic. */ 167 | return parent[u] = findSet(parent[u]); 168 | } 169 | 170 | /* inSameSet(u, v) returns true if u and v are in the same set; false otherwise. */ 171 | bool inSameSet(int u, int v) { 172 | /* We compare the set representatives. */ 173 | return findSet(u) == findSet(v); 174 | } 175 | 176 | /* Union the sets that u and v belong in. */ 177 | void unionSet(int u, int v) { 178 | if (!inSameSet(u, v)) { 179 | int rep1 = findSet(u); 180 | int rep2 = findSet(v); 181 | /* Union by rank heuristic. */ 182 | if (rank[rep1] > rank[rep2]) { 183 | parent[rep2] = rep1; 184 | } else { 185 | parent[rep1] = rep2; 186 | if (rank[rep1] == rank[rep2]) { 187 | rank[rep2]++; 188 | } 189 | } 190 | } 191 | } 192 | }; 193 | \end{lstlisting} 194 | 195 | 196 | \subsubsection{Analysis of Union-Find Operations} 197 | 198 | 199 | In order to discuss the running time of each of the union-find operations, we will need to use the \vocab{inverse Ackermann function}, denoted $\alpha(n)$. For our purposes, all we need to know is that this is an \textit{extremely} slowly growing function (for all practical purposes, its value never exceeds $5$). \\ 200 | 201 | While we won't derive the bound, we will take it for granted that the \verb!UNION! and \verb!FIND-SET! operations run in $\mathcal{O}(\alpha(n))$ time (approximately constant time). A full derivation is provided in CLRS $21.4$. 202 | 203 | 204 | 205 | \subsection{The Minimum Spanning Tree Problem} 206 | 207 | \subsubsection{Problem Statement} 208 | 209 | The \vocab{minimum spanning tree} problem is stated as follows: 210 | \begin{quote} 211 | ``Given a graph $G = (V_1, E_1)$, find a connected subgraph $H = (V_2, E_2)$ such that $V_1 = V_2$ and the quantity 212 | \[ 213 | \sum_{(u, v) \in E_2} \texttt{weight}(u, v) 214 | \] 215 | is as minimal as possible." 216 | \end{quote} 217 | 218 | The following proposition shows that $H$ will always be a tree: 219 | 220 | \begin{proposition} 221 | If $H = (V_2, E_2)$ is a connected subgraph of $G = (V_1, E_1)$ with the properties described above, then $H$ is a tree. 222 | \end{proposition} 223 | \begin{proof} 224 | By definition, $H$ must be connected. Thus, it suffices to show that $H$ doesn't have any cycles. Suppose $H$ contained a cycle $C$. Let $e$ be an edge on $C$, and consider the graph $H \setminus \{e\}$. This graph is still connected since removing an edge in a cycle can't disconnect a graph, but this graph is also ``cheaper" than $H$; this is a contradiction. 225 | \end{proof} 226 | 227 | A simple brute force algorithm to find the minimum spanning tree would work by generating each possible spanning tree and storing the generated tree if its cost is less than our previously stored minimum. Unfortunately, this algorithm is not feasible since graph has exponentially many different spanning trees. Thus, we are compelled to look for more efficient solutions. \\ 228 | 229 | Tody, we will discuss \vocab{Kruskal's algorithm} and \vocab{Prim's algorithm}, both of which are used to find the minimum spanning tree of a graph. \\ 230 | 231 | Both of these algorithms are classified as \vocab{greedy algorithms} --- they repeatedly make locally optimal choices in an attempt to find a globally optimal solution. 232 | 233 | 234 | \subsubsection{Kruskal's Algorithm} 235 | 236 | Let $S$ be an initially empty set, and let $G = (V, E)$ be our graph. Kruskal's algorithm works by iteratively adding edges the least weight to $S$ as long as $(u, v)$ does not form a cycle with any of the other edges in $S$. The algorithm terminates when adding any edge in $E \setminus S$ to $S$ would result in a cycle. \\ 237 | 238 | How do we quickly check if adding an edge $(u, v)$ to $S$ will result in a cycle? This can be done quite easily using the union-find data structure. \\ 239 | 240 | The pseudocode for Kruskal's algorithm is below: 241 | 242 | 243 | \vspace{1em} 244 | \begin{center} 245 | \line(1,0){400} 246 | \end{center} 247 | 248 | \begin{allintypewriter} 249 | \# Input: A graph G. 250 | 251 | \hspace{0cm} 252 | 253 | \# Output: A set of edges that form a minimum spanning tree of G. 254 | 255 | \hspace{0.5cm} 256 | 257 | 258 | KRUSKAL(G) \string{ 259 | 260 | \hspace{0.5cm} let S be an empty set. 261 | 262 | \hspace{0cm} 263 | 264 | \hspace{0.5cm} for each vertex v $\in$ G \string{ 265 | 266 | \hspace{1cm} MAKE-SET(v) 267 | 268 | \hspace{0.5cm} \string} 269 | 270 | \hspace{0cm} 271 | 272 | \hspace{0.5cm} sort the edges in G.Edges into nondecreasing order by weight. 273 | 274 | \hspace{0cm} 275 | 276 | \hspace{0.5cm} for each edge (u, v) $\in$ G.Edges taken in sorted order \string{ 277 | 278 | \hspace{1cm} if FIND-SET(u) != FIND-SET(v) \string{ 279 | 280 | \hspace{1.5cm} \# (u, v) won't form a cycle. 281 | 282 | \hspace{1.5cm} Add the edge (u, v) to S. 283 | 284 | \hspace{1.5cm} UNION(u, v) 285 | 286 | \hspace{1cm} \string} 287 | 288 | \hspace{0.5cm} \string} 289 | 290 | \hspace{0.5cm} return S 291 | 292 | \string} 293 | 294 | \hspace{0cm} 295 | 296 | \end{allintypewriter} 297 | \begin{center} 298 | \line(1,0){400} 299 | \end{center} 300 | 301 | As mentioned earlier, there isn't too much to this algorithm: 302 | 303 | \begin{enumerate} 304 | \item First, we sort the edges in non-decreasing order by weight so that we can traverse the list of edges from lowest weight to highest weight. 305 | \item For each weight we look at, we check whether we can add the weight without adding a cycle. This is done by maintaining a union-find data structure. 306 | \item Finally, we return the set of edges that form our minimum spanning tree. 307 | \end{enumerate} 308 | 309 | 310 | How fast is Kruskal's algorithm? Firstly, note that the first for-loop performs $V$ \verb!MAKE-SET! operations. Subsequently, we sort the list of edges; doing so requires $\mathcal{O}(E\log(E))$ time. Finally, the second for-loop performs $O(E)$ \verb!FIND-SET! and \verb!UNION! operations. Putting everything together, we have a runtime of $\mathcal{O}((V + E)\alpha(V))$ time. But since $\alpha(|V|) = \O(\log(V)) = \O(\log(E))$ (where the second equality follows due to the fact that $|E| \geq |V| - 1$ in a connected graph), the total running time of Kruskal's algorithm as $\mathcal{O}(E\log(E))$. 311 | 312 | 313 | \subsubsection{Prim's Algorithm} 314 | 315 | Prim's algorithm works by starting with an empty set $S$ and iteratively adding edges to $S$ until our minimum spanning tree is complete. We start by adding an arbitrary vertex to $S$, and at each step we add a vertex that is connected to some other vertex in $S$. \\ 316 | 317 | 318 | Some pseudocode illustrating how Prim's algorithm works is shown below: 319 | 320 | \vspace{1em} 321 | \begin{center} 322 | \line(1,0){400} 323 | \end{center} 324 | 325 | \begin{allintypewriter} 326 | \# Input: A graph G and a source vertex v. 327 | 328 | \hspace{0cm} 329 | 330 | \# Output: A set S containing the edges that represent a minimum 331 | 332 | \# spanning tree. 333 | 334 | \hspace{0.5cm} 335 | 336 | 337 | PRIM(G, v) \string{ 338 | 339 | \hspace{0.5cm} let key[1...V] be an array. 340 | 341 | \hspace{0.5cm} let Q be an empty minimum priority queue 342 | 343 | \hspace{0cm} 344 | 345 | \hspace{0.5cm} for each vertex u $\in$ G \string{ 346 | 347 | \hspace{1cm} key[u] = $\infty$ 348 | 349 | \hspace{1cm} parent[u] = NIL 350 | 351 | \hspace{1cm} enqueue u into Q. 352 | 353 | \hspace{0.5cm} \string} 354 | 355 | \hspace{0.5cm} key[v] = 0 356 | 357 | \hspace{0.5cm} 358 | 359 | \hspace{0.5cm} \# This is the main loop. 360 | 361 | \hspace{0.5cm} while Q isn't empty \string{ 362 | 363 | \hspace{1cm} let u = EXTRACT-MIN(Q) 364 | 365 | \hspace{1cm} for each vertex v in Adj[u] \string{ 366 | 367 | \hspace{1.5cm} if v $\in$ Q and weight(u, v) < key[v] \string{ 368 | 369 | \hspace{2cm} key[v] = weight(u, v) 370 | 371 | \hspace{2cm} parent[v] = u 372 | 373 | \hspace{1.5cm} \string} 374 | 375 | \hspace{1cm} \string} 376 | 377 | \hspace{0.5cm} \string} 378 | 379 | \string} 380 | \end{allintypewriter} 381 | \begin{center} 382 | \line(1,0){400} 383 | \end{center} 384 | 385 | How does this algorithm work? 386 | 387 | \begin{itemize} 388 | \item We start building our minimum spanning tree from an arbitrary vertex $v$. This vertex is passed in as a parameter to our function. 389 | \item Next, we process enqueue all of our vertices into a minimum priority queue $Q$ which allows us to extract elements with the minimum \verb!key! value in logarithmic time. 390 | \item While $Q$ isn't empty, we take the vertex with the smallest \verb!key! value from $Q$; denote this vertex by $u$. Note that, on the first iteration, the vertex we grab is always $v$. 391 | \item For each neighbor $v$ of $u$, we check whether the edge $(u, v)$ is cheaper than the stored \verb!key! value of $v$. If so, we update the key value of $v$ to the weight of edge \verb!(u, v)!. We additionally store the vertex $u$ from which we took $v$. 392 | \end{itemize} 393 | 394 | The purpose of the minimum priority queue is to iteratively identify the cheapest edge that we can add to our minimum spanning tree. The \verb!key! value of a vertex $v$ represents the ``cheapest" amount that we can pay in order to add that vertex to our spanning tree. \\ 395 | 396 | Prim's algorithm greedily selects the pair $(u, v)$ in front of the priority queue---which has the minimum weight $w$---if the end point of this edge, namely $v$, has not been taken before. When the \verb!while! loop terminates, the minimum spanning tree consists of the set of edges 397 | \[ 398 | A = \{(v, \verb!parent![v]) \mid v\in V - \{r\} - Q\}. 399 | \] -------------------------------------------------------------------------------- /ekesh.sty: -------------------------------------------------------------------------------- 1 | % style file adapted from https://github.com/vEnhance's file 2 | \ProvidesPackage{ekesh} 3 | \usepackage{amsmath,amssymb} 4 | \newif\ifevanfancy\evanfancytrue 5 | \newif\ifevanhdr\evanhdrtrue 6 | \newif\ifevanhref\evanhreftrue 7 | \newif\ifevansetup\evansetuptrue 8 | \newif\ifevanthm\evanthmtrue 9 | \newif\ifevansecthm\evansecthmfalse 10 | \newif\ifevanht\evanhtfalse 11 | \newif\ifevanpkg\evanpkgtrue 12 | \newif\ifevanpdf\evanpdftrue 13 | \newif\ifevantitling\evantitlingtrue 14 | \newif\ifevanauthor\evanauthortrue 15 | \newif\ifevanchinese\evanchinesefalse 16 | \newif\ifevanmdthm\evanmdthmfalse 17 | \newif\ifevandiagrams\evandiagramsfalse 18 | \newif\ifevanpatchasy\evanpatchasyfalse 19 | \newif\ifevanhints\evanhintsfalse 20 | \newif\ifevanasy\evanasytrue 21 | \newif\ifevancolorsec\evancolorsecfalse 22 | \newif\ifevantitlemark\evantitlemarktrue 23 | 24 | %Receive Arguments 25 | \DeclareOption{chinese}{\evanhreffalse\evanchinesetrue} % Chinese support 26 | % allow href to override this one 27 | 28 | \DeclareOption{sexy}{\evansecthmtrue\evanmdthmtrue\evancolorsectrue} % long docs 29 | 30 | \DeclareOption{fancy}{\evanfancytrue} 31 | \DeclareOption{nofancy}{\evanfancyfalse} 32 | \DeclareOption{hdr}{\evanhdrtrue} 33 | \DeclareOption{nohdr}{\evanhdrfalse} 34 | \DeclareOption{href}{\evanhreftrue} 35 | \DeclareOption{nohref}{\evanhreffalse} 36 | 37 | \DeclareOption{nosetup}{\evansetupfalse} 38 | \DeclareOption{thm}{\evanthmtrue} 39 | \DeclareOption{nothm}{\evanthmfalse} 40 | \DeclareOption{secthm}{\evansecthmtrue} 41 | \DeclareOption{nosecthm}{\evansecthmfalse} 42 | 43 | \DeclareOption{ht}{\evanhttrue} 44 | \DeclareOption{nopdf}{\evanpdffalse} 45 | \DeclareOption{nopkg}{\evanpkgfalse} 46 | \DeclareOption{oldtitle}{\evantitlingfalse} 47 | \DeclareOption{newtitle}{\evantitlingtrue} 48 | \DeclareOption{noauthor}{\evanauthorfalse} 49 | \DeclareOption{titlemark}{\evantitlemarktrue} % Sets title in ohead, not \rightmark 50 | \DeclareOption{sectionmark}{\evantitlemarkfalse} % Uses \rightmark not title in ohead 51 | 52 | \DeclareOption{mdthm}{\evanmdthmtrue} 53 | \DeclareOption{nomdthm}{\evanmdthmfalse} 54 | \DeclareOption{diagrams}{\evandiagramstrue} 55 | \DeclareOption{nodiagrams}{\evandiagramsfalse} 56 | \DeclareOption{colorsec}{\evancolorsectrue} 57 | \DeclareOption{nocolorsec}{\evancolorsecfalse} 58 | 59 | \DeclareOption{patchasy}{\evanpatchasytrue} 60 | \DeclareOption{noasy}{\evanasyfalse} 61 | 62 | \DeclareOption{hints}{\evanhintstrue} 63 | 64 | \ProcessOptions\relax 65 | 66 | % if packages not loaded, turn off mdthm and asy 67 | \ifevanpkg\else\evanmdthmfalse\fi 68 | \ifevanpkg\else\evanasyfalse\fi 69 | 70 | % If no setup, turn off theorems 71 | \ifevansetup\else\evanthmfalse\fi 72 | 73 | %%fakesection Some macros 74 | %Small commands 75 | \newcommand{\cbrt}[1]{\sqrt[3]{#1}} 76 | \newcommand{\floor}[1]{\left\lfloor #1 \right\rfloor} 77 | \newcommand{\ceiling}[1]{\left\lceil #1 \right\rceil} 78 | \newcommand{\mailto}[1]{\href{mailto:#1}{\texttt{#1}}} 79 | \newcommand{\ol}{\overline} 80 | \newcommand{\ul}{\underline} 81 | \newcommand{\wt}{\widetilde} 82 | \newcommand{\wh}{\widehat} 83 | \newcommand{\eps}{\varepsilon} 84 | %\renewcommand{\iff}{\Leftrightarrow} 85 | %\renewcommand{\implies}{\Rightarrow} 86 | \newcommand{\vocab}[1]{\textbf{\color{blue} #1}} 87 | \providecommand{\alert}{\vocab} 88 | \providecommand{\half}{\frac{1}{2}} 89 | \newcommand{\catname}{\mathsf} 90 | \newcommand{\hrulebar}{ 91 | \par\hspace{\fill}\rule{0.95\linewidth}{.7pt}\hspace{\fill} 92 | \par\nointerlineskip \vspace{\baselineskip} 93 | } 94 | 95 | %For use in author command 96 | \newcommand{\plusemail}[1]{\\ \normalfont \texttt{\mailto{#1}}} 97 | 98 | %More commands and math operators 99 | \DeclareMathOperator{\cis}{cis} 100 | \DeclareMathOperator{\OPT}{OPT} 101 | \DeclareMathOperator*{\lcm}{lcm} 102 | \DeclareMathOperator*{\argmin}{arg min} 103 | \DeclareMathOperator*{\argmax}{arg max} 104 | 105 | %Convenient Environments 106 | \newenvironment{soln}{\begin{proof}[Solution]}{\end{proof}} 107 | \newenvironment{parlist}{\begin{inparaenum}[(i)]}{\end{inparaenum}} 108 | \newenvironment{gobble}{\setbox\z@\vbox\bgroup}{\egroup} 109 | 110 | %Inequalities 111 | \newcommand{\cycsum}{\sum_{\mathrm{cyc}}} 112 | \newcommand{\symsum}{\sum_{\mathrm{sym}}} 113 | \newcommand{\cycprod}{\prod_{\mathrm{cyc}}} 114 | \newcommand{\symprod}{\prod_{\mathrm{sym}}} 115 | 116 | %From H113 "Introduction to Abstract Algebra" at UC Berkeley 117 | \newcommand{\CC}{\mathbb C} 118 | \newcommand{\FF}{\mathbb F} 119 | \newcommand{\NN}{\mathbb N} 120 | \newcommand{\QQ}{\mathbb Q} 121 | \newcommand{\RR}{\mathbb R} 122 | \newcommand{\ZZ}{\mathbb Z} 123 | \newcommand{\charin}{\text{ char }} 124 | \DeclareMathOperator{\sign}{sign} 125 | \DeclareMathOperator{\Aut}{Aut} 126 | \DeclareMathOperator{\Inn}{Inn} 127 | \DeclareMathOperator{\Syl}{Syl} 128 | \DeclareMathOperator{\Gal}{Gal} 129 | \DeclareMathOperator{\GL}{GL} % General linear group 130 | \DeclareMathOperator{\SL}{SL} % Special linear group 131 | 132 | %From Kiran Kedlaya's "Geometry Unbound" 133 | \newcommand{\abs}[1]{\left\lvert #1 \right\rvert} 134 | \newcommand{\norm}[1]{\left\lVert #1 \right\rVert} 135 | \newcommand{\dang}{\measuredangle} %% Directed angle 136 | \newcommand{\ray}[1]{\overrightarrow{#1}} 137 | \newcommand{\seg}[1]{\overline{#1}} 138 | \newcommand{\arc}[1]{\wideparen{#1}} 139 | 140 | %From M275 "Topology" at SJSU 141 | \newcommand{\id}{\mathrm{id}} 142 | \newcommand{\taking}[1]{\xrightarrow{#1}} 143 | \newcommand{\inv}{^{-1}} 144 | 145 | %From M170 "Introduction to Graph Theory" at SJSU 146 | \DeclareMathOperator{\diam}{diam} 147 | \DeclareMathOperator{\ord}{ord} 148 | \newcommand{\defeq}{\overset{\mathrm{def}}{=}} 149 | 150 | %From the USAMO .tex filse 151 | \newcommand{\ts}{\textsuperscript} 152 | \newcommand{\dg}{^\circ} 153 | \newcommand{\ii}{\item} 154 | 155 | % From Math 55 and Math 145 at Harvard 156 | \newenvironment{subproof}[1][Proof]{% 157 | \begin{proof}[#1] \renewcommand{\qedsymbol}{$\blacksquare$}}% 158 | {\end{proof}} 159 | 160 | \newcommand{\liff}{\leftrightarrow} 161 | \newcommand{\lthen}{\rightarrow} 162 | \newcommand{\opname}{\operatorname} 163 | \newcommand{\surjto}{\twoheadrightarrow} 164 | \newcommand{\injto}{\hookrightarrow} 165 | \newcommand{\On}{\mathrm{On}} % ordinals 166 | \DeclareMathOperator{\img}{im} % Image 167 | \DeclareMathOperator{\Img}{Im} % Image 168 | \DeclareMathOperator{\coker}{coker} % Cokernel 169 | \DeclareMathOperator{\Coker}{Coker} % Cokernel 170 | \DeclareMathOperator{\Ker}{Ker} % Kernel 171 | \DeclareMathOperator{\rank}{rank} 172 | \DeclareMathOperator{\Spec}{Spec} % spectrum 173 | \DeclareMathOperator{\Tr}{Tr} % trace 174 | \DeclareMathOperator{\pr}{pr} % projection 175 | \DeclareMathOperator{\ext}{ext} % extension 176 | \DeclareMathOperator{\pred}{pred} % predecessor 177 | \DeclareMathOperator{\dom}{dom} % domain 178 | \DeclareMathOperator{\ran}{ran} % range 179 | \DeclareMathOperator{\Hom}{Hom} % homomorphism 180 | \DeclareMathOperator{\End}{End} % endomorphism 181 | 182 | % Things Lie 183 | \newcommand{\kb}{\mathfrak b} 184 | \newcommand{\kg}{\mathfrak g} 185 | \newcommand{\kh}{\mathfrak h} 186 | \newcommand{\kn}{\mathfrak n} 187 | \newcommand{\ku}{\mathfrak u} 188 | \newcommand{\kz}{\mathfrak z} 189 | \DeclareMathOperator{\Ext}{Ext} % Ext functor 190 | \DeclareMathOperator{\Tor}{Tor} % Tor functor 191 | \newcommand{\gl}{\opname{\mathfrak{gl}}} % frak gl group 192 | \renewcommand{\sl}{\opname{\mathfrak{sl}}} % frak gl group 193 | 194 | % More script letters etc. 195 | \newcommand{\SA}{\mathscr A} 196 | \newcommand{\SB}{\mathscr B} 197 | \newcommand{\SC}{\mathscr C} 198 | \newcommand{\SD}{\mathscr D} 199 | \newcommand{\SE}{\mathscr E} 200 | \newcommand{\SF}{\mathscr F} 201 | \newcommand{\SG}{\mathscr G} 202 | \newcommand{\SH}{\mathscr H} 203 | \newcommand{\OO}{\mathcal O} 204 | 205 | %% Napkin commands 206 | \newcommand{\prototype}[1]{ 207 | \emph{{\color{red} Prototypical example for this section:} #1} \par\medskip 208 | } 209 | \newenvironment{moral}{% 210 | \begin{mdframed}[linecolor=green!70!black]% 211 | \bfseries\color{green!70!black}}% 212 | {\end{mdframed}} 213 | 214 | 215 | 216 | %%fakesection Asymptote setup 217 | \ifevanasy 218 | \ifevanpatchasy 219 | \usepackage{patch-asy} 220 | \else 221 | \usepackage{asymptote} 222 | \fi 223 | \begin{asydef} 224 | import olympiad; 225 | import cse5; 226 | pointpen = black; 227 | pathpen = black; 228 | pathfontpen = black; 229 | anglepen = black; 230 | anglefontpen = black; 231 | pointfontsize = 10; 232 | defaultpen(fontsize(10pt)); 233 | size(8cm); // set a reasonable default 234 | usepackage("amsmath"); 235 | usepackage("amssymb"); 236 | settings.tex="latex"; 237 | settings.outformat="pdf"; 238 | \end{asydef} 239 | \fi 240 | \ifevanthm 241 | \usepackage{amsthm} 242 | \fi 243 | 244 | %%fakesection BEGIN MAIN SETUP 245 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 246 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 247 | 248 | \ifevansetup 249 | %%fakesection Set up author and date 250 | \ifevanauthor 251 | \AtBeginDocument{% 252 | \author{Evan Chen} 253 | \date{\today} 254 | } 255 | \fi 256 | %%fakesection Hyperref 257 | \ifevanpkg 258 | \PassOptionsToPackage{usenames,svgnames,dvipsnames}{xcolor} 259 | \usepackage{xcolor} 260 | \ifevanhref 261 | \usepackage[colorlinks=true]{hyperref} 262 | \hypersetup{urlcolor=RubineRed,linkcolor=RoyalBlue,citecolor=ForestGreen} 263 | \fi 264 | \usepackage[nameinlink]{cleveref} 265 | \fi 266 | 267 | %%fakesection New Theorem Styles -- mdbluebox, mdrecbox, mdinlinebox 268 | \ifevanmdthm 269 | \usepackage{thmtools} 270 | \usepackage[framemethod=TikZ]{mdframed} 271 | 272 | \mdfdefinestyle{mdbluebox}{% 273 | roundcorner = 10pt, 274 | linewidth=1pt, 275 | skipabove=12pt, 276 | innerbottommargin=9pt, 277 | skipbelow=2pt, 278 | linecolor=blue, 279 | nobreak=true, 280 | backgroundcolor=TealBlue!5, 281 | } 282 | \declaretheoremstyle[ 283 | headfont=\sffamily\bfseries\color{MidnightBlue}, 284 | mdframed={style=mdbluebox}, 285 | headpunct={\\[3pt]}, 286 | postheadspace={0pt} 287 | ]{thmbluebox} 288 | 289 | \mdfdefinestyle{mdrecbox}{% 290 | linewidth=0.5pt, 291 | skipabove=12pt, 292 | frametitleaboveskip=5pt, 293 | frametitlebelowskip=0pt, 294 | skipbelow=2pt, 295 | frametitlefont=\bfseries, 296 | innertopmargin=4pt, 297 | innerbottommargin=8pt, 298 | nobreak=true, 299 | backgroundcolor=Salmon!5, 300 | } 301 | \declaretheoremstyle[ 302 | headfont=\bfseries\color{RawSienna}, 303 | mdframed={style=mdrecbox}, 304 | headpunct={\\[3pt]}, 305 | postheadspace={0pt}, 306 | ]{thmrecbox} 307 | 308 | \mdfdefinestyle{mdinlinebox}{% 309 | skipabove=8pt, 310 | linewidth=3pt, 311 | rightline=false, 312 | leftline=true, 313 | topline=false, 314 | bottomline=false, 315 | linecolor=green, 316 | backgroundcolor=green!2 317 | } 318 | \declaretheoremstyle[ 319 | headfont=\bfseries\color{Cerulean}, 320 | bodyfont=\normalfont\small, 321 | spaceabove=0pt, 322 | spacebelow=0pt, 323 | mdframed={style=mdinlinebox} 324 | ]{thminlinebox} 325 | 326 | \newcommand{\listhack}{$\empty$\vspace{-2em}} 327 | \fi 328 | 329 | %%fakesection Theorem setup 330 | \ifevanthm 331 | \theoremstyle{plain} 332 | %Branching here: the option secthm changes theorems to be labelled by section 333 | \ifevanmdthm 334 | \ifevansecthm 335 | \declaretheorem[% 336 | style=thmbluebox,name=Theorem,numberwithin=section]{theorem} 337 | \else 338 | \declaretheorem[% 339 | style=thmbluebox,name=Theorem]{theorem} 340 | \fi 341 | \declaretheorem[style=thmbluebox,name=Lemma,sibling=theorem]{lemma} 342 | \declaretheorem[style=thmbluebox,name=Proposition,sibling=theorem]{proposition} 343 | \declaretheorem[style=thmbluebox,name=Corollary,sibling=theorem]{corollary} 344 | \declaretheorem[style=thmbluebox,name=Theorem,numbered=no]{theorem*} 345 | \declaretheorem[style=thmbluebox,name=Lemma,numbered=no]{lemma*} 346 | \declaretheorem[style=thmbluebox,name=Proposition,numbered=no]{proposition*} 347 | \declaretheorem[style=thmbluebox,name=Corollary,numbered=no]{corollary*} 348 | \else 349 | \ifevansecthm 350 | \newtheorem{theorem}{Theorem}[section] 351 | \else 352 | \newtheorem{theorem}{Theorem} 353 | \fi 354 | \newtheorem{lemma}[theorem]{Lemma} 355 | \newtheorem{proposition}[theorem]{Proposition} 356 | \newtheorem{corollary}[theorem]{Corollary} 357 | \newtheorem*{theorem*}{Theorem} 358 | \newtheorem*{lemma*}{Lemma} 359 | \newtheorem*{proposition*}{Proposition} 360 | \newtheorem*{corollary*}{Corollary} 361 | \fi 362 | 363 | %Def-style theorems 364 | \theoremstyle{definition} 365 | 366 | % This ought to be a real theorem, but would be too much italics 367 | \ifevanmdthm 368 | \declaretheorem[style=thmbluebox,name=Algorithm,sibling=theorem]{algorithm} 369 | \else 370 | \newtheorem{algorithm}[theorem]{Algorithm} 371 | \newtheorem*{algorithm*}{Algorithm} 372 | \fi 373 | 374 | \newtheorem{claim}[theorem]{Claim} 375 | \newtheorem{conjecture}[theorem]{Conjecture} 376 | \newtheorem{definition}[theorem]{Definition} 377 | \newtheorem{fact}[theorem]{Fact} 378 | 379 | \newtheorem{answer}[theorem]{Answer} 380 | \newtheorem{case}[theorem]{Case} 381 | \newtheorem{ques}[theorem]{Question} 382 | \newtheorem{exercise}[theorem]{Exercise} 383 | \newtheorem{problem}[theorem]{Problem} 384 | 385 | \newtheorem*{answer*}{Answer} 386 | \newtheorem*{case*}{Case} 387 | \newtheorem*{claim*}{Claim} 388 | \newtheorem*{conjecture*}{Conjecture} 389 | \newtheorem*{definition*}{Definition} 390 | \newtheorem*{fact*}{Fact} 391 | \newtheorem*{joke*}{Joke} 392 | \newtheorem*{ques*}{Question} 393 | \newtheorem*{exercise*}{Exercise} 394 | \newtheorem*{problem*}{Problem} 395 | 396 | 397 | \ifevanmdthm 398 | \declaretheorem[style=thmrecbox,name=Example,sibling=theorem]{example} 399 | \declaretheorem[style=thmrecbox,name=Example,numbered=no]{example*} 400 | \else 401 | \newtheorem{example}[theorem]{Example} 402 | \newtheorem*{example*}{Example} 403 | \fi 404 | 405 | % Remark-style theorems 406 | %\theoremstyle{remark} 407 | \newtheorem{note}[theorem]{Note} 408 | \newtheorem{remark}[theorem]{Remark} 409 | \newtheorem*{note*}{Note} 410 | \newtheorem*{remark*}{Remark} 411 | \newtheorem{abuse}[theorem]{Abuse of Notation} 412 | \newtheorem*{abuse*}{Abuse of Notation} 413 | \fi 414 | 415 | %%fakesection Fancy section and chapter heads 416 | \ifevancolorsec 417 | \@ifundefined{KOMAClassName}{}{ 418 | \@ifundefined{chapter}{}{ 419 | \addtokomafont{partprefix}{\rmfamily} 420 | \renewcommand*{\partformat}{\color{purple} 421 | \scalebox{2.5}{\thepart}\enlargethispage{2em}} 422 | \addtokomafont{chapterprefix}{\raggedleft} 423 | \RedeclareSectionCommand[beforeskip=0.5em]{chapter} 424 | \renewcommand*{\chapterformat}{\mbox{% 425 | \scalebox{1.5}{\chapappifchapterprefix{\nobreakspace}}% 426 | \scalebox{2.718}{\color{purple}\thechapter}\enskip}} 427 | } 428 | \renewcommand*{\sectionformat}% 429 | {\color{purple}\S\thesection\enskip} 430 | \renewcommand*{\subsectionformat}% 431 | {\color{purple}\S\thesubsection\enskip} 432 | \renewcommand*{\subsubsectionformat}% 433 | {\color{purple}\S\thesubsubsection\enskip} 434 | \KOMAoptions{numbers=noenddot} 435 | \usepackage[tocindentauto]{tocstyle} 436 | \usetocstyle{KOMAlike} 437 | } 438 | \fi 439 | 440 | 441 | %%fakesection Loads a bunch of useful packages (but allow disabling) 442 | \ifevanpkg 443 | \IfFileExists{von.sty}{\usepackage{von}}{} 444 | \usepackage{listings} 445 | \usepackage{mathrsfs} 446 | \usepackage{textcomp} 447 | \lstset{basicstyle=\footnotesize\ttfamily, 448 | numbers=left, 449 | numbersep=5pt, 450 | numberstyle=\tiny, 451 | keywordstyle=\bfseries, 452 | % title=\lstname, 453 | showstringspaces=false, 454 | tabsize=4, 455 | frame=single, 456 | keywordstyle=\bfseries\color{blue}, 457 | commentstyle=\color{green!70!black}, 458 | identifierstyle=\color{green!20!black}, 459 | stringstyle=\color{orange}, 460 | breaklines=true, 461 | breakatwhitespace=true, 462 | frame=none 463 | } 464 | \usepackage[shortlabels]{enumitem} 465 | % a list I like for walkthrough's --- Drew-style parts 466 | \newlist{walk}{enumerate}{3} 467 | \setlist[walk]{label=\bfseries (\alph*)} 468 | \usepackage[obeyFinal,textsize=scriptsize,shadow]{todonotes} 469 | \usepackage{textcomp} 470 | % Tiny tiny optimizations: 471 | \usepackage{mathtools} 472 | \usepackage{microtype} 473 | \fi 474 | 475 | %%fakesection \maketitle configuration 476 | \ifevantitling 477 | %If we have titling... 478 | \usepackage{titling} 479 | \@ifundefined{KOMAClassName}{}{ 480 | \newcommand{\thesubtitle}{} 481 | \@ifundefined{chapter}{ 482 | % No \chapter, so small style 483 | \renewcommand{\subtitle}[1]{% 484 | \renewcommand{\thesubtitle}{#1} 485 | \pretitle{\begin{center}\bfseries \sffamily \Large} 486 | \posttitle{\\ {\normalsize \thesubtitle} \end{center}\vspace{\posttitledrop}} 487 | } 488 | }{ 489 | % \chapter exists, so large style 490 | \renewcommand{\subtitle}[1]{% 491 | \renewcommand{\thesubtitle}{#1} 492 | \pretitle{\begin{center}\bfseries \sffamily \Huge} 493 | \posttitle{\\ {\Large \thesubtitle} \end{center}\vspace{\posttitledrop}} 494 | } 495 | } 496 | } 497 | \newlength{\posttitledrop} 498 | \newlength{\postauthordrop} 499 | \@ifundefined{chapter}{ 500 | % No \chapter, so small style 501 | \setlength{\posttitledrop}{-0.7em} 502 | \setlength{\postauthordrop}{-1.2em} 503 | \pretitle{\begin{center}\bfseries \sffamily \Large} 504 | \posttitle{\end{center}\vspace{\posttitledrop}} 505 | \preauthor{\begin{center} \scshape} 506 | \postauthor{\end{center}\vspace{\postauthordrop}} 507 | \predate{\begin{center}\small} 508 | \postdate{\end{center}} 509 | }{ 510 | % \chapter exists, so large style 511 | \setlength{\droptitle}{-8cm} 512 | \setlength{\posttitledrop}{1em} 513 | \setlength{\postauthordrop}{0.5em} 514 | \pretitle{\begin{center}\bfseries \sffamily \Huge} 515 | \posttitle{\end{center}\vspace{\posttitledrop}} 516 | \preauthor{\begin{center} \scshape \LARGE} 517 | \postauthor{\end{center}\vspace{\postauthordrop}} 518 | \predate{\begin{center} \Large} 519 | \postdate{\end{center}} 520 | } 521 | \fi 522 | 523 | %%fakesection Commutative diagrams support 524 | \ifevandiagrams 525 | \usepackage{diagrams} 526 | \newarrow{Inj}C---> 527 | \newarrow{Surj}----{>>} 528 | \newarrow{Proj}----{>>} 529 | \newarrow{Embed}>---> 530 | \newarrow{Incl}C---> 531 | \newarrow{Mapsto}|---> 532 | \newarrow{Isom}<---> 533 | \newarrow{Dotted}....> 534 | \newarrow{Dashed}{}{dash}{}{dash}> 535 | \newarrow{Line}----- 536 | \usepackage{tikz-cd} 537 | \usetikzlibrary{decorations.pathmorphing} 538 | \tikzcdset{row sep/normal=3.14em,column sep/normal=3.14em} 539 | \fi 540 | 541 | %%fakesection Page Setup 542 | \ifevanfancy 543 | \@ifundefined{KOMAClassName} 544 | { 545 | \usepackage{fancyhdr} 546 | \setlength{\headheight}{0.75in} 547 | \setlength{\oddsidemargin}{0in} 548 | \setlength{\evensidemargin}{0in} 549 | \setlength{\voffset}{-1.0in} 550 | \setlength{\headsep}{10pt} 551 | \setlength{\textwidth}{6.5in} 552 | \setlength{\headwidth}{6.5in} 553 | \setlength{\textheight}{8.75in} 554 | \setlength{\parskip}{1ex plus 0.5ex minus 0.2ex} 555 | \setlength{\footskip}{0.3in} 556 | \ifevantitling 557 | \addtolength{\posttitledrop}{0.4em} 558 | \addtolength{\postauthordrop}{0.2em} 559 | \fi 560 | \ifevanhdr 561 | \renewcommand{\headrulewidth}{0.5pt} 562 | \renewcommand{\footrulewidth}{0.0pt} 563 | \pagestyle{fancy} 564 | \ifevantitling \lhead{\theauthor} \else \lhead{Evan Chen} \fi 565 | \chead{} 566 | \rhead{\nouppercase{\leftmark}} 567 | \lfoot{} 568 | \cfoot{\thepage} 569 | \rfoot{} 570 | \fi 571 | } 572 | { 573 | \usepackage[headsepline]{scrlayer-scrpage} 574 | \renewcommand{\headfont}{} 575 | \addtolength{\textheight}{3.14cm} 576 | \setlength{\footskip}{0.5in} 577 | \setlength{\headsep}{10pt} 578 | \ifevantitling 579 | \ihead{\footnotesize\textbf{\theauthor} (\@date)} 580 | \else 581 | \ihead{\footnotesize\textbf{\@author} (\@date)} 582 | \fi 583 | \automark{section} 584 | \chead{} 585 | \ifevantitlemark 586 | \ohead{\footnotesize\textbf{\thetitle}} 587 | \else 588 | \ohead{\footnotesize\textbf{\@title}} 589 | \fi 590 | \cfoot{\pagemark} 591 | } 592 | \fi 593 | 594 | %%fakesection Chinese support 595 | \ifevanchinese 596 | \usepackage[encapsulated]{CJK} 597 | \usepackage{ucs} 598 | \usepackage[utf8x]{inputenc} 599 | \newenvironment{bsmi}{\begin{CJK}{UTF8}{bsmi}}{\end{CJK}} 600 | \newcommand{\cn}[1]{\begin{bsmi}#1\end{bsmi}} 601 | \AtBeginDocument{\begin{CJK}{UTF8}{bsmi}} 602 | \AtEndDocument{\end{CJK}} 603 | \fi 604 | 605 | %%fakesection Hints 606 | \ifevanhints 607 | \usepackage{answers} 608 | \Newassociation{hint}{hintitem}{hints} 609 | \renewcommand{\solutionextension}{out} 610 | \Opensolutionfile{hints} 611 | \newcommand{\makehints}{\Closesolutionfile{hints}\input{hints.out}} 612 | \fi 613 | %%fakesection END MAIN SETUP 614 | \fi --------------------------------------------------------------------------------