Modèle De Base D'algorithme

Contenus

Théorie des graphes

Programmation dynamique

Algorithmes de base

1 Tri

1.1 Modèle d’algorithme de tri rapide

—— Problème modèle AcWing 785. Tri rapide

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

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

1.2 Modèle d’algorithme de tri par fusion

—— Problème modèle AcWing 787. Tri par fusion

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

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

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

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

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

2 Recherche binaire

2.1 Modèle d’algorithme de recherche binaire pour les entiers

—— Problème modèle AcWing 789. Plage de nombres

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
bool check(int x) {/* ... */} // Vérifie si x satisfait une certaine propriété

// Utilisé lorsque l'intervalle [l, r] est divisé en [l, mid] et [mid + 1, r] :
int bsearch_1(int l, int r)
{
    while (l < r)
    {
        int mid = l + r >> 1;
        if (check(mid)) r = mid;    // check() vérifie si mid satisfait la propriété
        else l = mid + 1;
    }
    return l;
}
// Utilisé lorsque l'intervalle [l, r] est divisé en [l, mid - 1] et [mid, r] :
int bsearch_2(int l, int r)
{
    while (l < r)
    {
        int mid = l + r + 1 >> 1;
        if (check(mid)) l = mid;
        else r = mid - 1;
    }
    return l;
}

2.2 Modèle d’algorithme de recherche binaire pour les nombres à virgule flottante

—— Problème modèle AcWing 790. Racine cubique d’un nombre

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
bool check(double x) {/* ... */} // Vérifie si x satisfait une certaine propriété

double bsearch_3(double l, double r)
{
    const double eps = 1e-6;   // eps représente la précision, dépend des exigences de précision du problème
    while (r - l > eps)
    {
        double mid = (l + r) / 2;
        if (check(mid)) r = mid;
        else l = mid;
    }
    return l;
}

3 Calcul de haute précision

3.1 Addition de haute précision

—— Problème modèle AcWing 791. Addition de haute précision

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

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

    if (t) C.push_back(t);
    return C;
}

3.2 Soustraction de haute précision

—— Problème modèle AcWing 792. Soustraction de haute précision

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

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

3.3 Multiplication de haute précision par basse précision

—— Problème modèle AcWing 793. Multiplication de haute précision

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

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

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

    return C;
}

3.4 Division de haute précision par basse précision

—— Problème modèle AcWing 794. Division de haute précision

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

4 Préfixe et différence

Préfixe : Calculer rapidement la somme d’un segment Différence : Ajouter rapidement +c à un segment, inverse du préfixe

4.1 Somme de préfixe unidimensionnelle

—— Problème modèle AcWing 795. Somme de préfixe

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

4.2 Somme de préfixe bidimensionnelle

—— Problème modèle AcWing 796. Somme de sous-matrice

1
2
3
S[i, j] = Somme de tous les éléments dans la partie supérieure gauche de la case i, j
La somme de la sous-matrice ayant pour coin supérieur gauche (x1, y1) et pour coin inférieur droit (x2, y2) est :
S[x2, y2] - S[x1 - 1, y2] - S[x2, y1 - 1] + S[x1 - 1, y1 - 1]

4.3 Différence unidimensionnelle

—— Problème modèle AcWing 797. Différence Ajouter c à chaque nombre dans l’intervalle [l, r] : B[l] += c, B[r + 1] -= c

4.4 Différence bidimensionnelle

—— Problème modèle AcWing 798. Matrice de différence

1
2
Ajouter c à tous les éléments de la sous-matrice ayant pour coin supérieur gauche (x1, y1) et pour coin inférieur droit (x2, y2) :
S[x1, y1] += c, S[x2 + 1, y1] -= c, S[x1, y2 + 1] -= c, S[x2 + 1, y2 + 1] += c

5 Opérations de bits

—— Problème modèle AcWing 801. Nombre de 1 dans un binaire

1
2
Trouver le k-ième chiffre de n : n >> k & 1
Retourner le dernier 1 de n : lowbit(n) = n & -n

6 Algorithme à deux pointeurs

—— Problème modèle AcWing 799. Sous-séquence la plus longue sans répétition, AcWing 800. Somme cible des éléments du tableau

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

    // Logique spécifique au problème
}
Classification des problèmes courants :
    (1) Pour une séquence, utiliser deux pointeurs pour maintenir un intervalle
    (2) Pour deux séquences, maintenir un certain ordre, par exemple l'opération de fusion de deux séquences triées dans le tri par fusion

7 Discrétisation

—— Problème modèle AcWing 802. Somme d’intervalles

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
vector<int> alls; // Stocker toutes les valeurs à discrétiser
sort(alls.begin(), alls.end()); // Trier toutes les valeurs
alls.erase(unique(alls.begin(), alls.end()), alls.end());   // Supprimer les éléments dupliqués

// Recherche binaire pour trouver la valeur discrétisée de x
int find(int x) // Trouver la première position supérieure ou égale à x
{
    int l = 0, r = alls.size() - 1;
    while (l < r)
    {
        int mid = l + r >> 1;
        if (alls[mid] >= x) r = mid;
        else l = mid + 1;
    }
    return r + 1; // Mappé à 1, 2, ...n
}

8 Fusion d’intervalles

