Comentário NOIC OBI 2018 - Fase 1 - Programação Nível 1

Comentário por Lúcio Cardoso

Xadrez

Conhecimento prévio necessário:

  1. Estruturas condicionais (Aula 2)
  2. Operador de resto da divisão (Aula 2)

Seja (x,y) o quadrado na linha x e coluna y do tabuleiro. Já que a cor do quadrado (1, 1) é branca, a cor de cada quadrado (x,y) pode ser encontrada baseando-se nos seguintes casos:
1. Se x e y tem a mesma paridade, a cor de (x,y) é branca.
2. Se x e y tem paridades diferentes, a cor de (x, y) é preta.
Como queremos encontrar a cor do (L,C) (canto inferior direito), basta checar em qual dos casos o quadrado se encaixa, imprimindo sua respectiva cor. Segue o código para melhor entendimento.


//Xadrez - Fase 1 P1 - 2018
//Por Lucio Cardoso
//Complexidade: O(1)
#include <bits/stdc++.h>
using namespace std;
int main(void)
{
int l, c;
cin >> l >> c;
if ((l%2) == (c%2)) {
// se l e c tem mesma paridade
cout << "1\n";
}
else {
// se l e c tem paridades distintas
cout << "0\n";
}
}

view raw

xadrez.cpp

hosted with ❤ by GitHub

Escadinha

Conhecimento prévio necessário:

  1. Estruturas de repetição (Aula 2)
  2. Vetores (Aula 3)

Perceba que uma escadinha é, na verdade, uma Progressão Aritmética (PA). Então, o problema se resume em encontrar a quantidade de PAs na sequência tais que nenhuma delas esteja totalmente contida em alguma outra,
e todas as PAs calculadas possuam o maior tamanho possível. Assim, para um índice i (inicialmente 1), encontraremos o maior índice j tal que a sequência de i a j seja uma PA, repetindo o processo novamente, ou seja, fazendo o novo indíce inical de uma escadinha sendo j, até que o último índice seja o próprio fim da sequência (n). Segue o código para melhor entendimento.


//Escadinha - Fase 1 P1 - 2018
//Por Lucio Cardoso
//Complexidade: O(n)
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1010;
// sequência de números
int num[MAXN];
int main(void)
{
int n;
cin >> n;
for (int i = 1; i <= n; i++)
cin >> num[i];
// faremos esse caso separadamente
if (n == 1)
{
cout << "1\n";
return 0;
}
// resposta do problema
int ans = 0;
for (int i = 1; i < n; i++)
{
// razão da PA
int dif = num[i]-num[i+1];
for (int j = i; j < n; j++)
{
// se j é o último número na PA
if (num[j]-num[j+1] != dif)
{
// na prox. iteração, i receberá j-1+1 = j
i = j-1;
break;
}
// n é o último número da PA, então faço i = n-1 para sair do loop
if (j+1 == n) i = n-1;
}
// chegamos ao fim da PA atual, então incremento ans
ans++;
}
cout << ans << "\n";
return 0;
}

view raw

escadinha.cpp

hosted with ❤ by GitHub

Pirâmide

  1. Programação dinâmica (Aula 13)

Para cada linha i da pirâmide com i caixas numeradas por colunas de j a j+i-1, a linha acima (i-1) possui i-1 caixas consecutivas, ou com colunas desde j a j+i-2, ou desde j+1 a j+i-1, já que, caso contrário, ao menos uma das caixas não possuiria uma respectiva caixa abaixo dela, na linha i. Assim, podemos utilizar uma função recursiva f(k, j) para encontrar a resposta mínima do problema, representando a pirâmide com a linha de índice k em sua base, e que por sua vez tem k caixas consecutivas a partir da coluna j. Temos, assim, que:

1. O caso base da função pode ser tomado quando k = 1, já que a resposta neste caso é apenas o valor da caixa na linha 1, coluna j.
2. Como a linha k é a base da pirâmide, a soma de todos os valores das k caixas desde a coluna j até a coluna j+k-1 estará incluída na resposta de f(k, j).
3. Pelas observações anteriores, há dois casos para a coluna inicial na linha k-1: ou j, ou j+1. Perceba que assim, a coluna final em tal linha está definida: será, respectivamente, j+k-2 ou j+k-1, já que a linha k-1 possui k-1 caixas. Como queremos a soma mínima possível dos valores das caixas, escolheremos o mínimo entre estes dois casos.

Podemos, então, finalmente montar a relação de recursão de f(k, j):
Se k = 1, f(k, j) = caixa[1][j];
Senão, f(k, j) = s[k][j...j+k-1] + min(f(k-1, j), f(k-1, j+1)), onde s[x][a...b] é a soma dos números na linha x desde a coluna a até a coluna b. No entanto, a complexidade da função é exponencial, e logo, não passa nas restrições do problema. Para tornar a complexidade aceitável, utilizaremos duas técnicas adicionais:

1. Podemos conseguir s[x][a..b] em O(1), com pré-cálculo em O(n²), da seguinte maneira: Primeiro, vamos definir uma matriz soma[x][b] como sendo a soma dos números na linha x desde a coluna 1 até a coluna b. Inicialmente, soma[x][0] = 0, para todo x tal que 1 <= x <= n. Então, soma[x][b] = soma[x][b-1]+caixa[x][b], com 1 <= b <= n. Portanto, é possível conseguir s[x][1...b] em O(1) após o pré-cálculo, já que s[x][a...b] = s[x][1...b]-s[x][1...(a-1)], ou seja, s[x][a...b] = soma[x][b]-soma[x][a-1].
2. Finalmente, usaremos uma matriz n*n chamada dp[n][n] que irá guardar todos os estados que já foram previamente calculados na recursão (essa técnica é conhecida como programação dinâmica). Assim, evitamos que um estado seja calculado mais de uma vez, tornando a complexidade da função O(n²), já que 1 <= k, j <= n. Obteremos então a resposta do problema chamando f(n, 1).
Segue o código para melhor entendimento.


//Piramide - Fase 1 P1 - 2018
//Por Lucio Cardoso
//Complexidade: O(n²)
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 110;
int num[MAXN][MAXN], soma[MAXN][MAXN], dp[MAXN][MAXN], n;
// pre-cálculo em O(n^2) para encontrar soma[][]
void pre(void)
{
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
soma[i][j] = soma[i][j-1]+num[i][j];
}
// recursão em O(n^2)
int f(int k, int j)
{
if (k == 1) return num[1][j]; // caso base
if (dp[k][j] != -1) return dp[k][j]; // se f(k,j) já foi calculado
int s = soma[k][j+k-1]-soma[k][j-1]; // soma na linha atual
int caso1 = f(k-1, j);
int caso2 = f(k-1, j+1);
// salvo em dp a resposta de f(k, j) e a retorno
return dp[k][j] = s+min(caso1, caso2);
}
int main(void)
{
cin >> n;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
cin >> num[i][j];
pre(); // faço o pré-cálculo
// inicializo dp com um valor que nunca será resposta, digamos -1
memset(dp, -1, sizeof dp);
cout << f(n, 1) << "\n"; // imprimo a resposta, começando na base
}

view raw

piramide.cpp

hosted with ❤ by GitHub