Algorithm template By Roshin

Posted by Mightywayne on Sun, 06 Mar 2022 15:05:20 +0100

memset also blocks time
\((x\mod a) \mod ba = (x \mod ba )\mod a\)
Find out how many numbers in \ (1 ~ n \) are multiples of \ (i \), that is \ (n / i \)
Array non global variable to initialize
Push more
Learn to quote complex variables appropriately

STL

vector, Variable length array, multiplication idea
    size()  Returns the number of elements
    empty()  Returns whether it is empty
    clear()  empty
    front()/back()
    push_back()/pop_back()
    begin()/end()
    []
    Support comparison operation, in dictionary order

pair<int, int>
    first, First element
    second, Second element
    Support comparison operation to first Is the first keyword, with second Is the second keyword (dictionary order)

string,character string
    size()/length()  Return string length
    empty()
    clear()
    substr(Starting subscript,(Substring length))  Return substring
    c_str()  Returns the starting address of the character array where the string is located

queue, queue
    size()
    empty()
    push()  Insert an element at the end of the queue
    front()  Return queue header element
    back()  Return tail element
    pop()  Pop up queue header element

priority_queue, Priority queue. The default is large root heap
    size()
    empty()
    push()  Insert an element
    top()  Returns the top of heap element
    pop()  Pop top element
    How to define as a small root heap: priority_queue<int, vector<int>, greater<int>> q;

stack, Stack
    size()
    empty()
    push()  Insert an element into the top of the stack
    top()  Return stack top element
    pop()  Pop up stack 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), the ordered sequence is maintained dynamically
    size()
    empty()
    clear()
    begin()/end()
    ++, -- Return precursor and successor, time complexity O(logn)

set/multiset
    insert()  Insert a number
    find()  Find a number
    count()  Returns the number of a number
    erase()
        (1) The input is a number x,Delete all x   O(k + logn)
        (2) Enter an iterator and delete it
    lower_bound()/upper_bound()
        lower_bound(x)  Return greater than or equal to x Iterator of the smallest number of
        upper_bound(x)  Return greater than x Iterator of the smallest number of
map/multimap
    insert()  The number of inserts is one pair
    erase()  The input parameter is pair Or iterator
    find()
    []  be careful multimap This operation is not supported. The time complexity is O(logn)
    lower_bound()/upper_bound()

unordered_set, unordered_map, unordered_multiset, unordered_multimap, Hashtable 
    Similar to the above, the time complexity of adding, deleting, modifying and querying is O(1)
    I won't support it lower_bound()/upper_bound(), Iterator++,--

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

    count()  Returns how many 1s there are

    any()  Determine whether there is at least one 1
    none()  Judge whether all are 0

    set()  Set all positions to 1
    set(k, v)  Will be the first k Bit becomes v
    reset()  Change all bits to 0
    flip()  Equivalent to~
    flip(k) Put the first k Bit inversion

lower_bound

Search for the first number greater than or equal to ≥ x in the array and return the iterator

No return end

#include<algorithm>
vector<int> a;
int pos = lower_bound(a.begin(), a.end(), x) - a.begin();	// pos is the target subscript
int pos = lower_bound(a.begin(), a.end(), x, greater<int>() ) - a.begin()  // Find the first number less than or equal to x

upper_bound

And lower_ The usage of bound is similar. Search the first number in the array greater than > x and return the iterator

No return end

#include<algorithm>
vector<int> a;
// pos is the target subscript
int pos = upper_bound(a.begin(), a.end(), x) - a.begin();	
// Find the first number less than x
int pos = upper_bound(a.begin(), a.end(), x, greater<int>() ) - a.begin();	

Basic algorithm

Divide and conquer

Divide and conquer method divides a problem into several similar sub problems with smaller scale, recursively solves these sub problems, and then deduces the solution of the original problem through them.

Divide and conquer to find the sum of equal ratio sequence

The fast power is used to find the sum of \ (1+p+\cdots+p^k \)

// Divide and conquer to find the equal ratio sequence and complexity (log k)
ll sum(int p, int k){
    if(!k) return 1;
    if(k & 1)
        return (1 + qmi(p, (k + 1) / 2)) % mod * sum(p, (k - 1) / 2) % mod;
    return ((1 + qmi(p, k / 2)) * sum(p, k / 2 - 1) + qmi(p, k)) % mod;
}

Basic sorting algorithm

Merge sort

Recursion is divided into equal sub segments. After sorting within the sub segment, it is merged and reordered during backtracking

const int N = 1e6 + 10;
int q[N],tmp[N];
void mergesort(int a[],int l,int r){
    if(l >= r) return;
    int mid = (l + r) / 2;
    mergesort(q,l,mid);
    mergesort(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++]; // Find the reverse order pair, followed by an ans += mid - i + 1;
    }
    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];  
}

int main(){
    int n;
    scanf("%d",&n);
    for(int i = 0;i < n;i++){
        scanf("%d",&q[i]);
    } 
    mergesort(q,0,n-1);
    for(int i = 0;i < n;i++){
        printf("%d ",q[i]);
    } 
    return 0;
}

Dichotomy

Integer dichotomy

bool check(int x) {/* ... */} // Check whether x meets certain properties

// 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 whether the mid satisfies the property
        else l = mid + 1;
    }
    return l;
}
// 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;
}

Floating point binary

bool check(double x) {/* ... */} // Check whether x meets certain properties

double bsearch_3(double l, double r)
{
    const double eps = 1e-6;   // eps indicates accuracy, which depends on the accuracy requirements of the subject
    while (r - l > eps)
    {
        double mid = (l + r) / 2;
        if (check(mid)) r = mid;
        else l = mid;
    }
    return l;
}

Prefix sum, difference

Core idea

Change the query to \ and (o)

The difference changes the interval to \ (O(1) \), \ (O(n) \) to get the original array

One dimensional prefix sum

for(int i = 1; i <= n; i++)
    s[i] = s[i - 1] + a[i];

2D prefix and

for(int i = 1; i <= n; i++)
    for(int j = 1; j <= n; j++)
        s[i] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + a[i][j];

One dimensional difference

b[i] = a[i] - a[i - 1]
// For [l, r] interval + c: b[l] += c, b[r + 1] -= c;
// For [l, r] interval - c: b[l] -= c, b[r + 1] += c;

Two dimensional difference

// b[x1][y1] += c is to increase the value of the submatrix from the vertex to the lower right corner by c
void insert(int x1, int y1, int x2, int y2, int c){     
    b[x1][y2 + 1] -= c;
    b[x2 + 1][y1] -= c;
    b[x2 + 1][y2 + 1] += c;
    b[x1][y1] += c;
}

Double pointer

Core idea
Eliminate repetitive actions and optimize to achieve \ (O(n) \) complexity

Interval discretization

unique() + erase() function

vector<int> v;
sort(v.begin(), v.end());       // unique only processes adjacent elements and requires sorting
v.erase(unique(v.begin(), b.end()), v.end());   // unique returns the beginning of the last repeating element

Data structure and string algorithm

STL Foundation

queue

sliding window

Sliding window is essentially a monotonic two terminal queue

#include<deque>
using namespace std;
const int N = 1e6 + 10;
deque<int> q;
int a[N];
int n, k;

// Sliding windows use double ended queues
int main(){
    cin >> n >> k;      // k is the sliding window size
    for(int i = 1; i <= n; i++){
        cin >> a[i];
    }
    for(int i = 1; i <= n; i++){        // Gets the minimum value of the sliding window
        int x = a[i];
        while(!q.empty() && i - q.front() > k - 1)
            q.pop_front();
        while(!q.empty() && a[q.back()] >= a[i]){       // Pop when the tail of the team is greater than or equal to x_ back
            q.pop_back();
        }
        q.push_back(i);
        if(i >= k)
            printf("%d ", a[q.front()]);
    }
    printf("\n");
    q.clear();
    for(int i = 1; i <= n; i++){        // Gets the maximum value of the sliding window
        int x = a[i];
        while(!q.empty() && i - q.front() > k - 1)
            q.pop_front();
        while(!q.empty() && a[q.back()] <= a[i]){       // If the tail of the team is less than x, it will pop_back
            q.pop_back();
        }
        q.push_back(i);
        if(i >= k)
            printf("%d ", a[q.front()]);
    }
    
    return 0;
}

Array simulation queue

int q[N];

int main(){
    int T;
    cin >> T;
    int hh = 0, tt = -1;
    while(T--){
        string op;
        cin >> op;
        if(op == "push"){       // Add elements to the end of the team
            int x;
            cin >> x;
            q[++tt] = x;
        }
        else if(op == "pop")    // Pop up the head of the team
            hh++;
        else if(op == "empty"){ // Whether the query is empty
            if(hh <= tt)
                puts("NO");
            else
                puts("YES");
        }
        else        // Query team leader
            printf("%d\n", q[hh]);
    }   
    return 0;
}

Stack

Array simulation stack

int s[N];

int main(){
    int T;
    cin >> T;
    int tt = 0;     // tt indicates the top of the stack
    while(T--){
        string op;
        cin >> op;
        if(op == "push"){       // Push 
            int x;
            cin >> x;
            s[++tt] = x;
        }
        if(op == "pop")     // Pop stack top
            tt--;
        if(op == "empty"){      // Query whether the stack is empty
            if(tt > 0)  
                puts("NO");
            else
                puts("YES");
        }
        if(op == "query")       // Query stack top
            printf("%d\n", s[tt]);
    }
    
    return 0;
}

Monotone stack

int s[N];

int main(){
    int n;
    cin >> n;
    int tt = 0;
    for(int i = 0; i < n; i++){
        int x;
        cin >> x;
        while(tt > 0 && s[tt] >= x)     // The stack is not empty, and the top of the stack is greater than or equal to x
            tt--;
        if(!tt)
            printf("-1 ");
        else
            printf("%d ", s[tt]);
        s[++tt] = x;        // Stack x
    }
    
    return 0;
}

KMP

using namespace std;
const int M = 1e6 + 10,N = 1e5 + 10;

char p[N],s[M];
int n,m;
int ne[M];

int main(){
    cin >> m >> p+1 >> n >> s+1;
    // Preprocessing next array
    ne[1] = 0; // The first element has no suffix
    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 process
    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){
            cout << i - m << " ";
            j = ne[j]; // Optimization speed
        }
    }
    return 0;
}

Trie tree

The size of the open \ (son \) array depends on the subject, which is generally \ (number of elements \ times stores the maximum number of nodes required for each element \)

int son[N][26], idx;        // idx stores the node number. Node 0 is both a root node and an empty node
int cnt[N];         

void insert(char str[]){    // insert
    int p = 0;
    for(int i = 0; str[i]; i++){
        int u = str[i] - 'a';
        if(!son[p][u]) son[p][u] = ++ idx;      // If the node does not exist, create a new one
        p = son[p][u];
    }
    cnt[p] ++;  // Mark the end node
}

int query(char str[]){      // query
    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];
}

Persistent Trie

  • There's not enough space. Go backwards with the maximum space and drive as large as you can
    Example: maximum XOR sum
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 6e5 + 10, M = N * 24;     // It used to be 3e5, 3e5 times. The length of the query sequence is up to 6e5, and each number is up to 24 bits. All the historical versions of Trie need up to 6e5 * 24 nodes
int n, m;
int tr[M][2], root[N], max_id[M], s[N], idx;    

// The idea of persistent trie: different from the previous version of trie, the points on the path should be re established
// Topic idea: convert the XOR prefix sum, s[p - 1] ^ s[n] ^ x is the largest, and find a p in [l - 1, r - 1] to make s[p - 1] ^ (s[n] ^ x) the largest

// Because you need to know the subscript of the child node to determine the subscript of the parent node, write it recursively
void insert(int i, int k, int p, int q){        // In the subscript in the prefix and, the number of bits (from left to right) are inserted. The old version corresponds to the node and the new version corresponds to the node
    if(k <= -1){
        max_id[q] = i;
        return ;
    }
    int v = (s[i] >> k) & 1;        // Take out this bit
    tr[q][v] = ++ idx;      // The path different from the original should be opened up
    if(p)
        tr[q][v ^ 1] = tr[p][v ^ 1];                            // Copy directly with the same path as the original
    insert(i, k - 1, tr[p][v], tr[q][v]);                       // Recursive insertion 
    max_id[q] = max(max_id[tr[q][0]], max_id[tr[q][1]]);        // Maximum subscript of backtracking update subtree
}

int query(int root, int C, int l){              // Root node number, value to be XOR, subscript limit
    int p = root;
    for(int i = 23; i >= 0; i--){
        int v = (C >> i) & 1;
        if(max_id[tr[p][v ^ 1]] >= l)           // Find the opposite node and meet the requirements of 01
            p = tr[p][v ^ 1];
        else
            p = tr[p][v];
    }
   return C ^ s[max_id[p]];
}

int main(){
    cin >> n >> m;
    max_id[0] = -1;     // Null node subscript infinitesimal
    root[0] = ++ idx;   // Must open up new nodes for the root 
    insert(0, 23, 0, root[0]);      // Insert, s[0] is also a prefix and
    int x;
    for(int i = 1; i <= n; i++){
        cin >> x;
        s[i] = s[i - 1] ^ x;
        root[i] = ++ idx;       // Open up new nodes for new roots
        insert(i, 23, root[i - 1], root[i]);
    }
    while(m--){
        string op;
        cin >> op;
        if(op == "A"){
            ++n;
            cin >> x;
            root[n] = ++ idx;       // Open up new nodes for new roots
            s[n] = s[n - 1] ^ x;
            insert(n, 23, root[n - 1], root[n]);
        }
        else{
            int l, r;
            cin >> l >> r >> x;
            cout << query(root[r - 1], s[n] ^ x, l - 1) << endl;    // Interval [l, r], find p after conversion belongs to [l - 1, r - 1], find it in root[r - 1], subscript limit > = L - 1;
        }
    }
    return 0;
}

Merge search set

Parallel query set initialization

for(int i = 1; i <= n; i++){
    p[i] = i;
    Size[i] = 1;    // Maintain connected block size with i as root
}

Query + path compression

int find(int x){
    if(x != p[x]) return p[x] = find(p[x]);
    return p[x];
}

Query + maintain distance from point to root node

int find(int x){
    if(x != p[x]){
        int root = find(p[x]);
        d[x] += d[p[x]];
        p[x] = root;
    }
    return p[x];
}

Merge and query set

p[find(a)] = find(b);
sz[b] += sz[a];     // Consolidated query set size of maintenance

Query whether two points are in the same set

if(find(a) == find(b))

Hashtable

storage structure

open addressing

\(N \) take \ (2 ~ 3 \) times of the input scale, and \ (null \) is the initialization value

const int N = 2e5 + 5, null = 0x3f3f3f3f; 
int h[N];

void insert(int x){ // insert
    int k = (x % N + N) % N;
    while(h[k] != null){
        k++;
        if(k == N)
            k = 0;
    }
    h[k] = x;
}

int find(int x){    // lookup
    int k = (x % N + N) % N;
    while(h[k] != null){
        if(h[k] == x)
            break;
        if(k == N)
            k = 0;
        k++;
    }
    return h[k];
}

Zipper method

Similar adjacency table

const int N = 1e5 + 3;  // The first prime number larger than the size of the input data

int e[N], ne[N], h[N], idx;

void insert(int x){     // insert
    int k = (x % N + N) % N;
    e[idx] = x, ne[idx] = h[k], h[k] = idx++;
}

bool find(int x){      // lookup
    int k = (x % N + N) % N;
    for(int i = h[k]; i != -1; i = ne[i]){
        if(e[i] == x)
            return true;
    }
    return false;
}

String hash

Hash map the string prefix from left to right, high to low, and map it to \ (P \) hexadecimal number.

  • Usually \ (P = 131 \) or \ (P = 13331 \)
  • The array is written as unsigned long to achieve the purpose of automatic module extraction
  • Get the hash value of \ ([l,r] \) string, \ (value=h_r-h_{l-1}\times p^{r-l+1} \)
typedef unsigned long long ull;     // The maximum value of unsigned long is 2 ^ 64 - 1, which can realize automatic mold taking
using namespace std;
const int N = 1e5 + 10, P = 131;    // P = 13331
ull p[N], h[N];
char str[N];
int n, m;

void init(){    // Preprocessing p-ary
    p[0] = 1;
    for(int i = 1; i <= n; i++){
        p[i] = p[i - 1] * P;
        h[i] = h[i - 1] * P + str[i];       // str[i] is not 0
    }
}