—— Problème modèle AcWing 803. Fusion d’intervalles

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// Fusionner tous les intervalles qui se chevauchent
void merge(vector<PII> &segs)
{
    vector<PII> res;

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

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

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

    segs = res;
}

Structures de données

1 Listes chaînées et listes d’adjacence : stockage d’arbres et de graphes

1.1 Liste chaînée simple

—— Problème modèle AcWing 826. Liste chaînée simple

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// head stocke la tête de la liste chaînée, e[] stocke les valeurs des nœuds, ne[] stocke les pointeurs next des nœuds, idx indique quel nœud est actuellement utilisé
int head, e[N], ne[N], idx;

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

// Insérer un nombre a en tête de la liste chaînée
void insert(int a)
{
    e[idx] = a, ne[idx] = head, head = idx ++ ;
}

// Supprimer le nœud de tête, il faut s'assurer que le nœud de tête existe
void remove()
{
    head = ne[head];
}

1.2 Liste chaînée double

—— Problème modèle AcWing 827. Liste chaînée double

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// e[] représente la valeur des nœuds, l[] représente le pointeur gauche des nœuds, r[] représente le pointeur droit des nœuds, idx indique quel nœud est actuellement utilisé
int e[N], l[N], r[N], idx;

// Initialisation
void init()
{
    //0 est le point d'extrémité gauche, 1 est le point d'extrémité droit
    r[0] = 1, l[1] = 0;
    idx = 2;
}

// Insérer un nombre x à droite du nœud a
void insert(int a, int x)
{
    e[idx] = x;
    l[idx] = a, r[idx] = r[a];
    l[r[a]] = idx, r[a] = idx ++ ;
}

// Supprimer le nœud a
void remove(int a)
{
    l[r[a]] = l[a];
    r[l[a]] = r[a];
}

2 Piles et files d’attente : file d’attente monotone, pile monotone

2.1 Pile

—— Problème modèle AcWing 828. Simulation de pile

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// tt représente le sommet de la pile
int stk[N], tt = 0;

// Insérer un nombre au sommet de la pile
stk[ ++ tt] = x;

// Retirer un nombre du sommet de la pile
tt -- ;

// Valeur au sommet de la pile
stk[tt];

// Vérifier si la pile est vide, si tt > 0, alors elle n'est pas vide
if (tt > 0)
{

}

2.2 File d’attente

—— Problème modèle AcWing 829. Simulation de file d’attente

  1. File d’attente ordinaire :
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// hh représente la tête de la file d'attente, tt représente la queue de la file d'attente
int q[N], hh = 0, tt = -1;

// Insérer un nombre à la queue de la file d'attente
q[ ++ tt] = x;

// Retirer un nombre de la tête de la file d'attente
hh ++ ;

// Valeur à la tête de la file d'attente
q[hh];

// Vérifier si la file d'attente est vide, si hh <= tt, alors elle n'est pas vide
if (hh <= tt)
{
}
  1. File d’attente circulaire
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// hh représente la tête de la file d'attente, tt représente la position après la queue de la file d'attente
int q[N], hh = 0, tt = 0;

// Insérer un nombre à la queue de la file d'attente
q[tt ++ ] = x;
if (tt == N) tt = 0;

// Retirer un nombre de la tête de la file d'attente
hh ++ ;
if (hh == N) hh = 0;

// Valeur à la tête de la file d'attente
q[hh];

// Vérifier si la file d'attente est vide, si hh != tt, alors elle n'est pas vide
if (hh != tt)
{
}

2.3 Pile monotone

—— Problème modèle AcWing 830. Pile monotone Modèle courant : trouver pour chaque nombre le plus proche à gauche qui est plus grand/petit que lui

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

2.4 File d’attente monotone

—— Problème modèle AcWing 154. Fenêtre coulissante Modèle courant : trouver la valeur maximale/minimale dans une fenêtre coulissante

1
2
3
4
5
6
7
int hh = 0, tt = -1;
for (int i = 0; i < n; i ++ )
{
    while (hh <= tt && check_out(q[hh])) hh ++ ;  // Vérifier si la tête de file est hors de la fenêtre
    while (hh <= tt && check(q[tt], i)) tt -- ;
    q[ ++ tt] = i;
}

3 KMP

—— Problème modèle AcWing 831. Chaîne KMP

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// s[] est le texte long, p[] est la chaîne modèle, n est la longueur de s, m est la longueur de p
Calculer le tableau Next de la chaîne modèle :
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;
}

// Appariement
for (int i = 1, j = 0; i <= n; i ++ )
{
    while (j && s[i] != p[j + 1]) j = ne[j];
    if (s[i] == p[j + 1]) j ++ ;
    if (j == m)
    {
        j = ne[j];
        // Logique après un appariement réussi
    }
}// La fonction de bibliothèque C strstr() est ainsi

4 Arbre Trie

—— Problème modèle AcWing 835. Statistiques de chaînes Trie

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
int son[N][26], cnt[N], idx;
// Le point 0 est à la fois le nœud racine et le nœud vide
// son[][] stocke les fils de chaque nœud de l'arbre
// cnt[] stocke le nombre de mots se terminant à chaque nœud

// Insérer une chaîne
void insert(char *str)
{
    int p = 0;
    for (int i = 0; str[i]; i ++ )
    {
        int u = str[i] - 'a';
        if (!son[p][u]) son[p][u] = ++ idx;
        p = son[p][u];
    }
    cnt[p] ++ ;
}

