Kirjautuminen

Haku

Tehtävät

Keskustelu: Yleinen keskustelu: Laskutehtävä: Saippuakauppias

Sivun loppuun

Antti Laaksonen [26.12.2006 23:38:35]

#

Vanhassa Suomen Kuvalehdessä (20-luvulta!) oli mainio laskutehtävä, josta on varmaan iloa teillekin.

Kuinka monella tavalla seuraavasta ruudukosta voi lukea sanan SAIPPUAKAUPPIAS?

SAIPPUAKAUPPIAS
AIPPUAKAUPPIASA
IPPUAKAUPPIASAI
PPUAKAUPPIASAIP
PUAKAUPPIASAIPP
UAKAUPPIASAIPPU
AKAUPPIASAIPPUA
KAUPPIASAIPPUAK
AUPPIASAIPPUAKA
UPPIASAIPPUAKAU
PPIASAIPPUAKAUP
PIASAIPPUAKAUPP
IASAIPPUAKAUPPI
ASAIPPUAKAUPPIA
SAIPPUAKAUPPIAS

Sana saa alkaa mistä tahansa, ruudukossa saa edetä pysty- ja vaakasuuntaan, ja samaan kohtaan saa mennä monta kertaa.

Vastauksia ja ratkaisutapoja saa lähettää tähän keskusteluun.

juha127 [27.12.2006 09:44:50]

#

Vaikutti aluksi helpolta, mutta kun luki eteenpäin Antin selostusta, tulinkin toisiin aatoksiin. Mutta sanon tässä pelkän vastauksen sen mitä aluksi tajusin 2. riviltä Antin viestistä

32

Onkyllä toinen vaihtoehto, mutta tämä saa kelvata aluksi, kun en oo kovin haka tämmösissä vielä ;)

Merri [27.12.2006 10:22:12]

#

Vastaan 327675, missä voi olla vähän liikaa koska en jaksa lähteä miettimään asiaa sen enempää kuin että aivan tolkuttomasti noita kuitenkin tulee, jos saa kääntyillä "mielivaltaisesti" aina lähimpään sopivaan kirjaimeen.

Muoks!
Lisämietinnällä päivitetty toistamiseen. Jaksaisikin koodata tuon selvittäjän, parempi tekemään semmoisen kuin laskemaan omassa päässä.

Merri [27.12.2006 11:26:14]

#

No ihan tyhmää, nyt tämä estää jo muokkaamisen kun sekoilen kahden vaiheilla väsyneenä :P Elikkä no, se toinen veikkaus joka oli jo jonkun aikaa aiemmin esillä on 491520.

KemXy [27.12.2006 14:40:02]

#

Itse sain pikaisella viritelmällä 680488. Liekö sitten jossain tullut virhe, en tiedä.

Antti Laaksonen [27.12.2006 17:05:39]

#

Oikeaa vastausta ei ole vielä tullut, mutta yksi on ollut aika lähellä...

Merri [27.12.2006 17:40:04]

#

No jos arvaan jälkimmäiselleni nyt sitten 491492 kun en edelleenkään jaksa koodata tarkistajaa. Tai ehkä vain teen sen ja annan laskea paremmin puolestani. Ehkä pitäisi nukkua pidemmät unet tällekin päivälle.

Jaska [27.12.2006 18:36:08]

#

32768?

VilleP [27.12.2006 22:46:25]

#

Kenties 334544?

(334544 == 2* Summa_{i käy 0:sta 7:ään} [(2^7 + (7 yli iin))^2] )

Antti Laaksonen [27.12.2006 23:14:58]

#

VilleP kirjoitti:

Kenties 334544?

Oikein meni! Aika lyhyen laskukaavankin sait aikaan.

Jaska [27.12.2006 23:16:39]

#

Olisi kiva kuulla kaavan perustelut, niin oppisin itsekin laskemaan tuollaisia kombinatoriikan tehtäviä.

VilleP [28.12.2006 00:36:32]

#

Tässäpä vieläkin lyhyempi kaava: 2^16+2^18+2*(14 yli 7:n). Tuon sain tosin perusteltua helpoiten sieventämällä alkuperäistä kaavaa, joten en sanoisi sitä varsinaiseksi parannukseksi.