ull get(int l, int r){
    return h[r] - h[l - 1] * p[r - l + 1];      // Prefix and + base alignment
}

ST table (RMQ algorithm)

Difference from segment tree: static query of interval maximum value, and modification is not supported
ST table initialization

void init(){
    for(int i = 0; i < M; i++)  // Similar to interval DP, enumerate the interval length first and then the starting point
        for(int j = 1; j + (1 << i) - 1 <= n; j++){
            if(!i)
                st[j][0] = a[j];
            else
                st[j][i] = max(st[j][i - 1], st[j + (1 << (i - 1))][i - 1]);
        }
}

ST table query

int query(int l, int r){
    int len = r - l + 1;
    int k = log(len) / log(2);      // Less than the power of the maximum 2 of the interval length
    return max(st[l][k], st[r - (1 << k) + 1][k]);      // The maximum value of the maximum value of the previous two segments is the maximum value of the interval
}

Tree group

Essential function and expansion

  • Interval query, query prefix and complexity \ (O(logn) \) (can be combined with difference)
  • Single point modification: modify the complexity of array elements \ (O(logn) \).
  • The number of occurrences of values can be used as the maintenance object to solve the problem of reverse order pairs
  • On the basis of difference, it can be changed from single point query to interval and query. The method adopts the idea of complement set:
    • Each element in the interval is the prefix sum of the differential array, and the interval sum is the sum of the elements of the differential array of two-layer loops
    • Use the complement set to supplement a (x + 1, x) matrix. The originally required interval sum = matrix sum ((x + 1) * differential array prefix and [x]) - i * a[i] is the prefix and [x] of the element array;
  • Handle the small problem of \ (k \) in the array (the array is an array):
    • The tree group maintains an array with an element value of 1, representing that this number occurs once
    • Delete this number, that is, add(x, -1), find the smallest K, and use the dichotomy method. Since the array has only 0 and 1, and the prefix sum is monotonous, find the smallest x, ask(x) = k, which is the smallest number \ (K \)

Three basic functions

int lowbit(int x){
    return x & -x;
}

void add(int x, int c){     // Single point modification
    for(int i = x; i <= n; i += lowbit(i))
        tr[i] += c;
}

int ask(int x){     // Query interval [1, x]
    int res = 0;
    for(int i = x; i; i -= lowbit(i))
        res += tr[i];
    return res;
}

Segment tree

Five functions: push up, push down, build query, modify
Ordinary segment tree: supports interval query and single point modification
Segment tree marked with \ (lazy \): supports interval modification and requires pushdown operation

Key points and expansion

  • The segment tree maintenance attributes are obtained according to the meaning of the topic. If the existing attributes are insufficient, add new attributes until they can be updated smoothly
  • Maintenance interval maximum common divisor:
    • Convert interval addition to single point modification (maintain differential array)
    • Maximum common divisor of interval \ (GCD (a_1, a_2,..., a_n) < = > GCD (a_1, a_2 - a_1, a_3 - a_2,..., a_n - a_ {n-1}) \)
    • Where \ (a_1 \) is calculated by the sum of the differential prefix (the interval sum needs to be maintained)
  • The lazy labeling problem of interval addition and interval multiplication is maintained at the same time. Multiply first and then add

Segment tree classic template

Template I Maximum value of dynamic search interval (no pushdown)

#define ls u << 1
#define rs u << 1 | 1
using namespace std;
const int N = 2e5 + 10;
int n, m, p;

struct T{
    int l, r, v;
}tr[N << 2];

void pushup(int u){     // Push up: the child node updates the parent node information
    tr[u].v = max(tr[ls].v, tr[rs].v);
}

void build(int u, int l, int r){        // Establish a segment tree, (node number, left end point of node interval, right end point of node interval)
    tr[u] = (T){l, r}; // Assign node interval first
    if(l == r)      // return when you reach the leaf node
        return;
    int mid = (l + r) >> 1;
    build(ls, l, mid), build(rs, mid + 1, r);       // Continue recursion on two intervals (l, mid) (mid + 1, r)
    pushup(u);
}

int query(int u, int l, int r){             // Query operation, (node number, left end point of query interval, right end point of query interval)
    if(tr[u].l >= l && tr[u].r <= r)        // The query interval completely includes the node interval and returns the node value
        return tr[u].v;
    int mid = (tr[u].l + tr[u].r) >> 1;
    int res = 0;
    if(l <= mid) res = query(ls, l, r);     // The query interval has an intersection on the left of the node interval
    if(r > mid) res = max(res, query(rs, l, r));    // At the right intersection of the node interval, the result takes the maximum value (node value attribute definition)
    return res;
}

void modify(int u, int pos, int v){         // Single point modification, (node number, query point subscript, change value)
    if(tr[u].l == pos && tr[u].r == pos)    // When the query point is reached, the node value is returned
       tr[u].v = v;
    else{
        int mid = (tr[u].l + tr[u].r) >> 1;
        if(pos <= mid) modify(ls, pos, v);      // The query point is on the left of the node interval, and query to the left subtree
        else modify(rs, pos, v);                // Otherwise, query to the right subtree
        pushup(u);          // When the child node changes, push up updates the information to the parent node
    }
}

int main(){
    cin >> m >> p;
    build(1, 1, m);     // Length up to m
    int last = 0;
    while(m --){
        string op;
        long long d;
        cin >> op >> d;
        if(op == "Q"){
            last = query(1, n - d + 1, n);
            cout << last << endl;
        }
        else{
            d = (d + last) % p;
            modify(1, n++ + 1, d);
        }
    }
    return 0;
}

Template II Interval modification problem (with pushdown)

#include<iostream>
#include<cstring>
#include<algorithm>
#define ls u << 1
#define rs u << 1 | 1
typedef long long ll;
using namespace std;
const int N = 1e5 + 10;
int n, m;
ll a[N];

struct T{
    int l, r;
    ll sum, add;    // add is the lazy flag, and the subinterval needs to be modified
}tr[N << 2];

void pushup(int u){
    tr[u].sum = tr[ls].sum + tr[rs].sum;
}

void pushdown(int u){       // lazy tag drop
    tr[ls].sum += (tr[ls].r - tr[ls].l + 1) * tr[u].add, tr[ls].add += tr[u].add;       // The parent node updates the child interval, and the child interval lazy mark should be added with the parent node lazy mark
    tr[rs].sum += (tr[rs].r - tr[rs].l + 1) * tr[u].add, tr[rs].add += tr[u].add;
    tr[u].add = 0;          // The parent node's lazy flag is clear to ensure that the parent node's lazy flag is clear during query and modification
}

void build(int u, int l, int r){
    if(l == r)
        tr[u] = (T){l, l, a[l], 0};
    else{
        tr[u].l = l, tr[u].r = r;
        int mid = (tr[u].l + tr[u].r) >> 1;
        build(ls, l, mid), build(rs, mid + 1, r);
        pushup(u);
    }
}

void modify(int u, int l, int r, ll v){
    if(tr[u].l >= l && tr[u].r <= r){       // Note the recursive exit of interval modification
        tr[u].sum += (tr[u].r - tr[u].l + 1) * v;
        tr[u].add += v;     // lazy mark plus v
    }
    else{
        pushdown(u);        // pushdown before recursive splitting  
        int mid = (tr[u].l + tr[u].r) >> 1;
        if(l <= mid) modify(ls, l, r, v);
        if(r > mid) modify(rs, l, r, v);
        pushup(u);
    }
}

ll query(int u, int l, int r){
    if(tr[u].l >= l && tr[u].r <= r)        
        return tr[u].sum;
    else{
        pushdown(u);        // pushdown before recursive splitting
        int mid = (tr[u].l + tr[u].r) >> 1;
        ll res = 0;
        if(l <= mid) res = query(ls, l, r);
        if(r > mid) res += query(rs, l, r);
        return res;
    }
}

int main(){
    cin >> n >> m;
    for(int i = 1; i <= n; i++)
        cin >> a[i];
    build(1, 1, n);
    while(m--){
        string op;
        ll l, r, d;
        cin >> op >> l >> r;
        if(op == "Q")
            cout << query(1, l, r) << endl;
        else{
            cin >> d;
            modify(1, l, r, d);
        }
    }
    return 0;
}

Formwork III Atlantis (scanline algorithm)

#include<iostream>
#include<cstring>
#include<algorithm>
#include<iomanip>
#include<vector>
#define ls u << 1
#define rs u << 1 | 1
#define pb push_back
typedef long long ll;
using namespace std;
const int N = 1e4 + 10;
int n;
vector<double> ys;

// Review the practice of scanning line more, and the expansibility is not strong

struct Seg{
    double x, y1, y2;
    int k;
    bool operator < (const Seg& s) const{
        return x < s.x;
    }
}seg[N << 1];

struct T{
    int l, r, cnt;
    double len;
}tr[N << 3];        // The space of segment tree is 4 times of the sequence maintained by segment tree

int find(double y){
    return lower_bound(ys.begin(), ys.end(), y) - ys.begin();
}

void pushup(int u){
    if(tr[u].cnt) tr[u].len = ys[tr[u].r + 1] - ys[tr[u].l];    // Nodes are covered, and the elements in ys represent intervals, which should express tr [u] R the desired element must be + 1
    else if(tr[u].l != tr[u].r)     // The node is not covered, but it is not a leaf node. len can be obtained from the child node
        tr[u].len = tr[ls].len + tr[rs].len;
    else
        tr[u].len = 0;      // The node is not overwritten. It may be updated. cnt = 0. You need to set the len of the node to 0
}

void build(int u, int l, int r){
    if(l == r)
        tr[u] = (T){l, r, 0, 0};
    else{
        tr[u] = (T){l, r, 0 ,0};
        int mid = (tr[u].l + tr[u].r) >> 1;
        build(ls, l, mid), build(rs, mid + 1, r);
    }
}

void modify(int u, int l, int r, int k){        // The scan line does not need pushdown
    if(tr[u].l >= l && tr[u].r <= r){
        tr[u].cnt += k;
        pushup(u);      // This node has been updated. The parent node needs to be updated by pushing up
    }
    else{
        int mid = (tr[u].l + tr[u].r) >> 1;
        if(l <= mid) modify(ls, l, r, k);
        if(r > mid) modify(rs, l, r, k);
        pushup(u);
    }
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr), cout.tie(nullptr);
    int T = 0;
    while(cin >> n, n){
        ys.clear();
        for(int i = 0, j = 0; i < n; i++){
            double x1, y1, x2, y2;
            cin >> x1 >> y1 >> x2 >> y2;
            seg[j++] = {x1, y1, y2, 1};
            seg[j++] = {x2, y1, y2, -1};
            ys.pb(y1), ys.pb(y2);       
        }
        sort(ys.begin(), ys.end());
        ys.erase(unique(ys.begin(), ys.end()), ys.end());
        sort(seg, seg + 2 * n);
        build(1, 0, ys.size() - 2);     // The subscript is length - 1, and the record element indicates interval - 1
        double res = 0<F8>;
        for(int i = 0; i < 2 * n; i++){
            if(i > 0)
                res += tr[1].len * (seg[i].x - seg[i - 1].x);
            modify(1, find(seg[i].y1), find(seg[i].y2) - 1, seg[i].k);      // A single element represents the node with the subscript value as the left end point, and the right end point of the interval maintained by the segment tree should be - 1
        }
        cout << "Test case #" << ++T << endl;
        cout << "Total explored area: " << fixed << setprecision(2) << res << endl << endl;         
    }
    return 0;
}

Chairman tree (persistent segment tree)

Find the smallest \ (k \) of the interval

#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
const int N = 1e5 + 10;
vector<int> v;
int n, m, a[N], root[N], idx;

// The chairman tree maintains the value domain space

struct T{
    int l, r, cnt;      // l. r points to the left subtree and the right subtree. cnt records the number of values in the range maintained
}tr[N * 4 + N * 17]; // O(N * 4 + N * logM);

int find(int x){
    return lower_bound(v.begin(), v.end(), x) - v.begin();
}

int build(int l, int r){        // On the range interval [l, r]
    int p = idx++;      
    if(l == r)      // Is a leaf node
        return p;
    int mid = (l + r) >> 1;
    tr[p].l = build(l, mid), tr[p].r = build(mid + 1, r);   // Left subtree and right subtree build trees on [l, mid] and [mid + 1, r]
    return p;
}

int insert(int p, int l, int r, int x){
    int q = idx++;      // Open up new nodes
    tr[q] = tr[p];      // Replication node
    if(l == r){
        tr[q].cnt++;        // At the point of insertion, cnt++
        return q;
    }
    int mid = (l + r) >> 1;
    if(x <= mid) tr[q].l = insert(tr[p].l, l, mid, x);      // The point to be inserted is in the left half of the value range, and the left subtree of the new node should be different from the original
    else tr[q].r = insert(tr[p].r, mid + 1, r, x);          // Otherwise, the right subtree is different
    tr[q].cnt = tr[tr[q].l].cnt + tr[tr[q].r].cnt;          // Update cnt from subtree, similar to pushup
    return q;
}

int query(int p, int q, int l, int r, int k){               // Contains a binary search (the interval characteristics maintained by the left and right subtrees of the line segment tree node have been determined)
    if(l == r)          // Since the query interval must contain answers, if the end points of the interval are the same, it means that the target interval has been searched
        return l;
    int mid = (l + r) >> 1;
    int cnt = tr[tr[q].l].cnt - tr[tr[p].l].cnt;            // Because the tree structures of different versions are the same, the intervals maintained by nodes are actually the same, but cnt is different. cnt has the characteristic of monotonic increase. The subtraction before and after is actually the number of times between [L, R]
    if(cnt >= k) return query(tr[p].l, tr[q].l, l, mid, k); // If the number of left ends is greater than cnt, look to the left
    else return query(tr[p].r, tr[q].r, mid + 1, r, k - cnt);   // Otherwise, search to the right and k -= cnt, because the number of CNTs is smaller than all the numbers on the right
}

int main(){
    cin >> n >> m;
    for(int i = 1; i <= n; i++){
        cin >> a[i];
        v.push_back(a[i]);
    }
    sort(v.begin(), v.end());           // Discretization operation
    v.erase(unique(v.begin(), v.end()), v.end());
    root[0] = build(0, v.size() - 1);   // Root 0 builds a tree directly to form a structure, but does not update the cnt
    for(int i = 1; i <= n; i++)
        root[i] = insert(root[i - 1], 0, v.size() - 1, find(a[i]));     // Insert new values in turn and update cnt
    while(m--){
        int l, r, k;
        cin >> l >> r >> k;
        cout << v[query(root[l - 1], root[r], 0, v.size() - 1, k)] << endl;     // Prefix and idea s[r] - s[l - 1] is what we want in the [l, r] interval. What we query is the subscript, and then convert the answer from the discretized array
    }

    return 0;
}

Balanced tree

Treap

principle

  1. With BST (binary search tree) as the leading, the right subtree node key > parent node key > left subtree node key
  2. By adding val, BST has the property constraint of large root heap, which is relatively balanced and evolved into a tree equilibrium tree

Tree template

Template I

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 1e5 + 10, INF = 1e8;
int idx, root;

struct Node{
    int l, r;
    int key, val;
    int cnt, size;
}tr[N];

void pushup(int p){         // In the same way as the segment tree pushup, the child node updates the parent node
    tr[p].size = tr[tr[p].l].size + tr[tr[p].r].size + tr[p].cnt;
}

int get_node(int key){      // Create node and pass in key
    tr[++idx].key = key;
    tr[idx].val = rand();   // Random val
    tr[idx].cnt = tr[idx].size = 1;
    return idx;             // Return node number
}

void zig(int& p){   // Dextral
    int q = tr[p].l;        // Take the left son by right rotation
    tr[p].l = tr[q].r, tr[q].r = p, p = q;  // The parent node runs to the right of the left son, the right of the left son becomes the parent node, and the pointer is adjusted to the left son
    pushup(tr[p].r), pushup(p);             // pushup changed two points
}

void zag(int& p){   // Sinistral
    int q = tr[p].r;
    tr[p].r = tr[q].l, tr[q].l = p, p = q;  // The parent node runs to the left of the right son, the left son of the right son becomes the parent node, and the pointer is adjusted to the right son
    pushup(tr[p].l), pushup(p);             // pushup changed two points
}

void build(){       // Build a tree
    get_node(-INF), get_node(INF);          // The establishment of sentinel nodes is conducive to dealing with the boundary
    root = 1, tr[1].r = 2;
    pushup(root);
    if(tr[1].val < tr[2].val) zag(root);    
}

