Kirjautuminen

Haku

Tehtävät

Keskustelu: Yleinen keskustelu: Taikaneliöt ja -kuutiot

Sivun loppuun

Antti Laaksonen [20.05.2004 14:26:04]

#

Kirjastossa satuin lukemaan Tiede-lehteä ja huomasin siinä mielenkiintoisen uutisen uudesta löytyneestä taikakuutiosta (tämä tosin oli oikeasti tapahtunut jo puoli vuotta sitten). Taikaneliö on neliönmuotoinen ruudukko, jonka ruutuihin on sijoitettu numerot yhdestä eteenpäin. Erikoista on se, että kaikkien pysty-, vaaka- ja vinorivien summa on sama. Taikakuutio taas on sama juttu kolmiulotteisena: summa on sama, vaikka numerot lukisi missä suunnassa tahansa.

Äskettäin löytyneen kuution sivunpituus on 5, ja se on pienin mahdollinen taikakuutio. Kuution olemassaolo oli kiehtonut matemaatikkoja 150 vuoden ajan, suurempia kuutiota tunnettiin ennestäänkin. Kuution löysivät saksalainen Walter Trump ja ranskalainen Christian Boyer, ja laskemiseen kului viideltä tietokoneelta monta viikkoa.

Asiaan liittyviä linkkejä:
http://www.tiede.fi/keskustelut/keskustelu.asp?id=1243066&alue=1
http://www.zeit.de/2003/50/N-Mathew_9frfel
http://mathworld.wolfram.com/news/2003-11-18/magiccube/
http://www.multimagie.com/English/Perfectcubes.htm

Rupesin myös miettimään, kuinka taikaneliöitä tai -kuutioita kannattaisi etsiä tietokoneella. Johdin ensin kaavan, jolla yhden rivin numeroiden vaaditun summan voi laskea:

s = (p ^ (u + 1) + p) / 2, jossa p = sivunpituus ja u = ulottuvuudet
Esim. 4x4-neliön summa on siis (4 ^ (2 + 1) + 4) / 2 = 34 ja 5x5x5-kuution summa on (5 ^ (3 + 1) + 5) / 2 = 315.

Tein sitten yksinkertaisen ohjelman, joka muodostaa kaikki mahdolliset neliöt ja näyttää ne, joiden numeroiden summat ovat oikeat. 3x3-neliöllä ohjelma toimi ihan hyvin, laskeminen kesti kymmenisen sekuntia. Muodostettavien yhdistelmien määrä kuitenkin kasvaa aivan liian nopeasti. 3x3-neliöllä niitä on 9! eli 362880, 4x4-neliöllä 16! eli 20922789888000 ja esim. 5x5x5-kuutiolla 125! eli hirveän paljon. Jo kaikkien 4x4-neliöiden laskemiseen tällä tavalla kuluisi aikaa 10 s * (16! / 9!) eli yli 1579660 vuotta!

Tarvitaan siis paljon parempi algoritmi. Eräs helppo parannus liittyy siihen, että kun tarpeeksi moni numero on tiedossa, muut voidaan laskea niiden perusteella. Esim. 3x3-neliössä täytyy laskea vain kolme numeroa, jotta muut voidaan päätellä. 4x4-neliössä vastaava luku näyttäisi olevan kahdeksan.

Muita ohjelmia en kuitenkaan ole vielä tehnyt. Olisikin kiva kuulla, jos joku muukin on koettanut tehdä tällaisia ohjelmia, millä algoritmeilla ja millä menestyksellä?

Linkku [20.05.2004 15:05:48]

#

Mielenkiintoista.. Pitää jossain vaiheessa lukea ihan ajan kanssa.

efteri [20.05.2004 15:08:59]

#

No joo voisihan sitä lukea jos jaksaa...

peki [20.05.2004 15:41:11]

#

Aihe rupesi kiinnostamaan.
Kertoisitko, miten johdit kyseiset kaavat?

Edit: Tuli tässä mieleen, että ongelman ratkaisua voisi lähestyä näin:
esim 3x3 neliö(rivin arvo 15):
taulukoidaan kaikki luku triot luvuista jotka ovat 0 < x <= 9(3*3), jotka muodostavat luvun 15.
Sitten sovitetaan ne yhteen, niin kuin rsitisanaa tehdessä.

hunajavohveli [20.05.2004 16:03:57]

#

