Algorithm Basics Template

Contents

Graph Theory

Dynamic Programming

Basic Algorithms

1 Sorting

1.1 Quick Sort Template

—— Template problem AcWing 785. Quick Sort

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
void quick_sort(int q[], int l, int r)
{
    if (l >= r) return;

    int i = l - 1, j = r + 1, x = q[l + r >> 1];
    while (i < j)
    {
        do i ++ ; while (q[i] < x);
        do j -- ; while (q[j] > x);
        if (i < j) swap(q[i], q[j]);
    }
    quick_sort(q, l, j), quick_sort(q, j + 1, r);
}

1.2 Merge Sort Template

—— Template problem AcWing 787. Merge Sort

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
void merge_sort(int q[], int l, int r)
{
    if (l >= r) return;

    int mid = l + r >> 1;
    merge_sort(q, l, mid);
    merge_sort(q, mid + 1, r);

    int k = 0, i = l, j = mid + 1;
    while (i <= mid && j <= r)
        if (q[i] <= q[j]) tmp[k++] = q[i++];
        else tmp[k++] = q[j++];

    while (i <= mid) tmp[k++] = q[i++];
    while (j <= r) tmp[k++] = q[j++];

    for (i = l, j = 0; i <= r; i++, j++) q[i] = tmp[j];
}

2.1 Integer Binary Search Template

—— Template problem AcWing 789. Range of Numbers

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
bool check(int x) {/* ... */} // Checks whether x satisfies some property

// When the interval [l, r] is divided into [l, mid] and [mid + 1, r]:
int bsearch_1(int l, int r)
{
    while (l < r)
    {
        int mid = l + r >> 1;
        if (check(mid)) r = mid;    // check() determines if mid meets the criteria
        else l = mid + 1;
    }
    return l;
}
// When the interval [l, r] is divided into [l, mid - 1] and [mid, r]:
int bsearch_2(int l, int r)
{
    while (l < r)
    {
        int mid = l + r + 1 >> 1;
        if (check(mid)) l = mid;
        else r = mid - 1;
    }
    return l;
}

2.2 Floating-Point Binary Search Template

—— Template problem AcWing 790. Cube Root of a Number

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
bool check(double x) {/* ... */} // Checks whether x satisfies some property

double bsearch_3(double l, double r)
{
    const double eps = 1e-6;   // eps represents precision, depending on the problem's requirements
    while (r - l > eps)
    {
        double mid = (l + r) / 2;
        if (check(mid)) r = mid;
        else l = mid;
    }
    return l;
}

3 High Precision

3.1 High Precision Addition

—— Template problem AcWing 791. High Precision Addition

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// C = A + B, A >= 0, B >= 0
vector<int> add(vector<int> &A, vector<int> &B)
{
    if (A.size() < B.size()) return add(B, A);

    vector<int> C;
    int t = 0;
    for (int i = 0; i < A.size(); i++)
    {
        t += A[i];
        if (i < B.size()) t += B[i];
        C.push_back(t % 10);
        t /= 10;
    }
1
2
3
if (t) C.push_back(t);
    return C;
}

3.2 High-Precision Subtraction

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// C = A - B, provided A >= B, A >= 0, B >= 0
vector<int> sub(vector<int> &A, vector<int> &B)
{
    vector<int> C;
    for (int i = 0, t = 0; i < A.size(); i ++ )
    {
        t = A[i] - t;
        if (i < B.size()) t -= B[i];
        C.push_back((t + 10) % 10);
        if (t < 0) t = 1;
        else t = 0;
    }

    while (C.size() > 1 && C.back() == 0) C.pop_back();
    return C;
}

3.3 High-Precision Multiplied by Low-Precision

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// C = A * b, A >= 0, b >= 0
vector<int> mul(vector<int> &A, int b)
{
    vector<int> C;

    int t = 0;
    for (int i = 0; i < A.size() || t; i ++ )
    {
        if (i < A.size()) t += A[i] * b;
        C.push_back(t % 10);
        t /= 10;
    }

    while (C.size() > 1 && C.back() == 0) C.pop_back();

    return C;
}

3.4 High-Precision Division by Low-Precision

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// A / b = C ... r, A >= 0, b > 0
vector<int> div(vector<int> &A, int b, int &r)
{
    vector<int> C;
    r = 0;
    for (int i = A.size() - 1; i >= 0; i -- )
    {
        r = r * 10 + A[i];
        C.push_back(r / b);
        r %= b;
    }
    reverse(C.begin(), C.end());
    while (C.size() > 1 && C.back() == 0) C.pop_back();
    return C;
}

4 Prefix and Difference

Prefix: Quickly calculate the sum of a certain segment Difference: Quickly add a certain value +c to a segment, inverse operation of prefix

4.1 1D Prefix Sum

1
2
S[i] = a[1] + a[2] + ... a[i]
a[l] + ... + a[r] = S[r] - S[l - 1]

4.2 2D Prefix Sum

1
2
3
S[i, j] = sum of all elements in the upper left part of the cell at row i and column j
The sum of a submatrix with (x1, y1) as the upper left corner and (x2, y2) as the lower right corner is:
S[x2, y2] - S[x1 - 1, y2] - S[x2, y1 - 1] + S[x1 - 1, y1 - 1]

4.3 1D Difference

  • Template Question AcWing 797. Difference To add c to every number in the interval [l, r]: B[l] += c, B[r + 1] -= c

4.4 2D Difference

1
2
To add c to every element in the submatrix whose upper left corner is (x1, y1) and lower right corner is (x2, y2):
S[x1, y1] += c, S[x2 + 1, y1] -= c, S[x1, y2 + 1] -= c, S[x2 + 1, y2 + 1] += c

5 Bit Manipulation

1
2
To get the k-th digit of n: n >> k & 1
Return the last bit 1 of n: lowbit(n) = n & -n

6 Two Pointers Algorithm

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
for (int i = 0, j = 0; i < n; i ++ )
{
    while (j < i && check(i, j)) j ++ ;

    // logic specific to the problem
}
Common problem categories:

1. For a sequence, use two pointers to maintain a range
2. For two sequences, maintain some kind of order, like merging two ordered sequences in merge sort