void insert(int &p, int key){   // Insert node
    if(!p) p = get_node(key);                   // This node does not exist. Create it directly
    else if(tr[p].key == key) tr[p].cnt++;      // cnt if the node already exists++
    else if(tr[p].key > key){                   // To the left
        insert(tr[p].l, key);
        if(tr[tr[p].l].val > tr[p].val) zig(p); // The left son value is larger than the current node. After insertion, the left son needs to be adjusted to the parent node for right rotation
    }    
    else{                                       // Insert to the right
        insert(tr[p].r, key);
        if(tr[tr[p].r].val > tr[p].val) zag(p); // The value of the right son is larger than the current node. After insertion, the right son needs to be adjusted to the parent node for left rotation
    }
    pushup(p);  ///Update the following information to the node indicated by the p pointer. Since it is backtracking, it has the function of bottom-up
}

void rm(int& p, int key){       // Delete node
    if(!p) return;
    if(tr[p].key == key){       // Node exists
        if(tr[p].cnt > 1) tr[p].cnt--;      // cnt > 1, cnt --
        else if(tr[p].l || tr[p].r){        // Not a leaf node
            if(!tr[p].r || tr[tr[p].l].val > tr[tr[p].r].val){      // If the right subtree does not exist or the value on the left is greater than that on the right, the left son acts as the parent node for right rotation
                zig(p);
                rm(tr[p].r, key);           // After right rotation, the original parent node is on the right side of the p pointer
            }
            else{                                                   // Otherwise, the right son acts as the parent node and rotates left
                zag(p);
                rm(tr[p].l, key);           // After left rotation, the original parent node is on the left of the p pointer
            }
        }
        else p = 0;     // Node delete leaves directly
    }
    else if(tr[p].key > key) rm(tr[p].l, key);
    else rm(tr[p].r, key);
    pushup(p);      // Update the following information to the node indicated by the p pointer. Since it is backtracking, it has the function of bottom-up
}

int get_rank_by_key(int p, int key){    // Find node ranking through key (the smallest)
    if(!p) return 0;
    if(tr[p].key == key)
        return tr[tr[p].l].size + 1;        // Because it is the smallest ranking, it is the left subtree size + 1
    else if(tr[p].key < key)        // Find the ranking and look to the right
        return tr[tr[p].l].size + tr[p].cnt + get_rank_by_key(tr[p].r, key);    // The returned result should be added with the left subtree and node size
    else                            // Look to the left
        return get_rank_by_key(tr[p].l, key);

}

int get_key_by_rank(int p, int rank){   // Find the key of the node through the node ranking
    if(!p) return 0;
    if(tr[tr[p].l].size >= rank)        // The size of the left is larger than the rank. Go to the left
        return get_key_by_rank(tr[p].l, rank);
    else if(tr[tr[p].l].size + tr[p].cnt >= rank)       // Plus you're bigger than rank, right at p
        return tr[p].key;
    else                                // Otherwise, on the right, the number smaller than that on the left should be subtracted from the ranking
        return get_key_by_rank(tr[p].r, rank - tr[tr[p].l].size - tr[p].cnt);
}

int get_prev(int p, int key){       // Find the maximum number less than key
    if(!p) return -INF;
    if(tr[p].key >= key) return get_prev(tr[p].l, key);     // Current key > = target key, look to the left
    else return max(tr[p].key, get_prev(tr[p].r, key));     // Current key < target key, the answer is in the current key or the subtree on the right
}

int get_next(int p, int key){       // Find the smallest decimal greater than key
    if(!p) return INF;
    if(tr[p].key <= key) return get_next(tr[p].r, key);     // Current key < = target key, look to the right
    else return min(tr[p].key, get_next(tr[p].l, key));     // Current key > target key. The answer is in the current key or the subtree on the left
}


int main(){
    int m;
    cin >> m;
    build();        // Establish balance tree
    while(m--){
        int op, x;
        cin >> op >> x;
        if(op == 1) insert(root, x);
        else if(op == 2) rm(root, x);
        else if(op == 3) cout << get_rank_by_key(root, x) - 1 << endl;
        else if(op == 4) cout << get_key_by_rank(root, x + 1) << endl;
        else if(op == 5) cout << get_prev(root, x) << endl;
        else if(op == 6) cout << get_next(root, x) << endl;
    }
    return 0;
}

Splay

Balance principle: each operation rotates the node to the root, and play (x, k) rotates \ (x \) below \ (k \)
Core: Splay always keeps the number of middle order traversals unchanged, and the number of \ (k \) actually maintains the number of \ (k \) of middle order traversals

Example: maintenance sequence (NOI 2005)

  1. Insert sequence
  2. Delete interval
  3. Change the continuous interval to the same number
  4. Flip interval
  5. Sum of intervals
  6. Maximum subsequence of summation interval
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 5e5 + 10, INF = 1e9 + 10;

struct Node{
    int s[2], v, p;                 // p parent node v value
    int sum, ms, ls, rs, size;      // All values here mean the values after the lazy tag operation
    int same, rev;
    void init(int v_, int p_){
        v = v_, p = p_;
        s[0] = s[1] = rev = same = 0, size = 1;        // If there is reuse, the subtree should also be initialized
        sum = ms = v;
        ls = rs = max(v, 0);        // ls and rs can be 0, but according to the meaning of the question, ms cannot be 0
    }
}tr[N];

int nodes[N], root, idx, n, m, w[N], tt;        // nodes [] is similar to a stack, but it stores the node numbers that can be used

// The child node updates the parent node, and there is a push up after modification
void pushup(int u){         
    auto& t = tr[u], &l = tr[tr[u].s[0]], &r = tr[tr[u].s[1]];
    t.size = l.size + r.size + 1;
    t.sum = l.sum + r.sum + t.v;            // Different from the segment tree, the value t.v of the node is added here
    t.ms = max(max(l.ms, r.ms), l.rs + r.ls + t.v);
    t.ls = max(l.ls, l.sum + r.ls + t.v);
    t.rs = max(r.rs, r.sum + l.rs + t.v);
}

// The parent node updates the child node's lazy tag and adds it before recursion
void pushdown(int u){
    auto& t = tr[u], &l = tr[tr[u].s[0]], &r = tr[tr[u].s[1]];
    if(t.same){
        t.rev = t.same = 0;
        if(t.s[0]) l.v = t.v, l.same = 1, l.sum = t.v * l.size; // Only when the left and right subtrees exist can they be updated
        if(t.s[1]) r.v = t.v, r.same = 1, r.sum = t.v * r.size;
        if(t.v > 0){
            if(t.s[0]) l.ls = l.rs = l.ms = l.sum;
            if(t.s[1]) r.ls = r.rs = r.ms = r.sum;
        }
        else{
            if(t.s[0]) l.ls = l.rs = 0, l.ms = t.v;
            if(t.s[1]) r.ls = r.rs = 0, r.ms = t.v;
        }
    }
    else if(t.rev){
        t.rev = 0, l.rev ^= 1, r.rev ^= 1;
        swap(l.ls, l.rs), swap(r.ls, r.rs);
        swap(l.s[0], l.s[1]), swap(r.s[0], r.s[1]);
    }
}

// Rotate (merge left and right)
void rotate(int x){
    int y = tr[x].p, z = tr[y].p;
    int k = tr[y].s[1] == x;
    tr[z].s[tr[z].s[1] == y] = x, tr[x].p = z;
    tr[y].s[k] = tr[x].s[k ^ 1], tr[tr[x].s[k ^ 1]].p = y;
    tr[x].s[k ^ 1] = y, tr[y].p = x;
    pushup(y), pushup(x);
}

// Rotate node x below node K (k is 0, which means x is set as the root)
void splay(int x, int k){
    while(tr[x].p != k){
        int y = tr[x].p, z = tr[y].p;
        if(z != k){         // If z is k, x rotates only once
            if(tr[z].s[0] == y != tr[y].s[0] == x) rotate(x);
            else rotate(y);
        }
        rotate(x);
    }
    if(!k) root = x;
}

// Find the k-th number in the middle order traversal sequence
int get_k(int k){   
    int u = root;
    while(1){
        pushdown(u);
        int ls_sum = tr[tr[u].s[0]].size;   // Number of nodes in the left subtree (excluding the node itself)
        if(ls_sum >= k) u = tr[u].s[0];     // ls_ Sum > = k, indicating that the point to be found is in the left subtree (medium order ergodic property)
        else if(ls_sum + 1 == k) return u;  // ls_sum+1==k, indicating that the point to be found is u
        else k -= ls_sum + 1, u = tr[u].s[1];       // Otherwise k-=ls_sum+1, go to the right subtree to find
    }
    return -1;
}
// Insert the node and use recursive tree build ing in this problem
void insert(int v){
    int u = root, p = 0;            // u represents the node and p represents the parent node
    while(u) p = u, u = tr[u].s[v > tr[u].v];   // Search the node to be inserted according to v from top to bottom
    u = ++idx;                      
    if(p) tr[p].s[v > tr[p].v] = u; // If the parent node is not empty, bind the parent node to the node according to v
    tr[u].init(v, p);               // initialization
    splay(u, 0);                    // After the operation, rotate the u to the root node
}

// Establishing a class complete binary tree on w[l, r]
int build(int l, int r, int p){     
    int mid = (l + r) >> 1;
    int u = nodes[tt--];
    tr[u].init(w[mid], p);
    if(l < mid) tr[u].s[0] = build(l, mid - 1, u);
    if(r > mid) tr[u].s[1] = build(mid + 1, r, u);
    pushup(u);          // Push up after construction
    return u;
}
// Traversing the output subtree in middle order is useless in this problem
void output(int u){
    pushdown(u);
    if(tr[u].s[0]) output(tr[u].s[0]);
    if(tr[u].v >= 1 && tr[u].v <= n) cout << tr[u].v << " ";        // Judge whether the node is a sentry
    if(tr[u].s[1]) output(tr[u].s[1]);
}
// Traverse the subtree and put the nodes into the recycling array
void dfs(int u){        
    if(tr[u].s[0]) dfs(tr[u].s[0]);
    if(tr[u].s[1]) dfs(tr[u].s[1]);
    nodes[++tt] = u;
}

int main(){
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    cin >> n >> m;
    tr[0].ms = w[0] = w[n + 1] = -INF;          // 0 and n + 1 are two sentinels, because sentinels are required for interval operation
    for(int i = 1; i < N; i++) nodes[++tt] = i;
    for(int i = 1; i <= n; i++) cin >> w[i];
    root = build(0, n + 1, 0);                  // Assignment root node
    while(m--){
        string op;
        int posi, tot, c;
        cin >> op;
        if(op == "INSERT"){
            cin >> posi >> tot;
            for(int i = 0; i < tot; i++) cin >> w[i];
            int l = get_k(posi + 1), r = get_k(posi + 2);       // I was looking for [posi,posi+1], because there were sentinels, it was [posi+1,posi+2];
            splay(l, 0), splay(r, l);           // During operation, play (L, 0), play (R, l); The left subtree of R is the interval of [l,r]
            int u = build(0, tot - 1, r);       // Convert sequence to balanced binary tree
            tr[r].s[0] = u;     // Connected to the section
            pushup(r), pushup(l);
        }
        else if(op == "DELETE"){
            cin >> posi >> tot;
            int l = get_k(posi), r = get_k(posi + tot + 1);
            splay(l, 0), splay(r, l);
            dfs(tr[r].s[0]);        // Traverse the [l,r] subtree and put it into the recycling array
            tr[r].s[0] = 0;         // Delete subtree
            pushup(r), pushup(l);
        }
        else if(op == "MAKE-SAME"){
            cin >> posi >> tot >> c;
            int l = get_k(posi), r = get_k(posi + tot + 1);
            splay(l, 0), splay(r, l);
            auto& son = tr[tr[r].s[0]];
            son.same = 1;       // The root node of the subtree is the same=1, and then the value on the root node is updated
            son.v = c, son.sum = son.size * c;
            if(c > 0) son.ls = son.rs = son.ms = son.sum;
            else son.ls = son.rs = 0, son.ms = c;
            pushup(r), pushup(l);
        } 
        else if(op == "REVERSE"){
            cin >> posi >> tot;
            int l = get_k(posi), r = get_k(posi + tot + 1);
            splay(l, 0), splay(r, l);
            auto& son = tr[tr[r].s[0]];
            swap(son.ls, son.rs);       // Prefix and suffix, left and right subtrees should be flipped
            swap(son.s[0], son.s[1]);
            son.rev ^= 1;
            pushup(r), pushup(l);
        }
        else if(op == "GET-SUM"){
            cin >> posi >> tot;
            int l = get_k(posi), r = get_k(posi + tot + 1);
            splay(l, 0), splay(r, l);
            cout << tr[tr[r].s[0]].sum << endl;
        }
        else cout << tr[root].ms << endl;
    }
    return 0;
}

Tree cover tree

Segment tree set balance tree

Principle: the segment tree maintains the whole interval, and the balance tree maintains the ordered sequence in the node interval of the segment tree

  • When only single point modification and interval predecessor and successor are involved, multiset can be set up in the segment tree node to replace the balance tree

