Всего чисел, имеющих частоту k в заданном диапазоне

Как найти общие числа, имеющие частоту = k в определенном диапазоне (l, r) в заданном массиве. Всего имеется 10^5 запросов формата l,r, и каждый запрос строится на основе ответа на предыдущий запрос. В частности, после каждого запроса мы увеличиваем l на результат запроса, меняя местами l и r, если l > r. Обратите внимание, что 0<=a[i]<=10^9. Всего элементов в массиве n=10^5.

Моя попытка:

n,k,q = map(int,input().split())
a = list(map(int,input().split()))
ans = 0
for _ in range(q):
    l,r = map(int,input().split())
    l+=ans
    l%=n
    r+=ans
    r%=n
    if l>r:
        l,r = r,l
    d = {}
    for i in a[l:r+1]:
        try:
            d[i]+=1
        except:
            d[i] = 1
    curr_ans = 0
    for i in d.keys():
        if d[i]==k:
            curr_ans+=1
    ans = curr_ans
    print(ans)

Пример ввода:
5 2 3
7 6 6 5 5
0 4
3 0
4 1

Пример вывода:
2
1
1


person Mohan Singh    schedule 01.05.2019    source источник
comment
Что не работает с вашей попыткой?   -  person Willem Van Onsem    schedule 01.05.2019
comment
Я не могу сделать это лучше, чем O (n ^ 2).   -  person Mohan Singh    schedule 01.05.2019
comment
Добро пожаловать в Stack Overflow. Пожалуйста, включите код (или описание алгоритма) для вашей попытки и объясните, в чем проблема. Кроме того, если вы можете добавить несколько примеров входных данных и ожидаемых результатов, это будет очень полезно для полного понимания проблемы, которую вы пытаетесь решить.   -  person jdehesa    schedule 01.05.2019
comment
@jdehesa Готово..!   -  person Mohan Singh    schedule 01.05.2019
comment
Незначительное изменение, но вы можете упростить вторую половину своего алгоритма, если добавите from collections import Counter в начале и сделаете d = Counter(a[l:r+1]); ans = sum(1 for v in d.values() if v == k)   -  person jdehesa    schedule 01.05.2019
comment
@jdehesa Спасибо! Но то, что вы сделали, это просто оптимизация кода. Любая структура данных или алгоритм, которые могут уменьшить временную сложность, будут очень полезны.   -  person Mohan Singh    schedule 01.05.2019
comment
@MohanSingh Да, нет, я понимаю, это просто упрощение кода (это почти оптимизация, за исключением того, что Counter может быть немного быстрее, чем цикл). Я пытаюсь придумать структуру данных, поддерживающую это, но пока не уверен.   -  person jdehesa    schedule 01.05.2019
comment
Вы представляете, сколько уникальных чисел содержится в массиве?   -  person jdehesa    schedule 01.05.2019
comment
@jdehesa В вопросе нет упоминания об уникальных числах. Но мы можем легко их посчитать, используя len(set(a)).   -  person Mohan Singh    schedule 01.05.2019
comment
Кстати, в вашем коде кажется, что l и r меняются в соответствии с последним значением ans, верно? Я думаю, что это не соответствует вводу и выводу примера (я предполагаю, что значения l и r всегда относятся к началу массива?)   -  person jdehesa    schedule 01.05.2019
comment
Нет, они совершенно правы. Первоначально ans=0 Для запроса 1: ` l = (l+ans)%n = (0+0)%5 = 0, r = (r+ans)%n = (4+0)%5 = 4 ` Now,ans = 2 Для запроса 2: ` l = (l+ans)%n = (3+2)%5 = 0, r = (r+ans)%n = (0+2)%5 = 2 ` Now,ans = 1 Для запроса 3: ` l = (l+ans)%n = (4+1)%5 = 0, r = (r+ans)%n = (1+1)%5 = 2 ` Теперь, ans =1   -  person Mohan Singh    schedule 01.05.2019


Ответы (2)


Если количество различных значений в массиве не слишком велико, вы можете рассмотреть возможность хранения массивов такой же длины, как и входной массив, по одному на уникальное значение, подсчитывая количество вхождений значения до каждой точки. Затем вам просто нужно вычесть конечные значения из начальных значений, чтобы найти, сколько существует частотных совпадений:

def range_freq_queries(seq, k, queries):
    n = len(seq)
    c = freq_counts(seq)
    result = [0] * len(queries)
    offset = 0
    for i, (l, r) in enumerate(queries):
        result[i] = range_freq_matches(c, offset, l, r, k, n)
        offset = result[i]
    return result

def freq_counts(seq):
    s = {v: i for i, v in enumerate(set(seq))}
    counts = [None] * (len(seq) + 1)
    counts[0] = [0] * len(s)
    for i, v in enumerate(seq, 1):
        counts[i] = list(counts[i - 1])
        j = s[v]
        counts[i][j] += 1
    return counts

def range_freq_matches(counts, offset, start, end, k, n):
    start, end = sorted(((start + offset) % n, (end + offset) % n))
    num = 0
    return sum(1 for cs, ce in zip(counts[start], counts[end + 1]) if ce - cs == k)

seq = [7, 6, 6, 5, 5]
k = 2
queries = [(0, 4), (3, 0), (4, 1)]
print(range_freq_queries(seq, k, queries))
# [2, 1, 1]

