Piet Mondriaan was een Nederlandse schilder die beschouwd wordt als een van de grote kunstenaars van de 20e eeuw. Sommige van Mondriaans kunstwerken hebben een unieke, geometrische stijl.

Piet Mondriaan
Piet Mondriaan was een Nederlandse schilder die beschouwd wordt als een van de grote kunstenaars van de 20e eeuw. Sommige van Mondriaans kunstwerken hadden een unieke, geometrische stijl.

Bij een Mondriaanse kunstpuzzel moet je een vierkant $$n \times n$$ rooster ($$n$$ rijen en $$n$$ kolommen) betegelen met niet-overlappende rechthoeken die elk een aantal vakjes van het rooster bedekken.

De afmetingen van een $$h \times b$$ rechthoek worden beschreven met twee natuurlijke getallen: het aantal rijen $$h$$ en het aantal kolommen $$b$$ van het rooster die door de rechthoek bedekt worden. De oppervlakte van een $$h \times b$$ rechthoek is gelijk aan het aantal bedekte vakjes: $$h \times b$$. Het is niet toegelaten om twee rechthoeken met dezelfde afmetingen te gebruiken: als je een deel van het rooster bedekt met een $$h \times b$$ rechthoek, dan mag de betegeling geen andere $$h \times b$$ of $$b \times h$$ rechthoeken meer gebruiken. Een betegeling mag wel twee rechthoeken met dezelfde oppervlakte hebben.

De score van een betegeling is gelijk aan de oppervlakte van de grootste rechthoek min de oppervlakte van de kleinste rechthoek. Het doel is om een betegeling te vinden met een zo klein mogelijke score.

Dit is bijvoorbeeld een betegeling van een $$10 \times 10$$ rooster met score $$42 - 7 = 35$$.

10x10 puzzel met score 35
Betegeling van een $$10 \times 10$$ rooster met score $$42 - 7 = 35$$.

Er zijn echter betere betegelingen mogelijk. Dit is bijvoorbeeld een betegeling met score $$30 - 7 = 23$$. Dat is nog steeds suboptimaal, maar al beter dan wat we hadden.

10x10 puzzel met score 23
Betegeling van een $$10 \times 10$$ rooster met score $$30 - 7 = 23$$.

Merk op dat de $$4 \times 3$$ rechthoek en de $$6 \times 2$$ rechthoek weliswaar dezelfde oppervlakte hebben ($$12$$), maar dit is een geldige betegeling omdat ze verschillende afmetingen hebben. Er is echter nog een betere betegeling met score $$25 - 7 = 18$$.

10x10 puzzel met score 18
Betegeling van een $$10 \times 10$$ rooster met score $$25 - 7 = 18$$.

Onderstaande betegeling lijkt de beste die we tot nu toe gevonden hebben, maar dat is ze niet. Ze voldoet immers niet aan de regel dat een betegeling geen rechthoeken met dezelfde afmetingen mag hebben. De betegeling heeft een $$4 \times 3$$ rechthoek en een $$3 \times 4$$ rechthoek, en dat is niet toegelaten. De $$5 \times 2$$ rechthoek en de $$1 \times 10$$ rechthoek hebben dezelfde oppervlakte ($$10$$), maar omdat ze verschillende afmetingen hebben is dat geen inbreuk tegen de regels voor betegelingen.

10x10 puzzel met ongeldige betegeling
Ongeldige betegeling van een $$10 \times 10$$ rooster: ze voldoet niet aan de regel dat een betegeling geen rechthoeken met dezelfde afmetingen mag hebben. De betegeling heeft een $$4 \times 3$$ rechthoek en een $$3 \times 4$$ rechthoek, en dat is niet toegelaten. De $$5 \times 2$$ rechthoek en de $$1 \times 10$$ rechthoek hebben dezelfde oppervlakte (10), maar omdat ze verschillende afmetingen hebben is dat geen inbreuk tegen de regels voor betegelingen.

Als bonus hebben we alle rechthoeken ingekleurd met zo weinig mogelijk kleuren, zodat twee rechthoeken met dezelfde kleur nooit een gemeenschappelijk zijde of een gemeenschappelijk hoekpunt hebben.

Opgave

