Comentário NOIC Seletiva IOI 2018 – Dia 3

Seletiva IOI 2018 – Dia 3

Se você quiser se preparar para a OBI e seletiva, 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 nossos materiais.

Clique aqui para conferir a prova na íntegra *A seletiva da OBI 2017 foi feita para a IOI 2018

Cabo de Guerra

Comentário escrito por Caique Paiva

Conhecimento prévio necessário:

Primeiro, veja que, se $$N \le 20$$, nós poderiamos brutar todas as somas possíveis, e conseguiriamos fazer o problema em $$O(2^N)$$. Então, temos que achar uma maneira mais esperta de brutar as respostas. Com isso, perceba que fazer uma Knapsack dp é uma maneira de brutar todas as somas. Quando fazemos ela para achar uma soma máxima por exemplo, nós sempre salvamos a maior resposta anterior para não temos problema de complexidade, mas no final, nós testamos todos os casos possíveis.

Com essa observação, perceba que $$F_i$$ também é pequeno, então vamos fazer a seguinte dp:

$$dp(pos, qtd, sum)$$

Onde ela vai guardar se, no intervalo $$[1, pos]$$ usando $$qtd$$ números, a soma dá igual a $$sum$$ (A dp é 1 se é verdade, e é 0 caso contrário). Veja que $$pos, qtd \le N \le 50$$ e $$sum \le N \cdot max(F_i) \le 50^2$$, ou seja, a quantidade de memória que estamos utilizando é $$50^4 < 10^7$$, que cabe na memória e no tl. Com isso, o caso base da dp vai ser $$dp(0, 0, 0) = 1$$, $$dp(0, x, y) = 0, \forall (x > 0$$ ou $$y > 0)$$ e $$dp(x, y, z) = 0$$ com $$x >0$$ e $$y = 0$$ ou $$z = 0$$. Então, a transição vai ser

$$dp(pos, qtd, sum) = max(dp(pos-1, qtd, sum), dp(pos-1, qtd-1, sum-F[pos]))$$

(Os raciocínios são iguais a de uma knapsack padrão).

Com isso, como achar a solução do nosso problema. Bem, tem uma forma mais rápida que essa com matemática, mas uma possibilidade é ver que se $$dp(n, n/2, i) = 1$$, então o valor de um grupo vai ser $$i$$, e o de outro vai ser $$SUM-i$$, onde $$SUM = F_1+F_2+\cdots +F_n$$, e a diferença entre os dois grupos vai ser abs(SUM-2*i), então, basta criar uma variável auxiliar $$res$$, e se $$dp(n, n/2, i) = 1$$, então fazemos $$res = max(res, abs(SUM-2*i))$$.

Clique aqui para conferir o código

Equipe

Comentário escrito por Estela Baron

Subtask 20 pontos:  1 <= N <= 20

Conhecimento prévio:

Como $$N$$ é pequeno, podemos simular todas as possibilidades de equipes. Para cada conjunto, vemos se a equipe é válida, ou seja, vemos se cada tema é dominado por pelo menos uma pessoa e se não é por pelo menos uma. Caso a equipe seja válida, atualizamos a resposta caso ela contenha o menor número de pessoas. Para gerar todos os subconjuntos, podemos utilizar uma bitmask, em que cada pessoa é representada por um bit. A complexidade fica $$O(2^N)$$.

Subtask 80 pontos: 1 <= N <= 200

Conhecimento prévio:

Para cada pessoa, vamos representar as suas habilidades por meio de uma bitmask: mask_pessoa: se o $$assunto_i$$ for dominado, então o $$bit_i$$ estará ligado. Além disso, vamos criar também uma bitmask inversa: inv para representar os assuntos não dominados: todo assunto não dominado tem o bit aceso. Com isso, podemos representar um conjunto de assuntos dominados ou não como um inteiro. A partir disso, vamos fazer: dp[mask assuntos dominados][mask assuntos não dominados] = menor quantidade de pessoas para ter as duas masks.

Para calcular os estados, vamos fazer um loop e passar por cada pessoa:

  • em cada pessoa, vamos simular todas os estados possíveis, ou seja, desde dp[0][0] até dp[$$2^k – 1$$][$$2^k – 1$$]
  • para cada estado, vemos se vale ou não a pena adicionar a pessoa à equipe: fazemos dp[$$mask_i$$][$$mask_j$$] = min ( dp[$$mask_i$$][$$mask_j$$], 1 + dp[($$mask_i$$ | mask_pessoa) – mask_pessoa][($$mask_j$$ | inv) – inv]
  • Observe que precisamos realizar a operação OR| para só retirar o que precisamos.

Assim a resposta estará em dp[$$2^K -1$$][$$2^K-1$$]

A complexidade fica $$O(N * 2^K * 2^K)$$.

Clique aqui para conferir o código

Tribo

Comentário escrito por Henrique Vianna

Conhecimento prévio necessário:

 

Perceba, primeiramente, que o problema pode ser reduzido ao seguinte: Dada uma árvore com pesos nas arestas, escolha um subconjunto de exatamente $$k$$ vértices de tal forma que a soma dos pesos das arestas necessárias para interligá-los seja mínima.

Intuitivamente, trata-se de um problema de DP em árvore. Define-se então:

$$dp[u][j]$$ é o minimo custo possível para escolher exatamente $$j$$ vértices na subárvore de $$u$$, sendo $$u$$ um desses escolhidos.

Imagine agora que queremos calcular a $$dp$$ para um vértice especifico que chamaremos de $$u$$. Para tanto, teremos que “juntar” as respostas de seus filhos.  Logo, faremos uso de uma segunda $$dp$$, de tal forma que:

$$dp2[i][j]$$ é o minimo custo possível para escolher exatamente $$j$$ vértices, considerando os $$i$$ primeiros filhos de $$u$$.

Inicialmente, todos os valores de $$dp2$$ devem ser iguais a infinito, exceto $$dp2[0][1]$$, que deve ser igual a $$0$$. A recorrência de $$dp2[i][j]$$ é bastante similar à DP clássica da Knapsack: deve-se brutar, para o filho atual, quantos vértices de sua subárvore serão escolhidos. Chamaremos esse numero de $$c$$. Sendo assim:

$$dp2[i][j] = min(dp2[i – 1][j], dp2[i – 1][j – c] + dp[v][c] + w)$$, tal que $$1 \leq c \leq j$$

Na recorrência acima, assim como no código a seguir, $$v$$ é o filho sendo processado e $$w$$ é o peso da aresta entre $$u$$ e $$v$$. Ao final, para termos os valores de $$dp[u][j]$$ de fato, basta fazermos:

$$dp[u][j] = dp2[f][j]$$, sendo $$f$$ o numero de filhos de $$u$$.

Note, entretanto, que assim como na Knapsack podemos facilmente eliminar a primeira informação da $$dp2$$, utilizando a técnica conhecida como Memory Saving Trick. Basta passarmos por $$j$$ em ordem decrescente, assim como feito no código abaixo.

A resposta final será:

$$resp = min(dp[i][k])$$, $$1 \leq i \leq n$$

Clique aqui para conferir o código