7 Discretization

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
vector<int> alls; // store all values to be discretized
sort(alls.begin(), alls.end()); // sort all values
alls.erase(unique(alls.begin(), alls.end()), alls.end());   // remove duplicate elements


```cpp
// Use binary search to find the discretized value corresponding to x
int find(int x) // Find the first position that is greater than or equal to x
{
    int l = 0, r = alls.size() - 1;
    while (l < r)
    {
        int mid = l + r >> 1;
        if (alls[mid] >= x) r = mid;
        else l = mid + 1;
    }
    return r + 1; // Map to 1, 2, ...n
}

8 Interval Merging

—— Template problem AcWing 803. Interval Merging

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// Merge all intervals with intersections
void merge(vector<PII> &segs)
{
    vector<PII> res;

    sort(segs.begin(), segs.end());

    int st = -2e9, ed = -2e9;
    for (auto seg : segs)
        if (ed < seg.first)
        {
            if (st != -2e9) res.push_back({st, ed});
            st = seg.first, ed = seg.second;
        }
        else ed = max(ed, seg.second);

    if (st != -2e9) res.push_back({st, ed});

    segs = res;
}

Data Structures

1 Linked Lists and Adjacency Lists: Storage of Trees and Graphs

1.1 Singly Linked List

—— Template problem AcWing 826. Singly Linked List

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// head stores the head of the list, e[] stores the value of the node, ne[] stores the next pointer of the node, idx indicates the current node in use
int head, e[N], ne[N], idx;

// Initialization
void init()
{
    head = -1;
    idx = 0;
}

// Insert a number a at the head of the list
void insert(int a)
{
    e[idx] = a, ne[idx] = head, head = idx ++ ;
}

// Remove the head node, ensuring the head node exists
void remove()
{
    head = ne[head];
}

1.2 Doubly Linked List

—— Template problem AcWing 827. Doubly Linked List

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// e[] represents the value of the node, l[] represents the left pointer of the node, r[] represents the right pointer of the node, idx indicates the current node in use
int e[N], l[N], r[N], idx;

// Initialization
void init()
{
    // 0 is the left endpoint, 1 is the right endpoint
    r[0] = 1, l[1] = 0;
    idx = 2;
}

// Insert a number x to the right of node a
void insert(int a, int x)
{
    e[idx] = x;
    l[idx] = a, r[idx] = r[a];
    l[r[a]] = idx, r[a] = idx ++ ;
}

// Delete node a
void remove(int a)
{
    l[r[a]] = l[a];
    r[l[a]] = r[a];
}

2 Stacks and Queues: Monotonic Queues, Monotonic Stacks

2.1 Stack

—— Template problem AcWing 828. Simulation Stack

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// tt represents the top of the stack
int stk[N], tt = 0;

// Insert a number at the top of the stack
stk[ ++ tt] = x;

// Pop a number from the top of the stack
tt -- ;

// The value at the top of the stack
stk[tt];

// Check if the stack is empty, if tt > 0, it indicates it's not empty
if (tt > 0)
{

}

2.2 Queue

—— Template problem AcWing 829. Simulation Queue

  1. Regular queue:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// hh represents the head of the queue, tt represents the tail of the queue
int q[N], hh = 0, tt = -1;

// Insert a number at the tail of the queue
q[ ++ tt] = x;

// Pop a number from the head of the queue
hh ++ ;

// The value at the head of the queue
q[hh];

// Check if the queue is empty, if hh <= tt, it indicates it's not empty
if (hh <= tt)
{
}
  1. Circular queue
1
2
// hh represents the head of the queue, tt represents the position after the tail of the queue
int q[N], hh = 0, tt = 0;
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// Insert a number at the end of the queue
q[tt ++ ] = x;
if (tt == N) tt = 0;

// Pop a number from the front of the queue
hh ++ ;
if (hh == N) hh = 0;

// Value at the front of the queue
q[hh];

// Check if the queue is empty, if hh != tt, then it is not empty
if (hh != tt)
{
}

2.3 Monotonic Stack

—— Template problem AcWing 830. Monotonic Stack

Common models: Find the nearest number to the left of each number that is larger/smaller

1
2
3
4
5
6
int tt = 0;
for (int i = 1; i <= n; i ++ )
{
    while (tt && check(stk[tt], i)) tt -- ;
    stk[ ++ tt] = i;
}

2.4 Monotonic Queue

—— Template problem AcWing 154. Sliding Window

Common models: Find the maximum/minimum value in the sliding window

1
2
3
4
5
6
7
int hh = 0, tt = -1;
for (int i = 0; i < n; i ++ )
{
    while (hh <= tt && check_out(q[hh])) hh ++ ;  // Check if the head of the queue slides out of the window
    while (hh <= tt && check(q[tt], i)) tt -- ;
    q[ ++ tt] = i;
}

3 KMP

—— Template problem AcWing 831. KMP String Matching

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// s[] is the long text, p[] is the pattern string, n is the length of s, m is the length of p
Calculate the Next array of the pattern string:
for (int i = 2, j = 0; i <= m; i ++ )
{
    while (j && p[i] != p[j + 1]) j = ne[j];
    if (p[i] == p[j + 1]) j ++ ;
    ne[i] = j;
}

// Matching
for (int i = 1, j = 0; i <= n; i ++ )
{
    while (j && s[i] != p[j + 1]) j = ne[j];
    if (s[i] == p[j + 1]) j ++ ;
    if (j == m)
    {
        j = ne[j];
        // Logic after a successful match
    }
}//The C library function strstr() works in the same way

4 Trie Tree

—— Template problem AcWing 835. Trie String Statistics

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
int son[N][26], cnt[N], idx;
// Node 0 is both the root node and the null node
// son[][] stores the children of each node in the tree
// cnt[] stores the number of words ending in each node

// Insert a string
void insert(char *str)
{
    int p = 0;
    for (int i = 0; str[i]; i ++ )
    {
        int u = str[i] - 'a';
        if (!son[p][u]) son[p][u] = ++ idx;
        p = son[p][u];
    }
    cnt[p] ++ ;
}

// Query the number of occurrences of a string
int query(char *str)
{
    int p = 0;
    for (int i = 0; str[i]; i ++ )
    {
        int u = str[i] - 'a';
        if (!son[p][u]) return 0;
        p = son[p][u];
    }
    return cnt[p];
}//It's possible to use map<string,int>, but the timing would be more than twice as slow

5 Union Find

—— Template problems [AcWing 836. Union Sets](https://www.acwing.com/problem/content/838/, AcWing 837. Number of Points in Connected Components

(1)Naive Union Find:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
    int p[N]; //Stores the ancestor node of each point

    // Return the ancestor node of x
    int find(int x)
    {
        if (p[x] != x) p[x] = find(p[x]);
        return p[x];
    }

    // Initialization, assuming node numbers are 1~n
    for (int i = 1; i <= n; i ++ ) p[i] = i;

    // Merge the two sets a and b belong to:
    p[find(a)] = find(b);

(2)Union Find with size maintenance:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
    int p[N], size[N];
    //p[] stores the ancestor node of each point, size[] only has meaning for ancestor nodes, indicating the number of points in the set the ancestor node belongs to

    // Return the ancestor node of x
    int find(int x)
    {
        if (p[x] != x) p[x] = find(p[x]);
        return p[x];
    }

    // Initialization, assuming node numbers are 1~n
    for (int i = 1; i <= n; i ++ )
    {
        p[i] = i;
        size[i] = 1;
    }
1
2
3
// Merge the two sets to which a and b belong:
size[find(b)] += size[find(a)];
p[find(a)] = find(b);
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
(3) Union-find set maintaining the distance to the ancestor node:
```cpp
    int p[N], d[N];
    //p[] stores the ancestor node of each point, d[x] stores the distance from x to p[x]

    // Return the ancestor node of x
    int find(int x)
    {
        if (p[x] != x)
        {
            int u = find(p[x]);
            d[x] += d[p[x]];
            p[x] = u;
        }
        return p[x];
    }

    // Initialization, assuming the node numbers are 1~n
    for (int i = 1; i <= n; i ++ )
    {
        p[i] = i;
        d[i] = 0;
    }

    // Merge the two sets that a and b belong to:
    p[find(a)] = find(b);
    d[find(a)] = distance; // Initialize the offset of find(a) based on the specific problem

