Найдите k из n подмножества с максимальной площадью - PullRequest
9 голосов
/ 08 мая 2020

У меня n баллов, и мне нужно найти максимальную объединенную область между k баллами (k <= n). Итак, это сумма площади этих точек за вычетом общей площади между ними.

enter image description here] 1

Предположим, у нас есть n=4, k=2. Как показано на изображении выше, площади рассчитываются от каждой точки до начала координат, а конечная площадь представляет собой сумму области B и D (только один раз подсчитывая площадь их пересечения). Нет смысла доминировать

Я реализовал алгоритм программирования восходящей динамики c, но где-то есть ошибка. Вот код, который распечатывает лучший результат:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct point {
    double x, y;
} point;
struct point *point_ptr;

int n, k;
point points_array[1201];
point result_points[1201];

void qsort(void *base, size_t nitems, size_t size,
           int (*compar)(const void *, const void *));

int cmpfunc(const void *a, const void *b) {
    point *order_a = (point *)a;
    point *order_b = (point *)b;
    if (order_a->x > order_b->x) {
        return 1;
    }
    return -1;
}

double max(double a, double b) {
    if (a > b) {
        return a;
    }
    return b;
}

double getSingleArea(point p) {
    return p.x * p.y;
}

double getCommonAreaX(point biggest_x, point new_point) {
    double new_x;
    new_x = new_point.x - biggest_x.x;
    return new_x * new_point.y;
}

double algo() {
    double T[k][n], value;
    int i, j, d;
    for (i = 0; i < n; i++) {
        T[0][i] = getSingleArea(points_array[i]);
    }
    for (j = 0; j < k; j++) {
        T[j][0] = getSingleArea(points_array[0]);
    }
    for (i = 1; i < k; i++) {
        for (j = 1; j < n; j++) {
            for (d = 0; d < j; d++) {
                value = getCommonAreaX(points_array[j - 1], points_array[j]);
                T[i][j] = max(T[i - 1][j], value + T[i - 1][d]);
            }
        }
    }
    return T[k - 1][n - 1];
}

void read_input() {
    int i;
    fscanf(stdin, "%d %d\n", &n, &k);
    for (i = 0; i < n; i++) {
        fscanf(stdin, "%lf %lf\n", &points_array[i].x, &points_array[i].y);
    }
}

int main() {
    read_input();
    qsort(points_array, n, sizeof(point), cmpfunc);
    printf("%.12lf\n", algo());
    return 0;
}

с вводом:

5 3
0.376508963445 0.437693410334
0.948798695015 0.352125307881
0.176318878234 0.493630156084
0.029394902328 0.951299438575
0.235041868262 0.438197791997

, где первое число равно n, второе k и следующие строки - координаты x и y каждой точки соответственно, результат должен быть: 0.381410589193,

, тогда как у меня 0.366431740966. Значит, я упустил момент?

Ответы [ 2 ]

1 голос
/ 08 мая 2020

Это небольшая аккуратная задача, спасибо за сообщение! В остальном я предполагаю, что ни одна точка не является преобладающей , то есть нет точек c, таких, что существует точка d с c.x < d.x и c.y < d.y. Если да, то использование c никогда не является оптимальным (почему?), Поэтому мы можем спокойно игнорировать любые доминирующие точки. Ни один из ваших примеров не является доминирующим.

Ваша проблема демонстрирует оптимальную подструктуру: как только мы решили, какой элемент должен быть включен в первую итерацию, у нас снова возникнет та же проблема с k - 1 и n - 1 (удаляем выбранный элемент из набора допустимых точек). Конечно, отдача зависит от выбранного набора - мы не хотим пересчитывать площади дважды.

Я предлагаю предварительно отсортировать все точки по их значению x в порядке возрастания. Это гарантирует, что значение выбранных точек может быть вычислено как кусочно-размерные области. Я проиллюстрирую это на примере: предположим, у нас есть три точки (x1, y1), ..., (x3, y3) со значениями (2, 3), (3, 1), (4, .5). Тогда общая площадь, покрытая этими точками, составит (4 - 3) * .5 + (3 - 2) * 1 + (2 - 0) * 3. Надеюсь, это имеет смысл на графике:

figure