Jaskalle täsmennystä, perusideana oli laskea erikseen jokaiselle K-kirjaimiselle ruudulle, kuinka monella tavalla siitä lähtien voidaan muodostaa tehtävän mukaisesti liikkumalla sana KAUPPIAS. Jos tämä lukumäärä on jollekin K-ruudulle N, niin tällöin sana SAIPPUAKAUPPIAS saadaan muodostettua (käyttämällä kyseenomaista K-ruutua SAIPPUAKAUPPIAS-sanan keskimmäisenä Koona) N^2 eri tavalla. Tämä johtuu siitä, että kullakin SAIPPUAKAUPPIAS-reitillä on ensin 'peruutettava' jotain KAUPPIASta pitkin ensimmäisestä ässästä K-kirjaimeen, ja tämän jälkeen jatkettava eteenpäin jotain KAUPPIASta (samaa tai eri kuin alussa) pitkin K:sta viimeiseen ässään.

Koska K-ruutuja on 16 kpl, saadaan lopulliseksi lukumääräksi Summa{i=0..15}[N_i ^2]. Ruudukko on pistesymmetrinen keskipisteensä suhteen, joten lkm on itse asiassa 2*Summa{i=0..7}[N_i ^2], kun K-ruudut numeroidaan ilmiselvässä järjestyksessä (ylhäältä alas, vasemmalta oikealle).

Ongelmaksi jää enää laskea erikseen kukin N_i. Tässä auttaa, kun huomataan, että siirryttäessä ruudukossa yhden ruudun verran alas tai oikealle, siirtyy vastaavan kirjaimen kohta sanassa SAIPPUAKAUPPIAS aina yhden kirjaimen verran oikealle/eteenpäin. Esim. jos kuljetaan vasemmasta ylänurkasta reittiä alas/oikea/alas/o/o/a/o/a..., ovat vastaavat kirjaimet S->A->I->P->P->U->A->K->... . Vastaavasti, kun liikutaan ruudukossa ylös tai vasemmalle, siirrytään SAIPPUAKAUPPIAS-kirjaimissa aina yhden verran taaksepäin/vasemmalle.

Helposti voi nähdä, että esimerkiksi ylhäältä laskettuna kolmannesta K-ruudusta (vastaa indeksiä i = 2) saadaan aikaiseksi oikeaoppinen KAUPPIAS-reitti ainoastaan kulkemalla joko koko ajan oikealle_tai_alas, tai vaihtoehtoisesti koko ajan vasemmalle_tai_ylös, astumatta kuitenkaan ruudukon reunan yli. Oikealle/alas -reittejä on 2^7 erilaista kappaletta ja vasemmalle/ylös -reittejä on (7 yli i) = (7 yli 2) kappaletta. Jälkimmäisen voi nähdä siitä, että ylöspäin siirtymisiä on oikeaoppisella vasemmalle/ylös -reitillä oltava tasan 2 kappaletta (jotta päästäisiin vasemman ylänurkan S-kirjaimeen asti ensimmäiselle riville), ja kaiken kaikkiaan siirtymisiä yhteensä on ko. reitillä oltava yhtä monta kuin KAUPPIAS-sanassa on kirjaimia - 1 eli 8-1=7, joten tapoja valita oikeaoppinen ylös/vasen -reitti on sama kuin tapoja valita 2 käännöstä 7:stä eli (7 yli 2). Kun tuon yleistää jokaiselle vasemman yläalueen K-kirjaimelle, saadaan alunperin esitetty kaava.

juha127 [28.12.2006 00:52:46]

#

Täytyy varmaan odotella pari vuotta että ymmärtää nuo ^^
=D

lukujenVihaaja [28.12.2006 01:00:46]

#

Tuloksen voi tarkistaa yksinkertaisella syvyyshaulla.
Optimointi ei ole mielekästä, sillä ohjelma toimii jo nyt salamannopeasti.

#include <iostream>
using namespace std;

char *word="SAIPPUAKAUPPIAS";
char *map="SAIPPUAKAUPPIAS"
          "AIPPUAKAUPPIASA"
          "IPPUAKAUPPIASAI"
          "PPUAKAUPPIASAIP"
          "PUAKAUPPIASAIPP"
          "UAKAUPPIASAIPPU"
          "AKAUPPIASAIPPUA"
          "KAUPPIASAIPPUAK"
          "AUPPIASAIPPUAKA"
          "UPPIASAIPPUAKAU"
          "PPIASAIPPUAKAUP"
          "PIASAIPPUAKAUPP"
          "IASAIPPUAKAUPPI"
          "ASAIPPUAKAUPPIA"
          "SAIPPUAKAUPPIAS";