Example: tree covering tree

  1. The integer of the query \ [R, x] is within the range \ [l].
  2. l,r, k, query the value ranked as \ (k \) in the interval \ ([l,r] \).
  3. pos x, change the number of positions of \ (pos \) to \ (x \).
  4. l r x, query the precursor of integer \ (x \) in the interval \ ([l,r] \) (precursor is defined as the maximum number less than \ (x \).
  5. l r x, query the successor of the integer \ (x \) in the interval \ ([l,r] \) (the successor is defined as the smallest number greater than \ (x \).
#define ls u << 1
#define rs u << 1 | 1
using namespace std;
const int N = 5e4 + 10, M = 1500010, INF = 1e9;     
// Space calculation: the segment tree node is 4*n, each node is fixed with 2 sentinels, which is equal to 4*n * 2. There are a total of logn layers, and the length of each layer is n
// The balance tree node n * logn, the last space 8 * n + logn * n < = > 1200000, and 1500001 if you turn it up

struct Splay{
    int v, p, s[2];
    int size;
    void init(int v_, int p_){
        v = v_, p = p_;
        size = 1;
    }
}spl[M];

int idx, w[N], n, m;

void pushup(int u){
    spl[u].size = spl[spl[u].s[0]].size + spl[spl[u].s[1]].size + 1;
}

void rotate(int x){
    int y = spl[x].p, z = spl[y].p;
    int k = spl[y].s[1] == x;
    spl[z].s[spl[z].s[1] == y] = x, spl[x].p = z;
    spl[y].s[k] = spl[x].s[k ^ 1], spl[spl[x].s[k ^ 1]].p = y;
    spl[x].s[k ^ 1] = y, spl[y].p = x;
    pushup(y), pushup(x);
}

void splay(int x, int k, int& root){
    while(spl[x].p != k){
        int y = spl[x].p, z = spl[y].p;
        if(z != k){
            if(spl[y].s[0] == x != spl[z].s[0] == y) rotate(x);
            else rotate(y);
        }
        rotate(x);
    }
    if(!k) root = x;
}

int get_less(int v, int root){        // Find out how many numbers are smaller than the value v
    int u = root, res = 0;
    while(u){
        if(v > spl[u].v) res += spl[spl[u].s[0]].size + 1, u = spl[u].s[1];
        else u = spl[u].s[0];
    }
    return res;
}

int get_pre(int v, int& root){      // Get the prefix of v
    int u = root, res = -INF;
    while(u){
        if(v > spl[u].v) res = max(res, spl[u].v), u = spl[u].s[1];
        else u = spl[u].s[0];
    }
    return res;
}

int get_next(int v, int root){     // Get the suffix of v
    int u = root, res = INF;
    while(u){
        if(v < spl[u].v) res = min(res, spl[u].v), u = spl[u].s[0];
        else u = spl[u].s[1];
    }
    return res;
}

void insert(int v, int& root){
    int u = root, p = 0;
    while(u) p = u, u = spl[u].s[v > spl[u].v];
    u = ++idx;
    if(p) spl[p].s[v > spl[p].v] = u;
    spl[u].init(v, p);
    splay(u, 0, root);
}

struct T{
    int l, r, u;
}tr[N << 2];

void update(int x, int y, int& root){       // Find the point with the value of x in the play and update it to y
    int u = root;
    while(u){
        if(spl[u].v == x) break;
        else if(spl[u].v < x) u = spl[u].s[1];
        else u = spl[u].s[0];
    }
    splay(u, 0, root);          // Rotate node x to root
    int l = spl[u].s[0], r = spl[u].s[1];
    while(spl[l].s[1]) l = spl[l].s[1];
    while(spl[r].s[0]) r = spl[r].s[0];
    splay(l, 0, root), splay(r, l, root);       // Find the previous node and the next node of x in the sequence, and play to the root
    spl[r].s[0] = 0;        // Delete node x
    pushup(r), pushup(l);   // Push up (R) and push up (L) after deletion (modification)
    insert(y, root);        // Insert y and insert will nest play and rotate with push up
}

void change(int u, int pos, int x){
    update(w[pos], x, tr[u].u);     // update is required as long as the interval maintained by the segment tree node contains pos
    if(tr[u].l == tr[u].r) return;
    int mid = (tr[u].l + tr[u].r) >> 1;
    if(pos <= mid) change(ls, pos, x);
    else change(rs, pos, x);
}

int query(int u, int l, int r, int x){     // Query x's ranking
    if(tr[u].l >= l && tr[u].r <= r){
        return get_less(x, tr[u].u) - 1;    // Find the number less than x, because - 1 is required to include sentinels
    }
    int mid = (tr[u].l + tr[u].r) >> 1, res = 0;
    if(l <= mid) res += query(ls, l, r, x);     // The number of the whole interval less than x = the sum of the numbers of the left and right intervals less than x
    if(r > mid) res += query(rs, l, r, x);
    return res;
}

int query_pre(int u, int l, int r, int x){
    if(tr[u].l >= l && tr[u].r <= r)
        return get_pre(x, tr[u].u);
    int mid = (tr[u].l + tr[u].r) >> 1, res = -INF;
    if(l <= mid) res = max(res, query_pre(ls, l, r, x));    // The maximum number less than x in the interval is the max of the number less than x in the left and right intervals
    if(r > mid) res = max(res, query_pre(rs, l, r, x));
    return res;
}

int query_next(int u, int l, int r, int x){
    if(tr[u].l >= l && tr[u].r <= r)
        return get_next(x, tr[u].u);
    int mid = (tr[u].l + tr[u].r) >> 1, res = INF;
    if(l <= mid) res = min(res, query_next(ls, l, r, x));       // Same as above
    if(r > mid) res = min(res, query_next(rs, l, r, x));
    return res;
}

void build(int u, int l, int r){
    tr[u].l = l, tr[u].r = r;           // tr[u].u will change with the function reference
    insert(-INF, tr[u].u), insert(INF, tr[u].u);        // Every balance tree should be joined by sentinels
    for(int i = l; i <= r; i++) insert(w[i], tr[u].u);
    if(tr[u].l == tr[u].r) return;
    int mid = (tr[u].l + tr[u].r) >> 1;
    build(ls, l, mid), build(rs, mid + 1, r);
}

int main(){
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    cin >> n >> m;
    for(int i = 1; i <= n; i++) cin >> w[i];
    build(1, 1, n);
    for(int i = 1; i <= m; i++){
        int op, l, r, x, pos, k;
        cin >> op;
        if(op == 1){    // x ranks in [l,r]
            cin >> l >> r >> x;
            cout << query(1, l, r, x) + 1 << endl;      // The number of query queries is less than x, and it needs + 1 to become the ranking of X
        }
        else if(op == 2){       // Value of rank k in [l,r]
            cin >> l >> r >> k;
            int a = 0, b = 1e8;
            while(a < b){               // Due to the monotonicity of ranking and value, the number ranked k can be found by dichotomy
                int mid = (a + b + 1) >> 1;
                if(query(1, l, r, mid) + 1 <= k) a = mid;   // The number of query queries is less than x, and it needs + 1 to become the ranking of X
                else b = mid - 1;
            }
            cout << b << endl;
        }
        else if(op == 3){
            cin >> pos >> x;
            change(1, pos, x);
            w[pos] = x;
        }
        else if(op == 4){
            cin >> l >> r >> x;
            cout << query_pre(1, l, r, x) << endl;
        }
        else{
            cin >> l >> r >> x;
            cout << query_next(1, l, r, x) << endl;
        }
    }
    return 0;
}

Segment tree nested segment tree

Example: query the largest number of \ (K \)

  1. Offline processing stores all the data to be inserted
  2. The outer weight segment tree maintains the weight, and the inner ordinary segment tree maintains the subscript interval within the weight range, which supports interval addition and summation
  3. Interval labeling is used to perpetuate the optimization constant
#define ls u << 1
#define rs u << 1 | 1
#define pb push_back
using namespace std;
// Offline processing + weight segment tree set dynamic opening point segment tree maintenance interval information + mark permanence
// It can also be solved by tree array + line segment tree + overall bisection
const int N = 5e4 + 10, M = 1e7 + 3e6 + 10;

struct Q{
    int op, l, r, x;
}q[N];

struct T1{
    int l, r, u;
}tr1[N << 2];

int n, m, idx;
vector<int> alls;

struct T2{
    int l, r, add, sum;     // l. R is the left and right son
}tr2[M];

int inter(int l, int r, int ql, int qr){
    return min(qr, r) - max(l, ql) + 1;
}

void update(int u, int l, int r, int ql, int qr){       // l. R is the left and right end points of tr2 interval
    tr2[u].sum += inter(l, r, ql, qr);
    if(l >= ql && r <= qr){
        tr2[u].add ++;
        return ;
    }
    int mid = (l + r) >> 1;
    if(ql <= mid){
        if(!tr2[u].l) tr2[u].l = ++ idx;
        update(tr2[u].l, l, mid, ql, qr);
    }
    if(qr > mid){
        if(!tr2[u].r) tr2[u].r = ++ idx;
        update(tr2[u].r, mid + 1, r, ql, qr);
    }
}

int get_sum(int u, int l, int r, int ql, int qr, int add){
    int res = 0;
    if(l >= ql && r <= qr)
        return tr2[u].sum + (r - l + 1) * add;
    add += tr2[u].add;          // The marks of the node itself remain unchanged, and the passed marks should be accumulated
    int mid = (l + r) >> 1;
    if(ql <= mid){
        if(!tr2[u].l) res += inter(l, mid, ql, qr) * add;
        else res += get_sum(tr2[u].l, l, mid, ql, qr, add);
    }
    if(qr > mid){
        if(!tr2[u].r) res += inter(mid + 1, r, ql, qr) * add;
        else res += get_sum(tr2[u].r, mid + 1, r, ql, qr, add);
    }
    return res;
}

int get(int x){
    return lower_bound(alls.begin(), alls.end(), x) - alls.begin();
}

int query(int u, int l, int r, int x){
    if(tr1[u].l == tr1[u].r) return tr1[u].l;
    int mid = (tr1[u].l + tr1[u].r) >> 1;
    int k = get_sum(tr1[rs].u, 1, n, l, r, 0);      // Get the number of subtrees on the right
    if(k >= x) return query(rs, l, r, x);
    else return query(ls, l, r, x - k);
}

void change(int u, int l, int r, int x){
    update(tr1[u].u, 1, n, l, r);
    if(tr1[u].l == tr1[u].r) return ;
    int mid = (tr1[u].l + tr1[u].r) >> 1;
    if(x <= mid) change(ls, l, r, x);
    else change(rs, l, r, x);
}

void build(int u, int l, int r){
    tr1[u].l = l, tr1[u].r = r, tr1[u].u = ++ idx;
    if(tr1[u].l == tr1[u].r) return;
    int mid = (tr1[u].l + tr1[u].r) >> 1;
    build(ls, l, mid), build(rs, mid + 1, r);
}

int main(){
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    cin >> n >> m;
    for(int i = 1; i <= m; i++){
        cin >> q[i].op >> q[i].l >> q[i].r >> q[i].x;
        if(q[i].op == 1)
            alls.pb(q[i].x);
    }
    sort(alls.begin(), alls.end());
    alls.erase(unique(alls.begin(), alls.end()), alls.end());
    build(1, 0, alls.size() - 1);
    for(int i = 1; i <= m; i++){
        if(q[i].op == 1) change(1, q[i].l, q[i].r, get(q[i].x));
        else cout << alls[query(1, q[i].l, q[i].r, q[i].x)] << endl;
    }
    return 0;
}

AC automata

principle

  1. Many are similar to KMP algorithm, but have their own optimization (Trie graph optimization)

Application and expansion

  • Using the ne [] array feature, cnt [] records the occurrence times of all word prefixes at the same time, accumulates all CNTs from the back to the front, records the position of word nodes, and can count the occurrence times of all words in the string

Trie graph optimization template

#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int N = 1e4 + 10, S = 55;     // Number of nodes = number of words * maximum length of words
int son[N * S][26], idx, ne[N * S];
int cnt[N * S], n;
string str;

void insert(string s){              // Trie insert
    int len = s.size(), p = 0;
    for(int i = 0; i < len; i++){
        int t = s[i] - 'a';
        if(!son[p][t]) son[p][t] = ++idx;
        p = son[p][t];
    }
    cnt[p]++;
}

void build(){                               // Similar to kmp, wide search is carried out in two dimensions, layer by layer
    int len = str.size();
    queue<int> q;
    for(int i = 0; i < 26; i++)             // Put all the sub nodes in the first layer first, and i in kmp starts to cycle from 2
        if(son[0][i])
            q.push(son[0][i]);
    while(q.size()){
        auto t = q.front();
        q.pop();
        for(int i = 0; i < 26; i++){        // Loop through all possible child nodes
            int& p = son[t][i];
            if(!p) p = son[ne[t]][i];       // If the child node does not exist, jump to the node pointed to by the parent node ne pointer
            else{
                ne[p] = son[n[t]][i];      // if(p[i] == p[j + 1]) ne[i] = ++j;
                q.push(p);
            }
        }
    }
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr), cout.tie(nullptr);
    int T;
    cin >> T;
    while(T--){
        memset(son, 0, sizeof son);
        memset(cnt, 0, sizeof cnt);
        memset(ne, 0, sizeof ne);
        cin >> n;
        for(int i = 0; i < n; i++){
            cin >> str;
            insert(str);
        }
        cin >> str;
        build();
        int len = str.size();
        long long res = 0;
        for(int i = 0, j = 0; i < len; i++){
            int t = str[i] - 'a';
            j = son[j][t];      // Since the end node points to the empty node, that is, the root, it is equivalent to traversing the whole trie
            int p = j;
            while(p){
                res += cnt[p];
                cnt[p] = 0;     // The visited node cnt should be cleared to avoid double calculation
                p = ne[p];      // Node pointed to by ne pointer
            }
        }
        cout << res << endl;
    }
    return 0;
}

Search and graph theory

DFS deep search

enumeration

Exponential enumeration

Select and deselect

// Exponential enumeration
void dfs(int u){
    if(u > n){
        for(auto t: v)
            printf("%d ", t);
        printf("\n");
        return ;
    }
    v.pb(u);        // choose
    dfs(u + 1);
    v.pob();
    dfs(u + 1);     // Not selected
}

\(n \) queen enumeration example

The main difference between the two is that the information maintained by dfs parameters leads to different search order

Enumerate according to the selection and non selection of each grid

void dfs(int x, int y, int s){
    if(y == n){     // Beyond the horizontal axis, switch to the next layer to continue enumeration
        y = 0;
        x++;
    }
    if(x == n){
        if(s == n){
            for(int i = 0; i < n; i++)
                puts(g[i]);
            puts("");
        }
        return;     // return to prevent stack explosion
    }
    // Deselect the current grid
    dfs(x, y + 1, s);
    
    // Select current grid
    if(!dg[x + y] && !udg[x - y + n] && !row[x] && !col[y]){
        g[x][y] = 'Q';
        dg[x + y] = udg[x - y + n] = row[x] = col[y] = true;
        dfs(x, y + 1, s + 1);
        dg[x + y] = udg[x - y + n] = row[x] = col[y] = false;
        g[x][y] = '.';
    }
}

Enumerate by column

// Enumerate by row
void dfs(int u){
    if(u == n){
        for(int i = 0; i < n; i++)
            puts(g[i]);
        puts("");
    }
    for(int i = 0; i < n; i++){        // Enumeration column
        if(!col[i] && !udg[u - i + n] && !dg[u + i]){
            g[u][i] = 'Q';
            col[i] = udg[u - i + n] = dg[u + i] = true;
            dfs(u+1);
            col[i] = udg[u - i + n] = dg[u + i] = false;
            g[u][i] = '.';
        }
    }
}

Combinatorial enumeration

// Combinatorial enumeration
void dfs(int u){
    if(ans.size() > m || ans.size() + (n - u + 1) < m)  // Exponential enumeration pruning
        return;
    if(ans.size() == m){
        for(auto t: ans)
            printf("%d ", t);
        printf("\n");
        return;
    }
    ans.pb(u);
    dfs(u + 1);
    ans.pob();
    dfs(u + 1);
}

Permutation enumeration

// Permutation enumeration
void dfs(int u){        // u records where the current enumeration is located
    if(u > n){      // After enumerating everything, output it and return
        for(auto t : ans)
            printf("%d ", t);
        printf("\n");
        return;
    }
    for(int i = 1; i <= n; i++){
        if(!st[i]){
            st[i] = true;
            ans.pb(i);
            dfs(u + 1);
            ans.pob();      // to flash back
            st[i] = false;
        }
    }
}

BFS wide search

bfs searches slowly in circles.
The following two points should be considered in solving the problem:

  1. What information does the node in the queue maintain
  2. How to solve the representation of distance in bfs

global template
BFS must pay attention to the information maintained by a single node in the queue, which is very important.

int bfs(){
    queue<PII> q;
    q.push({1,1});
    d[1][1] = 0;        // Mark the distance from the upper left corner and initialize it to - 1. If it is not - 1, it means that it has been accessed
    while(q.size()){
        auto t = q.front();
        q.pop();
        for(int i = 0; i < 4; i++){
            int x= t.first + dx[i], y = t.second + dy[i];
            if(x < 1 || x > n || y < 1 || y > m || d[x][y] != -1 || g[x][y] == 1)
                continue;
            d[x][y] = d[t.first][t.second] + 1;
            q.push({x,y});
        }
    }
    return d[n][m];
}

Eight digital examples

  • The queue node is \ (string \)
  • The hash table maps the distance of each string state
int dx[4] = {0,0,1,-1}, dy[4] = {1,-1,0,0};
unordered_map<string, int> d;
string ed = "12345678x";

int bfs(string st){
    queue<string> q;
    q.push(st);
    d[st] = 0;
    while(q.size()){
        auto t = q.front();
        q.pop();
        int dist = d[t];
        if(t == ed) return dist;
        int k = t.find('x');
        for(int i = 0; i < 4; i++){
            int x = k / 3 + dx[i], y = k % 3 + dy[i];
            if(x < 0 || x > 2 || y < 0 || y > 2) continue;
            swap(t[k], t[x * 3 + y]);
            if(!d.count(t)){
                d[t] = dist + 1;
                q.push(t);               
            }
            swap(t[k], t[x * 3 + y]);
        }
    }
    return -1;
}

Storage of trees and graphs

  • Tree is a special graph, which is stored in the same way as graph
  • For undirected graphs \ (ab \), store two directed edges, \ (a - > b \), \ (B - > a \)

Adjacency matrix storage

g[a][b] = a->b

Adjacency table storage

int h[N], e[N], ne[N], w[N], idx;
// Add edge a - > b
void add(int a, int b, int c){
    e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}
int main(){
    memset(h, -1, sizeof h);
    ...
    return 0;
}

Traversal of trees and graphs

Time complexity
\(O(n + m) \), \ (n \) represents the number of points, \ (m \) represents the number of sides

dfs depth first traversal

Examples

AcWing 846. The center of gravity of the tree
dfs returns int, which can maintain the number of nodes in the subtree

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

bfs width first traversal

Sometimes you can use an array to maintain both tag and distance functions

#include<queue>

void bfs(){
    queue<int> q;
    q.push(1);      // First element listing
    st[1] = true;   // Mark when listed
    while(q.size()){    // Queue is not empty
        int t = q.front();  // Take the lead
        q.pop();    // Remember pop
        for(int i = h[t]; ~i; i = ne[i]){    // Traverse all directions
            int j = e[i];
            if(!st[j]){     // Not reached or qualified for traversal
                q.push(j)
                st[j] = true;   // Join the team and mark
            }
        }
    }
}

Topological sorting

Topological graph is also called directed acyclic graph. The left and right order in the topological sequence must meet the starting point to the end point of the edge.

void topo(){
    queue<int> q;
    for(int i = 1; i <= n; i++){
        if(!indegree[i]){
            q.push(i);
            ans.pb(i);
        }
    }
    while(q.size()){
        auto t = q.front();
        q.pop();
        for(int i = h[t]; i != -1; i = ne[i]){
            int j = e[i];
            indegree[j]--;
            if(!indegree[j]){
                q.push(j);
                ans.pb(j);
            }
        }
    }
}

Shortest path problem

\(n \) represents the number of points and \ (m \) represents the number of sides.

  • Single source shortest circuit
    • All edge weights are positive
      • Naive dijkstra algorithm \ (O(n^2) \) (suitable for dense graphs)
      • Heap optimization dijkstra algorithm \ (O(m*logn) \) (suitable for sparse graphs, \ (m \) and \ (n \) of one order of magnitude)
    • Existence of negative weight edge
      • Bellman Ford algorithm \ (O(n*m) \) (no more than \ (k \) edges can be used)
      • SPFA algorithm generally \ (O(m)\), \(O(n*m) \)
  • Multi source sink shortest path
    • Floyd algorithm \ (O(n^3) \)

Dijkstra algorithm

Plain Dijkstra \(O(n^2) \)

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;

const int N = 510;  // point
int g[N][N],dist[N]; // g [] [] accesses points and edges in the form of adjacency matrix, and dist [] stores the shortest distance from each point to the first point
bool st[N]; // Record whether the shortest circuit has been determined at each point
int n,m;

int dijkstra(){
    memset(dist,0x3f,sizeof dist);

    dist[1] = 0;
    // Cycle n times
    for(int i = 1;i <= n;i++){  
        int t = -1;
        // Find out the point with the shortest distance from the first point in the undetermined shortest path point (greedy proof, just memorize it)
        for(int j = 1;j <= n;j++)
            if(!st[j] && (t == -1 || dist[t] > dist[j]))
                t = j;

        st[t] = true;   // sign

        for(int j = 1;j <= n;j++)
            dist[j] = min(dist[j],g[t][j] + dist[t]);
    }
    if(dist[n] == 0x3f3f3f3f) return -1;
    else return dist[n];
}


int main(){
    scanf("%d%d",&n,&m);
    memset(g,0x3f,sizeof g);
    while(m--){
        int x,y,z;
        scanf("%d%d%d",&x,&y,&z);
        // The self loop is automatically ignored, and the double edge is the shortest
        g[x][y] = min(g[x][y],z);
    }

    cout << dijkstra() << endl;
    return 0;
}

Heap optimization Dijkstra \(O(m*logm = m *logn) \)

#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
const int N = 150010;
// Represents a point, first represents the shortest distance from the point to the root node, and second represents the number of the point
typedef pair<int,int> PII;      

int e[N],ne[N],h[N],w[N],idx;   // Sparse graph is stored in the form of adjacency table
int n,m,dist[N];    // dist [] record the shortest distance from each point to the root node
bool st[N];     // Has the marked point been updated with the minimum value

void add(int a,int b,int c){
    w[idx] = c; e[idx] = b; ne[idx] = h[a];h[a] = idx++;
}

int dijkstra(){
    // Priority queue, incremental storage, establishment of small root heap and storage of the shortest edge. This heap cannot delete or modify specified elements
    priority_queue <PII, vector<PII>, greater<PII>> heap;   
    memset(dist,0x3f,sizeof dist);      // Initialize dist [] to infinity
    // The distance from the first node to itself is 0
    dist[1] = 0;    
    heap.push({0,1});   

    while(heap.size()){
        PII t = heap.top();     // Take out the point with the shortest distance
        heap.pop();
        int distance = t.first, ver = t.second; // Extract the number of the shortest distance

        if(st[ver]) continue;   // If the point has updated the shortest distance to the root node, continue
        st[ver] = true;      // Not updated, marked as true

        // Use this point to update the distance of other points
        for(int i = h[ver];i != -1;i = ne[i]){
            int j = e[i];
            // If (distance from J to origin) > (distance from origin to t + distance from t to j), update the shortest distance of J
            if(dist[j] > distance + w[i]){
                dist[j] = distance + w[i];
                heap.push({dist[j],j});     // Add the updated information of point j to the heap
            }
        }
    }
    if(dist[n] == 0x3f3f3f3f) return -1;        // It indicates that the distance of n is not updated, and there is no connected edge from the origin to n
    return dist[n];
}

int main(){
    scanf("%d%d",&n,&m);
    memset(h,-1,sizeof h); // The initialization header of adjacency table is - 1
    while(m--){
        int x,y,z;
        scanf("%d%d%d",&x,&y,&z);
        add(x,y,z);     // The following functions automatically use the shortest edge, and the double edge problem can be ignored
    }
    cout << dijkstra() << endl;
    return 0;
}

Bellman Ford algorithm

const int N = 510,M = 10010, INF = 0x3f3f3f3f;
int dist[N],last[N]; // dist [] records the distance to the origin, and last [] makes a backup, because the existence of negative weight edge prevents series connection
int n,m,k;
// Save points and edges of structure array
struct Edge{
    int a,b,w;
}edges[M];

int bellman_ford(){
    memset(dist, 0x3f,sizeof dist);
    dist[1] = 0;
    for(int i = 0;i < k;i++){       // The shortest distance of k edges
        memcpy(last,dist,sizeof dist);      // Copy
        for(int j = 0;j < m;j++){           // Update shortest distance
            auto e = edges[j];
            // Similar to dijkstra, note that the latter distance is the backup distance last[e.a]
            dist[e.b] = min(dist[e.b], last[e.a] + e.w);    
        }
    }
    return dist[n];     // If there is a negative weight edge, judge dist [n] > inf / 2 and output impossible. The specific value depends on the topic range
}

spfa algorithm

spfa finding the shortest path

\The codes of (spfa \) and \ (dijkstra \) are similar, and the algorithm of \ (bellman_ford \) mainly lies in:

  • Instead of traversing all the edges, update the other points with the point whose \ (dist \) value becomes smaller
int spfa(){
    memset(dist,0x3f,sizeof dist);      // Initialization distance
    dist[1] = 0;
    queue<int> q;       // Establish a queue and put it into the first point. The queue accesses the point number
    q.push(1);
    st[1] = true;       // Mark that the first point has been put into the queue
    // Queue is not empty
    while(q.size()){    
        int t = q.front();      // Take the head of the team
        q.pop();
        st[t] = false;  // The tag header is no longer in the queue
        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];       // Update distance
                // If j is not in the queue, it is put in the queue to update other points
                if(!st[j]){ 
                    q.push(j);
                    st[j] = true;
                }
            }
        }
    }
    if(dist[n] == 0x3f3f3f3f) return -1;
    else return dist[n];
}

