diff --git a/0-0-test.cpp b/0-0-test.cpp new file mode 100644 index 0000000..df5b480 --- /dev/null +++ b/0-0-test.cpp @@ -0,0 +1,6 @@ +#include + +int main() { + std::cout << "Hello world" << std::endl; + return 0; +} \ No newline at end of file diff --git a/leetcodecheatsheet/cpp_reference.typ b/leetcodecheatsheet/cpp_reference.typ new file mode 100644 index 0000000..82e98fd --- /dev/null +++ b/leetcodecheatsheet/cpp_reference.typ @@ -0,0 +1,1102 @@ +// C Standard Library Cheatsheet Template +// A clean, two-column layout for quick reference during coding interviews. +// Created with Typst. + +// ================= DOCUMENT SETUP ================= +#set document(title: "C Standard Library Reference", author: "David Gao") + +#let base-text-size = 9pt + +#set text(font: ("Arial", "DengXian"), size: base-text-size, lang: "zh") +#show raw: set text( + font: ( + "Cascadia Code", + "SimHei", + ), +) + +#set page(paper: "a4", margin: (top: 0.75in, bottom: 0.75in, left: 0.5in, right: 0.5in)) + +// Define colors for styling to make sections stand out. +#let primary_color = navy +#let secondary_color = luma(230) + +// ================= CUSTOM SHOW RULE (The Cheatsheet Engine) ================= +#let entry(signature, description, code_lang: "cpp") = { + grid.cell[ + #rect(width: 100%, inset: (x: 4pt, y: 3pt), fill: luma(240), radius: 2pt, [#raw(signature, lang: code_lang)]) + ] + grid.cell[ + #description + ] +} + +// This function formats a larger code snippet for algorithms/data structures. +#let code_snippet(body, title: none) = { + // This `if` statement checks if a title was passed. + // If `title` is anything other than `none`, this block is rendered. + if title != none { + pad(bottom: 4pt, text(weight: "bold", title)) + } + + // The main block for the code is always rendered. + block( + width: 100%, + inset: 8pt, + fill: luma(240), + radius: 3pt, + body, + ) + v(1em) +} + +// ================= HEADER FUNCTION ================= +#let section_header(title) = { + // v(0.5em) + block(width: 100%, fill: primary_color, inset: 6pt, radius: 3pt, text(white, weight: "bold", title)) + v(0.5em) +} + +// ================= SUBSECTION HEADER FUNCTION ================= +#let subsection_header(title) = { + // v(1.2em) // A bit of space above the subsection title. + // Make text bold, slightly larger, and use the primary color. + text(weight: "bold", title) + // Add a subtle line underneath to separate it from the content. + line(length: 100%, stroke: 0.5pt + primary_color) + v(0.6em) // Space after the subsection header. +} + +#show heading.where(level: 1): it => section_header(it.body) +#show heading.where(level: 2): it => subsection_header(it.body) + +// ================= CHEATSHEET CONTENT ================= + +// Main Title +#align(center)[ + #text(size: 20pt, weight: "bold", "C Standard Library Reference") + #line(length: 100%, stroke: 1pt) + #v(1em) +] + +#section_header([`stdio.h`]) + +#grid( + columns: (auto, 1fr), + column-gutter: 8pt, + row-gutter: 6pt, +)[ + #entry( + "int printf(const char* format, ...);", + [Prints formatted output to `stdout`. Common specifiers: `%d` (int), `%f` (float/double), `%c` (char), `%s` (string), `%p` (pointer).], + ) + #entry( + "int sprintf(char* str, const char* format, ...);", + [Prints formatted output to a string `str`.], + ) + #entry( + "int scanf(const char* format, ...);", + [Reads formatted input from `stdin`.], + ) + #entry( + "int getchar(void);", + [Reads the next character from `stdin`.], + ) + #entry( + "int putchar(int ch);", + [Writes a character `ch` to `stdout`.], + ) +] + +#section_header([`string.h`]) + +#grid( + columns: (auto, 1fr), + column-gutter: 8pt, + row-gutter: 6pt, +)[ + #entry( + "void *memset(void *str, int c, size_t n);", + [`n` is the number of bytes needed to be set. Can be obtained by `sizeof(array)`, where `array` is of type `T[]`.], + ) + #entry( + "void *memcpy(void *dst, const void *src, size_t n);", + [ + Copy `n` bytes of data starting from `src` into `dst`. 需要确保`src`和`dst`空间不重叠。 + ], + ) + #entry( + "int strcmp (const char * str1, const char * str2);", + [返回`str1`与`str2`的字典序,`str1`在前则$<0$,`str1`在后则$>0$。返回0则相等。], + ) + #entry( + "size_t strlen(const char *str);", + [返回字符串长度], + ) + #entry( + "char *strcpy(char *__restrict__ _Dest, const char *__restrict__ _Source);", + [将`src`内的字符串复制进`dest`], + ) + #entry( + "char *strcat(char *dest, const char *src);", + [把`src`所指向的字符串追加到`dest`所指向的字符串的结尾], + ) + #entry( + "char *strchr(const char *str, int c);", + [在参数`str`所指向的字符串中搜索第一次出现字符 `c`(一个无符号字符)的位置。], + ) + #entry( + "char *strpbrk(const char *str1, const char *str2);", + [检索字符串`str1`中第一个匹配字符串`str2`中某个字符的字符,不包含空结束字符。也就是说,依次检验字符串`str1`中的字符,当被检验字符在字符串`str2`中也包含时,则停止检验,并返回该字符位置。], + ) + #entry( + "char *strstr(const char *haystack, const char *needle);", + [在字符串`haystack`中查找第一次出现字符串`needle`(不包含空结束字符)的位置。], + ) + #entry( + "char *strtok(char *str, const char *delim);", + [ + 分解字符串 str 为一组字符串,delim 为分隔符。 + ```c + char *token = strtok(str, s); + /* 继续获取其他的子字符串 */ + while(token != NULL) { + printf("%s\n", token); + token = strtok(NULL, s); + } + ``` + ], + ) +] + + +// --- stdlib.h Section --- +#section_header([`stdlib.h`]) + +#grid( + columns: (auto, 1fr), + column-gutter: 8pt, + row-gutter: 6pt, +)[ + #entry( + "void* malloc(size_t size);", + "Allocates `size` bytes of uninitialized memory. Returns a void pointer to the allocated space.", + ) + #entry( + "void* calloc(size_t nmemb, size_t size);", + "Allocates memory for an array of `nmemb` elements of `size` bytes each and initializes all bits to zero.", + ) + #entry( + "void* realloc(void* ptr, size_t size);", + "Resizes the memory block pointed to by `ptr` to `size` bytes. The content will be unchanged up to the minimum of the old and new sizes.", + ) + #entry( + "void free(void* ptr);", + "Deallocates a block of memory previously allocated by `malloc`, `calloc`, or `realloc`.", + ) + #entry("int atoi(const char* str);", "Converts the initial portion of the string `str` to an `int`.") + #entry( + "long int strtol(const char* str, char** endptr, int base);", + "Converts string `str` to a `long int`. `endptr` can be used to see where conversion stopped. `base` is the number base (e.g., 10).", + ) + #entry( + "void qsort(void* base, size_t nmemb, size_t size, int (*compar)(const void*, const void*));", + "Sorts an array. `compar` is a pointer to a function that compares two elements.", + ) + #entry("int rand(void);", "Returns a pseudo-random integer between 0 and `RAND_MAX`.") + #entry("void srand(unsigned int seed);", "Seeds the pseudo-random number generator used by `rand()`.") + #entry("void exit(int status);", "Causes normal program termination. `EXIT_SUCCESS` or `EXIT_FAILURE`.") +] + +#v(1.5em) + +// --- string.h Section --- += `string.h` — String Manipulation + +#grid( + columns: (auto, 1fr), + column-gutter: 8pt, + row-gutter: 6pt, +)[ + #entry( + "size_t strlen(const char* s);", + "Calculates the length of string `s`, excluding the terminating null byte (`'\\0'`).", + ) + #entry( + "char* strcpy(char* dest, const char* src);", + "Copies the string `src` to `dest`. *Unsafe*, does not check buffer size.", + ) + #entry( + "char* strncpy(char* dest, const char* src, size_t n);", + "Copies up to `n` characters from `src` to `dest`. May not be null-terminated if `src` is too long.", + ) + #entry( + "int strcmp(const char* s1, const char* s2);", + "Compares two strings. Returns < 0 if s1 < s2, 0 if s1 == s2, > 0 if s1 > s2.", + ) + #entry( + "char* strchr(const char* s, int c);", + "Returns a pointer to the first occurrence of character `c` in string `s`, or `NULL` if not found.", + ) + #entry( + "char* strstr(const char* haystack, const char* needle);", + "Finds the first occurrence of the substring `needle` in the string `haystack`.", + ) + #entry( + "void* memset(void* s, int c, size_t n);", + "Fills the first `n` bytes of the memory area pointed to by `s` with the constant byte `c`.", + ) + #entry( + "void* memcpy(void* dest, const void* src, size_t n);", + "Copies `n` bytes from memory area `src` to memory area `dest`.", + ) +] + += 常用技巧 + +== 防止栈溢出 +```c +#pragma comment(linker, "/STACK:1024000000,1024000000") +``` + +== C++常用头文件 +```cpp +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +``` + += Common Algorithms & Data Structures + +== 字符串匹配 + +=== KMP算法:$O(n)$ + +#code_snippet[ + ```cpp + int next[MAXN]; + int KMP(string txt, string pattern) { + int i = next[0] = -1 + int j = 0; + int n = text.size(); + int m = pattern.size(); + + // 生成next表 + while (j < m) { + if (i == -1 || pattern[j] == pattern[i]) { + i++; + j++; + next[j] = i; + } else { + i = next[i]; + } + } + + // KMP + i = j = 0; + while (i < m && j < n) { + if (i == -1 || text[j] == pattern[i]) { + i++; + j++; + } else { + i = next[i]; + } + + if (i == m) { + // 成功匹配,第一个字符在text的index为i - j + 1 + // 完美匹配,当作失配处理 + // i = next[i]; + } + } + } + ```] + +== 日期处理 + +#code_snippet[```cpp +struct Date{ + int year, month, day; + Date(int y, int m, int d): year(y), month(m), day(d) {} + int week(){ // 今天是星期几? 基姆拉尔森公式 + int y = year, m = month, d = day; + if( m == 1 || m == 2 ) { + m += 12; + y--; + }; + int w = (d + 2*m + 3*(m+1)/5 + y + y/4 - y/100 + y/400) % 7; + return ++w; // w:0:星期一...依此类推 + } + int MONTH[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + int kthDay(){ // 给定一个日期,输出这个日期是该年的第几天 + if(year % 4 == 0 && (year % 100 || year % 400 == 0)) + MONTH[2] = 29; + int days = 0; + for (int i = 1; i < month; i++) days += MONTH[i]; + return days += day; + } +}; +```] + + +== 排序 + +=== 快速排序:$Theta (n log n) + O(1)$ + +#code_snippet[```cpp +void quicksort(vector &a, int le, int ri) { + // 排序[le, ri) + int i = le, j = ri - 1; + int pivot = a[(rand()%(ri-le))+le]; + while (i <= j) { + while (a[i] < pivot) i++; + while (a[j] > pivot) j--; + if (i <= j) swap(&a[i++], &a[j--]); // 先swap,之后执行自增 + } + + if (le < j) quicksort(a, le, j + 1); + if (i < ri - 1) quicksort(a, i, ri); +} +```] + +=== 归并排序/求逆序对:$O(n log n) + O(n)$ + +#code_snippet[```cpp +long long mergesort(vector& a, long long le, long long ri){ + long long ans = 0; + if(le >= ri - 1) return 0; + long long mid = (le + ri) >> 1; + ans += mergesort(a, le, mid); + ans += mergesort(a, mid, ri); + + vector b; //辅助数组 + long long p = le, q = mid; + long long count = 0; + while(p < mid && q < ri){ + if(a[p] <= a[q]) b.push_back(a[p++]); + else{ + b.push_back(a[q++]); + count += (mid - p); // 求逆序 + } + } + while(p < mid) b.push_back(a[p++]); + while(q < ri) b.push_back(a[q++]); + for(long long i = le; i < ri; ++i) + a[i] = b[i-le]; + + ans += count; + //ans += min(count, (mid - le)*(ri - mid) - count); //求二叉树最小逆序 + return ans; +} +```] + +== 二分查找:$O(log n)$ + +#code_snippet[```cpp +int binarySearch(vector &a, int e){ // 保证输入有序; lower_bound形式 + int le = 0, ri = a.size(); + while(ri - le > 1){ // [le, ri)夹逼 + int mid = (le + ri) >> 1; + if(a[mid] <= e) + le = mid; // le处元素总是<= + else + ri = mid; // ri处元素总是> + } + return le; // 返回下标 +} +```] + +== 贪心问题 + +=== 区间选点 + +数轴上$n$个闭区间,尽可能选择少的点,使得每个区间内至少有一个点被选到。 + +解法:将区间结束点从小到大排列;选第一个结束点作为我们选择的第一个点,删去所有已经被选了点的区间;不停地选择剩余区间中结束点最靠前的点,直到所有区间都有至少一个点。 + +=== 区间覆盖 + +数轴上有数个区间$[a, b]$。给定一个线段$[s, t]$,使用最少数量的区间将线段完全覆盖。 + +解法:在预处理:将各个区间按$a$的大小排序。首先确认最小的开头在$s$前,否则直接无解。之后使用贪心。对于所有$a < s$的区间,找$b$最大的(这样能覆盖更多)。之后将$s$移动到$b$,接着寻找新的最长的,直到全部覆盖。 + +== DP + +=== 普通背包问题:$O(n V) + O(V)$ + +第`i`个物品的体积为`w[i]`,价值为`v[i]`,`d[i][j]`代表从开头到第`i`个物品(包含)在`j`体积内能达到的最大价值。由于`d[i + 1]`只依赖于`d[i]`,数组只需要一维。需要从`j`较大的往下更新,防止更新大`j`时用到的小`j`的价值是已经是加入了本物品的价值。 + +#code_snippet(```cpp +int dp[MAXN]; +int backpack(vector w, vector v, int n, int V) { // n为物品数,V为总容量 + for (int i = 0; i < n; i++) { + for (int j = V; j >= w[i]; j--) { + dp[j] = max(dp[j], dp[j - w[i]] + v[i]); + } + } +} +```) + +=== 完美背包问题:$O(n V) + O(V)$ + +与普通背包的区别是每个物品都有无限个。只要正向更新,刚好满足要求(考虑大体积时可以用之前已经加了这个物品的接着加入)。 + +#code_snippet(```cpp +int dp[MAXN]; +int backpack(vector w, vector v, int n, int V) { // n为物品数,V为总容量 + for (int i = 0; i < n; i++) { + for (int j = w[i]; j <= V; j++) { + dp[j] = max(dp[j], dp[j - w[i]] + v[i]); + } + } +} +```) + +=== 多重背包:$O(V sum(log_2(k_i)))$ + +对于第`i`种物品,体积`w[i]`,价值`v[i]`,数量`k[i]`。对数量进行二进制拆分:$k = 1 + 2 + 4 + dots + 2^p + (k - (2^(p + 1) - 1))$。这些数量的物品各自算作一个新物品,继续做01背包。 + +拆分代码: + +#code_snippet(```cpp +int __v[MAXN], __w[MAXN], cnt = 0; // 拆分后物品总数 +for(int i = 0; i < n; ++i){ // 分组 + int v, w, k; + scanf("%d%d%d", &w, &v, &k); + int c = 1; + while(k - c > 0){ + k -= c; + __w[cnt] = c*w; + __v[cnt++] = c*v; + c *= 1; + } + __w[cnt] = k*w; + __v[cnt++] = k*v; +} +```) + +=== 最优多边形剖分:$O(n^3)$ + +多边形中任意两点$i, j$间的弦有某个权值$w(i, j)$。求将多边形分割成数个三角形所需的最低权值。我们可以用$d(i, j)$表示子多边形$i, i + 1, dots, j - 1, j$的最优解(这个多边形是我们通过选择$i j$这条弦切割出来的)。那么 +$ d(i, j) = max_(i < k < j){d(i, k) + d(k, j) + w(i, k) + w(k, j)} $ + +#figure( + image( + "./img/三角形剖分.png", + width: 30%, + ), +) #label("fig: 三角剖分") + +=== 数位DP + +给出某个范围$[a, b]$求在这个范围中满足一定要求的数有多少个(通常与数大小等没有关系,与数的数位(digit)有关)。例如,给定$[a,b]$,求范围内每个数位出现次数。 + +解法:首先需要几个数组:$text("ten")(i)$为$10^i$,$f(i)$为$[0, 10^i - 1]$范围内任意数位出现的次数。这是我们DP算法的核心。核心状态转移: +$ f(i) = f(i - 1) times 10 + text("ten")(i) $ + +即对于所有小于等于$i$位的数,我们$i - 1$中重复了10次,最高位中我们关心的这个数(不论是0-9中的哪个)作为最高位的数都有$10^i$个。 + +#code_snippet(```cpp +#include +using namespace std; +long long a,b; +long long ten[20],f[20]; +long long cnta[20],cntb[20]; +void solve(long long x,long long *cnt){ + long long num[20]={0}; + int len=0; + while(x){ // 对x进行数位分解 + num[++len]=x%10; + x=x/10; + } + // 最低位在num[1],最高位在num[len] + + // 一下以543这个数为例。num = {0, 3, 4, 5} + // 从最高位开始往下。以5为例 + for(int i=len; i>=1; i--){ + // 处理0 - 499的计数,以及5作为首位的计数 + for(int j=0; j<=9; j++) + // 每个数位在0-499的十位和个位均匀出现。 + cnt[j] += f[i-1] * num[i]; + for(int j=0; j=1; j--){ + num2 = num2 * 10 + num[j]; + } + // 5作为百位出现了43 + 1次 + cnt[num[i]]+=num2+1; + // 0作为百位时不会被写出来。 + cnt[0]-= ten[i-1]; + } +} +int main() +{ + scanf("%lld %lld",&a,&b); // 区间范围 + ten[0]=1; + for(int i=1;i<=15;i++) + { + f[i]=f[i-1]*10+ten[i-1]; + ten[i]=10*ten[i-1]; + } + solve(a-1,cnta); // [1, a-1]总计数 + solve(b,cntb); // [a, b]总计数 + for(int i=0;i<=9;i++) + printf("%lld ",cntb[i]-cnta[i]); // 两者相减 +} +```) + +== 数学 + +=== 快速幂:$O(log n)$ + +Python中自带快速幂。 + +矩阵快速幂只需重载乘法。 + +#code_snippet(```cpp +int q_power(int a, int n) { + if (n == 0) return 1; + if (n == 1) return a; + int base = q_power(a, n / 2); + base *= base; + return (n % 2) ? a * base : base; +} +```) + +=== 费马小定理求幂的模:$O(log P)$ + +计算模意义下的幂。$p$必须为质数。因为$a^(p - 1) equiv 1 (mod p)$,因此$a^b equiv a^(b mod (p - 1)) (mod p)$ + +#code_snippet(```cpp +int q_power(int a, int n, int mod) { + int ans = 1, base = a; + while(n) { + if (n & 1) ans *= base; + base *= base; + ans %= mod; + base %= mod; + n >>=1; + } + return ans; +} + +int q_big_power(int a, int n, int p) { + return q_power(a, n % (p - 1), p) +} +```) + +=== 更快的快速幂:$O(T)$ + +在$a$不变的情况下进行$T$次幂查询$t_1, t_2, dots t_T$,可以记$n = max{t_i}$,则 +$ a^t = (a^(sqrt(n)))^(t \/ sqrt(n)) times a^(t % sqrt(n)) $ + +即,我们可以提前算好$a$的$0$到$sqrt(n)$次幂进行计算并存储,之后可以以$O(1)$的速度进行计算。 + +=== 最大公约数gcd:$O(log n)$ + +使用欧几里得算法。STL中提供了`__gcd(a, b)`。 + +#code_snippet(```cpp +inline int gcd(int a, int b) { + return b == 0 ? a: gcd(b, a % b); +} +```) + +快速gcd: +#code_snippet(```cpp +int kgcd(int a, int b) { + if (a == 0) return b; + if (b == 0) return a; + if (!(a & 1) && !(b &1)) return kgcd(a >> 1, b >> 1) << 1; + else if (!(b & 1)) return kgcd(a, b >> 1); + else if (!(a & 1)) return kgcd(a >> 1, b); + else return kgcd(abs(a - b), min(a, b)); +} +```) + +=== 最小公倍数lcm + +#code_snippet(```cpp +int lcm(int a, int b) { + return a * b / gcd(a, b) +} +```) + +=== 扩展欧几里得算法 + +可以解出$a x + b y = gcd(a, b)$的一组特解。 + +#code_snippet(```cpp +void exgcd(int a, int b, int &x, int &y) { + if (b == 0) { + x = 1; + y = 0; + return; + } + exgcd(b, a % b, y, x); + y -= a / b * x; +} +```) + +== 数位分解 + +=== 除余法 + +#code_snippet(```cpp +vector split(int n){ // 传入整数 + vector ret; + while(n){ + int t = n % 10; ret.push_back(t); + n /= 10; + } + reverse(ret.begin(), ret.end()); // 反转,按需 + return ret; +} +```) + +=== ASCII转数字 + +#code_snippet(```cpp +vector split(string n){ // 传入字符串 + vector ret; + ret.resize(n.size()); + for(int i = 0; i < n.size(); ++i) + ret[i] = n[i] - '0'; // 利用ASCII减法获得真实数字 + return ret; +} +```) + +=== 格雷码 + +格雷序列中的第$i$个项目的格雷码为$i xor (i >> 1)$。 + +#code_snippet(```cpp +vector Gray_Create(int n){ + vector res; res.clear(); + for(int i = 0; i < (1 << n); ++i) + res.push_back(i ^ (i >> 1)); + return res; +} +```) + +== 高精度类 + +#code_snippet(```cpp +struct BigInt{ // 仅限正整数,当MAXN较大时容易栈溢出,尽量定义为全局变量 + int digit[MAXN]; // [低位 -> 高位),与I/O顺序相反 + int SIZE, BASE; // 数位和进制 + + BigInt(){BASE = 10; SIZE = 0;} // 默认十进制 + BigInt(int base){BASE = base; SIZE = 0;} + BigInt(const string& S, int base = 10){ + SIZE = S.size(); + BASE = base; + string baseChar = "0123456789ABCDEF"; // 数位 + for (int i = 0, i < SIZE; i++) { + digit[i] = baseChar.find(S[i]); + } + } + void overflow(){ // 溢出进位 + for(int i = 0; i < SIZE; ++i) { + if(digit[i] >= BASE){ + if(i < SIZE - 1){ + digit[i + 1] += digit[i] / BASE; + digit[i] %= BASE; + } else{ + digit[SIZE++] = digit[i] / BASE; + digit[i] %= BASE; + } + } + } + } + + BigInt operator +(const BigInt &A){ // 高精度加法 + BigInt ret(BASE); // 采取前者的BASE,使用时注意保证两个数进制相同 + int max_size = max(SIZE, A.SIZE); + ret.SIZE = max_size; + for(int i =0; i < max_size; ++i){ + if(i < SIZE) ret.digit[i] += digit[i]; + if(i < A.SIZE) ret.digit[i] += A.digit[i]; + } + ret.overflow(); + return ret; + } + BigInt operator +(const int x){ // 普通加法 + BigInt ret = *this; + ret.digit[0] += x; + ret.overflow(); + return ret; + } + BigInt operator *(const BigInt &A){ // 高精度朴素乘法 O(n^2) + BigInt ret(BASE); + int max_size = SIZE + A.SIZE - 1; + ret.SIZE = max_size; + for(int i = 0; i < SIZE; ++i) + for(int j = 0; j < A.SIZE; ++j) + ret.digit[i + j] += digit[i] * A.digit[j]; + ret.overflow(); + return ret; + } + BigInt operator *(const int x){ // 普通乘法 + BigInt ret = *this; + for(int i = 0; i < SIZE; ++i) + ret.digit[i] *= x; + ret.overflow(); + return ret; + } + BigInt operator /(const int x){ // 普通除法 + BigInt ret(BASE); ret.SIZE = SIZE; + int remainder = 0; // 余数 + for(int i = SIZE - 1; i >= 0; --i){ // 高位 -> 低位 + int t = (remainder * BASE + digit[i]) / x; + int r = (remainder * BASE + digit[i]) % x; + ret.digit[ret.SIZE++] = t; remainder = r; + } + reverse(ret.digit, ret.digit + SIZE); // 反置为[低位 -> 高位) + while(ret.digit[ret.SIZE - 1] == 0 && ret.SIZE > 1) ret.SIZE--; // 去掉前缀0 + return ret; + } + int operator %(const int x){ // 普通模运算 + int remainder = 0; // 余数 + for(int i = SIZE - 1; i >= 0; --i) // 高位 -> 低位 + remainder = (remainder * BASE + digit[i]) % x; + return remainder; + } + bool operator ==(const BigInt &A){ + if(SIZE == A.SIZE){ + for(int i = SIZE -1; i >= 0; --i){ + if(digit[i] != A.digit[i]) return false; + } + return true; + } else return false; + } + bool operator <(const BigInt &A){ + if(SIZE == A.SIZE){ + for(int i = SIZE -1; i >= 0; --i){ + if(digit[i] < A.digit[i]) + return true; + else if(digit[i] > A.digit[i]) + return false; + } + return false; // 相等时返回false + } else return SIZE < A.SIZE; + } + bool operator <=(const BigInt &A){ + if(SIZE == A.SIZE){ + for(int i = SIZE -1; i >= 0; --i){ + if(digit[i] < A.digit[i]) + return true; + else if(digit[i] > A.digit[i]) + return false; + } + return true; // 相等时返回true + } else return SIZE < A.SIZE; + } +}; + +BigInt convert_to(BigInt A, int base){ // 进制转换,该方法存在BUG,慎用 + BigInt ret(base); + while(A.SIZE > 1 || A.digit[0] > 0){ // 保证A>0 + ret.digit[ret.SIZE++] = A % base; + A = A / base; + } + return ret; +} + +string baseChar = "0123456789ABCDEF"; // 数位 +char S[MAXN]; +istream& operator >>(istream &in , BigInt &A){ + scanf("%s", S); + A.SIZE = 0; A.BASE = 10; // 可调整输入进制 + for(int i = strlen(S) - 1; i >= 0; --i){ + A.digit[A.SIZE++] = baseChar.find(S[i]); + } + return in; +} +ostream& operator <<(ostream &out, const BigInt& A){ + for(int i = A.SIZE - 1; i >= 0; --i) + printf("%c", baseChar[A.digit[i]]); + return out; +} +```) + +== 最小生成树 + +=== Kruskal算法:$O(e log e)$ + +将所有的边按从小到大排列,依次取出。如果取到的边可以将两个不连通的部分联通,则选择它;如果取到的边连接的是已经连图的两个点,则不做操作。提前退出可以生成最小生成森林($n - k$次连接对应$k$棵树的$k-text("森林")$。 + +#code_snippet(```cpp +struct Edge{ // 带权边 + int from, to, dist; + Edge(int u, int v, int d): from(u), to(v), dist(d) {} + bool operator < (Edge x) const{ // const才能用PQ调用 + return dist > x.dist; //【最小】/最大支撑树 + } +}; +int n, m; +priority_queue PQ; // 按边权排序(默认大根堆) +uf_set U(MAXNODE); // 【并查集模板】 +int Kruskal(){ + int ret = 0, cnt = 0; + while(!PQ.empty()){ + Edge t = PQ.top(); PQ.pop(); // 取出最小边 + // cout << "dist:" <> n >> m; + for (int i = 0; i < m; i++) { + int x, y, z; cin >> x >> y >> z; + PQ.push(Edge(x, y, z)); + } + int ret = Kruskal(); + + if(ret == INF) cout << "orz" < +#include +#include +using namespace std; +constexpr int N = 5050, M = 2e5 + 10; + +struct E { + // v: 终点的编号 + // w: 权 + // x: 同起始点的下一个边在e[]中的编号 + int v, w, x; +} e[M * 2]; + +int n, m, h[N], cnte; + +// h[n] 为n号点为起始点的第一个边在e[]中的编号 + +// 最后一个加入的u起始的边将成为h[u] +void adde(int u, int v, int w) { e[++cnte] = E{v, w, h[u]}, h[u] = cnte; } + +// 以u为起点到达目前已生成树的一条边 +struct S { + int u, d; +}; + +// STL默认priority queue是大堆,这样可以使得它变为小堆 +bool operator<(const S &x, const S &y) { return x.d > y.d; } + +priority_queue q; +// dis[u]为u到当前最小树的最近距离 +int dis[N]; +bool vis[N]; + +int res = 0, cnt = 0; + +void Prim() { + memset(dis, 0x3f, sizeof(dis)); + dis[1] = 0; + q.push({1, 0}); + while (!q.empty()) { + if (cnt >= n) break; + int u = q.top().u, d = q.top().d; + q.pop(); + if (vis[u]) continue; + vis[u] = true; + ++cnt; + res += d; + for (int i = h[u]; i; i = e[i].x) { + int v = e[i].v, w = e[i].w; + if (w < dis[v]) { + dis[v] = w, q.push({v, w}); + } + } + } +} + +int main() { + cin >> n >> m; + for (int i = 1, u, v, w; i <= m; ++i) { + cin >> u >> v >> w, adde(u, v, w), adde(v, u, w); + } + Prim(); + if (cnt == n) + cout << res; + else + cout << "No MST."; + return 0; +} +```) + +== 最短路 + +=== Dijkstra算法:$O(e log n)$ + +将起点加入集合;找集合点向外连接的边中最短的边;将终点加入集合,并记录最短距离。 + +#code_snippet(```cpp +struct edge { + int v, w; +}; + +struct node { + int dis, u; + + bool operator>(const node& a) const { return dis > a.dis; } +}; + +vector e[MAXN]; +int dis[MAXN], vis[MAXN]; +priority_queue, greater> q; + +void dijkstra(int n, int s) { + memset(dis, 0x3f, (n + 1) * sizeof(int)); + memset(vis, 0, (n + 1) * sizeof(int)); + dis[s] = 0; + q.push({0, s}); + while (!q.empty()) { + int u = q.top().u; + q.pop(); + if (vis[u]) continue; + vis[u] = 1; + for (auto ed : e[u]) { + int v = ed.v, w = ed.w; + if (dis[v] > dis[u] + w) { + dis[v] = dis[u] + w; + q.push({dis[v], v}); + } + } + } +} +```) + +== 树状数组 + +#code_snippet(```cpp +int C[MAXN]; +int lowbit(int x) {return x&(-x);} // 计算x二进制下只保留最低位1后的值 +vector A(1,0); +struct BIT{ // 树状数组 + int n; + //所有数组统一从下标1开始使用!!! + BIT(vector& A){ // 初始化,O(nlogn) + n = A.size() - 1; + for(int i = 1; i < A.size(); ++i) + add(i, A[i]); // 从C[1]开始插入 + } + int sum(int x){ // 前缀和[1, x] + int ret = 0; + while(x > 0){ ret += C[x]; x-= lowbit(x);} + return ret; + } + int query(int L, int R){ // 查询操作,O(logn) + return sum(R) - sum(L-1); + } + void add(int x, int d){ // 修改操作,O(logn) + while(x <= n){ + C[x] += d; x += lowbit(x); + } + } +}; +```) + +== 线段树 + +与树状数组的基本思路类似,都是维护一段的信息。支持 +- `update(x, v)`,将$A_x$修改为$v$:$O(log n)$。 +- `query(L, R)`,求集合${A_L, A_(L + 1), dots, A_R}$的min,max,sum或者其它性质。这个性质必须满足结合律。 + +#code_snippet(```cpp +struct SegmentTree{ // 区间求和型,所有下标从1开始!!! + int n; // 元素个数 + int *a; + int d[2 * MAXN - 1]; // 保存节点信息 + SegmentTree(int N, int *A){ + n = N; a = A; // 传入初始数组 + build(1, n, 1); + } + void build(int s, int t, int p) { // build(1, n, 1) + // 对 [s,t] 区间建立线段树,当前根的编号为 p + if (s == t) { d[p] = a[s]; return;} + int m = (s + t) / 2; + build(s, m, p * 2), build(m + 1, t, p * 2 + 1); // 递归对左右区间建树 + d[p] = d[p * 2] + d[(p * 2) + 1]; // 【求和】 + } + void update(int x, int v, int s, int t, int p){ // 将A[x]修改为v + // update(x, v, 1, n, 1); + if(x == s && x == t) return; + d[p] += v - a[x]; a[x] = v; // 【更新】 + int m = (s + t) >> 1; + if (s <= x && x <= m) update(x, v, s, m, p * 2); + else update(x, v, m + 1, t, p * 2 + 1); + } + int query_sum(int le, int ri, int s, int t, int p) { // 计算区间和 + // query_sum(le, ri, 1, n, 1); + // [le,ri] 为查询区间,[s,t] 为当前节点包含的区间,p 为当前节点的编号 + if (le <= s && t <= ri) + return d[p]; // 当前区间为询问区间的子集时直接返回当前区间的和 + int m = (s + t) >> 1, sum = 0; + if (le <= m) sum += query_sum(le, ri, s, m, p * 2); + if (ri > m) sum += query_sum(le, ri, m + 1, t, p * 2 + 1); + return sum; + } +}; +```) + +== 并查集 + +#code_snippet(```cpp +#define MAXNODE 5005 +struct uf_set{ + int f[MAXNODE], SIZE; + int size[MAXNODE]; // 子树大小,初始为1 + uf_set(int s): SIZE(s) { + _for(i, 0, SIZE) f[i] = i; // i就在它本身的集合里 + _for(i, 0, SIZE) size[i] = 1; + } + int find(int x){ // ≈ O(!) + return (f[x] == x) ? x: f[x] = find(f[x]); // 路径压缩 + } + void Union(int x, int y){ + int xx = find(x), yy = find(y); + if (xx == yy) return; + if (size[xx] > size[yy]) //启发式合并,保证小的合到大的里 + swap(xx, yy); + f[xx] = yy; + size[yy] += size[xx]; + } +}; +```) \ No newline at end of file diff --git a/leetcodecheatsheet/img/三角形剖分.png b/leetcodecheatsheet/img/三角形剖分.png new file mode 100644 index 0000000..c3b1568 Binary files /dev/null and b/leetcodecheatsheet/img/三角形剖分.png differ