OBI 2023 – Fase 3 – Programação Nível 2

OBI 2023 – Fase 3 – Programação Nível 2

Se você quiser se preparar para a OBI, não deixe de conferir o Roteiro de estudos de informática, e também a página de informática do NOIC, com todos os nossos materiais.

Pirâmide

Comentário escrito por Murilo Maeda Kataoka

Conhecimento prévio necessário:

Para esse problema, podemos simplesmente testar todas as possíveis ordens dos 6 números (que são, no total, $$6! = 720$$) e estabelecer quais posições pertencerão a qual camada. Isto é, guardo os 6 números da entrada em um vetor e determino que as posições $$0,1,2$$ são a primeira camada, $$3,4$$ são a segunda e  $$5$$ é a terceira. Depois, testo todas as ordenações dos 6 números possíveis e checo se em alguma dessas ordenações a condição do enunciado é válida. Se sim, então é possível montar, caso contrário, não é possível montar.

Clique aqui para conferir o código

Transportes

Comentário escrito por Enzo Dantas

Conhecimento prévio necessário:

Subtask 1 (11 pontos): K=1

Como todas as arestas são do mesmo tipo, só precisamos pagar para usar a primeira aresta. Se for possível ir de $$A$$ a $$B$$ (checamos isso com uma dfs), printe $$P[1]$$, caso contrário printe -1.

Subtask 4 (11 pontos): $$K \le 100$$

Para essa subtask, usaremos uma ideia clássica de Dijkstra com vértices auxiliares.

Defina $$dist[u][c]$$ = menor distância para chegar no vértice $$u$$ com a cor $$c$$.

Transição: estamos no vértice $$u$$ com a cor $$c$$ e vamos passar por todas suas arestas. Agora estamos olhando para uma aresta $$(u,v,w)$$ (ou seja, uma aresta entre $$u$$ e $$v$$ de cor $$w$$). Se a aresta atual é da cor que estamos agora, essa aresta tem custo 0. Caso contrário ele tem o custo definido no input. Ou seja, se $$c=w$$, então $$dist[v][w] = dist[u][c]+0$$. Caso contrário, $$dist[v][w] = dist[u][c] + P[w]$$.

Como o primeiro vértice do Dijkstra não terá cor definida, podemos dizer que ele tem uma cor 0, pois ela será diferente de todas as outras cores.

Complexidade: O grafo auxiliar criado tem $$K*N$$ vértices e $$K+M$$ arestas. Pela análise de complexidade normal do Dijkstra, a complexidade final é $$O(KM + KNlog)$$.

Subtask 5 (11 pontos): $$N \le 10^5$$, $$K \le N$$

Para resolver o problema completo otimizaremos a ideia da subtask anterior. Em vez de passar por todas as arestas quando estamos no vértice $$(u,c)$$, passaremos apenas pelos vértices de cor $$c$$ (criamos uma lista de adjacência para cada cor). Evidentemente, isso não considera a troca de de cores. Para resolver isso, criamos um vértice auxiliar de cor 0 para todos os vértices, o qual visita todas as arestas do vértice e é visitado com custo 0 quando estamos com alguma cor diferente de 0 no vértice em questão. Assim, passaremos por cada aresta apenas duas vezes (uma vez no vértice de sua cor e outra vez no vértice sem cor), e a complexidade do Dijkstra ficará O(M+Nlog).

Note que, se declarássemos um array global $$dist[n][c]$$ como fizemos na subtask passada, usaríamos $$O(NK)$$ de memória, o que passa dos limites de memória. Sendo assim, usaremos um map em vez de um array. Perceba que não passaremos por todos os $$NK$$ vértices: um vértice $$(u,c)$$ só pode ser acessado se existir uma aresta de $$u$$ de cor $$c$$. Logo, acessaremos apenas $$\sum{grau_i}$$ vértices no total. Pelo mesmo motivo, também usaremos um map para criar a lista de adjacência de cada cor.

Complexidade: para armazenar e acessar todas as listas de adjacência com map é $$O(Mlog)$$, o que não ultrapassa a complexidade do Dijkstra comum. Logo, a complexidade final é $$O(M+Nlog)$$.

Clique aqui para conferir o código

Oficina Mecânica

Comentário escrito por Caique Paiva

Conhecimentos prévio necessários:

Para esse problema, uma pessoa pode pensar em vários algoritmos gulosos diferentes, porém a maior parte deles dá errado, então, é importante que você saiba provar que algoritmos funcionam e saber construir exemplos que quebram um algoritmo. Vamos provar uma observação antes de construir o algoritmo. Primeiramente, defina $$ \{ X_1, X_2, \cdots, X_n \} $$ uma permutação de $$ \{ T_1, T_2, \cdots , T_n \} $$ de modo que, quando trocarmos 2 carros $$X_i, X_j$$ no mecânico, temos que $$i < j$$, e além disso, a resposta do problema é minima.

Observação: Se $$i < j$$ e trocamos $$X_i$$ por $$X_j$$, então $$X_i < X_j$$.

Prova: Vamos olhar para um mecânico $$M_t$$. Seja $$P_1, P_2, \cdots, P_l$$ os carros que passaram por ele, nessa ordem. Veja que

  • $$P_1$$ esperou 0 segundos para entrar
  • $$P_2$$ esperou $$P_1M_t$$ segundos para entrar
  • $$P_3$$ esperou $$P_1M_t + P_2M_t$$ segundos para entrar.
  • $$P_l$$ esperou $$P_1M_t + P_2M_t + \cdots + P_{l-1}M_t$$ segundos para entrar

Então, a somas dos segundos esperados para esse mecânico é $$((l-1)P_{1} + (l-2)P_2 + \cdots + P_{l-1})M_t$$, com isso, se $$P_i > P_j$$ com $$i < j$$, então, podemos trocar eles dois de ordem, então conseguimos uma resposta menor, o que é um absurdo, pois essa é a resposta optimal, então $$P_i < P_j$$ sempre que $$i < j$$.

 

Com isso, podemos supor que $$ \{ X_1 < X_2 < \cdots < X_n \} $$. Essa observação não é tão importante assim, a parte mais importante é que ela nos mostra a forma da resposta para cada mecânico, que é $$((l-1)P_{1} + (l-2)P_2 + \cdots + P_{l-1})M_t$$, com $$P_1 < P_2 < \cdots < P_{l-1}$$. Então, vamos tentar criar o algoritmo guloso colocando os carros no . Vamos olhar para o $$X_n$$, qual mecânico queremos colocar ele?

Bem, como $$X_n$$ é maior que todos os outros elementos do vetor, temos que sua contribuição na resposta final vai ser nula, então podemos colocar ele em qualquer mecânico. Sendo mais geral, os últimos $$n-m$$ carros não contribuirão na resposta, então, seja $$X_i$$ o primeiro carro em qual colocar ele vai afetar nossa resposta, então, podemos escolher colocar ele no $$M_1$$, assim aumentando nossa resposta em $$X_iM_1$$, ou então no $$M_2$$, assim aumentando em $$X_iM_2$$, e assim vai. Veja que, gulosamente o optimal é pegar o $$j$$ tal que $$M_j$$ é minimo, e assim aumentando nossa resposta em $$M_jX_i$$!

Agora, vamos olhar para o $$X_{i-1}$$. Dá mesma maneira do anterior, gulosamente, é melhor pegar o menor $$M_k$$, mas se pegarmos o mesmo em que colocamos o $$X_i$$, a nossa resposta vai aumentar em $$2X_{i-1}M_j$$ ao invés de só $$X_{i-1}M_j$$, portanto, só vale a pena pegar esse $$M_j$$ se $$2M_j$$ ainda for menor que todos os outros $$M_k$$. Vendo esses casos bases, é fácil de ver qual vai ser nosso algoritmo guloso.

Primeiro, vamos olhar para o nosso vetor em ordem decrescente. Seja $$ \{ V_1 > V_2 > \cdots > V_n \} $$ o nosso vetor $$X$$ ordenado em ordem decrescente.

  • Passo $$i$$: Para cada mecânico $$M_j$$, seja $$C_j$$ a quantidade de elementos que já colocamos nele. Então, vemos qual é o mínimo entre $$M_jC_j$$, seja ele $$M_jC_k$$. Adicionamos a nossa resposta $$V_iM_kC_k$$, e aumentamos $$C_j$$ em 1.

Para calcular esses mínimos, podemos manter esses valores numa priority_queue, e então, a complexidade do problema fica $$O(MlogM + N)$$.

Clique aqui para conferir o código

Bonecas

Comentário escrito por Estela Baron

Conhecimento prévio necessário:

Uma dica interessante para este problema é ordenarmos os nossos valores, pois a relação entre as alturas importam. Portanto a dica é: se a ordem dos itens não importar, tente ordenar de alguma forma para ver se alguma ideia surge.

Subtarefa 1 <= $$T_i$$ <= 2

Vamos ordenar os valores de T em ordem crescente. Após isso, o nosso vetor pode ficar:

  • 111…111 ou 222…222

Observe que, nos dois casos, a resposta vai ser 0. Para os outros casos, vamos separar os valores em triplas. Quando os valores passam de 1 para 2, temos: ou …112…, ou …122… (para todos os outros casos, temos uma tripla só de 1 seguida de uma só de 2). Para …112… a diferença continua sendo 0 (entre os dois menores). Para …122.. temos dois casos: se tivermos outra tripla antes só de 1, podemos “trocar” o 1 da última posição com o 2 que está na segunda, ficando com (1,1,2) e (1,1,2). Assim, a diferença continua 0. O problema é se não tivermos uma tripla só de 1 antes. Por isso, devemos analisar se o N é maior que 3K. Se for, então podemos simplesmente “excluir” o 1 e ficar com 1,2,2,2…2,2,2, tornando a diferença 0. Caso contrário, a resposta vai ser 1.

Subtarefa N <= 30

Vamos fazer duas observações: se ordenarmos em ordem decrescente, podemos manter o número de bonecas com valores menores e isso irá auxiliar o cálculo da nossa resposta – vamos utilizar uma dp. Além disso, suponha que temos selecionamos quatro bonecas cujos valores serão usados na diferença, sendo x,y,z a diferença entre cada valor:

Como eles serão valores que serão contados em nossa resposta, veja que é sempre melhor agrupar as bonecas mais próximas uma das outras (T_a e T_b) e (T_c e T_d), pois ficamos com a soma dos balanceamentos igual a $$x^2 + z^2$$. Com as outras configurações, teríamos (T_a e T_c) e (T_b e T_d): $$(x + y)^ 2 + (y + z)^2$$ ou (T_a e T_d) e (T_b e T_c): $$(x + y + z)^2 + y^2$$.

Com isso, vamos utilizar uma dp para calcularmos as respostas com os seguintes parâmetros: dp[posição que estamos analisando para ser ou não a boneca com o menor valor do trio][quantidade de trios já formados][quantas bonecas não foram utilizadas] = menor valor de balanceamento possível ( dp[pos][i][j]). Observe que o parâmetro das bonecas não utilizadas servirá para guardar quantos valores maiores que T_pos estão disponíveis para ser a boneca de maior valor do trio – só podemos fazer isso porque os valores estão ordenados em ordem decrescente. Para calcular cada estado dp[pos][i][j], fazemos o mínimo entre:

  • dp[pos-1][i][j-1] (caso não utilizarmos a boneca com aquele valor para ser a menor do trio)
  • caso a boneca daquela posição for a menor do trio, vamos iterar por todas as bonecas das posições menores que pos para ser a segunda boneca maior- vamos definir a posição da outra boneca como l: dp[l-1][i-1][j – (pos – l – 1)] + (T_l – T_pos)$$^2$$

Iniciamos todos os estados da dp com um valor muito grande para indicar que não foi calculado – também para dizer que é impossível. Temos dp[0][0][0] = 0 como caso base. A complexidade dessa ideia é $$O(N^3 * K)$$.

Subtarefa N <= 1000 e K <= 100

Para esta tarefa, vamos utilizar a mesma ideia da anterior. No entanto, vamos remover um dos parâmetros: quantas bonecas não foram utilizadas. Observe que esta informação nos é útil para garantir que há bonecas maiores em um número suficiente para formar a quantidade de trios que queremos. Porém, podemos suprimir este parâmetro. Em vez de guardá-lo, vamos verificar se a quantidade de bonecas já vistas é maior ou igual a 3 vezes o número de trios (ou seja, se pos >= 3*i). Se não for, marcamos que o estado não é possível. Caso contrário, prosseguimos com o cálculo da dp vendo os estados anteriores. Vamos continuar escolhendo de forma quadrática qual a menor e a segunda menor boneca. A complexidade da ideia é $$O(N^2 * K)$$.

Solução Completa:

