Partición y reestructuración

9

Dadas dos formas contiguas de la misma área, determine la forma óptima de dividir la primera forma en un número mínimo de segmentos contiguos de modo que puedan reorganizarse para formar la segunda forma. En otras palabras, encuentre el número mínimo de segmentos requeridos que pueden formar ambas formas.

"Contiguo" significa que se puede llegar a cada cuadrado de la forma desde cualquier otro cuadrado caminando a través de los bordes. Se permite que las formas y los segmentos tengan agujeros.

"Reorganizar" significa que mueve los segmentos; puedes traducirlos, rotarlos y reflejarlos.

Las formas están contenidas en una cuadrícula; en otras palabras, cada forma consiste en una colección de cuadrados unitarios unidos por sus esquinas / bordes.

Especificaciones de entrada

La entrada se proporcionará en un formato razonable: lista de puntos, conjunto de cadenas que representan cada cuadrícula, etc. También puede tomar los tamaños de la cuadrícula si se solicita. Las cuadrículas tendrán las mismas dimensiones y se garantiza que las dos formas tendrán la misma área, y el área será positiva.

Especificaciones de salida

La salida debería ser solo un entero positivo. Tenga en cuenta que siempre habrá una respuesta positiva porque en el peor de los casos, simplemente divide las formas en Ncuadrados unitarios.

Ejemplos

Los ejemplos se presentan como una cuadrícula que .representa un espacio en blanco y que #representa parte de la forma.

Caso 1

Entrada

.....
.###.
.#.#.
.###.
.....

###..
..#..
..#..
..###
.....

Salida

2

Explicación

Puedes dividirlo en dos bloques en forma de L de 4:

#
###

Caso 2

Entrada

#...
##..
.#..
.##.

.##.
####
....
....

Salida

2

Explicación

Puedes dividir las formas así:

A...
AA..
.A.
.BB.

.AA.
BBAA
....
....

También puedes hacer:

A...
AA..
.B..
.BB.

.AB.
AABB
....
....

Caso 3

Entrada

#....#
######

.####.
.####.

Salida

2

Explicación

A....B
AAABBB

.ABBB.
.AAAB.

(Este caso de prueba demuestra la necesidad de rotar / reflejar formas para una salida óptima)

Caso 4

Entrada

.###.
..#..

.##..
.##..

Salida

2

Explicación

No importa cómo seleccione los bloques, seleccionar un 2x1 de la primera forma necesariamente evita que los otros dos se agrupen; por lo tanto, puede usar un 2x1 y dos 1x1s. Sin embargo, (gracias @Jonah), puede dividirlo en una forma de L de 3 bloques y un solo cuadrado de esta manera:

.AAB.
..A..

.AA..
.BA..
Hiperneutrino
fuente
Sandbox
HyperNeutrino
1
Interesante problema Y aparentemente duro. ¿Hay algún algoritmo para esto más eficiente que la fuerza bruta?
Jonás
@ Jonás, no estoy muy seguro; Todavía no he pensado en implementaciones eficientes para esto. Suena como un problema de DS.
HyperNeutrino
Además, probablemente valga la pena agregar más casos de prueba para un problema tan complejo.
Jonás
@ Jonás Buena sugerencia, gracias.
HyperNeutrino

Respuestas:

6

Python 3.6 , 799 791 bytes

7 bytes guardados por Jonathan Frech y motavica

B=frozenset
M=min
X={}
T=lambda s:((x,y)for y,x in s)
N=lambda s:B((x-M(s)[0],y-M(T(s))[0])for x,y in s)
G=lambda s:{(x,y):s&{(x-1,y),(x+1,y),(x,y-1),(x,y+1)}for(x,y)in s}
C=lambda g,s,i=0:i<=len(g)and(len(g)==len(s)or C(g,s.union(*map(g.get,s)),i+1))
F=lambda s:N([(-x,y)for x,y in s])
P=lambda s,i=4:M([N(s),F(s),P(F(T(s)),i-1)],key=list)if i else s
S=lambda s,t=B(),i=0:t|S(s,B().union(u|{p}for u in t for p in s-u if C(G(u|{p}),{p}))if i else B([B([next(iter(s))])]),i+1)if-~i<len(s)else t
def U(s):
 k=P(s)
 if k in X:return
 j=[t for t in S(k)if C(G(k-t),{next(iter(k-t))})];X[k]={P(t):B()for t in j}
 for t in j:X[k][P(t)]|=B([B(P(k-t))]);U(t);U(k-t)
V=lambda s,t:1+M(V(v,w)for u in B(X[s])&B(X[t])for v in X[s][u]for w in X[t][u])if s^t else 1
A=lambda s,t:U(s)or U(t)or V(P(s),P(t))

Pruébalo en línea!

Uso