On kyllä mielenkiintoinen aihe. Voisin yrittää väsätä jonkinmoisen algoritmin ainakin tuolle neliölle. Kuutiosta en sitten vielä tiedä. Ja muuten, kai matikassa voi laskea myös neliulotteisesti, vaikkei sellaista muotoa pystykään ajattelmaan, koska sellaista ei voi olla.

jcd3nton [20.05.2004 16:10:32]

#

hunajavohveli kirjoitti:

... kai matikassa voi laskea myös neliulotteisesti, vaikkei sellaista muotoa pystykään ajattelmaan, koska sellaista ei voi olla.

Monimutkaista =S

Antti Laaksonen [20.05.2004 16:27:24]

#

peki kirjoitti:

Kertoisitko, miten johdit kyseiset kaavat?

Numeroiden määrä (ja suurin numero) on p ^ u. Numeroiden summa voidaan sen vuoksi laskea lausekkeella ((p ^ u) * (p ^ u + 1)) / 2. Erillisten rivien määrä (kaikki numerot kuuluvat yhteen mutta vain yhteen riviin) taas on p ^ (u - 1). Tästä saadaan lauseke, jota voidaan sieventää: ((p ^ u) * (p ^ u + 1)) / (2 * p ^ (u - 1)) = (p * (p ^ u + 1)) / 2 = (p ^ (u + 1) + p) / 2

Kertoma tulee tietysti siitä, että esim. 3x3-neliössä ensimmäinen numero voidaan laittaa yhdeksään paikkaan, toinen kahdeksaan, kolmas seitsemään jne., ja viimeinen sitten vain yhteen paikkaan. Näin saadaan yhdistelmien määrä.

peki kirjoitti:

taulukoidaan kaikki luku triot luvuista jotka ovat 0 < x <= 9(3*3), jotka muodostavat luvun 15.
Sitten sovitetaan ne yhteen, niin kuin rsitisanaa tehdessä.

Kuulostaa ihan hyvältä, kokeiletko tehdä ohjelmaa tähän perustuen?

peki [20.05.2004 19:11:00]

#

Voisin kokeilla.
Luku triojen muodostaminen on helppoa, mutta se triojen yhdistäminen saattaa tuottaa hankaluuksia.

peki [20.05.2004 20:00:20]

#

Enpä tuota osannut tehdä.
Olisi pitänyt koodata tekoäly, joka yhdistelee trioja älykkäästi.
Ei onnistunut.
Edit: Myös triojen muodostamisessa on ongelmia.
tässä koodi:

Dim i, j, a, b As Integer
Dim index As Integer
Dim olemassajo As Boolean = False
Dim triot(900, 2) As Integer
For i = 1 To 9
    For j = 1 To 9
        For a = 1 To 9
            If i + j + a = 15 Then
                If i <> j And i <> a And j <> i And j <> a Then
                    ' Jos trio jo olemassa(luvut eri järjestyksessä)
                    For b = 0 To index
                        If triot(b, 0) = i Or triot(b, 0) = j Or triot(b, 0) = a Then
                            If triot(b, 1) = i Or triot(b, 1) = j Or triot(b, 1) = a Then
                                If triot(b, 2) = i Or triot(b, 2) = j Or triot(b, 2) = a Then
                                    olemassajo = True
                                End If
                            End If
                        End If
                    Next
                    If olemassajo = False Then
                        triot(index, 0) = i
                        triot(index, 1) = j
                        triot(index, 2) = a
                        index += 1
                    End If
                    olemassajo = False
                End If
            End If
        Next
    Next
Next

Tuossa on joku käsittämätön ongelma.

Antti Laaksonen [20.05.2004 21:42:52]

#

Yhdistelmien muodostamisen tekisin ainakin itse rekursiivisella eli itseään kutsuvalla aliohjelmalla, koska nuo sisäkkäiset silmukat ovat vähän hankalia. Silloin myös on helppoa muodostaa eri pituisia yhdistelmiä.

peki [20.05.2004 22:09:31]

#

