C, puntaje 2.397x10 ^ 38
Hombre, esto tardó demasiado en hacerlo, probablemente debido a mi elección del idioma. Obtuve el algoritmo funcionando bastante temprano, pero me encontré con muchos problemas con la asignación de memoria (no podía liberar recursivamente cosas debido a desbordamientos de pila, los tamaños de fuga eran enormes).
¡Todavía! Es mejor que la otra entrada en cada caso de prueba, e incluso puede ser óptimo, se acerca bastante o es exactamente una solución óptima la mayor parte del tiempo.
De todos modos, aquí está el código:
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#define WHITE 'W'
#define BLACK 'B'
#define RED 'R'
typedef struct image {
int w, h;
char* buf;
} image;
typedef struct point {
int x, y;
struct point *next;
struct point *parent;
} point;
typedef struct shape {
point* first_point;
point* last_point;
struct shape* next_shape;
} shape;
typedef struct storage {
point* points;
size_t points_size;
size_t points_index;
shape* shapes;
size_t shapes_size;
size_t shapes_index;
} storage;
char getpx(image* img, int x, int y) {
if (0>x || x>=img->w || 0>y || y>=img->h) {
return WHITE;
} else {
return img->buf[y*img->w+x];
}
}
storage* create_storage(int w, int h) {
storage* ps = (storage*)malloc(sizeof(storage));
ps->points_size = 8*w*h;
ps->points = (point*)calloc(ps->points_size, sizeof(point));
ps->points_index = 0;
ps->shapes_size = 2*w*h;
ps->shapes = (shape*)calloc(ps->shapes_size, sizeof(shape));
ps->shapes_index = 0;
return ps;
}
void free_storage(storage* ps) {
if (ps != NULL) {
if (ps->points != NULL) {
free(ps->points);
ps->points = NULL;
}
if (ps->shapes != NULL) {
free(ps->shapes);
ps->shapes = NULL;
}
free(ps);
}
}
point* alloc_point(storage* ps) {
if (ps->points_index == ps->points_size) {
printf("WHOAH THERE BUDDY SLOW DOWN\n");
/*// double the size of the buffer
point* new_buffer = (point*)malloc(ps->points_size*2*sizeof(point));
// need to change all existing pointers to point to new buffer
long long int pointer_offset = (long long int)new_buffer - (long long int)ps->points;
for (size_t i=0; i<ps->points_index; i++) {
new_buffer[i] = ps->points[i];
if (new_buffer[i].next != NULL) {
new_buffer[i].next += pointer_offset;
}
if (new_buffer[i].parent != NULL) {
new_buffer[i].parent += pointer_offset;
}
}
for(size_t i=0; i<ps->shapes_index; i++) {
if (ps->shapes[i].first_point != NULL) {
ps->shapes[i].first_point += pointer_offset;
}
if (ps->shapes[i].last_point != NULL) {
ps->shapes[i].last_point += pointer_offset;
}
}
free(ps->points);
ps->points = new_buffer;
ps->points_size = ps->points_size * 2;*/
}
point* out = &(ps->points[ps->points_index]);
ps->points_index += 1;
return out;
}
shape* alloc_shape(storage* ps) {
/*if (ps->shapes_index == ps->shapes_size) {
// double the size of the buffer
shape* new_buffer = (shape*)malloc(ps->shapes_size*2*sizeof(shape));
long long int pointer_offset = (long long int)new_buffer - (long long int)ps->shapes;
for (size_t i=0; i<ps->shapes_index; i++) {
new_buffer[i] = ps->shapes[i];
if (new_buffer[i].next_shape != NULL) {
new_buffer[i].next_shape += pointer_offset;
}
}
free(ps->shapes);
ps->shapes = new_buffer;
ps->shapes_size = ps->shapes_size * 2;
}*/
shape* out = &(ps->shapes[ps->shapes_index]);
ps->shapes_index += 1;
return out;
}
shape floodfill_shape(image* img, storage* ps, int x, int y, char* buf) {
// not using point allocator for exploration stack b/c that will overflow it
point* stack = (point*)malloc(sizeof(point));
stack->x = x;
stack->y = y;
stack->next = NULL;
stack->parent = NULL;
point* explored = NULL;
point* first_explored;
point* next_explored;
while (stack != NULL) {
int sx = stack->x;
int sy = stack->y;
point* prev_head = stack;
stack = stack->next;
free(prev_head);
buf[sx+sy*img->w] = 1; // mark as explored
// add point to shape
next_explored = alloc_point(ps);
next_explored->x = sx;
next_explored->y = sy;
next_explored->next = NULL;
next_explored->parent = NULL;
if (explored != NULL) {
explored->next = next_explored;
} else {
first_explored = next_explored;
}
explored = next_explored;
for (int dy=-1; dy<2; dy++) {
for (int dx=-1; dx<2; dx++) {
if (dy != 0 || dx != 0) {
int nx = sx+dx;
int ny = sy+dy;
if (getpx(img, nx, ny) == WHITE || buf[nx+ny*img->w]) {
// skip adding point to fringe
} else {
// push point to top of stack
point* new_point = (point*)malloc(sizeof(point));
new_point->x = nx;
new_point->y = ny;
new_point->next = stack;
new_point->parent = NULL;
stack = new_point;
}
}
}
}
}
/*if (getpx(img, x, y) == WHITE || buf[x+y*img->w]) {
return (shape){NULL, NULL, NULL};
} else {
buf[x+y*img->w] = 1;
shape e = floodfill_shape(img, ps, x+1, y, buf);
shape ne = floodfill_shape(img, ps, x+1, y+1, buf);
shape n = floodfill_shape(img, ps, x, y+1, buf);
shape nw = floodfill_shape(img, ps, x-1, y+1, buf);
shape w = floodfill_shape(img, ps, x-1, y, buf);
shape sw = floodfill_shape(img, ps, x-1, y-1, buf);
shape s = floodfill_shape(img, ps, x, y-1, buf);
shape se = floodfill_shape(img, ps, x+1, y-1, buf);
point *p = alloc_point(ps);
p->x = x;
p->y = y;
p->next = NULL;
p->parent = NULL;
shape o = (shape){p, p, NULL};
if (e.first_point != NULL) {
o.last_point->next = e.first_point;
o.last_point = e.last_point;
}
if (ne.first_point != NULL) {
o.last_point->next = ne.first_point;
o.last_point = ne.last_point;
}
if (n.first_point != NULL) {
o.last_point->next = n.first_point;
o.last_point = n.last_point;
}
if (nw.first_point != NULL) {
o.last_point->next = nw.first_point;
o.last_point = nw.last_point;
}
if (w.first_point != NULL) {
o.last_point->next = w.first_point;
o.last_point = w.last_point;
}
if (sw.first_point != NULL) {
o.last_point->next = sw.first_point;
o.last_point = sw.last_point;
}
if (s.first_point != NULL) {
o.last_point->next = s.first_point;
o.last_point = s.last_point;
}
if (se.first_point != NULL) {
o.last_point->next = se.first_point;
o.last_point = se.last_point;
}
return o;
}*/
shape out = {first_explored, explored, NULL};
return out;
}
shape* create_shapes(image* img, storage* ps) {
char* added_buffer = (char*)calloc(img->w*img->h, sizeof(char));
shape* first_shape = NULL;
shape* last_shape = NULL;
int num_shapes = 0;
for (int y=0; y<img->h; y++) {
for (int x=0; x<img->w; x++) {
if (getpx(img, x, y) != WHITE && !(added_buffer[x+y*img->w])) {
shape* alloced_shape = alloc_shape(ps);
*alloced_shape = floodfill_shape(img, ps, x, y, added_buffer);
if (first_shape == NULL) {
first_shape = alloced_shape;
last_shape = alloced_shape;
} else if (last_shape != NULL) {
last_shape->next_shape = alloced_shape;
last_shape = alloced_shape;
}
num_shapes++;
}
}
}
free(added_buffer);
return first_shape;
}
void populate_buf(image* img, shape* s, char* buf) {
point* p = s->first_point;
while (p != NULL) {
buf[p->x+p->y*img->w] = 1;
p = p->next;
}
}
bool expand_frontier(image* img, storage* ps, shape* prev_frontier, shape* next_frontier, char* buf) {
point* p = prev_frontier->first_point;
point* n = NULL;
bool found = false;
size_t starting_points_index = ps->points_index;
while (p != NULL) {
for (int dy=-1; dy<2; dy++) {
for (int dx=-1; dx<2; dx++) {
if (dy != 0 || dx != 0) {
int nx = p->x+dx;
int ny = p->y+dy;
if ((0<=nx && nx<img->w && 0<=ny && ny<img->h) // in bounds
&& !buf[nx+ny*img->w]) { // not searched yet
buf[nx+ny*img->w] = 1;
if (getpx(img, nx, ny) != WHITE) {
// found a new shape!
ps->points_index = starting_points_index;
n = alloc_point(ps);
n->x = nx;
n->y = ny;
n->next = NULL;
n->parent = p;
found = true;
goto __expand_frontier_fullbreak;
} else {
// need to search more
point* f = alloc_point(ps);
f->x = nx;
f->y = ny;
f->next = n;
f->parent = p;
n = f;
}
}
}
}}
p = p->next;
}
__expand_frontier_fullbreak:
p = NULL;
point* last_n = n;
while (last_n->next != NULL) {
last_n = last_n->next;
}
next_frontier->first_point = n;
next_frontier->last_point = last_n;
return found;
}
void color_from_frontier(image* img, point* frontier_point) {
point* p = frontier_point->parent;
while (p->parent != NULL) { // if everything else is right,
// a frontier point should come in a chain of at least 3
// (f point (B) -> point to color (W) -> point in shape (B) -> NULL)
img->buf[p->x+p->y*img->w] = RED;
p = p->parent;
}
}
int main(int argc, char** argv) {
if (argc < 3) {
printf("Error: first argument must be filename to load, second argument filename to save to.\n");
return 1;
}
char* fname = argv[1];
FILE* fp = fopen(fname, "r");
if (fp == NULL) {
printf("Error opening file \"%s\"\n", fname);
return 1;
}
int w, h;
w = 0;
h = 0;
fscanf(fp, "%d %d\n", &w, &h);
if (w==0 || h==0) {
printf("Error: invalid width/height specified\n");
return 1;
}
char* buf = (char*)malloc(sizeof(char)*w*h+1);
fgets(buf, w*h+1, fp);
fclose(fp);
image img = (image){w, h, buf};
int nshapes = 0;
storage* ps = create_storage(w, h);
while (nshapes != 1) {
// main loop, do processing step until one shape left
ps->points_index = 0;
ps->shapes_index = 0;
shape* head = create_shapes(&img, ps);
nshapes = 0;
shape* pt = head;
while (pt != NULL) {
pt = pt->next_shape;
nshapes++;
}
if (nshapes % 1024 == 0) {
printf("shapes left: %d\n", nshapes);
}
if (nshapes == 1) {
goto __main_task_complete;
}
shape* frontier = alloc_shape(ps);
// making a copy so we can safely free later
point* p = head->first_point;
point* ffp = NULL;
point* flp = NULL;
while (p != NULL) {
if (ffp == NULL) {
ffp = alloc_point(ps);
ffp->x = p->x;
ffp->y = p->y;
ffp->next = NULL;
ffp->parent = NULL;
flp = ffp;
} else {
point* fnp = alloc_point(ps);
fnp->x = p->x;
fnp->y = p->y;
fnp->next = NULL;
fnp->parent = NULL;
flp->next = fnp;
flp = fnp;
}
p = p->next;
}
frontier->first_point = ffp;
frontier->last_point = flp;
frontier->next_shape = NULL;
char* visited_buf = (char*)calloc(img.w*img.h+1, sizeof(char));
populate_buf(&img, frontier, visited_buf);
shape* new_frontier = alloc_shape(ps);
new_frontier->first_point = NULL;
new_frontier->last_point = NULL;
new_frontier->next_shape = NULL;
while (!expand_frontier(&img, ps, frontier, new_frontier, visited_buf)) {
frontier->first_point = new_frontier->first_point;
frontier->last_point = new_frontier->last_point;
new_frontier->next_shape = frontier;
}
free(visited_buf);
color_from_frontier(&img, new_frontier->first_point);
__main_task_complete:
img = img;
}
free_storage(ps);
char* outfname = argv[2];
fp = fopen(outfname, "w");
if (fp == NULL) {
printf("Error opening file \"%s\"\n", outfname);
return 1;
}
fprintf(fp, "%d %d\n", img.w, img.h);
fprintf(fp, "%s", img.buf);
free(img.buf);
fclose(fp);
return 0;
}
Probado en: Arch Linux, GCC 9.1.0, -O3
Este código toma entrada / salida en un archivo personalizado que llamo "cppm" (porque es como una versión condensada del clásico formato PPM). A continuación se muestra un script de Python para convertir a / desde:
from PIL import Image
BLACK='B'
WHITE='W'
RED ='R'
def image_to_cppm(infname, outfname):
outfile = open(outfname, 'w')
im = Image.open(infname)
w, h = im.width, im.height
outfile.write(f"{w} {h}\n")
for y in range(h):
for x in range(w):
r, g, b, *_ = im.getpixel((x, y))
if r==0 and g==0 and b==0:
outfile.write(BLACK)
elif g==0 and b==0:
outfile.write(RED)
else:
outfile.write(WHITE)
outfile.write("\n")
outfile.close()
im.close()
def cppm_to_image(infname, outfname):
infile = open(infname, 'r')
w, h = infile.readline().split(" ")
w, h = int(w), int(h)
im = Image.new('RGB', (w, h), color=(255, 255, 255))
for y in range(h):
for x in range(w):
c = infile.read(1)
if c==BLACK:
im.putpixel((x,y), (0, 0, 0))
elif c==RED:
im.putpixel((x,y), (255, 0, 0))
infile.close()
im.save(outfname)
im.close()
if __name__ == "__main__":
import sys
if len(sys.argv) < 3:
print("Error: must provide 2 files to convert, first is from, second is to")
infname = sys.argv[1]
outfname = sys.argv[2]
if not infname.endswith("cppm") and outfname.endswith("cppm"):
image_to_cppm(infname, outfname)
elif infname.endswith("cppm") and not outfname.endswith("cppm"):
cppm_to_image(infname, outfname)
else:
print("didn't do anything, exactly one file must end with .cppm")
Explicación del algoritmo
El funcionamiento de este algoritmo es que comienza por encontrar todas las formas conectadas en la imagen, incluidos los píxeles rojos. Luego toma el primero y expande su frontera un píxel a la vez hasta que encuentra otra forma. Luego colorea todos los píxeles desde el toque hasta la forma original (usando la lista vinculada que hizo en el camino para realizar un seguimiento). Finalmente, repite el proceso, encontrando todas las nuevas formas creadas, hasta que solo quede una forma.
Galería de imágenes
Caso de prueba 1, 183 píxeles
Testcase 2, 140 píxeles
Testcase 3, 244 píxeles
Testcase 4, 42 píxeles
Testcase 5, 622 píxeles
Testcase 6, 1 píxel
Testcase 7, 104 píxeles
Testcase 8, 2286 píxeles
Testcase 9, 22 píxeles
Testcase 10, 31581 píxeles
Testcase 11, 21421 píxeles
Testcase 12, 5465 píxeles
Testcase 13, 4679 píxeles
Testcase 14, 7362 píxeles