- Sommes前缀es =============
1.1 Principe fondamental
La somme前缀e constitue une technique permettant de mémoriser le cumul des éléments précédents dans une structure de données. Cette approche offre une complexité temporelle constante O(1) pour récupérer la somme de n'importe quel intervalle donné.
Tableau unidimensionnel
Pour calculer la somme des éléments de l'indice 1 jusqu'à li'ndice i, on procède de manière itérative : lorsque l'algorithme atteint la position i, les positions 1 à i-1 ont déjà été traitées. La formule devient donc : table[i] = table[i] + table[i-1].
Pour obtenir la somme d'un intervalle [l, r], on utilise simplement : table[r] - table[l-1].
Tableau bidimensionnel
Pour une matrice, la somme前缀e permet de calculer rapidement la somme de n'importe quel sous-rectangle. En trattant la position (i, j), les lignes précédentes i-1 et les colonnes précédentes j-1 sont déjà calculées. La formule de construction est : table[i][j] = table[i][j] + table[i-1][j] + table[i][j-1] - table[i-1][j-1].
Pour un rectangle défini par les coins supérieur gauche (x1, y1) et inférieur droit (x2, y2), la somme s'obtient par : table[x2][y2] - table[x2][y1-1] - table[x1-1][y2] + table[x1-1][y1-1].
1.2 Applications pratiques
Problème 795 : Somme d'un intervalle
Implémentation de référence :
void calculerSomme(){
int n, m;
cin >> n >> m;
for(int i = 1; i <= n; i++) {
cin >> prefix[i];
prefix[i] += prefix[i-1];
}
while(m--) {
int gauche, droite;
cin >> gauche >> droite;
cout << prefix[droite] - prefix[gauche - 1] << endl;
}
}
Problème 796 : Somme d'une sous-matrice
Implémentation de référence :
void calculerMatrice(){
int n, m, requetes;
cin >> n >> m >> requetes;
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= m; j++) {
cin >> matrice[i][j];
matrice[i][j] += matrice[i-1][j] + matrice[i][j-1] - matrice[i-1][j-1];
}
}
while(requetes--) {
int x1, y1, x2, y2;
cin >> x1 >> y1 >> x2 >> y2;
cout << matrice[x2][y2] - matrice[x2][y1-1] - matrice[x1-1][y2] + matrice[x1-1][y1-1] << endl;
}
}
- Tableaux de différences ==========================
2.1 Principe fondamental
Le tableau de différences stocke l'écart entre chaque élément et son prédécesseur. Cette structure permet d'effectuer des opérations d'addition ou de soustraction sur des intervalles complets avec une complexité O(1). Une propriété fondamentale réside dans le fait que la somme前缀e du tableau de différences redonne le tableau original.
Différence unidimensionnelle
Pour construire le tableau de différences, on calcule : diff[i] = original[i] - original[i-1].
Pour ajouter une valeur c à tous les éléments de l'intervalle [l, r], il suffit de modifier : diff[l] += c et diff[r+1] -= c.
Différence bidimensionnelle
Pour un rectangle défini par les coins (x1, y1) et (x2, y2), l'ajout de la valeur c s'effectue en quatre opérations :
diff[x1][y1] += cdiff[x1][y2+1] -= cdiff[x2+1][y1] -= cdiff[x2+1][y2+1] += c
2.2 Applications pratiques
Problème 797 : Différences
Implémentation de référence :
void resoudreProbleme(){
for(int i = 1; i <= n; i++) {
cin >> valeurs[i];
}
for(int i = 1; i <= n; i++) {
diff[i] = valeurs[i] - valeurs[i-1];
}
while(operations--) {
int debut, fin, valeur;
cin >> debut >> fin >> valeur;
diff[debut] += valeur;
diff[fin + 1] -= valeur;
}
for(int i = 1, accumulateur = 0; i <= n; i++) {
accumulateur += diff[i];
cout << accumulateur << ' ';
}
}
Problème 798 : Matrice de différences
Implémentation de référence :
void ajouterRectangle(int x1, int y1, int x2, int y2, int valeur) {
diff[x1][y1] += valeur;
diff[x1][y2 + 1] -= valeur;
diff[x2 + 1][y1] -= valeur;
diff[x2 + 1][y2 + 1] += valeur;
}
void resoudreProbleme(){
for(int i = 1; i <= lignes; i++) {
for(int j = 1; j <= colonnes; j++) {
cin >>donnees[i][j];
int x1 = i, x2 = i, y1 = j, y2 = j;
int val = donnees[i][j];
ajouterRectangle(x1, y1, x2, y2, val);
}
}
while(requetes--) {
int x1, y1, x2, y2, val;
cin >> x1 >> y1 >> x2 >> y2 >> val;
ajouterRectangle(x1, y1, x2, y2, val);
}
for(int i = 1; i <= lignes; i++) {
for(int j = 1; j <= colonnes; j++) {
donnees[i][j] = diff[i][j] + donnees[i-1][j] + donnees[i][j-1] - donnees[i-1][j-1];
cout << donnees[i][j] << ' ';
}
cout << endl;
}
}
- Méthode des deux pointeurs =============================
3.1 Principe fondamental
La technique des deux pointeurs exploite les propriétés intrinsèques des séquences ordonnées pour optimiser les performances algorithmiques. Un pointeur secondaire permet de délimiter la plage de recherche valide, réduisant ainsi significativement la complexité temporelle.
Cette approche se divise en deux catégories principales :
- Sur une seule séquence : Deux pointeurs délimitent une fenêtre glissante.
- Sur deux séquences : Maintien d'un ordre spécifique entre les éléments.
Modèle générique (inspiré d'AcWing) :
void resoudre(){
for(int i = 0, j = 0; i < taille; i++){
while(j < i && conditionVerification(i, j)) {
j++;
}
// Logique spécifique au problème
}
}
3.2 Applications pratiques
Problème 799 : Sous-séquence continue sans doublons
Approche : Utiliser un tableau de comptage pour suivre les occurrences dans la fenêtre [j, i]. Lorsque l'élément à la position i existe déjà dans la fenêtre, déplacer le pointeur j vers l'avant jusqu'à élimination du doublon.
Implémentation de référence :
void trouverPlusLongue(){
int resultat = 0;
for(int i = 1, j = 1; i <= nombre; i++){
if(presence[elements[i]] > 0){
while(presence[elements[i]] > 0) {
presence[elements[j]]--;
j++;
}
}
presence[elements[i]]++;
resultat = max(i - j + 1, resultat);
}
cout << resultat << endl;
}
Problème 800 : Cible numérique dans un tableau
Approche : Puisque les tableaux sont triés par ordre croissant, si a[i] + b[j] > cible, alors toute valeur avec un indice plus grand dans le second tableau dépassera également la cible. Le pointeur j peut donc reculer de manière monotone.
Implémentation de référence :
void rechercherCible(){
for(int i = 0, j = taille2 - 1; i < taille1 && j >= 0; i++){
while(i < taille1 && j >= 0 && tableau1[i] + tableau2[j] > valeurCible) {
j--;
}
if(tableau1[i] + tableau2[j] == valeurCible){
cout << i << ' ' << j << endl;
return;
}
}
}
Problème 799 : Vérification de sous-séquence
Approche : Vérifier si une chaîne secondaire apparaît dans une chaîne principale dans le même ordre. Parcourir simultanément les deux chaînes en recherchant chaque caractère.
Implémentation de référence :
void verifierSequence(){
int i = 0, j = 0;
while(i < longueurPrincipale && j < longueurSecondaire){
while(j < longueurSecondaire && principale[i] != secondaire[j]) {
j++;
}
if(j == longueurSecondaire) break;
i++;
j++;
}
if(i == longueurPrincipale) {
cout << "Oui" << endl;
} else {
cout << "Non" << endl;
}
}