[{"content":"Luogu P1093 — Scholarship link: https://www.luogu.com.cn/problem/P1093\n1. Problem Statement There are $n$ Stus, each with three exam scores: Chinese (ch), Math (math), and English (eng). The task is to rank them by the following criteria:\nTotal score — descending (higher is better); Chinese score — descending (tiebreaker for equal totals); Stu ID — ascending (final tiebreaker: smaller ID first). Output the ID and total score of the top $5$ Stus.\nTrivial — $n \\le 300$, any algorithm passes. But I\u0026rsquo;m not here to talk about complexity. I want to talk about three pieces of code that solve exactly the same problem, written roughly 15 years apart, and what the differences between them tell us about what it even means to code in an era where AI writes faster than we type.\n2. A Bit of History: The STL Ban (Pre-2011) Before we look at the code, here\u0026rsquo;s something most people outside the Chinese OI scene don\u0026rsquo;t know.\nCCF banned the use of STL in NOIP before 2011. The official reasoning: std::sort and std::vector hid too much — if you used them, you didn\u0026rsquo;t \u0026ldquo;really understand.\u0026rdquo; I remember my coach: \u0026ldquo;You can\u0026rsquo;t use sort. Write your own quick sort. How else will you learn?\u0026rdquo;\nThe ban lifted in 2011. Overnight, std::sort, std::vector, std::string, std::priority_queue became legal. A generation of OIers who\u0026rsquo;d memorized quick sort by heart could finally breathe.\nThe three code samples below span this divide.\n3. Version A: Hand-Written Quick Sort (Pre-2011) This is the code I grew up typing. No STL. No std::vector. Everything from scratch.\n#include\u0026lt;cstdio\u0026gt; #include\u0026lt;iostream\u0026gt; using namespace std; const int MAXN = 300 + 5; struct Stu{ int ch, math, eng, total, num; const bool operator\u0026lt;(const Stu \u0026amp;other){ if (total != other.total) return total \u0026lt; other.total; else if (ch != other.ch) return ch \u0026lt; other.ch; else return num \u0026gt; other.num; } }; Stu stu[MAXN]; void quick_sort(int l, int r){ if (l \u0026gt;= r) return; Stu pivot = stu[(l + r) \u0026gt;\u0026gt; 1]; int i = l - 1, j = r + 1; while (i \u0026lt; j){ do i++; while(stu[i] \u0026lt; pivot); do j--; while(pivot \u0026lt; stu[j]); if (i \u0026lt; j) { Stu t = stu[i]; stu[i] = stu[j]; stu[j] = t; } } quick_sort(l, j); quick_sort(j + 1, r); } int main(){ int n; cin \u0026gt;\u0026gt; n; for (int i = 1; i \u0026lt;= n; i++){ cin \u0026gt;\u0026gt; stu[i].ch \u0026gt;\u0026gt; stu[i].math \u0026gt;\u0026gt; stu[i].eng; stu[i].total = stu[i].ch + stu[i].math + stu[i].eng; stu[i].num = i; } quick_sort (1, n); for (int i = n; i \u0026gt; n - 5; i--){ Stu x = stu[i]; cout \u0026lt;\u0026lt; x.num \u0026lt;\u0026lt; \u0026#34; \u0026#34; \u0026lt;\u0026lt; x.total \u0026lt;\u0026lt; endl; } } There\u0026rsquo;s a lot going on in this code, but I\u0026rsquo;ll point out just three things.\nFirst, the hand-written quick sort — Hoare partition, pivot cached before swapping, recursion on (l, j) and (j+1, r). Each of these choices was something you got wrong at least once. The pivot-cache bug alone has probably wasted collectively years of debugging across Chinese OI training rooms.\nSecond, the inverted comparator logic. The problem wants descending totals, but quick sort builds ascending order. So smaller totals go first, and then you iterate backwards from the end. The num \u0026gt; other.num to get smaller IDs at the front — that inversion only makes sense when you remember you\u0026rsquo;re printing in reverse. If this reads like solving a puzzle, that\u0026rsquo;s because it is. Every contestant spent mental energy on this inversion that could have gone to something else.\n4. Version B: std::sort Arrives (Post-2011) CCF lifts the ban. std::sort is legal. The sorting burden vanishes. What happens to the rest?\n#include\u0026lt;iostream\u0026gt; #include\u0026lt;algorithm\u0026gt; using namespace std; struct Stu{ int ch, math, eng, total, num; bool operator \u0026lt; (const Stu \u0026amp; other){ if (total != other.total) return total \u0026lt; other.total; else if (ch != other.ch) return ch \u0026lt; other.ch; else return num \u0026gt; other.num ; } }; const int MAXN = 300 + 5; Stu stu[MAXN]; int main (){ int n; cin \u0026gt;\u0026gt; n; for (int i = 1; i \u0026lt;= n; i++){ int ch, math, eng; cin \u0026gt;\u0026gt; ch \u0026gt;\u0026gt; math \u0026gt;\u0026gt; eng; stu[i].ch = ch, stu[i].math = math, stu[i].eng = eng, stu[i].total = ch + math + eng, stu[i].num = i; } sort (stu + 1, stu + n + 1); for (int i = n; i \u0026gt; n - 5; i--){ auto x = stu[i]; cout \u0026lt;\u0026lt; x.num \u0026lt;\u0026lt; \u0026#34; \u0026#34; \u0026lt;\u0026lt; x.total; if (i \u0026gt; n - 5) cout \u0026lt;\u0026lt; endl; } return 0; } The big change is obvious: ~15 lines of quick sort hidden behind a single call to sort(stu + 1, stu + n + 1). Introsort under the hood — $O(n \\log n)$ guaranteed, no pivot bugs, no recursion anxiety.\nI think this is the most honest version of the three. It captures a real moment: a community that had been told for years \u0026ldquo;you must build everything yourself\u0026rdquo; suddenly got permission to use the library, but didn\u0026rsquo;t yet know what else could change. The STL ban ended in 2011; the aesthetic of hand-rolled-everything took another decade to loosen its grip. New tools don\u0026rsquo;t automatically give you new habits. Those are transmitted person to person — coach to Stu, senior to junior — and they lag behind policy by years.\n5. Version C: Modern C++ If I write this today, without contest pressure or historical weight:\n#include \u0026lt;iostream\u0026gt; #include \u0026lt;vector\u0026gt; #include \u0026lt;algorithm\u0026gt; #include \u0026lt;tuple\u0026gt; using namespace std; struct Stu { int id, ch, math, eng, total; }; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); // To facilitate int n; cin \u0026gt;\u0026gt; n; vector\u0026lt;Stu\u0026gt; stu(n); for (int i = 1; i \u0026lt;= n; ++i) { auto\u0026amp; s = stu[i]; cin \u0026gt;\u0026gt; s.ch \u0026gt;\u0026gt; s.math \u0026gt;\u0026gt; s.eng; s.total = s.ch + s.math + s.eng; s.id = i; } sort(stu.begin(), stu.end(), [](const auto\u0026amp; x, const auto\u0026amp; y) { return tie(y.total, y.ch, x.id) \u0026lt; tie(x.total, x.ch, y.id); }); for (int i = 0; i \u0026lt; 5; ++i) cout \u0026lt;\u0026lt; stu[i].id \u0026lt;\u0026lt; \u0026#39; \u0026#39; \u0026lt;\u0026lt; stu[i].total \u0026lt;\u0026lt; \u0026#39;\\n\u0026#39;; } And if you want to see where the language is headed (C++20):\n#include \u0026lt;iostream\u0026gt; #include \u0026lt;vector\u0026gt; #include \u0026lt;algorithm\u0026gt; #include \u0026lt;ranges\u0026gt; #include \u0026lt;tuple\u0026gt; using namespace std; struct Stu { int id, ch, math, eng, total; auto operator\u0026lt;=\u0026gt;(const Stu\u0026amp; other) const { return tie(-total, -ch, id) \u0026lt;=\u0026gt; tie(-other.total, -other.ch, other.id); // The modern style } }; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n; cin \u0026gt;\u0026gt; n; vector\u0026lt;Stu\u0026gt; a(n); for (int i = 0; i \u0026lt; n; ++i) { auto\u0026amp; s = a[i]; cin \u0026gt;\u0026gt; s.ch \u0026gt;\u0026gt; s.math \u0026gt;\u0026gt; s.eng; s.total = s.ch + s.math + s.eng; s.id = i + 1; } ranges::sort(a); for (const auto\u0026amp; s : a | views::reverse | views::take(5)) cout \u0026lt;\u0026lt; s.id \u0026lt;\u0026lt; \u0026#39; \u0026#39; \u0026lt;\u0026lt; s.total \u0026lt;\u0026lt; \u0026#39;\\n\u0026#39;; } Look at the comparator in the C++17 version: tie(y.total, y.ch, x.id) \u0026lt; tie(x.total, x.ch, y.id). That one line says exactly what the problem asks: \u0026ldquo;rank by total descending, then Chinese descending, then ID ascending.\u0026rdquo; No mental inversion. No puzzle-solving. The direction is encoded in which side of the \u0026lt; each field sits on. I didn\u0026rsquo;t invent this trick — I saw it in someone else\u0026rsquo;s code and it stuck — but the point isn\u0026rsquo;t the trick. The point is that the code now reads like the problem statement.\nThe C++20 version goes further: views::reverse | views::take(5) says \u0026ldquo;from the back, take five.\u0026rdquo; ranges::sort(a) says \u0026ldquo;sort the whole thing.\u0026rdquo; The code is the spec. There\u0026rsquo;s nothing to decode.\nAnd the smaller things: vector means no guessing MAXN. ios::sync_with_stdio(false) is automatic now — roughly 10× on input-heavy problems, zero cognitive cost. Everything 0-based, so no +1/-1 arithmetic in your head.\n6. Side by Side Dimension Version A (Pre-2011) Version B (Post-2011) Version C (Modern) Who sorts? You (Hoare quick sort, by hand) std::sort std::sort + lambda or ranges::sort Container Global C array + MAXN + 5 Global C array + MAXN + 5 std::vector Comparator reads like\u0026hellip; A riddle A riddle The problem statement Index base 1 1 0 Lines ~40 ~28 ~22 Constraint STL banned STL allowed No restrictions Mental weight Algorithm + inversion + bounds Inversion + bounds The problem 7. Debugging Chronicle (What Goes Wrong) Stage Version Symptom Root Cause 1 A Infinite loop on sorted input Pivot always extreme; partition unbalanced 2 A Wrong ranking on ties Comparator violates strict weak ordering 3 A Corrupted comparison mid-partition Compared stu[mid] directly — swap overwrote it 4 B num \u0026gt; other.num confusion Inverted iteration + ascending ID = brain-bender 5 B Trailing newline paranoia if (i \u0026gt; n-5) guard — OJ superstition 6 C Compile error on \u0026lt;=\u0026gt; C++17 compiler, C++20 code Meta-cognitive takeaway: The hardest bugs in Versions A and B aren\u0026rsquo;t in the sorting algorithm. They\u0026rsquo;re in the comparator — the part where you, a human, translate \u0026ldquo;descending total, descending Chinese, ascending ID\u0026rdquo; into a chain of if-else and boolean inversions. tie eliminates that translation step entirely. The direction lives in the value, not in the operator. That\u0026rsquo;s not syntax sugar — that\u0026rsquo;s removing an entire class of errors.\n8. What Twenty Years Taught Me — And What AI Changes flowchart LR A[\u0026#34;Pre-2011\u0026lt;br/\u0026gt;Hand-write everything\u0026lt;br/\u0026gt;STL banned\u0026#34;] --\u0026gt; B[\u0026#34;Post-2011\u0026lt;br/\u0026gt;std::sort allowed\u0026lt;br/\u0026gt;Old habits persist\u0026#34;] B --\u0026gt; C[\u0026#34;C++17\u0026lt;br/\u0026gt;tie, lambda, vector\u0026lt;br/\u0026gt;Say what you mean\u0026#34;] C --\u0026gt; D[\u0026#34;C++20\u0026lt;br/\u0026gt;ranges, spaceship\u0026lt;br/\u0026gt;The code is the spec\u0026#34;] D --\u0026gt; E[\u0026#34;AI era\u0026lt;br/\u0026gt;Code as intent\u0026lt;br/\u0026gt;Human as architect\u0026#34;] And perhaps this is the larger pattern behind every technological transition.\nFor most programmers, writing code has always been about turning ideas into executable instructions. In the early days, that meant implementing everything yourself: sorting algorithms, memory management, data structures. You needed to know how the machine worked because there were few layers between your intention and the hardware.\nThen came the era of abstraction. We stopped rewriting quick sort and started calling std::sort. We stopped managing memory manually and started relying on containers and libraries. The skill was no longer just knowing how to build every component from scratch, but knowing which components already existed, how they worked, and when to trust them.\nAI represents the next step of the same evolution.\nThis is not the first time humanity has automated a form of intellectual labor. In the early twentieth century, complex scientific calculations — including the calculations required for projects like nuclear physics — were often performed by teams of human \u0026ldquo;computers.\u0026rdquo; They performed enormous amounts of repetitive arithmetic by hand. But as problems grew beyond human capacity, machines like ENIAC emerged to take over the mechanical computation. The existence of computers did not make mathematics less important. It changed what mattered: from performing calculations to designing models, choosing methods, and understanding results.\nAI coding tools are creating a similar transition.\nThe important shift is not that knowing Hoare\u0026rsquo;s partition scheme has become useless. Just as mathematicians still learn arithmetic, programmers still need to understand algorithms, data structures, and the principles behind the abstractions they use. The difference is where we place our attention.\nPreviously, programming often focused on: Why do I need to write this? How do I implement it correctly?\nToday, increasingly, the important questions become: What is this code actually doing? Why was this approach chosen? How does it affect the entire system? How can it be improved? Is this the right abstraction? Does this design have elegance and clarity?\nThe bottleneck is moving from code production to code judgment.\nFor decades, the industry needed large numbers of programmers who could translate specifications into working code — much like organizations once needed large numbers of human calculators. AI will increasingly handle more of that mechanical translation. What becomes scarce are engineers who can recognize a flawed solution, design a clean architecture, understand trade-offs, and create software with taste.\nThe future programmer is not a faster typist. They are closer to an architect, a critic, and an artist: someone who understands the machine deeply enough to guide it, and understands the problem deeply enough to know what should be built.\nThe goal was never to become a human compiler.\nThe goal was always to become a human who understands. And that is what no mere matrix calculator can ever replace.\n","permalink":"https://xialiao.org/notes/algorithms/algo-notebook-2/","summary":"\u003ch2 id=\"luogu-p1093--scholarship\"\u003eLuogu P1093 — Scholarship\u003c/h2\u003e\n\u003cp\u003elink: \u003ca href=\"https://www.luogu.com.cn/problem/P1093\"\u003ehttps://www.luogu.com.cn/problem/P1093\u003c/a\u003e\u003c/p\u003e\n\u003ch3 id=\"1-problem-statement\"\u003e1. Problem Statement\u003c/h3\u003e\n\u003cp\u003eThere are $n$ Stus, each with three exam scores: Chinese (\u003ccode\u003ech\u003c/code\u003e), Math (\u003ccode\u003emath\u003c/code\u003e), and English (\u003ccode\u003eeng\u003c/code\u003e). The task is to rank them by the following criteria:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003eTotal score\u003c/strong\u003e — descending (higher is better);\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eChinese score\u003c/strong\u003e — descending (tiebreaker for equal totals);\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eStu ID\u003c/strong\u003e — ascending (final tiebreaker: smaller ID first).\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eOutput the ID and total score of the top $5$ Stus.\u003c/p\u003e\n\u003cp\u003eTrivial — $n \\le 300$, any algorithm passes. But I\u0026rsquo;m not here to talk about complexity. I want to talk about three pieces of code that solve exactly the same problem, written roughly 15 years apart, and what the differences between them tell us about what it even means to \u003cem\u003ecode\u003c/em\u003e in an era where AI writes faster than we type.\u003c/p\u003e","title":"Algorithm Notebook 2 — Sorting Stu Records: Three Eras of C++"},{"content":"Luogu P1045 — Mersenne Numbers link: https://www.luogu.com.cn/problem/P1045\n1. Problem Statement A Mersenne number is an integer of the form $2^P - 1$ where $P$ is prime. Given $P$ ($1000 \\lt P \\lt 3,100,000$), the task is to compute:\nThe number of decimal digits of $2^P - 1$; The last $500$ decimal digits of $2^P - 1$, printed in $10$ rows of $50$ digits each, zero-padded on the left if necessary. It is not required to verify the primality of $2^P - 1$ or $P$.\n2. Key Observations \u0026amp; Theoretical Background 2.1 Number of Digits The number of digits of a positive integer $N$ in base $10$ is $\\lfloor \\log_{10} N \\rfloor + 1$. For $N = 2^P - 1$, since subtracting $1$ changes the digit count only when $2^P$ is an exact power of $10$ (which never happens for $P \\ge 1$), we have:\n$$ \\text{digits}(2^P - 1) = \\lfloor P \\cdot \\log_{10} 2 \\rfloor + 1 $$\nThis avoids the need to compute the full high-precision integer just for the digit count.\n2.2 Truncation to the Last 500 Digits High-precision arithmetic can be truncated: when only the last $k$ digits are needed, any carry beyond position $k$ can be discarded. This transforms the time complexity of each multiplication from $O(L^2)$ to $O(k^2)$, where $L$ is the full length (up to $\\approx 9.3 \\times 10^5$) and $k = 500$. The speedup is on the order of $\\left(\\frac{L}{k}\\right)^2 \\approx 3.5 \\times 10^6$.\n2.3 Binary Exponentiation (Fast Power) Computing $2^P$ by repeated multiplication requires $P$ steps — infeasible for $P \\le 3.1 \\times 10^6$. Instead, binary exponentiation reduces the number of multiplications to $O(\\log P)$ by exploiting:\n$$ 2^P = \\prod_{i: b_i = 1} 2^{2^i}, \\quad \\text{where } (b_k b_{k-1} \\dots b_0)_2 = P $$\n2.4 The Trailing Digit of $2^P$ The sequence of the units digit of $2^n$ (for $n \\ge 1$) cycles through ${2, 4, 8, 6}$. Hence $2^P$ always ends in an even digit, and $2^P - 1$ requires no borrow — a simple a[1]-- suffices. Recognizing this removes an unnecessary borrow-loop that would be both error-prone and irrelevant.\n3. Implementation #include \u0026lt;iostream\u0026gt; #include \u0026lt;cstring\u0026gt; #include \u0026lt;cstdio\u0026gt; #include \u0026lt;algorithm\u0026gt; #include \u0026lt;cmath\u0026gt; using namespace std; const int MAXN = 1005; // enough for 500-digit intermediate products struct BigInt { int len; int a[MAXN]; void init(int x) { int index = 1; memset(a, 0, sizeof(a)); while (x) { a[index++] = x % 10; x /= 10; } len = max(index - 1, 1); } int\u0026amp; operator[](int i) { return a[i]; } void flatten(int L) { len = L; for (int i = 1; i \u0026lt;= len; i++) { a[i + 1] += a[i] / 10; a[i] %= 10; } len++; while (len \u0026gt; 1 \u0026amp;\u0026amp; !a[len]) len--; if (len \u0026gt; 500) len = 500; // truncate: we only need last 500 digits } BigInt operator*(const BigInt\u0026amp; other) const { BigInt res; res.init(0); for (int i = 1; i \u0026lt;= len; i++) for (int j = 1; j \u0026lt;= other.len; j++) res.a[i + j - 1] += a[i] * other.a[j]; res.flatten(len + other.len); return res; } }; BigInt quick_pow(BigInt base, int exp) { BigInt res; res.init(1); while (exp) { if (exp \u0026amp; 1) res = res * base; base = base * base; exp \u0026gt;\u0026gt;= 1; } return res; } int main() { int p; cin \u0026gt;\u0026gt; p; BigInt base; base.init(2); BigInt res = quick_pow(base, p); res.a[1]--; // 2^P always even, no borrow needed // output digit count cout \u0026lt;\u0026lt; (int)(p * log10(2)) + 1 \u0026lt;\u0026lt; endl; // output last 500 digits, 10 rows × 50 columns int cnt = 0; for (int i = 500; i \u0026gt;= 1; i--) { printf(\u0026#34;%d\u0026#34;, res[i]); cnt++; if (cnt == 50 \u0026amp;\u0026amp; i \u0026gt; 1) { printf(\u0026#34;\\n\u0026#34;); cnt = 0; } } return 0; } 4. Debugging Chronicle The solution progressed through 5 iterations. The errors, in order of discovery:\nTLE → WA → RE → AC Stage Symptom Root Cause Fix 1 TLE while(p--) linear exponentiation ($\\Theta(P)$) Binary exponentiation ($\\Theta(\\log P)$) 2 WA×5 len = index-- off-by-one; parameter len shadows member; res.a[...] = overwrites instead of accumulating; log10(p) instead of log10(2); cnt == 10 instead of 50 Correct each individually (see §4.1) 3 RE MAXN = 550 while i+j-1 reaches 999 MAXN ≥ 1000 4 WA len = 505 typo, missing \u0026lt;algorithm\u0026gt;, while(!a[len]) no lower-bound guard 500, add header, len \u0026gt; 1 \u0026amp;\u0026amp; Meta-cognitive takeaway: The most instructive failure was not a code bug but a mindset one — reflexively writing a full borrow-subtraction loop for 2^P - 1 without realizing $2^P$ always ends in an even digit, so borrowing is impossible. Pause before coding: does the operand have special structure that simplifies the operation?\n4.1 Key Pitfalls in BigInt Implementation # Location Wrong Right 1 init() len = index-- (post-decrement: use-then-subtract) len = max(index - 1, 1) 2 flatten() signature void flatten(int len) shadows this-\u0026gt;len Rename parameter to int L 3 flatten() body while(!a[l]) (l is the immutable parameter) while(len \u0026gt; 1 \u0026amp;\u0026amp; !a[len]) 4 operator* = a[i] * other.a[j] (assignment) += a[i] * other.a[j] (accumulation) 5 flatten() end if (len \u0026gt; 500) len = 505 (typo) len = 500 6 Header missing \u0026lt;algorithm\u0026gt; for std::max #include \u0026lt;algorithm\u0026gt; 5. Summary of BigInt Design Principles Principle Rationale Use memset + loop-based init Avoids manual zero-reset bugs; consistent initialization Return int\u0026amp; from operator[] Enables both read and write access through the same interface Accumulate with += in multiplication loops Multiple $(i,j)$ pairs may map to the same result position Guard while(!a[len]) with len \u0026gt; 1 Prevents underflow when the value is zero Truncate len after flatten() Only the last $k$ digits influence subsequent operations Explicitly assign this-\u0026gt;len in flatten() Avoids parameter-name shadowing of member variables Mark operator* as const Signals no mutation of operands; enables chaining Luogu P1177 — Quick Sort Link: https://www.luogu.com.cn/problem/P1177\n1. Problem Statement (P1177) Sort $N$ integers in ascending order and output them space-separated. For $100%$ of the testdata, $1 \\leq N \\leq 10^5$, $1 \\le a_i \\le 10^9$.\nThe challenge: writing a sorting algorithm from scratch that passes the anti-quicksort testcases — i.e., already-sorted input, reverse-sorted input, and arrays where all elements are equal.\n2. Algorithm Overview Quick sort is a divide-and-conquer algorithm. It picks a pivot, partitions the array into \u0026ldquo;less than pivot\u0026rdquo; and \u0026ldquo;greater than pivot\u0026rdquo; halves, then recurses on each half. When a subarray has length $\\le 1$, it is trivially sorted.\nflowchart TD A[\u0026#34;quick_sort(l, r)\u0026#34;] --\u0026gt; B{\u0026#34;l \u0026gt;= r ?\u0026#34;} B --\u0026gt;|Yes| C[\u0026#34;return (empty or single-element, stop)\u0026#34;] B --\u0026gt;|No| D[\u0026#34;1. Pick pivot = a[(l+r)/2]\u0026#34;] D --\u0026gt; E[\u0026#34;2. Partition: two pointers i, j move towards each other\u0026#34;] E --\u0026gt; E1[\u0026#34;i scans left, stops at element \u0026gt;= pivot\u0026#34;] E --\u0026gt; E2[\u0026#34;j scans right, stops at element \u0026lt;= pivot\u0026#34;] E1 --\u0026gt; F{\u0026#34;i \u0026lt; j ?\u0026#34;} E2 --\u0026gt; F F --\u0026gt;|Yes| G[\u0026#34;swap(a[i], a[j])\u0026#34;] G --\u0026gt; E1 F --\u0026gt;|No| H[\u0026#34;Partition done, mid = j\u0026#34;] H --\u0026gt; I[\u0026#34;3. Divide and conquer\u0026#34;] I --\u0026gt; I1[\u0026#34;quick_sort(l, mid)\u0026#34;] I --\u0026gt; I2[\u0026#34;quick_sort(mid+1, r)\u0026#34;] I1 --\u0026gt; J[\u0026#34;Left half sorted\u0026#34;] I2 --\u0026gt; K[\u0026#34;Right half sorted\u0026#34;] J --\u0026gt; L[\u0026#34;Entire sequence sorted\u0026#34;] K --\u0026gt; L 2.1 Partition Step in Detail The core of quick sort is the partition step (Hoare\u0026rsquo;s scheme). We use two pointers $i$ and $j$:\n$i$ scans left to right, skipping elements already smaller than pivot. $j$ scans right to left, skipping elements already larger than pivot. When both stop, they point at elements that belong on the other side — swap them. sequenceDiagram participant Array as \u0026#34;a[l..r]\u0026#34; participant i as \u0026#34;Pointer i\u0026#34; participant j as \u0026#34;Pointer j\u0026#34; Note over Array: pivot = 4 Array-\u0026gt;\u0026gt;i: i = l-1 (starts outside the interval) Array-\u0026gt;\u0026gt;j: j = r+1 (starts outside the interval) loop while i \u0026lt; j i-\u0026gt;\u0026gt;i: do i++ while a[i] \u0026lt; pivot Note over i: Stops at first element \u0026gt;= pivot j-\u0026gt;\u0026gt;j: do j-- while a[j] \u0026gt; pivot Note over j: Stops at first element \u0026lt;= pivot alt i \u0026lt; j i-\u0026gt;\u0026gt;j: swap(a[i], a[j]) Note over i,j: Swap and continue next round else i \u0026gt;= j Note over i,j: Pointers crossed, partition done end end Note over Array: mid = j, left side \u0026lt;= pivot, right side \u0026gt;= pivot 3. Key Design Decisions \u0026amp; Rationale 3.1 Why i = l-1, j = r+1? The loop uses do...while, which executes the body before checking the condition. If we initialized i = l, j = r, the very first do i++ would skip a[l] — missing the first element entirely.\nBy starting one step outside the interval, do i++ moves i to l on the first iteration, and do j-- moves j to r. This guarantees every element in $[l, r]$ is examined.\nInitialization First do i++ effect Correct? i = l i becomes l+1, skips a[l] No i = l-1 i becomes l, starts at a[l] Yes 3.2 Why pivot = a[(l+r)/2] (middle element)? Choosing the middle element as pivot avoids the worst-case $O(n^2)$ degradation on already-sorted arrays. If we always chose a[l] or a[r], a sorted input would produce maximally unbalanced partitions every time.\n3.3 Why mid = j (not i)? After the while(i \u0026lt; j) loop exits, $i$ and $j$ have crossed. The invariant at exit is:\n$$a[l \\dots j] \\le \\text{pivot} \\le a[j+1 \\dots r]$$\nThus $j$ (not $i$) is the correct split point. Using $i$ would cause overlapping intervals and potential infinite recursion.\n4. Implementation #include \u0026lt;iostream\u0026gt; using namespace std; const int MAXN = 1e5 + 5; int a[MAXN]; void quick_sort(int l, int r) { // Empty or single-element interval: recursion terminates. // When every subarray has length \u0026lt; 1, the whole sequence is sorted. if (l \u0026gt;= r) return; // 1. Pick a pivot — the array will be split into \u0026lt; pivot and \u0026gt; pivot halves. int pivot = a[(l + r) / 2]; int i = l - 1, j = r + 1; // Works with do-while; pointers start outside the interval. // 2. Partition: two pointers move towards each other, swapping misfits. int mid; // mid records the final split point after the pointers cross. while (i \u0026lt; j) { do i++; while (a[i] \u0026lt; pivot); // Scan left for the first element \u0026gt;= pivot. do j--; while (a[j] \u0026gt; pivot); // Scan right for the first element \u0026lt;= pivot. if (i \u0026lt; j) swap(a[i], a[j]); // Swap into the correct side. } mid = j; // j is the split point: left \u0026lt;= pivot, right \u0026gt;= pivot. // 3. Divide and conquer: recursively sort the two subarrays. quick_sort(l, mid); quick_sort(mid + 1, r); } int main() { int n; cin \u0026gt;\u0026gt; n; for (int i = 1; i \u0026lt;= n; i++) cin \u0026gt;\u0026gt; a[i]; quick_sort(1, n); // Avoid trailing space: print the first element, then \u0026#34; \u0026#34; + each remaining. cout \u0026lt;\u0026lt; a[1]; for (int i = 2; i \u0026lt;= n; i++) cout \u0026lt;\u0026lt; \u0026#34; \u0026#34; \u0026lt;\u0026lt; a[i]; return 0; } 5. Debugging Chronicle Stage Symptom Root Cause Fix 1 WA / infinite loop do j--; while (a[i] \u0026gt; pivot) — typo: used i instead of j in the condition; a[i] does not change as j decrements a[i] → a[j] 2 WA (format) No spaces in output, all numbers concatenated cout \u0026lt;\u0026lt; a[i] \u0026lt;\u0026lt; \u0026quot; \u0026quot;, and print the first element separately to avoid trailing space Meta-cognitive takeaway: The a[i] vs a[j] typo is especially dangerous because it doesn\u0026rsquo;t always crash — it silently produces wrong results or infinite loops depending on the data. When using dual pointers, double-check that each pointer\u0026rsquo;s condition references the correct variable.\n6. Complexity Analysis Case Time Condition Best $O(n \\log n)$ Pivot always splits array into equal halves Average $O(n \\log n)$ Random pivot yields balanced partitions in expectation Worst $O(n^2)$ Pivot always extreme (min or max), e.g. sorted array with naive pivot choice Space: $O(\\log n)$ for recursion stack (balanced case), $O(n)$ worst case. Stability: Quick sort is not stable — equal elements may be swapped across the pivot. 6.1 Why the Worst Case Matters for P1177 Luogu P1177 includes adversarial testcases designed to break naive quick sort:\nTestcase Naive pivot (a[l]) Middle pivot (a[(l+r)/2]) Already sorted $O(n^2)$ — fail $O(n \\log n)$ — pass Reverse sorted $O(n^2)$ — fail $O(n \\log n)$ — pass All elements equal $O(n^2)$ — fail $O(n^2)$ — fail (still degrades!) For the \u0026ldquo;all equal\u0026rdquo; case, even middle-pivot degrades. The fix is three-way partitioning (§7.1).\n7. Further Optimizations graph LR A[\u0026#34;Basic quicksort\u0026lt;br/\u0026gt;O(n²) worst\u0026#34;] --\u0026gt; B[\u0026#34;Random pivot\u0026lt;br/\u0026gt;Avoid sorted-input degradation\u0026#34;] B --\u0026gt; C[\u0026#34;Three-way partitioning\u0026lt;br/\u0026gt;Handle many duplicates\u0026#34;] C --\u0026gt; D[\u0026#34;Insertion sort for small n\u0026lt;br/\u0026gt;Reduce constant factor\u0026#34;] D --\u0026gt; E[\u0026#34;Tail-recursion elimination\u0026lt;br/\u0026gt;Control stack depth\u0026#34;] E --\u0026gt; F[\u0026#34;Introspective sort\u0026lt;br/\u0026gt;Guaranteed O(n log n)\u0026#34;] style A fill style F fill 7.1 Three-Way Partitioning (Handling All-Equal Data) When many elements equal the pivot, the standard two-way partition still degrades. Three-way partitioning splits into \u0026lt; pivot | = pivot | \u0026gt; pivot:\nvoid quick_sort_3way(int l, int r) { if (l \u0026gt;= r) return; int pivot = a[l + rand() % (r - l + 1)]; int lt = l, gt = r, i = l; while (i \u0026lt;= gt) { if (a[i] \u0026lt; pivot) swap(a[i++], a[lt++]); else if (a[i] \u0026gt; pivot) swap(a[i], a[gt--]); // Don\u0026#39;t advance i — the swapped-in element hasn\u0026#39;t been checked yet. else i++; } quick_sort_3way(l, lt - 1); quick_sort_3way(gt + 1, r); // All elements in [lt, gt] equal pivot — already in place, no recursion needed. } 7.2 Small-Array Insertion Sort When $r - l \\le 15$, insertion sort has lower constant factor:\nif (r - l \u0026lt;= 15) { for (int i = l + 1; i \u0026lt;= r; i++) { int key = a[i], j = i - 1; while (j \u0026gt;= l \u0026amp;\u0026amp; a[j] \u0026gt; key) { a[j + 1] = a[j]; j--; } a[j + 1] = key; } return; } 7.3 Optimization Summary Technique Solves Added Complexity Random / median-of-three pivot Degradation on sorted input Low Three-way partitioning Degradation on many duplicates Medium Insertion sort for small $n$ High constant factor at leaves Low Tail-recursion elimination Stack overflow on deep recursion Low Introspective sort (switch to heapsort) Guarantees $O(n \\log n)$ worst case High (≈ std::sort) 8. Key Pitfalls in Quick Sort Implementation # Pitfall Wrong Right 1 Pointer initialization i = l, j = r (with do-while, skips a[l]) i = l-1, j = r+1 2 Wrong variable in condition do j--; while (a[i] \u0026gt; pivot) do j--; while (a[j] \u0026gt; pivot) 3 Wrong split point mid = i (i and j have already crossed) mid = j 4 Overlapping recursive intervals quick_sort(l, mid-1) + quick_sort(mid, r) quick_sort(l, mid) + quick_sort(mid+1, r) 5 Pivot at endpoint pivot = a[l] (degrades on sorted arrays) pivot = a[(l+r)/2] or random 6 Output format Trailing space / no spaces Print first element separately, then \u0026quot; \u0026quot; + a[i] 9. Connection to std::sort The C++ standard library\u0026rsquo;s std::sort uses Introsort:\nUses quicksort as the primary algorithm. Switches to heapsort when recursion depth exceeds $2\\lfloor \\log_2 n \\rfloor$ (prevents $O(n^2)$). Switches to insertion sort when the subarray is smaller than a threshold (usually 16). For competitive programming, using std::sort directly is the safest choice; hand-writing quicksort is mainly for understanding divide-and-conquer and for interview preparation.\nLuogu P1923 — Quick Select ($k$-th Smallest Element) This problem is a simliar version of the former one, only a little difference.\nLink: https://www.luogu.com.cn/problem/P1923\n1. Problem Statement (P1923) Given $n$ integers $a_i$ and an integer $k$ ($0 \\le k \u0026lt; n$), output the $k$-th smallest number. The smallest number is the 0-th smallest (0-indexed).\nThis is the selection problem: find the $k$-th order statistic. A full sort would solve it in $O(n \\log n)$, but quick select achieves $O(n)$ average time by recursing into only one partition per level.\n2. Algorithm Overview (Quick Select) Quick select is obtained by modifying exactly one part of quick sort — the recursive calls. After partitioning, we check which side contains the $k$-th element and recurse only there.\nflowchart TD A[\u0026#34;quick_select(l, r)\u0026#34;] --\u0026gt; B{\u0026#34;l \u0026gt;= r ?\u0026#34;} B --\u0026gt;|Yes| C[\u0026#34;return (single element is the answer)\u0026#34;] B --\u0026gt;|No| D[\u0026#34;1. Pick pivot = a[(l+r)/2]\u0026#34;] D --\u0026gt; E[\u0026#34;2. Partition: i, j two-pointer scan\u0026#34;] E --\u0026gt; E1[\u0026#34;i scans left, stops at \u0026gt;= pivot\u0026#34;] E --\u0026gt; E2[\u0026#34;j scans right, stops at \u0026lt;= pivot\u0026#34;] E1 --\u0026gt; F{\u0026#34;i \u0026lt; j ?\u0026#34;} E2 --\u0026gt; F F --\u0026gt;|Yes| G[\u0026#34;swap(a[i], a[j])\u0026#34;] G --\u0026gt; E1 F --\u0026gt;|No| H[\u0026#34;Partition done\u0026#34;] H --\u0026gt; I{\u0026#34;k \u0026lt;= j ?\u0026#34;} I --\u0026gt;|Yes| I1[\u0026#34;quick_select(l, j)\u0026#34;] I --\u0026gt;|No| I2[\u0026#34;quick_select(j+1, r)\u0026#34;] I1 --\u0026gt; J[\u0026#34;k-th element found in left half\u0026#34;] I2 --\u0026gt; K[\u0026#34;k-th element found in right half\u0026#34;] 3. Key Insight: Why if-else on $j$? The only difference between quick sort and quick select lies in the final two recursive calls:\nQuick Sort Quick Select quick_sort(l, mid); if (k \u0026lt;= j) quick_select(l, j); quick_sort(mid + 1, r); else quick_select(j + 1, r); Why can\u0026rsquo;t we unconditionally recurse into one side?\n3.1 Post-Partition Invariant After the while (i \u0026lt; j) loop exits, the following invariant holds:\n$$a[l \\dots j] \\le \\text{pivot} \\le a[j+1 \\dots r]$$\nThe pivot\u0026rsquo;s final resting position (call it pos) is not guaranteed to equal $j$. Depending on the data, it may land anywhere relative to $i$ and $j$:\nCase Layout Description A $[i,\\ \\text{pos},\\ j]$ Pivot sits strictly between the crossed pointers B $[\\text{pos}(i),\\ j]$ Pivot lands at or to the left of $i$ C $[i,\\ \\text{pos}(j)]$ Pivot lands at or to the right of $j$ Because the pivot\u0026rsquo;s exact position is ambiguous, we cannot blindly assume which side contains $k$. The if-else resolves this: if $k \\le j$, the $k$-th element lies in $[l, j]$; otherwise in $[j+1, r]$.\n3.2 Why Use $j$ (Not $i$) as the Split Decision Point? By the loop invariant, $j$ is the conservative split: everything in $[l, j]$ is $\\le$ pivot, everything in $[j+1, r]$ is $\\ge$ pivot. The intervals $[l, j]$ and $[j+1, r]$ are guaranteed to be disjoint and cover the full range. Using $i$ would risk overlapping or missing elements, since $i \\ge j+1$ after the pointers cross.\n4. Implementation (Quick Select) #include \u0026lt;iostream\u0026gt; using namespace std; const int MAXN = 5e6 + 5; int a[MAXN]; int k; // global: the 0-indexed rank to locate // Standard quick sort — for comparison void quick_sort(int l, int r) { if (l \u0026gt;= r) return; int pivot = a[(l + r) / 2]; int i = l - 1, j = r + 1; int mid; while (i \u0026lt; j) { do i++; while (a[i] \u0026lt; pivot); do j--; while (a[j] \u0026gt; pivot); if (i \u0026lt; j) swap(a[i], a[j]); } mid = j; quick_sort(l, mid); quick_sort(mid + 1, r); } // Quick select: only the recursive calls differ void quick_select(int l, int r) { if (l \u0026gt;= r) return; // Single element — this is our answer int pivot = a[(l + r) / 2]; int i = l - 1, j = r + 1; while (i \u0026lt; j) { do i++; while (a[i] \u0026lt; pivot); do j--; while (a[j] \u0026gt; pivot); if (i \u0026lt; j) swap(a[i], a[j]); } // Only recurse into the half that contains the k-th element if (k \u0026lt;= j) quick_select(l, j); else quick_select(j + 1, r); } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n; cin \u0026gt;\u0026gt; n \u0026gt;\u0026gt; k; k++; // Problem uses 0-indexed k; our array is 1-indexed — adjust for (int i = 1; i \u0026lt;= n; i++) cin \u0026gt;\u0026gt; a[i]; quick_select(1, n); cout \u0026lt;\u0026lt; a[k] \u0026lt;\u0026lt; endl; return 0; } Note: Luogu P1923 uses 0-indexed $k$ (the smallest is the 0-th). Since our array starts at index $1$, we pre-increment $k$ so a[k] points to the correct element after the partial sort.\n5. Complexity Analysis Case Time Condition Best $O(n)$ Pivot is the $k$-th element itself — one partition suffices Average $O(n)$ Each partition roughly halves the search space Worst $O(n^2)$ Pivot always extreme (min or max), same degradation as naive quick sort The expected recurrence:\n$$T(n) = T!\\left(\\frac{n}{2}\\right) + O(n) \\implies T(n) = O(n)$$\nSpace: $O(\\log n)$ for recursion stack in the balanced case, $O(n)$ in the worst case. 6. Pitfalls \u0026amp; Debugging Notes # Pitfall Wrong Right 1 Blind recursion Always recurse into (j+1, r) Check if (k \u0026lt;= j) to decide the correct side 2 Wrong split variable Use i instead of j Always use j — the post-partition invariant guarantees correctness 3 0-index / 1-index mismatch Forget to adjust $k$ when array is 1-indexed k++ before calling quick_select 4 Overlapping intervals quick_select(l, i) + quick_select(i+1, r) Use j: quick_select(l, j) + quick_select(j+1, r) 7. Connection to std::nth_element The C++ standard library provides std::nth_element, which implements quick select (typically as introselect — falling back to heapsort when recursion depth is excessive):\n// Equivalent to the hand-written quick_select above: nth_element(a + 1, a + k, a + n + 1); cout \u0026lt;\u0026lt; a[k] \u0026lt;\u0026lt; endl; Hand-writing quick select reinforces the two-pointer partition invariant — the same reasoning that underpins quick sort, three-way partitioning (Dutch national flag), and partial sorting. Once you understand that $j$ is the safe split point, both algorithms become two variations of the same idea.\n","permalink":"https://xialiao.org/notes/algorithms/algo-notebook-1/","summary":"\u003ch2 id=\"luogu-p1045--mersenne-numbers\"\u003eLuogu P1045 — Mersenne Numbers\u003c/h2\u003e\n\u003cp\u003elink: \u003ca href=\"https://www.luogu.com.cn/problem/P1045\"\u003ehttps://www.luogu.com.cn/problem/P1045\u003c/a\u003e\u003c/p\u003e\n\u003ch3 id=\"1-problem-statement\"\u003e1. Problem Statement\u003c/h3\u003e\n\u003cp\u003eA \u003cstrong\u003eMersenne number\u003c/strong\u003e is an integer of the form $2^P - 1$ where $P$ is prime. Given $P$ ($1000 \\lt P \\lt 3,100,000$), the task is to compute:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eThe number of decimal digits of $2^P - 1$;\u003c/li\u003e\n\u003cli\u003eThe last $500$ decimal digits of $2^P - 1$, printed in $10$ rows of $50$ digits each, zero-padded on the left if necessary.\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eIt is \u003cem\u003enot\u003c/em\u003e required to verify the primality of $2^P - 1$ or $P$.\u003c/p\u003e","title":"Algorithm Notebook 1 — High-Precision Arithmetic \u0026 Quick Sort"},{"content":"This 63-pages document is my first deep dive into $\\LaTeX$, and I have made a lot of effort on it, spending a large quantity of time. It is my proudest work in my Freshman year in Hunan University, more than just notes, but a demonstration of my passion for learning and a nick on the road of my professional competence growth. For this reason, I am proud to feature it as my first project.\nIf you cannot view the PDF, click here to download it\n","permalink":"https://xialiao.org/projects/chemistry/inorganic_elementary_notes/","summary":"\u003cp\u003eThis 63-pages document is my first deep dive into $\\LaTeX$, and I have made a lot of effort on it, spending a large quantity of time. It is my proudest work in my Freshman year in Hunan University, more than just notes, but a demonstration of my passion for learning and a nick on the road of my professional competence growth.\nFor this reason, I am proud to feature it as my first project.\u003c/p\u003e","title":"Inorganic Elemental Notes"},{"content":"What is Organic Chemistry? Origins of organic chemistry — from essential oils and alkaloids to the chemistry of carbon How organic chemistry makes sense of the world — odors, drugs, and the nature of Science The five pillars of organic chemistry — structure, theory, mechanism, synthesis, and biochemistry From Essential Oils to the Chemistry of Carbon Organic chemistry is the study of the chemistry of carbon. Its roots trace back to essential oils and alkaloids extracted from plants, and later, coal and fossils became sources of organic substances. In the 19th century, coal tar gave rise to benzene, phenol, aniline, and other aromatic compounds. In 1856, Perkin attempted to synthesize quinine from aniline and accidentally produced mauveine — the first synthetic dye — giving birth to the synthetic dye industry.\nToday, the scope of organic chemistry covers every possible arrangement of carbon atoms. Even restricted to molecules with 30 or fewer carbon atoms, the number of possible structures reaches $10^{63}$ — the entire universe may not contain enough carbon atoms to synthesize every single one of them.\nHow Organic Chemistry Makes Sense of the World Clayden devotes considerable space to odor molecules: the stench of skunks comes from thiols, the aroma of black truffles also stems from sulfur-containing compounds, and the scent of roses derives from damascenone. There are also insect pheromones, human bitter-taste agents (such as Bitrex in toilet cleaners), narcotics, and pharmaceuticals. Honestly, I recall only a handful of them — but perhaps that is precisely the point: what matters is not memorizing which molecule gives which smell, but realizing that every odor and every physiological effect can be reduced to molecular-level understanding. The essence of Science is simply the description and understanding of Nature.\nOrganic Chemistry in Daily Life Organic chemistry profoundly shapes everyday existence. Ritonavir turned AIDS from a terminal illness into a manageable chronic condition. Aspartame, built from just two amino acids, is 200 times sweeter than sucrose. Modern insecticides, modeled on natural pyrethrins, target specific pests and degrade in sunlight with virtually no environmental residue. From pharmaceuticals to household products, organic chemistry is everywhere.\nThe Boundaries of Organic Chemistry The elements that organic chemists truly care about cluster in the upper-right corner of the periodic table — $\\ce{C, H, O, N}$, plus $\\ce{S, P}$ and the halogens. However, $\\ce{B, Si, Li, Pd}$ and others have become increasingly important.\nWhere, then, is the boundary of organic chemistry? In truth, there is no clear dividing line. $\\ce{CO2}$ and carbonates contain carbon yet are typically classified as inorganic. Grignard reagents ($\\ce{RMgX}$) resemble both inorganic and organic species. $\\ce{(Ph3P)4Pd}$ has twelve phenyl rings but no carbon skeleton — so what is it? \u0026ldquo;We don\u0026rsquo;t know, and we don\u0026rsquo;t care.\u0026rdquo; Blurred boundaries only enrich the field.\nThe Five Pillars of Organic Chemistry Clayden organizes organic chemistry into five aspects:\nUnderstanding and determining the structure of substances — what methods to use, how to establish connectivity and stereochemistry. Understanding structure from the perspective of bonds and electrons — why carbon bonds the way it does, why a particular conformation is more stable. Studying electron flow and reaction mechanisms — curved arrows are the core language. Using structure, theory, and mechanism to build the molecules we need. Applying all of the above to understand Nature itself! This book unfolds in precisely that logical order: starting from the structure of substances, moving to why they are built that way and how structure dictates properties, ultimately revealing the mysteries of Nature and the power of human industry.\n","permalink":"https://xialiao.org/notes/chemistry/clayden_organic_chem/clayden-2/","summary":"\u003ch2 id=\"what-is-organic-chemistry\"\u003eWhat is Organic Chemistry?\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003eOrigins of organic chemistry — from essential oils and alkaloids to the chemistry of carbon\u003c/li\u003e\n\u003cli\u003eHow organic chemistry makes sense of the world — odors, drugs, and the nature of Science\u003c/li\u003e\n\u003cli\u003eThe five pillars of organic chemistry — structure, theory, mechanism, synthesis, and biochemistry\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"from-essential-oils-to-the-chemistry-of-carbon\"\u003eFrom Essential Oils to the Chemistry of Carbon\u003c/h2\u003e\n\u003cp\u003eOrganic chemistry is the study of \u003cem\u003ethe chemistry of carbon\u003c/em\u003e. Its roots trace back to essential oils and alkaloids extracted from plants, and later, coal and fossils became sources of organic substances. In the 19th century, coal tar gave rise to benzene, phenol, aniline, and other aromatic compounds. In 1856, Perkin attempted to synthesize quinine from aniline and accidentally produced mauveine — the first synthetic dye — giving birth to the synthetic dye industry.\u003c/p\u003e","title":"Clayden Ch.1 (Detailed): What is Organic Chemistry?"},{"content":"Preface: How to learn Organic Chemistry? We should focus on the following points:\nStructure determination: Finding the structure of a new organic compound even if we cannot see it. Theoretical part: From the perspective of atoms, electrons, and bonds. Reaction mechanism: How do atoms react with each other? The mechanism behind it, and how to use that understanding to predict reactions. Synthesis: After the theoretical part and reaction mechanisms, designing molecules and making them. Biological chemistry: Biological compounds and reactions in nature, understanding them and using them. Characteristics of Clayden Organic Chemistry An important feature of Clayden Organic Chemistry is its outline order, it uses the order of mechanism, while the chemistry education in China takes the order of species and functional groups. And the the translator emphasizes the reaction mechanism rather than treating reactions as isolated facts. And the practical experience still keeps its significance.\n12 131415161718 H the organic chemist's\nperiodic table Li BCNOF NaMg AlSiPSCl KTiCrFeCuZn SeBr RuPdAg SnI OsAuHg Sm ","permalink":"https://xialiao.org/notes/chemistry/clayden_organic_chem/clayden-1/","summary":"\u003ch2 id=\"preface-how-to-learn-organic-chemistry\"\u003ePreface: How to learn Organic Chemistry?\u003c/h2\u003e\n\u003cp\u003eWe should focus on the following points:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eStructure determination: Finding the structure of a new organic compound even if we cannot see it.\u003c/li\u003e\n\u003cli\u003eTheoretical part: From the perspective of atoms, electrons, and bonds.\u003c/li\u003e\n\u003cli\u003eReaction mechanism: How do atoms react with each other? The mechanism behind it, and how to use that understanding to predict reactions.\u003c/li\u003e\n\u003cli\u003eSynthesis: After the theoretical part and reaction mechanisms, designing molecules and making them.\u003c/li\u003e\n\u003cli\u003eBiological chemistry: Biological compounds and reactions in nature, understanding them and using them.\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"characteristics-of-clayden-organic-chemistry\"\u003eCharacteristics of Clayden Organic Chemistry\u003c/h2\u003e\n\u003cp\u003eAn important feature of Clayden Organic Chemistry is its outline order, it uses the order of mechanism, while the chemistry education in China takes the order of species and functional groups. And the the translator emphasizes the reaction mechanism rather than treating reactions as isolated facts. And the practical experience still keeps its significance.\u003c/p\u003e","title":"Clayden Preface and Ch.1: What is Organic Chemistry?"}]