A observação essencial que devemos fazer é que, se escolhemos a boneca com T_x para ser a menor do trio, devemos também escolher a boneca com o menor valor maior ou igual a T_x, ou seja, T_x-1. Suponha que temos os valores em ordem decrescente T_a, T_b, T_c, T_d e T_e. Digamos que a resposta é (T_a e T_b) e (T_c e T_e) para k = 2. Veja que podemos melhorar a resposta pois T_d está mais próximo de T_c do que T_e está de T_c (também poderíamos falar que T_d está mais perto de T_e). Logo, se for para fixar uma boneca como a menor do trio, temos que selecionar a boneca imediatamente maior para ser a segunda para minimizar o valor. Com isso transformamos a nossa dp em: dp[posição][quantidade de trios já formados] = menor valor (dp[pos][i]). Para calcular os estados:

  • verificamos se posição >= 3*quantidade de trios formados. Caso a condição não seja cumprida, dp[pos][i] = inf (valor muito grande)
  • fazemos o mínimo entre dp[pos-1][i] e dp[pos-1][i-2] + (T[pos -1] – T[pos])$$^2$$

O caso base é dp[1][0] = 0 e a resposta vai estar armazenada em dp[N][k]. A complexidade fica $$O(N*K)$$.

Clique aqui para conferir o código

Fastfood

Comentário escrito por Arthur Lobo

Conhecimento prévio necessário:

O problema pede para que minimizemos o máximo. Essa expressão “minimizar o máximo” é comumente associada à busca binária na resposta. A base para todo o problema aqui vai ser que fazer fazer uma busca binária vendo se é possível formar os grupo de modo que a a distância máxima seja no máximo $$D$$. Mas como fazer essa busca?

Obs: $$dist(i,j)$$ vai ser a distaância entre os pontos $$i$$ e $$j$$, ou seja, $$ dist(i,j) = |X_i-X_j| + |Y_i-Y_j|$$.

Subtask 2 ($$N \leq 2000$$)

Depois de fixar um $$D$$ na busca binária, vamos transformar o problema em um grafo. Vamos ligar uma aresta entre todos os pares de vértices que não podem estar no mesmo grupo, ou seja, criamos uma aresta entre $$i$$ e $$j$$ se $$dist(i,j) > D$$.

Agora temos um grafo em que queremos pintar cada vértice de azul ou vermelho e uma aresta significa que os vértices não podem ter a mesma cor. Isso é exatamente a descrição de um Grafo Bipartido.

Podemos checar se um grafo é bipartido tentando colorir ele usando uma dfs. Quando estamos um vértice azul, colorimos todos os seus vizinhos de vermelho e vice-versa. Se um vértice já está colorido de uma cor e tentamos colorir ele com outra, então é impossível bicolorir o grafo.

Essa solução funciona em $$O(N^2)$$ por checada da busca binária, então $$O(N^2logD)$$.

Subtask 3 ($$N \leq 10^5$$)