spfa judging negative ring

  • Do not initialize the \ (dist \) array
  • Each point should be added to the queue in advance
int ne[N],e[N],w[N],dist[N],h[N],idx,n,m;
bool st[N];
int cnt[N];     // Record the number of sides to go through at a certain point
// The code body is the same as spfa. There are two main differences: the initialization of distanceless dist, and each point is put into the queue in advance
void add(int a,int b,int c){
    e[idx] = b,w[idx] = c,ne[idx] = h[a],h[a] = idx++;
}

bool spfa(){
    queue<int> q;
    for(int i = 1;i <= n;i++){
        q.push(i);
        st[i] = true;
    }
    while(q.size()){
        int 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];
                cnt[j] = cnt[t] + 1;    // + 1 based on the cnt of the previous point
                // cnt greater than n indicates that there are at least n+1 points. According to the drawer principle, there must be a ring and a negative ring
                if(cnt[j] >= n) return true;    
                if(!st[j]){
                    q.push(j);
                    st[j] = true;
                }
            }
        }
    }
    return false;   // Return false if there is no exception
}

Floyd algorithm

  • Based on \ (DP \), \ (f[k][i][j] \) represents the shortest path from \ (I \) through \ (K \) to \ (j \)
  • Handling self loop is to put \ (g[i][i] = 0 \)

State transition equation:

\[g[k][i][j] = min(g[k][i][j], g[k-1][i][k] + g[k-1][k][j]) \]

void init(){
    memset(g, 0x3f, sizeof g);
    for(int i = 1; i <= n; i++)
        g[i][i] = 0;    // Self loop is 0
}

void floyd(){       
    for(int k = 1; k <= n; k++)     
        for(int i = 1; i <= n; i++)
            for(int j = 1; j <= n; j++)
                g[i][j] = min(g[i][j], g[i][k] + g[k][j]);   // Optimized the first dimensional space
}

minimum spanning tree

\(n \) represents the number of points and \ (m \) represents the number of sides

  • \(Prim \) algorithm
    • Plain version \ (Prim\) \(O(n^2) \) (dense map)
    • Heap optimization \ (Prim\) \(O(m*logn) \) (not commonly used)
  • \(Kruskal \) algorithm \ (O(m*logm) \) (sparse graph)

Plain Prim algorithm \ (O(n^2) \)

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 510,INF = 0x3f3f3f3f;

int g[N][N],dist[N];    // Adjacency matrix stores the midpoint and edge of the graph, dist [] accesses the distance from a point to the set (the shortest distance to all points in the set)
int n,m;
bool st[N]; // Tag array

int prim(){
    memset(dist,INF,sizeof dist);
    int res = 0;
    for(int i = 0;i < n;i++){
        int t = -1;     // t is in the for loop
        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;     // Indicates that the graph is not connected
        if(i) res += dist[t];       // Write res first and then update dist to exclude self ring

        // Note that dist[j] and g[t][j] take the minimum and distinguish them from dijkstra
        for(int j = 1;j <= n;j++) dist[j] = min(dist[j],g[t][j]);

        // sign
        st[t] = true;
    }
    return res;
}

int main(){
    cin >> n >> m;
    memset(g,INF,sizeof g); // Initialize edge to infinity
    while(m--){
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        g[u][v] = g[v][u] = min(g[u][v],w); // Undirected graph
    }
    int t = prim();
    if(t == INF) puts("impossible");
    else printf("%d",t);
    return 0;
}

Heap optimized Prim algorithm \ (O(m * logn) \)

#include <cstring>
#include <iostream>
#include <queue>
using namespace std;

const int MAXN = 510, MAXM = 2 * 1e5 + 10, INF = 0x3f3f3f3f;
typedef pair<int, int> PII;
int h[MAXM], e[MAXM], w[MAXM], ne[MAXM], idx;
bool vis[MAXN];
int n, m;

void add(int a, int b, int c) {
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}

int Prim()
{
    memset(vis, false, sizeof vis);
    int sum = 0, cnt = 0;
    priority_queue<PII, vector<PII>, greater<PII>> q;
    q.push({0, 1});

    while (!q.empty())
    {
        auto t = q.top();
        q.pop();
        int ver = t.second, dst = t.first;
        if (vis[ver]) continue;
        vis[ver] = true, sum += dst, ++cnt;

        for (int i = h[ver]; i != -1; i = ne[i])
        {
            int j = e[i];
            if (!vis[j]) {
                q.push({w[i], j});
            }
        }
    }

    if (cnt != n) return INF;
    return sum;
}

int main()
{
    cin >> n >> m;
    memset(h, -1, sizeof h);
    for (int i = 0; i < m; ++i)
    {
        int a, b, w;
        cin >> a >> b >> w;
        add(a, b, w);
        add(b, a, w);
    }

    int t = Prim();
    if (t == INF) cout << "impossible" << endl;
    else cout << t << endl; 
}

Kruskal (kruskam)

  • Sort all edges from small to large
  • Enumerate all the edges. If the endpoints are not in the same connected block, they will be connected until all the points are connected
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 2e5 + 10, INF = 0x3f3f3f3f;
int n, m, p[N];

void init(){        // Initialize and query set
    for(int i = 1; i <= n; i++)
        p[i] = i;
}

int find(int x){        // Find operation
    if(x != p[x]) return p[x] = find(p[x]);
    return p[x];
}

struct Edge{        // Structure edge saving
    int a, b, w;
    bool operator < (const Edge& W) const{
        return w < W.w;
    }
}edges[N];

int kruskal(){
    int cnt = 0, res = 0;;
    for(int i = 0; i < m; i++){
        if(cnt >= n - 1) break;
        int a = edges[i].a, b = edges[i].b, w = edges[i].w;
        a = find(a), b = find(b);
        if(a != b){     // A and b are not in a connected block
            p[b] = a;
            cnt++;
            res += w;
        }
    }
    if(cnt < n - 1)     // If the number of connected edges is less than n - 1, there is no minimum spanning tree
        return INF;
    return res;
}


int main(){
    cin >> n >> m;
    init();
    for(int i = 0; i < m; i++){
        int a, b, c;
        cin >> a >> b >> c;
        edges[i] = {a, b, c};
    }
    sort(edges, edges + m);     // Sort all edges from small to large
    int ans = kruskal();
    if(ans == INF)
        puts("impossible");
    else
        printf("%d", ans);
    return 0;
}

Sub small spanning tree

The next smallest spanning number is the adjacent set of the minimum spanning tree, and only one edge is different from the minimum spanning tree.
There are two ways to calculate the secondary small spanning tree:

  • Find the minimum spanning tree, enumerate and delete each edge of the minimum spanning tree, and then find the minimum spanning tree in turn (this small spanning tree cannot be guaranteed) \ (O(mlogm + nm) \)
  • Find the minimum spanning tree, mark whether each edge is in the minimum spanning tree, preprocess the maximum value and sub maximum value of edge weight in the path from the midpoint to the point of the tree, enumerate non tree edges, add the edge to the tree, and delete the tree edge with the maximum edge weight or sub maximum edge weight (when the enumerated edge length is equal to the maximum edge weight), Finally, we get the small spanning tree (we can get the strict small spanning tree) \ (O(mlogm + n^2) \)
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N = 510, M = 1e4 + 10;
int h[N], e[2 * N], ne[2 * N], w[2 * N], idx;
int dist1[N][N], dist2[N][N], n, m, p[N];       // dist1 [] [] saves the maximum edge weight from point to point, and dist [] [] saves the second largest edge weight

// Finding sub small spanning tree

struct Edge{
    int a, b, w;
    bool f;
    bool operator < (const Edge & W) const{
        return w < W.w;
    }
}edges[M];

void init(){
    for(int i = 1; i <= n; i++)
        p[i] = i;
}

int find(int x){
    if(x != p[x]) p[x] = find(p[x]);
    return p[x];
}

void add(int a, int b, int c){
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}

void dfs(int u, int fa, int maxd1, int maxd2, int d1[], int d2[]){
    d1[u] = maxd1;      // According to the characteristics of the minimum spanning tree, you can update directly
    d2[u] = maxd2;
    for(int i = h[u]; ~i; i = ne[i]){
        int j = e[i];
        if(j != fa){
            int t1 = maxd1, t2 = maxd2;     // It must be stored with temporary variables, and maxd1 and maxd2 will be used later;
            if(w[i] > t1){      // Update the largest and second largest edges
                t2 = t1;
                t1 = w[i];
            }
            else if(w[i] > t2 && w[i] < t1)     // Update minor edge
                t2 = w[i];
            dfs(j, u, t1, t2, d1, d2);
        }
    }
}

int main(){
    cin >> n >> m;
    init();
    memset(h, -1, sizeof h);
    for(int i = 0; i < m; i++)
        cin >> edges[i].a >> edges[i].b >> edges[i].w;
    sort(edges, edges + m);
    ll sum = 0, res = 1e18;
    for(int i = 0; i < m; i++){
        auto t = edges[i];
        int a = t.a, b = t.b, w = t.w;
        int pa = find(a), pb = find(b);
        if(pa != pb){
            p[pa] = pb;
            sum += w;
            edges[i].f = true;      // Mark as tree edge
            add(a, b, w), add(b, a, w);     // Build a tree
        }
    }
    for(int i = 1; i <= n; i++)         // Maximum edge weight and sub maximum edge weight between preprocessing points
        dfs(i, -1, 0, 0, dist1[i], dist2[i]);
        
    for(int i = 0; i < m; i++){
        if(!edges[i].f){
            auto t = edges[i];
            int a = t.a, b = t.b, w = t.w;
            if(w > dist1[a][b])     // Greater than maximum edge
                res = min(res, sum + w - dist1[a][b]);
            else if(w > dist2[a][b])    // Less than or equal to the maximum edge weight, greater than the second largest edge weight
                res = min(res, sum + w - dist2[a][b]);
        }
    }
    cout << res << endl;
    
    return 0;
}

