codeforces 1461D, что за волшебный метод офлайн-запроса, почему он такой быстрый?

искусственный интеллект алгоритм

Всем привет, добро пожаловать в тему codeforces.

Тема, которую мы сегодня выбрали, это вопрос D из 1461 сеансов, этот вопрос сдали 3702 человека, что является средним по сложности. Это несложно, а также очень подходит для того, чтобы ученики тренировали руки. Кроме того, этосовершенно новая идеяЭто не появлялось в наших предыдущих статьях, я думаю, это вдохновит вас.

Ссылка на сайт:codeforces.com/contest/146…

Без дальнейших церемоний, давайте начнем.

смысл названия

Нам дан массив из n положительных целых чисел, после того, как мы сможем выполнить некоторые операции над этим массивом, мы можемПусть сумма элементов в массиве будет числом, которое мы хотим.

Наша работа над массивом разделена на три шага.Первый шаг заключается в том, что мы сначала вычисляем промежуточное значение середины массива. Определение середины здесь не является ни медианой, ни средним значением, а средним значением максимального и минимального значений. То есть mid = (min + max)/2.

Получив мид, мыРазделите массив на две части в зависимости от размера элементов в массиве. Делим элементы меньше или равные середине на первую часть, а элементы больше середины делим на вторую часть. Это эквивалентно преобразованию исходного большого массива в два разных небольших массива.

Теперь у нас есть всего q запросов, каждый из которых содержит целое число k. Мы хотим, чтобы программа дала нам, можем ли мы сделать сумму элементов результирующего массива равной k с помощью вышеуказанных операций.

Если да, выведите Yes, иначе выведите No.

Образец

Сначала введите целое число t, которое представляет количество групп тестовых данных (1t1001 \le t \le 100).

Введите два целых числа n и q для каждого набора данных, где n — количество элементов в массиве, а q — количество запросов (1n,q1051\le n, q\le 10^5). Затем введите строку из n целых чисел во второй строке, каждое из которыхaia_i, оба1ai1061 \le a_i \le 10^6.

Каждая из следующих q строк содержит целое число, представляющее число k, которое мы запрашиваем (1k1091 \le k \le 10^9), чтобы сумма всех n и q не превышала10510^5.

Для каждого запроса мы выводим Да или Нет, чтобы указать, может ли он быть выполнен.

В первом примере массив, с которого мы начинаем, равен[1, 2, 3, 4, 5]. При первом выполнении операции получаем mid=(1+5)/2=3. Таким образом, массив делится на[1, 2, 3]и[4, 5]. за[1, 2, 3]Двигаясь дальше, мы можем получить mid = (1 + 3) / 2 = 2, поэтому массив можно разделить на[1, 2]и[3].[1, 2]окончательно можно разделить на[1]и[2].

Мы можем найти, что k, которое можно найти, равно:[1, 2, 3, 4, 5, 6, 9, 15].

отвечать

Эта проблема не очень сложна, и решение относительно ясно.

Легко обнаружить, что операции над массивами на самом деле фиксированы, потому чтоОпределяются максимальное и минимальное значения в массиве. Нам просто нужно отсортировать массив побинарный поискРазделение массива может быть сделано легко. Точно так же для суммирования массивов нам не нужно использовать циклы для операций накопления, которые легко сделать с префиксными суммами.

Так что единственная трудность этого вопроса состоит в том, как судить о том, можно ли найти искомое k.На самом деле это не сложно, нам нужно только поставить егокак поисковый вопрос, чтобы найти все k, до которых можно добраться. Это базовый глубокий поиск, и он не слишком сложен.

bool examine(int l, int r, int k) {
    if (l == r) return (tot[r] - tot[l-1] == k);
    // 如果[l, r]的区间和已经小于k了,那么就没必要去考虑继续拆分了
    if (l > r || tot[r] - tot[l-1] < k) {
        return false;
    }
    if (tot[r] - tot[l-1] == k) {
        return true;
    }
    // 中间值就是首尾的均值
    int m = (nums[l] + nums[r]) / 2;
    // 二分查找到下标
    int md = binary_search(l, r+1, m);
    if (md == r) return false;
    return examine(l, md, k) | examine(md+1, r, k);
}

Этот кусок логики сам по себе написать несложно, но когда мы его запишем, мы обнаружим, что AC по-прежнему нельзя использовать, и он истечет по тайм-ауту. Я долго думал об этом и, наконец, понял, в чем проблема.

