Partição e Reestruturação

9

Dadas duas formas contíguas da mesma área, determine a maneira ideal de dividir a primeira forma em um número mínimo de segmentos contíguos, de forma que eles possam ser reorganizados para formar a segunda forma. Em outras palavras, encontre o número mínimo de segmentos necessários que podem formar as duas formas.

"Contíguo" significa que todos os quadrados da forma podem ser alcançados a partir de qualquer outro quadrado andando pelas bordas. Formas e segmentos podem ter furos.

"Reorganizar" significa que você move os segmentos; você pode traduzir, girar e refleti-los.

As formas estão contidas em uma grade; em outras palavras, cada forma consiste em uma coleção de quadrados de unidades unidos por seus cantos / arestas.

Especificações de entrada

A entrada será fornecida em algum formato razoável - lista de pontos, matriz de cadeias representando cada grade, etc. Você também pode obter os tamanhos da grade, se solicitado. As grades terão as mesmas dimensões e as duas formas terão a mesma área e a área será positiva.

Especificações de saída

A saída deve ser apenas um número inteiro positivo único. Observe que sempre haverá uma resposta positiva porque, na pior das hipóteses, você apenas divide as formas em Nunidades de quadrados.

Exemplos

Os exemplos são apresentados como uma grade .representando um espaço em branco e #representando parte da forma.

Caso 1

Entrada

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

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

Resultado

2

Explicação

Você pode dividi-lo em dois blocos em forma de L de 4:

#
###

Caso 2

Entrada

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

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

Resultado

2

Explicação

Você pode dividir as formas da seguinte maneira:

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

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

Você também pode fazer:

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

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

Caso 3

Entrada

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

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

Resultado

2

Explicação

A....B
AAABBB

.ABBB.
.AAAB.

(Este caso de teste demonstra a necessidade de girar / refletir formas para obter uma saída ideal)

Caso 4

Entrada

.###.
..#..

.##..
.##..

Resultado

2

Explicação

Não importa como você seleciona blocos, selecionar um 2x1 da primeira forma necessariamente impede que os outros dois sejam agrupados; assim, você pode usar um 2x1 e dois 1x1s. No entanto, (obrigado @Jonah), você pode dividi-lo em uma forma de L de 3 blocos e um único quadrado da seguinte forma:

.AAB.
..A..

.AA..
.BA..
HyperNeutrino
fonte
Sandbox
HyperNeutrino
1
Problema interessante. E aparentemente difícil. Existem algoritmos para isso mais eficiente que força bruta?
Jonah
@ Jonah, não tenho certeza; Ainda não pensei em implementações eficientes para isso. Parece um problema do DS.
HyperNeutrino 13/06/19
Além disso, provavelmente vale a pena adicionar mais casos de teste para um problema desse complexo.
Jonah
@ Jonah Boa sugestão, obrigado.
HyperNeutrino 13/06/19

Respostas:

6

Python 3.6 , 799 791 bytes

7 bytes salvos por Jonathan Frech e 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))

Experimente online!

Uso

A(s, t)assume duas formas em que cada forma é fornecida por uma lista de x, yposições da grade.

Uma função auxiliar para converter a representação gráfica em uma lista de posições está abaixo:

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]]

Exemplo:

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

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

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

Explicação

O algoritmo usado aqui é um pouco melhor que a força bruta, armazenando sub-formas em cache. Para uma determinada forma, ele armazena em cache todas as maneiras de dividir essa forma em duas formas contíguas, depois normalizo essas formas (mude as coordenadas para que iniciem na origem e, em seguida, encontre uma rotação / reflexão dela que é usada no cache) e armazene-os no cache para procurar rapidamente mais tarde. Todas as sub-formas têm suas sub-formas em cache também até que elas atinjam a forma de bloco único.

Essas sub-formas são geradas convertendo-a em uma lista de adjacência de gráfico e usando um BFS para gerar todos os sub-gráficos. Podemos então filtrar esses subgráficos para aqueles em que os vértices que não foram incluídos são um componente conectado. Determinar se o gráfico está conectado é feito com outro BFS.

Após a conclusão do cache, a solução é encontrada comparando as duas formas para encontrar as sub-formas que ele tem em comum. Depois de ter essa lista de sub-formas, ele pega o par de sub-formas que sobraram após remover a forma comum e aplica recursivamente o mesmo algoritmo novamente para encontrar o número mínimo de blocos necessários para reconstruir a forma. Isso retorna a sub-forma com o mínimo de todos esses valores e temos a nossa solução.

Eu coloquei uma versão anotada abaixo para explicar o que cada linha está fazendo.

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
fonte
1
if s==t elsepossivelmente poderia ser revertida, permitindo a substituição de !=para ^.
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))possivelmente lambda s,t:U(s)or U(t)or V(P(s),P(t)), economizando três bytes.
Jonathan Frech
1
s.union(*[g[n]for n in s])~>s.union(*map(g.get,s))
movatica 15/06/19