Enpä taida tuota osata rekursiolla tehdä.
Pitää koettaa, kun on enemmän aikaa :(

Sami [20.05.2004 23:07:54]

#

Yritysten määrää voisi pudottaa melko hyvin myös sellaisella optimoinnilla, ettei se laske erikseen neliöitä, jotka ovat toistensa peilikuvia tai jotka saadaan kiertämällä jotain muuta kuviota.
Esimerkki parista peilikuvasta/kierrosta:
1 2 3
4 5 6 (alkutila)
7 8 9
-------
7 4 1
8 5 2 (kierretty 90 astetta oikealle)
9 6 3
-------
3 2 1
6 5 4 (peilattu pystyakselin suhteen)
9 8 7

Antti Laaksonen [21.05.2004 07:50:55]

#

Hyvä huomio. Esim. noita 3x3-neliöitä ohjelma löysi kahdeksan, mutta kaikki olivat toistensa kiertoja tai peilikuvia. Itse asiassa 3x3-neliössä taitaa riittää, että numeron 1 paikka on kulmassa, sivulla ja keskellä. Yksistään tämä pienentää ohjelman suoritusajan kolmannekseen.

jcd3nton [21.05.2004 12:36:36]

#

Ei mulla oikeen onnistu =/ 3x3 tein päässä ja menikin ekalla yrityksellä läpi mutta 4x4 ei enää onnistu... ja jos noin huonosti menee niin turha edes yrittää jotain laskukaavaa tietokoneelle tehdä...

Antti Laaksonen [21.05.2004 13:22:43]

#

Mainiossa Antero Vipusessa on eräs esimerkki 4x4-taikaneliöstä.

16 3  2  13
5  10 11 8
9  6  7  12
4  15 14 1

Paitsi että vaaka- ja pystyrivien sekä lävistäjien summa on 34, keskellä olevan pienemmän neliön lukujen ja nurkissa olevien lukujen summa on myös 34. Ja jos neliö jaetaan neljään pienempään neliöön, on niiden kaikkien lukujen summa 34. Tämä neliö on osa Albrecht Dürerin kuparipiirrosta, joka valmistui vuonna 1514 (sama luku löytyy alimmalta riviltä).

hunajavohveli [21.05.2004 14:39:44]

#

Antti Laaksonen kirjoitti:

Mainiossa Antero Vipusessa on eräs esimerkki 4x4-taikaneliöstä.

Enpä tuota muistanutkaan, vaikka minulla tuo kirja onkin. Pitää katsoa.

peki [23.05.2004 16:26:58]

#

Olen keksinyt tällaisen keinon(oli turnauksessa vähän vapaa-aikaa :D)

Tässä ratkaisu 3x3 neliöön.
Ensiksi muodostetaan jokaisesta luvusta niin monta trioa, kuin mahdollista:

ensimmäisen trion rajoitukset(pätee kaikkiin trioihin, kunhan ykköstä kasvatetaan aina sopivaan indeksiin)
1 + x => 5 ja 1 + x <= 15 (suurempi kuin viisi, koska triossa on 3 numeroa,
ja 1, 9 on kymmenen, joten kolmannen numeron on oltava vähintään 5)

1:n triot:

1, 9, 5
1, 8, 6		kaksi trioa -> "sivupala"

2:n triot

2, 7, 6
2, 8, 5
2, 4, 9		kolme trioa -> "kulmapala"

3:n triot

3, 4, 8
3, 5, 7		kaksi trioa -> "sivupala"

4:n triot

4, 2, 9
4, 3, 8
4, 5, 6		kolme trioa -> "kulmapala"

5:n triot

5, 1, 9
5, 2, 8
5, 3, 7
5, 4, 6		neljä trioa -> "keskuspala"

6:n triot

6, 1, 8
6, 2, 7
6, 4, 5		kolme trioa -> "kulmapala"

7:n triot

7, 2, 6
7, 3, 5		kaksi trioa -> "sivupala"

8:n triot

8, 1, 6
8, 2, 5
8, 3, 4		kolme trioa -> "kulmapala"

9:n triot

9, 1, 5
9, 2, 4		kaksi trioa -> "sivupala"

Nyt tiedämme, mihin jokaiset numerot kuuluvat.
Sovitetaan ne paikalleen:

koska 5 kuuluu keskelle -> laitetaan se keskelle.

 _____
|_|_|_|
|_|5|_|
|_|_|_|

etsitään trio, jossa on numero viisi, ja on "sivupala"

 _____
|_|3|_|
|_|5|_|
|_|7|_|

etsitään toinen trio, jossa on numero viisi, ja on "sivupala"

 _____
|_|3|_|
|1|5|9|
|_|7|_|

etsitään trio, jossa on numero viisi, ja on "kulmapala"

 _____
| |3|4|
|1|5|9|
|6|7|_|

etsitään toinen trio, jossa on numero viisi, ja on "kulmapala"

 _____
|8|3|4|
|1|5|9|
|6|7|2|

VALMIS!!

Tietokoneella voisi tehdä reittipuut, ja valita niistä reitin, joka johtaa tähän tulokseen.

Edit: Toivottavasti ei ole liian pitkä.

ez [23.05.2004 17:23:53]

#

Laskemiseen kuluvaa aikaa voidaan vähentää, jos lasketaan monella koneella.. Meilla on koulun Labrassa 15*2000Mhz konetta, joille voisin antaa tehtäväksi laskea 4x4-neliön. Vertaa Antti sun koneen tehoihin ja laske kauanko menisi...

Antti Laaksonen [23.05.2004 18:02:17]

#

peki kirjoitti:

Tässä ratkaisu 3x3 neliöön.
Ensiksi muodostetaan jokaisesta luvusta niin monta trioa, kuin mahdollista:

Hieno ratkaisutapa, ja tuohon ei tarvita edes tietokonetta. Suurempienkin neliöiden ratkaisuun tuosta on varmaan myös apua, joskaan paikat eivät silloin selviä yhtä helposti.

ez kirjoitti:

Meilla on koulun Labrassa 15*2000Mhz konetta, joille voisin antaa tehtäväksi laskea 4x4-neliön. Vertaa Antti sun koneen tehoihin ja laske kauanko menisi...

Suoraan megahertsien perusteella nopeutus olisi kymmenkertainen, eli laskuaika olisi "vain" 157966 vuotta. Tämä siis sillä hitaimmalla algoritmilla, jota ei tietenkään kannata käyttää.

peki [23.05.2004 18:03:56]

#

En tuota omaa keinoani osaa toteuttaa rekursiolla. Onko se mahdollista toteuttaa silmukoin? Jos on, niin miten?
Menee tuon algoritmin koodaaminen vähän yli hilseen.

tn [23.05.2004 21:18:53]

#

Tämä laskee kaikki 4x4 -neliön ratkaisut omalla koneellani (P3 500Mhz) alle kolmeen sekuntiin. 5x5 -neliöllä en päässyt ensimmäistä pistettä (testaa niin valkenee) pidämmälle. Koodi on valitettavasti kommentoimatonta ja optimoimisen varaakin kyllä vielä olisi. Kääntyy ainakin Dev-c++ :lla mutta luulisin toimivan muillakin ongelmitta.

#include <iostream>
#include <cstdlib>
#include <ctime>

const int sivu = 3;  //neliön sivun pituus

const int ruutuja = sivu * sivu;
const int summa = ((ruutuja/2)*(ruutuja+1)+(ruutuja%2)*(ruutuja/2+1)) / sivu;
int maara = 0;
bool kaytetty[ruutuja];
int nelio[ruutuja];
int ratkaisu[ruutuja];
int jarjestys[ruutuja];
int xNum[sivu], yNum[sivu], dNum1 = -1, dNum2 = -1;
int xSum[sivu], ySum[sivu], dSum1 = 0, dSum2 = 0;
//  xSum:   -
//  ySum:   |
//  dSum1:  \
//  dSum2:  /
int min[sivu], max[sivu];


void Alusta()
{
    for( int i = 0; i < ruutuja; ++i )
        kaytetty[i] = false;
    for( int i = 0; i < sivu; ++i )
    {
        xSum[i] = ySum[i] = 0;
        xNum[i] = yNum[i] = -1;
    }

    min[sivu-1] = 0;
    max[sivu-1] = 0;
    for( int i = 1; i < sivu; ++i )
    {
        min[sivu-i-1] = min[sivu-i] + i;
        max[sivu-i-1] = max[sivu-i] + ruutuja + 1 - i;
    }

    int r = 0;
    for( int e = 0; e < sivu; ++e )
        jarjestys[r++] = e;
    for( int e = 1; e < sivu; ++e )
        jarjestys[r++] = e*sivu;
    for( int e = 1; e < sivu-1; ++e )
        jarjestys[r++] = e*sivu+sivu-e-1;
    for( int i = 1; i < sivu; ++i )
    {
        for( int e = i; e < sivu; ++e )
            if( i+e+1 != sivu )
                jarjestys[r++] = i*sivu+e;
        for( int e = i+1; e < sivu; ++e )
            if( i+e+1 != sivu )
                jarjestys[r++] = e*sivu+i;
    }
}


void EtsiTaikaneliot(int num)
{
    int sarake = jarjestys[num] % sivu;
    int rivi = jarjestys[num] / sivu;

    int xSTmp = xSum[rivi];
    int ySTmp = ySum[sarake];
    int dSTmp1 = dSum1;
    int dSTmp2 = dSum2;
    int & xS = xSum[rivi];
    int & yS = ySum[sarake];

    int dNTmp1 = dNum1;
    int dNTmp2 = dNum2;
    int & xN = xNum[rivi];
    int & yN = yNum[sarake];

    ++xN;
    ++yN;
    if( rivi == sarake )
        ++dNum1;
    if( rivi == sivu-sarake-1 )
        ++dNum2;

    int xMin = min[xN];
    int yMin = min[yN];
    int dMin1 = min[dNum1];
    int dMin2 = min[dNum2];
    int xMax = max[xN];
    int yMax = max[yN];
    int dMax1 = max[dNum1];
    int dMax2 = max[dNum2];

    for( int i = 1; i <= ruutuja; ++i )
    {
        if( !kaytetty[i-1] )
        {
            nelio[jarjestys[num]] = i;

            xS = xSTmp + i;
            if( xS+xMin > summa || xS+xMax < summa )
                continue;

            yS = ySTmp + i;
            if( yS+yMin > summa || yS+yMax < summa )
                continue;

            dSum1 = dSTmp1;
            if( rivi == sarake )
            {
                dSum1 += i;
                if( dSum1+dMin1 > summa || dSum1+dMax1 < summa )
                    continue;
            }

            dSum2 = dSTmp2;
            if( rivi == sivu-sarake-1 )
            {
                dSum2 += i;
                if( dSum2+dMin2 > summa || dSum2+dMax2 < summa )
                    continue;
            }

            if( num < ruutuja-1 )
            {
                kaytetty[i-1] = true;
                EtsiTaikaneliot(num+1);
                kaytetty[i-1] = false;
            }else{
                //ratkaisu löytyi -> tässä vaiheessa hoidettaisiin tulostus
                ++maara;
                memcpy(ratkaisu, nelio, ruutuja*sizeof(int));
            }
        }

        if( num == 1 )
            std::cout << '.';
        if( num == 0 )
            std::cout << " Testattu " << i << ", J\x84ljell\x84 " << ruutuja-i << std::endl;
    }

    xS = xSTmp;
    yS = ySTmp;
    dSum1 = dSTmp1;
    dSum2 = dSTmp2;

    --xN;
    --yN;
    dNum1 = dNTmp1;
    dNum2 = dNTmp2;
}


int main()
{
    Alusta();
    std::cout << "Rivin summa on " << summa << std::endl;

    std::cout << "Etsit\x84\x84n ratkaisuja:\n";
    int aikaAlussa = clock();
    EtsiTaikaneliot(0);
    int aikaLopussa = clock();
    int aika = aikaLopussa - aikaAlussa;

    std::cout << "\n\nValmis. Ratkaisuja l\x94ytyi " << maara << " (" << maara/8
            << " erilaista) kpl.\n\n";
    std::cout << "Viimeisin l\x94ydetty ratkaisu oli:\n";
    for( int i = 0; i < ruutuja; ++i )
    {
        std::cout.width(3);
        std::cout << ratkaisu[i];
        if( i%sivu == sivu-1 )
            std::cout << std::endl;
    }
    std::cout << "\nAikaa kului " << aika/3600000 << "h " << (aika/60000)%60 << "min "
            << (aika/1000)%60 << '.' << (aika/100)%10 << (aika/10)%10 << aika%10 << "s.\n\n";

    system("pause");
}

peki [23.05.2004 21:33:14]

#

Monimutkaista koodia! Täytyy tuohon perehtyä oikein ajan kanssa.

makeuu [23.05.2004 22:39:04]

#

Tästä voisi tehdä esimerkiksi koodivinkin?

Antti Laaksonen [23.05.2004 23:18:34]

#

Hyvin tuntui toimivan koodi, olisi kiva kuulla vähän tarkemmin sen toimintaperiaatteesta. Seuraava haaste on järkevässä ajassa 5x5-taikaneliöiden etsiminen, joita lienee melko suuri määrä: 3x3-neliöitä on vain yksi, mutta 4x4-neliöitä jo 880 erilaista.

Jaska [26.05.2004 17:31:55]

#

Erilaisia 5x5 taikaneliöitä on 275305224 kappaletta (tässä taikaneliöt ovat erilaisia vaikka toinen saataisiin toisesta permutoimalla sarakkeita tai rivejä) joten siitä vain etsimään!


Sivun alkuun

Vastaus

Aihe on jo aika vanha, joten et voi enää vastata siihen.

Tietoa sivustosta