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); | |
} |