Happiness
¿No es conveniente cómo el enunciado nos dice qué tenemos que hacer? (Intentemos demostrar por nuestra cuenta la condición necesaria descrita en el enunciado.)
Necesitamos una estructura de datos que soporte lo siguiente:
- Insertar números en un multiconjunto .
- Borrar números de .
- Comprobar si existe algún número tal que .
Solución 1 - Árbol de Segmentos disperso
Como queremos manejar consultas de rango con actualizaciones, la opción obvia sería usar un BIT o un árbol de segmentos.
Sin embargo, los números que queremos insertar pueden ser muy grandes (hasta ). Como hay que responder las consultas online, usar un BIT queda descartado.
Por suerte, ¡podemos usar un árbol de segmentos disperso en su lugar!
Aun así necesitamos hallar una forma de comprobar si existe el descrito arriba.
La observación clave es que si sabemos que
para algún , entonces también sabemos que
para todo . Esto es cierto porque
Esto significa que el siguiente número que hay que revisar es .
Si elegimos de esta forma los números que revisamos, entonces solo revisaremos números por cada consulta (ya que el -ésimo número que revisamos es siempre al menos ).
La complejidad de este algoritmo es entonces .
Código
#include "happiness.h"
struct Node {
long long l, r, val;
Node *lc, *rc;
Node(long long L, long long R) : l(L), r(R), val(0), lc(nullptr), rc(nullptr) {}
void update(long long p, long long v) {
val += v;
if (l != r) {
long long mid = (l + r) / 2;
if (p > mid) {
if (!rc) rc = new Node(mid + 1, r);
rc->update(p, v);
} else {
if (!lc) lc = new Node(l, mid);
lc->update(p, v);
}
}
}
long long query(long long a, long long b) {
if (r < a || l > b) return 0;
if (r <= b && l >= a) return val;
long long ret = 0;
if (lc) ret += lc->query(a, b);
if (rc) ret += rc->query(a, b);
return ret;
}
};
Node *root;
bool check() {
long long curr = 1, sm = root->val;
while (curr < sm) {
long long t = root->query(1, curr);
if (t < curr) return false;
curr = t + 1;
}
return true;
}
bool init(int coinsCount, long long maxCoinSize, long long coins[]) {
root = new Node(1, maxCoinSize);
for (int i = 0; i < coinsCount; i++) root->update(coins[i], coins[i]);
return check();
}
bool is_happy(int event, int coinsCount, long long coins[]) {
for (int i = 0; i < coinsCount; i++) root->update(coins[i], event * coins[i]);
return check();
}Solución 2 - Cubetas
Una solución más inteligente es partir los elementos de en las cubetas para cada de 0 a . Nótese que hay exactamente cubetas.
¿Por qué es esto conveniente?
En primer lugar, solo los dos elementos más pequeños de cada cubeta podrían ser malos (ya que ). Esto reduce de forma significativa la cantidad de elementos que hay que revisar.
En segundo lugar, podemos guardar la suma de los elementos de cada cubeta y ser capaces de hallar las sumas de prefijos de las cubetas en tiempo .
Podemos usar multiconjuntos para guardar los elementos de las cubetas, así que la complejidad de este algoritmo es . En la práctica, esta solución corre más rápido que la primera.
Código
#include "happiness.h"
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
multiset<ll> todo[40];
ll SUM[40];
void ad(ll x, int b) {
int p = 0;
while ((1LL << (p + 1)) <= x) p++;
if (b == 1) {
todo[p].insert(x), SUM[p] += x;
} else {
todo[p].erase(todo[p].find(x)), SUM[p] -= x;
}
}
bool ok() {
ll sum = 0;
for (int i = 0; i < 40; ++i)
if (todo[i].size() > 0) {
ll x = *begin(todo[i]);
if (sum + 1 < x) return 0;
if (todo[i].size() > 1 && sum + x + 1 < *next(begin(todo[i]))) return 0;
sum += SUM[i];
}
return 1;
}
bool init(int coinsCount, long long maxCoinSize, long long coins[]) {
for (int i = 0; i < coinsCount; ++i) ad(coins[i], 1);
return ok();
}
bool is_happy(int event, int coinsCount, long long coins[]) {
for (int i = 0; i < coinsCount; ++i) {
if (event == -1) ad(coins[i], -1);
else ad(coins[i], 1);
}
return ok();
}