3

假设我有以下二进制矩阵:

0 0 0 1 1 1 0
0 0 0 1 1 1 0
0 0 0 1 1 1 0
1 1 1 1 1 1 1
1 1 1 1 1 1 1
1 1 1 1 1 1 1
0 0 0 1 1 1 0
0 0 0 1 1 1 0
0 0 0 1 1 1 0

我想找到一组平行于 x 和 y 轴的矩形,它们1 至少覆盖一次,并且不覆盖一个0具有最小基数(矩形数量最少)的矩形。在上面的示例中,这将是矩形((0, 3), (6, 5))((3, 0), (5, 8))(符号形式为(topleft, bottomright)) - 最小的解决方案是使用两个矩形。

我之前的尝试是找到面积最大的仅覆盖1' 的矩形,将该矩形添加到集合中,然后将所有这些标记1's0',直到所有1' 消失。虽然这个集合会覆盖每个 1 而不是单个0,但它不一定具有最小的基数(这个算法在上面的例子中会失败)。

4

3 回答 3

4

我认为你应该用 2 而不是 0 替换覆盖的 1。这样,您可以在覆盖 1 时包含 2,但仍然不覆盖任何 0。

这是我想出的:

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

struct board {
  int **data;
  int w,h;
};

int  load_board(char *, struct board *);
void print_board(struct board *);

int  max_height_with_fixed_w(struct board *board, int i, int j, int w) {
  int jj = -1, ii;
  if (board->data[j][i] != 0) {
    for (jj = j; jj < board->h && board->data[jj][i] != 0; jj++) { 
      for (ii = i; ii - i < w; ii++) {
        if (board->data[jj][ii] == 0)
          return jj - j;
      }
    }
    printf("maximum height = %d\n", jj);
  }
  return jj - j;
}

void find_largest_rect_from(
    struct board *board, 
    int i, int j, int *ei, int *ej) {
  int max_w = 0, max_h = 0, max_a = 0;
  *ei = *ej = -1;
  for (max_w = 0; max_w < board->w - i && 
      (board->data[j][i + max_w] != 0); 
      max_w++) {
    int max_aa;
    int max_hh = max_height_with_fixed_w(board, i, j, max_w + 1);
    if (max_hh > max_h) {
      max_h  = max_hh; 
    }
    max_aa = max_hh * (max_w + 1);
    printf("  area: %d x %d = %d\n", max_hh, max_w + 1, max_aa);
    if (max_aa > max_a) {
      max_a = max_aa;
      *ei = i + max_w;
      *ej = j + max_hh - 1; 
    }
  }
  printf("max width : %d\n", max_w);
  printf("max height: %d\n", max_h);
  printf("max area  : %d\n", max_a);
}

int main(int arc, char **argv) {
  struct board board;
  int jj, ii, i = 0, j = 0;
  int total_rects = 0;
  if(load_board(argv[1], &board)) return 1;
  print_board(&board);
  for (j = 0; j < board.h; j++) {
    for (i = 0; i < board.w; i++) {
      if (board.data[j][i] == 1) {
        find_largest_rect_from(&board, i, j, &ii, &jj);
        printf("largest from %d, %d ends at %d,%d\n", i, j, ii, jj);
        int marki, markj;
        total_rects++;
        for (markj = j; markj <= jj; markj++) {
          for (marki = i; marki <= ii; marki++) {
            board.data[markj][marki] = 2;
          }
        }
        print_board(&board);
      }
    }
  }
  printf("minimum %d rects are required\n", total_rects);
  return 0;
}

int load_board(char *fname, struct board *board) {
  FILE *file = fopen(fname, "r");
  int j,i;
  if (!file) return 1;
  fscanf(file, "%d %d", &board->w, &board->h);
  board->data = (int**)malloc(sizeof(int*)*board->h);
  for (j = 0; j < board->h; j++) {
    board->data[j] = (int*)malloc(sizeof(int)*board->w);
    for (i = 0; i < board->w; i++) {
      fscanf(file, "%d", &board->data[j][i]);
    }
  }
  return 0;
}

void print_board(struct board *board) {
  int i,j;
  printf("board size: %d, %d\n", board->w, board->h);
  for (j = 0; j < board->h; j++) {
    for (i = 0; i < board->w; i++) {
      printf("%d ", board->data[j][i]);
    } printf("\n");
  }
}

示例输入 1:

7 9
0 0 0 1 1 1 0
0 0 0 1 1 1 0
0 0 0 1 1 1 0
1 1 1 1 1 1 1
1 1 1 1 1 1 1
1 1 1 1 1 1 1
0 0 0 1 1 1 0
0 0 0 1 1 1 0
0 0 0 1 1 1 0

示例输入 2:

7 7
0 0 0 1 0 0 0
0 0 1 1 1 0 0
0 1 1 1 1 1 0
1 1 1 1 1 1 1
0 1 1 1 1 1 0
0 0 1 1 1 0 0
0 0 0 1 0 0 0
于 2012-07-17T17:11:57.497 回答
0

算法的想法:

  • 只要1矩阵中有 s 就可以:
  • 对于1没有在其1上方且不必1在其左侧的每个,请执行以下操作:
  • 贪婪:从这里开始1,沿着对角线向右和向下走,只要1途中有 s - 创建一个矩形并将创建的矩形的 s 更改10s。
于 2012-07-17T17:10:52.693 回答
0

我会选择一种算法来选择点并扩展,直到它消耗所有可能的空间,然后再选择更多,直到网格上的所有点都被消耗掉。

对于您的示例,假设我们正在消耗 1s。

我会选择 (0,3),最左边的 1 的最上面。我的矩形将从 0,0 开始。我会向右和向下扩展它,直到它增长到 6,2 的大小。此时,我会将这些点标记为已占用。

然后我会选择另一个点,比如 (3,0),矩形大小为 0,0。我会一直向下生长,直到它占据最大的可用空间,大小为 2.6。

考虑以下:

0 0 0 1 0 0 0
0 0 1 1 1 0 0
0 1 1 1 1 1 0
1 1 1 1 1 1 1
0 1 1 1 1 1 0
0 0 1 1 1 0 0
0 0 0 1 0 0 0

您可以轻松确定对于任何随机起点,它总是需要 4 个矩形。

为了将点标记为“已占用”,您应该将它们标记为与标记为“不可消耗”的点不同。然后,您可以区分不可消耗的(不能扩展为)和“占用的”(可以扩展为,但不必是,因为它们已经存在)。

于 2012-07-17T17:17:22.107 回答