int dfs(int x, int y, int c){ //c on nykyisen merkin indeksi sanassa 'saippuakauppias'
   if(x<0 || x>14 || y<0 || y>14 || map[y*15+x]!=word[c])
      return 0;
   if(c==14)
      return 1;
   return dfs(x-1,y,c+1) + dfs(x+1,y,c+1) + dfs(x,y-1,c+1) + dfs(x,y+1,c+1);
}

int main(int argc, char* argv[]){
   int x,y,count;
   count=0;
   for(y=0;y<15;y++){
      for(x=0;x<15;x++){
         count+=dfs(x,y,0);
      }
   }
   cout << count << endl;
}

Ja tulokseksi tulee sama 334544

Pekka Karjalainen [28.12.2006 10:17:26]

#

Pisteet VilleP:lle ja lukujenVihaajalle selkeistä ratkaisuista.

Mahdollinen lisätehtävä tuli mieleeni.

Polut voi luokitella sen mukaan, kuinka monta käännöstä niissä tehdään. Käännös on sellainen kohta polussa, jossa siirrytään eri suuntaan kuin edellisellä kerralla. Aivan ensimmäinen valinta ei voi olla käännös, joten SAIPPUAKAUPPIAS-sanan pituuden 15 nojalla käännöksiä voidaan tehdä 0-14 kpl. (Jos käännöksiä on nolla, on silloin jokainen siirtyminen samaan suuntaan.)

Jos ei tehdä yhtään käännöstä, pitää aloittaa nurkasta ja mennä toiseen, koska reunalla on S-kirjain vain nurkissa ja 15x15-taulukossa 15:n pituisen suoran täytyy koskea reunoja. Näitä mahdollisuuksia on kaksi per nurkka, eli yhteensä 8.

Mitenkä sitten jatketaan? Montako erilaista polkua on käännöksien lukumäärän ollessa 1-14?

(näitä arvoja voi nimittää n:nen asteen madonluvuiksi...)

Jaska [28.12.2006 13:25:30]

#

Juu. Noinhan tuo meneekin. On kyllä kombinatoriikan taidot päässeet rapistumaan, kun ei ole YO-kirjoitusten jälkeen kovinkaan paljoa tarvinnut.

Pekka Karjalainen [28.12.2006 16:30:45]

#

N:n pituisessa madossa on tietenkin [0..n-2] mutkaa, eli 0-13 on tehtävän väli. Huoh.

Jaska [28.12.2006 17:07:08]

#

juha127 kirjoitti:

Täytyy varmaan odotella pari vuotta että ymmärtää nuo

Tai sitten ei tarvitse. Kokemuksesta voin sanoa, että vaikeitakin asioita voi opiskella nopeasti, jos on motivaatiota ottaa asioista selvää. Voithan kysella kohdista, jotka tuntuvat vaikeilta vaikka tällä palstalla. Joku voi selventää noita juttuja jo parin minuutin päästä, ja opit asioita paljon nopeammin kuin odottamalla lukion kombinatoriikan kurssia. Minä olen aina opiskellut paljon matematiikkaa etukäteen ja mielestäni se on kannattanut.

lukujenVihaaja [28.12.2006 18:02:39]

#

Kopeekka:

n       Madonluku
------------------
0       8
1       172
2       1392
3       6560
4       20040
5       42596
6       65456
7       74392
8       62488
9       38484
10      16896
11      5064
12      920
13      76

KemXy [28.12.2006 19:32:34]

#

Juu, mitätön, mutta tuhoisa kirjoitusvirhehän se vääristi ohjelmani tuloksen. Todellinen ratkaisu on kuitenkin juuri kynän ja peperin avulla, kuten varmaan tuolloin 20-luvulla. :)

Pekka Karjalainen [30.12.2006 16:32:32]

#

Kiitän lukujenVihaajaa ongelmaani tarttumisesta. En keksinyt hyvää kombinatorista tapaa ratkaista ongelmaani paperilla, joten esitän vain ohjelmallisen ratkaisun. Jos joku keksii elegantin tavan (tuon VilleP:n esittämän kaltaisen), kertokoon.

Voi olla, että sellaista ei ole.

Tässä on Haskell-koodini. Se on kauheaa minustakin. :-)

-- Madonluvut.hs -- Pekka Karjalainen 2006
-- lisenssi: public domain
-- yksinkertainen ratkaisu madonluvut-ongelmaan
-- joka esiintyi Ohjelmointiputkan keskustelussa
-- (Pekan itse esittamana)