Bipartite graph

  • Staining method \ (O(n + m) \)

  • Hungarian algorithm \ (O(m*n) \), actually less than \ (O(m*n) \)

A bipartite graph has no odd rings and is divided into two parts without edges, that is, the two ends of an edge in the original graph cannot be of the same color, otherwise it is not a bipartite graph

Judge whether bipartite graph by coloring method

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 2e5 + 10;

// Template idea: a bipartite graph has no odd rings and is divided into two parts without edges, that is, the two ends of an edge in the original graph cannot be of the same color, otherwise it is not a bipartite graph

int n,m;
int e[N],ne[N],h[N],idx;    // Adjacency table storage map
int color[N];   // Color array, divided into two colors, 1 and 2

void add(int a, int b){
    e[idx] = b,ne[idx] = h[a],h[a] = idx++;
}

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]){
            if(!dfs(j,3 - c)) return false; // Dye into 3-c color, original 1 is dyed 2, original 2 is dyed 1
        }
        else if(color[j] == c) return false;    // Same color as another point, non bipartite graph
    }
    return true;
}

int main(){
    scanf("%d%d",&n,&m);
    memset(h,-1,sizeof h);
    while(m--){
        int a,b;
        scanf("%d%d",&a,&b);
        add(a,b),add(b,a);
    }
    bool flag = true;
    for(int i = 1;i <= n;i++){
        if(!color[i])
            if(!dfs(i,1)){          // Dyeing problem
                flag = false;
                break;
            }
    }
    if(flag) puts("Yes");
    else puts("No");

}

Maximum matching number of bipartite graphs

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 510,M = 1e5 + 10;

int ne[M],e[M],h[N],idx;    // Adjacency table storage map
int n1,n2,m;
int match[N];   // Which boy does each girl correspond to
bool st[N];     // Mark whether the girl has matched for a boy

void add(int a,int b){
    e[idx] = b,ne[idx] = h[a],h[a] = idx++;
}

bool find(int x){
    for(int i = h[x];i != -1;i = ne[i]){
        int j = e[i];
        if(!st[j]){
            st[j] = true;
            // The girl has not matched, or the boy who has matched can find another suitable one
            if(match[j] == 0 || find(match[j])){    
                match[j] = x;
                return true;
            }
        }
    }
    return false;   // If it doesn't work, return false
}

int main(){
    scanf("%d%d%d",&n1,&n2,&m);
    memset(h,-1,sizeof h);      // Initialize header
    while(m--){
        int a,b;
        scanf("%d%d",&a,&b);
        add(a,b);
    }
    int res = 0;
    for(int i = 1;i <= n1;i++){
        memset(st,false,sizeof st);             // For each boy to match from front to back, memset is required
        if(find(i)) res++;
    }
    printf("%d\n",res);
    return 0;
}

number theory

Fast power & high precision

Counting

typedef long long ll;

// solution1
ll qmi(ll a, ll k, ll p){ 	// a^k mod p
    ll res = 1;
    while(k){
        if(k & 1)
            res = res * a % p; // int res = 1LL * res * a % p
            k >>= 1;
            a = a * a % p; // int a = 1LL * a * a % p;
    }
    return res;
}

// solution 2
int qmi(int a, long long b){
    int res = 1;
    for(; b; b >>= 1){
        if(b & 1)
            res = (long long) res * a % mod;
            a = (long long) a * a % mod;
    }
    return res;
}

// solution 3
long long quick_pow(long long x,long long y, ll p)
{
	if(y==1) return x;
	if(y==0) return 1; //The codes in case 1 and 2 must be placed before case 3
	if(y%2==0) return quick_pow(x*x%p,y/2);
	if(y%2!=0) return x*quick_pow(x*x%p,y/2)%p;
}

Fast acceleration (high precision multiplication)

With modulus, \ (a*b\mod p \)

// solution1 is similar to the fast power idea, which splits k into binary numbers
typedef long long ll;
ll qmult(ll a, ll k, ll p){
    ll res = 0;
    while(k){
        if(k & 1)
            res = (res + a) % p;
        k >>= 1;
        a = (a * 2) % p;
    }
    return res;
}

// solution2 is derived from the advanced guide, using the automatic modulo of ull and the reserved integer of long double
ull mul(ull a, ull b, ull p){
    a %= p, b %= p;
    ull c = (long double) a * b / p;
    ull x = a * b, y = c * p;
    ull res = x - y;
    if(res < 0) res += p;
    return res;
}

No modulus, \ (a*b \)

vector<int> mul(vector<int> a, int b)
{
    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;
    }
    // while(C.size()>1 && C.back()==0) C.pop_ back();// Consider that only when b==0 can there be pop redundant 0 B= 0 doesn't need this line
    return c;
}

Prime number

Determination of prime number by trial division

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;
}

Decomposition of prime factor by trial division

No matter how decomposed, the cycle range must be within \ (sqrt(n) \), and the prime numbers greater than \ (sqrt(n) \) are judged separately.

void divide(int x)
{
    for (int i = 2; i <= x / i; i ++ )
        if (x % i == 0)	// If i is a prime divisor, i must be a prime divisor. So it's not composite
        {
            int s = 0;
            while (x % i == 0) x /= i, s ++ ;
            cout << i << ' ' << s << endl;	// i-bit factor s is exponential
        }
    if (x > 1) cout << x << ' ' << 1 << endl;		// Note that the maximum factor will remain
    cout << endl;
}

// another version
int p[N], c[N], cnt;     // p [] stores all the prime factors, c [] stores the index of the corresponding prime factor, and cnt stores the number of prime factors
void divide(int n) {
    cnt = 0;
    for(int i = 2; i <= n / i; i++){
        if(n % i == 0){
            p[++cnt] = i, c[cnt] = 1;
            while(n % i == 0) n /= i, c[cnt]++;
        }
    }
    if(n > 1)
        p[++cnt] = n, c[cnt] = 1;
}

Plain screen \ (O(nloglogn) \)

int primes[N], cnt;     // Primes [] stores all primes
bool st[N];         // st[x] is storage x screened 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;
    }
}

Linear sieve (Euler sieve) \ (O(n) \)

int primes[N], cnt;     // Primes [] stores all primes
bool st[N];         // st[x] is storage x screened out

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[i * primes[j]] = true;
            if (i % primes[j] == 0) break;
        }
    }
}

Divisor

gcd maximum common divisor

typedef long long ll;
ll gcd(ll a, ll b){
    return b ? gcd(b, a % b) : a;
}

Number of divisors and sum of divisors

  • \(prime factor decomposition of a\subset Z: \ BF {a = \ PI {I = 1} ^ KP I ^ {n I}}, K is the number of prime factors and n I is a prime factor index \)

  • \(find the number of divisors of \ forall a\subset N ^ +: \)

    \(\ quad \because there are n+1 choices for each p_i index of 0,1\dots n, and different divisors can be obtained by multiplying different p_i. \)

    \(\ Quad \ thereforeis obtained from the multiplication principle, and the number of divisors is \ prod {I = 1} ^ k (n _i + 1) \)

  • \(find the sum of all divisors of \ forall a\subset N ^ +: \)

    ​ \ (\ because \forall m is the divisor of a, and M is obtained by multiplying each prime factor by its different exponent \)

    ​ \ (\ therefore sum of divisors = (p_1^0+p_1^1+\dots+p_1^{n_0})\times\dots\times()(p_k^0+p_k^1+\dots+p_k^{n_k}) \)

    ​ \ (simplify the above formula to: \)

    ​ \ (sum of divisors = \ PI {J = 1} ^ k (\ sum {I = 0} ^ NP _j ^ I) \)

unordered_map<int, int> primes;
const int p = 1e9 + 7;
typedef long long ll;
// Theorem of the number of divisors: prime factor decomposition a = π (pi^(ai^n)), in each pi part, there are 0~ai^n, a total of n + 1 methods, and the conclusion can be obtained from the multiplication principle
// Sum of divisors theorem: a = π (∑ PI ^ a (ij)) (outside I, inside j)
int main(){
    int n;
    scanf("%d", &n);
    while(n --){
        int x;
        scanf("%d", &x);
        for(int i = 2; i <= x / i; i++){
            while(x % i == 0){          // while
                primes[i] ++;
                x /= i;
            }
        }
        if(x > 1)
            primes[x]++;
    }
    // Approximate number
    ll ans = 1;
    for(auto t: primes){
        ans = ans * (t.second + 1) % p;
    }
    printf("%lld\n", ans);
    // Sum of divisors
    ll ans = 1;
    for(auto t: primes){
        ll k = 1;
        while(t.second--){
            k = (k * t.first + 1) % p;
        }
        ans = (ans * k) % p;
    }
    printf("%lld\n", ans);
    return 0;
}

Euler function

Realization of Euler function formula

\(\phi(n)=n\times\Pi_{i=1}^k(1-\frac{1}{p_i})\color{black},p_ I is the prime factor of N and K is the number of prime factors \)

\(\ phi(n) \) size is less than the number of Coprime integers of \ (n \) and \ (n \).

typedef long long ll;
// Euler function: for n = n * π (1 - 1/pi), pi is each prime factor
ll ans = a;
for(int i = 2; i <= a / i; i++){
    if(a % i == 0){
        ans = ans / i * (i - 1);        // Divide i first to prevent decimal and overflow
        while(a % i == 0) a /= i;
    }
}
if(a > 1) ans = ans / a * (a - 1);
printf("%lld\n", ans);

Finding Euler function by sieve method \ (O(nloglogn) \)

Find the prefix sum of the Euler function of \ (n \)

typedef long long ll;

// Euler function: f(n) = n * π (1 - 1 ÷ pi)
int primes[N];
bool st[N];
int phi[N];
int cnt = 0;

ll get_eulers(int n){
    phi[1] = 1;         // The Euler function of 1 is 1
    for(int i = 2; i <= n; i++){
        if(!st[i]){
            primes[cnt++] = i;
            phi[i] = i - 1;
        }
        for(int j = 0; primes[j] <= n / i; j++){
            st[primes[j] * i] = true;
            if(i % primes[j] == 0){
                phi[i * primes[j]] = phi[i] * primes[j];   
                // primes[j] has appeared in phi[i]. Just multiply primes[j]
                break;
            }
            // primes[j] is the smallest prime factor of i * primes[j], and it is not the prime factor of i
            phi[i * primes[j]] = phi[i] * (primes[j] - 1);  // = phi[i] * (phi[primes[j])
        }
    }
    ll ans = 0;     // longlong explosion proof int
    for(int i = 1; i <= n; i++)
        ans += phi[i];
    return ans;
}

Extended Euclidean algorithm (exgcd)

exgcd

\(for a bunch of integers, find \ color{navy}{\exists x,y\subset Z}\color{black}, so that \ color{navy}{a\times x+b\times y=gcd(a,b)} \)

// The equation is established by D = GCD (a, b) = GCD (B, a% B) = ax + by = ax + B (Y - B * (A / b))

void exgcd(int a, int b, int &x, int &y){
    if(!b){
        x = 1, y = 0;
        return;
    }
    // printf("before:a=%d b=%d x=%d y=%d\n", a, b, x, y);
    exgcd(b, a % b, y, x);      // Recurse all the way to the boundary, and the specific values of x and y will not change
    // printf("after:a=%d b=%d x=%d y=%d\n", a, b, x, y);
    y -= a / b * x;     // Backtracking updates the values of x and y
}

Solving linear congruence equation

\(\ color{black} calculate an x_i for each group, so that \ color{purple}{a_i\times x_i\equiv b_i (mod\space m_i)}, \ color{black} has no solution output \ color{red}{impossible} \)

\(the equation is transformed into ax + my = b. if there is a solution, there must be gcd(a, m) | b \)

int main(){
    int n;
    scanf("%d", &n);
    while(n--){
        int a, b, m;
        scanf("%d%d%d", &a, &b, &m);
        int d = gcd(a, m);
        if(b % d){      // If there is a solution, there must be gcd(a, m) | b
            puts("impossible");
            continue;
        }
        int x = 0, y = 0;
        exgcd(a, m, x, y);
        printf("%d\n", (ll)x * b / d % m);      // Just multiply x by b / d, open ll, and% m
    }
    return 0;
}

Chinese remainder theorem

Gauss elimination

Combination number

\[C_a^b=\frac{a!}{b! * (a - b)!} \]

\[C_a^b = \frac{a*(a-1)*\cdots*(a-b+1)}{b!} \]

Basic model

Diaphragm method

AcWing basic course talked about \ (4 \) methods for finding combination numbers, which deal with different data ranges respectively

Number of combinations \ (I \)

meaning of the title

Find \ (C_a^b \mod (10^9 + 7) \)

Data range

\(1≤n≤10000, 1≤b≤a≤2000\)

thinking

\(C_a^b = C_{a-1}^{b-1} + C_{a-1}^{b}\)

const int N = 2010, mod = 1e9 + 7;
int c[N][N];        // c[a][b] -> C_a^b

void init(){
    for(int i = 0; i < N; i++)
        for(int j = 0; j <= i; j++)     // b <= a
            if(!j) 
                c[i][j] = 1;
            else
                c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % mod;        // % mod
}

int main(){
    int T;
    cin >> T;
    init();
    while(T--){
        int a, b;
        cin >> a >> b;
        cout << c[a][b] << endl;
    }

    return 0;
}

Number of combinations \ (II \)

meaning of the title

Find \ (C_a^b \mod (10^9 + 7) \)

Data range

\(1≤n≤10000, 1≤b≤a≤10^5\)

thinking

  • The factorial of numerator and denominator is preprocessed
  • \(a/b \equiv a *b^{-1} \mod p\), \(b^{-1} \) is the multiplicative inverse of \ (B \) module \ (P \)
const int N = 100010, mod = 1e9 + 7;
typedef long long ll;
int fact[N], infact[N];

// Preprocessing factorial (n * logn)

int qmi(int a, int k){
    int res = 1;
    while(k){
        if(k & 1)
            res = 1LL * res * a % mod;
        a = 1LL * a * a % mod;
        k >>= 1;
    }
    return res;
}

int main(){
    fact[0] = 1, infact[0] = 1;
    for(int i = 1; i < N; i++){
        fact[i] = 1LL * fact[i - 1] * i % mod;
        // a / b congruence a * B ^ {- 1}% mod - > A * B ^ {mod - 2}; Fermat's theorem
        infact[i] = 1LL * infact[i - 1] * qmi(i, mod - 2) % mod;    
        }
    int T;
    cin >> T;
    while(T--){
        int a, b;
        cin >> a >> b;
        // Middle% mod anti overflow long long
        cout << 1LL * fact[a] * infact[b] % mod * infact[a - b] % mod << endl;         
        }
    
    
    return 0;
}

Combinatorial number \ (III \) (\ (Lucas \) theorem)

meaning of the title

Enter \ (a,b,p \) and find the value of \ (C_a^b\mod p \)

Data range

\(1≤n≤20\)
\(1≤b≤a≤10^{18}\)
\(1≤p≤10^5\)

thinking

  • Using the \ (Lucas \) theorem:

\[C_a^b\equiv C_{a \% p}^{b \% p} * C_{a / p}^{b / p}\mod p \]

int qmi(int a, int k, int p){
    int res = 1;
    while(k){
        if(k & 1)
            res = 1ll * res * a % p;
        a = 1ll * a * a % p;
        k >>= 1;
    }
    return res;
}

int C(int a, int b, int p){
    if(b > a) return 0; 
    int res = 1;
    // C_a^b = \frac{a*(a-1)*\cdots *(a - b + 1)}{b!};
    for(int i = 1, j = a; i <= b; i++, j--){
        res = 1ll * res * j % p;
        // Fermat's small theorem, a / b congruence, a * b^{-1} module p, b^{-1} = b^{p-2}
        res = 1ll * res * qmi(i, p - 2, p) % p;     
        }
    return res;
    }

// Lucas theorem:
int lucas(ll a, ll b, int p){       // Parameters a and B take long and long
    if(a < p && b < p) return C(a, b, p);
    return 1ll * C(a % p, b % p, p) * lucas(a / p, b / p, p) % p;
}


int main(){
    int T;
    cin >> T;
    while(T--){
        ll a, b, p;
        cin >> a >> b >> p;
        cout << lucas(a, b, p) << endl;
    }
    return 0; // 21
}

Number of combinations \ (IV \)

meaning of the title