Вы также можете сделать это быстрее с NumPy. Поскольку каждый результат зависит от предыдущего, вам в любом случае придется зацикливаться, но вы можете использовать Numba, чтобы действительно ускорить процесс:

import numpy as np
import numba as nb

def range_freq_queries_np(seq, k, queries):
    seq = np.asarray(seq)
    c = freq_counts_np(seq)
    return _range_freq_queries_np_nb(seq, k, queries, c)

@nb.njit  # This is not necessary but will make things faster
def _range_freq_queries_np_nb(seq, k, queries, c):
    n = len(seq)
    offset = np.int32(0)
    out = np.empty(len(queries), dtype=np.int32)
    for i, (l, r) in enumerate(queries):
        l = (l + offset) % n
        r = (r + offset) % n
        l, r = min(l, r), max(l, r)
        out[i] = np.sum(c[r + 1] - c[l] == k)
        offset = out[i]
    return out

def freq_counts_np(seq):
    uniq = np.unique(seq)
    seq_pad = np.concatenate([[uniq.max() + 1], seq])
    comp = seq_pad[:, np.newaxis] == uniq
    return np.cumsum(comp, axis=0)

seq = np.array([7, 6, 6, 5, 5])
k = 2
queries = [(0, 4), (3, 0), (4, 1)]
print(range_freq_queries_np(seq, k, queries))
# [2 1 2]

Давайте сравним его с исходным алгоритмом:

from collections import Counter

def range_freq_queries_orig(seq, k, queries):
    n = len(seq)
    ans = 0
    counter = Counter()
    out = [0] * len(queries)
    for i, (l, r) in enumerate(queries):
        l += ans
        l %= n
        r += ans
        r %= n
        if l > r:
            l, r = r, l
        counter.clear()
        counter.update(seq[l:r+1])
        ans = sum(1 for v in counter.values() if v == k)
        out[i] = ans
    return out

Вот быстрый тест и время:

import random
import numpy

# Make random input
random.seed(0)
seq = random.choices(range(1000), k=5000)
queries = [(random.choice(range(len(seq))), random.choice(range(len(seq))))
           for _ in range(20000)]
k = 20
# Input as array for NumPy version
seq_arr = np.asarray(seq)
# Check all functions return the same result
res1 = range_freq_queries_orig(seq, k, queries)
res2 = range_freq_queries(seq, k, queries)
print(all(r1 == r2 for r1, r2 in zip(res1, res2)))
# True
res3 = range_freq_queries_np(seq_arr, k, queries)
print(all(r1 == r3 for r1, r3 in zip(res1, res3)))
# True

# Timings
%timeit range_freq_queries_orig(seq, k, queries)
# 3.07 s ± 1.11 s per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit range_freq_queries(seq, k, queries)
# 1.1 s ± 307 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit range_freq_queries_np(seq_arr, k, queries)
# 265 ms ± 726 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)

Очевидно, что эффективность этого зависит от характеристик данных. В частности, если повторяющихся значений меньше, затраты времени и памяти на создание таблицы счетчиков будут приближаться к O(n2).

person jdehesa    schedule 01.05.2019
comment
В freq_counts_np вы не можете создать 2D-массив comp, потому что он превысит пределы памяти, так как максимальный элемент может быть 10 ^ 9, а максимальное n = 10 ^ 5. Таким образом, двумерный массив размером (10 ^ 5) * (10 ^ 9) превысит пределы памяти. - person Mohan Singh; 02.05.2019
comment
@MohanSingh Но количество столбцов в таблице будет равно количеству уникальных значений в массиве, которое не может быть больше, чем сам массив. В худшем случае у вас будет таблица размером (10^5)*(10^5). Это все еще слишком много, но предполагается, что будут повторяющиеся значения, поэтому уникальных чисел будет не так много. - person jdehesa; 02.05.2019
comment
@MohanSingh Вы также можете составить таблицу по частям, взяв максимальное количество столбцов за раз и перебирая их. Требуется меньше памяти, но больше итераций. Но если у вас действительно очень мало повторений, это может не сработать в любом случае. - person jdehesa; 02.05.2019
comment
Да, конечно. Поэтому должен быть другой подход к этой проблеме. Возможно, используя дерево сегментов или разложение квадратного корня. - person Mohan Singh; 02.05.2019
comment
Используя дерево сегментов, вы можете попытаться сохранить карту частот в каждом узле. Но и для этого временная сложность будет высокой, когда значения различны. Пожалуйста, поправьте меня, если я ошибаюсь. - person atishaya11; 02.05.2019

Допустим, входной массив A, |A|=n. Я собираюсь предположить, что количество различных элементов в A намного меньше, чем n.

Мы можем разделить A на сегменты sqrt(n), каждый из которых имеет размер sqrt(n). Для каждого из этих сегментов мы можем рассчитать карту от элемента до счетчика. Построение этих карт занимает O(n) времени.

Выполнив эту предварительную обработку, мы можем ответить на каждый запрос, сложив вместе все карты, полностью содержащиеся в (l,r), которых не более sqrt(n), затем добавив любые дополнительные элементы (или перейдя на один сегмент и вычтя) , а также sqrt(n).

Если есть k различных элементов, это занимает O (sqrt (n) * k), поэтому в худшем случае O (n), если на самом деле каждый элемент A различен.

Вы можете отслеживать элементы, которые имеют желаемое количество, комбинируя хэши и дополнительные элементы.

person Dave    schedule 02.05.2019