module Main where

import Data.Array
import Data.List

data Suunta = Ylos | Vas | Oik | Alas deriving (Eq, Show)
data Paikka = Paikka Int Int
data Rajat  = Rajat (Int, Int) (Int, Int)

kaikkiSuunnat = [Ylos, Vas, Oik, Alas]

onkoRajoissa (Rajat (minx, maxx) (miny, maxy)) (Paikka x y) =
    minx <= x && x <= maxx && miny <= y && y <= maxy

astu (Paikka x y) s = case s of
    Ylos -> Paikka x (y-1)
    Vas  -> Paikka (x-1) y
    Oik  -> Paikka (x+1) y
    Alas -> Paikka x (y+1)

-- taman voisi jotenkin kirjoittaa paremmin, mutta kun ei keksi miten
vaihtojenLkm []    = error "Vilahti nulli-lista tuloksiin!"
vaihtojenLkm lista = length $ filter (uncurry (/=)) $ zip lista (tail lista)

-- indeksini alkavat yhdesta
indeksiksi (Paikka x y) = (y-1)*15+x

kirjaimet = "SAIPPUAKAUPPIAS"
pituus    = length kirjaimet
pitNelio  = pituus*pituus

sana  = listArray (1,pituus) kirjaimet
taulu = listArray (1,pitNelio) $ take (pitNelio) toisto
    where toisto = (init kirjaimet) ++ toisto

oikeaPaikka pari syvyys = taulu ! i == sana ! syvyys
    where i = indeksiksi pari

onkoTaulussa = onkoRajoissa $ Rajat (1,pituus) (1,pituus)

-- tehdaanpa sitten negaatiot kuitenkin
eiOikeaPaikka pari syvyys = not $ oikeaPaikka pari syvyys

eiTaulussa = not . onkoTaulussa

bfs paikka suunnat syvyys
    | eiTaulussa paikka           = [[]]
    | eiOikeaPaikka paikka syvyys = [[]]
    | syvyys == pituus            = [suunnat]
    | otherwise                   = syvenna
    where syvenna = [seuraava | s <- kaikkiSuunnat,
                     let p = astu paikka s,
                     seuraava <- bfs p (s:suunnat) (syvyys+1),
                     not $ null seuraava ]

-- tassa ratkaisussa on pieni puute - nimittain se palauttaa nulli-listoja
-- kun aloitetaan paikasta, jossa ei ole S-kirjainta. muuten nulli-osalistat
-- karsitaan haussa pois, joten listat ovat joko taydellisia 14:n suunnan
-- listoja tai nulleja.

-- ratkesi sitten niin, että filtteroin alla (not . null):in avulla ne veks,
-- mutta ratkaisua ei voi pitaa kovin eleganttina. kelvatkoon minulle.

alut     = map ($[]) [ bfs $ Paikka x y | x <- [1..15], y <- [1..15]]
tulokset = filter (not . null) $ concatMap ($1) alut
mutkat   = map vaihtojenLkm tulokset
tilasto  = map length $ group (sort mutkat)

otsikko = "n\tMadonluku\n" ++
          "------------------"

tulostus a b = show a ++ "\t" ++ show b

main = do putStrLn otsikko;
          mapM putStrLn $ zipWith tulostus [0..] tilasto

-- loppui vihdoin!

Saattaapa huomata, että korjailin sen tulostusmuotoilua ennen julkaisua, koska tulostus käy näin hyvin yhteen lV:n esittämään (riippuu tietenkin miten nuo tabit siirtyvät tänne Putkan keskusteluun). Tässä on tulostus ajosta. Meni pieni hetki, en jaksanut mitata montako sekuntia.

n       Madonluku
------------------
0       8
1       172
2       1392
3       6560
4       20040
5       42596
6       65456
7       74392
8       62488
9       38484
10      16896
11      5064
12      920
13      76

Summaksi tulee tietenkin tuo ym. 334544, jonka jokainen voi itse tarkistaa.

MUOKKAUS: oli jotain ohjelman siirtelyn aiheuttamia skandivammoja, jotka toivottavasti nyt on kaikki korjattu. Siitä johtuu kummallinen äö-kirjainten puute suurimmalta osalta. Ja sitten selvisi, että ne olisivat toimineet oikein täällä. Grr tuota MS-DOS-lootaa.


Sivun alkuun

Vastaus

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

Tietoa sivustosta