6 Heap

— Template problem AcWing 838. Heap Sort, AcWing 839. Simulated Heap

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// h[N] stores the values in the heap, h[1] is the heap top, the left child of x is 2x, the right child is 2x + 1
// ph[k] stores the position in the heap of the kth inserted point
// hp[k] stores which insert order the point at index k in the heap was
int h[N], ph[N], hp[N], size;

// Swap two points, and their mapping relationships
void heap_swap(int a, int b)
{
    swap(ph[hp[a]],ph[hp[b]]);
    swap(hp[a], hp[b]);
    swap(h[a], h[b]);
}

void down(int u)
{
    int t = u;
    if (u * 2 <= size && h[u * 2] < h[t]) t = u * 2;
    if (u * 2 + 1 <= size && h[u * 2 + 1] < h[t]) t = u * 2 + 1;
    if (u != t)
    {
        heap_swap(u, t);
        down(t);
    }
}

void up(int u)
{
    while (u / 2 && h[u] < h[u / 2])
    {
        heap_swap(u, u / 2);
        u >>= 1;
    }
}

// O(n) build heap
for (int i = n / 2; i; i -- ) down(i);

7 Hash

7.1 General Hash

— Template problem AcWing 840. Simulated Hash Table

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
(1) Chaining
    int h[N], e[N], ne[N], idx;

    // Insert a number into the hash table
    void insert(int x)
    {
        int k = (x % N + N) % N;
        e[idx] = x;
        ne[idx] = h[k];
        h[k] = idx ++ ;
    }

    // Check if a number exists in the hash table
    bool find(int x)
    {
        int k = (x % N + N) % N;
        for (int i = h[k]; i != -1; i = ne[i])
            if (e[i] == x)
                return true;

        return false;
    }

(2) Open Addressing
    int h[N];

    // If x is in the hash table, return the index of x; if x is not in the hash table, return the insertion position for x
    int find(int x)
    {
        int t = (x % N + N) % N;
        while (h[t] != null && h[t] != x)
        {
            t ++ ;
            if (t == N) t = 0;
        }
        return t;
    }

7.2 String Hash

— Template problem AcWing 841. String Hash

Core idea: Treat the string as a base-P number, where P is usually 131 or 13331, as these values are less likely to cause collisions

Tips: Use 2^64 as the modulus. This way, direct storage with unsigned long long, and overflow result is the modulo result

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
typedef unsigned long long ULL;
ULL h[N], p[N]; // h[k] stores the hash value of the first k letters of the string, p[k] stores P^k mod 2^64

// Initialization
p[0] = 1;
for (int i = 1; i <= n; i ++ )
{
    h[i] = h[i - 1] * P + str[i];
    p[i] = p[i - 1] * P;
}
1
2
3
4
5
// Calculate the hash value of the substring str[l ~ r]
ULL get(int l, int r)
{
    return h[r] - h[l - 1] * p[r - l + 1];
}

7.3 Introduction to C++ STL

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
vector, dynamic array, idea of doubling
    size()  Returns the number of elements
    empty()  Returns whether it is empty
    clear()  Clears
    front()/back()
    push_back()/pop_back()
    begin()/end()
    []
    Supports comparison operations, ordered by lexicographical order

pair<int, int>
    first, the first element
    second, the second element
    Supports comparison operations, with first as the primary key and second as the secondary key (lexicographical order)

string, string
    size()/length()  Returns the length of the string
    empty()
    clear()
    substr(start index, (substring length))  Returns substring
    c_str()  Returns the address of the start of the character array containing the string

queue, queue
    size()
    empty()
    push()  Inserts an element at the end of the queue
    front()  Returns the front element
    back()  Returns the back element
    pop()  Pops the front element

priority_queue, priority queue, default is a max heap
    size()
    empty()
    push()  Inserts an element
    top()  Returns the top element
    pop()  Pops the top element
    To define as a min heap: priority_queue<int, vector<int>, greater<int>> q;

stack, stack
    size()
    empty()
    push()  Inserts an element at the top of the stack
    top()  Returns the top element
    pop()  Pops the top element

deque, double-ended queue
    size()
    empty()
    clear()
    front()/back()
    push_back()/pop_back()
    push_front()/pop_front()
    begin()/end()
    []

