Comentário Noic OBI 2019 - Fase 3 - Programação Nível 1

Comentário por Pedro Racchetti

Para conferir a prova na íntegra, clique aqui.

Coleção de Upas

Conhecimentos prévios necessários:

Para resolver esse problema, temos que ter uma observação essencial: para qualquer k, temos que 2^0 + 2^1 + ... 2^{k-2} + 2^{k-1} < 2^k. Isso é facilmente compreendido, ao se entender o conceito de representação binária de um número, onde pode se ver que 2^0 + 2^1 + ... 2^{k-2} + 2^{k-1} = 2^k - 1.

Portanto, podemos iterar pelas upas, da direita para a esquerda, e na i-ésima upa, marcamos todas as upas que não combinam com ela e somamos um ao total de upas que Mayuri deve manter na coleção, a menos que ela já esteja marcada já que com certeza, os valores somados de todas as que não combinam com ela, ao serem somadas, não ultrapassarão o valor da atual.

No final, basta imprimir o total, e iterar, agora da esquerda para a direita, pelo vetor de marcação, imprimindo todas que não estão marcadas.

Complexidade: O(N + M)

Segue o código, comentado, para melhor compreensão da solução:


#include<bits/stdc++.h>
using namespace std;
const int MAXN = 112345;
vector<int> upanaocomb[MAXN]; //guardaremos as relacoes entre upas em um vetor de vectors,
// muito como em um grafo.
bool marc[MAXN]; //vetor de marcacao, descrito na explicacao
int n, m, totaldeupas = 0;
int main(){
scanf("%d %d", &n, &m);
for(int i = 0; i < m; i++){
int u, v;
scanf("%d %d", &u, &v);
upanaocomb[u].push_back(v);
upanaocomb[v].push_back(u);
}
for(int i = n; i > 0; i--){
if(!marc[i]){
// se a upa i ainda nao foi marcada,
// ela nao afetara nenhuma upa com valor maior que o dela
// e com certeza, é melhor usar ela do que qualquer upa que nao combina com ela
totaldeupas++;
for(int j = 0; j < upanaocomb[i].size(); j++){
int upa = upanaocomb[i][j];
marc[upa] = true;
}
}
}
printf("%d\n", totaldeupas);
for(int i = 1; i <= n; i++){
// imprimimos todas as upas nao marcadas
if(!marc[i]){
printf("%d ", i);
}
}
printf("\n");
}

view raw

upas.cpp

hosted with ❤ by GitHub

Linhas de Ônibus

Conhecimentos prévios necessários:

Para esse problema, representaremos o sistema de ônibus da grande cidade na China como um grafo, com as vértices representando os pontos de ônibus, e as arestas representarão as ligações de uma linha, armazenando as duas vértices que ela liga, e também em qual linha ela está.

Iremos então usar um algoritmo de menor caminho para achar o menor número de linhas usadas entre a origem e o destino, caminhando pelo grafo da seguinte maneira:

  • Quando passarmos para uma aresta que muda a linha que estamos atualmente, somamos 1 à distância.
  • Quando passarmos para uma aresta que não muda a linha que estamos atualmente, somamos 0 à distância

Note porém, que uma simples BFS não funciona, já que para trocar de uma linha para outra, temos peso 1, e na mesma linha, temos peso 0, portanto, teremos que usar uma BFS 0 - 1.

Complexidade: Como cada vértice pode se conectar a duas vezes o número de linhas, teremos O(T + TL) ou simplesmente O(TL).

Segue o código, comentado, para melhor compreensão da solução:


#include <bits/stdc++.h>
using namespace std;
const int maxn = 512;
vector<pair<int, int> > grafo[maxn]; //guardaremos no grafo a vértice
//em que cada aresta liga, e sua linha
int t, l, o, d, dist[maxn];
void bfs(int x) {
memset(dist, 0x3f3f3f3f, sizeof dist); //Inicializando distancia como "infinito"
dist[x] = 1;
deque<pair<int, int> > fila;
for(int i = 0; i < grafo[x].size(); i++){
int v = grafo[x][i].first, linha = grafo[x][i].second;
dist[v] = 1;
fila.push_back(make_pair(v, linha));
}
while (!fila.empty()) {
int u = fila.front().first;
int linhaatual = fila.front().second;
fila.pop_front();
for(int i = 0; i < (int)grafo[u].size(); i++) {
int v = grafo[u][i].first;
int linha = grafo[u][i].second;
int d;
if(linha != linhaatual) d = 1; //se temos que trocar de linha, o peso deve ser 1
else d = 0; //caso contrário, deve ser 0
if(dist[v] > dist[u] + d) {
dist[v] = dist[u] + d;
if(d == 1) fila.push_back(make_pair(v, linha)); // Caso a distancia seja 1, insere o vertice normalmente na fila
else fila.push_front(make_pair(v, linha)); // Caso seja 0, insere como prioridade.
}
}
}
}
int main(){
scanf("%d %d %d %d", &t, &l, &o, &d);
for(int i = 0; i < l; i++){
int k;
scanf("%d", &k);
int ultk;
scanf("%d", &ultk);
for(int j = 1; j < k; j++){
//conectaremos o ultimo terminal no atual, e vice versa
int v;
scanf("%d", &v);
grafo[ultk].push_back(make_pair(v, i));
grafo[v].push_back(make_pair(ultk, i));
}
}
bfs(o); //iniciaremos a BFS no vértice O
printf("%d\n", dist[d]); // Imprimimos quantas linhas tivemos que usar.
return 0;
}

Parcelamento Sem Juros

Conhecimento prévio necessário:

Para esse problema, cada parcela terá como preço base V/P, e então nas parcelas i, tal que i \leq Vmod(P), ou seja o resto da divisão de V por P, somamos 1.

Complexidade: O(P)

Segue o código, comentado, para melhor compreensão da solução:


#include<bits/stdc++.h>
using namespace std;
int v, p;
int main(){
scanf("%d", &v);
scanf("%d", &p);
for(int i = 1; i <= p; i++){
if(i <= v%p) printf("%d\n",v/p + 1 );
else printf("%d\n", v/p );
}
}

Manchas de Pele

Conteúdos prévios necessários:

Para esse problema, iremos representar a imagem dada como um grafo, usando a idéia de Dx Dy, onde ao invés de guardarmos os vizinhos, usaremos dois vetores, que guardam em qual direção deve-se ir na matriz, economizando memória. Como a OBI não recomenda uma implementação recursiva para esse problema, usaremos uma BFS para atravessar o grafo, guardando uma matriz de marcação, e sempre que encontrarmos um ponto não marcado que faz parte de uma mancha, aumentaremos 1 na resposta.

Complexidade: $$O(N*M)

Segue o código, comentado, para melhor compreensão da solução:


#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1123, MAXM = 1123;
//vetores auxiliares
int dx[5] = {1, 0, -1, 0};
int dy[5] = {0, 1, 0, -1};
bool marc[MAXN][MAXM];
int imagem[MAXN][MAXM];
int componentes = 0;
int n, m;
void BFS(int i, int j){
queue<pair<int, int> > fila; //na fila, guardamos as coordenadas das vértices
fila.push(make_pair(i, j));
marc[i][j] = true;
while(!fila.empty()){
int x = fila.front().first;
int y = fila.front().second;
fila.pop();
for(int k = 0; k < 4; k++){
int xatual = x + dx[k];
int yatual = y + dy[k];
//se ja visitamos o vizinhom ou ele não faz parte de um mancha,
//avançamos para o próximo vizinho
if(marc[xatual][yatual] || !imagem[xatual][yatual]) continue;
marc[xatual][yatual] = true;
fila.push(make_pair(xatual, yatual));
}
}
}
int main(){
cin >> n >> m;
for(int i = 1; i <= n; i++){
for(int j = 1; j <= m; j++){
cin >> imagem[i][j];
}
}
for(int i = 1; i <= n; i++){
for(int j = 1; j <= m; j++){
if(imagem[i][j] == 1 && !marc[i][j]){
//se essa casa nao foi visitada, e faz parte de uma imagem, temos que encontrar todos os seus vizinhos
componentes++;
BFS(i, j);
}
}
}
cout << componentes << endl;
}

view raw

mancha.cpp

hosted with ❤ by GitHub