Comentário Noic OBI 2019 - Fase 2 - Programação Nível 1
Comentário por Willian Wang e Lúcio Cardoso
Para conferir a prova na íntegra, clique aqui.
Nota esquecida
Conhecimento prévio necessário:
Perceba que e .
Um possível erro é tentar calcular usando divisão com variáveis inadequadas, como sendo e inteiros, por isso uma boa prática é sempre evitar a operação de divisão no código.
Complexidade: .
// Comentário Noic OBI 2019 - Fase 2 - Programação Nível 1 | |
// Nota esquecida | |
#include <bits/stdc++.h> | |
using namespace std; | |
int main() | |
{ | |
int a, m; | |
cin >> a >> m; | |
int b = m*2 - a; | |
cout << b << "\n"; | |
} |
Ponto do Meio
Conhecimento Prévio Necessário:
Seja a quantidade de pontos no lado do quadrado no passo . É possível notar que e criar uma função recursiva em cima disso.
O método recursivo de calcular a resposta descrito faz parte de uma área mais ampla chamada programação dinâmica.
Complexidade: .
// Comentário Noic OBI 2019 - Fase 2 - Programação Nível 1 | |
// Ponto do Meio | |
#include <bits/stdc++.h> | |
using namespace std; | |
long long f(int x) | |
{ | |
//base da recurssão | |
if(x==0) return 2; | |
return 2 * f(x-1) - 1; | |
} | |
int main() | |
{ | |
int n; | |
cin >> n; | |
long long resposta = f(n); | |
//é necessário ainda obter a área do quadrado | |
resposta *= resposta; | |
cout << resposta << "\n"; | |
} |
Implementar a solução acima deve garantir os 100 pontos do problema, mas quando analisamos mais a fundo a recursão, descobrimos que , e com isso criar uma solução com complexidade logarítmica usando a técnica de exponenciação de matrizes, deixamos claro que essa solução não é necessária para o problema.
Matriz Super-Legal
Conhecimento prévio necessário:
Este mesmo problema estava presente na prova do Nível 2 e, provavelmente, foi um dos problemas mais interessantes dessa fase da OBI.
O seguinte lema é essencial na solução do problema:
Lema: Uma matriz é super-legal se, e somente se, todas as suas submatrizes x são legais.
Note que, pelo enunciado do problema, todas as submatrizes x de uma matriz super-legal devem ser legais. Queremos, então, provar que a condição é suficiente.
Fato 1: Toda matriz x () que possui todas as suas submatrizes x legais é super-legal.
Prova do Fato 1:
Vamos supor, por indução, que o fato vale para qualquer matriz x, onde . Se provarmos que a matriz x é legal, podemos concluir, como um resultado da hipótese de indução, que ela é super-legal.
Note que o caso base é o caso trivial consistindo apenas de uma matriz x. Temos que:
1. , pois a matriz de cantos e é legal.
2. , pois, por hipótese de indução, a matriz de cantos e é legal.
Somando as desigualdades, temos que:
.
Ou seja, a matriz x é legal. Assim, o Fato 1 está provado por indução.
Fato 2: Toda matriz x () que possui todas as suas submatrizes x legais é super-legal.
Prova do Fato 2: Análoga à prova do Fato 1.
Agora, vamos supor, novamente por indução, que uma matriz x () qualquer é super-legal. Para finalizar a prova, é suficiente provar que a matriz x será legal.
Perceba que os casos base da indução são exatamente o Fato 1 e o Fato 2. Temos que:
1. , pois pela hipótese de indução, a matriz de cantos e é legal.
2. , pois, pelo Fato 1, a matriz de cantos e é legal.
Somando as desigualdades, obtemos que:
.
Ou seja, a matriz x inteira é legal. Assim, a prova está concluída.
Redução do Problema
Utilizando dessa observação, vamos construir uma nova matriz de dimensões x, de valores ou . Ela será definida da seguinte maneira:
- Se a submatriz x de de cantos e for legal, então .
- Caso contrário, .
Suponha que a maior matriz super-legal de possui cantos e (). Então, teremos que todos os valores na submatriz em de cantos e serão iguais a . Ou seja, todas as submatrizes x em com canto superior esquerdo indo desde até devem ser legais.
Logo, a maior submatriz super-legal em representa, em , a submatriz de comprimentos e tal que o valor é máximo e que todos os seus elementos sejam iguais a . Resta agora encontrarmos esta matriz em .
Algoritmo em
Defina como o maior valor tal que são células de valor . Ou seja, é a "altura" da célula . Além disso, defina e como os comprimentos da matriz que desejamos encontrar em . Perceba que o valor de cada uma das células na última linha da matriz procurada é maior ou igual a .
Assim, para encontrar o valor máximo, ou seja, a área da maior matriz super-legal, podemos realizar o seguinte algoritmo:
Algoritmo 1:
- Fixe uma célula qualquer da matriz de tal forma que .
- Encontre o índice mais à direita tal que e .
- Encontre o índice mais à esquerda tal que e .
- Caso seja maior do que a resposta atual, atualize-a para este valor.
- Repita as etapas para as outras células não-nulas de .
Essencialmente, o algoritmo acima escolhe uma célula e encontra a matriz de maior comprimento possível tal que seu comprimento seja e que a célula seja uma das células na base da matriz.
Assim, note que é possível realizar um algoritmo de complexidade : Podemos encontrar os valores para cada célula em (mais detalhes no código abaixo) e realizar o Algoritmo 1 para cada uma das células, encontrando os valores e em . Essa solução era suficiente para conseguir pontos no problema.
Otimização do Algoritmo 1
Para uma solução mais eficiente, vamos encontrar os valores de de maneira rápida. Para isso, iremos utilizar a estrutura de dados stack (pilha) da STL do C++. O algoritmo será o seguinte:
Algoritmo 2:
- Inicie na célula da matriz , onde é a linha atual (iniciando por ) e declare uma pilha vazia de pares da forma .
- Se a célula atual for , remova o par no topo da pilha caso seu segundo elemento tenha valor maior ou igual a .
- Repita a etapa até que a pilha esteja vazia ou até que o valor do segundo elemento do par no seu topo seja menor do que .
- O índice será o valor do primeiro elemento do par no topo da pilha (ou será inexistente caso a pilha esteja vazia).
- Insira o par na pilha.
- Repita as etapas para os elementos restantes na linha atual. Após isso, recomece da etapa na linha seguinte.
O Algoritmo 2 é extremamente similar àquele usado no Problema da Semana #39 - Avançado. Para melhor entendimento, confira a sua solução.
Para encontrar o valor , o algoritmo será análogo: Basta iterar por cada linha de trás para frente e refazer o mesmo processo. Finalmente, perceba que a complexidade do Algoritmo 2 em cada uma das linhas de será , já que cada elemento na linha é inserido/removido da pilha no máximo uma vez.
Complexidade da solução: .
// Comentário Noic OBI 2019 - Fase 2 - Programação Nível 2 | |
// Matriz Super Legal | |
#include <bits/stdc++.h> | |
using namespace std; | |
typedef pair<int, int> pii; | |
const int maxn = 1e3+10; | |
int n, m; | |
// altura de cada célula | |
int h[maxn][maxn]; | |
// matrizes | |
int A[maxn][maxn], B[maxn][maxn]; | |
int j_0[maxn][maxn], j_1[maxn][maxn]; | |
void calc_h(void) | |
{ | |
for (int i = 1; i < n; i++) | |
{ | |
for (int j = 1; j < m; j++) | |
{ | |
if (!B[i][j]) continue; | |
// calculamos h[i][j] baseando-nos na célula acima | |
if (B[i-1][j] == 1) h[i][j] = 1+h[i-1][j]; | |
else h[i][j] = 1; | |
} | |
} | |
} | |
void calc_j0(void) | |
{ | |
for (int i = 1; i < n; i++) | |
{ | |
stack<pii> stk; | |
for (int j = 1; j < m; j++) | |
{ | |
// algoritmo 2 | |
while (!stk.empty() && stk.top().second >= h[i][j]) | |
stk.pop(); | |
// se j0 não existe, digamos que ele será uma posição a | |
// menos do limite (1) | |
if (stk.empty()) j_0[i][j] = 0; | |
else j_0[i][j] = stk.top().first; | |
stk.push({j, h[i][j]}); | |
} | |
} | |
} | |
void calc_j1(void) | |
{ | |
for (int i = 1; i < n; i++) | |
{ | |
stack<pii> stk; | |
for (int j = m-1; j >= 1; j--) | |
{ | |
// algoritmo 2 | |
while (!stk.empty() && stk.top().second >= h[i][j]) | |
stk.pop(); | |
// se j1 não existe, digamos que ele será uma posição a | |
// mais do que o limite (m-1) | |
if (stk.empty()) j_1[i][j] = m; | |
else j_1[i][j] = stk.top().first; | |
stk.push({j, h[i][j]}); | |
} | |
} | |
} | |
int main(void) | |
{ | |
scanf("%d %d", &n, &m); | |
for (int i = 1; i <= n; i++) | |
for (int j = 1; j <= m; j++) | |
scanf("%d", &A[i][j]); | |
// checando todas as matrizes 2x2 | |
for (int i = 1; i < n; i++) | |
for (int j = 1; j < m; j++) | |
if (A[i][j]+A[i+1][j+1] <= A[i+1][j]+A[i][j+1]) | |
B[i][j] = 1; | |
calc_h(); | |
calc_j0(); | |
calc_j1(); | |
// resposta do problema | |
int ans = 0; | |
for (int i = 1; i < n; i++) | |
for (int j = 1; j < m; j++) | |
if (h[i][j]) | |
ans = max(ans, (j_1[i][j]-j_0[i][j])*(h[i][j]+1)); // (x_m+1)*(y_m+1) | |
printf("%d\n", ans); | |
} |