Solução Cartões

Solução por Rogério Júnior

Para ver o problema original, clique aqui.

Para resolvermos o problema, usaremos uma DP bem simples: Seja dp[ini][fim] a maior quantidade de pontos que Alberto consegue obter quando a parte da sequência que eles têm disponível é a compreendida entre os cartões de índices ini e fim na sequência original.

Deste modo, a DP é bem simples: se ini data-recalc-dims=fim" />, não há movimento possível, logo podemos obter pontos e devemos retornar 0. Caso contrário, ainda há movimentos possíveis (com no mínimo dois cartões, pois quando Alberto joga, o número de cartões é sempre par), logo, devemos olhar qual cartão Alberto devia tirar: o da ponta esquerda ou da ponta direita.

Supondo que os cartões estão salvos no array de nome vetor. Se ele remover o cartão da esquerda, ele ganhará os pontos deste cartão (vetor[esq]) mais o que ele ganhará com o resto da sequência, que agora está compreendida entre ini+1 e fim. Note que Wanderley irá jogar de modo a minimizar os pontos de Alberto, logo, veremos em qual das condições Alberto pontua menos (Wanderley tirando o cartão da esquerda ou da direita) e a retornaremos. Deste modo, se Alberto retirar o cartão da esquerda, ganhará vetor[ini]+min(dp[ini+2][fim], dp[ini+1][fim-1]). De maneira análoga, se ele retirar o da direita, ganhará vetor[fim]+min(tab[ini+1][fim-1], tab[ini][fim-2]). Basta retornarmos a melhor das escolhas.

O único problema com este código é a memória, que excede o limite do juiz se submetermos uma matriz de tamanho 10^4 \times 10^4, para ser nossa tabela de DP. Para contornar isso, usaremos o seguinte truque: usaremos uma DP bottom-up e só vamos guardar na matriz os estados que realmente precisamos para calcular os novos estados. Para calcularmos uma coluna da matriz, só precisamos das duas colunas anteriores, pois para calcularmos um estado cuja última carta é fim, só precisamos dos cálculos com os estados com fim-1 e fim-2 (observe a regra da recursão). Assim, nossa matriz só terá 3 colunas, e usaremos colunas já utilizadas, escrevendo por cima delas, quando se tornarem inúteis, para economizar memória. Quando formos calcular a coluna 3, as colunas 1 e 2 estarão lá para o cálculo, mas a escreveremos por cima da coluna 0. Quando calcularmos a coluna 4, onde precisaríamos da 3 e 2, usaremos a 0 e 2, ou seja, vamos olhar apenas para o resto de fim módulo 3.

No fim, basta retornarmos o valor de dp[1][fim \ mod \ 3]. Segue o código para melhor entendimento. Note que, nele, o nome da tabela de DP é tab.


#include <cstdio>
#include <cstring>
#include <algorithm>
#define MAXN 10010
using namespace std;
typedef long long int lli;
// função que retorna o resto (positivo) de um número módulo 3
int mod(int x){
int a=x%3;
if(a<0) return a+3;
return a;
}
lli n, vetor[MAXN], tab[MAXN][3];
// Função que calcula a DP Bottom-Up
lli dp(){
for(int fim=1; fim<=n; fim++)
for(int ini=n; ini>=1; ini--){
// se fim<ini, retorn 0
if(fim<ini) tab[ini][mod(fim)]=0;
// caso contrário, retorne o melhor entre tirar o cartão do início ou do fim
if(fim>ini) tab[ini][mod(fim)]=max(vetor[ini]+min(tab[ini+2][mod(fim)], tab[ini+1][mod(fim-1)]), vetor[fim]+min(tab[ini+1][mod(fim-1)], tab[ini][mod(fim-2)]));
}
// por fim, retorne o último valor calculado
return tab[1][mod(n)];
}
int main(){
// enquanto houver entrada
while(scanf("%lld", &n)!=EOF){
// limpe a tabela de DP
memset(tab, 0, sizeof(tab));
// leia os valores dos cartões
for(lli i=1; i<=n; i++) scanf("%lld", &vetor[i]);
// e imprima o resultado da DP
printf("%lld\n", dp());
}
return 0;
}

view raw

cartoes.cpp

hosted with ❤ by GitHub