TFC 2022 - Programação Nível 2
Para conferir a prova na íntegra, clique aqui.
Portas
Escrito por Enzo Dantas
Conhecimento prévio necessário:
- Update em range (BIT ou Lazy update (Segment Tree))
- Algorimo de Kadane
Ao ler o enunciado, percebe-se que o problema pode ser dividido em duas partes: 1) Construir o array e 2) Achar o subarray máximo desse array (o valor da posição do array é o valor das cartas que estão no baú de posição ). Tal estruturação é evidente pois todos os updates são realizados antes da query, ou seja, ele é estático. Problemas dinâmicos (que possuem updates intercalados com queries) geralmente têm uma solução bem mais complexa do que a versão estática.
Primeiramente, o problema de achar o maior subarray de um array é bastante conhecido: ele pode ser implementado em (utilizando um ou uma ) ou em utilizando o algoritmo de Kadane. Como é um problema clássico, não vou explicá-lo aqui - há uma infinidade de vídeos no YouTube e blogs explicando-o.
Secundariamente, a parte de construir o array pode ser feita de várias formas. Para os leitores que têm familiaridade com lazy update, adicionar um valor a um intervalo [l,r] deve ser uma etapa simples. Tal processo é clássico e é explicado na aula do NOIC citada acima, então também não será explicado aqui.
Existe uma maneira de realizar esse update em range sem usar nenhuma das estruturas acima em tempo linear. Essa ideia é parecida com a ideia de update em range da BIT, mas usa o fato de que todos os updates são dados antes das queries, ou seja, é possível realizar esses updates offline. O código mencionado abaixo utiliza essa ideia.
Finalmente, o único detalhe restante é que a implementação deve ser feita com ou com a linha
ios::sync_with_stdio(false); cin.tie(0);
para ler o input mais rapidamente, pois o input é muito grande . Caso contrário, seu código vai receber TLE.
Clique aqui para conferir o código
Reencontro
Escrito por Caique Paiva
Conhecimento prévio necessário:
Vamos transformar o problema em um grafo, onde os vértices são as cidades e as arestas são as estradas. Primeiramente, temos que, se dois vértices e estão na mesma componente conexa, então, se pode participar do reencontro, também pode, pois, como eles estão na mesma componente conexa, significa que tem um caminho de para , logo, basta a pessoa em ir até , e então ir junto com ela para a cidade em que vai ter a reunião.
Com isso, vamos resolver a subtask 2, que é . Nesse caso, vamos dividir o grafo em várias componentes conexas, e então, a resposta vai ser a maior componente conexa do grafo, por conta do que foi dito acima. Então, para dividir o grafo em componentes conexas, vamos fazer várias dfs! A entrada da dfs vai ser int , int , onde é um vértice no grafo, e qual componente conexa a gente está marcando agora. Então, crie um vetor com elementos, e inicialize com 1 , com todos inicializados com . Agora, vamos fazer um for com e indo até , onde, se , fazemos a , e depois .
Dentro da , fazemos o seguinte: Se , paramos a dfs. Então, fazemos , e pecorremos todos os em , e chamamos . A ideia desse algoritmo todo é que, se e estão em componentes conexas diferentes, então .
Agora, vamos criar um vetor também com elementos, e então, fazemos um for de até , e fazemos . Então, vai nos dizer qual o tamanho da componente conexa , que sabemos que temos componentes conexas no total. Com isso, basta retornar o maior valor em .
Então vamos resolver o problema geral. Para calcular o tamanho de cada componente conexa já tá feito acima, e então, a chave final do problema é ver que, se ligarmos dois vértices de duas componentes conexas, elas se tornam uma componente conexa só, então, para chegar na quantidade máxima de vértices, basta ligar as maiores componentes conexas do grafo, e vamos conseguir a maior componente conexa possível.
Para fazer isso, basta fazer um sort no vetor , e somar as maiores componentes conexas.
Clique aqui para conferir o código
Xor Máximo
Escrito por Arthur Lobo
Conhecimento prévio necessário:
O problema nos fala que o conjunto tem uma peculiaridade: Para qualquer par de inteiros distintos , do conjunto , o AND entre e é menor que . Mas o que exatamente isso quer dizer?
Primeiro vamos relembrar uma propriedade das potências de 2: a soma de todas as potências de 2 menores que é menor que . Formalmente, .
Devido à essa propriedade, podemos concluir que a única forma de o AND entre dois números ser maior ou igual à é se eles tiverem algum bit (potência de 2) ativo em comum que é maior do que , pois caso contrário, o AND deles terá somente bits menores que , e, pela propriedade citada a cima, sabemos que a soma de potências de 2 menores que sempre é menor que .
Recapitulando, podemos concluir que, para qualquer , existe no máximo um elemento em que tem o bit (potência ) ativo. Então, já que , temos no máximo números maiores ou iguais à , todos os outros são menores.
Parcial 1 ()
Nessa parcial o é bem pequeno, então não precisamos nem usar a peculiaridade do conjunto para resolve-la. Podemos testar todas as combinações possíveis de sub-conjuntos e ver qual delas possui o maior XOR.
Para conseguir listar todas as combinações, podemos usar uma Bitmask, em que se o bit estiver ativo, então o elemento está no sub-conjunto que a bitmask representa, caso contrário, ele não está.
Parcial 2 ( AND )
Agora vamos precisar usar a peculiaridade que o problema nos apresenta, mas ao invés de , temos , ou seja, temos no máximo um número com cada um dos bits ativo.
Devido à propriedade de potências de , sempre vai valer a pena escolher esses número que tem os bits ativo. Podemos provar isso por contradição: Digamos que o xor atual seja e não escolhemos um número que tem um dos bits ativo, digamos que seja o bit ; se colocarmos o nesse sub-conjunto do XOR , sabemos que ele vai fazer com que o bit seja ativado (já que ele não estava ativado antes em ) e, no pior dos casos vai fazer com que os bits e sejam desativados (já que ele não possui nenhum bit maior ou igual à 4 ativo diferente de . Mas já que é maior do que , sempre vai valer a pena colocar no conjunto. Portanto, podemos concluir que todos os números que tem um dos bits ativo vai estar no nosso conjunto.
Agora só precisamos saber quais dos outros números vão entrar nesse conjunto também. Já que eles não tem nenhum bit maior que (potência ) ativo, os números que sobraram serão menores que , ou seja, serão os números , mas eles podem estar repetidos, então temos que deletar as copias desnecessárias.
Depois de deletar as repetições, o conjunto de números que estamos pensando em colocar ou não no XOR tem no máximo números, então conseguimos resolver assim como resolvemos a parcial anterior, mas primeiro temos que colocar os número que com certeza estarão no sub-conjunto do XOR.
Clique aqui para conferir o código
Laser
Escrito por Enzo Dantas
Conhecimento prévio necessário:
Após simular alguns casos mentalmente, percebe-se que a seguinte ideia parece promissora: uma matriz só pode estar completamente preenchida se 1) há um laser em todas as colunas da matriz ou se 2) há um laser em todas as linhas da matriz.
Olhemos para uma linha qualquer dessa matriz: para que todas as posições dessa linha estejam preenchidas, ou 1) há um laser naquela linha ou 2) há um laser em todas as colunas daquela linha. Assim, se uma matriz está completamente preenchida e não há um laser em todas as linhas, então há um laser em todas as colunas.
Assim, precisamos de uma estrutura que mantém quais linhas e quais colunas tem um laser e consegue adicionar e remover rapidamente um laser (a coluna e a linha dele, mais precisamente). Além disso, ela deve responder rapidamente a pergunta "em um intervalo , existe um laser em toda linha de a (ou em toda coluna)?". Em outras palavras, essa estrutura deve armazenar quais posições de um array estão "ligadas", "ligar" e "desligar" posições e responder rapidamente se todas as posições de um intervalo estão "ligadas". Para tanto, usaremos uma BIT.
Por fim, manteremos um array de frequência para as linhas e colunas. Ao adicionar um laser, devemos checar se ele é o único laser naquela coluna - se esse for o caso, atualizamos a BIT na posição correspondente (a mesma coisa é feita em relação às linhas). Assim, quando utilizarmos a operação da BIT de soma em um intervalo, contaremos apenas a quantidade de lasers em linhas (ou colunas) diferentes.
Cada operação da BIT tem uma complexidade logarítmica no tamanho do array, então a complexidade final é .