Programação Dinâmica e Contagem de Inversões com Otimização de Soma de Prefixos

Problemas de Programação Competitiva: Contagem de Inversões

Neste artigo, exploraremos soluções para problemas de contagem de inversões, abordando tanto abordagens diretas quanto soluções otimizadas usando programação dinâmica com soma de prefixos.

Problema 1: Contagem de Inversões (P1521)

O problema consiste em encontrar o número de permutações de tamanho N com exatamente K inversões. Uma inversão ocorre quando em uma sequência ordenada, um elemento precede outro elemento manor.

Solução Bruta (30 pontos)

Uma abordagem inicial é gerar todas as permutações possíveis e contar as inversões em cada uma. Embora ineficiente para valores grandes de N, pode render pontos significativos em competições.


#include <bits>
using namespace std;

int n, k;
long long resultado = 0;
int sequencia[1010];

int main() {
    cin >> n >> k;
    
    // Inicializa a primeira permutação
    long long total_permutacoes = 1;
    for (int i = 1; i <= n; i++) {
        sequencia[i] = i;
        total_permutacoes *= i;
    }
    
    // Conta permutações com exatamente K inversões
    for (int i = 1; i <= total_permutacoes; i++) {
        next_permutation(sequencia + 1, sequencia + n + 1);
        
        int inversoes = 0;
        for (int x = 1; x <= n; x++)
            for (int y = x + 1; y <= n; y++)
                if (sequencia[x] > sequencia[y]) 
                    inversoes++;
        
        if (inversoes == k) 
            resultado++;
    }
    
    cout << resultado % 10000 << endl;
    return 0;
}
</bits>

Solução Otimizada com Programação Dinâmica

Uma abordagem mais eficiente usa programação dinâmica com otimização de soma de prefixos. Definimos dp[i][j] como o número de sequências com i elementos que contêm exatamente j inversões.

A transição de estado considera onde o i-ésimo elemento é inserido na sequência existente. Cada posição de inserção adiciona um número diferente de inversões:


#include <bits>
using namespace std;

int n, k;
int dp[5010][5010];

int main() {
    scanf("%d%d", &n, &k);
    
    // Casos base
    dp[1][0] = 1;
    dp[2][0] = 1;
    dp[2][1] = 1;
    dp[0][0] = 1;
    
    // Programação dinâmica com otimização
    for (int i = 3; i <= n; i++) {
        for (int j = 0; j <= k; j++) {
            // Soma das transições possíveis
            for (int q = 0; q <= min(i - 1, j); q++) {
                dp[i][j] = (dp[i - 1][j - q] + dp[i][j]) % 10000;
            }
        }
    }
    
    printf("%d\n", dp[n][k]);
    return 0;
}
</bits>

Análise da Solução Otimizada

A solução otimizada explora a relação entre o i-ésimo elemento e a sequência dos primeiros i-1 elementos. Ao inserir o novo elemento em diferentes posições, podemos controlar quantas novas inversões são adicionadas à sequência existente.

A complexidade temporal é O(N*K*N), mas pode ser otimizada para O(N*K) usando somas de prefixos para evitar o loop interno mais profundo.

Problemas Relacionados

Outros problemas interessantes nesta categoria incluem:

  • P2511: Divisão de Varas - Problema de dificuldade superior que requer técnicas avançadas de programação dinâmica
  • Problemas de Mesclagem de Pedras - Variações clássicas que podem ser resolvidas com abordagens semelhantes

É fundamental dominar tanto soluções diretas quanto otimizadas. As soluções brutas frequentemente fornecem insights cruciais para desenvolver algoritmos mais eficientes.

Tags: programação dinâmica contagem de inversões otimização de soma de prefixos Algoritmos Competitivos

Publicado em 7-5 06:05