web-dev-qa-db-fra.com

Trouver k sur n sous-ensemble avec une aire maximale

J'ai n points et je dois trouver la zone unie maximale entre k points (k <= n). Donc, c'est la somme de la zone de ces points moins la zone commune entre eux.

enter image description here] 1

Supposons que nous ayons n=4, k=2. Comme illustré dans l'image ci-dessus, les zones sont calculées de chaque point à l'origine et, la zone finale est la somme de la zone B avec les D sont (en ne comptant qu'une seule fois la zone de leur intersection). Aucun point n'est dominé

J'ai implémenté un algorithme de programmation dynamique ascendant, mais il a une erreur quelque part. Voici le code qui imprime le meilleur résultat:

#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;
}

avec l'entrée:

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

où le premier nombre est égal à n, le second k et les lignes suivantes les coordonnées x et y de chaque point respectivement, le résultat devrait être: 0.381410589193,

alors que le mien est 0.366431740966. Alors je manque un point?

9

C'est un petit problème sympa, merci d'avoir posté! Dans le reste, je vais supposer qu'aucun point n'est dominé , c'est-à-dire qu'il n'y a pas de points c tels qu'il existe un point d avec c.x < d.x et c.y < d.y. S'il y en a, alors il n'est jamais optimal d'utiliser c (pourquoi?), Afin que nous puissions ignorer en toute sécurité tout point dominé. Aucun de vos exemples n'est dominé.

Votre problème présente une sous-structure optimale: une fois que nous avons décidé quel élément doit être inclus dans la première itération, nous avons à nouveau le même problème avec k - 1 Et n - 1 (Nous supprimons l'élément sélectionné de la ensemble de points autorisés). Bien sûr, le gain dépend de l'ensemble que nous choisissons - nous ne voulons pas compter deux fois les zones.

Je propose de pré-trier tous les points par leur valeur x, par ordre croissant. Cela garantit que la valeur d'une sélection de points peut être calculée comme des zones par morceaux. Je vais illustrer avec un exemple: supposons que nous ayons trois points, (x1, y1), ..., (x3, y3) Avec des valeurs (2, 3), (3, 1), (4, .5). La superficie totale couverte par ces points est alors (4 - 3) * .5 + (3 - 2) * 1 + (2 - 0) * 3. J'espère que cela a du sens dans un graphique:

figure

En supposant qu'il n'y a pas de points dominés, nous aurons toujours un chiffre aussi faiblement décroissant. Ainsi, le pré-tri résout tout le problème du "comptage des surfaces deux fois"!

Transformons cela en un algorithme de programmation dynamique. Prenons un ensemble de n points, étiquetés {p_1, p_2, ..., p_n}. Soit d[k][m] La surface maximale d'un sous-ensemble de taille k + 1 Où le (k + 1) - ème point du sous-ensemble est le point p_m. Clairement, m ne peut pas être choisi comme (k + 1) - ème point si m < k + 1, Puisque nous aurions alors un sous-ensemble de taille inférieure à k + 1, Ce qui est jamais optimal. Nous avons la récursivité suivante,

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

Les cas initiaux où k = 1 Sont les zones rectangulaires de chaque point. Les cas initiaux ainsi que l'équation de mise à jour suffisent pour résoudre le problème. J'évalue le code suivant comme O(n^2 * k). Le terme au carré dans n peut également être abaissé, car nous avons une collection ordonnée et pouvons être en mesure d'appliquer une recherche binaire pour trouver le meilleur sous-ensemble dans le temps de log n, En réduisant n^2 À n log n. Je vous laisse cela.

Dans le code, j'ai réutilisé ma notation ci-dessus dans la mesure du possible. C'est un peu laconique, mais je l'espère clair avec l'explication donnée.

#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));
}

En utilisant les exemples de données que vous avez fournis, je trouve un résultat optimal de 0.381411, Comme souhaité.

1
N. Wouda

D'après ce que je peux dire, vous et moi utilisons tous les deux la même méthode pour calculer la surface, ainsi que le concept global, mais mon code semble renvoyer un résultat correct. Peut-être que son examen peut vous aider à trouver un écart.

Code 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));
0