По нашему предположению, что нет доминирующих точек, у нас всегда будет такая слабо убывающая цифра. Таким образом, предварительная сортировка решает всю проблему «подсчета площадей дважды»!

Давайте превратим это в алгоритм динамического c программирования. Рассмотрим набор n точек, помеченных {p_1, p_2, ..., p_n}. Пусть d[k][m] будет максимальной площадью подмножества размером k + 1, где (k + 1) -я точка в подмножестве - это точка p_m. Ясно, что m не может быть выбрано в качестве (k + 1) -й точки, если m < k + 1, поскольку тогда у нас будет подмножество размером меньше k + 1, что никогда не является оптимальным. У нас есть следующая рекурсия:

d[k][m] = max {d[k - 1][l] + (p_m.x - p_l.x) * p_m.y, for all k <= l < m}.

Начальные случаи, когда k = 1 - это прямоугольные angular области каждой точки. Для решения проблемы достаточно начальных случаев вместе с уравнением обновления. Я оцениваю следующий код как O(n^2 * k). Термин, возведенный в квадрат в n, вероятно, также можно уменьшить, поскольку у нас есть упорядоченная коллекция и, возможно, мы сможем применить двоичный поиск, чтобы найти лучшее подмножество за log n время, уменьшив n^2 до n log n. Я оставляю это вам.

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

#include <stdio.h>

typedef struct point
{
    double x;
    double y;
} point_t;


double maxAreaSubset(point_t const *points, size_t numPoints, size_t subsetSize)
{
    // This should probably be heap allocated in your program.
    double d[subsetSize][numPoints];

    for (size_t m = 0; m != numPoints; ++m)
        d[0][m] = points[m].x * points[m].y;

    for (size_t k = 1; k != subsetSize; ++k)
        for (size_t m = k; m != numPoints; ++m)
            for (size_t l = k - 1; l != m; ++l)
            {
                point_t const curr = points[m];
                point_t const prev = points[l];

                double const area = d[k - 1][l] + (curr.x - prev.x) * curr.y;

                if (area > d[k][m])  // is a better subset
                    d[k][m] = area;
            }

    // The maximum area subset is now one of the subsets on the last row.
    double result = 0.;

    for (size_t m = subsetSize; m != numPoints; ++m)
        if (d[subsetSize - 1][m] > result)
            result = d[subsetSize - 1][m];

    return result;
}

int main()
{
    // I assume these are entered in sorted order, as explained in the answer.
    point_t const points[5] = {
            {0.029394902328, 0.951299438575},
            {0.176318878234, 0.493630156084},
            {0.235041868262, 0.438197791997},
            {0.376508963445, 0.437693410334},
            {0.948798695015, 0.352125307881},
    };

    printf("%f\n", maxAreaSubset(points, 5, 3));
}

Используя данные примера, которые вы предоставили, я нахожу оптимальный результат 0.381411, что и нужно.

0 голосов
/ 10 мая 2020

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

JavaScript код:

function f(pts, k){
  // Sort the points by x
  pts.sort(([a1, b1], [a2, b2]) => a1 - a2);

  const n = pts.length;
  let best = 0;
  
  // m[k][j] represents the optimal
  // value if the jth point is chosen
  // as rightmost for k points
  let m = new Array(k + 1);
  
  // Initialise m
  for (let i=1; i<=k; i++)
    m[i] = new Array(n);
  for (let i=0; i<n; i++)
    m[1][i] = pts[i][0] * pts[i][1];
    
  // Build the table
  for (let i=2; i<=k; i++){
    for (let j=i-1; j<n; j++){
      m[i][j] = 0;
      for (let jj=j-1; jj>=i-2; jj--){
        const area = (pts[j][0] - pts[jj][0]) * pts[j][1];
        m[i][j] = Math.max(m[i][j], area + m[i-1][jj]);
      }
      best = Math.max(best, m[i][j]);
    }
  }

  return best;
}

var pts = [
  [0.376508963445, 0.437693410334],
  [0.948798695015, 0.352125307881],
  [0.176318878234, 0.493630156084],
  [0.029394902328, 0.951299438575],
  [0.235041868262, 0.438197791997]
];

var k = 3;

console.log(f(pts, k));
...