Balé (Contagem de Inversões)
Autor(es): Vinicius Lima
Uma academia possui $N$ bailarinas. O dono quer escolher uma dupla onde:
A entrada fornece a lista de habilidades em ordem cronológica (da mais antiga para a mais nova). Seu objetivo é contar quantos pares $(i, j)$ existem tal que $i < j$ (índice da primeira é menor que o da segunda) e $\text{Habilidade}[i] > \text{Habilidade}[j]$.
Em Ciência da Computação, esse problema é conhecido como Contagem de Inversões de um array.
Entrada
A primeira linha contém um inteiro $N$. A segunda linha contém $N$ inteiros representando as habilidades das bailarinas, em ordem de chegada.
Saída
Um único inteiro representando o número de duplas válidas.
Restrições
Exemplos
| Entrada | Saída |
|---|---|
| 5 1 5 2 4 3 |
4 |
| 9 9 8 7 6 5 4 3 1 2 |
35 |
Uma verificação de todos os pares possíveis (abordagem ingênua) teria complexidade $O(N^2)$, o que é muito lento para $N = 100,000$. Para garantir a eficiência exigida pelas restrições de $N$, utilizamos o algoritmo Merge Sort modificado.
A complexidade final do algoritmo será $O(N \log N)$.
O Merge Sort divide o vetor ao meio recursivamente e depois intercala (une) as partes ordenadas. Durante a etapa de intercalação (merge) de duas metades (Esquerda e Direita) já ordenadas, podemos contar as inversões:
Código em JavaScript (com Fast I/O):
const fs = require('fs');
// --- FAST I/O para leitura eficiente de grandes inputs ---
const buffer = fs.readFileSync(0);
let bufferIdx = 0;
function readInt() {
let res = 0;
while (bufferIdx < buffer.length && buffer[bufferIdx] <= 32) bufferIdx++;
if (bufferIdx >= buffer.length) return null;
while (bufferIdx < buffer.length && buffer[bufferIdx] > 32) {
res = res * 10 + (buffer[bufferIdx++] - 48);
}
return res;
}
function solve() {
const valN = readInt();
if (valN === null) return;
const N = valN;
const arr = new Int32Array(N);
for (let i = 0; i < N; i++) {
arr[i] = readInt();
}
// Array auxiliar global para o Merge Sort (evita alocacao de memoria excessiva)
const temp = new Int32Array(N);
let inversions = 0;
// Funcao de Merge (Intercalacao)
function merge(left, mid, right) {
let i = left; // Indice inicio esquerda
let j = mid + 1; // Indice inicio direita
let k = left; // Indice temp
while (i <= mid && j <= right) {
if (arr[i] <= arr[j]) {
temp[k++] = arr[i++];
} else {
// Se arr[i] > arr[j], eh uma INVERSAO.
// Quantidade = (mid + 1) - i
inversions += (mid + 1 - i);
temp[k++] = arr[j++];
}
}
// Copia restantes da esquerda
while (i <= mid) temp[k++] = arr[i++];
// Copia restantes da direita
while (j <= right) temp[k++] = arr[j++];
// Copia de volta para o array original
for (let x = left; x <= right; x++) {
arr[x] = temp[x];
}
}
// Merge Sort Recursivo
function mergeSort(left, right) {
if (left < right) {
const mid = (left + right) >>> 1; // Divisao inteira por 2
mergeSort(left, mid);
mergeSort(mid + 1, right);
merge(left, mid, right);
}
}
mergeSort(0, N - 1);
console.log(inversions);
}
solve();A organização SBC inspeciona calculadoras que realizam apenas multiplicação (*) e divisão (/) por números de um único dígito (1 a 9). A calculadora inicia com o valor 1. Uma sequência de $N$ operações é fornecida.
Apesar de a entrada ser limitada a um dígito, o visor pode exibir números fracionários ou muito grandes durante o processo. No entanto, é garantido que o resultado final é um número inteiro que cabe em uma variável padrão (até $2^{30}$).
Sua tarefa é processar as operações e imprimir o resultado final.
Entrada
A primeira linha contém um inteiro $N$. As próximas $N$ linhas contêm um dígito e um caractere (* ou /).
Saída
Uma única linha com o resultado inteiro final.
Restrições
Exemplos
| Entrada | Saída |
|---|---|
| 3 2 * 1 * 3 * |
6 |
| 3 2 / 3 / 6 * |
1 |
| 11 9 * ... (9x) 9 / |
387420489 |
A simulação direta das operações usando números de ponto flutuante (double) é arriscada devido a possíveis erros de precisão, o que comprometeria o resultado final, que deve ser exato. Usar inteiros gigantes (BigInt) para numerador e denominador seria computacionalmente custoso para $N=100,000$.
A melhor abordagem é utilizar a fatoração prima dos números de 1 a 9. Como todos os dígitos de entrada são pequenos, seus fatores primos são apenas: 2, 3, 5 e 7.
Manteremos um contador para o expoente de cada um desses primos. O resultado final $R$ é dado por: $$R = 2^{c_2} \cdot 3^{c_3} \cdot 5^{c_5} \cdot 7^{c_7}$$
*): Adicionamos os fatores primos de $X$ aos respectivos contadores./): Subtraímos os fatores primos de $X$ dos respectivos contadores.| Dígito ($X$) | Fatoração Prima | Contadores ($c_2, c_3, c_5, c_7$) |
|---|---|---|
| 1 | Neutro | $(0, 0, 0, 0)$ |
| 2 | $2^1$ | $(1, 0, 0, 0)$ |
| 3 | $3^1$ | $(0, 1, 0, 0)$ |
| 4 | $2^2$ | $(2, 0, 0, 0)$ |
| 5 | $5^1$ | $(0, 0, 1, 0)$ |
| 6 | $2^1 \cdot 3^1$ | $(1, 1, 0, 0)$ |
| 7 | $7^1$ | $(0, 0, 0, 1)$ |
| 8 | $2^3$ | $(3, 0, 0, 0)$ |
| 9 | $3^2$ | $(0, 2, 0, 0)$ |
Como o resultado final é garantido ser inteiro e $\leq 2^{30}$, esta abordagem garante um cálculo exato e eficiente ($O(N)$).
[Image of prime factorization for small numbers]
Código em JavaScript (com Fast I/O):
const fs = require('fs');
// --- FAST I/O para leitura eficiente de grandes inputs ---
const buffer = fs.readFileSync(0);
let bufferIdx = 0;
function readInt() {
let res = 0;
while (bufferIdx < buffer.length && buffer[bufferIdx] <= 32) bufferIdx++;
if (bufferIdx >= buffer.length) return null;
while (bufferIdx < buffer.length && buffer[bufferIdx] > 32) {
res = res * 10 + (buffer[bufferIdx++] - 48);
}
return res;
}
// Funcao auxiliar para ler o operador (* ou /)
function readChar() {
while (bufferIdx < buffer.length && buffer[bufferIdx] <= 32) bufferIdx++;
if (bufferIdx >= buffer.length) return null;
return buffer[bufferIdx++]; // Retorna o codigo ASCII
}
function solve() {
const valN = readInt();
if (valN === null) return;
const N = valN;
// Contadores para os expoentes dos primos 2, 3, 5, 7
let p2 = 0, p3 = 0, p5 = 0, p7 = 0;
for (let i = 0; i < N; i++) {
const val = readInt();
const op = readChar(); // 42 is '*', 47 is '/'
// Determina o sinal: +1 se multiplicar, -1 se dividir
const sign = (op === 42) ? 1 : -1;
// Atualiza os contadores baseados na fatoracao do digito val
if (val === 2) { p2 += sign; }
else if (val === 3) { p3 += sign; }
else if (val === 4) { p2 += 2 * sign; }
else if (val === 5) { p5 += sign; }
else if (val === 6) { p2 += sign; p3 += sign; }
else if (val === 7) { p7 += sign; }
else if (val === 8) { p2 += 3 * sign; }
else if (val === 9) { p3 += 2 * sign; }
// val === 1 nao faz nada
}
// Reconstroi o resultado
// Como o resultado final cabe em 2^30, Math.pow e a multiplicacao
// sao seguras em JavaScript sem perda de precisao.
const res = Math.pow(2, p2) * Math.pow(3, p3) * Math.pow(5, p5) * Math.pow(7, p7);
console.log(res);
}
solve();