Условие задачи
Даны людей, земель и событий.
У каждого участка земли есть владелец .
Даны людей, земель и событий.
У каждого участка земли есть владелец .
Решите эти внешние задачи (например, из Codeforces) чтобы закрепить материал урока:
У каждого человека есть целевое количество ресурсов .
Каждое событие описывается как и означает, что каждый участок земли на отрезке генерирует ресурсов. Если отрезок «замыкается», то земли считаются расположенными по кругу.
То есть если человек владеет участками внутри затронутого отрезка, то этот человек получает ресурсов от этого события.
Для каждого человека нужно определить, после какого события он впервые достигает своего целевого количества. Если он никогда его не достигает, ответ — -1.
Мы можем решать задачу независимо для каждого человека.
Для одного фиксированного человека пусть означает, может ли человек достичь требуемого количества, используя только первые событий.
Эта функция монотонна:
can(x, m) истинно, то can(x, m+1) тоже истинно.Значит, для каждого человека можно сделать бинарный поиск по ответу.
Чтобы вычислить can(x, m), нужно применить первые событий. Для этого можно использовать дерево Фенвика или дерево отрезков вместе со вспомогательными функциями вроде:
add_updates(l, r)remove_updates(l, r)Тогда получаем следующее решение для одного человека:
Это работает, но слишком медленно, потому что одни и те же обновления добавляются и удаляются много раз для разных людей.
Медленная часть — не сама проверка can(x, m), а многократное пересоздание состояния для множества разных значений m.
Ключевое наблюдение: многие люди задают вопросы про одну и ту же середину m.
Поэтому вместо запуска отдельного бинарного поиска для каждого человека мы обрабатываем всех людей параллельно:
Это называется параллельный бинарный поиск.
Мы пишем рекурсивную функцию:
которая означает, что ответ для каждого человека из people лежит в интервале .
Если l == r, то ответ для всех этих людей уже зафиксирован.
Иначе:
l до m;can(x, m) истинно, идут в левую половину;Преимущество в том, что обновления переиспользуются для многих людей вместо того, чтобы пересобираться с нуля для каждого человека отдельно.
Ту же идею можно реализовать и итеративно.
Для каждого человека мы поддерживаем текущий диапазон поиска:
tl[x] — левая граница;tr[x] — правая граница.Изначально для каждого человека:
tl[x] = 0tr[x] = K + 1Затем мы повторяем следующий процесс:
m, применяя обновления по мере продвижения;m, отвечаем для всех людей, назначенных этой середине;int solve(int x) {
int l = 0, r = K + 1;
while (l < r) {
int m = (l + r) / 2;
add_updates(1, m);
if (can(x, m)) {
r = m;
} else {
l = m + 1;
}
remove_updates(1, m);
}
if (l == K + 1) {
return -1;
}
return l;
}
solve(l, r, people)
void solve(int l, int r, vector<int> people) {
if (people.empty()) {
return;
}
if (l == r) {
for (int x : people) {
if (l == K + 1) {
res[x] = -1;
} else {
res[x] = l;
}
}
return;
}
int m = (l + r) / 2;
add_updates(l, m);
vector<int> left_people, right_people;
for (int x : people) {
if (can(x, m)) {
left_people.push_back(x);
} else {
right_people.push_back(x);
}
}
remove_updates(l, m);
solve(l, m, left_people);
solve(m + 1, r, right_people);
}
void solve() {
for (int i = 1; i <= n; i++) {
tl[i] = 0;
tr[i] = K + 1;
}
for (int it = 0; it < LOG_K; it++) {
for (int i = 0; i <= K + 1; i++) {
query[i].clear();
}
for (int i = 1; i <= n; i++) {
if (tl[i] < tr[i]) {
int mid = (tl[i] + tr[i]) / 2;
query[mid].push_back(i);
}
}
for (int m = 0; m <= K; m++) {
if (m > 0) {
add_updates(m, m);
}
for (int x : query[m]) {
if (can(x, m)) {
tr[x] = m;
} else {
tl[x] = m + 1;
}
}
}
remove_updates(1, K);
}
for (int i = 1; i <= n; i++) {
if (tl[i] == K + 1) {
res[i] = -1;
} else {
res[i] = tl[i];
}
}
}