Definieer een klasse Mondriaan waarmee we vierkante $$n \times n$$ roosters ($$n$$ rijen en $$n$$ kolommen) kunnen betegelen volgens de regels van een Mondriaanse kunstpuzzel. Bij het aanmaken van een nieuwe betegeling (Mondriaan) moet de dimensie $$n$$ (int) van het rooster doorgegeven worden.

Om naar de vakjes in het rooster te kunnen verwijzen, nummeren we de rijen van het rooster van boven naar onder en de kolommen van links naar rechts, telkens vanaf nul. Op die manier kunnen we de positie van een vakje voorstellen als een tuple $$(r, k)$$, met $$r$$ het nummer van de rij ($$0 \leq r < n$$) en $$k$$ het nummer van de kolom ($$0 \leq k < n$$) waarop het vakje gelegen is in het rooster.

Het moet mogelijk zijn om rechthoeken toe te voegen aan en te verwijderen uit een betegeling. Bij het toevoegen van een rechthoek aan een betegeling, wordt die rechthoek aangeduid met de eerste niet-gebruikte hoofdletter van het alfabet. Op een betegeling $$\mathcal{M}$$ (Mondriaan) moet je minstens de volgende methoden kunnen aanroepen:

Als er een betegeling $$\mathcal{M}$$ (Mondriaan) wordt doorgegeven aan de ingebouwde functie str, dan moet die een stringvoorstelling (str) van het rooster van betegeling $$\mathcal{M}$$ teruggeven. Daarin is elke rij van het rooster een afzonderlijke regel, en wordt elk vakje op de rij voorgesteld door de hoofdletter waarmee de rechthoek die het vakje bedekt wordt aangeduid in betegeling $$\mathcal{M}$$, of een onderstrepingsteken (_) als het vakje niet door een rechthoek bedekt wordt in betegeling $$\mathcal{M}$$.

Voorbeeld

In deze interactieve sessie gebruiken we de optimale betegeling van een $$10 \times 10$$ rooster met score 18 uit de inleiding als voorbeeld om te illustreren hoe de klasse Mondriaan moet kunnen gebruikt worden.

