Vous disposez de N masses, chacune ayant un poids unique A_1, A_2, ..., A_N. Votre tâche consiste à placer chaque masse sur un plateau d'une balance (gauche ou droite) dans un ordre spécifique. Une chaîne de caractères S de longueur N indique la condition d'équilibre à respecter après le placement de la i-ème masse : 'L' signifie que le plateau gauche doit être plus lourd, et 'R' signifie que le plateau droit doit être plus lourd. L'objectif est de trouver une séquence de placement des masses et leur côté respectif qui satisfasse toutes les conditions de S.
Stratégie de Résolution L'absence de cas "impossible" dans les exemples suggère qu'une solution existe toujours. L'approche consiste à trier les masses A par ordre croissant. Ensuite, nous allons raisonner à rebours, en déterminant quelle masse a été placée en dernier, puis l'avant-dernière, et ainsi de suite jusqu'à la première.
Initialement, nous distribuons les masses triées en deux groupes conceptuels : le groupe G1 (représentant les masses potentielles pour le côté gauche) reçoit A_1, A_3, A_5, ... et le groupe G2 (pour le côté droit) reçoit A_2, A_4, A_6, .... Ces groupes sont gérés comme des double-ended queues (deques) pour permettre des extractions efficaces aux deux extrémités.
La clé est d'aligner la distribution initiale avec la contrainte finale S[N]. Si la parité de N implique que G1 devrait être le côté le plus lourd, mais S[N] exige que G2 le soit, nous inversons simplement les deux groupes. À partir de ce point, G1 représentera le côté "gauche" et G2 le côté "droit" par rapport à l'état désiré pour S[N].
Nous parcourons les contraintes de S de N à 1. Pour chaque étape i :
- Nous examinons si la condition d'équilibre change entre l'étape
i-1et l'étapei. Soitinverse_necessairecette information (vrai siS[i] != S[i-1], faux sinon). - Si
inverse_necessaireest vrai (le plateau dominant doit changer) : Pour que le retrait de lai-ème masse inverse l'équilibre, cette masse doit avoir été l'une des plus lourdes parmi celles disponibles dans les deux groupes. Nous comparons la plus grande masse deG1(G1.back()) et la plus grande deG2(G2.back()). Nous choisissons la plus grande des deux. Le fait de retirer une masse lourde aura un impact significatif et peut inverser l'équilibre. - Si
inverse_necessaireest faux (le plateau dominant doit rester le même) : Pour que le retrait de lai-ème masse maintienne l'équilibre, cette masse doit avoir été l'une des plus légères parmi celles disponibles. Nous comparons la plus petite masse deG1(G1.front()) et la plus petite deG2(G2.front()). Nous choisissons la plus petite des deux. Retirer une masse légère a un impact minimal sur la direction de l'équilibre. La masse choisie à l'étapeiest enregistrée avec le côté d'où elle a été retirée. Nous poursuivons ce processus jusqu'ài=1.
Implémentation Le code utilise deux std::deque<int></int>, nommées cotesGauche et cotesDroite, pour gérer les masses disponibles. Un tableau de booléens estCoteGauchePlusLourd enregistre la contrainte S. Le résultat est stocké dans un tableau de paires (masse, cote).
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#include <deque>
struct MassePlacee {
int poids;
char cote;
};
void resoudre() {
int nombreMasses;
std::cin >> nombreMasses;
std::vector<int> poidsMasses(nombreMasses);
for (int i = 0; i < nombreMasses; ++i) {
std::cin >> poidsMasses[i];
}
std::sort(poidsMasses.begin(), poidsMasses.end());
std::string sequenceDesiree;
std::cin >> sequenceDesiree;
std::vector<bool> estCoteGauchePlusLourd(nombreMasses + 1); // 1-indexed
for (int i = 0; i < nombreMasses; ++i) {
estCoteGauchePlusLourd[i + 1] = (sequenceDesiree[i] == 'L');
}
std::deque<int> cotesGauche, cotesDroite;
for (int i = 0; i < nombreMasses; ++i) {
if ((i + 1) % 2 != 0) { // Masses impaires vers le groupe "gauche" initial
cotesGauche.push_back(poidsMasses[i]);
} else { // Masses paires vers le groupe "droit" initial
cotesDroite.push_back(poidsMasses[i]);
}
}
// Ajuster les groupes en fonction de la contrainte finale S[N]
// Si la contrainte finale indique que le côté lourd ne correspond pas à notre distribution initiale pour le groupe "gauche"
// (i.e., si N est impair, G1 est naturellement plus lourd, si S[N] est 'R', il faut inverser)
// C'est flag[N] vs (N&1) en C++. Ici, flag[N] est estCoteGauchePlusLourd[nombreMasses].
// Si N est impair, le groupe cotesGauche a une masse de plus.
// Si (estCoteGauchePlusLourd[nombreMasses] est VRAI et nombreMasses est PAIR)
// OU (estCoteGauchePlusLourd[nombreMasses] est FAUX et nombreMasses est IMPAIR)
// alors on inverse.
// Equivalent à (estCoteGauchePlusLourd[nombreMasses] != (nombreMasses % 2 != 0))
// C'est-à-dire, si le côté gauche doit être plus lourd (L) mais N est pair (cotesDroite initialement plus lourd)
// ou si le côté droit doit être plus lourd (R) mais N est impair (cotesGauche initialement plus lourd)
if (estCoteGauchePlusLourd[nombreMasses] != (cotesGauche.size() > cotesDroite.size())) {
std::swap(cotesGauche, cotesDroite);
}
std::vector<MassePlacee> resultatPlacement(nombreMasses + 1);
for (int i = nombreMasses; i >= 1; --i) {
bool inversionAttendue = (estCoteGauchePlusLourd[i] != estCoteGauchePlusLourd[i - 1]);
MassePlacee choixG = {0, 'O'}; // Sentinel
MassePlacee choixD = {0, 'O'}; // Sentinel
if (inversionAttendue) { // Le basculement de l'équilibre est attendu
// On veut retirer la masse la plus lourde pour provoquer ou refléter le basculement
if (!cotesGauche.empty()) choixG = {cotesGauche.back(), 'L'};
if (!cotesDroite.empty()) choixD = {cotesDroite.back(), 'R'};
// Comparer pour trouver la plus grande masse
if (choixG.cote == 'O' || (choixD.cote != 'O' && choixD.poids > choixG.poids)) {
resultatPlacement[i] = choixD;
cotesDroite.pop_back();
} else {
resultatPlacement[i] = choixG;
cotesGauche.pop_back();
}
} else { // L'équilibre doit être maintenu
// On veut retirer la masse la plus légère pour perturber le moins possible
if (!cotesGauche.empty()) choixG = {cotesGauche.front(), 'L'};
if (!cotesDroite.empty()) choixD = {cotesDroite.front(), 'R'};
// Comparer pour trouver la plus petite masse
if (choixG.cote == 'O' || (choixD.cote != 'O' && choixD.poids < choixG.poids)) {
resultatPlacement[i] = choixD;
cotesDroite.pop_front();
} else {
resultatPlacement[i] = choixG;
cotesGauche.pop_front();
}
}
}
for (int i = 1; i <= nombreMasses; ++i) {
std::cout << resultatPlacement[i].poids << " " << resultatPlacement[i].cote << std::endl;
}
}
int main() {
std::ios_base::sync_with_stdio(false);
std::cin.tie(NULL);
resoudre();
return 0;
}
Algorithmique, Greedy, Déque, Tri, Structure de Données
Optimisation de l'Apprentissage de Cartes: Une Approche Probabiliste
Vous participez à un jeu où N cartes sont disponibles. Pour chaque partie, le système sélectionne aléatoirement trois cartes. Ensuite, vous et votre adversaire choisissez chacun une carte à bannir (il est possible de bannir la même carte). Enfin, le système sélectionne une carte au hasard parmi celles qui restent pour la partie. Pour maximiser vos chances de victoire, vous souhaitez étudier un minimum de cartes tout en garantissant une probabilité d'au moins P de jouer sur une carte que vous avez étudiée. Votre adversaire ignore quelles cartes vous avez étudiées.
Analyse du Problème et Calcul des Probabilités Nous devons déterminer le nombre minimal de cartes à étudier, disons k. Pour chaque valeur de k de 0 à N, nous calculons la probabilité de jouer sur une carte étudiée et vérifions si elle atteint ou dépasse P. Nous supposons un jeu optimal de votre part.
Le nombre total de façons de choisir 3 cartes parmi N est donné par le coefficient binomial C(N, 3) = N * (N-1) * (N-2) / 6. Pour simplifier les calculs et éviter les divisions par des nombres non entiers (pour P), nous allons travailler avec le numérateur, c'est-à-dire N * (N-1) * (N-2), qui représente le nombre total de façons ordonnées de choisir 3 cartes distinctes.
Soit k le nombre de cartes étudiées et N-k le nombre de cartes non étudiées. Nous analysons les cas en fonction de la composition des 3 cartes sélectionnées par le système :
- 0 carte étudiée, 3 cartes non étudiées : - Nombre de sélections ordonnées :
(N-k) \* (N-k-1) \* (N-k-2). - Dans ce cas, vous ne pouvez pas gagner, quelle que soit votre action. La probabilité de victoire est 0. - 1 carte étudiée, 2 cartes non étudiées : - Nombre de sélections ordonnées :
k \* (N-k) \* (N-k-1) \* 3. Le facteur3vient des 3 positions possibles pour la carte étudiée dans l'ordre de sélection. - Soient les cartesS(étudiée),U1,U2(non étudiées). - Vous jouez de manière optimale et bannissez toujours une carte non étudiée si possible. Vous bannissezU1(ouU2). Il resteS, U2(ouS, U1). - L'adversaire ne sait pas ce que vous avez étudié. Il bannit une carte au hasard parmi les deux restantes. - Si l'adversaire bannitS, il resteU2: vous perdez. - Si l'adversaire bannitU2, il resteS: vous gagnez. - La probabilité de victoire dans ce cas est
1/2. - 2 cartes étudiées, 1 carte non étudiée : - Nombre de sélections ordonnées :
k \* (k-1) \* (N-k) \* 3. Le facteur3vient des 3 positions possibles pour la carte non étudiée. - Soient les cartesS1, S2(étudiées),U(non étudiée). - Vous bannissezU. Il resteS1, S2. - L'adversaire bannit une carte au hasard parmiS1, S2. Quoi qu'il arrive, une carte étudiée restera (soitS1, soitS2). - La probabilité de victoire est1. - 3 cartes étudiées, 0 carte non étudiée : - Nombre de sélections ordonnées :
k \* (k-1) \* (k-2). - Soient les cartesS1, S2, S3. - Vous bannisesz n'importe laquelle, par exempleS1. Il resteS2, S3. - L'adversaire bannit n'importe laquelle, par exempleS2. Il resteS3. - La probabilité de victoire est1.
En additionnant les contributions pondérées par les probabilités de victoire pour chaque cas, nous obtenons le nombre total de résultats ordonnés favorables. Nous comparons cette somme à P multiplié par le nombre total de sélections ordonnées possibles pour les 3 cartes.
Implémentation Le code itère sur k de 0 à N. Pour chaque k, il calcule la somme des contributions favorables et la compare au seuil P * (N * (N-1) * (N-2)). Dès que la condition est remplie, il affiche k et termine.
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#include <iomanip> // Pour std::fixed et std::setprecision
void resoudre() {
int totalCartes;
double probaMinRequise;
std::cin >> totalCartes >> probaMinRequise;
for (int cartesEtudiees = 0; cartesEtudiees <= totalCartes; ++cartesEtudiees) {
double favorableWeightSum = 0; // Somme pondérée des issues favorables (numérateur pour proba)
// Cas 1: 1 carte étudiée, 2 non étudiées
// Nombre de façons ordonnées de choisir: C(cartesEtudiees, 1) * C(totalCartes - cartesEtudiees, 2) * 3!
// Mais comme dans le code original, c'est directement k * (N-k) * (N-k-1) * 3
if (cartesEtudiees >= 1 && totalCartes - cartesEtudiees >= 2) {
favorableWeightSum += 3.0 * (cartesEtudiees) * (totalCartes - cartesEtudiees) * (totalCartes - cartesEtudiees - 1) * 0.5;
}
// Cas 2: 2 cartes étudiées, 1 non étudiée
// Nombre de façons ordonnées de choisir: C(cartesEtudiees, 2) * C(totalCartes - cartesEtudiees, 1) * 3!
// Mais comme dans le code original, c'est directement k * (k-1) * (N-k) * 3
if (cartesEtudiees >= 2 && totalCartes - cartesEtudiees >= 1) {
favorableWeightSum += 3.0 * (cartesEtudiees) * (cartesEtudiees - 1) * (totalCartes - cartesEtudiees) * 1.0;
}
// Cas 3: 3 cartes étudiées, 0 non étudiée
// Nombre de façons ordonnées de choisir: C(cartesEtudiees, 3) * 3!
// Mais comme dans le code original, c'est directement k * (k-1) * (k-2)
if (cartesEtudiees >= 3) {
favorableWeightSum += (cartesEtudiees) * (cartesEtudiees - 1) * (cartesEtudiees - 2) * 1.0;
}
// Le dénominateur commun, si on calculait C(N,3) puis multipliait par 6
// C'est totalCartes * (totalCartes - 1) * (totalCartes - 2) pour les permutations des 3 cartes.
double totalPermutations = (double)totalCartes * (totalCartes - 1) * (totalCartes - 2);
// Si la somme pondérée des issues favorables atteint le seuil requis
if (favorableWeightSum >= probaMinRequise * totalPermutations) {
std::cout << cartesEtudiees << std::endl;
return;
}
}
}
int main() {
std::ios_base::sync_with_stdio(false);
std::cin.tie(NULL);
// Utiliser std::fixed et std::setprecision pour éviter les problèmes de précision des doubles,
// bien que dans ce problème la comparaison semble être robuste.
// std::cout << std::fixed << std::setprecision(10);
resoudre();
return 0;
}
Algorithmique, Probabilités, Combinatoire
Vérification de Progression Arithmétique par Sommes de Puissances Modulo
Étant donnée une séquence A = [a_1, a_2, ..., a_N] de longueur N. Pour chaque requête, on nous fournit un intervalle [l, r] et une différence commune d. La question est de savoir si les éléments dans le sous-tableau A[l..r] peuvent être réarrangés pour former une progression arithmétique de différence d. Tous les calculs doivent être effectués modulo 10^9 + 7.
Principes et Méthode Pour vérifier si un ensemble d'éléments peut former une progression arithmétique (PA) de différence d, nous utilisons une propriété basée sur les sommes de puissances. Si un ensemble de k nombres {x_0, x_1, ..., x_{k-1}} peut former une PA (a, a+d, ..., a+(k-1)d), alors la somme de leurs j-ièmes puissances doit être égale à la somme des j-ièmes puissances des termes de la PA. Cette méthode est robuste, surtout lorsque nous testons pour plusieurs puissances.
Pour un intervalle [l, r] de longueur len = r - l + 1 :
- **Calcul du premier terme (
a) de la PA potentielle :**La somme des éléments d'une PA estS_1 = len * a + d * sum_{i=0}^{len-1}(i). Nous pouvons calculersum_{i=0}^{len-1}(i) = len * (len-1) / 2. La somme réelle des éléments dansA[l..r](notéeSommeReelle) est obtenue par des sommes de préfixes. En égalisant les deux, nous pouvons résoudre poura:
a = (SommeReelle - d * (len * (len-1) / 2) ) * len^{-1} (mod M)
où len^{-1} est l'inverse modulaire de len. 2. **Vérification des sommes de puissances :**Une fois a et d sont connus, nous pouvons construire la PA attendue : a, a+d, a+2d, ..., a+(len-1)d. Pour k allant de 1 à une constante MAX_K (ici 10), nous comparons la somme des k-ièmes puissances des éléments réels de A[l..r] avec la somme des k-ièmes puissances des termes de la PA attendue.
La somme attendue S_k = sum_{i=0}^{len-1} (a + i*d)^k peut être calculée en utilisant le théorème binomial :
(a + i*d)^k = sum_{j=0}^k C(k, j) * a^{k-j} * (i*d)^j
En intervertissant les sommes, on obtient :
S_k = sum_{j=0}^k [ C(k, j) * a^{k-j} * d^j * (sum_{i=0}^{len-1} i^j) ]
Les termes C(k, j) (coefficients binomiaux) et sum_{i=0}^{len-1} i^j sont précalculés pour une efficacité optimale.
Précalculs Pour chaque k de 0 à MAX_K et pour chaque indice i de 0 à N, nous précalculons :
coefficientsBinomiaux[k][j] = C(k, j).prefixSumsElementsPowers[k][i]: somme desk-ièmes puissances desa_1, ..., a_i.prefixSumsIndicesPowers[k][i]: somme desk-ièmes puissances des0, ..., i(c'est-à-diresum_{x=0}^i x^k).
Détails d'Implémentation
- Modulo Arithmétique : Toutes les opérations (addition, soustraction, multiplication) sont effectuées modulo
10^9 + 7. La division est remplacée par la multiplication par l'inverse modulaire (calculé avec l'exponentiation rapide pourx^(M-2)selon le petit théorème de Fermat). - Efficacité : Les sommes de préfixes permettent de calculer les sommes sur un intervalle
[l, r]enO(1). Chaque requête prendO(MAX_K)car la formule binomiale nécessite de sommerMAX_K+1termes. Le précalcul est enO(N * MAX_K + MAX_K^2). - Cas
d=0: Sid=0, la PA attendue esta, a, ..., a. La formule générale gère ce cas correctement, car la somme des puissanceskdevient simplementlen * a^k. La vérification de la somme des puissances détectera si les éléments réels ne sont pas tous égaux àa.
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
// Utilisation de long long pour les calculs intermédiaires afin d'éviter les débordements avant l'opération modulo.
using ll = long long;
const int MAX_N = 2e5 + 10;
const int MOD = 1e9 + 7;
const int MAX_K = 10; // Niveau de puissance max pour la vérification
int N_elements, Q_queries;
ll valeursElements[MAX_N];
// prefixSumsElementsPowers[k][i] = sum_{j=1 to i} (valeursElements[j])^k
ll prefixSumsElementsPowers[MAX_K + 1][MAX_N];
// prefixSumsIndicesPowers[k][i] = sum_{x=0 to i} x^k
ll prefixSumsIndicesPowers[MAX_K + 1][MAX_N];
// coefficientsBinomiaux[k][j] = C(k, j)
ll coefficientsBinomiaux[MAX_K + 1][MAX_K + 1];
// Fonction pour l'exponentiation modulaire rapide (x^a % MOD)
ll power(ll base, ll exp) {
ll res = 1;
base %= MOD;
while (exp > 0) {
if (exp % 2 == 1) res = (res * base) % MOD;
base = (base * base) % MOD;
exp /= 2;
}
return res;
}
// Fonction pour calculer l'inverse modulaire (x^(MOD-2) % MOD)
ll modInverse(ll n) {
return power(n, MOD - 2);
}
// Calcule la somme des k-ièmes puissances de la PA attendue: sum_{i=0}^{len-1} (first_term + i*diff_com)^k
ll calculateExpectedSumOfPowers(ll first_term, ll diff_com, int len, int k_power) {
ll total_sum = 0;
for (int j = 0; j <= k_power; ++j) {
ll term = coefficientsBinomiaux[k_power][j];
term = (term * power(first_term, k_power - j)) % MOD;
term = (term * power(diff_com, j)) % MOD;
term = (term * prefixSumsIndicesPowers[j][len - 1]) % MOD; // sum_{i=0}^{len-1} i^j
total_sum = (total_sum + term) % MOD;
}
return total_sum;
}
void solve() {
std::cin >> N_elements >> Q_queries;
// Précalcul des coefficients binomiaux C(k, j)
for (int i = 0; i <= MAX_K; ++i) {
coefficientsBinomiaux[i][0] = 1;
for (int j = 1; j <= i; ++j) {
coefficientsBinomiaux[i][j] = (coefficientsBinomiaux[i - 1][j - 1] + coefficientsBinomiaux[i - 1][j]) % MOD;
}
}
// Précalcul des sommes de puissances des indices: sum_{x=0}^i x^k
for (int i = 0; i < N_elements; ++i) {
// x^0 = 1 pour x >= 0
prefixSumsIndicesPowers[0][i] = (i == 0) ? 1 : (prefixSumsIndicesPowers[0][i-1] + 1) % MOD;
}
for (int k = 1; k <= MAX_K; ++k) {
prefixSumsIndicesPowers[k][0] = 0; // 0^k = 0 pour k > 0
for (int i = 1; i < N_elements; ++i) {
prefixSumsIndicesPowers[k][i] = (prefixSumsIndicesPowers[k][i-1] + power(i, k)) % MOD;
}
}
// Lecture des éléments et précalcul des sommes de puissances des éléments: sum_{j=1 to i} (valeursElements[j])^k
for (int i = 1; i <= N_elements; ++i) {
std::cin >> valeursElements[i];
for (int k = 0; k <= MAX_K; ++k) {
prefixSumsElementsPowers[k][i] = (prefixSumsElementsPowers[k][i - 1] + power(valeursElements[i], k)) % MOD;
}
}
while (Q_queries--) {
int idxDebut, idxFin;
ll diffCommune;
std::cin >> idxDebut >> idxFin >> diffCommune;
int longueur = idxFin - idxDebut + 1;
if (longueur == 0) { // Cas vide
std::cout << "Yes\n";
continue;
}
if (longueur == 1) { // Une seule valeur toujours une PA
std::cout << "Yes\n";
continue;
}
// Somme des éléments réels dans l'intervalle [idxDebut, idxFin]
ll sommeElementsReels = (prefixSumsElementsPowers[1][idxFin] - prefixSumsElementsPowers[1][idxDebut - 1] + MOD) % MOD;
// Somme des indices pour la PA (0 + 1 + ... + (longueur-1))
ll sommeIndicesPA = (ll)longueur * (longueur - 1) / 2 % MOD;
// Calcul du premier terme 'a' de la PA
ll premierTerme = (sommeElementsReels - (diffCommune * sommeIndicesPA % MOD) + MOD) % MOD;
premierTerme = (premierTerme * modInverse(longueur)) % MOD;
bool estProgressionArithmetique = true;
// Vérification des sommes de puissances pour k=1 à MAX_K
for (int k = 1; k <= MAX_K; ++k) {
ll sommePuissancesReelles = (prefixSumsElementsPowers[k][idxFin] - prefixSumsElementsPowers[k][idxDebut - 1] + MOD) % MOD;
ll sommePuissancesAttendues = calculateExpectedSumOfPowers(premierTerme, diffCommune, longueur, k);
if (sommePuissancesReelles != sommePuissancesAttendues) {
estProgressionArithmetique = false;
break;
}
}
if (estProgressionArithmetique) {
std::cout << "Yes\n";
} else {
std::cout << "No\n";
}
}
}
int main() {
std::ios_base::sync_with_stdio(false);
std::cin.tie(NULL);
solve();
return 0;
}