Задача о покрытии набора точек отрезками равной длины

Существует классическая задача о покрытии точек отрезками - на числовой оси находятся n целочисленных точек и задача просит найти минимальное количество отрезков указанной длины способных покрыть все точки. Точка считается покрытой если находится на границе отрезка. Например, нам даны пять точек с координатами {1, 2, 3, 4, 5} и отрезки единичной длины - для такого набора ответ на задачу будет 3, тк все точки можно покрыть 3 отрезками [1,2], [3,4], [4,5], двумя отрезками этого не выйдет. Такая задача легко решается жадным алгоритмом.

int Cover(std::vector<int>& data, int l) {
//сортируем вектор координат точек
    std::sort(data.begin(), data.end());
    

    int interval_count = 1; // количество отрезков
    int r_point = data[0] + l; //правая граница последнего установленного отрезка
    for(int& elem : data){
        if(elem > r_point){
            interval_count += 1;
            r_point = elem + l;
        }
    }
    
    return interval_count;
}

Так вот меня интересует обратная задача - найти минимальную длину отрезков, когда по условию дан набор точек и количество отрезков. На самом деле ничего лучше перебора разных длин я не придумал :( Своё решение я построил на основе бинарного поиска по длинам в диапазоне (0, data.back() - data.front()). Каждая длина в поиске ставится в проверку - покроют ли k отрезков (по условию) длиной l набор точек data (переделанное решение классической задачи.

bool CanCover(const std::vector<int>& data, int l, int k){
    
    --k;
    int r_point = data[0] + l;
    
    for(const int& elem : data){
        if(elem > r_point){
            --k;
            r_point = elem + l;
            if (k < 0) return false;
        }
    }
    
    return true;
}

Вот сам бинарный поиск

int Binary(std::vector<int>& data, int k){
    if(k >= data.size()) return 0; //если отрезков больше чем точек, то отрезки могут быть длиной 0 и лежать на каждой из точек
    
    std::sort(data.begin(), data.end());
    
    //границы поиска
    int left = 0;
    int right = data.back() - data.front();
    
    while(left < right){
            int mid = (left + right) / 2;
            
            if(CanCover(data, mid, k)) right = mid;
            else left = mid + 1;
    }
    
    return left;
}

Но как выяснилось в результате тестов, решение через бинарный поиск недостаточно быстрое. Может есть способ как найти минимальную длину отрезков без прохода по всем длинам? Что-нибудь наподобие жадного алгоритма, как для классической задачи?


Ответы (1 шт):

Автор решения: Qwertiy

Алгоритм эффективнее пока не придумывается, но есть несколько замечаний:

for(int& elem : data){
for(const int& elem : data){

Это медленнее, чем просто

for (int elem : data) {
int right = data.back() - data.front();

Очевидно, что можно точнее:

int right = (data.back() - data.front() + k - 1) / k;

Ещё в качестве оптимизации можно проверить k==1.

→ Ссылка