>>> puzzel = Mondriaan(10)
>>> print(puzzel)
__________
__________
__________
__________
__________
__________
__________
__________
__________
__________
>>> puzzel.bedekking()
0
>>> puzzel.score()
-1
>>> puzzel.volgende_letter()
'A'
>>> puzzel.vakjes(rij=4, kolom=0, hoogte=5, breedte=2)
{(4, 0), (4, 1), (5, 0), (5, 1), (6, 0), (6, 1), (7, 0), (7, 1), (8, 0), (8, 1)}
>>> print(puzzel.rechthoek_toevoegen(rij=4, kolom=0, hoogte=5, breedte=2))
__________
__________
__________
__________
AA________
AA________
AA________
AA________
AA________
__________
>>> puzzel.bedekking()
10
>>> puzzel.score()
-1
>>> puzzel.volgende_letter()
'B'
>>> puzzel.vakjes(rij=1, kolom=3, hoogte=3, breedte=7)
{(1, 3), (1, 4), (1, 5), (1, 6), (1, 7), (1, 8), (1, 9), (2, 3), (2, 4), (2, 5), (2, 6), (2, 7), (2, 8), (2, 9), (3, 3), (3, 4), (3, 5), (3, 6), (3, 7), (3, 8), (3, 9)}
>>> print(puzzel.rechthoek_toevoegen(rij=1, kolom=3, hoogte=3, breedte=7))
__________
___BBBBBBB
___BBBBBBB
___BBBBBBB
AA________
AA________
AA________
AA________
AA________
__________
>>> puzzel.bedekking()
31
>>> puzzel.score()
-1
>>> puzzel.vakjes(rij=8, kolom=8, hoogte=3, breedte=3)                # rechthoek niet ingesloten in vierkant
Traceback (most recent call last):
AssertionError: ongeldige rechthoek
>>> puzzel.rechthoek_toevoegen(rij=8, kolom=8, hoogte=3, breedte=3)   # rechthoek niet ingesloten in vierkant
Traceback (most recent call last):
AssertionError: ongeldige rechthoek
>>> puzzel.rechthoek_toevoegen(rij=5, kolom=4, hoogte=2, breedte=5)   # andere rechthoek heeft dezelfde afmetingen
Traceback (most recent call last):
AssertionError: ongeldige rechthoek
>>> puzzel.rechthoek_toevoegen(rij=2, kolom=1, hoogte=3, breedte=3)   # rechthoek overlapt met andere rechthoek
Traceback (most recent call last):
AssertionError: ongeldige rechthoek
>>> print(puzzel)
__________
___BBBBBBB
___BBBBBBB
___BBBBBBB
AA________
AA________
AA________
AA________
AA________
__________
>>> puzzel.volgende_letter()
'C'
>>> print(puzzel.rechthoek_toevoegen(rij=6, kolom=7, hoogte=3, breedte=3))
__________
___BBBBBBB
___BBBBBBB
___BBBBBBB
AA________
AA________
AA_____CCC
AA_____CCC
AA_____CCC
__________
>>> puzzel.volgende_letter()
'D'
>>> print(puzzel.rechthoek_toevoegen(rij=9, kolom=0, hoogte=1, breedte=10))
__________
___BBBBBBB
___BBBBBBB
___BBBBBBB
AA________
AA________
AA_____CCC
AA_____CCC
AA_____CCC
DDDDDDDDDD
>>> puzzel.rechthoek_verwijderen('X')
Traceback (most recent call last):
AssertionError: ongeldige rechthoek
>>> puzzel.volgende_letter()
'E'
>>> print(puzzel.rechthoek_verwijderen('C'))
__________
___BBBBBBB
___BBBBBBB
___BBBBBBB
AA________
AA________
AA________
AA________
AA________
DDDDDDDDDD
>>> puzzel.volgende_letter()
'C'
>>> print(puzzel.rechthoek_toevoegen(rij=0, kolom=0, hoogte=4, breedte=3))
CCC_______
CCCBBBBBBB
CCCBBBBBBB
CCCBBBBBBB
AA________
AA________
AA________
AA________
AA________
DDDDDDDDDD
>>> puzzel.bedekking()
53
>>> puzzel.score()
-1
>>> print(puzzel.rechthoek_toevoegen(0, 3, 1, 7).rechthoek_toevoegen(4, 7, 5, 3).rechthoek_toevoegen(4, 2, 5, 5))
CCCEEEEEEE
CCCBBBBBBB
CCCBBBBBBB
CCCBBBBBBB
AAGGGGGFFF
AAGGGGGFFF
AAGGGGGFFF
AAGGGGGFFF
AAGGGGGFFF
DDDDDDDDDD
>>> puzzel.bedekking()
100
>>> puzzel.score()
18

De oplossingen die je indient, worden ook getest aan de hand van deze betegelingen.

5x5 puzzel met score 4
Betegeling van een $$5 \times 5$$ rooster met optimale score $$10 - 6 = 4$$.
6x6 puzzel met score 5
Betegeling van een $$6 \times 6$$ rooster met optimale score $$8 - 3 = 5$$.
7x7 puzzel met score 5
Betegeling van een $$7 \times 7$$ rooster met optimale score $$9 - 4 = 5$$.
8x8 puzzel met score 6
Betegeling van een $$8 \times 8$$ rooster met optimale score $$12 - 6 = 6$$.
9x9 puzzel met score 6
Betegeling van een $$9 \times 9$$ rooster met optimale score $$12 - 6 = 6$$.

Epiloog

Hoe toepasselijk: PIET MONDRIAN is een anagram van I PAINT MODERN.

Piet Mondriaan gaf ook zijn naam aan de stapelgebaseerde esotherische programmeertaal Piet1 waarin programma's eruit zien als abstracte schilderijen.

Epiloog

Voor Mondriaanse kunstpuzzels bestaan er geen snelle algoritmen om een betegeling met een minimale score te vinden. We weten wel dat elk $$n \times n$$ rooster een betegeling heeft met een score die naar boven begrensd is door \[ \left\lceil \frac{n}{\log{n}} \right\rceil + 3 \] Het is ook niet bekend of er een dimensie $$n$$ is waarvoor er een betegeling met score 0 bestaat.