проблема иДело не в том, что сложность нашего поиска здесь слишком высока, а в том, что количество поисков слишком велико.. q В лучшем случае будет10510^5, а сложность каждого поискаlog2n\log^2 n. Поскольку наши поисковые слоиlogn\log n, плюс получившийсяlogn\log n, поэтому предельная сложность105log2n10 ^ 5 log^2 n, где n равно10510^5, это значение приблизительно21072 \cdot 10^7, плюс некоторые другие расходы, так что я застрял.

Для решения этой проблемы мы ввелиАвтономный механизм.

Офлайн-онлайн здесь хорошо понимается, так называемый онлайн-запрос означает, что каждый раз, когда мы получаем запрос, мы опрашиваем его один раз, а затем возвращаем результат. В офлайне наоборот, мыСначала запрашивайте все запросы, а затем возвращайте по одному. Многие студенты могут быть очень удивлены, разве это не одно и то же? Только порядок другой.

Большую часть времени это то же самое,Но иногда наш автономный запрос может выполняться в пакетном режиме.. Например, в этом вопросе мы можем через одну рекурсию узнать все k, которые могут быть сформированы за один раз, а затем сохранить их в наборе. После этого нам нужно только проверить, существует ли набор по входному запросу, потому что скорость запроса набора намного быстрее, чем поиск через рекурсию. Это эквивалентно сжатию q раз запросов в один, что экономит время работы, и в определенной степени это также алгоритм пространства-в-времени.

Давайте посмотрим на код для более подробной информации:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <vector>
#include <cmath>
#include <cstdlib>
#include <string>
#include <map>
#include <set>
#include <algorithm>
#include "time.h"
#include <functional>
#define rep(i,a,b) for (int i=a;i<b;i++)
#define Rep(i,a,b) for (int i=a;i>b;i--)
#define foreach(e,x) for (__typeof(x.begin()) e=x.begin();e!=x.end();e++)
#define mid ((l+r)>>1)
#define lson (k<<1)
#define rson (k<<1|1)
#define MEM(a,x) memset(a,x,sizeof a)
#define L ch[r][0]
#define R ch[r][1]
const int N=100050;
const long long Mod=1000000007;
 
using namespace std;

int nums[N];
long long tot[N];
set<long long> ans;

int binary_search(int l, int r, int val) {
    while (r - l > 1) {
        if (nums[mid] <= val) {
            l = mid;
        }else {
            r = mid;
        }
    }
    return l;
}

// 离线查询,一次把所有能构成的k放入set当中
void prepare_ans(int l, int r) {
    if (l > r) return ;
    if (l == r) {
        ans.insert(nums[l]);
        return ;
    }
    ans.insert(tot[r] - tot[l-1]);
    int m = (nums[l] + nums[r]) / 2;
    int md = binary_search(l, r+1, m);
    if (md == r) return ;
    prepare_ans(l, md);
    prepare_ans(md+1, r);
}


int main() {
    int t;
    scanf("%d", &t);
    rep(z, 0, t) {
        ans.clear();
        MEM(tot, 0);
        int n, q;
        scanf("%d %d", &n, &q);
        rep(i, 1, n+1) {
            scanf("%d", &nums[i]);
        }
        sort(nums+1, nums+n+1);
        rep(i, 1, n+1) {
            tot[i] = tot[i-1] + nums[i];
        }
        prepare_ans(1, n);
        rep(i, 0, q) {
            int k;
            scanf("%d", &k);
            // 真正请求起来的时候,我们只需要在set里找即可
            if (ans.find(k) != ans.end()) {
                puts("Yes");
            }else {
                puts("No");
            }
        }
    }

    return 0;
}

Онлайн в оффлайн — очень распространенный навык в вопросах соревнований., который часто используется для решения очень больших проблем с запросами. Напрямую сказать не сложно, но немного хлопотно, если не знаешь и хочешь разобраться сам. Если у вас есть время, лучше всего испытать это на себе с кодом.

Сегодняшняя проблема алгоритма обсуждается здесь, и я искренне надеюсь, что каждый будет получать что-то каждый день. Если вам все еще нравится сегодняшний контент, пожалуйста, зайдите на одинТройная поддержкабар~(Нравится, подписывайтесь, делайте репост)

Оригинальная ссылка, обратите внимание