// Rechercher le nombre d'occurrences d'une chaîne
int query(char *str)
{
    int p = 0;
    for (int i = 0; str[i]; i ++ )
    {
        int u = str[i] - 'a';
        if (!son[p][u]) return 0;
        p = son[p][u];
    }
    return cnt[p];
}// On peut utiliser map<string,int>, mais le temps est plus de deux fois supérieur

5 Ensemble disjoint

—— Problème modèle AcWing 836. Fusion d’ensembles, AcWing 837. Nombre de points dans un bloc connexe (1) Ensemble disjoint naïf :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
    int p[N]; // Stocke le nœud ancêtre de chaque point

    // Retourne le nœud ancêtre de x
    int find(int x)
    {
        if (p[x] != x) p[x] = find(p[x]);
        return p[x];
    }

    // Initialisation, supposons que les numéros de nœud sont de 1 à n
    for (int i = 1; i <= n; i ++ ) p[i] = i;

    // Fusionner les deux ensembles contenant a et b :
    p[find(a)] = find(b);

(2) Ensemble disjoint maintenant la taille :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
    int p[N], size[N];
    //p[] stocke le nœud ancêtre de chaque point, size[] n'a de sens que pour les nœuds ancêtres, indique le nombre de points dans l'ensemble du nœud ancêtre

    // Retourne le nœud ancêtre de x
    int find(int x)
    {
        if (p[x] != x) p[x] = find(p[x]);
        return p[x];
    }

    // Initialisation, supposons que les numéros de nœud sont de 1 à n
    for (int i = 1; i <= n; i ++ )
    {
        p[i] = i;
        size[i] = 1;
    }

    // Fusionner les deux ensembles contenant a et b :
    size[find(b)] += size[find(a)];
    p[find(a)] = find(b);

(3) Ensemble disjoint maintenant la distance au nœud ancêtre :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
    int p[N], d[N];
    //p[] stocke le nœud ancêtre de chaque point, d[x] stocke la distance de x à p[x]

    // Retourne le nœud ancêtre de x
    int find(int x)
    {
        if (p[x] != x)
        {
            int u = find(p[x]);
            d[x] += d[p[x]];
            p[x] = u;
        }
        return p[x];
    }

    // Initialisation, supposons que les numéros de nœud sont de 1 à n
    for (int i = 1; i <= n; i ++ )
    {
        p[i] = i;
        d[i] = 0;
    }

    // Fusionner les deux ensembles contenant a et b :
    p[find(a)] = find(b);
    d[find(a)] = distance; // Selon le problème spécifique, initialiser le décalage de find(a)

6 Tas

—— Problème modèle AcWing 838. Tri par tas, AcWing 839. Simulation de tas

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// h[N] stocke les valeurs dans le tas, h[1] est le sommet du tas, le fils gauche de x est 2x, le fils droit est 2x + 1
// ph[k] stocke la position dans le tas du k-ième point inséré
// hp[k] stocke le k-ième point dans le tas est le k-ième inséré
int h[N], ph[N], hp[N], size;

// Échanger deux points et leur relation de mappage
void heap_swap(int a, int b)
{
    swap(ph[hp[a]],ph[hp[b]]);
    swap(hp[a], hp[b]);
    swap(h[a], h[b]);
}

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

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

// O(n) construction de tas
for (int i = n / 2; i; i -- ) down(i);

7 Hachage

7.1 Hachage général

—— Problème modèle AcWing 840. Simulation de table de hachage

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

    // Insérer un nombre dans la table de hachage
    void insert(int x)
    {
        int k = (x % N + N) % N;
        e[idx] = x;
        ne[idx] = h[k];
        h[k] = idx ++ ;
    }

    // Rechercher si un nombre existe dans la table de hachage
    bool find(int x)
    {
        int k = (x % N + N) % N;
        for (int i = h[k]; i != -1; i = ne[i])
            if (e[i] == x)
                return true;

        return false;
    }

(2) Méthode de recherche ouverte
    int h[N];

    // Si x est dans la table de hachage, retourne l'indice de x ; si x n'est pas dans la table de hachage, retourne la position où x devrait être inséré
    int find(int x)
    {
        int t = (x % N + N) % N;
        while (h[t] != null && h[t] != x)
        {
            t ++ ;
            if (t == N) t = 0;
        }
        return t;
    }

7.2 Hachage de chaînes

—— Problème modèle AcWing 841. Hachage de chaînes Idée principale : considérer la chaîne comme un nombre en base P, la valeur expérimentale de P est 131 ou 13331, la probabilité de collision avec ces deux valeurs est faible Petite astuce : utiliser 2^64 comme nombre de modulo, ainsi on peut directement utiliser unsigned long long pour stocker, le résultat du débordement est le résultat du modulo

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
typedef unsigned long long ULL;
ULL h[N], p[N]; // h[k] stocke la valeur de hachage des k premiers caractères de la chaîne, p[k] stocke P^k mod 2^64

// Initialisation
p[0] = 1;
for (int i = 1; i <= n; i ++ )
{
    h[i] = h[i - 1] * P + str[i];
    p[i] = p[i - 1] * P;
}

// Calculer la valeur de hachage de la sous-chaîne str[l ~ r]
ULL get(int l, int r)
{
    return h[r] - h[l - 1] * p[r - l + 1];
}