Enter \ (a,b \) and find the value of \ (C_a^b \)
The result is large and requires high-precision calculation (no \ (\% \) operation)

Data range

\(1\leq b\leq a\leq 5000\)

thinking

  1. Sieve prime number
  2. Find the number of times each prime number appears in factorial
  3. Traversing prime numbers, high-precision multiplication
int primes[N], cnt;
int sum[N];
bool st[N];
int a, b;

// Linear sieve
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[i * primes[j]] = true;
            if(i % primes[j] == 0)
                break;
        }
    }
}

// Get n! Index with p
int get(int n, int p){
    int res = 0;
    while(n){
        res += n / p;
        n /= p;
    }
    return res;
}

// High precision multiplication
vector<int> mult(vector<int> a, int b){
    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;
}


int main(){
    cin >> a >> b;
    get_primes(a);
    
    for(int i = 0; i < cnt; i++){
        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++){
        int p = primes[i];
        for(int j = 0; j < sum[i]; j++){
            res = mult(res, p);
        }
    }
    // Reverse order output
    for(int i = res.size() - 1; i >= 0; i--)
        cout << res[i];
     
    return 0;
}

Catalan number

\[number of schemes = C {2n} ^ n - C {2n} ^ {n - 1} = \ frac {1} {n + 1} * C {2n} ^ n \]

Examples

AcWing889. 01 sequence satisfying conditions
AcWing129. The train entered the stack

Inclusion exclusion principle

Time complexity
Usually \ (O(2^n) \)

\[\bigcup_{i=1}^nS_i=\sum_{i=1}^{n}S_i-(S_1\bigcap S_2+S_1\bigcap S_3+\cdots+S_{n-1}\bigcap S_n)+\cdots+(-1)^{n-1}\bigcap_{i=1}^nS_i \]

Examples

AcWing890. Divisible number

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N = 20;
int p[N];

int main(){
    int n, m;
    cin >> n >> m;
    for(int i = 0; i < m; i++)
        cin >> p[i];
    ll res = 0;
    for(int i = 1; i < 1 << m; i++){    // Binary enumeration idea
        ll t = 1, cnt = 0;
        for(int j = 0; j < m; j++){
            if(i >> j & 1){
                t *= p[j];      // There may be overflow here. Open longlong
                if(t > n){
                    t = -1;
                    break;
                }
                cnt ++;
            }
        }
        if(t != -1){
            if(cnt & 1)     // Decide whether to add or subtract according to the number of sets
                res += n / t;  
            else
                res -= n / t;
        }
    }
    cout << res << endl;
    return 0;
}

Game theory

nature

  • Finiteness: no matter how they make a decision, they will decide the outcome after a limited step.
  • Fairness: that is, two people follow the same rules in making decisions.

P/N status

P-position: P stands for Previous. The situation where the person who took the last action had a winning strategy is p-position, that is, "the first hand will lose"

N-position: N stands for Next. The situation where the current action person has a winning strategy is n-position, that is, "the first hand can guarantee the winning"

Point P: that is, the point where the player must lose when both sides are in the optimal strategy
N point: that is, the winning point. Under the optimal strategy of both sides, the player will win at this point

Winning state and losing state

  • If the person facing the end state wins, the end state is winning, otherwise the end state is losing
  • The necessary and sufficient condition for a situation to be a winning state is that the situation becomes a winning state after making some decision
  • The necessary and sufficient condition for a situation to be a failure state is that the situation will become a win state no matter what decision is made

nim game

If the exclusive or sum of the number of stones in each pile is not 0, the first hand must win, otherwise lose.

SG function

SG theorem

\[SG(b1,b2) = SG(b1)\oplus SG(b2) \]

#include<unordered_set>
// SG theorem: sg(b1, b2) = sg(b1) ^ sg(b2)
// SG function
int sg(int x){
    if(f[x] != -1) return f[x];
    unordered_set<int> S;
    for(int i = 0; i < x; i++){
        for(int j = 0; j <= i; j++)
            S.insert(sg(i) ^ sg(j));
    }
    for(int i = 0; ; i++)       // The XOR value of sg function may be greater than x. do not write conditions
        if(!S.count(i))
            return f[x] = i;
}

dynamic programming

\(dp \) core idea
A set represents multiple states to form optimization

Digital triangle model

Example: pass a note

#include<iostream>
#include<cstring>
using namespace std;
const int N = 55;
int w[N][N];
int f[N + N][N][N];         // It is the same as the grid status and will not be repeated
                            // Status indication: f[k][i1][i2] indicates that the maximum number is obtained when walking to (i1, k-i1)(i2, k-i2)
                            // When i1 + j1 == i2 + j2, the two paths may coincide, and the end point is f[2n][n][n];
                            // State transition: f[k][i1][i2] = max(f[k-1][i1-1][i2],..[i1][i2-1],...[i1][i2],...[i1-1][i2-1]) + w;
                            // When the values of W coincide, take w[i1][k - i1], and if they do not coincide, add w[i2][k - i2];
The copyright belongs to the author. For commercial reprint, please contact the author for authorization. For non-commercial reprint, please indicate the source.
int main(){
    int n, m;
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= m; j++)
            scanf("%d", &w[i][j]);
    for(int k = 2; k <= n + m; k++){
        for(int i1 = 1; i1 <= n; i1++){
            for(int i2 = 1; i2 <= n; i2++){
                int j1 = k - i1, j2 = k - i2;
                if(j1 < 1 || j1 > m || j2 < 1 || j2 > m) continue;
                int t = w[i1][j1];
                if(i1 != i2) t += w[i2][j2];
                int &x = f[k][i1][i2];
                x = max(x, f[k - 1][i1][i2] + t);
                x = max(x, f[k - 1][i1 - 1][i2] + t);
                x = max(x, f[k - 1][i1][i2 - 1] + t);
                x = max(x, f[k - 1][i1 - 1][i2 - 1] + t);
                // cout << x << " " << endl;
            }
        }
    }
    printf("%d", f[n + m][n][n]);       // Take n for both coordinates, because I1 and I2 are abscissa
    return 0;
}

LIS model

Knapsack model

// 01 backpack:
// Full backpack: 532 Monetary system
// Multiple backpacks:
// Group backpack: 487 Jin Ming's budget is 1020 Diver 1013 Machine allocation
// Specific scheme: 1013 Machine allocation

​ Enter unified \ (n \) to represent the number of items and \ (m \) to represent the volume of the backpack

Scroll array optimization:

  • Use the state of \ (f[i-1] \) to enumerate the volume from large to small
  • Use the state of \ (f[i] \) to enumerate the volume from small to large

be careful ⚠️: You need to find a specific scheme, and you can't optimize the rolling array

01 Backpack

Core: choose or not

General meaning:

​ Given \ (n \) items, each item has a value of \ (w_i \) and only one, and the backpack volume is \ (v \), find the maximum value of all schemes

Status indication: \ (f[i][j] \) indicates the maximum value of all schemes whose volume does not exceed \ (j \) in the first \ (I \) articles

State calculation: \ (f[i][j]=max(f[i-1][j],f[i -1][j - v_i] + w_i) \)

// Rolling array (one-dimensional space optimization)
for(int i = 1; i <= n; i++)
    for(int j = m; j >= v[i]; j--)
        f[j] = max(f[j], f[j - v[i]] + w[i]);

Complete Backpack

State transition certificate:

  • A value \ (f[i][j -v] + w \) represents all the maximum values of the prefix. Recursive optimization

​ \(\because f[i][j] = max(f[i-1][j], f[i-1][j-v]+w,f[i-1][j-2v]+2w,\dots )\)

\(\& f[i][j-v] = max(f[i-1][j-v], f[i-1][j-v]+w, ...)\)

​ \(\therefore f[i][j] = max(f[i][j], f[i][j-v_i] + w_i)\)

General meaning:

​ There are \ (N \) items and a backpack with a capacity of \ (V \), with unlimited items available. The \ (I \) article has a volume of \ (v_i \) and a value of \ (w_i \).

​ Solve which items are selected to enter the backpack, which can maximize the total value of the backpack without exceeding the capacity.

Status indication: \ (f[i][j] \) indicates the maximum value of all schemes with a total volume of no more than \ (j \) selected from the first \ (I \) articles

State calculation: \ (f[i][j]=max(f[i-1][j],f[i][j - v_i] + w_i) \)

// Rolling array (one-dimensional space optimization)
for(int i = 1; i <= n; i++){    // Enumerate each item group
    for(int j = v; j <= m; j++) // Unlike the 01 knapsack, the state is transferred from f[i][j-v[i]], so j is enumerated from small to large
        f[j] = max(f[j], f[j - v[i]] + w[i]);
}

Multiple Backpack

Core: Master \ (2 \) multiple knapsack optimization methods (binary optimization & monotone queue optimization) for different data ranges

General meaning:

​ There are \ (N \) items and a backpack with a capacity of \ (V \). The \ (I \) item has a maximum of \ (s_i \), each with a volume of \ (v_i \) and a value of \ (w_i \).

​ Solve which items are loaded into the backpack, so that the total volume of items does not exceed the capacity of the backpack, and the total value is the largest.

Multiple backpacks \ (I \)

Data range:

​ \(0<N,V≤100\)

​ \ (0 < v_i, w_i, s_i ≤ 100 \) (not absolutely about this order)

Status indication: $f[i][j] $indicates that the total volume of the first \ (I \) items does not exceed the maximum value of \ (j \)

State calculation: \ (f[i][j] = max(f[i][j], f[i-1][j - k * v_i + k * w_i]) \)

Consider this kind of multiple backpack as a special complete backpack, or a complete backpack as a special multiple backpack. Heap cycle

(code omitted)

Multiple knapsack \ (II \) (binary optimization)

Time complexity: \ (O(n\times m\times \log{s}) \)

Data range:

​ \(0<N≤1000\)

​ \(0<V≤2000\)

​ \(0<v_i,w_i,s_i≤2000\)

Binary optimization into 01 knapsack proof:

​ \ (\ because each s_i can be expressed in binary as s_i=1+2+4+8+\dots+2^{k-1}+c\quad(c can not be a power of 2) \)

​ \ (suppose that for each s_i, C < 2 ^ k, it is divided into k items, then the volume of each item is 2^{k_i}\times w \)

​ \ (\ therefore all items are divided into cnt items (cnt = \ sum {J = 1} ^ {n}{K J}), and the selection decision of each item is a 01 knapsack problem \)

const int N = 25000; // 2000 * 1000 * log2000
int n,m;
int f[N];
int v[N], w[N], s[N];

int main(){
    scanf("%d%d", &n, &m);
    int cnt = 0;        // Record the total number of items divided into
    for(int i = 1; i <= n; i++){
        int a, b, s;
        scanf("%d%d%d", &a, &b, &s);        // Volume mass number
        int k = 1;
        while(k <= s){      // s = 1 + 2 + 4 +...+ 2^k + c;
            cnt ++;
            v[cnt] = a * k;
            w[cnt] = b * k;
            s -= k;
            k *= 2;
        }
        if(s > 0){          // Residual constant c
            cnt ++;
            v[cnt] = a * s;
            w[cnt] = b * s;
        }
    }

    n = cnt;
    for(int i = 1; i <= n; i++){        // The circulation part is the same as 01 backpack
        for(int j = m; j >= v[i]; j--){
            f[j] = max(f[j], f[j - v[i]] + w[i]);
        }
    }

    printf("%d\n", f[m]);
    return 0;
}

Multiple knapsack \ (III \) (monotonic queue optimization)

Time complexity: \ (O(n\times m) \)

Data range:

​ \(0<N≤1000\)

​ \(0<V≤20000\)

​ \(0<v_i,w_i,s_i≤20000\)

Monotone queue optimization proof:

For details, please refer to this title

​ At the beginning of each enumeration of item groups, the backup array saves the information of \ (f[i-1] \), and then uses the one-dimensional state

​ Express \ (m \) as \ (k * V + J \) \ ((J = m \% v) \), \ (f [J + k * V] = max (f [J + k_i * V] + (k-k_i) * W) \ Quad while (k_i ≤ k) \)

​ Because you want to maintain the maximum value with a monotonic queue, the number of queue heads cannot change, so it is converted to:

​ \(f[j+k*v]=max(f[j+k_i*v-k_i*w])+k*w\)

The monotone queue maintenance problem has two important points:

	1. Maintain the number of queue elements. If you can't continue to join the queue, pop up the queue header element
	2. Maintain the monotonicity of the queue, that is: $Tail value >= dp[j + k*v] - k*w$
// Queue elements maintain the subscript of f []
int main(){
    int n, V;
    cin >> n >> V;
    while(n--){
        int v, w, s;
        cin >> v >> w >> s;
        memcpy(g, f, sizeof f);         // Backup the status of dp[i-1] [] with g []
        for(int j = 0; j < v; j++){
            int hh = 0, tt = -1;        // Resets the queue every time j is enumerated
            for(int k = j; k <= V; k += v){
                if(hh <= tt && q[hh] < k - v * s)       // Too many window elements
                    hh++;
                if(hh <= tt)        // Queue exists, update f[k], g[q[hh]] itself contains + (q[hh] - j)/v*w;
                    f[k] = max(f[k], g[q[hh]] + (k - q[hh]) / v * w);   
                    
                while(hh <= tt && g[q[tt]] - (q[tt] - j) / v * w < g[k] - (k - j) / v * w)
                    tt--;       // If the tail element is less than or equal to g[k]-(k-j)/v*w, the queue will be eliminated
                q[++tt] = k;    // Remember to join the team
            }
        }
    }
    cout << f[V] << endl;
    return 0;
}

Group Backpack

First enumerate the item group, then enumerate the volume from large to small, and enumerate the decisions within the group

General meaning:

There are \ (N \) groups of items and a backpack with a capacity of \ (V \). There are several items in each group. At most one item in the same group can be selected. The volume of each item is \ (V {ij} \) and the value is \ (w {ij} \), where \ (i \) is the group number and \ (j \) is the number in the group. Solve which items are loaded into the backpack, so that the total volume of the items does not exceed the capacity of the backpack, and the total value is the largest.

Status representation: \ (f[i][j] \) represents the top \ (I \) groups, and the volume does not exceed the maximum value of all schemes selected

Status calculation: \ (f[i][j] = max(f[i - 1][j], f[i - 1][j - v_{ik} + w_{ik}]) \)

for(int i = 1; i <= n; i++)	// Enumerate item groups
        for(int j = m; j >= 0; j--) // Enumeration volume
            for(int k = 0; k < s[i]; k++) // Enumerate intra group allocation schemes
                if(v[i][k] <= j)              // If you can put it in your backpack
                    f[j] = max(f[j], f[j - v[i][k]] + w[i][k]);

State machine model DP

  • Establish a state machine model, open more one-dimensional to represent the state, and the states can transfer to each other

State compression DP

Chessboard model

main points

  • For the problem of legal placement, from the point of view of the line, whether the current line can be placed is related to the placement state of the previous line. The problem can be solved by binary compression combined with bit operation

Example: artillery position

#define pb push_back
const int N = 110, M = 1 << 10;
int n, m, cnt[M], g[N], f[2][M][M];     
vector<int> state;

// In order to place in which behavior decision-making stage, there is not enough space, and the rolling array optimization only uses the i - 1 line state, which is taken directly&
// Status indication: f[i][j][k], which indicates the maximum number of places currently in line I, line I is j, and line i - 1 is K
// State calculation: F [I & 1] [b] [a] = max (f [I & 1] [b] [a], f [(I - 1) & 1] [a] [C] + CNT [b]);


bool check(int x){
    for(int i = 0; i < m; i++)
        if( x >> i & 1 && (x >> (i + 1) & 1 || x >> (i + 2) & 1))       // 1. 4 adjacent positions cannot have 1
            return false;
    return true;
}

int count(int x){
    int res = 0;
    for(int i = 0; i < m; i++)
        if(x >> i & 1) res++;
    return res;
}

int main(){
    cin >> n >> m;
    for(int i = 1; i <= n; i++)
        for(int j = 0; j < m; j++){
            char c;
            cin >> c;
            if(c == 'H')
                g[i] += 1 << j;
        }
    for(int i = 0; i < 1 << m; i++)
        if(check(i)){
            state.pb(i);
            cnt[i] = count(i);
        }
    for(int i = 1; i <= n + 2; i++){
        for(int j = 0; j < state.size(); j++)
            for(int k = 0; k < state.size(); k++)
                for(int u = 0; u < state.size(); u++){
                    int a = state[k], b = state[j], c = state[u];   // a: Line i-1, b: line i, c: Line i-2
                    if(a & b | b & c | a & c) continue;             // Three states cannot be transferred
                    if(a & g[i - 1] | b & g[i]) continue;           // It cannot be placed on the mountain
                    f[i & 1][b][a] = max(f[i & 1][b][a], f[(i - 1) & 1][a][c] + cnt[b]);
                }
    }
    cout << f[(n + 2) & 1][0][0] << endl;
    return 0;
}

