Comentário por Henrique Vianna
Conhecimento prévio necessário:
Esse problema pode ser reduzido ao seguinte - dado um vetor de tamanho em que para todo , sendo que cada tipo de elemento tem um custo especifico, deve-se responder a consultas da forma: dado um intervalo , entre os elementos cuja frequência é no mínimo nesse intervalo, qual o valor mínimo de , sendo o tipo do elemento em questão?
Subtask 1: e , valendo pontos.
Para a resolução dessa parcial, basta passarmos pelo intervalo inteiro para cada uma das consultas, calculando a resposta com o auxílio de um vetor de frequência, por exemplo.
Subtask 2: , valendo pontos.
Como , qualquer elemento que ocorra alguma vez no intervalo pode ser considerado para a resposta. Sendo assim, basta utilizarmos uma Segment Tree, que guarda o custo minimo considerando um dado intervalo. Com isso, conseguimos responder cada consulta em .
Subtask 3: e , valendo pontos
Para a resolução dessa parcial, podemos fazer a técnica de decomposição em blocos de tamanho : o Algoritmo de Mo. Como qualquer problema de Mo, só nos preocupamos em como adicionar e remover um índice ao intervalo atual. Para fazer isso, faremos uso de algumas estruturas auxiliares:
- guarda a frequência de no intervalo atual.
- Manteremos, além disso, um multiset que guarda os custos dos tipos de brinquedos que podem ser considerados para a resposta.
Sendo assim, podemos sintetizar as operações de adição e remoção como o seguinte:
- Adição: deve-se somar a . Caso passe a ser , podemos adicionar seu custo ao multiset.
- Remoção: deve-se subtrair a . Caso passe a ser , deve-se remover o custo do multiset.
Vale notar que, por conta do TL generoso de segundos, é possível que essa solução passe para pontos com um Mo bem otimizado (veja o código.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <bits/stdc++.h> | |
using namespace std; | |
#define int long long int | |
const int MAX = 1e6 + 15; | |
const int INF = 0x3f3f3f3f; | |
int n, m, k, q, c[MAX], t[MAX], T; | |
int ans[MAX], block[MAX], f[MAX]; | |
struct Query | |
{ | |
int l, r, id; | |
Query(int a = 0, int b = 0, int c = 0) : l(a), r(b), id(c) { } | |
bool operator < (Query other) | |
{ | |
if(block[l] != block[other.l]) return block[l] < block[other.l]; | |
return (block[l] % 2 == 0 ? r < other.r : r > other.r); | |
} | |
}; | |
int32_t main(void) | |
{ | |
ios_base::sync_with_stdio(false); | |
cin.tie(NULL); | |
cin >> n >> m >> k; | |
for(int i = 1; i <= m; i++) cin >> c[i]; | |
for(int i = 1; i <= n; i++) cin >> t[i]; | |
T = sqrt(n) + 1; | |
for(int i = 1; i <= n; i++) block[i] = i / T; | |
cin >> q; | |
vector<Query> queries; | |
for(int i = 1; i <= q; i++) | |
{ | |
int l, r; cin >> l >> r; | |
queries.emplace_back(l, r, i); | |
} | |
sort(queries.begin(), queries.end()); | |
multiset<int> work; | |
auto add = [&](int x){ if(++f[x] == k) work.insert(c[x]); }; | |
auto remove = [&](int x){ if(--f[x] == k - 1) work.erase(work.find(c[x])); }; | |
auto getAns = [&](void){ if(work.empty()) return (int)-1; return k * (*work.begin()); }; | |
int l = 0, r = -1; | |
for(auto [curL, curR, id] : queries) | |
{ | |
while(l > curL) add(t[--l]); | |
while(r < curR) add(t[++r]); | |
while(l < curL) remove(t[l++]); | |
while(r > curR) remove(t[r--]); | |
ans[id] = getAns(); | |
} | |
for(int i = 1; i <= q; i++) | |
cout << ans[i] << '\n'; | |
} |
Subtask 4: Sem restrições adicionais.
Para a solução final, utilizaremos a técnica de line sweep, respondendo as queries de forma offline. Guardaremos um vector<int> para cada um dos tipos de brinquedos, adicionando as posições em que ele ocorreu. Passaremos pelos índices de a , mantendo uma Segment Tree que guarda em toda posição o custo de . Ativamos uma posição apenas quando ela se torna a -ésima ultima ocorrência de seu tipo. Para responder uma consulta, basta fazermos uma query de minimo na nossa Segment Tree. Para melhor compreensão, confira o código abaixo:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <bits/stdc++.h> | |
using namespace std; | |
#define int long long | |
const int MAXN = 1e6 + 10; | |
const int INF = 0x3f3f3f3f; | |
array<int, 4*MAXN> seg; | |
void update(int pos, int ini, int fim, int id, int val) | |
{ | |
if (id < ini || id > fim) return; | |
if (ini == fim) | |
{ | |
seg[pos] = val; | |
return; | |
} | |
int m = (ini + fim) >> 1; | |
int e = 2*pos; int d = 2*pos + 1; | |
update(e, ini, m, id, val); | |
update(d, m+1, fim, id, val); | |
seg[pos] = min(seg[e], seg[d]); | |
} | |
int query(int pos, int ini, int fim, int p, int q) | |
{ | |
if (q < ini || p > fim) return INF; | |
if (p <= ini && fim <= q) return seg[pos]; | |
int m = (ini + fim) >> 1; | |
int e = 2*pos; int d = 2*pos + 1; | |
return min(query(e, ini, m, p, q), query(d, m+1, fim, p, q)); | |
} | |
int32_t main() | |
{ | |
ios_base::sync_with_stdio(false); | |
cin.tie(NULL); | |
int N, M, K; | |
cin >> N >> M >> K; | |
vector<int> custo(M+1); | |
for (int i = 1; i <= M; i++) cin >> custo[i]; | |
vector<int> v(N+1); | |
for (int i = 1; i <= N; i++) cin >> v[i]; | |
int Q; | |
cin >> Q; | |
vector<vector<pair<int, int>>> queries(N+1); vector<int> resp(Q); | |
for (int i = 0; i < Q; i++) | |
{ | |
int L, R; | |
cin >> L >> R; | |
queries[R].emplace_back(L, i); | |
} | |
seg.fill(INF); | |
vector<vector<int>> brinq(M+1); | |
for (int i = 1; i <= N; i++) | |
{ | |
brinq[v[i]].push_back(i); | |
if (brinq[v[i]].size() >= K) update(1, 1, N, brinq[v[i]][brinq[v[i]].size() - K], custo[v[i]]); | |
for (auto [L, id] : queries[i]) resp[id] = query(1, 1, N, L, i); | |
} | |
for (auto x : resp) cout << (x == INF ? -1 : x * K) << '\n'; | |
} |