7.3 Introduction à la STL C++

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
vector, tableau de longueur variable, idée de doublement
    size()  Retourne le nombre dléments
    empty()  Retourne si c'est vide
    clear()  Effacer
    front()/back()
    push_back()/pop_back()
    begin()/end()
    []
    Supporte les opérations de comparaison, par ordre lexicographique

pair<int, int>
    first, Premier élément
    second, Deuxième élément
    Supporte les opérations de comparaison, avec first comme premier critère, second comme deuxième critère (ordre lexicographique)

string, chaîne de caractères
    size()/length()  Retourne la longueur de la chaîne
    empty()
    clear()
    substr(indice de départ, (longueur de la sous-chaîne))  Retourne la sous-chaîne
    c_str()  Retourne l'adresse de début du tableau de caractères  se trouve la chaîne

queue, file d'attente
    size()
    empty()
    push()  Insérer un élément à la queue
    front()  Retourne llément en tête
    back()  Retourne llément en queue
    pop()  Retirer llément en tête

priority_queue, file d'attente prioritaire, par défaut c'est un tas max
    size()
    empty()
    push()  Insérer un élément
    top()  Retourne llément au sommet du tas
    pop()  Retirer llément au sommet du tas
    Définir comme un tas min : priority_queue<int, vector<int>, greater<int>> q;

stack, pile
    size()
    empty()
    push()  Insérer un élément au sommet
    top()  Retourne llément au sommet
    pop()  Retirer llément au sommet

deque, file d'attente double
    size()
    empty()
    clear()
    front()/back()
    push_back()/pop_back()
    push_front()/pop_front()
    begin()/end()
    []

set, map, multiset, multimap, basé sur un arbre binaire équilibré (arbre rouge-noir), maintient dynamiquement une séquence ordonnée
    size()
    empty()
    clear()
    begin()/end()
    ++, -- Retourne le prédécesseur et le successeur, complexité temporelle O(logn)

    set/multiset
        insert()  Insérer un nombre
        find()  Rechercher un nombre
        count()  Retourne le nombre d'occurrences d'un nombre
        erase()
            (1) L'entrée est un nombre x, supprime tous les x   O(k + logn)
            (2) L'entrée est un itérateur, supprime cet itérateur
        lower_bound()/upper_bound()
            lower_bound(x)  Retourne l'itérateur du plus petit nombre supérieur ou égal à x
            upper_bound(x)  Retourne l'itérateur du plus petit nombre supérieur à x
    map/multimap
        insert()  Llément inséré est une paire
        erase()  L'entrée est une paire ou un itérateur
        find()
        []  Attention, multimap ne supporte pas cette opération. La complexité temporelle est O(logn)
        lower_bound()/upper_bound()

unordered_set, unordered_map, unordered_multiset, unordered_multimap, table de hachage
    Similaire à ce qui précède, la complexité temporelle pour ajouter, supprimer, modifier et rechercher est O(1)
    Ne supporte pas lower_bound()/upper_bound(), ++, -- pour les itérateurs

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

    count()  Retourne le nombre de 1

    any()  Vérifie s'il y a au moins un 1
    none()  Vérifie si tout est 0

    set()  Mettre tous les bits à 1
    set(k, v)  Mettre le k-ième bit à v
    reset()  Mettre tous les bits à 0
    flip()  Équivalent à ~
    flip(k) Inverser le k-ième bit

Recherche et théorie des graphes

1 DFS et BFS

1.1 Stockage d’arbres et de graphes

Un arbre est un type spécial de graphe, et sa méthode de stockage est la même que celle des graphes. Pour une arête ab dans un graphe non orienté, stocker deux arêtes orientées a->b, b->a. Ainsi, nous pouvons seulement considérer le stockage des graphes orientés. (1) Matrice d’adjacence : g[a][b] stocke l’arête a->b (2) Liste d’adjacence :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// Pour chaque point k, ouvrir une liste chaînée simple, stocker tous les points atteignables à partir de k. h[k] stocke le nœud de tête de cette liste chaînée
int h[N], e[N], ne[N], idx;

// Ajouter une arête a->b
void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}

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

1.2 Parcours d’arbres et de graphes

Complexité temporelle O(n+m), n représente le nombre de points, m représente le nombre d’arêtes (1) Parcours en profondeur —— Problème modèle AcWing 846. Centre d’un arbre

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
int dfs(int u)
{
    st[u] = true; // st[u] représente que le point u a déjà été parcouru

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

(2) Parcours en largeur —— Problème modèle AcWing 847. Niveau des points dans un graphe

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
queue<int> q;
st[1] = true; // Représente que le point 1 a déjà été parcouru
q.push(1);

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

    for (int i = h[t]; i != -1; i = ne[i])
    {
        int j = e[i];
        if (!st[j])
        {
            st[j] = true; // Représente que le point j a déjà été parcouru
            q.push(j);
        }
    }
}

2 Parcours d’arbres et de graphes Tri topologique

—— Problème modèle AcWing 848. Séquence topologique d’un graphe orienté Complexité temporelle O(n+m), n représente le nombre de points, m représente le nombre d’arêtes

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

    // d[i] stocke le degré entrant du point i
    for (int i = 1; i <= n; i ++ )
        if (!d[i])
            q[ ++ tt] = i;

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

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

    // Si tous les points ont été ajoutés à la file, cela signifie qu'il existe une séquence topologique ; sinon, il n'en existe pas.
    return tt == n - 1;
}

