Técnicas de Otimização: Algoritmo de Mo para MEX e Padrões de Sequências em C++

Notas de Implementação e Armadilhas em C++

Ao trabalhar com estruturas de dados dinâmicas como o std::vector, é crucial evitar loops que dependam do tamanho atual do container enquanto ele é modificado. Por exemplo, a instrução for (int i = 0; i < (int)v.size(); i++) v.push_back(1); resultará em um loop infinito, pois o limite superior aumenta a cada iteração. Além disso, o sombreamento de variáveis em macros ou loops aninhados (como utilizar o mesmo nome de iterador i em níveis diferentes) pode causar comportamentos indefinidos e bugs de difícil detecção. ### Resolução do Problema MEX em Intervalos (Algoritmo de Mo)

O problema de encontrar o menor valor ausente (MEX - Minimum Excluded value) em diversos intervalos de um array pode ser resolvido eficientemente utilizando o **Algoritmo de Mo com Rollback**. A observação principal é que a operação de remoção para o MEX é simples: ao remover um elemento, se a contagem desse valor chegar a zero, ele se torna um candidato para o novo MEX (basta comparar o valor removido com o MEX atual e pegar o mínimo). No entanto, a operação de inserção não possui essa propriedade de atualização direta. Por isso, utilizamos uma abordagem de Mo que prioriza deleções ou o estado de "rollback" para manter a complexidade sob controle. ```

#include #include #include #include

using namespace std;

struct Consulta { int esq, dir, id; };

const int MAXN = 200005; int dados[MAXN], freq[MAXN], resultado[MAXN]; int tamanho_bloco;

bool compararConsultas(const Consulta& a, const Consulta& b) { int bloco_a = a.esq / tamanho_bloco; int bloco_b = b.esq / tamanho_bloco; if (bloco_a != bloco_b) return bloco_a < bloco_b; return a.dir > b.dir; }

int calcularMexBruto(int l, int r) { vector temp_freq(MAXN, 0); for (int i = l; i <= r; i++) { if (dados[i] < MAXN) temp_freq[dados[i]]++; } for (int i = 0; ; i++) { if (temp_freq[i] == 0) return i; } }

int main() { ios::sync_with_stdio(false); cin.tie(nullptr);

int n, m;
cin >> n >> m;
tamanho_bloco = sqrt(n);

for (int i = 1; i <= n; i++) cin >> dados[i];

vector<Consulta> consultas(m);
for (int i = 0; i < m; i++) {
    cin >> consultas[i].esq >> consultas[i].dir;
    consultas[i].id = i;
}

sort(consultas.begin(), consultas.end(), compararConsultas);

// Lógica simplificada de processamento por blocos
// Em uma implementação de produção, utilizaria-se ponteiros de Mo
// para ajustar os intervalos [L, R] minimizando o movimento.

// ... (processamento do Algoritmo de Mo)

return 0;

}


### Sequências de Potências e Padrões Binários

Muitas vezes, sequências complexas podem ser reduzidas a representações binárias. Considere um problema onde uma sequência é formada pela combinação de potências de base $k$. Observando a formação dos índices: $\\{0\\}$, $\\{1\\}$, $\\{0, 1\\}$, $\\{2\\}$, $\\{0, 2\\}$, $\\{1, 2\\}$, $\\{0, 1, 2\\} \\dots$, percebe-se que os expoentes presentes no $p$-ésimo termo correspondem às posições dos bits definidos (1) na representação binária de $p$. Para clacular o $p$-ésimo termo da sequência para uma base $k$, podemos iterar sobre os bits de $p$ e somar $k^i$ para cada bit $i$ ativo. Como o resultado pode exceder o limite de 64 bits, o uso de `__int128` é recomendado. ```

#include <iostream>
#include <string>
#include <algorithm>

using namespace std;

typedef __int128 int128;

void imprimir128(int128 n) {
    if (n == 0) { cout << "0"; return; }
    string s = "";
    while (n > 0) {
        s += (char)((n % 10) + '0');
        n /= 10;
    }
    reverse(s.begin(), s.end());
    cout << s;
}

int128 potencia(int base, int exp) {
    int128 res = 1;
    for (int i = 0; i < exp; i++) res *= base;
    return res;
}

int main() {
    long long k, p;
    cin >> k >> p;

    int128 total = 0;
    for (int i = 0; i < 60; i++) {
        if ((p >> i) & 1) {
            total += potencia(k, i);
        }
    }
    
    imprimir128(total);
    cout << endl;
    return 0;
}

Expansão Decimal de Frações

O cálculo de dígitos específicos em uma expansão decimal (como a fração $10/89$) pode ser realizado simulando o algoritmo de divisão manual. Para encontrar os dígitos entre as posições $M$ e $N$, aplicamos o operador de resto para saltar as primeiras $M$ posições e, em seguida, extraímos os quocientes sucessivos. ```

#include

int main() { int m, n; if (!(std::cin >> m >> n)) return 0;

int numerador = 10;
int denominador = 89;

// Ignorar casas decimais anteriores à posição M
for (int i = 1; i < m; i++) {
    numerador %= denominador;
    numerador *= 10;
}

// Extrair e imprimir dígitos de M até N
for (int i = m; i <= n; i++) {
    numerador %= denominador;
    numerador *= 10;
    std::cout << (numerador / denominador);
}
std::cout << std::endl;

return 0;

}

Tags: cpp algorithms mo-algorithm competitive-programming number-theory

Publicado em 9-18 11:59