从具有最大面积的 n 个子集中找出 k
Find k out of n subset with maximal area
我有 n
个点,必须找到 k
个点 (k <= n
) 之间的最大联合面积。所以,它是这些点面积的总和减去它们之间的公共面积。
]1
假设我们有 n=4, k=2
。如上图所示,面积是从每个点到原点计算的,最终面积是 B 面积与 D 面积的总和(只计算它们相交的面积一次)。无点支配
我已经实现了一个bottom-up动态规划算法,但是它某处有错误。这是打印出最佳结果的代码:
#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
。所以我漏掉了一点?
这是一个巧妙的小问题,感谢发帖!在其余部分,我将假设没有点是 dominated,也就是说,没有点 c
使得存在点 d
with c.x < d.x
和 c.y < d.y
。如果有,那么使用 c
永远不是最优的(为什么?),因此我们可以安全地忽略任何支配点。 None 您的示例点数占优。
你的问题展示了最佳子结构:一旦我们决定了第一次迭代中要包含哪个项目,我们再次遇到与 k - 1
和 n - 1
相同的问题(我们删除了选定的允许点集中的项目)。当然,收益取决于我们选择的集合——我们不想计算两次区域。
我建议我们按 x 值按升序对所有点进行预排序。这确保可以将所选点的值计算为分段区域。我将举例说明:假设我们有三个点,(x1, y1), ..., (x3, y3)
,值为 (2, 3), (3, 1), (4, .5)
。那么这些点覆盖的总面积就是(4 - 3) * .5 + (3 - 2) * 1 + (2 - 0) * 3
。我希望它在图表中有意义:
根据我们假设没有主导点,我们将始终有这样一个微弱下降的数字。因此,预排序解决了 "counting areas twice"!
的整个问题
让我们把它变成一个动态规划算法。考虑一组 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
是每个点的矩形区域。初始情况连同更新方程足以解决问题。我估计下面的代码为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
的最佳结果。
据我所知,您和我都使用相同的方法来计算面积以及整体概念,但我的代码似乎返回了正确的结果。也许回顾它可以帮助您找到差异。
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));
我有 n
个点,必须找到 k
个点 (k <= n
) 之间的最大联合面积。所以,它是这些点面积的总和减去它们之间的公共面积。
假设我们有 n=4, k=2
。如上图所示,面积是从每个点到原点计算的,最终面积是 B 面积与 D 面积的总和(只计算它们相交的面积一次)。无点支配
我已经实现了一个bottom-up动态规划算法,但是它某处有错误。这是打印出最佳结果的代码:
#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
。所以我漏掉了一点?
这是一个巧妙的小问题,感谢发帖!在其余部分,我将假设没有点是 dominated,也就是说,没有点 c
使得存在点 d
with c.x < d.x
和 c.y < d.y
。如果有,那么使用 c
永远不是最优的(为什么?),因此我们可以安全地忽略任何支配点。 None 您的示例点数占优。
你的问题展示了最佳子结构:一旦我们决定了第一次迭代中要包含哪个项目,我们再次遇到与 k - 1
和 n - 1
相同的问题(我们删除了选定的允许点集中的项目)。当然,收益取决于我们选择的集合——我们不想计算两次区域。
我建议我们按 x 值按升序对所有点进行预排序。这确保可以将所选点的值计算为分段区域。我将举例说明:假设我们有三个点,(x1, y1), ..., (x3, y3)
,值为 (2, 3), (3, 1), (4, .5)
。那么这些点覆盖的总面积就是(4 - 3) * .5 + (3 - 2) * 1 + (2 - 0) * 3
。我希望它在图表中有意义:
根据我们假设没有主导点,我们将始终有这样一个微弱下降的数字。因此,预排序解决了 "counting areas twice"!
的整个问题让我们把它变成一个动态规划算法。考虑一组 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
是每个点的矩形区域。初始情况连同更新方程足以解决问题。我估计下面的代码为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
的最佳结果。
据我所知,您和我都使用相同的方法来计算面积以及整体概念,但我的代码似乎返回了正确的结果。也许回顾它可以帮助您找到差异。
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));