3 Chemin le plus court

3.1 Algorithme Dijkstra naïf

—— Problème modèle AcWing 849. Dijkstra pour le chemin le plus court I Complexité temporelle O(n2+m), n représente le nombre de points, m représente le nombre d’arêtes

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
int g[N][N];  // Stocke chaque arête
int dist[N];  // Stocke la distance la plus courte du point 1 à chaque point
bool st[N];   // Stocke si la distance la plus courte de chaque point a déjà été déterminée

// Calculer la distance la plus courte du point 1 au point n, si elle n'existe pas, retourner -1
int dijkstra()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;

    for (int i = 0; i < n - 1; i ++ )
    {
        int t = -1;     // Parmi les points dont la distance la plus courte n'est pas encore déterminée, trouver celui avec la plus petite distance
        for (int j = 1; j <= n; j ++ )
            if (!st[j] && (t == -1 || dist[t] > dist[j]))
                t = j;

        // Utiliser t pour mettre à jour la distance des autres points
        for (int j = 1; j <= n; j ++ )
            dist[j] = min(dist[j], dist[t] + g[t][j]);

        st[t] = true;
    }

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

3.2 Version optimisée par tas de Dijkstra

—— Problème modèle AcWing 850. Dijkstra pour le chemin le plus court II Complexité temporelle O(mlogn), n représente le nombre de points, m représente le nombre d’arêtes

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

int n;      // Nombre de points
int h[N], w[N], e[N], ne[N], idx;       // Liste d'adjacence stockant toutes les arêtes
int dist[N];        // Stocke la distance de tous les points au point 1
bool st[N];     // Stocke si la distance la plus courte de chaque point a déjà été déterminée

// Calculer la distance la plus courte du point 1 au point n, si elle n'existe pas, retourner -1
int dijkstra()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;
    priority_queue<PII, vector<PII>, greater<PII>> heap;
    heap.push({0, 1});      // first stocke la distance, second stocke le numéro du point

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

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

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

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

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

3.3 Algorithme de Bellman-Ford

—— Problème modèle AcWing 853. Chemin le plus court avec limite sur le nombre d’arêtes Complexité temporelle O(nm), n représente le nombre de points, m représente le nombre d’arêtes Attention, dans le problème modèle, il est nécessaire de modifier légèrement le modèle ci-dessous, en ajoutant un tableau de sauvegarde, voir le problème modèle pour plus de détails.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
int n, m;       // n représente le nombre de points, m représente le nombre d'arêtes
int dist[N];        // dist[x] stocke la distance la plus courte de 1 à x

struct Edge     // Arête, a représente le point de départ, b représente le point d'arrivée, w représente le poids de l'arête
{
    int a, b, w;
}edges[M];

// Calculer la distance la plus courte de 1 à n, si elle n'existe pas, retourner -1.
int bellman_ford()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;

    // Si la n-ième itération continue de relâcher l'inégalité triangulaire, cela signifie qu'il existe un chemin de longueur n+1, selon le principe des tiroirs, il y a au moins deux points identiques sur le chemin, indiquant qu'il existe un cycle de poids négatif dans le graphe.
    for (int i = 0; i < n; i ++ )
    {
        for (int j = 0; j < m; j ++ )
        {
            int a = edges[j].a, b = edges[j].b, w = edges[j].w;
            if (dist[b] > dist[a] + w)
                dist[b] = dist[a] + w;
        }
    }

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

3.4 Algorithme spfa (version optimisée par file d’attente de Bellman-Ford)

—— Problème modèle AcWing 851. spfa pour le chemin le plus court Complexité temporelle moyenne O(m), pire O(nm), n représente le nombre de points, m représente le nombre d’arêtes

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
int n;      // Nombre total de points
int h[N], w[N], e[N], ne[N], idx;       // Liste d'adjacence stockant toutes les arêtes
int dist[N];        // Stocke la distance la plus courte de chaque point au point 1
bool st[N];     // Stocke si chaque point est dans la file d'attente

// Calculer la distance la plus courte du point 1 au point n, si elle n'existe pas, retourner -1
int spfa()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;

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

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

        st[t] = false;

        for (int i = h[t]; i != -1; i = ne[i])
        {
            int j = e[i];
            if (dist[j] > dist[t] + w[i])
            {
                dist[j] = dist[t] + w[i];
                if (!st[j])     // Si j est déjà dans la file d'attente, ne pas l'insérer à nouveau
                {
                    q.push(j);
                    st[j] = true;
                }
            }
        }
    }

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

3.5 spfa pour déterminer s’il existe un cycle négatif dans le graphe

—— Problème modèle AcWing 852. spfa pour déterminer le cycle négatif Complexité temporelle O(nm), n représente le nombre de points, m représente le nombre d’arêtes

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
int n;      // Nombre total de points
int h[N], w[N], e[N], ne[N], idx;       // Liste d'adjacence stockant toutes les arêtes
int dist[N], cnt[N];        // dist[x] stocke la distance la plus courte de 1 à x, cnt[x] stocke le nombre de points traversés dans le chemin le plus court de 1 à x
bool st[N];     // Stocke si chaque point est dans la file d'attente