set, map, multiset, multimap, based on balanced binary tree (red-black tree), dynamically maintaining ordered sequence
    size()
    empty()
    clear()
    begin()/end()
    ++, -- Returns the predecessor and successor, time complexity O(logn)

    set/multiset
        insert()  Inserts a number
        find()  Searches for a number
        count()  Returns the count of a specific number
        erase()
            (1) Input is a number x, deletes all x   O(k + logn)
            (2) Input is an iterator, deletes the iterator
        lower_bound()/upper_bound()
            lower_bound(x)  Returns an iterator to the smallest number that is greater than or equal to x
            upper_bound(x)  Returns an iterator to the smallest number that is greater than x
    map/multimap
        insert()  Insertion is a pair
        erase()  Input parameter is a pair or an iterator
        find()
        []  Note that multimap does not support this operation. Time complexity is O(logn)
        lower_bound()/upper_bound()

unordered_set, unordered_map, unordered_multiset, unordered_multimap, hash table
    Similar to above, time complexity of CRUD operations is O(1)
    Does not support lower_bound()/upper_bound(), iterator's ++, --

bitset, bitset
    bitset<10000> s;
    ~, &, |, ^
    >>, <<
    ==, !=
    []

    count()  Returns the number of ones

    any()  Checks if there is at least one 1
    none()  Checks if all are 0

    set()  Sets all positions to 1
    set(k, v)  Sets the k-th bit to v
    reset()  Sets all bits to 0
    flip()  Equivalent to ~
    flip(k)  Toggles the k-th bit

Search and Graph Theory

1 DFS and BFS

1.1 Storage of Trees and Graphs

Trees are a special kind of graph, with similar storage methods.

For an undirected graph’s edge ab, store two directed edges a->b, b->a.

Thus, we can only consider the storage of directed graphs.

(1) Adjacency matrix: g[a][b] stores the edge a->b

(2) Adjacency list:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// For each point k, open a linked list, storing all points that k can reach. h[k] stores the head of this linked list
int h[N], e[N], ne[N], idx;

// Add an edge a->b
void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}


// Initialization
idx = 0;
memset(h, -1, sizeof h);

1.2 Traversal of Trees and Graphs

Time complexity O(n+m), n represents the number of vertices, m represents the number of edges

(1) Depth-First Traversal — Template problem AcWing 846. Centroid of the Tree

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
int dfs(int u)
{
    st[u] = true; // st[u] indicates that vertex u has been traversed

    for (int i = h[u]; i != -1; i = ne[i])
    {
        int j = e[i];
        if (!st[j]) dfs(j);
    }
}

(2) Breadth-First Traversal — Template problem AcWing 847. Levels of vertices in a graph

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
queue<int> q;
st[1] = true; // indicates that vertex 1 has been traversed
q.push(1);

while (q.size())
{
    int t = q.front();
    q.pop();

    for (int i = h[t]; i != -1; i = ne[i])
    {
        int j = e[i];
        if (!st[j])
        {
            st[j] = true; // indicates that vertex j has been traversed
            q.push(j);
        }
    }
}

2 Traversal of Trees and Graphs Topological Sorting

— Template problem AcWing 848. Topological Ordering of a Directed Graph

Time complexity O(n+m), n represents the number of vertices, m represents the number of edges

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
bool topsort()
{
    int hh = 0, tt = -1;

    // d[i] stores the in-degree of vertex i
    for (int i = 1; i <= n; i ++ )
        if (!d[i])
            q[ ++ tt] = i;

    while (hh <= tt)
    {
        int t = q[hh ++ ];

        for (int i = h[t]; i != -1; i = ne[i])
        {
            int j = e[i];
            if (-- d[j] == 0)
                q[ ++ tt] = j;
        }
    }

    // If all vertices have been enqueued, a topological order exists; otherwise, it does not exist.
    return tt == n - 1;
}

3 Shortest Path

3.1 Naive dijkstra algorithm

— Template problem AcWing 849. Dijkstra for the shortest path I

The time complexity is O(n^2+m), n represents the number of vertices, m represents the number of edges

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
int g[N][N];  // Stores each edge
int dist[N];  // Stores the shortest distance from vertex 1 to every other vertex
bool st[N];   // Stores whether the shortest path for each vertex has been determined

// Seeks the shortest path from vertex 1 to vertex n, returns -1 if it does not exist
int dijkstra()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;

    for (int i = 0; i < n - 1; i ++ )
    {
        int t = -1;     // Among the vertices whose shortest path has not been determined, find the one with the minimum distance
        for (int j = 1; j <= n; j ++ )
            if (!st[j] && (t == -1 || dist[t] > dist[j]))
                t = j;

        // Use t to update the distance of other vertices
        for (int j = 1; j <= n; j ++ )
            dist[j] = min(dist[j], dist[t] + g[t][j]);

        st[t] = true;
    }

    if (dist[n] == 0x3f3f3f3f) return -1;
    return dist[n];
}

3.2 Heap-optimized dijkstra

— Template problem AcWing 850. Dijkstra for the shortest path II

Time complexity O(mlogn), n represents the number of vertices, m represents the number of edges

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
typedef pair<int, int> PII;

int n;      // Number of vertices
int h[N], w[N], e[N], ne[N], idx;       // Adjacency list to store all edges
int dist[N];        // Stores the distance of all vertices from vertex 1
bool st[N];     // Stores whether the shortest distance for each vertex has been determined