Primeiro, digamos que temos uma função $$MaxDist(A,B)$$ em que, dado dois conjuntos de pontos $$A$$ e $$B$$, retorna o par $$(i,j)$$, com $$i \in A$$ (está contido) e $$j \in B$$, com maior distância de manhattan, e também retorna essa distância $$dmax$$ ($$MaxDist(A,B) = (dmax,i,j)$$. Suponha também que essa função funciona em $$O(logN)$$ ou $$O(1)$$. No final eu explico como fazer isso, por enquanto só imagine que podemos fazer isso.

Vamos dividir os pontos em $$3$$ conjuntos:

  • Conjunto $$A$$ dos pontos azuis. Começa apenas com o ponto $$1$$ (pois, sem perda de generalidade, podemos falar que o ponto $$1$$ é azul).
  • Conjunto $$V$$ dos pontos vermelhos. Começa vazio.
  • Conjunto $$B$$ dos pontos brancos (ainda não coloridos). Começa com todos os pontos menos o ponto $$1$$.

Agora vamos, aos poucos, colocar os pontos de $$B$$ em $$A$$ e $$V$$. Se tiver algum ponto $$i$$ em $$B$$ que está mais longe do que $$D$$ de $$1$$, então obrigatoriamente esse ponto é vermelho, certo? Porque se ele fosse pintado de azul, então teriam dois pontos no mesmo conjunto com uma distância maior do que $$D$$.

A ideia é repetidamente colocar pontos brancos que “não podem” estar no grupo azul no grupo vermelho e vice-versa. Vamos repetidamente fazer esses dois passos.

  • Não pode estar no conjunto azul -> vai pro vermelho: Seja $$MaxDist(B,A) = (dmax,i,j)$$, se $$dmax > D$$, então o ponto $$j$$ deve ser pintado de vermelho. Tiramos ele do conjunto $$B$$ e colocamos no $$V$$.
  • Não pode estar no conjunto vermelho -> vai pro azul: Seja $$MaxDist(B,V) = (dmax,i,j)$$, se $$dmax > D$$, então o ponto $$j$$ deve ser pintado de azul. Tiramos ele do conjunto $$B$$ e colocamos no $$A$$.

Mas e se em algum momento nenhuma das duas condições acontecerem, ou seja, se não tiver nenhuma obrigatoriedade a ser feita? Podemos pegar qualquer ponto em $$B$$ e mandar ele tanto para $$A$$ quanto para $$V$$, tanto faz (a prova do porque pode fica como um exercício para o leitor, não é difícil).

No final basta checar se $$MaxDist(A,A)$$ ou $$MaxDist(V,V)$$ é maior que $$D$$ (sim, a função funciona entre dois conjuntos iguais). Se algum for, então é impossível colorir os pontos com a distância $$D$$ e temos que pegar uma distância maior do que $$D$$ na busca binária.

Função mágica de maior distância.

Digamos que temos dois pontos, $$(X_1,Y_1),(X_2,Y_2)$$ e queremos saber a distância de manhattan deles, mas vamos tentar encontrar outro jeito de representá-la sem usar os módulos.

Primeiro, vamos lembrar que $$|a-b| = max(a-b,b-a)$$, mas podemos expandir isso ainda mais, transformando a soma de dois módulos em apenas um máximo: $$|a-b|+|c-d| = max(a-b,b-a) + max(c-d,d-c) = max(a-b+c-d, a-b-c+d, -a+b+c-d, -a+b-c+d)$$.

Dessa maneira, podemos representar $$dis(1,2)$$ como máximo entre esses 4 valores:

  1. $$(X_1+Y_1) – (X_2+Y_2)$$
  2. $$(X_1-Y_1) – (X_2-Y_2)$$
  3. $$(X_2+Y_2) – (X_1+Y_1)$$
  4. $$(X_2-Y_2) – (X_1-Y_1)$$

Vamos fazer isso com dois conjunto $$A$$ e $$B$$ agora. Já que só precisamos pegar o maior entre esses 4 valores, é isso que vamos fazer, vamos ver cada um dos casos. Com $$i \in A$$ (está contido) e $$j \in B$$:

  1. $$(X_i+Y_i) – (X_j+Y_j) \rightarrow i$$ vai ser o ponto em $$A$$ com maior $$(X+Y)$$ enquanto $$j$$ vai ser o ponto em $$B$$ com menor $$(X+Y)$$.
  2. $$(X_i-Y_i) – (X_j-Y_j) \rightarrow i$$ vai ser o ponto em $$A$$ com maior $$(X-Y)$$ enquanto $$j$$ vai ser o ponto em $$B$$ com menor $$(X-Y)$$.
  3. $$(X_j+Y_j) – (X_i+Y_i) \rightarrow i$$ vai ser o ponto em $$A$$ com menor $$(X+Y)$$ enquanto $$j$$ vai ser o ponto em $$B$$ com maior $$(X+Y)$$.
  4. $$(X_j-Y_j) – (X_i-Y_i) \rightarrow i$$ vai ser o ponto em $$A$$ com menor $$(X-Y)$$ enquanto $$j$$ vai ser o ponto em $$B$$ com maior $$(X-Y)$$.

Para manter os maiores e menores valores de $$X+Y$$ e $$X-Y$$ enquanto colocamos/tiramos pontos dos conjunto, podemos usar dois sets do C++, um para $$X+Y$$ e outro para $$X-Y$$. Dessa maneira, para cada iteração da busca binária nosso algoritmo de checar funciona em $$O(NlogN)$$, deixando uma complexidade de $$O(NlogNlogD)$$ para a nossa solução. Dependendo da implementação e da constante, essa solução pode ou não passar no tempo limite.

Já que apenas adicionamos pontos em $$A$$ e $$V$$ e apenas removemos pontos de $$B$$, é possivel evitar o uso dos sets deixar a solução $$O(NlogD)$$. Isso será deixado como um exercício para o leitor. O código do site apresenta essa solução em $$O(NlogD)$$.

Clique aqui para conferir o código