// Si un cycle négatif existe, retourner true, sinon retourner false.
bool spfa()
{
    // Pas besoin d'initialiser le tableau dist
    // Principe : si un chemin le plus court contient n points (à l'exception de lui-même), alors en ajoutant lui-même, il y a n+1 points, selon le principe des tiroirs, il y a au moins deux points identiques, donc il existe un cycle.

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

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

        st[t] = false;

        for (int i = h[t]; i != -1; i = ne[i])
        {
            int j = e[i];
            if (dist[j] > dist[t] + w[i])
            {
                dist[j] = dist[t] + w[i];
                cnt[j] = cnt[t] + 1;
                if (cnt[j] >= n) return true;       // Si le chemin le plus court de 1 à x contient au moins n points (à l'exception de lui-même), cela signifie qu'il existe un cycle
                if (!st[j])
                {
                    q.push(j);
                    st[j] = true;
                }
            }
        }
    }

    return false;
}

3.6 Algorithme de Floyd

—— Problème modèle AcWing 854. Floyd pour le chemin le plus court Complexité temporelle O(n3), n représente le nombre de points

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

// Après l'algorithme, d[a][b] représente la distance la plus courte de a à b
void floyd()
{
    for (int k = 1; k <= n; k ++ )
        for (int i = 1; i <= n; i ++ )
            for (int j = 1; j <= n; j ++ )
                d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
}

4 Arbre couvrant minimal

4.1 Algorithme Prim naïf

—— Problème modèle AcWing 858. Algorithme Prim pour l’arbre couvrant minimal Complexité temporelle O(n2+m), n représente le nombre de points, m représente le nombre d’arêtes

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
int n;      // n représente le nombre de points
int g[N][N];        // Matrice d'adjacence, stocke toutes les arêtes
int dist[N];        // Stocke la distance des autres points à l'arbre couvrant minimal actuel
bool st[N];     // Stocke si chaque point est déjà dans l'arbre couvrant minimal


// Si le graphe n'est pas connexe, retourner INF (valeur 0x3f3f3f3f), sinon retourner la somme des poids des arêtes de l'arbre couvrant minimal
int prim()
{
    memset(dist, 0x3f, sizeof dist);

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

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

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

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

    return res;
}

4.2 Algorithme de Kruskal

—— Problème modèle AcWing 859. Algorithme de Kruskal pour l’arbre couvrant minimal Complexité temporelle O(mlogm), n représente le nombre de points, m représente le nombre d’arêtes

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
int n, m;       // n est le nombre de points, m est le nombre d'arêtes
int p[N];       // Tableau des nœuds parents de l'ensemble disjoint

struct Edge     // Stocke les arêtes
{
    int a, b, w;

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

int find(int x)     // Opération principale de l'ensemble disjoint
{
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}

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

    for (int i = 1; i <= n; i ++ ) p[i] = i;    // Initialisation de l'ensemble disjoint

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

        a = find(a), b = find(b);
        if (a != b)     // Si deux blocs connexes ne sont pas connectés, les fusionner
        {
            p[a] = b;
            res += w;
            cnt ++ ;
        }
    }

    if (cnt < n - 1) return INF;
    return res;
}

5 Graphe biparti

5.1 Méthode de coloration pour déterminer un graphe biparti

—— Problème modèle AcWing 860. Méthode de coloration pour déterminer un graphe biparti Complexité temporelle O(n+m), n représente le nombre de points, m représente le nombre d’arêtes

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
int n;      // n représente le nombre de points
int h[N], e[M], ne[M], idx;     // Liste d'adjacence stockant le graphe
int color[N];       // Représente la couleur de chaque point, -1 signifie non coloré, 0 signifie blanc, 1 signifie noir

// Paramètres : u représente le nœud actuel, c représente la couleur actuelle du point
bool dfs(int u, int c)
{
    color[u] = c;
    for (int i = h[u]; i != -1; i = ne[i])
    {
        int j = e[i];
        if (color[j] == -1)
        {
            if (!dfs(j, !c)) return false;
        }
        else if (color[j] == c) return false;
    }

    return true;
}

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

5.2 Algorithme hongrois

—— Problème modèle AcWing 861. Appariement maximal dans un graphe biparti Complexité temporelle O(nm), n représente le nombre de points, m représente le nombre d’arêtes

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
int n1, n2;     // n1 représente le nombre de points dans le premier ensemble, n2 représente le nombre de points dans le deuxième ensemble
int h[N], e[M], ne[M], idx;     // Liste d'adjacence stockant toutes les arêtes, l'algorithme hongrois n'utilisera que les arêtes allant du premier ensemble au deuxième ensemble, donc il suffit de stocker un seul sens d'arête
int match[N];       // Stocke le point du premier ensemble actuellement apparié à chaque point du deuxième ensemble
bool st[N];     // Représente si chaque point du deuxième ensemble a déjà été parcouru

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

    return false;
}

// Calculer le nombre maximal d'appariements, énumérer successivement si chaque point du premier ensemble peut être apparié à un point du deuxième ensemble
int res = 0;
for (int i = 1; i <= n1; i ++ )
{
    memset(st, false, sizeof st);
    if (find(i)) res ++ ;
}

Connaissances mathématiques

1 Nombres premiers

1.1 Méthode de division pour déterminer si un nombre est premier