A(s, t)toma dos formas donde cada forma está dada por una lista de x, yposiciones de la cuadrícula.

A continuación se muestra una función auxiliar para convertir la representación gráfica en una lista de posiciones:

def H(g):
 g=g.split("\n")
 return[(x,y)for x in range(len(g[0]))for y in range(len(g))if"#"==g[y][x]]

Ejemplo:

case1_1 = \
""".....
.###.
.#.#.
.###.
....."""

case1_2 = \
"""###..
..#..
..#..
..###
....."""

print(A(H(case1_1), H(case1_2))) # Prints 2

Explicación

El algoritmo utilizado aquí es un poco mejor que la fuerza bruta al almacenar en caché las subformas. Para una forma dada, almacena en caché todas las formas de dividir esa forma en dos formas contiguas, luego normalizo estas formas (desplazo las coordenadas para que comience en el origen, luego encuentro una rotación / reflexión de la misma que se usa en el caché) y guárdelos en el caché para buscarlos rápidamente más tarde. Todas las subformas tienen sus subformas almacenadas en caché también hasta que llegue a la forma de bloque único.

Estas subformas se generan convirtiéndolas en una lista de adyacencia de gráficos y usando un BFS para generar todos los sub-gráficos. Luego podemos filtrar estas subgrafías a aquellas donde los vértices que no se incluyeron son un componente conectado. La determinación de si el gráfico está conectado se realiza con otro BFS.

Una vez que se completa el caché, la solución se encuentra comparando las dos formas para encontrar las subformas que tiene en común. Una vez que tiene esta lista de subformas, toma el par de subformas sobrantes después de eliminar la forma común y aplica recursivamente el mismo algoritmo nuevamente para encontrar el número mínimo de bloques necesarios para reconstruir la forma. Esto devuelve la subforma con el mínimo de todos esos valores y tenemos nuestra solución.

Puse una versión anotada a continuación para explicar un poco lo que está haciendo cada línea.

B=frozenset
M=min
# Shapes are stored as a frozenset of tuples where each tuple represents an (x, y) position
# Cache of shape partitions. This is a two-level dictionary where the outer key is a shape, and the inner key is a sub-shape where the value is a list of the shapes left when the sub-shape is removed.
# there may be multiple shapes in the inner-most list if the sub-shape appears multiple times
X={}
# Transpose list of coords (flip around diagonal axis)
T=lambda s:((x,y)for y,x in s)
# Translate shape so its upper-left corner is at the origin
N=lambda s:B((x-M(s)[0],y-M(T(s))[0])for x,y in s)
# Convert shape to graph in adjacency list form
G=lambda s:{(x,y):s&{(x-1,y),(x+1,y),(x,y-1),(x,y+1)}for(x,y)in s}
# Check if graph is connected given a set of nodes, s, known to be connected
C=lambda g,s,i=0:i<=len(g)and(len(g)==len(s)or C(g,s.union(*map(g.get,s)),i+1))
# Flip shape around vertical axis
F=lambda s:N([(-x,y)for x,y in s])
# Converts shape to the minimal reflection or rotation. rotation is equivalent to transpose then flip.
P=lambda s,i=4:M([N(s),F(s),P(F(T(s)),i-1)],key=list)if i else s
# returns all the contiguous sub-shapes of s that contain the first pos, given by next(iter(s))
S=lambda s,t=B(),i=0:t|S(s,B().union(u|{p}for u in t for p in s-u if C(G(u|{p}),{p}))if i else B([B([next(iter(s))])]),i+1)if-~i<len(s)else t
# updates the sub-shape cache, X, recursively for an input shape s 
def U(s):
 k=P(s)
 if k in X:return
 j=[t for t in S(k)if C(G(k-t),{next(iter(k-t))})];X[k]={P(t):B()for t in j}
 for t in j:X[k][P(t)]|=B([B(P(k-t))]);U(t);U(k-t)
# Gets the minimum number of partitions for two shapes
V=lambda s,t:1+M(V(v,w)for u in B(X[s])&B(X[t])for v in X[s][u]for w in X[t][u])if s^t else 1
# The function to run, will update the cache for the two input shapes then return the minimum number of partitions
A=lambda s,t:U(s)or U(t)or V(P(s),P(t))
Cameron Aavik
fuente
1
if s==t elseposiblemente podría revertirse, permitiendo el reemplazo de !=a ^.
Jonathan Frech
1
if i<len(s)-1else~> if-~i<len(s)else.
Jonathan Frech
1
def A(s,t):U(s);U(t);return V(P(s),P(t))posiblemente podría ser lambda s,t:U(s)or U(t)or V(P(s),P(t)), ahorrando tres bytes.
Jonathan Frech
1
s.union(*[g[n]for n in s])~>s.union(*map(g.get,s))
movatica