Files
Leetcode-100/leetcodecheatsheet/cpp_reference.typ
2025-08-21 23:24:24 +08:00

1102 lines
30 KiB
Typst
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<string>
#include<vector>
#include<stack>
#include<bitset>
#include<cstdlib>
#include<cmath>
#include<set>
#include<list>
#include<deque>
#include<map>
#include<queue>
#include<functional>
#include<bits/stdc++.h>
```
= 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; // w0:星期一...依此类推
}
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<int> &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<int>& 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<long long> 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<int> &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<int> w, vector<int> 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<int> w, vector<int> 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<bits/stdc++.h>
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<num[i]; j++)
// 0, 1, 2, 3, 4作为百位多出现了一些次数。
cnt[j] += ten[i-1];
long long num2=0;
// 下面这个循环把43从数位再拼回来
for(int j= i-1; 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<int> split(int n){ // 传入整数
vector<int> 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<int> split(string n){ // 传入字符串
vector<int> 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<int> Gray_Create(int n){
vector<int> 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<Edge> PQ; // 按边权排序(默认大根堆)
uf_set U(MAXNODE); // 【并查集模板】
int Kruskal(){
int ret = 0, cnt = 0;
while(!PQ.empty()){
Edge t = PQ.top(); PQ.pop(); // 取出最小边
// cout << "dist:" <<t.dist <<endl;
if(U.find(t.from) != U.find(t.to)){ // 不属于同一集合
U.Union(t.from, t.to);
// EXECUTE(t); // 对当前边做特定操作
ret += t.dist;
cnt ++;
}
}
if(cnt != n - 1) return INF; // 原图不连通
return ret; // 返回最小生成树权值
}
int main(){
// freopen("OJ.in", "r", stdin);
cin >> 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" <<endl;
else cout << ret << endl;
return 0;
}
```)
=== Prim算法$O(n log n)$
维护一个已经加入树的点。选所有从已在数中的点延伸出的边中最短的一条,将对应的点加入树。
#code_snippet(```cpp
// 使用二叉堆优化的 Prim 算法。
#include <cstring>
#include <iostream>
#include <queue>
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<S> 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<edge> e[MAXN];
int dis[MAXN], vis[MAXN];
priority_queue<node, vector<node>, greater<node>> 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<int> A(1,0);
struct BIT{ // 树状数组
int n;
//所有数组统一从下标1开始使用
BIT(vector<int>& 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}$的minmaxsum或者其它性质。这个性质必须满足结合律。
#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];
}
};
```)