Binary compression idea

Example: Hamilton path

Given a complete graph, find the shortest path through all points from 0 to n-1

const int N = 20, M = 1 << 20;
int f[M][N], w[N][N];
int n;

int hamilton(){
    memset(f, 0x3f, sizeof f);
    f[1][0] = 0;        // f[i][j] status is I, staying at the shortest path of point j
    for(int i = 1; i < 1 << n; i++)         // Enumerate all States
        for(int j = 0; j < n; j++)      // At which point in the enumeration state
            if(i >> j & 1)      // There's this point
                for(int k = 0; k < n; k++)  // Enumerate from which point to move to j 
                    if((i ^ 1 << j) >> k & 1)       // There is k this point
                        f[i][j] = min(f[i][j], f[i ^ 1 << j][k] + w[k][j]);     // state transition 
            
    return f[(1 << n) - 1][n - 1];      // After all the points, I finally stay at the point of n - 1
}

int main(){
    cin >> n;
    for(int i = 0; i < n; i++)
        for(int j = 0; j < n; j++)
            cin >> w[i][j];
    cout << hamilton() << endl;
    return 0;
}

Set model

To be added

Interval DP

main points

  • Taking the interval length as the decision-making stage

One dimensional interval DP

Some skills

  • Circular interval, you can copy the interval at the back, break the ring and form a chain

Example: Ring Stone merging

const int N = 410, INF = 0x3f3f3f3f;
int w[N], n, s[N];
int f[N][N], g[N][N];

// Two identical chains simulate rings
// Status representation: f[l][r] represents the minimum consolidation cost from interval l to interval r
// State transition: f[l][r] = min(f[l][r], f[l][k] + f[k + 1][r] + s[r] - s[l - 1]);


int main(){
    cin >> n;
    for(int i = 1; i <= n; i ++){
        cin >> w[i];
        w[i + n] = w[i];
    }
    memset(f, 0x3f, sizeof f);
    memset(g, -0x3f, sizeof g);
    for(int i = 1; i <= 2 * n; i++)
        s[i] = s[i - 1] + w[i];
    for(int len = 1; len <= n; len++)
        for(int l = 1; l + len - 1 <= 2 * n; l++){
            int r = l + len - 1;
            if(r > 2 * n) break;
            if(len == 1) f[l][r] = g[l][r] = 0;
            else
                for(int k = l; k <= r; k++){
                    f[l][r] = min(f[l][r], f[l][k] + f[k + 1][r] + s[r] - s[l - 1]);
                    g[l][r] = max(g[l][r], g[l][k] + g[k + 1][r] + s[r] - s[l - 1]);
                }
        }
    int max_ = -INF, min_ = INF;
    for(int i = 1; i <= n; i++){
        max_ = max(max_, g[i][i + n - 1]);      // For the interval with length n, the endpoint difference is len - 1
        min_ = min(min_, f[i][i + n - 1]);
    }
    cout << min_ << endl << max_;
    return 0;
}

Two dimensional interval DP

Example: chessboard segmentation

#include<iomanip>
const int N = 9, M = 16;
double f[N][N][N][N][M], X;
int n, g[N][N], s[N][N];

// Two dimensional interval DP
// The upper and lower vertices of the rectangle (yx1] [YK) represent the minimum variance, and the upper and lower vertices of the rectangle (yx1] [YK) represent the minimum variance
// State calculation: dividing line of circular cutting (horizontal and vertical and which part to continue cutting)

int get_sum(int x1, int y1, int x2, int y2){
    return s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1];
}

double get(int x1, int y1, int x2, int y2){
    double sum = get_sum(x1, y1, x2, y2) - X;
    return sum * sum / n;
}

double dp(int x1, int y1, int x2, int y2, int k){
    double& v = f[x1][y1][x2][y2][k];
    if(v >= 0) return v;
    if(k == 1) return get(x1, y1, x2, y2);
    v = 1e9;
    for(int i = x1; i < x2; i++){
        v = min(v, dp(x1, y1, i, y2, k - 1) + get(i + 1, y1, x2, y2)); 
        v = min(v, dp(i + 1, y1, x2, y2, k - 1) + get(x1, y1, i, y2));
    }
    for(int i = y1; i < y2; i++){
        v = min(v, dp(x1, y1, x2, i, k - 1) + get(x1, i + 1, x2, y2));
        v = min(v, dp(x1, i + 1, x2, y2, k - 1) + get(x1, y1, x2, i));
    }
    return v;
}

int main(){
    cin >> n;
    for(int i = 1; i <= 8; i++)
        for(int j = 1; j <= 8; j++){
            cin >> g[i][j];
            s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + g[i][j];
        }
    memset(f, -1, sizeof f);
    X = s[8][8] * 1.0 / n;
    cout << fixed << setprecision(3) << sqrt(dp(1, 1, 8, 8, n)) << endl;
    return 0;
}

Tree DP

main points

  • A set is represented by a point

Find the diameter of the tree

const int N = 1e4 + 10, M = 2 * N;
int n, idx, e[M], ne[M], h[N], w[M], ans;

// Status representation: a point represents the maximum length of all paths with this point as the highest point

void add(int a, int b, int c){
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}

int dfs(int u, int fa){     // Bring in the parent node number to ensure that the search is from top to bottom
    int d1 = 0, d2 = 0;     // The path from the largest and second largest to the leaf node
    for(int i = h[u]; ~i; i = ne[i]){
        int j = e[i];
        if(j == fa) continue;
        int d = dfs(j, u) + w[i];
        if(d >= d1) d2 = d1, d1 = d;
        else if(d > d2) d2 = d;
    }
    ans = max(ans, d1 + d2);
    return d1;              // Returns the longest path to the leaf node
}

int main(){
    cin >> n;
    memset(h, -1, sizeof h);
    for(int i = 0; i < n - 1; i++){
        int a, b, c;
        cin >> a >> b >> c;
        add(a, b, c), add(b, a, c);
    }
    dfs(1, -1);
    cout << ans << endl;
    return 0;
}

Change root DP

main points

  • Think about whether the parent node updates the child node or the child node updates the parent node information

Find the center of the tree by changing the root DP

  • The center of the tree: the farthest and closest point to other nodes in the tree
const int N = 1e4 + 10, M = 2 * N, INF = 0x3f3f3f3f;
int h[N], e[M], ne[M], w[M], idx;
int n, d1[N], d2[N], up[N], p1[N], p2[N];

// Find the length of each node up or down (all directions) to the edge, enumerate all paths of all points, and find the center of the tree

void add(int a, int b, int c){
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}

int dfs_up(int u, int fa){      // Search from top to bottom, and update the parent node information from the child node information
    d1[u] = d2[u] = -INF;
    for(int i = h[u]; ~i; i = ne[i]){
        int j = e[i];
        if(j == fa) continue;
        int d = dfs_up(j, u) + w[i];
        if(d >= d1[u]){
            d2[u] = d1[u], d1[u] = d;
            p2[u] = p1[u], p1[u] = j;
        }
        else if(d > d2[u])
            d2[u] = d, p2[u] = j;
    }
    if(d1[u] == -INF) d1[u] = d2[u] = 0;
    return d1[u];
}

void dfs_down(int u, int fa){       // It still searches from top to bottom, but updates the child node information from the parent node information
    for(int i = h[u]; ~i; i = ne[i]){
        int j = e[i];
        if(j == fa) continue;
        if(p1[u] == j) up[j] = w[i] + max(up[u], d2[u]);        // The longest downward path of the parent node passes through this point and is updated by the second longest path
        else up[j] = w[i] + max(up[u], d1[u]);                  // Otherwise, it is updated by the longest path
        dfs_down(j, u);
    }
}

int main(){
    cin >> n;
    memset(h, -1, sizeof h);
    for(int i = 0; i < n - 1; i ++){
        int a, b, c;
        cin >> a >> b >> c;
        add(a, b, c), add(b, a, c);
    }
    dfs_up(1, -1);
    dfs_down(1, -1);
    int res = INF;
    for(int i = 1; i <= n; i++) res = min(res, max(d1[i], up[i]));
    cout << res << endl;
    return 0;
}

Digital DP

main points

  • Think about digital DP in the form of tree (think about digital)
  • Query the number of legal numbers in \ ([l, r] \), and use the prefix and ideas dp(r) - dp(l - 1), dp(x) to find the legal numbers in \ ([1,x] \)

State representation

  • \(f[i][j] \) indicates the number of schemes with the highest bit of \ (j \) and \ (I \) bits
  • \(f[i][j][k] \), indicating that there are \ (I \) bits, the highest bit is \ (j \), and the result of the sum of all bits \ (\% p \) is the number of digits of \ (K \)
  • \(f[i][j][a][b] \), there are \ (I \) bits, the highest bit is \ (j \), digit sum \ (\% 7=a \), value \ (\% 7=b \), and the number of legal schemes

Cyclic iteration

Example: number of degrees

  • Find out how many numbers meet \ (1 \) in the base of \ (k \) and \ (B \)
int K, B, l, r;
int C[N][N];
// Think about digital DP in the form of tree (think about digital)
void init(){
    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];
}

int dp(int n){
    if(!n) return 0;        // Step 1: judge the boundary
    vector<int> v;
    while(n){               // Step 2: digital conversion
        v.push_back(n % B);
        n /= B;
    }
    int res = 0, last = 0;  // Part III: define the answer res and the number of 1 in B-ary last
    for(int i = v.size() - 1; i >= 0; i--){         // Enumerate each bit from high to low
        int x = v[i];
        if(x){      // If x is 0, there is no value for discussion. Continue to the next discussion
            res += C[i][K - last];      // If x > 0, this bit can be taken as 0, followed by K-last 1
            if(x > 1){                  // X > 1, this bit can also take 1, and the following i bit can take K-last-1 1
                if(K - last - 1 >= 0) res += C[i][K - last - 1];
                break;                  // Because the branch on the right is greater than 1, and the topic requires only 0 or 1, the right is illegal and will not continue the discussion
            }
            else{                       // x = 1. To record last + +, it means that 1 must be taken
                last++;
                if(last > K) break;
            }
        }
        if(!i && last == K)             // At the end, there is a last 1 on the digit. In special cases, res++
            res++;
    }
    return res;
}


int main(){
    cin >> l >> r >> K >> B;
    init();
    cout << dp(r) - dp(l - 1) << endl;

    return 0;
}

Memory search

Monotone queue optimization DP

  • Monotone queue can only save the information of points that have passed

Example: beacon fire transmission

using namespace std;
const int N = 2e5 + 10;
int f[N], w[N], n, m, q[N];

// Status indication: f[i] indicates that the first i - 1 beacon has been transmitted and the minimum cost of lighting the i-th beacon (this definition can have no aftereffect)
// State calculation: F [i] = min (f [J]) + w [i] (I - M < = J < I - 1), optimized with monotone queue

int main(){
    cin >> n >> m;
    for(int i = 1; i <= n; i++) cin >> w[i];
    int hh = 0, tt = 0;
    for(int i = 1; i <= n; i++){
        if(hh <= tt && q[hh] < i - m) hh++;
        f[i] = f[q[hh]]+ w[i];
        while(hh <= tt && f[q[tt]] >= f[i]) tt--;
        q[++tt] = i;
    }
    while(hh <= tt && q[hh] < n - m + 1) hh++;      // Tip: the answer to moving the queue back one bit is min(f[n - m + 1, n])
    cout << f[q[hh]] << endl;
    return 0;
}

2D sliding window

Output only one integer, which is \ (a) × b) all "\ (n) in the matrix × N \) the minimum value of the difference between the maximum integer and the minimum integer in the square area.

Example: an ideal square

const int N = 1010;
int w[N][N], row_min[N][N], row_max[N][N], q[N], n, k, m;

// Idea: discuss the minimum value and the maximum value. Similarly, first make a monotonic queue record for each line. Each point is the right end point of the window with the length of k, and record the minimum value of the window
//       The record results of rows are monotonically queued on the column. For k rows, the number at all points after k columns is the maximum value in the matrix of k * k

// Get the minimum value of a sliding window and b[] access the results
void get_min(int a[], int b[], int len){
    int hh = 0, tt = 0;
    for(int i = 1; i <= len; i++){
        if(hh <= tt && q[hh] <= i - k) hh++;
        while(hh <= tt && a[q[tt]] >= a[i]) tt--;
        q[++tt] = i;
        b[i] = a[q[hh]];
    }
}

// Get the maximum value of a sliding window and b[] access the results
void get_max(int a[], int b[], int len){
    int hh = 0, tt = 0;
    for(int i = 1; i <= len; i++){
        if(hh <= tt && q[hh] <= i - k) hh++;
        while(hh <= tt && a[q[tt]] <= a[i]) tt--;
        q[++tt] = i;
        b[i] = a[q[hh]];
    }
}

int main(){
    ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
    cin >> n >> m >> k;
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= m; j++)
            cin >> w[i][j];
    for(int i = 1; i <= n; i++){
        get_min(w[i], row_min[i], m);
        get_max(w[i], row_max[i], m);
    }
    int a[N], b[N], c[N], res = 1e9 + 10;
    for(int i = k; i <= m; i++){
        for(int j = 1; j <= n; j++) a[j] = row_min[j][i];
            get_min(a, b, n);
        for(int j = 1; j <= n; j++) a[j] = row_max[j][i];
            get_max(a, c, n);
        for(int j = k; j <= n; j++)
            res = min(res, c[j] - b[j]);
    }
    cout << res << endl;
    return 0;
}

Slope optimization DP

  • Slope optimization generally requires binary / CDQ / balance tree optimization

Basic convex hull optimization

Example: task arrangement

There are \ (n \) tasks arranged in a sequence, and the order shall not be changed. The time consumption of the \ (I \) task is \ (T_i \), and the cost coefficient is \ (C_i \)

Now you need to divide the \ (n \) tasks into several batches for processing

The segment head of each batch requires an additional \ (S \) time to start the machine. The completion time of each task is the end time of the batch.

The cost of completing a task is: the time from \ (0 \) to the end of the batch of the task \ (t \) multiplied by the cost coefficient of the task \ (C \)

  • Using the idea of cost advance
const int N = 3e5 + 10;
ll f[N], q[N], t[N], c[N], n, s;        // f[i] represents the minimum cost of performing task I

// 1. Slope = t[i] + s monotonically increasing
// 2. Abscissa c[i] monotonically increasing
// 3. f[i] on the intercept, find the point with the largest intercept and consider convex hull optimization. Finding the first point with a slope greater than k is the answer. Due to monotonicity, monotone queue optimization is adopted to O(n)

int main(){
    cin >> n >> s;
    for(int i = 1; i <= n; i++){
        cin >> t[i] >> c[i];
        t[i] += t[i - 1], c[i] += c[i - 1];
    }
    int hh = 0, tt = 0;
    q[0] = 0;
    for(int i = 1; i <= n; i++){            // Queue hh, tt bring q []
        while(hh < tt && (f[q[hh + 1]] - f[q[hh]]) < (t[i] + s) * (c[q[hh + 1]] - c[q[hh]])) hh++;
        int j = q[hh];
        f[i] = f[j] + t[i] * (c[i] - c[j]) + s * (c[n] - c[j]);
        while(hh < tt && (f[i] - f[q[tt - 1]]) * (c[q[tt]] - c[q[tt - 1]]) < (f[q[tt]] - f[q[tt - 1]]) * (c[i] - c[q[tt - 1]])) tt--;
        q[++tt] = i;
    }
    cout << f[n] << endl;
    return 0;
}

Miscellaneous items such as IO

Unbinding of cin and cout

ios::sync_with_stdio(false);
cin.tie(nullptr), cout.tie(nullptr);

cout precision control

cout << fixed << setprecision(5) << 1.2 << endl;//The output is 1.20000

C + + optimization

\(O2 \) optimization

#pragma GCC optimize(2)

\(O3 \) optimization

#pragma GCC optimize(3,"Ofast","inline")

Python 3 multiline input

while True:
    try:
        m = int(input().strip())
    except EOFError:
        break
`

Topics: Algorithm