—— Problème modèle AcWing 866. Méthode de division pour déterminer si un nombre est premier

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

1.2 Méthode de division pour décomposer en facteurs premiers

—— Problème modèle AcWing 867. Décomposition en facteurs premiers

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

1.3 Crible naïf pour trouver les nombres premiers

—— Problème modèle AcWing 868. Crible pour les nombres premiers

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
int primes[N], cnt;     // primes[] stocke tous les nombres premiers
bool st[N];         // st[x] stocke si x a été criblé

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

1.4 Crible linéaire pour trouver les nombres premiers

—— Problème modèle AcWing 868. Crible pour les nombres premiers

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
int primes[N], cnt;     // primes[] stocke tous les nombres premiers
bool st[N];         // st[x] stocke si x a été criblé

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

2 Diviseurs

2.1 Méthode de division pour trouver tous les diviseurs

—— Problème modèle AcWing 869. Méthode de division pour trouver les diviseurs

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

2.2 Nombre de diviseurs et somme des diviseurs

—— Problème modèle AcWing 870. Nombre de diviseurs, AcWing 871. Somme des diviseurs

1
2
3
Si N = p1^c1 * p2^c2 * ... *pk^ck
Nombre de diviseurs : (c1 + 1) * (c2 + 1) * ... * (ck + 1)
Somme des diviseurs : (p1^0 + p1^1 + ... + p1^c1) * ... * (pk^0 + pk^1 + ... + pk^ck)

2.3 Algorithme d’Euclide

—— Problème modèle AcWing 872. Plus grand commun diviseur

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

3 Fonction d’Euler

3.1 Calculer la fonction d’Euler

—— Problème modèle AcWing 873. Fonction d’Euler

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

    return res;
}

3.2 Crible pour calculer la fonction d’Euler

—— Problème modèle AcWing 874. Crible pour calculer la fonction d’Euler

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
int primes[N], cnt;     // primes[] stocke tous les nombres premiers
int euler[N];           // Stocke la fonction d'Euler de chaque nombre
bool st[N];         // st[x] stocke si x a été criblé


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

4 Puissance rapide

—— Problème modèle AcWing 875. Puissance rapide Calculer m^k mod p, complexité temporelle O(logk).

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

5 Algorithme d’Euclide étendu

—— Problème modèle AcWing 877. Algorithme d’Euclide étendu

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

6 Élimination de Gauss

—— Problème modèle AcWing 883. Élimination de Gauss pour résoudre un système d’équations linéaires

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// a[N][N] est la matrice augmentée
int gauss()
{
    int c, r;
    for (c = 0, r = 0; c < n; c ++ )
    {
        int t = r;
        for (int i = r; i < n; i ++ )   // Trouver la ligne avec la plus grande valeur absolue
            if (fabs(a[i][c]) > fabs(a[t][c]))
                t = i;

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

        for (int i = c; i <= n; i ++ ) swap(a[t][i], a[r][i]);      // Échanger la ligne avec la plus grande valeur absolue avec la ligne du haut
        for (int i = n; i >= c; i -- ) a[r][i] /= a[r][c];      // Transformer le premier élément de la ligne actuelle en 1
        for (int i = r + 1; i < n; i ++ )       // Utiliser la ligne actuelle pour transformer toutes les colonnes en dessous en 0
            if (fabs(a[i][c]) > eps)
                for (int j = n; j >= c; j -- )
                    a[i][j] -= a[r][j] * a[i][c];

        r ++ ;
    }

    if (r < n)
    {
        for (int i = r; i < n; i ++ )
            if (fabs(a[i][n]) > eps)
                return 2; // Pas de solution
        return 1; // Il y a un nombre infini de solutions
    }

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

    return 0; // Il y a une solution unique
}

7 Comptage combinatoire

7.1 Méthode de récurrence pour calculer les combinaisons

—— Problème modèle AcWing 885. Calculer les combinaisons I

1
2
3
4
5
// c[a][b] représente le nombre de façons de choisir b pommes parmi a
for (int i = 0; i < N; i ++ )
    for (int j = 0; j <= i; j ++ )
        if (!j) c[i][j] = 1;
        else c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % mod;

7.2 Calculer les combinaisons en prétraitant les inverses

—— Problème modèle AcWing 886. Calculer les combinaisons II Tout d’abord, prétraiter tous les restes de factorielle fact[N], ainsi que tous les restes d’inverse de factorielle infact[N] Si le nombre de modulo est un nombre premier, on peut utiliser le petit théorème de Fermat pour calculer l’inverse

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

// Prétraiter les restes de factorielle et les restes d'inverse de factorielle
fact[0] = infact[0] = 1;
for (int i = 1; i < N; i ++ )
{
    fact[i] = (LL)fact[i - 1] * i % mod;
    infact[i] = (LL)infact[i - 1] * qmi(i, mod - 2, mod) % mod;
}

7.3 Théorème de Lucas

—— Problème modèle AcWing 887. Calculer les combinaisons III

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
Si p est un nombre premier, alors pour tout entier 1 <= m <= n, on a :
    C(n, m) = C(n % p, m % p) * C(n / p, m / p) (mod p)

int qmi(int a, int k, int p)  // Modèle de puissance rapide
{
    int res = 1 % p;
    while (k)
    {
        if (k & 1) res = (LL)res * a % p;
        a = (LL)a * a % p;
        k >>= 1;
    }
    return res;
}