// Seeks the shortest distance from vertex 1 to vertex n, returns -1 if it does not exist
int dijkstra()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;
    priority_queue<PII, vector<PII>, greater<PII>> heap;
    heap.push({0, 1});      // first stores the distance, second stores the vertex number

    while (heap.size())
    {
        auto t = heap.top();
        heap.pop();

        int ver = t.second, distance = t.first;


```cpp
        if (st[ver]) continue;
        st[ver] = true;

        for (int i = h[ver]; i != -1; i = ne[i])
        {
            int j = e[i];
            if (dist[j] > distance + w[i])
            {
                dist[j] = distance + w[i];
                heap.push({dist[j], j});
            }
        }
    }

    if (dist[n] == 0x3f3f3f3f) return -1;
    return dist[n];
}

3.3 Bellman-Ford Algorithm

—— Template problem AcWing 853. Shortest Path with Edge Limits Time complexity O(nm), n represents the number of vertices, m represents the number of edges

Note that in the template problem, you need to modify the template slightly, adding a backup array, for details see the template problem.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
int n, m;       // n represents the number of vertices, m represents the number of edges
int dist[N];        // dist[x] stores the shortest path distance from 1 to x

struct Edge     // Edge, a is the start vertex, b is the end vertex, w is the weight of the edge
{
    int a, b, w;
}edges[M];

// Finds the shortest path distance from 1 to n; returns -1 if it's not possible to travel from 1 to n.
int bellman_ford()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;

    // If the triangle inequality is still relaxed in the nth iteration, it means there exists a shortest path with length n+1. According to the pigeonhole principle, there must be at least two identical points on the path, indicating that the graph contains a negative weight cycle.
    for (int i = 0; i < n; i ++ )
    {
        for (int j = 0; j < m; j ++ )
        {
            int a = edges[j].a, b = edges[j].b, w = edges[j].w;
            if (dist[b] > dist[a] + w)
                dist[b] = dist[a] + w;
        }
    }

    if (dist[n] > 0x3f3f3f3f / 2) return -1;
    return dist[n];
}

3.4 spfa Algorithm (Queue Optimized Bellman-Ford Algorithm)

—— Template problem AcWing 851. spfa for shortest path Time complexity On average O(m), in the worst case O(nm), n represents the number of vertices, m represents the number of edges

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
int n;      // Total number of vertices
int h[N], w[N], e[N], ne[N], idx;       // Adjacency list to store all edges
int dist[N];        // Stores the shortest distance from vertex 1 to each vertex
bool st[N];     // Stores whether each vertex is in the queue

// Finds the shortest path distance from vertex 1 to n; returns -1 if it's not possible to walk from 1 to n.
int spfa()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;

    queue<int> q;
    q.push(1);
    st[1] = true;

    while (q.size())
    {
        auto t = q.front();
        q.pop();

        st[t] = false;

        for (int i = h[t]; i != -1; i = ne[i])
        {
            int j = e[i];
            if (dist[j] > dist[t] + w[i])
            {
                dist[j] = dist[t] + w[i];
                if (!st[j])     // If j is already in the queue, there's no need to insert j again
                {
                    q.push(j);
                    st[j] = true;
                }
            }
        }
    }

    if (dist[n] == 0x3f3f3f3f) return -1;
    return dist[n];
}

3.5 spfa Algorithm to Detect Negative Cycles in Graphs

—— Template problem AcWing 852. spfa to detect negative cycles Time complexity O(nm), n represents the number of vertices, m represents the number of edges

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int n;      // Total number of vertices
int h[N], w[N], e[N], ne[N], idx;       // Adjacency list to store all edges
int dist[N], cnt[N];        // dist[x] stores the shortest distance from vertex 1 to x, cnt[x] stores the number of vertices passed through from 1 to x on the shortest path
bool st[N];     // Stores whether each vertex is in the queue

// If there exists a negative cycle, returns true; otherwise, returns false.
bool spfa()
{
    // No need to initialize dist array
    // Principle: If there are n vertices on a certain shortest path (excluding oneself), then including oneself, there is a total of n+1 vertices. According to the pigeonhole principle, there must be two identical vertices, therefore a cycle exists.

    queue<int> q;
    for (int i = 1; i <= n; i ++ )
    {
        q.push(i);
        st[i] = true;
    }

    while (q.size())
    {
        auto t = q.front();
        q.pop();

        st[t] = false;
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
        for (int i = h[t]; i != -1; i = ne[i])
        {
            int j = e[i];
            if (dist[j] > dist[t] + w[i])
            {
                dist[j] = dist[t] + w[i];
                cnt[j] = cnt[t] + 1;
                if (cnt[j] >= n) return true;       // If the shortest path from node 1 to x contains at least n nodes (excluding itself), then it implies the existence of a cycle
                if (!st[j])
                {
                    q.push(j);
                    st[j] = true;
                }
            }
        }
    }

    return false;
}

3.6 Floyd’s Algorithm

—— Template problem AcWing 854. Floyd’s Algorithm for Shortest Paths The time complexity is O(n^3), where n represents the number of vertices.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
Initialization:
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= n; j ++ )
            if (i == j) d[i][j] = 0;
            else d[i][j] = INF;

// After the algorithm finishes, d[a][b] represents the shortest distance from a to b.
void floyd()
{
    for (int k = 1; k <= n; k ++ )
        for (int i = 1; i <= n; i ++ )
            for (int j = 1; j <= n; j ++ )
                d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
}

4 Minimum Spanning Tree

4.1 Naive Prim Algorithm

—— Template problem AcWing 858. Prim’s Algorithm for Minimum Spanning Tree The time complexity is O(n^2+m), where n represents the number of vertices, and m represents the number of edges.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
int n;      // n represents the number of vertices
int g[N][N];        // Adjacency matrix, storing all edges
int dist[N];        // Stores the distance from other vertices to the current minimum spanning tree
bool st[N];     // Stores whether each vertex is already in the spanning tree


// If the graph is not connected, it returns INF(value is 0x3f3f3f3f), otherwise it returns the sum of the tree edge weights of the minimum spanning tree.
int prim()
{
    memset(dist, 0x3f, sizeof dist);

    int res = 0;
    for (int i = 0; i < n; i ++ )
    {
        int t = -1;
        for (int j = 1; j <= n; j ++ )
            if (!st[j] && (t == -1 || dist[t] > dist[j]))
                t = j;

        if (i && dist[t] == INF) return INF;

        if (i) res += dist[t];
        st[t] = true;

        for (int j = 1; j <= n; j ++ ) dist[j] = min(dist[j], g[t][j]);
    }

    return res;
}

4.2 Kruskal’s Algorithm

—— Template problem AcWing 859. Kruskal’s Algorithm for Minimum Spanning Tree The time complexity is O(mlogm), n represents the number of vertices, m represents the number of edges.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
int n, m;       // n is the number of vertices, m is the number of edges
int p[N];       // The parent array of the disjoint set union

struct Edge     // To store edges
{
    int a, b, w;

    bool operator< (const Edge &W)const
    {
        return w < W.w;
    }
}edges[M];

int find(int x)     // Core operation of the disjoint set union
{
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}

int kruskal()
{
    sort(edges, edges + m);

    for (int i = 1; i <= n; i ++ ) p[i] = i;    // Initialize the disjoint set union

    int res = 0, cnt = 0;
    for (int i = 0; i < m; i ++ )
    {
        int a = edges[i].a, b = edges[i].b, w = edges[i].w;

        a = find(a), b = find(b);
        if (a != b)     // If the two connected components are not connected, then merge these two components
        {
            p[a] = b;
            res += w;
            cnt ++ ;
        }
    }
1
2
3
if (cnt < n - 1) return INF;
return res;
}

5 Bipartite Graph

5.1 Coloring Method to Judge Bipartite Graph

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
int n;      // n indicates the number of vertices
int h[N], e[M], ne[M], idx;     // Adjacency list to store the graph
int color[N];       // Indicates the color of each vertex, -1 means uncolored, 0 means white, 1 means black

// Parameters: u indicates the current node, c indicates the color of the current node
bool dfs(int u, int c)
{
    color[u] = c;
    for (int i = h[u]; i != -1; i = ne[i])
    {
        int j = e[i];
        if (color[j] == -1)
        {
            if (!dfs(j, !c)) return false;
        }
        else if (color[j] == c) return false;
    }

    return true;
}

bool check()
{
    memset(color, -1, sizeof color);
    bool flag = true;
    for (int i = 1; i <= n; i ++ )
        if (color[i] == -1)
            if (!dfs(i, 0))
            {
                flag = false;
                break;
            }
    return flag;
}

5.2 Hungarian Algorithm

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
int n1, n2;     // n1 indicates the number of vertices in the first set, n2 indicates the number of vertices in the second set
int h[N], e[M], ne[M], idx;     // Adjacency list stores all edges, in the Hungarian algorithm, only edges from the first set to the second set are used, so here it only needs to store one direction of the edges
int match[N];       // Stores which vertex in the first set is currently matched with each vertex in the second set
bool st[N];     // Indicates whether each vertex in the second set has been traversed

bool find(int x)
{
    for (int i = h[x]; i != -1; i = ne[i])
    {
        int j = e[i];
        if (!st[j])
        {
            st[j] = true;
            if (match[j] == 0 || find(match[j]))
            {
                match[j] = x;
                return true;
            }
        }
    }

    return false;
}

// To find the maximum matching number, enumerate each vertex in the first set to see if it can match a vertex in the second set
int res = 0;
for (int i = 1; i <= n1; i ++ )
{
    memset(st, false, sizeof st);
    if (find(i)) res ++ ;
}

Mathematical Knowledge

1 Prime Numbers

1.1 Trial Division for Prime Number Detection

1
2
3
4
5
6
7
8
bool is_prime(int x)
{
    if (x < 2) return false;
    for (int i = 2; i <= x / i; i ++ )
        if (x % i == 0)
            return false;
    return true;
}

1.2 Trial Division for Prime Factorization

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
void divide(int x)
{
    for (int i = 2; i <= x / i; i ++ )
        if (x % i == 0)
        {
            int s = 0;
            while (x % i == 0) x /= i, s ++ ;
            cout << i << ' ' << s << endl;
        }
    if (x > 1) cout << x << ' ' << 1 << endl;
    cout << endl;
}

1.3 Simple Sieve Method for Prime Number

1
2
int primes[N], cnt;     // primes[] stores all prime numbers
bool st[N];         // st[x] stores if x has been sieved out

void get_primes(int n) { for (int i = 2; i <= n; i ++ ) { if (st[i]) continue; primes[cnt ++ ] = i; for (int j = i + i; j <= n; j += i) st[j] = true; } }

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
### Linear Sieve Algorithm for Finding Prime Numbers
—— Template question [AcWing 868. Sieve of Eratosthenes](https://www.acwing.com/problem/content/870/)
```cpp
int primes[N], cnt;     // primes[] stores all the prime numbers
bool st[N];         // st[x] indicates whether x has been sieved

void get_primes(int n)
{
    for (int i = 2; i <= n; i ++ )
    {
        if (!st[i]) primes[cnt ++ ] = i;
        for (int j = 0; primes[j] <= n / i; j ++ )
        {
            st[primes[j] * i] = true;
            if (i % primes[j] == 0) break;
        }
    }
}

2 Divisor

2.1 Trial Division for Finding All Divisors

—— Template question AcWing 869. Trial Division

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
vector<int> get_divisors(int x)
{
    vector<int> res;
    for (int i = 1; i <= x / i; i ++ )
        if (x % i == 0)
        {
            res.push_back(i);
            if (i != x / i) res.push_back(x / i);
        }
    sort(res.begin(), res.end());
    return res;
}

2.2 Number of Divisors and Sum of Divisors

—— Template questions AcWing 870. Number of Divisors, AcWing 871. Sum of Divisors

1
2
3
If N = p1^c1 * p2^c2 * ... *pk^ck
Number of divisors: (c1 + 1) * (c2 + 1) * ... * (ck + 1)
Sum of divisors: (p1^0 + p1^1 + ... + p1^c1) * ... * (pk^0 + pk^1 + ... + pk^ck)

2.3 Euclidean Algorithm

—— Template question AcWing 872. Greatest Common Divisor

1
2
3
4
int gcd(int a, int b)
{
    return b ? gcd(b, a % b) : a;
}

3 Euler’s Totient Function

3.1 Finding Euler’s Totient Function

—— Template question AcWing 873. Euler’s Totient Function

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
int phi(int x)
{
    int res = x;
    for (int i = 2; i <= x / i; i ++ )
        if (x % i == 0)
        {
            res = res / i * (i - 1);
            while (x % i == 0) x /= i;
        }
    if (x > 1) res = res / x * (x - 1);

    return res;
}

3.2 Sieve Method for Euler’s Totient Function

—— Template question AcWing 874. Sieve Method for Euler’s Totient Function

1
2
3
int primes[N], cnt;     // primes[] stores all the prime numbers
int euler[N];           // stores Euler's Totient Function for each number
bool st[N];         // st[x] indicates whether x has been sieved
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void get_eulers(int n)
{
    euler[1] = 1;
    for (int i = 2; i <= n; i ++ )
    {
        if (!st[i])
        {
            primes[cnt ++ ] = i;
            euler[i] = i - 1;
        }
        for (int j = 0; primes[j] <= n / i; j ++ )
        {
            int t = primes[j] * i;
            st[t] = true;
            if (i % primes[j] == 0)
            {
                euler[t] = euler[i] * primes[j];
                break;
            }
            euler[t] = euler[i] * (primes[j] - 1);
        }
    }
}

4 Fast Exponentiation

—— Template Problem AcWing 875. Fast Exponentiation Calculate (m^k \mod p), with a time complexity of (O(\log k)).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
int qmi(int m, int k, int p)
{
    int res = 1 % p, t = m;
    while (k)
    {
        if (k&1) res = res * t % p;
        t = t * t % p;
        k >>= 1;
    }
    return res;
}

5 Extended Euclidean Algorithm

—— Template Problem AcWing 877. Extended Euclidean Algorithm

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// Solving for x, y such that ax + by = gcd(a, b)
int exgcd(int a, int b, int &x, int &y)
{
    if (!b)
    {
        x = 1; y = 0;
        return a;
    }
    int d = exgcd(b, a % b, y, x);
    y -= (a/b) * x;
    return d;
}

6 Gaussian Elimination

—— Template Problem AcWing 883. Gaussian Elimination for Solving Linear Equations

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// a[N][N] is the augmented matrix
int gauss()
{
    int c, r;
    for (c = 0, r = 0; c < n; c ++ )
    {
        int t = r;
        for (int i = r; i < n; i ++ )   // Find the row with the maximum absolute value
            if (fabs(a[i][c]) > fabs(a[t][c]))
                t = i;

        if (fabs(a[t][c]) < eps) continue;

        for (int i = c; i <= n; i ++ ) swap(a[t][i], a[r][i]);      // Swap the row with the maximum absolute value to the top
        for (int i = n; i >= c; i -- ) a[r][i] /= a[r][c];      // Make the first element of the current row 1
        for (int i = r + 1; i < n; i ++ )       // Use the current row to make all the elements of the column below 0
            if (fabs(a[i][c]) > eps)
                for (int j = n; j >= c; j -- )
                    a[i][j] -= a[r][j] * a[i][c];

        r ++ ;
    }

    if (r < n)
    {
        for (int i = r; i < n; i ++ )
            if (fabs(a[i][n]) > eps)
                return 2; // No solution
        return 1; // Infinitely many solutions
    }

    for (int i = n - 1; i >= 0; i -- )
        for (int j = i + 1; j < n; j ++ )
            a[i][n] -= a[i][j] * a[j][n];
1
2
return 0; // Has a unique solution
}

7 Combinatorial Counting

7.1 Recursive method to calculate combinations

—— Template problem AcWing 885. Calculate Combinations I

1
2
3
4
5
// c[a][b] represents the number of ways to choose b apples from a apples
for (int i = 0; i < N; i ++ )
    for (int j = 0; j <= i; j ++ )
        if (!j) c[i][j] = 1;
        else c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % mod;

7.2 Calculating combinations by pre-processing the inverse

—— Template problem AcWing 886. Calculate Combinations II First, pre-process the remainder of all factorials modulo fact[N], and the inverse elements of all factorials modulo infact[N] If the modulo number is a prime, the inverse element can be calculated using Fermat’s little theorem

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
int qmi(int a, int k, int p)    // Quick power template
{
    int res = 1;
    while (k)
    {
        if (k & 1) res = (LL)res * a % p;
        a = (LL)a * a % p;
        k >>= 1;
    }
    return res;
}

// Pre-process the remainder of factorials and the remainder of factorial inverses
fact[0] = infact[0] = 1;
for (int i = 1; i < N; i ++ )
{
    fact[i] = (LL)fact[i - 1] * i % mod;
    infact[i] = (LL)infact[i - 1] * qmi(i, mod - 2, mod) % mod;
}

7.3 Lucas’s theorem

—— Template problem AcWing 887. Calculate Combinations III

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// If p is a prime, then for any integer 1 <= m <= n, we have:
    C(n, m) = C(n % p, m % p) * C(n / p, m / p) (mod p)

int qmi(int a, int k, int p)  // Quick power template
{
    int res = 1 % p;
    while (k)
    {
        if (k & 1) res = (LL)res * a % p;
        a = (LL)a * a % p;
        k >>= 1;
    }
    return res;
}

int C(int a, int b, int p)  // Calculate combination number C(a, b) using the theorem
{
    if (a < b) return 0;

    LL x = 1, y = 1;  // x is the numerator, y is the denominator
    for (int i = a, j = 1; j <= b; i --, j ++ )
    {
        x = (LL)x * i % p;
        y = (LL) y * j % p;
    }

    return x * (LL)qmi(y, p - 2, p) % p;
}

int lucas(LL a, LL b, int p)
{
    if (a < p && b < p) return C(a, b, p);
    return (LL)C(a % p, b % p, p) * lucas(a / p, b / p, p) % p;
}

7.4 Prime factorization method to calculate combinations

—— Template problem AcWing 888. Calculate Combinations IV When we need to calculate the actual value of a combination, rather than the remainder modulo some number, the method of prime factorization is more useful:

  1. Use the sieve method to find all primes within a range
  2. Calculate the power of each prime factor through the formula C(a, b) = a! / b! / (a - b)!. The power of p in n! is n / p + n / p^2 + n / p^3 + …
  3. Use high-precision multiplication to multiply all the prime factors
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
int primes[N], cnt;     // Stores all primes
int sum[N];     // Stores the count for each prime
bool st[N];     // Indicates whether a number has been sieved out

void get_primes(int n)      // Linear sieve method for finding primes
{
    for (int i = 2; i <= n; i ++ )
    {
        if (!st[i]) primes[cnt ++ ] = i;
        for (int j = 0; primes[j] <= n / i; j ++ )
        {
            st[primes[j] * i] = true;
            if (i % primes[j] == 0) break;
        }
    }
}


int get(int n, int p)       // Calculate the frequency of p in n!
{
    int res = 0;
    while (n)
    {
        res += n / p;
        n /= p;
    }
    return res;
}


vector<int> mul(vector<int> a, int b)       // High precision multiplication by low precision template
{
    vector<int> c;
    int t = 0;
    for (int i = 0; i < a.size(); i ++ )
    {
        t += a[i] * b;
        c.push_back(t % 10);
        t /= 10;
    }

    while (t)
    {
        c.push_back(t % 10);
        t /= 10;
    }

    return c;
}

get_primes(a);  // Preprocess all primes within the range

for (int i = 0; i < cnt; i ++ )     // Calculate the frequency of each prime factor
{
    int p = primes[i];
    sum[i] = get(a, p) - get(b, p) - get(a - b, p);
}

vector<int> res;
res.push_back(1);

for (int i = 0; i < cnt; i ++ )     // Multiply all prime factors using high precision multiplication
    for (int j = 0; j < sum[i]; j ++ )
        res = mul(res, primes[i]);

8 Game theory

8.1 Catalan numbers

—— Template for AcWing 889. 01 Sequences that Satisfy Conditions

Given n zeros and n ones, they are arranged in a sequence of length 2n in some order, such that for any prefix the number of zeros is not less than the number of ones, the number of such sequences is: Cat(n) = C(2n, n) / (n + 1)

NIM game —— Template for AcWing 891. Nim Game

Given N piles of items, the i-th pile has Ai items. Two players take turns acting, each time they can choose a pile and take away any number of items, possibly emptying the pile, but cannot not take any item. The player who takes the last item wins. Assuming both players adopt the optimal strategy, the question is whether the first player is guaranteed to win.

This kind of game is called NIM. The statuses faced during the game process are called positions. The player who acts first in the entire game is called the first player, and the player who acts second is called the second player. If under a certain position, whatever action is taken, the game is lost, then that position is called a losing position.

The so-called optimal strategy means that if there exists some action under a certain position that leads to the opponent facing a losing position after the action, then prioritize that action. Similarly, such positions are called winning positions. The game theory problems we discuss generally consider only the ideal situation, that is, both players make no mistakes and act with the best strategy to determine the outcome of the game.

There are no ties in NIM, only situations where the first mover is assured to win or is assured to lose.

Theorem: In the NIM game, the first player is guaranteed to win if and only if A1 ^ A2 ^ … ^ An != 0

8.2 Impartial Combinatorial Games (ICG)

A game is considered an impartial combinatorial game if it satisfies:

  • Two players take turns to move;
  • At any moment in the game, the legal moves available do not depend on which player’s turn it is;
  • The player who cannot move loses; NIM game is an impartial combinatorial game, but strategic board games such as Go are not, because in Go, the battling sides can only place black or white stones, respectively, and the victory judgement is more complex, failing to meet conditions 2 and 3.

8.3 Directed Graph Games

Given a directed acyclic graph with a unique starting point where a piece is placed. Players take turns to move this piece along the directed edges, moving one step at a time, and the player who cannot move loses. This game is known as a directed graph game.

Any impartial combinatorial game can be transformed into a directed graph game. The specific method is to view each game state as a node in the graph, and to connect directed edges from each state to the next states that can be reached by legal moves.

8.4 Mex Operation

Let S be a set of non-negative integers. Define mex(S) as the operation of finding the smallest non-negative integer not in set S, i.e.: mex(S) = min{x}, x belongs to natural numbers, and x ∉ S

8.5 SG Function

In a directed graph game, for any node x, assume there are k directed edges starting from x, leading to nodes y1, y2, …, yk, define SG(x) as the result of performing mex(S) operation on the set of SG function values of the successor nodes y1, y2, …, yk, i.e.: SG(x) = mex({SG(y1), SG(y2), …, SG(yk)})

Specifically, the SG function value of the entire directed graph game G is defined as the SG function value of the starting point s of the directed graph game, i.e., SG(G) = SG(s).

8.6 Sum of Directed Graph Games

——— Template Problem AcWing 893. Collection-Nim game Suppose G1, G2, …, Gm are m directed graph games. Define a directed graph game G, which allows the action of choosing any directed graph game Gi and moving one step in Gi. G is called the sum of the directed graph games G1, G2, …, Gm.

The SG function value of the sum of directed graph games equals the XOR sum of the SG function values of each included sub-game, i.e.: SG(G) = SG(G1) ^ SG(G2) ^ … ^ SG(Gm)

Theorem A position in a directed graph game is winning if and only if the SG function value of the corresponding node is greater than 0. A position in a directed graph game is losing if and only if the SG function value of the corresponding node equals 0.

Time Complexity and Algorithm Choice for Different Data Ranges

In general, the time limit for ACM or written exam problems is 1 second or 2 seconds. In such cases, the best practice is to control the operation count in C++ code within $10^7$∼$10^8$. Below, time complexity and algorithm choices are provided for different data ranges:

$n≤30$, exponential, dfs+pruning, state compression dp

$n≤100=>O(n^3)$, floyd, dp, Gaussian elimination

$n≤1000=>O(n^2)$, $O(n^{2}logn)$, dp, binary search, naive Dijkstra, naive Prim, Bellman-Ford

$n≤10000=>O(n·\sqrt{n})$, block linked list, blocking, Mo’s algorithm

$n≤100000=>O(nlogn)$, various sorts, segment tree, binary indexed tree, set/map, heap, topological sorting, dijkstra+heap, prim+heap, Kruskal, spfa, convex hull, half-plane intersection, binary search, CDQ divide and conquer, integrated binary search, suffix array, heavy-light decomposition, dynamic trees

$n≤1000000=>O(n)$, and algorithms with small constants O(nlogn), monotonic queue, hash, two pointers, disjoint set, KMP, AC automaton, small constant O(nlogn) methods: sort, binary indexed tree, heap, dijkstra, spfa

$n≤10000000=>O(n)$, two pointers, KMP, AC automaton, linear sieve for primes

$n≤10^9=>O(\sqrt n)$, prime number judgement

$n≤10^{18}=>O(logn)$, greatest common divisor, fast power, digit DP

$n≤10^{1000}=>O((logn)^2)$, high-precision addition, subtraction, multiplication, division

$n≤10^{100000}=>O(logk×loglogk)$, k represents the number of digits, FFT/NTT

Qingfeng’s Algorithm Homepage

Buy me a coffee~
Tim AlipayAlipay
Tim PayPalPayPal
Tim WeChat PayWeChat Pay
0%