int C(int a, int b, int p)  // Calculer les combinaisons C(a, b) à l'aide du théorème
{
    if (a < b) return 0;

    LL x = 1, y = 1;  // x est le numérateur, y est le dénominateur
    for (int i = a, j = 1; j <= b; i --, j ++ )
    {
        x = (LL)x * i % p;
        y = (LL) y * j % p;
    }

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

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

7.4 Calculer les combinaisons en décomposant en facteurs premiers

—— Problème modèle AcWing 888. Calculer les combinaisons IV Lorsque nous devons calculer la valeur réelle des combinaisons, et non le reste par rapport à un certain nombre, la méthode de décomposition en facteurs premiers est plus utile : 1. Crible pour trouver tous les nombres premiers dans la plage 2. Calculer le nombre de fois que chaque facteur premier apparaît à l’aide de C(a, b) = a! / b! / (a - b)! . Le nombre de fois que p apparaît dans n! est n / p + n / p^2 + n / p^3 + … 3. Utiliser la multiplication de haute précision pour multiplier tous les facteurs premiers

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
int primes[N], cnt;     // Stocke tous les nombres premiers
int sum[N];     // Stocke le nombre de fois que chaque nombre premier apparaît
bool st[N];     // Stocke si chaque nombre a été criblé


void get_primes(int n)      // Crible linéaire pour trouver les nombres premiers
{
    for (int i = 2; i <= n; i ++ )
    {
        if (!st[i]) primes[cnt ++ ] = i;
        for (int j = 0; primes[j] <= n / i; j ++ )
        {
            st[primes[j] * i] = true;
            if (i % primes[j] == 0) break;
        }
    }
}


int get(int n, int p)       // Calculer le nombre de fois que p apparaît dans n!
{
    int res = 0;
    while (n)
    {
        res += n / p;
        n /= p;
    }
    return res;
}


vector<int> mul(vector<int> a, int b)       // Modèle de multiplication de haute précision par basse précision
{
    vector<int> c;
    int t = 0;
    for (int i = 0; i < a.size(); i ++ )
    {
        t += a[i] * b;
        c.push_back(t % 10);
        t /= 10;
    }

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

    return c;
}

get_primes(a);  // Prétraiter tous les nombres premiers dans la plage

for (int i = 0; i < cnt; i ++ )     // Calculer le nombre de fois que chaque facteur premier apparaît
{
    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 ++ )     // Utiliser la multiplication de haute précision pour multiplier tous les facteurs premiers
    for (int j = 0; j < sum[i]; j ++ )
        res = mul(res, primes[i]);

8 Théorie des jeux

8.1 Nombres de Catalan

—— Problème modèle AcWing 889. Séquence 01 satisfaisant les conditions Étant donné n zéros et n uns, ils sont disposés dans un ordre quelconque pour former une séquence de longueur 2n, satisfaisant que dans tout préfixe, le nombre de zéros n’est pas inférieur au nombre d’uns. Le nombre de telles séquences est : Cat(n) = C(2n, n) / (n + 1) Jeu de NIM —— Problème modèle AcWing 891. Jeu de Nim Étant donné N tas d’objets, la i-ème tas contient Ai objets. Deux joueurs jouent à tour de rôle, chaque fois, ils peuvent choisir une tas et retirer un nombre quelconque d’objets, ils peuvent retirer tous les objets d’une tas, mais ne peuvent pas ne rien retirer. Le joueur qui retire le dernier objet gagne. Les deux joueurs adoptent une stratégie optimale, le premier joueur est-il assuré de gagner ?

Nous appelons ce jeu un jeu de NIM. L’état auquel le jeu est confronté pendant le jeu est appelé une position. Le joueur qui effectue la première action dans le jeu entier est appelé le premier joueur, et le joueur qui effectue la deuxième action est appelé le deuxième joueur. Si dans une position donnée, quelle que soit l’action entreprise, le joueur perdra le jeu, alors cette position est appelée une position perdante. Adopter une stratégie optimale signifie que si dans une position donnée, il existe une action qui, après l’avoir entreprise, met l’adversaire dans une position perdante, alors cette action est prioritaire. Dans le même temps, une telle position est appelée une position gagnante. Les problèmes de théorie des jeux que nous discutons ne considèrent généralement que la situation idéale, c’est-à-dire que les deux joueurs ne commettent pas d’erreurs et adoptent tous deux une stratégie optimale lors de l’action. Le jeu de NIM n’a pas de match nul, il n’y a que deux situations : le premier joueur gagne ou le premier joueur perd.

Théorème : Le premier joueur du jeu de NIM gagne si et seulement si A1 ^ A2 ^ … ^ An != 0

8.2 Jeu de combinaison équitable ICG

Si un jeu satisfait : Deux joueurs agissent à tour de rôle ; À tout moment du jeu, les actions légales possibles ne dépendent pas de quel joueur doit agir ; Le joueur qui ne peut pas agir perd ; alors ce jeu est appelé un jeu de combinaison équitable. Le jeu de NIM appartient aux jeux de combinaison équitables, mais les jeux de construction, comme le go, ne sont pas des jeux de combinaison équitables. Parce que dans le go, les deux joueurs ne peuvent poser que des pierres noires et blanches respectivement, et le

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